zeitlich 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,42 +1,29 @@
1
1
  'use strict';
2
2
 
3
3
  var workflow = require('@temporalio/workflow');
4
- var z13 = require('zod');
4
+ var z14 = require('zod');
5
5
  var plugin = require('@temporalio/plugin');
6
6
  var messages = require('@langchain/core/messages');
7
7
  var crypto = require('crypto');
8
8
  var activity = require('@temporalio/activity');
9
9
  var justBash = require('just-bash');
10
+ var promises = require('fs/promises');
11
+ var path = require('path');
10
12
 
11
13
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
14
 
13
- var z13__default = /*#__PURE__*/_interopDefault(z13);
14
- var crypto__default = /*#__PURE__*/_interopDefault(crypto);
15
+ var z14__default = /*#__PURE__*/_interopDefault(z14);
15
16
 
16
17
  // src/lib/session.ts
17
18
  var SUBAGENT_TOOL_NAME = "Subagent";
18
19
  function buildSubagentDescription(subagents) {
19
- const subagentList = subagents.map((s) => `- **${s.agentName}**: ${s.description}`).join("\n");
20
- return `Launch a new agent to handle complex tasks autonomously.
21
-
22
- The ${SUBAGENT_TOOL_NAME} tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
23
-
24
- Available agent types:
20
+ const subagentList = subagents.map((s) => `## ${s.agentName}
21
+ ${s.description}`).join("\n\n");
22
+ return `The ${SUBAGENT_TOOL_NAME} tool launches specialized agents (subagents) that autonomously handle complex work. Each agent type has specific capabilities and tools available to it.
25
23
 
24
+ # Available subagents:
26
25
  ${subagentList}
27
-
28
- When using the ${SUBAGENT_TOOL_NAME} tool, you must specify a subagent parameter to select which agent type to use.
29
-
30
- Usage notes:
31
-
32
- - Always include a short description (3-5 words) summarizing what the agent will do
33
- - Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
34
- - When the agent is done, it will return a single message back to you.
35
- - Each invocation starts fresh - provide a detailed task description with all necessary context.
36
- - Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
37
- - The agent's outputs should generally be trusted
38
- - Clearly tell the agent what type of work you expect since it is not aware of the user's intent
39
- - If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.`;
26
+ `;
40
27
  }
