diff --git a/example-weather-bot/weather-bot.tines b/example-weather-bot/weather-bot.tines new file mode 100644 index 0000000..2cf1665 --- /dev/null +++ b/example-weather-bot/weather-bot.tines @@ -0,0 +1,263 @@ +{ + "schema_version": 26, + "standard_lib_version": 83, + "action_runtime_version": 35, + "name": "WeatherBot BSidesSF 2025 Example", + "description": null, + "guid": "3de2282bcaac247d34009b0245c316ba", + "slug": "weatherbot_bsidessf_2025_example", + "agents": [ + { + "type": "Agents::HTTPRequestAgent", + "name": "Ask OpenAI To Search the Weather", + "disabled": false, + "description": "Created from cURL command", + "guid": "a993f2b62e8d0a3d1ef2f855bece603b", + "origin_story_identifier": "cloud:cf89515d7caa9b49006ad5a414bdd380:764abda0a56dc2745376a76b34c616c8", + "options": { + "url": "https://api.openai.com/v1/responses", + "method": "post", + "content_type": "application_json", + "payload": { + "model": "gpt-4.1-mini", + "input": [ + { + "role": "system", + "content": [ + { + "type": "input_text", + "text": "You answer requests about the weather, taking in a location as a string and responding in JSON with your answer." + } + ] + }, + { + "role": "user", + "content": [ + { + "type": "input_text", + "text": "<>" + } + ] + } + ], + "text": { + "format": { + "type": "json_schema", + "name": "weather_forecast", + "strict": true, + "schema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The location for which the weather is being forecasted." + }, + "today_forecast": { + "type": "object", + "description": "Today's forecasted weather.", + "properties": { + "temperature": { + "type": "number", + "description": "The temperature for today." + }, + "conditions": { + "type": "string", + "description": "The weather conditions for today (e.g. sunny, rainy)." + }, + "humidity": { + "type": "number", + "description": "The humidity level for today." + } + }, + "required": [ + "temperature", + "conditions", + "humidity" + ], + "additionalProperties": false + }, + "week_forecast": { + "type": "array", + "description": "Weather forecast for the remainder of the week.", + "items": { + "type": "object", + "properties": { + "day": { + "type": "string", + "description": "The day of the week for the forecast." + }, + "high": { + "type": "number", + "description": "The high temperature for the day." + }, + "low": { + "type": "number", + "description": "The low temperature for the day." + }, + "conditions": { + "type": "string", + "description": "The weather conditions for the day." + } + }, + "required": [ + "day", + "high", + "low", + "conditions" + ], + "additionalProperties": false + } + } + }, + "required": [ + "location", + "today_forecast", + "week_forecast" + ], + "additionalProperties": false + } + } + }, + "reasoning": {}, + "tools": [ + { + "type": "web_search_preview", + "user_location": { + "type": "approximate" + }, + "search_context_size": "medium" + } + ], + "temperature": 1, + "max_output_tokens": 2048, + "top_p": 1, + "store": true + }, + "headers": { + "Authorization": "Bearer <>" + } + }, + "reporting": { + "time_saved_value": 0, + "time_saved_unit": "minutes" + }, + "monitoring": { + "monitor_all_events": false, + "monitor_failures": false, + "monitor_no_events_emitted": null + }, + "template": { + "created_from_template_guid": null, + "created_from_template_version": null, + "template_tags": [] + }, + "width": null, + "schedule": null + }, + { + "type": "Agents::EventTransformationAgent", + "name": "Extract JSON from GPT Response", + "disabled": false, + "description": null, + "guid": "3daccfdc203d265d5fa5e3fa51b292a2", + "origin_story_identifier": "cloud:fe39c2bc9bb5ebb0b5e24318b1f3b60d:7d1b8e278df1d019d8c177bcea99f672", + "options": { + "mode": "message_only", + "loop": false, + "payload": { + "current_temperature": "<>", + "current_conditions": "<>" + } + }, + "reporting": { + "time_saved_value": 0, + "time_saved_unit": "minutes" + }, + "monitoring": { + "monitor_all_events": false, + "monitor_failures": false, + "monitor_no_events_emitted": null + }, + "template": { + "created_from_template_guid": null, + "created_from_template_version": null, + "template_tags": [] + }, + "width": null, + "schedule": null + }, + { + "type": "Agents::WebhookAgent", + "name": "Webhook Action", + "disabled": false, + "description": null, + "guid": "b221dab1f04cfb42a33c218ce0595528", + "origin_story_identifier": "cloud:cf89515d7caa9b49006ad5a414bdd380:764abda0a56dc2745376a76b34c616c8", + "options": { + "path": "00ebff9c6332507fcbfa4b8075a0596c", + "secret": "3b58474011b515be17a5c00ad5b9abe8", + "verbs": "get,post" + }, + "reporting": { + "time_saved_value": 0, + "time_saved_unit": "minutes" + }, + "monitoring": { + "monitor_all_events": false, + "monitor_failures": false, + "monitor_no_events_emitted": null + }, + "template": { + "created_from_template_guid": null, + "created_from_template_version": null, + "template_tags": [] + }, + "width": null + } + ], + "diagram_notes": [], + "links": [ + { + "source": 0, + "receiver": 1 + }, + { + "source": 2, + "receiver": 0 + } + ], + "diagram_layout": "{\"a993f2b62e8d0a3d1ef2f855bece603b\":[210,210],\"3daccfdc203d265d5fa5e3fa51b292a2\":[210,300],\"b221dab1f04cfb42a33c218ce0595528\":[210,135]}", + "story_library_metadata": {}, + "monitor_failures": false, + "synchronous_webhooks_enabled": true, + "integrations": [], + "parent_only_send_to_story": false, + "keep_events_for": 86400, + "reporting_status": true, + "send_to_story_enabled": false, + "entry_agent_guid": null, + "exit_agent_guids": [], + "api_entry_action_guids": [ + "b221dab1f04cfb42a33c218ce0595528" + ], + "api_exit_action_guids": [ + "3daccfdc203d265d5fa5e3fa51b292a2" + ], + "send_to_story_access": null, + "send_to_story_access_source": 0, + "send_to_story_skill_use_requires_confirmation": true, + "pages": [], + "tags": [], + "time_saved_unit": "minutes", + "time_saved_value": 0, + "origin_story_identifier": "cloud:cf89515d7caa9b49006ad5a414bdd380:3de2282bcaac247d34009b0245c316ba", + "recipients": [ + "sullivan.matt@gmail.com" + ], + "integration_product": null, + "integration_vendor": null, + "llm_product_instructions": "", + "send_to_stories": [], + "exported_at": "2025-04-24T15:34:39Z", + "icon": ":simple_weather_api:" +} \ No newline at end of file diff --git a/ooo_checker/ooo-checker.tines b/ooo_checker/ooo-checker.tines index ada8bec..43b4144 100644 --- a/ooo_checker/ooo-checker.tines +++ b/ooo_checker/ooo-checker.tines @@ -9,22 +9,33 @@ // 4. Uses AI to determine if the Slack status indicates the user is OOO // 5. If approver is likely OOO, reassigns the task to their manager // -// Note: No actual credentials are stored in this file - credentials are referenced as <> and +// Note: No actual credentials are stored in this file - credentials are referenced as <> and // are securely managed within the Tines platform. { - "standardLibVersion": "82", - "actionRuntimeVersion": "34", + "standardLibVersion": "83", + "actionRuntimeVersion": "35", "agents": [ { "disabled": false, "name": "Search Tasks that are OPEN", "description": null, - "options": "{\"url\":\"https://DOMAIN.conductor.one/api/v1/search/tasks\",\"content_type\":\"application_json\",\"method\":\"post\",\"payload\":{\"taskStates\":[\"TASK_STATE_OPEN\"],\"taskTypes\":[{\"grant\":{}}],\"expandMask\":{\"paths\":[\"*\"]},\"currentStep\":\"TASK_SEARCH_CURRENT_STEP_APPROVAL\",\"createdAfter\":\"=DATE(\\\"60 minutes ago\\\")\"},\"headers\":{\"Authorization\":\"Bearer <>\"}}", - "position": { - "x": 735, - "y": 195 + "options": { + "url": "https://api.conductor.one/api/v1/search/tasks", + "content_type": "application_json", + "method": "post", + "payload": { + "taskStates": ["TASK_STATE_OPEN"], + "taskTypes": [{"grant": {}}], + "expandMask": {"paths": ["*"]}, + "currentStep": "TASK_SEARCH_CURRENT_STEP_APPROVAL", + "createdAfter": "=DATE(\"60 minutes ago\")" + }, + "headers": { + "Authorization": "Bearer <>" + } }, + "position": {"x": 240, "y": 165}, "schedule": [ { "cron": "0 */1 * * *", @@ -53,11 +64,16 @@ "disabled": false, "name": "Request is Present", "description": null, - "options": "{\"rules\":[{\"path\":\"=SIZE(search_tasks_that_are_open.body.list)\",\"type\":\"field>value\",\"value\":\"0\"}]}", - "position": { - "x": 735, - "y": 255 + "options": { + "rules": [ + { + "path": "=SIZE(search_tasks_that_are_open.body.list)", + "type": "field>value", + "value": "0" + } + ] }, + "position": {"x": 240, "y": 225}, "type": "trigger", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -80,11 +96,13 @@ "disabled": false, "name": "Explode all Stuck Requests", "description": null, - "options": "{\"mode\":\"explode\",\"path\":\"=search_tasks_that_are_open.body.list\",\"to\":\"stuck_request\",\"limit\":\"100\"}", - "position": { - "x": 735, - "y": 315 + "options": { + "mode": "explode", + "path": "=search_tasks_that_are_open.body.list", + "to": "stuck_request", + "limit": "100" }, + "position": {"x": 240, "y": 285}, "type": "eventTransformation", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -107,11 +125,16 @@ "disabled": false, "name": "Translate Approver User ID", "description": null, - "options": "{\"url\":\"https://DOMAIN.conductor.one/api/v1/users/<>\",\"content_type\":\"application_json\",\"method\":\"get\",\"payload\":{},\"headers\":{\"Authorization\":\"Bearer <>\"}}", - "position": { - "x": 735, - "y": 450 + "options": { + "url": "https://api.conductor.one/api/v1/users/<>", + "content_type": "application_json", + "method": "get", + "payload": {}, + "headers": { + "Authorization": "Bearer <>" + } }, + "position": {"x": 240, "y": 420}, "type": "httpRequest", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -134,11 +157,20 @@ "disabled": false, "name": "Search Slack user by email", "description": "Retrieve a single user by looking them up by their registered email address.\n\nLink to documentation: https://api.slack.com/methods/users.lookupByEmail\n\nRequired scope: users:read.email", - "options": "{\"url\":\"https://slack.com/api/users.lookupByEmail\",\"content_type\":\"json; charset=utf-8\",\"method\":\"get\",\"payload\":{\"email\":\"<>\"},\"headers\":{\"Authorization\":\"Bearer <>\"},\"log_error_on_status\":[\"400-499\",\"500\"],\"retry_on_status\":[\"0\",\"500\"]}", - "position": { - "x": 735, - "y": 600 - }, + "options": { + "url": "https://slack.com/api/users.lookupByEmail", + "content_type": "json; charset=utf-8", + "method": "get", + "payload": { + "email": "<>" + }, + "headers": { + "Authorization": "Bearer <>" + }, + "log_error_on_status": ["400-499", "500"], + "retry_on_status": ["0", "500"] + }, + "position": {"x": 240, "y": 570}, "type": "httpRequest", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -161,11 +193,16 @@ "disabled": false, "name": "Get Approver's Manager Information", "description": null, - "options": "{\"url\":\"https://DOMAIN.conductor.one/api/v1/users/<>\",\"content_type\":\"application_json\",\"method\":\"get\",\"payload\":{},\"headers\":{\"Authorization\":\"Bearer <>\"}}", - "position": { - "x": 735, - "y": 1035 + "options": { + "url": "https://api.conductor.one/api/v1/users/<>", + "content_type": "application_json", + "method": "get", + "payload": {}, + "headers": { + "Authorization": "Bearer <>" + } }, + "position": {"x": 240, "y": 1005}, "type": "httpRequest", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -188,11 +225,16 @@ "disabled": false, "name": "Ensure Approver's Manager Isn't Super Important", "description": "", - "options": "{\"rules\":[{\"path\":\"=get_approver_s_manager_information.body.userView.user.profile.globalJobLevel\",\"type\":\"field<=value\",\"value\":\"10\"}]}", - "position": { - "x": 735, - "y": 1110 + "options": { + "rules": [ + { + "path": "=get_approver_s_manager_information.body.userView.user.profile.globalJobLevel", + "type": "field<=value", + "value": "10" + } + ] }, + "position": {"x": 240, "y": 1080}, "type": "trigger", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -215,11 +257,20 @@ "disabled": false, "name": "Assign Task to User's Mananger", "description": null, - "options": "{\"url\":\"https://DOMAIN.conductor.one/api/v1/tasks/<>/action/reassign\",\"content_type\":\"application_json\",\"method\":\"post\",\"payload\":{\"policyStepId\":\"<>\",\"newStepUserIds\":[\"<>\"],\"comment\":\"The currently-assigned approver appears to be out-of-office, reassigning to approver's manager.\"},\"headers\":{\"Authorization\":\"Bearer <>\"}}", - "position": { - "x": 735, - "y": 1200 + "options": { + "url": "https://api.conductor.one/api/v1/tasks/<>/action/reassign", + "content_type": "application_json", + "method": "post", + "payload": { + "policyStepId": "<>", + "newStepUserIds": ["<>"], + "comment": "The currently-assigned approver appears to be out-of-office, reassigning to approver's manager." + }, + "headers": { + "Authorization": "Bearer <>" + } }, + "position": {"x": 240, "y": 1170}, "type": "httpRequest", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -241,12 +292,17 @@ { "disabled": false, "name": "Ensure Approver Isn't In Security", - "description": "Security handles special approval workflows that need different handling when team members are OoO.", - "options": "{\"rules\":[{\"path\":\"<>\",\"type\":\"field!=value\",\"value\":\"Eng Security\"}]}", - "position": { - "x": 735, - "y": 510 + "description": "Why do we care? Well, Security handles a lot of the \"fall through\" cases where someone requests something unexpected. If one of us is OoO, that means the approval is going to escalate to the manager, which is annoying for them.", + "options": { + "rules": [ + { + "path": "<>", + "type": "field!=value", + "value": "Security" + } + ] }, + "position": {"x": 240, "y": 480}, "type": "trigger", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -269,11 +325,15 @@ "disabled": false, "name": "Extract JSON from GPT", "description": null, - "options": "{\"mode\":\"message_only\",\"loop\":false,\"payload\":{\"currently_out_of_office_rating\":\"<>\",\"decision_description\":\"<>\"}}", - "position": { - "x": 735, - "y": 870 + "options": { + "mode": "message_only", + "loop": false, + "payload": { + "currently_out_of_office_rating": "<>", + "decision_description": "<>" + } }, + "position": {"x": 240, "y": 840}, "type": "eventTransformation", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -296,11 +356,16 @@ "disabled": false, "name": "Check If OpenAI Thinks Approver is OoO", "description": null, - "options": "{\"rules\":[{\"path\":\"=extract_json_from_gpt.currently_out_of_office_rating\",\"type\":\"field>=value\",\"value\":\"7\"}]}", - "position": { - "x": 735, - "y": 945 + "options": { + "rules": [ + { + "path": "=extract_json_from_gpt.currently_out_of_office_rating", + "type": "field>=value", + "value": "7" + } + ] }, + "position": {"x": 240, "y": 915}, "type": "trigger", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -323,11 +388,16 @@ "disabled": false, "name": "Check If Approver Slack Status Set", "description": null, - "options": "{\"rules\":[{\"path\":\"<>\",\"type\":\"field!=value\",\"value\":\"\"}]}", - "position": { - "x": 735, - "y": 735 + "options": { + "rules": [ + { + "path": "<>", + "type": "field!=value", + "value": "" + } + ] }, + "position": {"x": 240, "y": 705}, "type": "trigger", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -350,11 +420,16 @@ "disabled": false, "name": "Check If Slack Profile Found", "description": null, - "options": "{\"rules\":[{\"path\":\"<>\",\"type\":\"field==value\",\"value\":\"true\"}]}", - "position": { - "x": 735, - "y": 675 + "options": { + "rules": [ + { + "path": "<>", + "type": "field==value", + "value": "true" + } + ] }, + "position": {"x": 240, "y": 645}, "type": "trigger", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -377,11 +452,16 @@ "disabled": false, "name": "Check Task Approver Exists", "description": null, - "options": "{\"rules\":[{\"path\":\"<>\",\"type\":\"regex\",\"value\":\".*\"}]}", - "position": { - "x": 735, - "y": 390 + "options": { + "rules": [ + { + "path": "<>", + "type": "regex", + "value": ".*" + } + ] }, + "position": {"x": 240, "y": 360}, "type": "trigger", "timeSavedUnit": "minutes", "timeSavedValue": 0, @@ -402,14 +482,14 @@ }, { "disabled": false, - "name": "Ask AI Gateway", - "description": "Provide a system prompt and user prompt, AI Gateway will respond. ", - "options": "{\"url\":\"https://dzpricnofd.execute-api.us-east-1.amazonaws.com/proxy/DOMAIN-bot.security/openai/v1/chat/completions\",\"content_type\":\"application_json\",\"method\":\"post\",\"payload\":{\"model\":\"gpt-4o\",\"messages\":[{\"role\":\"system\",\"content\":\"<>\"},{\"role\":\"user\",\"content\":\"<>\"}],\"temperature\":1,\"max_tokens\":4096,\"top_p\":1,\"frequency_penalty\":0,\"presence_penalty\":0},\"headers\":{\"Authorization\":\"<>\"}}", - "position": { - "x": 735, - "y": 810 + "name": "Ask LLM", + "description": null, + "options": { + "prompt": "Act as an employee within a company. You want to understand if a peer is currently out of office or not. Respond with your recommendation for if the Slack status text you are provided is indicative that the user is out of office as of today, and their out of office will last for more than 1 day, with a rating from 0-10 about your confidence in your decision and a description of why you feel this user is likely out of office already and will be for more than 1 day. You will also be provided today's date to help you determine if a user is currently out of office, or their Slack status is simply warning about a future out of office status.\n\nRespond with JSON with 2 fields: \"decision_description\" and \"currently_out_of_office_rating\". Only use data from the event, do not generate any example data or make any assumptions. Only output JSON plain text, with no formatting, markdown, or code blocks. Start the JSON output with { and end it with }.\n\nToday's date:\n<>\n\nSlack status text:\n<>", + "json_mode": false }, - "type": "httpRequest", + "position": {"x": 240, "y": 780}, + "type": "llm", "timeSavedUnit": "minutes", "timeSavedValue": 0, "monitorAllEvents": false, @@ -422,106 +502,31 @@ "recordType": null, "recordWriters": [], "form": null, - "integration": { - "product": "AI Gateway", - "actionOptionsKeys": [], - "actionInputs": [ - { - "name": "system_prompt", - "description": "", - "type": "TEXT", - "required": true, - "value": "\"Act as an employee within a company. You want to understand if a peer is currently out of office or not. Respond with your recommendation for if the Slack status text you are provided is indicative that the user is out of office as of today, and their out of office will last for more than 1 day, with a rating from 0-10 about your confidence in your decision and a description of why you feel this user is likely out of office already and will be for more than 1 day. You will also be provided today's date to help you determine if a user is currently out of office, or their Slack status is simply warning about a future out of office status.\\n\\nRespond with JSON with 2 fields: \\\"decision_description\\\" and \\\"currently_out_of_office_rating\\\". Only use data from the event, do not generate any example data or make any assumptions. Only output JSON plain text, with no formatting, markdown, or code blocks. Start the JSON output with { and end it with }.\"", - "options": "[\"Option 1\", \"Option 2\"]", - "isCollapsed": false, - "multiSelectEnabled": false, - "subType": "HTML", - "llmJsonSchema": "{}" - }, - { - "name": "user_prompt", - "description": "", - "type": "TEXT", - "required": true, - "value": "\"Today's date:\\n<>\\n\\nSlack status text:\\n<>\"", - "options": "[\"Option 1\", \"Option 2\"]", - "isCollapsed": false, - "multiSelectEnabled": false, - "subType": "HTML", - "llmJsonSchema": "{}" - } - ] - }, - "createdFromTemplateGuid": "8bc5923539b17f31800a3a895eb078f7", - "createdFromTemplateVersion": 2, + "createdFromTemplateGuid": null, + "createdFromTemplateVersion": null, "templateTags": [], - "originStoryIdentifier": "cloud:fe39c2bc9bb5ebb0b5e24318b1f3b60d:ffaab8109a8ffc47c317bfe211847434" + "originStoryIdentifier": "cloud:fe39c2bc9bb5ebb0b5e24318b1f3b60d:79f7459bb637b4a6dd54c1af1991b5ca" } ], "links": [ - { - "sourceIdentifier": "0", - "receiverIdentifier": "1" - }, - { - "sourceIdentifier": "1", - "receiverIdentifier": "2" - }, - { - "sourceIdentifier": "5", - "receiverIdentifier": "6" - }, - { - "sourceIdentifier": "6", - "receiverIdentifier": "7" - }, - { - "sourceIdentifier": "9", - "receiverIdentifier": "10" - }, - { - "sourceIdentifier": "10", - "receiverIdentifier": "5" - }, - { - "sourceIdentifier": "3", - "receiverIdentifier": "8" - }, - { - "sourceIdentifier": "8", - "receiverIdentifier": "4" - }, - { - "sourceIdentifier": "4", - "receiverIdentifier": "12" - }, - { - "sourceIdentifier": "12", - "receiverIdentifier": "11" - }, - { - "sourceIdentifier": "2", - "receiverIdentifier": "13" - }, - { - "sourceIdentifier": "13", - "receiverIdentifier": "3" - }, - { - "sourceIdentifier": "11", - "receiverIdentifier": "14" - }, - { - "sourceIdentifier": "14", - "receiverIdentifier": "9" - } + {"sourceIdentifier": "0", "receiverIdentifier": "1"}, + {"sourceIdentifier": "1", "receiverIdentifier": "2"}, + {"sourceIdentifier": "5", "receiverIdentifier": "6"}, + {"sourceIdentifier": "6", "receiverIdentifier": "7"}, + {"sourceIdentifier": "9", "receiverIdentifier": "10"}, + {"sourceIdentifier": "10", "receiverIdentifier": "5"}, + {"sourceIdentifier": "3", "receiverIdentifier": "8"}, + {"sourceIdentifier": "8", "receiverIdentifier": "4"}, + {"sourceIdentifier": "4", "receiverIdentifier": "12"}, + {"sourceIdentifier": "12", "receiverIdentifier": "11"}, + {"sourceIdentifier": "2", "receiverIdentifier": "13"}, + {"sourceIdentifier": "13", "receiverIdentifier": "3"}, + {"sourceIdentifier": "11", "receiverIdentifier": "14"}, + {"sourceIdentifier": "14", "receiverIdentifier": "9"} ], "diagramNotes": [ { - "position": { - "x": 705, - "y": 105 - }, + "position": {"x": 213, "y": 80}, "content": "Check Approver Status - Slack", "width": 215 }