@mikode13/harness 11.0.1 → 11.2.0

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.
Files changed (120) hide show
  1. package/README.md +186 -16
  2. package/dist/agent/domain/agent.d.ts +41 -3
  3. package/dist/agent/domain/agent.js.map +1 -1
  4. package/dist/agent/domain/approval.d.ts +20 -0
  5. package/dist/agent/domain/approval.js.map +1 -1
  6. package/dist/agent/domain/inbox.d.ts +42 -0
  7. package/dist/agent/domain/inbox.js +81 -0
  8. package/dist/agent/domain/inbox.js.map +1 -0
  9. package/dist/agent/domain/oneRunAtATime.d.ts +17 -0
  10. package/dist/agent/domain/oneRunAtATime.js +32 -0
  11. package/dist/agent/domain/oneRunAtATime.js.map +1 -0
  12. package/dist/agent/domain/runContext.d.ts +9 -0
  13. package/dist/agent/domain/runContext.js +5 -0
  14. package/dist/agent/domain/runContext.js.map +1 -1
  15. package/dist/agent/domain/session.d.ts +15 -0
  16. package/dist/agent/domain/session.js +80 -0
  17. package/dist/agent/domain/session.js.map +1 -0
  18. package/dist/engines/claude/infrastructure/model/claudeAgent.d.ts +6 -2
  19. package/dist/engines/claude/infrastructure/model/claudeAgent.js +20 -5
  20. package/dist/engines/claude/infrastructure/model/claudeAgent.js.map +1 -1
  21. package/dist/engines/codex/infrastructure/model/codexAgent.d.ts +5 -1
  22. package/dist/engines/codex/infrastructure/model/codexAgent.js +13 -2
  23. package/dist/engines/codex/infrastructure/model/codexAgent.js.map +1 -1
  24. package/dist/engines/domain/model/backgroundAgents.d.ts +5 -2
  25. package/dist/engines/domain/model/backgroundAgents.js +11 -3
  26. package/dist/engines/domain/model/backgroundAgents.js.map +1 -1
  27. package/dist/engines/domain/model/compactor.d.ts +40 -0
  28. package/dist/engines/domain/model/compactor.js +5 -0
  29. package/dist/engines/domain/model/compactor.js.map +1 -0
  30. package/dist/engines/domain/model/llmAgent.d.ts +53 -2
  31. package/dist/engines/domain/model/llmAgent.js +419 -24
  32. package/dist/engines/domain/model/llmAgent.js.map +1 -1
  33. package/dist/engines/domain/model/recentCompaction.d.ts +39 -0
  34. package/dist/engines/domain/model/recentCompaction.js +119 -0
  35. package/dist/engines/domain/model/recentCompaction.js.map +1 -0
  36. package/dist/engines/domain/model/stepContext.js +3 -0
  37. package/dist/engines/domain/model/stepContext.js.map +1 -1
  38. package/dist/engines/domain/model/triagedCompaction.d.ts +27 -0
  39. package/dist/engines/domain/model/triagedCompaction.js +60 -0
  40. package/dist/engines/domain/model/triagedCompaction.js.map +1 -0
  41. package/dist/factory/infrastructure/agentLLMFactory.d.ts +56 -15
  42. package/dist/factory/infrastructure/agentLLMFactory.js +144 -25
  43. package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
  44. package/dist/index.d.ts +6 -2
  45. package/dist/index.js +3 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/llm/domain/contextTriage.d.ts +26 -0
  48. package/dist/llm/domain/contextTriage.js +3 -0
  49. package/dist/llm/domain/contextTriage.js.map +1 -0
  50. package/dist/llm/domain/llm.d.ts +18 -1
  51. package/dist/llm/domain/llm.js.map +1 -1
  52. package/dist/llm/domain/message.d.ts +18 -1
  53. package/dist/llm/domain/message.js +20 -0
  54. package/dist/llm/domain/message.js.map +1 -1
  55. package/dist/llm/infrastructure/claudeLLMClient.d.ts +4 -5
  56. package/dist/llm/infrastructure/claudeLLMClient.js +39 -13
  57. package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
  58. package/dist/llm/infrastructure/jevContextTriage.d.ts +18 -0
  59. package/dist/llm/infrastructure/jevContextTriage.js +92 -0
  60. package/dist/llm/infrastructure/jevContextTriage.js.map +1 -0
  61. package/dist/llm/infrastructure/openAILLMClient.d.ts +4 -5
  62. package/dist/llm/infrastructure/openAILLMClient.js +20 -9
  63. package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
  64. package/dist/orchestration/domain/model/orchestratorAgent.d.ts +9 -1
  65. package/dist/orchestration/domain/model/orchestratorAgent.js +20 -3
  66. package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
  67. package/dist/recovery/domain/history.d.ts +10 -3
  68. package/dist/recovery/domain/history.js +1 -0
  69. package/dist/recovery/domain/history.js.map +1 -1
  70. package/dist/recovery/domain/recoveryStore.d.ts +14 -2
  71. package/dist/recovery/domain/recoveryStore.js.map +1 -1
  72. package/dist/recovery/infrastructure/fileRecoveryStore.d.ts +21 -2
  73. package/dist/recovery/infrastructure/fileRecoveryStore.js +58 -6
  74. package/dist/recovery/infrastructure/fileRecoveryStore.js.map +1 -1
  75. package/dist/recovery/infrastructure/folderClaims.d.ts +27 -0
  76. package/dist/recovery/infrastructure/folderClaims.js +124 -0
  77. package/dist/recovery/infrastructure/folderClaims.js.map +1 -0
  78. package/dist/shared/domain/session.d.ts +12 -0
  79. package/dist/shared/domain/session.js +2 -0
  80. package/dist/shared/domain/session.js.map +1 -0
  81. package/dist/tools/domain/preparedCall.d.ts +5 -2
  82. package/dist/tools/domain/preparedCall.js +7 -1
  83. package/dist/tools/domain/preparedCall.js.map +1 -1
  84. package/dist/tools/domain/readRegistry.d.ts +11 -0
  85. package/dist/tools/domain/readRegistry.js.map +1 -1
  86. package/dist/tools/infrastructure/agentAsTool.d.ts +6 -0
  87. package/dist/tools/infrastructure/agentAsTool.js +9 -0
  88. package/dist/tools/infrastructure/agentAsTool.js.map +1 -1
  89. package/dist/tools/infrastructure/commandTool.js +6 -1
  90. package/dist/tools/infrastructure/commandTool.js.map +1 -1
  91. package/dist/tools/infrastructure/exploreTool.js +2 -0
  92. package/dist/tools/infrastructure/exploreTool.js.map +1 -1
  93. package/dist/tools/infrastructure/fileTools.d.ts +2 -0
  94. package/dist/tools/infrastructure/fileTools.js +10 -1
  95. package/dist/tools/infrastructure/fileTools.js.map +1 -1
  96. package/dist/tools/infrastructure/removeWorktreeTool.d.ts +23 -0
  97. package/dist/tools/infrastructure/removeWorktreeTool.js +178 -0
  98. package/dist/tools/infrastructure/removeWorktreeTool.js.map +1 -0
  99. package/dist/tools/infrastructure/subagents.d.ts +3 -1
  100. package/dist/tools/infrastructure/subagents.js +2 -1
  101. package/dist/tools/infrastructure/subagents.js.map +1 -1
  102. package/dist/tools/infrastructure/trackedEdits.js +1 -0
  103. package/dist/tools/infrastructure/trackedEdits.js.map +1 -1
  104. package/dist/tools/infrastructure/workspaceWrites.d.ts +17 -1
  105. package/dist/tools/infrastructure/workspaceWrites.js +48 -17
  106. package/dist/tools/infrastructure/workspaceWrites.js.map +1 -1
  107. package/dist/tools/infrastructure/worktrees.d.ts +49 -0
  108. package/dist/tools/infrastructure/worktrees.js +154 -0
  109. package/dist/tools/infrastructure/worktrees.js.map +1 -0
  110. package/dist/tools/infrastructure/writeCeiling.d.ts +16 -0
  111. package/dist/tools/infrastructure/writeCeiling.js +49 -0
  112. package/dist/tools/infrastructure/writeCeiling.js.map +1 -0
  113. package/dist/tools/infrastructure/writeSession.d.ts +5 -1
  114. package/dist/tools/infrastructure/writeSession.js +7 -3
  115. package/dist/tools/infrastructure/writeSession.js.map +1 -1
  116. package/dist/workflow/domain/workflow.d.ts +3 -0
  117. package/dist/workflow/domain/workflow.js.map +1 -1
  118. package/dist/workflow/domain/workflowRunner.js +8 -2
  119. package/dist/workflow/domain/workflowRunner.js.map +1 -1
  120. package/package.json +2 -1
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Where the recent compactor cuts `context`, the context the model was last sent with what the
3
+ * run added since: everything before the cut is summarized, and the run's prompt and its last
4
+ * answers, each with its tool results, are kept word for word after the summary. Answers of
5
+ * earlier runs are always summarized, so what is kept starts at the prompt and keeps its order.
6
+ * `undefined` when there is no answer to summarize.
7
+ *
8
+ * @param prompt the run's prompt. Found by identity; when it is not there, an earlier compaction
9
+ * of this run kept it, first, so it is in the summary's message, which is summarized again.
10
+ */
11
+ export function recentCut(context, prompt, keptSteps) {
12
+ const found = context.indexOf(prompt);
13
+ const promptAt = Math.max(found, 0);
14
+ const answers = context.flatMap((message, index) => message.role === 'assistant' && index > promptAt ? [index] : []);
15
+ const earlier = context.slice(0, promptAt).some(message => message.role === 'assistant');
16
+ // At least one answer is summarized, or compacting would only add a summary.
17
+ const kept = Math.max(0, Math.min(keptSteps, earlier ? answers.length : answers.length - 1));
18
+ // A step starts at an answer, so each call keeps its results, which follow it.
19
+ const cut = kept === 0 ? context.length : (answers[answers.length - kept] ?? context.length);
20
+ const summarized = found === -1
21
+ ? context.slice(0, cut)
22
+ : [...context.slice(0, found), ...context.slice(found + 1, cut)];
23
+ if (!summarized.some(message => message.role === 'assistant'))
24
+ return undefined;
25
+ return {
26
+ summarized,
27
+ kept: withoutReplayedReasoning([prompt, ...context.slice(cut)]),
28
+ };
29
+ }
30
+ /**
31
+ * Copies of `messages` without what only a provider reads back, which a changed context before
32
+ * it may no longer accept: its signed reasoning and its compactions. The last answer keeps its
33
+ * reasoning, since the provider needs it to go on with that answer's tool calls.
34
+ */
35
+ export function withoutReplayedReasoning(messages) {
36
+ const last = messages.findLastIndex(message => message.role === 'assistant');
37
+ return messages.flatMap((message, index) => {
38
+ const content = structuredClone(message.content).filter(part => part.type !== 'compaction' &&
39
+ part.type !== 'contextSummary' &&
40
+ (part.type !== 'providerData' || index === last));
41
+ return content.length > 0 ? [{ role: message.role, content }] : [];
42
+ });
43
+ }
44
+ /** What the recent compactor keeps, as its summary requests tell the model. */
45
+ export function recentlyKept(keptSteps) {
46
+ return `the user's request for this run and your last ${String(keptSteps)} answers, with their tool results,`;
47
+ }
48
+ /**
49
+ * What the model is asked once its context passed the threshold, after that whole context.
50
+ *
51
+ * @param kept what is kept word for word after the summary, as `recentlyKept` says it.
52
+ */
53
+ export function summaryRequest(kept) {
54
+ return {
55
+ role: 'user',
56
+ content: [
57
+ {
58
+ type: 'text',
59
+ text: `Your context is about to be compacted. Write a summary of this conversation so far that lets you go on without it: what the user asked, what you found and decided, what you changed and why, what is left to do, and what you would need to read again. After your summary, only ${kept} are kept word for word, and not your reasoning in them: say what you were thinking and planning there too. Answer with the summary alone, in plain text, and call no tool.`,
60
+ },
61
+ ],
62
+ };
63
+ }
64
+ // A context that no longer fits cannot be sent again to ask for its summary, so the model reads
65
+ // it as text, bounded to fit any model the harness offers.
66
+ const transcriptCharacters = 300_000;
67
+ const partCharacters = 3_000;
68
+ /**
69
+ * What the model is asked when the context no longer fit: the part to summarize, as text, since
70
+ * it can no longer be sent as it was.
71
+ */
72
+ export function transcriptSummaryRequest(summarized, kept) {
73
+ return {
74
+ role: 'user',
75
+ content: [
76
+ {
77
+ type: 'text',
78
+ text: [
79
+ `Below is the start of a conversation in which you, an agent, work on a task for the user. It no longer fits your context, so write a summary of it that lets you go on without it: what the user asked, what you found and decided, what you changed and why, what is left to do, and what you would need to read again. Only ${kept} are kept word for word after your summary. Answer with the summary alone, in plain text.`,
80
+ '',
81
+ messagesAsText(summarized, transcriptCharacters, partCharacters),
82
+ ].join('\n'),
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ /**
88
+ * `messages` as plain text, for a model to summarize: who said or did what, each part cut at
89
+ * `partChars`, and the whole at `maxChars`, marked when cut.
90
+ */
91
+ export function messagesAsText(messages, maxChars, partChars) {
92
+ const lines = messages.flatMap(message => message.content.flatMap(part => partAsText(message, part, partChars)));
93
+ const text = lines.join('\n');
94
+ return text.length <= maxChars ? text : `${text.slice(0, maxChars)}\n[the rest was cut]`;
95
+ }
96
+ function partAsText(message, part, partChars) {
97
+ switch (part.type) {
98
+ case 'text':
99
+ return [`${message.role}: ${shorten(part.text, partChars)}`];
100
+ case 'toolCall':
101
+ return [
102
+ `${message.role} called ${part.name} ${shorten(JSON.stringify(part.input), partChars)}`,
103
+ ];
104
+ case 'toolResult':
105
+ return [
106
+ `${part.name} ${part.isError ? 'failed' : 'returned'}: ${shorten(part.output, partChars)}`,
107
+ ];
108
+ // Readable reasoning, signed blocks and summaries only a provider reads are not the work.
109
+ case 'reasoning':
110
+ case 'providerData':
111
+ case 'compaction':
112
+ case 'contextSummary':
113
+ return [];
114
+ }
115
+ }
116
+ function shorten(text, max) {
117
+ return text.length <= max ? text : `${text.slice(0, max)}…`;
118
+ }
119
+ //# sourceMappingURL=recentCompaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recentCompaction.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/recentCompaction.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CACxB,OAAkB,EAClB,MAAe,EACf,SAAiB;IAEjB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAClD,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAC/D,CAAC;IACF,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;IACzF,6EAA6E;IAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7F,+EAA+E;IAC/E,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7F,MAAM,UAAU,GACf,KAAK,KAAK,CAAC,CAAC;QACX,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;QACvB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC;QAAE,OAAO,SAAS,CAAC;IAChF,OAAO;QACN,UAAU;QACV,IAAI,EAAE,wBAAwB,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;KAC/D,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAmB;IAC3D,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;IAC7E,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;QAC1C,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CACtD,IAAI,CAAC,EAAE,CACN,IAAI,CAAC,IAAI,KAAK,YAAY;YAC1B,IAAI,CAAC,IAAI,KAAK,gBAAgB;YAC9B,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,KAAK,IAAI,CAAC,CACjD,CAAC;QACF,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC7C,OAAO,iDAAiD,MAAM,CAAC,SAAS,CAAC,oCAAoC,CAAC;AAC/G,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IAC1C,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE;YACR;gBACC,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,qRAAqR,IAAI,6KAA6K;aAC5c;SACD;KACD,CAAC;AACH,CAAC;AAED,gGAAgG;AAChG,2DAA2D;AAC3D,MAAM,oBAAoB,GAAG,OAAO,CAAC;AACrC,MAAM,cAAc,GAAG,KAAK,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAqB,EAAE,IAAY;IAC3E,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE;YACR;gBACC,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE;oBACL,iUAAiU,IAAI,2FAA2F;oBACha,EAAE;oBACF,cAAc,CAAC,UAAU,EAAE,oBAAoB,EAAE,cAAc,CAAC;iBAChE,CAAC,IAAI,CAAC,IAAI,CAAC;aACZ;SACD;KACD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAmB,EAAE,QAAgB,EAAE,SAAiB;IACtF,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CACxC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CACrE,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,sBAAsB,CAAC;AAC1F,CAAC;AAED,SAAS,UAAU,CAAC,OAAgB,EAAE,IAAiB,EAAE,SAAiB;IACzE,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,MAAM;YACV,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;QAC9D,KAAK,UAAU;YACd,OAAO;gBACN,GAAG,OAAO,CAAC,IAAI,WAAW,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,EAAE;aACvF,CAAC;QACH,KAAK,YAAY;YAChB,OAAO;gBACN,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE;aAC1F,CAAC;QACH,0FAA0F;QAC1F,KAAK,WAAW,CAAC;QACjB,KAAK,cAAc,CAAC;QACpB,KAAK,YAAY,CAAC;QAClB,KAAK,gBAAgB;YACpB,OAAO,EAAE,CAAC;IACZ,CAAC;AACF,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,GAAW;IACzC,OAAO,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC;AAC7D,CAAC","sourcesContent":["import type { Message, MessagePart } from '#src/llm/domain/message';\n\n/**\n * Where the recent compactor cuts `context`, the context the model was last sent with what the\n * run added since: everything before the cut is summarized, and the run's prompt and its last\n * answers, each with its tool results, are kept word for word after the summary. Answers of\n * earlier runs are always summarized, so what is kept starts at the prompt and keeps its order.\n * `undefined` when there is no answer to summarize.\n *\n * @param prompt the run's prompt. Found by identity; when it is not there, an earlier compaction\n * of this run kept it, first, so it is in the summary's message, which is summarized again.\n */\nexport function recentCut(\n\tcontext: Message[],\n\tprompt: Message,\n\tkeptSteps: number,\n): { summarized: Message[]; kept: Message[] } | undefined {\n\tconst found = context.indexOf(prompt);\n\tconst promptAt = Math.max(found, 0);\n\tconst answers = context.flatMap((message, index) =>\n\t\tmessage.role === 'assistant' && index > promptAt ? [index] : [],\n\t);\n\tconst earlier = context.slice(0, promptAt).some(message => message.role === 'assistant');\n\t// At least one answer is summarized, or compacting would only add a summary.\n\tconst kept = Math.max(0, Math.min(keptSteps, earlier ? answers.length : answers.length - 1));\n\t// A step starts at an answer, so each call keeps its results, which follow it.\n\tconst cut = kept === 0 ? context.length : (answers[answers.length - kept] ?? context.length);\n\tconst summarized =\n\t\tfound === -1\n\t\t\t? context.slice(0, cut)\n\t\t\t: [...context.slice(0, found), ...context.slice(found + 1, cut)];\n\tif (!summarized.some(message => message.role === 'assistant')) return undefined;\n\treturn {\n\t\tsummarized,\n\t\tkept: withoutReplayedReasoning([prompt, ...context.slice(cut)]),\n\t};\n}\n\n/**\n * Copies of `messages` without what only a provider reads back, which a changed context before\n * it may no longer accept: its signed reasoning and its compactions. The last answer keeps its\n * reasoning, since the provider needs it to go on with that answer's tool calls.\n */\nexport function withoutReplayedReasoning(messages: Message[]): Message[] {\n\tconst last = messages.findLastIndex(message => message.role === 'assistant');\n\treturn messages.flatMap((message, index) => {\n\t\tconst content = structuredClone(message.content).filter(\n\t\t\tpart =>\n\t\t\t\tpart.type !== 'compaction' &&\n\t\t\t\tpart.type !== 'contextSummary' &&\n\t\t\t\t(part.type !== 'providerData' || index === last),\n\t\t);\n\t\treturn content.length > 0 ? [{ role: message.role, content }] : [];\n\t});\n}\n\n/** What the recent compactor keeps, as its summary requests tell the model. */\nexport function recentlyKept(keptSteps: number): string {\n\treturn `the user's request for this run and your last ${String(keptSteps)} answers, with their tool results,`;\n}\n\n/**\n * What the model is asked once its context passed the threshold, after that whole context.\n *\n * @param kept what is kept word for word after the summary, as `recentlyKept` says it.\n */\nexport function summaryRequest(kept: string): Message {\n\treturn {\n\t\trole: 'user',\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: 'text',\n\t\t\t\ttext: `Your context is about to be compacted. Write a summary of this conversation so far that lets you go on without it: what the user asked, what you found and decided, what you changed and why, what is left to do, and what you would need to read again. After your summary, only ${kept} are kept word for word, and not your reasoning in them: say what you were thinking and planning there too. Answer with the summary alone, in plain text, and call no tool.`,\n\t\t\t},\n\t\t],\n\t};\n}\n\n// A context that no longer fits cannot be sent again to ask for its summary, so the model reads\n// it as text, bounded to fit any model the harness offers.\nconst transcriptCharacters = 300_000;\nconst partCharacters = 3_000;\n\n/**\n * What the model is asked when the context no longer fit: the part to summarize, as text, since\n * it can no longer be sent as it was.\n */\nexport function transcriptSummaryRequest(summarized: Message[], kept: string): Message {\n\treturn {\n\t\trole: 'user',\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: 'text',\n\t\t\t\ttext: [\n\t\t\t\t\t`Below is the start of a conversation in which you, an agent, work on a task for the user. It no longer fits your context, so write a summary of it that lets you go on without it: what the user asked, what you found and decided, what you changed and why, what is left to do, and what you would need to read again. Only ${kept} are kept word for word after your summary. Answer with the summary alone, in plain text.`,\n\t\t\t\t\t'',\n\t\t\t\t\tmessagesAsText(summarized, transcriptCharacters, partCharacters),\n\t\t\t\t].join('\\n'),\n\t\t\t},\n\t\t],\n\t};\n}\n\n/**\n * `messages` as plain text, for a model to summarize: who said or did what, each part cut at\n * `partChars`, and the whole at `maxChars`, marked when cut.\n */\nexport function messagesAsText(messages: Message[], maxChars: number, partChars: number): string {\n\tconst lines = messages.flatMap(message =>\n\t\tmessage.content.flatMap(part => partAsText(message, part, partChars)),\n\t);\n\tconst text = lines.join('\\n');\n\treturn text.length <= maxChars ? text : `${text.slice(0, maxChars)}\\n[the rest was cut]`;\n}\n\nfunction partAsText(message: Message, part: MessagePart, partChars: number): string[] {\n\tswitch (part.type) {\n\t\tcase 'text':\n\t\t\treturn [`${message.role}: ${shorten(part.text, partChars)}`];\n\t\tcase 'toolCall':\n\t\t\treturn [\n\t\t\t\t`${message.role} called ${part.name} ${shorten(JSON.stringify(part.input), partChars)}`,\n\t\t\t];\n\t\tcase 'toolResult':\n\t\t\treturn [\n\t\t\t\t`${part.name} ${part.isError ? 'failed' : 'returned'}: ${shorten(part.output, partChars)}`,\n\t\t\t];\n\t\t// Readable reasoning, signed blocks and summaries only a provider reads are not the work.\n\t\tcase 'reasoning':\n\t\tcase 'providerData':\n\t\tcase 'compaction':\n\t\tcase 'contextSummary':\n\t\t\treturn [];\n\t}\n}\n\nfunction shorten(text: string, max: number): string {\n\treturn text.length <= max ? text : `${text.slice(0, max)}…`;\n}\n"]}
@@ -39,6 +39,9 @@ export function measureContext({ instructions, providerDataSource, tools, contex
39
39
  measured.reasoning += JSON.stringify(part.data).length;
40
40
  }
41
41
  break;
42
+ // The agent sends the context derived from it, where the summary is text.
43
+ case 'contextSummary':
44
+ break;
42
45
  case 'toolCall':
43
46
  calls.set(part.id, part);
44
47
  measured.toolCalls += JSON.stringify(part.input).length;
@@ -1 +1 @@
1
- {"version":3,"file":"stepContext.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/stepContext.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StepContext } from '#src/agent/domain/agent';\nimport { type Message, sentContext, type ToolCallPart } from '#src/llm/domain/message';\nimport type { ToolDefinition } from '#src/llm/domain/tool';\n\n// Enough to name what fills a context without listing every result of a long run.\nconst largestResults = 5;\n\n/**\n * What one call to the model sends, in characters, by where it comes from. Characters, not\n * tokens: the harness does not tokenize, and the provider's count arrives with the answer.\n * `summarize` names a tool call as its tool does, or gives nothing.\n */\nexport function measureContext({\n\tinstructions,\n\tproviderDataSource,\n\ttools,\n\tcontext,\n\tsummarize,\n}: {\n\tinstructions: string | undefined;\n\t/** The only `providerData` the client sends back; every other is dropped. */\n\tproviderDataSource: string | undefined;\n\ttools: ToolDefinition[];\n\tcontext: Message[];\n\tsummarize: (call: ToolCallPart) => string | undefined;\n}): StepContext {\n\tconst measured: StepContext = {\n\t\tinstructions: instructions?.length ?? 0,\n\t\ttools: JSON.stringify(tools).length,\n\t\tmessages: 0,\n\t\treasoning: 0,\n\t\ttoolCalls: 0,\n\t\ttoolResults: [],\n\t\tlargestResults: [],\n\t};\n\tconst calls = new Map<string, ToolCallPart>();\n\tconst byTool = new Map<string, { characters: number; calls: number }>();\n\tconst results: StepContext['largestResults'] = [];\n\n\tfor (const part of sentContext(context, providerDataSource).flatMap(message => message.content)) {\n\t\tswitch (part.type) {\n\t\t\tcase 'text':\n\t\t\t\tmeasured.messages += part.text.length;\n\t\t\t\tbreak;\n\t\t\t// The provider's summary of the turns before it stands in for their messages.\n\t\t\tcase 'compaction':\n\t\t\t\tif (part.source === providerDataSource) {\n\t\t\t\t\tmeasured.messages += JSON.stringify(part.data).length;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t// Readable reasoning is never sent back: only the provider's own blocks are, which\n\t\t\t// already hold it, signed or encrypted.\n\t\t\tcase 'reasoning':\n\t\t\t\tbreak;\n\t\t\tcase 'providerData':\n\t\t\t\tif (providerDataSource === undefined || part.source === providerDataSource) {\n\t\t\t\t\tmeasured.reasoning += JSON.stringify(part.data).length;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'toolCall':\n\t\t\t\tcalls.set(part.id, part);\n\t\t\t\tmeasured.toolCalls += JSON.stringify(part.input).length;\n\t\t\t\tbreak;\n\t\t\tcase 'toolResult': {\n\t\t\t\tconst characters = part.output.length;\n\t\t\t\tconst tool = byTool.get(part.name) ?? { characters: 0, calls: 0 };\n\t\t\t\ttool.characters += characters;\n\t\t\t\ttool.calls += 1;\n\t\t\t\tbyTool.set(part.name, tool);\n\t\t\t\tconst call = calls.get(part.callId);\n\t\t\t\tconst summary = call && summarize(call);\n\t\t\t\tresults.push({ tool: part.name, characters, ...(summary ? { summary } : {}) });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tmeasured.toolResults = [...byTool]\n\t\t.map(([tool, { characters, calls }]) => ({ tool, characters, calls }))\n\t\t.sort((a, b) => b.characters - a.characters);\n\tmeasured.largestResults = results\n\t\t.sort((a, b) => b.characters - a.characters)\n\t\t.slice(0, largestResults);\n\treturn measured;\n}\n"]}
1
+ {"version":3,"file":"stepContext.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/stepContext.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StepContext } from '#src/agent/domain/agent';\nimport { type Message, sentContext, type ToolCallPart } from '#src/llm/domain/message';\nimport type { ToolDefinition } from '#src/llm/domain/tool';\n\n// Enough to name what fills a context without listing every result of a long run.\nconst largestResults = 5;\n\n/**\n * What one call to the model sends, in characters, by where it comes from. Characters, not\n * tokens: the harness does not tokenize, and the provider's count arrives with the answer.\n * `summarize` names a tool call as its tool does, or gives nothing.\n */\nexport function measureContext({\n\tinstructions,\n\tproviderDataSource,\n\ttools,\n\tcontext,\n\tsummarize,\n}: {\n\tinstructions: string | undefined;\n\t/** The only `providerData` the client sends back; every other is dropped. */\n\tproviderDataSource: string | undefined;\n\ttools: ToolDefinition[];\n\tcontext: Message[];\n\tsummarize: (call: ToolCallPart) => string | undefined;\n}): StepContext {\n\tconst measured: StepContext = {\n\t\tinstructions: instructions?.length ?? 0,\n\t\ttools: JSON.stringify(tools).length,\n\t\tmessages: 0,\n\t\treasoning: 0,\n\t\ttoolCalls: 0,\n\t\ttoolResults: [],\n\t\tlargestResults: [],\n\t};\n\tconst calls = new Map<string, ToolCallPart>();\n\tconst byTool = new Map<string, { characters: number; calls: number }>();\n\tconst results: StepContext['largestResults'] = [];\n\n\tfor (const part of sentContext(context, providerDataSource).flatMap(message => message.content)) {\n\t\tswitch (part.type) {\n\t\t\tcase 'text':\n\t\t\t\tmeasured.messages += part.text.length;\n\t\t\t\tbreak;\n\t\t\t// The provider's summary of the turns before it stands in for their messages.\n\t\t\tcase 'compaction':\n\t\t\t\tif (part.source === providerDataSource) {\n\t\t\t\t\tmeasured.messages += JSON.stringify(part.data).length;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t// Readable reasoning is never sent back: only the provider's own blocks are, which\n\t\t\t// already hold it, signed or encrypted.\n\t\t\tcase 'reasoning':\n\t\t\t\tbreak;\n\t\t\tcase 'providerData':\n\t\t\t\tif (providerDataSource === undefined || part.source === providerDataSource) {\n\t\t\t\t\tmeasured.reasoning += JSON.stringify(part.data).length;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t// The agent sends the context derived from it, where the summary is text.\n\t\t\tcase 'contextSummary':\n\t\t\t\tbreak;\n\t\t\tcase 'toolCall':\n\t\t\t\tcalls.set(part.id, part);\n\t\t\t\tmeasured.toolCalls += JSON.stringify(part.input).length;\n\t\t\t\tbreak;\n\t\t\tcase 'toolResult': {\n\t\t\t\tconst characters = part.output.length;\n\t\t\t\tconst tool = byTool.get(part.name) ?? { characters: 0, calls: 0 };\n\t\t\t\ttool.characters += characters;\n\t\t\t\ttool.calls += 1;\n\t\t\t\tbyTool.set(part.name, tool);\n\t\t\t\tconst call = calls.get(part.callId);\n\t\t\t\tconst summary = call && summarize(call);\n\t\t\t\tresults.push({ tool: part.name, characters, ...(summary ? { summary } : {}) });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tmeasured.toolResults = [...byTool]\n\t\t.map(([tool, { characters, calls }]) => ({ tool, characters, calls }))\n\t\t.sort((a, b) => b.characters - a.characters);\n\tmeasured.largestResults = results\n\t\t.sort((a, b) => b.characters - a.characters)\n\t\t.slice(0, largestResults);\n\treturn measured;\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import type { TriagedResult } from '#src/llm/domain/contextTriage';
2
+ import type { Message } from '#src/llm/domain/message';
3
+ /** A tool result before the model's last answer, which a triage judges. */
4
+ export interface TriageCandidate extends TriagedResult {
5
+ callId: string;
6
+ }
7
+ /**
8
+ * The tool results of `context` a triage judges: each one before the model's last answer whose
9
+ * call is there too. The last answer's own results are always kept.
10
+ */
11
+ export declare function triageCandidates(context: Message[]): TriageCandidate[];
12
+ /**
13
+ * Where a triaged compaction cuts `context`: the run's prompt, each tool call the triage kept
14
+ * with its result, and the model's last answer with everything after it are kept word for word,
15
+ * in their order; everything else is summarized. `undefined` when the model has answered once
16
+ * at most, or the triage kept every answer whole, so a summary would replace nothing.
17
+ *
18
+ * @param prompt the run's prompt, found by identity as in `recentCut`; when it is not there, it
19
+ * is kept first.
20
+ * @param kept the ids of the tool calls kept with their results.
21
+ */
22
+ export declare function triagedCut(context: Message[], prompt: Message, kept: ReadonlySet<string>): {
23
+ summarized: Message[];
24
+ kept: Message[];
25
+ } | undefined;
26
+ /** What a triaged compaction keeps, as its summary requests tell the model. */
27
+ export declare function triagedKept(kept: TriageCandidate[]): string;
@@ -0,0 +1,60 @@
1
+ import { withoutReplayedReasoning } from "./recentCompaction.js";
2
+ /**
3
+ * The tool results of `context` a triage judges: each one before the model's last answer whose
4
+ * call is there too. The last answer's own results are always kept.
5
+ */
6
+ export function triageCandidates(context) {
7
+ const last = context.findLastIndex(message => message.role === 'assistant');
8
+ const before = context.slice(0, Math.max(last, 0));
9
+ const calls = new Map(before.flatMap(message => message.content.flatMap(part => (part.type === 'toolCall' ? [[part.id, part]] : []))));
10
+ return before.flatMap(message => message.content.flatMap(part => {
11
+ const call = part.type === 'toolResult' ? calls.get(part.callId) : undefined;
12
+ return part.type === 'toolResult' && call
13
+ ? [{ callId: part.callId, tool: call.name, input: call.input, output: part.output }]
14
+ : [];
15
+ }));
16
+ }
17
+ /**
18
+ * Where a triaged compaction cuts `context`: the run's prompt, each tool call the triage kept
19
+ * with its result, and the model's last answer with everything after it are kept word for word,
20
+ * in their order; everything else is summarized. `undefined` when the model has answered once
21
+ * at most, or the triage kept every answer whole, so a summary would replace nothing.
22
+ *
23
+ * @param prompt the run's prompt, found by identity as in `recentCut`; when it is not there, it
24
+ * is kept first.
25
+ * @param kept the ids of the tool calls kept with their results.
26
+ */
27
+ export function triagedCut(context, prompt, kept) {
28
+ const last = context.findLastIndex(message => message.role === 'assistant');
29
+ const found = context.indexOf(prompt);
30
+ const isKept = (part) => (part.type === 'toolCall' && kept.has(part.id)) ||
31
+ (part.type === 'toolResult' && kept.has(part.callId));
32
+ const keptMessages = found === -1 ? [prompt] : [];
33
+ const summarized = [];
34
+ context.slice(0, Math.max(last, 0)).forEach((message, index) => {
35
+ if (index === found) {
36
+ keptMessages.push(prompt);
37
+ return;
38
+ }
39
+ const keptParts = message.content.filter(isKept);
40
+ const rest = message.content.filter(part => !isKept(part));
41
+ if (keptParts.length > 0)
42
+ keptMessages.push({ role: message.role, content: keptParts });
43
+ if (rest.length > 0)
44
+ summarized.push({ role: message.role, content: rest });
45
+ });
46
+ if (!summarized.some(message => message.role === 'assistant'))
47
+ return undefined;
48
+ return { summarized, kept: withoutReplayedReasoning([...keptMessages, ...context.slice(last)]) };
49
+ }
50
+ /** What a triaged compaction keeps, as its summary requests tell the model. */
51
+ export function triagedKept(kept) {
52
+ const calls = kept.map(({ tool, input }) => `${tool} ${shorten(JSON.stringify(input), 200)}`);
53
+ return calls.length === 0
54
+ ? "the user's request for this run and your last answer, with its tool results,"
55
+ : `the user's request for this run, your last answer with its tool results, and the results of these earlier tool calls: ${calls.join('; ')},`;
56
+ }
57
+ function shorten(text, max) {
58
+ return text.length <= max ? text : `${text.slice(0, max)}…`;
59
+ }
60
+ //# sourceMappingURL=triagedCompaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"triagedCompaction.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/triagedCompaction.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TriagedResult } from '#src/llm/domain/contextTriage';\nimport type { Message, MessagePart } from '#src/llm/domain/message';\nimport { withoutReplayedReasoning } from './recentCompaction.ts';\n\n/** A tool result before the model's last answer, which a triage judges. */\nexport interface TriageCandidate extends TriagedResult {\n\tcallId: string;\n}\n\n/**\n * The tool results of `context` a triage judges: each one before the model's last answer whose\n * call is there too. The last answer's own results are always kept.\n */\nexport function triageCandidates(context: Message[]): TriageCandidate[] {\n\tconst last = context.findLastIndex(message => message.role === 'assistant');\n\tconst before = context.slice(0, Math.max(last, 0));\n\tconst calls = new Map(\n\t\tbefore.flatMap(message =>\n\t\t\tmessage.content.flatMap(part => (part.type === 'toolCall' ? [[part.id, part] as const] : [])),\n\t\t),\n\t);\n\treturn before.flatMap(message =>\n\t\tmessage.content.flatMap(part => {\n\t\t\tconst call = part.type === 'toolResult' ? calls.get(part.callId) : undefined;\n\t\t\treturn part.type === 'toolResult' && call\n\t\t\t\t? [{ callId: part.callId, tool: call.name, input: call.input, output: part.output }]\n\t\t\t\t: [];\n\t\t}),\n\t);\n}\n\n/**\n * Where a triaged compaction cuts `context`: the run's prompt, each tool call the triage kept\n * with its result, and the model's last answer with everything after it are kept word for word,\n * in their order; everything else is summarized. `undefined` when the model has answered once\n * at most, or the triage kept every answer whole, so a summary would replace nothing.\n *\n * @param prompt the run's prompt, found by identity as in `recentCut`; when it is not there, it\n * is kept first.\n * @param kept the ids of the tool calls kept with their results.\n */\nexport function triagedCut(\n\tcontext: Message[],\n\tprompt: Message,\n\tkept: ReadonlySet<string>,\n): { summarized: Message[]; kept: Message[] } | undefined {\n\tconst last = context.findLastIndex(message => message.role === 'assistant');\n\tconst found = context.indexOf(prompt);\n\tconst isKept = (part: MessagePart) =>\n\t\t(part.type === 'toolCall' && kept.has(part.id)) ||\n\t\t(part.type === 'toolResult' && kept.has(part.callId));\n\tconst keptMessages: Message[] = found === -1 ? [prompt] : [];\n\tconst summarized: Message[] = [];\n\tcontext.slice(0, Math.max(last, 0)).forEach((message, index) => {\n\t\tif (index === found) {\n\t\t\tkeptMessages.push(prompt);\n\t\t\treturn;\n\t\t}\n\t\tconst keptParts = message.content.filter(isKept);\n\t\tconst rest = message.content.filter(part => !isKept(part));\n\t\tif (keptParts.length > 0) keptMessages.push({ role: message.role, content: keptParts });\n\t\tif (rest.length > 0) summarized.push({ role: message.role, content: rest });\n\t});\n\tif (!summarized.some(message => message.role === 'assistant')) return undefined;\n\treturn { summarized, kept: withoutReplayedReasoning([...keptMessages, ...context.slice(last)]) };\n}\n\n/** What a triaged compaction keeps, as its summary requests tell the model. */\nexport function triagedKept(kept: TriageCandidate[]): string {\n\tconst calls = kept.map(({ tool, input }) => `${tool} ${shorten(JSON.stringify(input), 200)}`);\n\treturn calls.length === 0\n\t\t? \"the user's request for this run and your last answer, with its tool results,\"\n\t\t: `the user's request for this run, your last answer with its tool results, and the results of these earlier tool calls: ${calls.join('; ')},`;\n}\n\nfunction shorten(text: string, max: number): string {\n\treturn text.length <= max ? text : `${text.slice(0, max)}…`;\n}\n"]}
@@ -39,7 +39,8 @@ export interface CreateLLMAgentOptions {
39
39
  * The folders the agent works on, the same as given to `createFileTools`. With it, the agent
40
40
  * follows the workspace's history: after the user undoes runs, its next run starts from the
41
41
  * conversation as it was, and is told what was undone. The system prompt is followed by the
42
- * roots and how to name a file in them.
42
+ * roots and how to name a file in them. It gives no tools: pass those of `createFileTools`
43
+ * in `tools`, and an agent given a workspace and no tools is refused.
43
44
  */
44
45
  workspace?: WorkspaceOptions;
45
46
  /**
@@ -54,12 +55,27 @@ export interface CreateLLMAgentOptions {
54
55
  */
55
56
  maxSteps?: number;
56
57
  /**
57
- * The input tokens at which the provider compacts the conversation on its side: it replaces
58
- * every turn before that call with its own summary, which only its model reads back. The
59
- * conversation keeps every original turn. Without it, the conversation is never compacted.
60
- * At least 50,000 on Anthropic.
58
+ * The tokens of context at which the provider compacts it on its side: it replaces every turn
59
+ * before that call with its own summary, which only its model reads back. The conversation
60
+ * keeps every original turn. Defaults to the model's threshold: 450,000 for Claude Opus,
61
+ * Fable and Sonnet and GPT astra, sol and terra, and 125,000 for Claude Haiku and GPT luna.
62
+ * `false` turns compaction off. Either way, a context that no longer fits the model is
63
+ * compacted once, and the call tried again. At least 50,000 on Anthropic.
61
64
  */
62
- compactAt?: number;
65
+ compactAt?: number | false;
66
+ /**
67
+ * Who compacts the context once it passes `compactAt`. `provider`, the default, has the
68
+ * model's provider compact it on its side. `recent` has the agent's own model summarize it
69
+ * before the next call, from its provider's cache, and keeps the run's prompt and the run's
70
+ * last 10 answers, each with its tool results, word for word after the summary, without
71
+ * their signed reasoning. Earlier runs are always summarized. `jev` summarizes the same way,
72
+ * but TypeSafe's Jev chooses what is kept: the run's prompt, the model's last answer with its
73
+ * tool results, and each earlier tool result Jev judges the agent still needs word for word,
74
+ * with its call. Jev is sent the run's prompt, the model's last answer and each of those tool
75
+ * results, so what the tools read leaves for TypeSafe. It reads the key from `TYPESAFE_API_KEY`; without it, or when Jev fails, the
76
+ * `recent` choice is made instead, and logged.
77
+ */
78
+ compaction?: CompactionMode;
63
79
  /**
64
80
  * Runs every tool call without asking, `destructive` ones included, for CI where no one can
65
81
  * answer. Without it, a `destructive` call goes to the run's `approve`, or is denied.
@@ -73,10 +89,14 @@ export interface CreateLLMAgentOptions {
73
89
  */
74
90
  onTranscript?: TranscriptCallback;
75
91
  }
92
+ /** Who compacts an agent's context; see `CreateLLMAgentOptions.compaction`. */
93
+ export type CompactionMode = 'provider' | 'recent' | 'jev';
76
94
  /** A limit per role of `createLLMOrchestrator`; a role left out keeps its default. */
77
95
  export type RoleSteps = Partial<Record<OrchestratorRole, number>>;
78
96
  /** A number of tokens per role of `createLLMOrchestrator`; a role left out keeps its default. */
79
97
  export type RoleTokens = Partial<Record<OrchestratorRole, number>>;
98
+ /** The consumer's agents, from `agentAsTool`, that each role of `createLLMOrchestrator` may call. */
99
+ export type RoleAgents = Partial<Record<OrchestratorRole, DelegationTool[]>>;
80
100
  type OrchestratorRole = CommandRole;
81
101
  export interface CreateLLMOrchestratorOptions extends CreateOrchestratorOptions {
82
102
  /**
@@ -93,11 +113,14 @@ export interface CreateLLMOrchestratorOptions extends CreateOrchestratorOptions
93
113
  */
94
114
  maxSteps?: number | RoleSteps;
95
115
  /**
96
- * The input tokens at which each role's provider compacts its conversation, as
97
- * `CreateLLMAgentOptions.compactAt`: one number for every role, or a number per role. Without
98
- * it, no role compacts.
116
+ * The tokens of context at which each role's provider compacts it, as
117
+ * `CreateLLMAgentOptions.compactAt`: one number for every role, or a number per role. A role
118
+ * left out compacts at its model's threshold, as do the explorers. `false` turns compaction
119
+ * off for every role.
99
120
  */
100
- compactAt?: number | RoleTokens;
121
+ compactAt?: number | false | RoleTokens;
122
+ /** Who compacts every agent's context, as `CreateLLMAgentOptions.compaction`. */
123
+ compaction?: CompactionMode;
101
124
  /**
102
125
  * Commands the roles may run, such as the project's tests, through `runCommand`. Each goes to
103
126
  * the roles its `roles` names, the executor by default. See `createFileTools`. Each needs a
@@ -119,6 +142,17 @@ export interface CreateLLMOrchestratorOptions extends CreateOrchestratorOptions
119
142
  * ended, for debugging. Each transcript names its role. See `Transcript`.
120
143
  */
121
144
  onTranscript?: TranscriptCallback;
145
+ /**
146
+ * Gives the executor `removeWorktree`, which removes a worktree of the repository the first
147
+ * root is in when the user asks. See `createFileTools`.
148
+ */
149
+ removeWorktree?: boolean;
150
+ /**
151
+ * The consumer's agents, from `agentAsTool`, that each role may hand a task to, beside its own
152
+ * tools: the planner and the reviewer keep their explorers. An agent never writes more than
153
+ * the role that calls it, so only the executor's may write, within the `write` roots.
154
+ */
155
+ agents?: RoleAgents;
122
156
  }
123
157
  /**
124
158
  * Builds an agent whose conversation MiKode owns, on a provider's model API, already wrapped in
@@ -127,12 +161,12 @@ export interface CreateLLMOrchestratorOptions extends CreateOrchestratorOptions
127
161
  * `destructive` one runs only when the run's `approve` allows it, or with `autoApprove`.
128
162
  *
129
163
  * @throws {InvalidAgentConfigError} for an unknown provider, a model or reasoning effort its
130
- * client does not support, or two tools that share a name.
164
+ * client does not support, two tools that share a name, or a workspace given with no tools.
131
165
  * @throws {UnrecoverableError} when the client cannot be set up, for example because
132
166
  * `OPENAI_API_KEY` is missing. The Anthropic SDK resolves credentials lazily, so a missing
133
167
  * `ANTHROPIC_API_KEY` fails the first run instead, with an `UnrecoverableError`.
134
168
  */
135
- export declare function createLLMAgent(provider: AgentProvider, { model, reasoningEffort, systemPrompt, tools, workspace, summarizeUndone, maxSteps, compactAt, autoApprove, logger, onTranscript, }: CreateLLMAgentOptions): Agent;
169
+ export declare function createLLMAgent(provider: AgentProvider, { model, reasoningEffort, systemPrompt, tools, workspace, summarizeUndone, maxSteps, compactAt, compaction, autoApprove, logger, onTranscript, }: CreateLLMAgentOptions): Agent;
136
170
  /**
137
171
  * The file tools for an agent from `createLLMAgent` on `provider`, over `workspace`, in the
138
172
  * provider's own format:
@@ -160,15 +194,22 @@ export declare function createLLMAgent(provider: AgentProvider, { model, reasoni
160
194
  * in the history. When it fails, the file stays as the edit wrote it, and the model is told
161
195
  * why. Not supported on Windows yet.
162
196
  *
197
+ * With `removeWorktree`, they include `removeWorktree`, which removes a git worktree of the
198
+ * repository the first root is in, when the user asks: its folder and every file in it, never
199
+ * its branch. Each call asks the approver, whose request's `worktree` says what is lost:
200
+ * changes no commit has, and the commits only a detached HEAD holds. It refuses the main folder
201
+ * and a worktree the agent works in. See `createWorktree`.
202
+ *
163
203
  * @throws {InvalidAgentConfigError} for an unknown provider, a workspace with no root, or an
164
204
  * invalid command or formatter, including one whose `cwd` is not in a `write` root, or any on
165
- * Windows.
205
+ * Windows, or `removeWorktree` with a first root in no git repository.
166
206
  * @throws {UnrecoverableError} when no program can read the first root.
167
207
  */
168
- export declare function createFileTools(provider: AgentProvider, workspace: WorkspaceOptions, { logger, commands, formatter, }?: {
208
+ export declare function createFileTools(provider: AgentProvider, workspace: WorkspaceOptions, { logger, commands, formatter, removeWorktree, }?: {
169
209
  logger?: ILogger;
170
210
  commands?: Commands;
171
211
  formatter?: Formatter;
212
+ removeWorktree?: boolean;
172
213
  }): Promise<FileTool[]>;
173
214
  /**
174
215
  * Builds the planner → executor → reviewer workflow with every role on the model APIs, each
@@ -198,5 +239,5 @@ export declare function createFileTools(provider: AgentProvider, workspace: Work
198
239
  * @throws {UnrecoverableError} when a role cannot be set up, for example because
199
240
  * `OPENAI_API_KEY` is missing, or when no program can read the repository.
200
241
  */
201
- export declare function createLLMOrchestrator({ provider, autoApprove, systemPrompts, workspace, maxSteps, compactAt, commands, formatter, summarizeUndone, logger, onTranscript, }?: CreateLLMOrchestratorOptions): Promise<Agent>;
242
+ export declare function createLLMOrchestrator({ provider, autoApprove, systemPrompts, workspace, maxSteps, compactAt, compaction: compactionMode, commands, formatter, summarizeUndone, logger, onTranscript, removeWorktree, agents, }?: CreateLLMOrchestratorOptions): Promise<Agent>;
202
243
  export {};