41
28
  function createSubagentTool(subagents) {
42
29
  if (subagents.length === 0) {
@@ -46,10 +33,10 @@ function createSubagentTool(subagents) {
46
33
  return {
47
34
  name: SUBAGENT_TOOL_NAME,
48
35
  description: buildSubagentDescription(subagents),
49
- schema: z13__default.default.object({
50
- subagent: z13__default.default.enum(names).describe("The type of subagent to launch"),
51
- description: z13__default.default.string().describe("A short (3-5 word) description of the task"),
52
- prompt: z13__default.default.string().describe("The task for the agent to perform")
36
+ schema: z14__default.default.object({
37
+ subagent: z14__default.default.enum(names).describe("The type of subagent to launch"),
38
+ description: z14__default.default.string().describe("A short (3-5 word) description of the task"),
39
+ prompt: z14__default.default.string().describe("The task for the agent to perform")
53
40
  })
54
41
  };
55
42
  }
@@ -73,23 +60,77 @@ function createSubagentHandler(subagents) {
73
60
  taskQueue: config.taskQueue ?? parentTaskQueue
74
61
  };
75
62
  const { toolResponse, data, usage } = typeof config.workflow === "string" ? await workflow.executeChild(config.workflow, childOpts) : await workflow.executeChild(config.workflow, childOpts);
76
- const validated = config.resultSchema ? config.resultSchema.parse(data) : null;
63
+ if (!toolResponse) {
64
+ return {
65
+ toolResponse: "Subagent workflow returned no response",
66
+ data: null,
67
+ ...usage && { usage }
68
+ };
69
+ }
70
+ const validated = config.resultSchema ? config.resultSchema.safeParse(data) : null;
71
+ if (validated && !validated.success) {
72
+ return {
73
+ toolResponse: `Subagent workflow returned invalid data: ${validated.error.message}`,
74
+ data: null,
75
+ ...usage && { usage }
76
+ };
77
+ }
77
78
  return {
78
79
  toolResponse,
79
- data: validated,
80
+ data: validated ? validated.data : data,
80
81
  ...usage && { usage }
81
82
  };
82
83
  };
83
84
  }
85
+ var READ_SKILL_TOOL_NAME = "ReadSkill";
86
+ function buildReadSkillDescription(skills) {
87
+ const skillList = skills.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
88
+ return `Load the full instructions for a skill. Read the skill before following its instructions.
84
89
 
85
- // src/lib/tool-router.ts
90
+ # Available skills:
91
+ ${skillList}
92
+ `;
93
+ }
94
+ function createReadSkillTool(skills) {
95
+ if (skills.length === 0) {
96
+ throw new Error("createReadSkillTool requires at least one skill");
97
+ }
98
+ const names = skills.map((s) => s.name);
99
+ return {
100
+ name: READ_SKILL_TOOL_NAME,
101
+ description: buildReadSkillDescription(skills),
102
+ schema: z14__default.default.object({
103
+ skill_name: z14__default.default.enum(names).describe("The name of the skill to load")
104
+ })
105
+ };
106
+ }
107
+
108
+ // src/tools/read-skill/handler.ts
109
+ function createReadSkillHandler(skills) {
110
+ const skillMap = new Map(skills.map((s) => [s.name, s]));
111
+ return (args) => {
112
+ const skill = skillMap.get(args.skill_name);
113
+ if (!skill) {
114
+ return {
115
+ toolResponse: JSON.stringify({
116
+ error: `Skill "${args.skill_name}" not found`
117
+ }),
118
+ data: null
119
+ };
120
+ }
121
+ return {
122
+ toolResponse: skill.instructions,
123
+ data: null
124
+ };
125
+ };
126
+ }
86
127
  function createToolRouter(options) {
87
128
  const { appendToolResult } = options;
88
129
  const toolMap = /* @__PURE__ */ new Map();
89
130
  for (const [_key, tool] of Object.entries(options.tools)) {
90
131
  toolMap.set(tool.name, tool);
91
132
  }
92
- const isEnabled = (tool) => tool.enabled?.() ?? true;
133
+ const isEnabled = (tool) => tool.enabled ?? true;
93
134
  if (options.subagents) {
94
135
  if (options.subagents.length > 0) {
95
136
  const subagentHooksMap = /* @__PURE__ */ new Map();
@@ -119,6 +160,12 @@ function createToolRouter(options) {
119
160
  });
120
161
  }
121
162
  }
163
+ if (options.skills && options.skills.length > 0) {
164
+ toolMap.set(READ_SKILL_TOOL_NAME, {
165
+ ...createReadSkillTool(options.skills),
166
+ handler: createReadSkillHandler(options.skills)
167
+ });
168
+ }
122
169
  async function processToolCall(toolCall, turn, handlerContext) {
123
170
  const startTime = Date.now();
124
171
  const tool = toolMap.get(toolCall.name);
@@ -228,7 +275,9 @@ function createToolRouter(options) {
228
275
  }
229
276
  }
230
277
  if (!recovered) {
231
- throw error;
278
+ throw workflow.ApplicationFailure.fromError(error, {
279
+ nonRetryable: true
280
+ });
232
281
  }
233
282
  }
234
283
  if (!resultAppended) {
@@ -291,9 +340,10 @@ function createToolRouter(options) {
291
340
  },
292
341
  getToolDefinitions() {
293
342
  const activeSubagents = options.subagents?.filter((subagent) => isEnabled(subagent)) ?? [];
343
+ const activeSkills = options.skills ?? [];
294
344
  return [
295
345
  ...Array.from(toolMap).filter(
296
- ([, tool]) => isEnabled(tool) && tool.name !== SUBAGENT_TOOL_NAME
346
+ ([, tool]) => isEnabled(tool) && tool.name !== SUBAGENT_TOOL_NAME && tool.name !== READ_SKILL_TOOL_NAME
297
347
  ).map(([name, tool]) => ({
298
348
  name,
299
349
  description: tool.description,
@@ -301,7 +351,8 @@ function createToolRouter(options) {
301
351
  strict: tool.strict,
302
352
  max_uses: tool.max_uses
303
353
  })),
304
- ...activeSubagents.length > 0 ? [createSubagentTool(activeSubagents)] : []
354
+ ...activeSubagents.length > 0 ? [createSubagentTool(activeSubagents)] : [],
355
+ ...activeSkills.length > 0 ? [createReadSkillTool(activeSkills)] : []
305
356
  ];
306
357
  },
307
358
  // --- Methods for processing tool calls ---
@@ -415,18 +466,17 @@ function hasNoOtherToolCalls(toolCalls, excludeName) {
415
466
  var createSession = async ({
416
467
  threadId,
417
468
  agentName,
418
- description,
419
469
  maxTurns = 50,
420
470
  metadata = {},
421
471
  runAgent,
422
472
  threadOps,
423
473
  buildContextMessage,
424
474
  subagents,
475
+ skills,
425
476
  tools = {},
426
477
  processToolsInParallel = true,
427
478
  hooks = {},
428
479
  appendSystemPrompt = true,
429
- systemPrompt,
430
480
  waitForInputTimeout = "48h"
431
481
  }) => {
432
482
  const {
@@ -441,6 +491,7 @@ var createSession = async ({
441
491
  threadId,
442
492
  hooks,
443
493
  subagents,
494
+ skills,
444
495
  parallel: processToolsInParallel
445
496
  });
446
497
  const callSessionEnd = async (exitReason, turns) => {
@@ -484,8 +535,15 @@ var createSession = async ({
484
535
  metadata
485
536
  });
486
537
  }
538
+ const systemPrompt = stateManager.getSystemPrompt();
487
539
  await initializeThread(threadId);
488
- if (appendSystemPrompt && systemPrompt && systemPrompt.trim() !== "") {
540
+ if (appendSystemPrompt) {
541
+ if (!systemPrompt || systemPrompt.trim() === "") {
542
+ throw workflow.ApplicationFailure.create({
543
+ message: "No system prompt in state",
544
+ nonRetryable: true
545
+ });
546
+ }
489
547
  await appendSystemMessage(threadId, systemPrompt);
490
548
  }
491
549
  await appendHumanMessage(threadId, await buildContextMessage());
@@ -498,9 +556,7 @@ var createSession = async ({
498
556
  const { message, rawToolCalls, usage } = await runAgent({
499
557
  threadId,
500
558
  agentName,
501
- metadata,
502
- systemPrompt,
503
- description
559
+ metadata
504
560
  });
505
561
  if (usage) {
506
562
  stateManager.updateUsage(usage);
@@ -557,7 +613,7 @@ var createSession = async ({
557
613
  }
558
614
  } catch (error) {
559
615
  exitReason = "failed";
560
- throw error;
616
+ throw workflow.ApplicationFailure.fromError(error);
561
617
  } finally {
562
618
  await callSessionEnd(exitReason, stateManager.getTurns());
563
619
  }
@@ -572,14 +628,13 @@ var createSession = async ({
572
628
  function proxyDefaultThreadOps(options) {
573
629
  const activities = workflow.proxyActivities(
574
630
  options ?? {
575
- startToCloseTimeout: "30m",
631
+ startToCloseTimeout: "10s",
576
632
  retry: {
577
633
  maximumAttempts: 6,
578
634
  initialInterval: "5s",
579
635
  maximumInterval: "15m",
580
636
  backoffCoefficient: 4
581
- },
582
- heartbeatTimeout: "5m"
637
+ }
583
638
  }
584
639
  );
585
640
  return {
@@ -591,12 +646,14 @@ function proxyDefaultThreadOps(options) {
591
646
  }
592
647
 
593
648
  // src/lib/types.ts
649
+ var agentQueryName = (agentName) => `get${agentName}State`;
650
+ var agentStateChangeUpdateName = (agentName) => `waitFor${agentName}StateChange`;
594
651
  function isTerminalStatus(status) {
595
652
  return status === "COMPLETED" || status === "FAILED" || status === "CANCELLED";
596
653
  }
597
654
  function createAgentStateManager({
598
655
  initialState,
599
- agentConfig
656
+ agentName
600
657
  }) {
601
658
  let status = initialState?.status ?? "RUNNING";
602
659
  let version = initialState?.version ?? 0;
@@ -607,6 +664,7 @@ function createAgentStateManager({
607
664
  let totalCachedWriteTokens = 0;
608
665
  let totalCachedReadTokens = 0;
609
666
  let totalReasonTokens = 0;
667
+ let systemPrompt = initialState?.systemPrompt;
610
668
  const tasks = new Map(initialState?.tasks);
611
669
  const {
612
670
  status: _,
@@ -626,28 +684,32 @@ function createAgentStateManager({
626
684
  ...customState
627
685
  };
628
686
  }
629
- workflow.setHandler(workflow.defineQuery(`get${agentConfig.agentName}State`), () => {
687
+ const stateQuery = workflow.defineQuery(
688
+ agentQueryName(agentName)
689
+ );
690
+ const stateChangeUpdate = workflow.defineUpdate(
691
+ agentStateChangeUpdateName(agentName)
692
+ );
693
+ workflow.setHandler(stateQuery, () => buildState());
694
+ workflow.setHandler(stateChangeUpdate, async (lastKnownVersion) => {
695
+ await workflow.condition(
696
+ () => version > lastKnownVersion || isTerminalStatus(status),
697
+ "55s"
698
+ );
630
699
  return buildState();
631
700
  });
632
- workflow.setHandler(
633
- workflow.defineUpdate(
634
- `waitFor${agentConfig.agentName}StateChange`
635
- ),
636
- async (lastKnownVersion) => {
637
- await workflow.condition(
638
- () => version > lastKnownVersion || isTerminalStatus(status),
639
- "55s"
640
- );
641
- return buildState();
642
- }
643
- );
644
701
  return {
702
+ stateQuery,
703
+ stateChangeUpdate,
645
704
  getStatus() {
646
705
  return status;
647
706
  },
648
707
  isRunning() {
649
708
  return status === "RUNNING";
650
709
  },
710
+ getSystemPrompt() {
711
+ return systemPrompt;
712
+ },
651
713
  isTerminal() {
652
714
  return isTerminalStatus(status);
653
715
  },
@@ -710,11 +772,14 @@ function createAgentStateManager({
710
772
  tools = newTools.map((tool) => ({
711
773
  name: tool.name,
712
774
  description: tool.description,
713
- schema: z13.z.toJSONSchema(tool.schema),
775
+ schema: z14.z.toJSONSchema(tool.schema),
714
776
  strict: tool.strict,
715
777
  max_uses: tool.max_uses
716
778
  }));
717
779
  },
780
+ setSystemPrompt(newSystemPrompt) {
781
+ systemPrompt = newSystemPrompt;
782
+ },
718
783
  deleteTask(id) {
719
784
  const deleted = tasks.delete(id);
720
785
  if (deleted) {
@@ -741,11 +806,79 @@ function createAgentStateManager({
741
806
  }
742
807
  };
743
808
  }
744
- var AGENT_HANDLER_NAMES = {
745
- getAgentState: "getAgentState",
746
- waitForStateChange: "waitForStateChange",
747
- addMessage: "addMessage"
748
- };
809
+
810
+ // src/lib/skills/parse.ts
811
+ function parseSkillFile(raw) {
812
+ const trimmed = raw.replace(/^\uFEFF/, "");
813
+ const match = trimmed.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/);
814
+ if (!match) {
815
+ throw new Error(
816
+ "SKILL.md must start with YAML frontmatter delimited by ---"
817
+ );
818
+ }
819
+ const [, yamlBlock, body] = match;
820
+ const frontmatter = parseSimpleYaml(yamlBlock);
821
+ if (!frontmatter.name || typeof frontmatter.name !== "string") {
822
+ throw new Error("SKILL.md frontmatter must include a 'name' field");
823
+ }
824
+ if (!frontmatter.description || typeof frontmatter.description !== "string") {
825
+ throw new Error("SKILL.md frontmatter must include a 'description' field");
826
+ }
827
+ const result = {
828
+ name: frontmatter.name,
829
+ description: frontmatter.description
830
+ };
831
+ if (frontmatter.license) result.license = String(frontmatter.license);
832
+ if (frontmatter.compatibility)
833
+ result.compatibility = String(frontmatter.compatibility);
834
+ if (frontmatter["allowed-tools"]) {
835
+ result.allowedTools = String(frontmatter["allowed-tools"]).split(/\s+/).filter(Boolean);
836
+ }
837
+ if (frontmatter.metadata && typeof frontmatter.metadata === "object" && !Array.isArray(frontmatter.metadata)) {
838
+ result.metadata = frontmatter.metadata;
839
+ }
840
+ return { frontmatter: result, body: body.trim() };
841
+ }
842
+ function parseSimpleYaml(yaml) {
843
+ const result = {};
844
+ const lines = yaml.split(/\r?\n/);
845
+ let currentMapKey = null;
846
+ let currentMap = null;
847
+ for (const line of lines) {
848
+ if (line.trim() === "" || line.trim().startsWith("#")) continue;
849
+ const nestedMatch = line.match(/^(\s{2,}|\t+)(\S+)\s*:\s*(.*)$/);
850
+ if (nestedMatch && currentMapKey && currentMap) {
851
+ const [, , key2, rawVal2] = nestedMatch;
852
+ currentMap[key2] = unquote(rawVal2.trim());
853
+ continue;
854
+ }
855
+ if (currentMapKey && currentMap) {
856
+ result[currentMapKey] = currentMap;
857
+ currentMapKey = null;
858
+ currentMap = null;
859
+ }
860
+ const topMatch = line.match(/^(\S+)\s*:\s*(.*)$/);
861
+ if (!topMatch) continue;
862
+ const [, key, rawVal] = topMatch;
863
+ const val = rawVal.trim();
864
+ if (val === "" || val === "|" || val === ">") {
865
+ currentMapKey = key;
866
+ currentMap = {};
867
+ } else {
868
+ result[key] = unquote(val);
869
+ }
870
+ }
871
+ if (currentMapKey && currentMap) {
872
+ result[currentMapKey] = currentMap;
873
+ }
874
+ return result;
875
+ }
876
+ function unquote(s) {
877
+ if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
878
+ return s.slice(1, -1);
879
+ }
880
+ return s;
881
+ }
749
882
  var globTool = {
750
883
  name: "Glob",
751
884
  description: `Search for files matching a glob pattern within the available file system.
@@ -760,9 +893,9 @@ Examples:
760
893
  - "**/*.test.ts" - Find all test files recursively
761
894
  - "src/**/*.ts" - Find all TypeScript files in src directory
762
895
  `,
763
- schema: z13.z.object({
764
- pattern: z13.z.string().describe("Glob pattern to match files against"),
765
- root: z13.z.string().optional().describe("Optional root directory to search from")
896
+ schema: z14.z.object({
897
+ pattern: z14.z.string().describe("Glob pattern to match files against"),
898
+ root: z14.z.string().optional().describe("Optional root directory to search from")
766
899
  }),
767
900
  strict: true
768
901
  };
@@ -780,13 +913,13 @@ Examples:
780
913
  - Search for function definitions with "function.*handleClick"
781
914
  - Search case-insensitively with ignoreCase: true
782
915
  `,
783
- schema: z13.z.object({
784
- pattern: z13.z.string().describe("Regex pattern to search for in file contents"),
785
- ignoreCase: z13.z.boolean().optional().describe("Case-insensitive search (default: false)"),
786
- maxMatches: z13.z.number().optional().describe("Maximum number of matches to return (default: 50)"),
787
- includePatterns: z13.z.array(z13.z.string()).optional().describe("Glob patterns to include (e.g., ['*.ts', '*.js'])"),
788
- excludePatterns: z13.z.array(z13.z.string()).optional().describe("Glob patterns to exclude (e.g., ['*.test.ts'])"),
789
- contextLines: z13.z.number().optional().describe("Number of context lines to show around matches")
916
+ schema: z14.z.object({
917
+ pattern: z14.z.string().describe("Regex pattern to search for in file contents"),
918
+ ignoreCase: z14.z.boolean().optional().describe("Case-insensitive search (default: false)"),
919
+ maxMatches: z14.z.number().optional().describe("Maximum number of matches to return (default: 50)"),
920
+ includePatterns: z14.z.array(z14.z.string()).optional().describe("Glob patterns to include (e.g., ['*.ts', '*.js'])"),
921
+ excludePatterns: z14.z.array(z14.z.string()).optional().describe("Glob patterns to exclude (e.g., ['*.test.ts'])"),
922
+ contextLines: z14.z.number().optional().describe("Number of context lines to show around matches")
790
923
  }),
791
924
  strict: true
792
925
  };
@@ -804,12 +937,12 @@ The tool returns the file content in an appropriate format:
804
937
  - Images: Base64-encoded image data
805
938
  - PDFs: Extracted text content
806
939
  `,
807
- schema: z13.z.object({
808
- path: z13.z.string().describe("Virtual path to the file to read"),
809
- offset: z13.z.number().optional().describe(
940
+ schema: z14.z.object({
941
+ path: z14.z.string().describe("Virtual path to the file to read"),
942
+ offset: z14.z.number().optional().describe(
810
943
  "Line number to start reading from (1-indexed, for text files)"
811
944
  ),
812
- limit: z13.z.number().optional().describe("Maximum number of lines to read (for text files)")
945
+ limit: z14.z.number().optional().describe("Maximum number of lines to read (for text files)")
813
946
  }),
814
947
  strict: true
815
948
  };
@@ -826,9 +959,9 @@ IMPORTANT:
826
959
  - This is an atomic write operation - the entire file is replaced
827
960
  - Path must be relative to the root of the file system (e.g., "docs/readme.md", not "/docs/readme.md")
828
961
  `,
829
- schema: z13.z.object({
830
- file_path: z13.z.string().describe("The path to the file to write"),
831
- content: z13.z.string().describe("The content to write to the file")
962
+ schema: z14.z.object({
963
+ file_path: z14.z.string().describe("The path to the file to write"),
964
+ content: z14.z.string().describe("The content to write to the file")
832
965
  }),
833
966
  strict: true
834
967
  };
@@ -848,13 +981,13 @@ IMPORTANT:
848
981
  - The operation fails if old_string is not found
849
982
  - old_string and new_string must be different
850
983
  `,
851
- schema: z13.z.object({
852
- file_path: z13.z.string().describe("The absolute virtual path to the file to modify"),
853
- old_string: z13.z.string().describe("The exact text to replace"),
854
- new_string: z13.z.string().describe(
984
+ schema: z14.z.object({
985
+ file_path: z14.z.string().describe("The absolute virtual path to the file to modify"),
986
+ old_string: z14.z.string().describe("The exact text to replace"),
987
+ new_string: z14.z.string().describe(
855
988
  "The text to replace it with (must be different from old_string)"
856
989
  ),
857
- replace_all: z13.z.boolean().optional().describe(
990
+ replace_all: z14.z.boolean().optional().describe(
858
991
  "If true, replace all occurrences of old_string (default: false)"
859
992
  )
860
993
  }),
@@ -862,7 +995,7 @@ IMPORTANT:
862
995
  };
863
996
  var taskCreateTool = {
864
997
  name: "TaskCreate",
865
- description: `Use this tool to create a structured task list for the control test. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
998
+ description: `Use this tool to create a structured task list. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
866
999
  It also helps the user understand the progress of the task and overall progress of their requests.
867
1000
 
868
1001
  ## When to Use This Tool
@@ -901,17 +1034,17 @@ var taskCreateTool = {
901
1034
  - Include enough detail in the description for another agent to understand and complete the task
902
1035
  - After creating tasks, use TaskUpdate to set up dependencies (blocks/blockedBy) if needed
903
1036
  - Check TaskList first to avoid creating duplicate tasks`,
904
- schema: z13__default.default.object({
905
- subject: z13__default.default.string().describe(
1037
+ schema: z14__default.default.object({
1038
+ subject: z14__default.default.string().describe(
906
1039
  'A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")'
907
1040
  ),
908
- description: z13__default.default.string().describe(
1041
+ description: z14__default.default.string().describe(
909
1042
  "Detailed description of what needs to be done, including context and acceptance criteria"
910
1043
  ),
911
- activeForm: z13__default.default.string().describe(
1044
+ activeForm: z14__default.default.string().describe(
912
1045
  'Present continuous form shown in spinner when task is in_progress (e.g., "Fixing authentication bug"). This is displayed to the user while you work on the task.'
913
1046
  ),
914
- metadata: z13__default.default.record(z13__default.default.string(), z13__default.default.string()).describe("Arbitrary key-value pairs for tracking")
1047
+ metadata: z14__default.default.record(z14__default.default.string(), z14__default.default.string()).describe("Arbitrary key-value pairs for tracking")
915
1048
  })
916
1049
  };
917
1050
  function createTaskCreateHandler(stateManager) {
@@ -936,8 +1069,8 @@ function createTaskCreateHandler(stateManager) {
936
1069
  var taskGetTool = {
937
1070
  name: "TaskGet",
938
1071
  description: `Retrieve full task details including dependencies.`,
939
- schema: z13__default.default.object({
940
- taskId: z13__default.default.string().describe("The ID of the task to get")
1072
+ schema: z14__default.default.object({
1073
+ taskId: z14__default.default.string().describe("The ID of the task to get")
941
1074
  })
942
1075
  };
943
1076
 
@@ -960,7 +1093,7 @@ function createTaskGetHandler(stateManager) {
960
1093
  var taskListTool = {
961
1094
  name: "TaskList",
962
1095
  description: `List all tasks with current state.`,
963
- schema: z13__default.default.object({})
1096
+ schema: z14__default.default.object({})
964
1097
  };
965
1098
 
966
1099
  // src/tools/task-list/handler.ts
@@ -976,11 +1109,11 @@ function createTaskListHandler(stateManager) {
976
1109
  var taskUpdateTool = {
977
1110
  name: "TaskUpdate",
978
1111
  description: `Update status, add blockers, modify details.`,
979
- schema: z13__default.default.object({
980
- taskId: z13__default.default.string().describe("The ID of the task to get"),
981
- status: z13__default.default.enum(["pending", "in_progress", "completed"]).describe("The status of the task"),
982
- addBlockedBy: z13__default.default.array(z13__default.default.string()).describe("The IDs of the tasks that are blocking this task"),
983
- addBlocks: z13__default.default.array(z13__default.default.string()).describe("The IDs of the tasks that this task is blocking")
1112
+ schema: z14__default.default.object({
1113
+ taskId: z14__default.default.string().describe("The ID of the task to get"),
1114
+ status: z14__default.default.enum(["pending", "in_progress", "completed"]).describe("The status of the task"),
1115
+ addBlockedBy: z14__default.default.array(z14__default.default.string()).describe("The IDs of the tasks that are blocking this task"),
1116
+ addBlocks: z14__default.default.array(z14__default.default.string()).describe("The IDs of the tasks that this task is blocking")
984
1117
  })
985
1118
  };
986
1119
 
@@ -1048,8 +1181,8 @@ Use this tool to:
1048
1181
  - Execute scripts and chain commands with pipes (|) or logical operators (&&, ||)
1049
1182
  - Inspect files and directories
1050
1183
  `,
1051
- schema: z13__default.default.object({
1052
- command: z13__default.default.string().describe(
1184
+ schema: z14__default.default.object({
1185
+ command: z14__default.default.string().describe(
1053
1186
  "The bash command to execute. Can include pipes (|), redirects (>, >>), logical operators (&&, ||), and shell features like command substitution $(...)."
1054
1187
  )
1055
1188
  }),
@@ -1070,18 +1203,18 @@ Usage notes:
1070
1203
  * Use multiSelect: true to allow multiple answers to be selected for a question
1071
1204
  * If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
1072
1205
  `,
1073
- schema: z13__default.default.object({
1074
- questions: z13__default.default.array(
1075
- z13__default.default.object({
1076
- question: z13__default.default.string().describe("The full question text to display"),
1077
- header: z13__default.default.string().describe("Short label for the question (max 12 characters)"),
1078
- options: z13__default.default.array(
1079
- z13__default.default.object({
1080
- label: z13__default.default.string(),
1081
- description: z13__default.default.string()
1206
+ schema: z14__default.default.object({
1207
+ questions: z14__default.default.array(
1208
+ z14__default.default.object({
1209
+ question: z14__default.default.string().describe("The full question text to display"),
1210
+ header: z14__default.default.string().describe("Short label for the question (max 12 characters)"),
1211
+ options: z14__default.default.array(
1212
+ z14__default.default.object({
1213
+ label: z14__default.default.string(),
1214
+ description: z14__default.default.string()
1082
1215
  })
1083
1216
  ).min(0).max(4).describe("Array of 0-4 choices, each with label and description"),
1084
- multiSelect: z13__default.default.boolean().describe("If true, users can select multiple options")
1217
+ multiSelect: z14__default.default.boolean().describe("If true, users can select multiple options")
1085
1218
  })
1086
1219
  )
1087
1220
  }),
@@ -1096,7 +1229,7 @@ var createAskUserQuestionHandler = () => async (args) => {
1096
1229
  };
1097
1230
  };
1098
1231
 
1099
- // node_modules/uuid/dist/esm-node/stringify.js
1232
+ // node_modules/uuid/dist/esm/stringify.js
1100
1233
  var byteToHex = [];
1101
1234
  for (let i = 0; i < 256; ++i) {
1102
1235
  byteToHex.push((i + 256).toString(16).slice(1));
@@ -1108,26 +1241,30 @@ var rnds8Pool = new Uint8Array(256);
1108
1241
  var poolPtr = rnds8Pool.length;
1109
1242
  function rng() {
1110
1243
  if (poolPtr > rnds8Pool.length - 16) {
1111
- crypto__default.default.randomFillSync(rnds8Pool);
1244
+ crypto.randomFillSync(rnds8Pool);
1112
1245
  poolPtr = 0;
1113
1246
  }
1114
1247
  return rnds8Pool.slice(poolPtr, poolPtr += 16);
1115
1248
  }
1116
- var native_default = {
1117
- randomUUID: crypto__default.default.randomUUID
1118
- };
1249
+ var native_default = { randomUUID: crypto.randomUUID };
1119
1250
 
1120
- // node_modules/uuid/dist/esm-node/v4.js
1251
+ // node_modules/uuid/dist/esm/v4.js
1121
1252
  function v4(options, buf, offset) {
1122
1253
  if (native_default.randomUUID && !buf && !options) {
1123
1254
  return native_default.randomUUID();
1124
1255
  }
1125
1256
  options = options || {};
1126
- const rnds = options.random || (options.rng || rng)();
1257
+ const rnds = options.random ?? options.rng?.() ?? rng();
1258
+ if (rnds.length < 16) {
1259
+ throw new Error("Random bytes length must be >= 16");
1260
+ }
1127
1261
  rnds[6] = rnds[6] & 15 | 64;
1128
1262
  rnds[8] = rnds[8] & 63 | 128;
1129
1263
  if (buf) {
1130
1264
  offset = offset || 0;
1265
+ if (offset < 0 || offset + 16 > buf.length) {
1266
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
1267
+ }
1131
1268
  for (let i = 0; i < 16; ++i) {
1132
1269
  buf[offset + i] = rnds[i];
1133
1270
  }
@@ -1139,9 +1276,23 @@ var v4_default = v4;
1139
1276
 
1140
1277
  // src/lib/thread-manager.ts
1141
1278
  var THREAD_TTL_SECONDS = 60 * 60 * 24 * 90;
1279
+ var APPEND_IDEMPOTENT_SCRIPT = `
1280
+ if redis.call('EXISTS', KEYS[1]) == 1 then
1281
+ return 0
1282
+ end
1283
+ for i = 2, #ARGV do
1284
+ redis.call('RPUSH', KEYS[2], ARGV[i])
1285
+ end
1286
+ redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
1287
+ redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
1288
+ return 1
1289
+ `;
1142
1290
  function getThreadKey(threadId, key) {
1143
1291
  return `thread:${threadId}:${key}`;
1144
1292
  }
1293
+ function storedMessageId(msg) {
1294
+ return msg.data.id ?? "";
1295
+ }
1145
1296
  function createThreadManager(config) {
1146
1297
  const {
1147
1298
  redis,
@@ -1151,6 +1302,7 @@ function createThreadManager(config) {
1151
1302
  deserialize = (raw) => JSON.parse(raw)
1152
1303
  } = config;
1153
1304
  const redisKey = getThreadKey(threadId, key);
1305
+ const idOf = config.idOf ?? (!config.serialize ? storedMessageId : void 0);
1154
1306
  const base = {
1155
1307
  async initialize() {
1156
1308
  await redis.del(redisKey);
@@ -1160,7 +1312,19 @@ function createThreadManager(config) {
1160
1312
  return data.map(deserialize);
1161
1313
  },
1162
1314
  async append(messages) {
1163
- if (messages.length > 0) {
1315
+ if (messages.length === 0) return;
1316
+ if (idOf) {
1317
+ const dedupId = messages.map(idOf).join(":");
1318
+ const dedupKey = getThreadKey(threadId, `dedup:${dedupId}`);
1319
+ await redis.eval(
1320
+ APPEND_IDEMPOTENT_SCRIPT,
1321
+ 2,
1322
+ dedupKey,
1323
+ redisKey,
1324
+ String(THREAD_TTL_SECONDS),
1325
+ ...messages.map(serialize)
1326
+ );
1327
+ } else {
1164
1328
  await redis.rpush(redisKey, ...messages.map(serialize));
1165
1329
  await redis.expire(redisKey, THREAD_TTL_SECONDS);
1166
1330
  }
@@ -1195,6 +1359,7 @@ function createThreadManager(config) {
1195
1359
  },
1196
1360
  createToolMessage(content, toolCallId) {
1197
1361
  return new messages.ToolMessage({
1362
+ id: v4_default(),
1198
1363
  content,
1199
1364
  tool_call_id: toolCallId
1200
1365
  }).toDict();
@@ -1267,7 +1432,7 @@ async function invokeModel({
1267
1432
  const parentWorkflowId = info.workflowExecution.workflowId;
1268
1433
  const parentRunId = info.workflowExecution.runId;
1269
1434
  const handle = client.getHandle(parentWorkflowId, parentRunId);
1270
- const { tools } = await handle.query(`get${agentName}State`);
1435
+ const { tools } = await handle.query(agentQueryName(agentName));
1271
1436
  const messages$1 = await thread.load();
1272
1437
  const response = await model.invoke(
1273
1438
  [...messages.mapStoredMessagesToChatMessages(messages$1)],
@@ -1464,9 +1629,66 @@ var toTree = async (fs, opts = {}) => {
1464
1629
  const base = basename(dir, separator) + separator;
1465
1630
  return base + subtree;
1466
1631
  };
1632
+ var FileSystemSkillProvider = class {
1633
+ constructor(baseDir) {
1634
+ this.baseDir = baseDir;
1635
+ }
1636
+ async listSkills() {
1637
+ const dirs = await this.discoverSkillDirs();
1638
+ const skills = [];
1639
+ for (const dir of dirs) {
1640
+ const raw = await promises.readFile(path.join(this.baseDir, dir, "SKILL.md"), "utf-8");
1641
+ const { frontmatter } = parseSkillFile(raw);
1642
+ skills.push(frontmatter);
1643
+ }
1644
+ return skills;
1645
+ }
1646
+ async getSkill(name) {
1647
+ const raw = await promises.readFile(
1648
+ path.join(this.baseDir, name, "SKILL.md"),
1649
+ "utf-8"
1650
+ );
1651
+ const { frontmatter, body } = parseSkillFile(raw);
1652
+ if (frontmatter.name !== name) {
1653
+ throw new Error(
1654
+ `Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"`
1655
+ );
1656
+ }
1657
+ return { ...frontmatter, instructions: body };
1658
+ }
1659
+ /**
1660
+ * Convenience method to load all skills with full instructions.
1661
+ * Returns `Skill[]` ready to pass into a workflow.
1662
+ */
1663
+ async loadAll() {
1664
+ const dirs = await this.discoverSkillDirs();
1665
+ const skills = [];
1666
+ for (const dir of dirs) {
1667
+ const raw = await promises.readFile(path.join(this.baseDir, dir, "SKILL.md"), "utf-8");
1668
+ const { frontmatter, body } = parseSkillFile(raw);
1669
+ skills.push({ ...frontmatter, instructions: body });
1670
+ }
1671
+ return skills;
1672
+ }
1673
+ async discoverSkillDirs() {
1674
+ const entries = await promises.readdir(this.baseDir, { withFileTypes: true });
1675
+ const dirs = [];
1676
+ for (const entry of entries) {
1677
+ if (!entry.isDirectory()) continue;
1678
+ try {
1679
+ await promises.readFile(path.join(this.baseDir, entry.name, "SKILL.md"), "utf-8");
1680
+ dirs.push(entry.name);
1681
+ } catch {
1682
+ }
1683
+ }
1684
+ return dirs;
1685
+ }
1686
+ };
1467
1687
 
1468
- exports.AGENT_HANDLER_NAMES = AGENT_HANDLER_NAMES;
1688
+ exports.FileSystemSkillProvider = FileSystemSkillProvider;
1469
1689
  exports.ZeitlichPlugin = ZeitlichPlugin;
1690
+ exports.agentQueryName = agentQueryName;
1691
+ exports.agentStateChangeUpdateName = agentStateChangeUpdateName;
1470
1692
  exports.askUserQuestionTool = askUserQuestionTool;
1471
1693
  exports.bashTool = bashTool;
1472
1694
  exports.createAgentStateManager = createAgentStateManager;
@@ -1475,6 +1697,8 @@ exports.createBashHandler = createBashHandler;
1475
1697
  exports.createBashToolDescription = createBashToolDescription;
1476
1698
  exports.createEditHandler = createEditHandler;
1477
1699
  exports.createGlobHandler = createGlobHandler;
1700
+ exports.createReadSkillHandler = createReadSkillHandler;
1701
+ exports.createReadSkillTool = createReadSkillTool;
1478
1702
  exports.createSession = createSession;
1479
1703
  exports.createSharedActivities = createSharedActivities;
1480
1704
  exports.createSubagentTool = createSubagentTool;
@@ -1492,6 +1716,7 @@ exports.grepTool = grepTool;
1492
1716
  exports.hasNoOtherToolCalls = hasNoOtherToolCalls;
1493
1717
  exports.invokeModel = invokeModel;
1494
1718
  exports.isTerminalStatus = isTerminalStatus;
1719
+ exports.parseSkillFile = parseSkillFile;
1495
1720
  exports.proxyDefaultThreadOps = proxyDefaultThreadOps;
1496
1721
  exports.readFileTool = readFileTool;
1497
1722
  exports.taskCreateTool = taskCreateTool;