@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
@@ -18,6 +18,7 @@ export declare class BackgroundAgents {
18
18
  private unrecordedWorking;
19
19
  private unrecordedEnded;
20
20
  private woken;
21
+ private interrupts;
21
22
  /**
22
23
  * What some agent spent is unknown: it was stopped while it worked, or failed in a way that
23
24
  * carries no usage.
@@ -42,8 +43,8 @@ export declare class BackgroundAgents {
42
43
  */
43
44
  takeAnswers(): Message | undefined;
44
45
  /**
45
- * Waits until an answer arrives, while agents are working. Rejects as cancelled when the run
46
- * is cancelled meanwhile, and with the failure that ended it.
46
+ * Waits until an answer arrives, or `interrupt` is called, while agents are working. Rejects
47
+ * as cancelled when the run is cancelled meanwhile, and with the failure that ended it.
47
48
  */
48
49
  nextAnswer(): Promise<void>;
49
50
  /** Throws the failure of an agent that ended the run, if one did. */
@@ -56,5 +57,7 @@ export declare class BackgroundAgents {
56
57
  private arrive;
57
58
  /** A failure ends the run: every agent is stopped for it. */
58
59
  private end;
60
+ /** Ends the wait in progress, if any, without an answer: something else needs the model. */
61
+ interrupt(): void;
59
62
  private wake;
60
63
  }
@@ -32,6 +32,8 @@ export class BackgroundAgents {
32
32
  // Such an agent ended since `mayHaveChangedUnseen` was last asked.
33
33
  unrecordedEnded = false;
34
34
  woken;
35
+ // How many times `interrupt` was called, so a wait sees one made since it began.
36
+ interrupts = 0;
35
37
  /**
36
38
  * What some agent spent is unknown: it was stopped while it worked, or failed in a way that
37
39
  * carries no usage.
@@ -115,11 +117,12 @@ export class BackgroundAgents {
115
117
  return { role: 'user', content: [{ type: 'text', text }] };
116
118
  }
117
119
  /**
118
- * Waits until an answer arrives, while agents are working. Rejects as cancelled when the run
119
- * is cancelled meanwhile, and with the failure that ended it.
120
+ * Waits until an answer arrives, or `interrupt` is called, while agents are working. Rejects
121
+ * as cancelled when the run is cancelled meanwhile, and with the failure that ended it.
120
122
  */
121
123
  async nextAnswer() {
122
- while (this.arrived.length === 0 && this.agents.size > 0) {
124
+ const interrupts = this.interrupts;
125
+ while (this.arrived.length === 0 && this.agents.size > 0 && this.interrupts === interrupts) {
123
126
  this.throwIfEnded();
124
127
  this.signal.throwIfAborted();
125
128
  await new Promise(resolve => {
@@ -156,6 +159,11 @@ export class BackgroundAgents {
156
159
  this.ended ??= { failure };
157
160
  this.stopping.abort();
158
161
  }
162
+ /** Ends the wait in progress, if any, without an answer: something else needs the model. */
163
+ interrupt() {
164
+ this.interrupts++;
165
+ this.wake();
166
+ }
159
167
  wake() {
160
168
  const woken = this.woken;
161
169
  this.woken = undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"backgroundAgents.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/backgroundAgents.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProgressEvent } from '#src/agent/domain/agent';\nimport type { Message } from '#src/llm/domain/message';\nimport { describeFailure } from '#src/shared/domain/providerFailure';\nimport { type Delegated, RunEndingToolError } from '#src/tools/domain/preparedCall';\n\n// How much of a task the answer repeats, so the model can tell which one it is.\nconst shownTaskCharacters = 200;\n\n/** How the model that sent an agent is told which one it is, such as `explorer 2`. */\nfunction labelOf({ name, subagent }: Delegated): string {\n\treturn `${name} ${String(subagent)}`;\n}\n\n/** What the model is told of the agents a call sent, as that call's result. */\nexport function sentNote(agents: readonly Delegated[]): string {\n\tconst names = agents.map(labelOf).join(', ');\n\treturn `Sent to work in the background: ${names}. Each one's answer reaches you in a message of its own once it finishes. Keep working meanwhile. When you have nothing else to do until they answer, say briefly that you are waiting, without calling a tool: the run waits for them, and you go on with their answers.`;\n}\n\n// What a model is told of an agent the end of its run stopped, in a conversation kept for the next.\nconst stoppedAnswer =\n\t'None: it was stopped when the run ended, before it answered. Anything it changed stays as it left it.';\n\nfunction shownTask(task: string): string {\n\treturn task.length > shownTaskCharacters ? `${task.slice(0, shownTaskCharacters)}…` : task;\n}\n\n/**\n * The agents a run's calls sent to work in the background. Each one's answer waits here, in the\n * order they finished, until the model's next step takes it. A failure that ends the run stops\n * every agent still working at once, and the run at its next boundary.\n */\nexport class BackgroundAgents {\n\tprivate readonly stopping = new AbortController();\n\t/** What the agents work under: the run's own signal, or the stop of the run's end. */\n\treadonly signal: AbortSignal;\n\tprivate readonly agents = new Set<Promise<void>>();\n\tprivate readonly arrived: string[] = [];\n\tprivate ended: { failure: unknown } | undefined;\n\tprivate unrecordedWorking = 0;\n\t// Such an agent ended since `mayHaveChangedUnseen` was last asked.\n\tprivate unrecordedEnded = false;\n\tprivate woken: (() => void) | undefined;\n\t/**\n\t * What some agent spent is unknown: it was stopped while it worked, or failed in a way that\n\t * carries no usage.\n\t */\n\tprivate unknownUsage = false;\n\n\tprivate readonly narrate: (event: ProgressEvent) => void;\n\n\t/** `narrate` tells the person watching when each agent ended; it must never throw. */\n\tconstructor(runSignal: AbortSignal, narrate: (event: ProgressEvent) => void) {\n\t\tthis.signal = AbortSignal.any([runSignal, this.stopping.signal]);\n\t\tthis.narrate = narrate;\n\t}\n\n\t/** Some agent is still working. */\n\tget working(): boolean {\n\t\treturn this.agents.size > 0;\n\t}\n\n\t/**\n\t * Whether an agent that may change files the history does not record worked since the last\n\t * time this was asked: one is working now, or one ended meanwhile.\n\t */\n\tmayHaveChangedUnseen(): boolean {\n\t\tconst worked = this.unrecordedWorking > 0 || this.unrecordedEnded;\n\t\tthis.unrecordedEnded = false;\n\t\treturn worked;\n\t}\n\n\tget usageUnknown(): boolean {\n\t\treturn this.unknownUsage;\n\t}\n\n\t/** Watches the agents call `callId` sent, until each one answers. */\n\tadd(callId: string, agents: readonly Delegated[], unrecorded: boolean): void {\n\t\tfor (const agent of agents) {\n\t\t\tif (unrecorded) this.unrecordedWorking++;\n\t\t\tconst ended = (outcome: 'answered' | 'failed' | 'stopped') => {\n\t\t\t\tconst { name, subagent } = agent;\n\t\t\t\tthis.narrate({ type: 'subagentEnded', callId, subagent, name, outcome });\n\t\t\t};\n\t\t\tconst watched = agent.answer\n\t\t\t\t.then(\n\t\t\t\t\t({ text, failed }) => {\n\t\t\t\t\t\tended(failed ? 'failed' : 'answered');\n\t\t\t\t\t\tthis.arrive(agent, callId, text);\n\t\t\t\t\t},\n\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\tif (this.signal.aborted) {\n\t\t\t\t\t\t\tended('stopped');\n\t\t\t\t\t\t\tthis.unknownUsage = true;\n\t\t\t\t\t\t\tthis.arrive(agent, callId, stoppedAnswer);\n\t\t\t\t\t\t} else if (error instanceof RunEndingToolError) {\n\t\t\t\t\t\t\tended('failed');\n\t\t\t\t\t\t\tthis.end(error.failure);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// It broke the promise of answering, so what it spent is unknown.\n\t\t\t\t\t\t\tended('failed');\n\t\t\t\t\t\t\tthis.unknownUsage = true;\n\t\t\t\t\t\t\tthis.arrive(agent, callId, `It failed: ${describeFailure(error)}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.finally(() => {\n\t\t\t\t\tif (unrecorded) {\n\t\t\t\t\t\tthis.unrecordedWorking--;\n\t\t\t\t\t\tthis.unrecordedEnded = true;\n\t\t\t\t\t}\n\t\t\t\t\tthis.agents.delete(watched);\n\t\t\t\t\tthis.wake();\n\t\t\t\t});\n\t\t\tthis.agents.add(watched);\n\t\t}\n\t}\n\n\t/**\n\t * The message holding every answer that arrived since the last one, if any did, and what\n\t * became of each agent a stop interrupted.\n\t */\n\ttakeAnswers(): Message | undefined {\n\t\tif (this.arrived.length === 0) return undefined;\n\t\tconst answers = this.arrived.splice(0);\n\t\tconst text = [\n\t\t\t'This message is from the harness, not the user: agents you sent finished.',\n\t\t\t...answers,\n\t\t].join('\\n\\n');\n\t\treturn { role: 'user', content: [{ type: 'text', text }] };\n\t}\n\n\t/**\n\t * Waits until an answer arrives, while agents are working. Rejects as cancelled when the run\n\t * is cancelled meanwhile, and with the failure that ended it.\n\t */\n\tasync nextAnswer(): Promise<void> {\n\t\twhile (this.arrived.length === 0 && this.agents.size > 0) {\n\t\t\tthis.throwIfEnded();\n\t\t\tthis.signal.throwIfAborted();\n\t\t\tawait new Promise<void>(resolve => {\n\t\t\t\tconst done = () => {\n\t\t\t\t\tthis.signal.removeEventListener('abort', done);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tthis.woken = done;\n\t\t\t\tthis.signal.addEventListener('abort', done, { once: true });\n\t\t\t});\n\t\t}\n\t\tthis.throwIfEnded();\n\t\tthis.signal.throwIfAborted();\n\t}\n\n\t/** Throws the failure of an agent that ended the run, if one did. */\n\tthrowIfEnded(): void {\n\t\tif (this.ended) throw this.ended.failure;\n\t}\n\n\t/**\n\t * Stops every agent still working, and waits until each one has: what they changed and spent\n\t * belongs to the run, which ends only after them.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (this.agents.size > 0) this.stopping.abort();\n\t\tawait Promise.all(this.agents);\n\t}\n\n\tprivate arrive(agent: Delegated, callId: string, answer: string): void {\n\t\tthis.arrived.push(\n\t\t\t`${labelOf(agent)}, sent by your call ${callId} with the task: ${shownTask(agent.task)}\\nIts answer:\\n${answer}`,\n\t\t);\n\t}\n\n\t/** A failure ends the run: every agent is stopped for it. */\n\tprivate end(failure: unknown): void {\n\t\tthis.ended ??= { failure };\n\t\tthis.stopping.abort();\n\t}\n\n\tprivate wake(): void {\n\t\tconst woken = this.woken;\n\t\tthis.woken = undefined;\n\t\twoken?.();\n\t}\n}\n"]}
1
+ {"version":3,"file":"backgroundAgents.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/backgroundAgents.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProgressEvent } from '#src/agent/domain/agent';\nimport type { Message } from '#src/llm/domain/message';\nimport { describeFailure } from '#src/shared/domain/providerFailure';\nimport { type Delegated, RunEndingToolError } from '#src/tools/domain/preparedCall';\n\n// How much of a task the answer repeats, so the model can tell which one it is.\nconst shownTaskCharacters = 200;\n\n/** How the model that sent an agent is told which one it is, such as `explorer 2`. */\nfunction labelOf({ name, subagent }: Delegated): string {\n\treturn `${name} ${String(subagent)}`;\n}\n\n/** What the model is told of the agents a call sent, as that call's result. */\nexport function sentNote(agents: readonly Delegated[]): string {\n\tconst names = agents.map(labelOf).join(', ');\n\treturn `Sent to work in the background: ${names}. Each one's answer reaches you in a message of its own once it finishes. Keep working meanwhile. When you have nothing else to do until they answer, say briefly that you are waiting, without calling a tool: the run waits for them, and you go on with their answers.`;\n}\n\n// What a model is told of an agent the end of its run stopped, in a conversation kept for the next.\nconst stoppedAnswer =\n\t'None: it was stopped when the run ended, before it answered. Anything it changed stays as it left it.';\n\nfunction shownTask(task: string): string {\n\treturn task.length > shownTaskCharacters ? `${task.slice(0, shownTaskCharacters)}…` : task;\n}\n\n/**\n * The agents a run's calls sent to work in the background. Each one's answer waits here, in the\n * order they finished, until the model's next step takes it. A failure that ends the run stops\n * every agent still working at once, and the run at its next boundary.\n */\nexport class BackgroundAgents {\n\tprivate readonly stopping = new AbortController();\n\t/** What the agents work under: the run's own signal, or the stop of the run's end. */\n\treadonly signal: AbortSignal;\n\tprivate readonly agents = new Set<Promise<void>>();\n\tprivate readonly arrived: string[] = [];\n\tprivate ended: { failure: unknown } | undefined;\n\tprivate unrecordedWorking = 0;\n\t// Such an agent ended since `mayHaveChangedUnseen` was last asked.\n\tprivate unrecordedEnded = false;\n\tprivate woken: (() => void) | undefined;\n\t// How many times `interrupt` was called, so a wait sees one made since it began.\n\tprivate interrupts = 0;\n\t/**\n\t * What some agent spent is unknown: it was stopped while it worked, or failed in a way that\n\t * carries no usage.\n\t */\n\tprivate unknownUsage = false;\n\n\tprivate readonly narrate: (event: ProgressEvent) => void;\n\n\t/** `narrate` tells the person watching when each agent ended; it must never throw. */\n\tconstructor(runSignal: AbortSignal, narrate: (event: ProgressEvent) => void) {\n\t\tthis.signal = AbortSignal.any([runSignal, this.stopping.signal]);\n\t\tthis.narrate = narrate;\n\t}\n\n\t/** Some agent is still working. */\n\tget working(): boolean {\n\t\treturn this.agents.size > 0;\n\t}\n\n\t/**\n\t * Whether an agent that may change files the history does not record worked since the last\n\t * time this was asked: one is working now, or one ended meanwhile.\n\t */\n\tmayHaveChangedUnseen(): boolean {\n\t\tconst worked = this.unrecordedWorking > 0 || this.unrecordedEnded;\n\t\tthis.unrecordedEnded = false;\n\t\treturn worked;\n\t}\n\n\tget usageUnknown(): boolean {\n\t\treturn this.unknownUsage;\n\t}\n\n\t/** Watches the agents call `callId` sent, until each one answers. */\n\tadd(callId: string, agents: readonly Delegated[], unrecorded: boolean): void {\n\t\tfor (const agent of agents) {\n\t\t\tif (unrecorded) this.unrecordedWorking++;\n\t\t\tconst ended = (outcome: 'answered' | 'failed' | 'stopped') => {\n\t\t\t\tconst { name, subagent } = agent;\n\t\t\t\tthis.narrate({ type: 'subagentEnded', callId, subagent, name, outcome });\n\t\t\t};\n\t\t\tconst watched = agent.answer\n\t\t\t\t.then(\n\t\t\t\t\t({ text, failed }) => {\n\t\t\t\t\t\tended(failed ? 'failed' : 'answered');\n\t\t\t\t\t\tthis.arrive(agent, callId, text);\n\t\t\t\t\t},\n\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\tif (this.signal.aborted) {\n\t\t\t\t\t\t\tended('stopped');\n\t\t\t\t\t\t\tthis.unknownUsage = true;\n\t\t\t\t\t\t\tthis.arrive(agent, callId, stoppedAnswer);\n\t\t\t\t\t\t} else if (error instanceof RunEndingToolError) {\n\t\t\t\t\t\t\tended('failed');\n\t\t\t\t\t\t\tthis.end(error.failure);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// It broke the promise of answering, so what it spent is unknown.\n\t\t\t\t\t\t\tended('failed');\n\t\t\t\t\t\t\tthis.unknownUsage = true;\n\t\t\t\t\t\t\tthis.arrive(agent, callId, `It failed: ${describeFailure(error)}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.finally(() => {\n\t\t\t\t\tif (unrecorded) {\n\t\t\t\t\t\tthis.unrecordedWorking--;\n\t\t\t\t\t\tthis.unrecordedEnded = true;\n\t\t\t\t\t}\n\t\t\t\t\tthis.agents.delete(watched);\n\t\t\t\t\tthis.wake();\n\t\t\t\t});\n\t\t\tthis.agents.add(watched);\n\t\t}\n\t}\n\n\t/**\n\t * The message holding every answer that arrived since the last one, if any did, and what\n\t * became of each agent a stop interrupted.\n\t */\n\ttakeAnswers(): Message | undefined {\n\t\tif (this.arrived.length === 0) return undefined;\n\t\tconst answers = this.arrived.splice(0);\n\t\tconst text = [\n\t\t\t'This message is from the harness, not the user: agents you sent finished.',\n\t\t\t...answers,\n\t\t].join('\\n\\n');\n\t\treturn { role: 'user', content: [{ type: 'text', text }] };\n\t}\n\n\t/**\n\t * Waits until an answer arrives, or `interrupt` is called, while agents are working. Rejects\n\t * as cancelled when the run is cancelled meanwhile, and with the failure that ended it.\n\t */\n\tasync nextAnswer(): Promise<void> {\n\t\tconst interrupts = this.interrupts;\n\t\twhile (this.arrived.length === 0 && this.agents.size > 0 && this.interrupts === interrupts) {\n\t\t\tthis.throwIfEnded();\n\t\t\tthis.signal.throwIfAborted();\n\t\t\tawait new Promise<void>(resolve => {\n\t\t\t\tconst done = () => {\n\t\t\t\t\tthis.signal.removeEventListener('abort', done);\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t\tthis.woken = done;\n\t\t\t\tthis.signal.addEventListener('abort', done, { once: true });\n\t\t\t});\n\t\t}\n\t\tthis.throwIfEnded();\n\t\tthis.signal.throwIfAborted();\n\t}\n\n\t/** Throws the failure of an agent that ended the run, if one did. */\n\tthrowIfEnded(): void {\n\t\tif (this.ended) throw this.ended.failure;\n\t}\n\n\t/**\n\t * Stops every agent still working, and waits until each one has: what they changed and spent\n\t * belongs to the run, which ends only after them.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (this.agents.size > 0) this.stopping.abort();\n\t\tawait Promise.all(this.agents);\n\t}\n\n\tprivate arrive(agent: Delegated, callId: string, answer: string): void {\n\t\tthis.arrived.push(\n\t\t\t`${labelOf(agent)}, sent by your call ${callId} with the task: ${shownTask(agent.task)}\\nIts answer:\\n${answer}`,\n\t\t);\n\t}\n\n\t/** A failure ends the run: every agent is stopped for it. */\n\tprivate end(failure: unknown): void {\n\t\tthis.ended ??= { failure };\n\t\tthis.stopping.abort();\n\t}\n\n\t/** Ends the wait in progress, if any, without an answer: something else needs the model. */\n\tinterrupt(): void {\n\t\tthis.interrupts++;\n\t\tthis.wake();\n\t}\n\n\tprivate wake(): void {\n\t\tconst woken = this.woken;\n\t\tthis.woken = undefined;\n\t\twoken?.();\n\t}\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import type { ContextTriage } from '#src/llm/domain/contextTriage';
2
+ /**
3
+ * How an `LLMAgent` keeps its context within the model: the conversation always keeps every part,
4
+ * and only the context sent to the model is compacted. Internal: the harness's compactors are the
5
+ * only ones.
6
+ *
7
+ * The provider's compactor hands its threshold to the client, and the provider compacts inside
8
+ * the call that passes it, answering with its own summary first.
9
+ */
10
+ export interface ProviderCompactor {
11
+ readonly by: 'provider';
12
+ /**
13
+ * The tokens of context at which the provider compacts it, or `undefined` when compaction is
14
+ * off. A context that no longer fits the model is compacted once all the same.
15
+ */
16
+ readonly at: number | undefined;
17
+ }
18
+ /**
19
+ * The harness's compactor: once the context passes `at`, before the next call, the agent's own
20
+ * model summarizes it, and what is kept follows the summary word for word. Without a `triage`,
21
+ * the recent compactor keeps the run's prompt and the run's last `keptSteps` answers of the
22
+ * model, each with its tool results.
23
+ */
24
+ export interface RecentCompactor {
25
+ readonly by: 'harness';
26
+ /** As `ProviderCompactor.at`. */
27
+ readonly at: number | undefined;
28
+ readonly keptSteps: number;
29
+ /**
30
+ * Jev's: it keeps the run's prompt, the model's last answer with its tool results, and each
31
+ * earlier tool result the triage scores `keptScore` or more, with its call. When the triage
32
+ * fails, the recent compactor chooses instead.
33
+ */
34
+ readonly triage?: ContextTriage;
35
+ }
36
+ export type Compactor = ProviderCompactor | RecentCompactor;
37
+ /** The provider's compactor, off: only a context that no longer fits is compacted. */
38
+ export declare const compactionOff: Compactor;
39
+ /** How many answers of the model, with their tool results, the recent compactor keeps. */
40
+ export declare const recentKeptSteps = 10;
@@ -0,0 +1,5 @@
1
+ /** The provider's compactor, off: only a context that no longer fits is compacted. */
2
+ export const compactionOff = { by: 'provider', at: undefined };
3
+ /** How many answers of the model, with their tool results, the recent compactor keeps. */
4
+ export const recentKeptSteps = 10;
5
+ //# sourceMappingURL=compactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compactor.js","sourceRoot":"","sources":["../../../../src/engines/domain/model/compactor.ts"],"names":[],"mappings":"AAwCA,sFAAsF;AACtF,MAAM,CAAC,MAAM,aAAa,GAAc,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;AAE1E,0FAA0F;AAC1F,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC","sourcesContent":["import type { ContextTriage } from '#src/llm/domain/contextTriage';\n\n/**\n * How an `LLMAgent` keeps its context within the model: the conversation always keeps every part,\n * and only the context sent to the model is compacted. Internal: the harness's compactors are the\n * only ones.\n *\n * The provider's compactor hands its threshold to the client, and the provider compacts inside\n * the call that passes it, answering with its own summary first.\n */\nexport interface ProviderCompactor {\n\treadonly by: 'provider';\n\t/**\n\t * The tokens of context at which the provider compacts it, or `undefined` when compaction is\n\t * off. A context that no longer fits the model is compacted once all the same.\n\t */\n\treadonly at: number | undefined;\n}\n\n/**\n * The harness's compactor: once the context passes `at`, before the next call, the agent's own\n * model summarizes it, and what is kept follows the summary word for word. Without a `triage`,\n * the recent compactor keeps the run's prompt and the run's last `keptSteps` answers of the\n * model, each with its tool results.\n */\nexport interface RecentCompactor {\n\treadonly by: 'harness';\n\t/** As `ProviderCompactor.at`. */\n\treadonly at: number | undefined;\n\treadonly keptSteps: number;\n\t/**\n\t * Jev's: it keeps the run's prompt, the model's last answer with its tool results, and each\n\t * earlier tool result the triage scores `keptScore` or more, with its call. When the triage\n\t * fails, the recent compactor chooses instead.\n\t */\n\treadonly triage?: ContextTriage;\n}\n\nexport type Compactor = ProviderCompactor | RecentCompactor;\n\n/** The provider's compactor, off: only a context that no longer fits is compacted. */\nexport const compactionOff: Compactor = { by: 'provider', at: undefined };\n\n/** How many answers of the model, with their tool results, the recent compactor keeps. */\nexport const recentKeptSteps = 10;\n"]}
@@ -1,9 +1,10 @@
1
1
  import { type Agent, type AgentResponse, type RunOptions } from '#src/agent/domain/agent';
2
2
  import type { LLMClient } from '#src/llm/domain/llm';
3
- import type { Message } from '#src/llm/domain/message';
3
+ import { type Message } from '#src/llm/domain/message';
4
4
  import type { TranscriptCallback } from '#src/agent/domain/transcript';
5
5
  import { type AgentTool } from '#src/tools/domain/preparedCall';
6
6
  import type { ILogger } from '#src/shared/domain/logger';
7
+ import { type Compactor } from './compactor.ts';
7
8
  import { type HistoryReader } from './historyFollower.ts';
8
9
  /**
9
10
  * An agent whose conversation is owned by MiKode: every run sends the whole context to a
@@ -24,9 +25,14 @@ export declare class LLMAgent implements Agent {
24
25
  private readonly history;
25
26
  private readonly summarizer;
26
27
  private readonly onTranscript;
28
+ private readonly writeRoots;
29
+ private readonly compactor;
30
+ private contextTokens;
27
31
  private seenRevision;
28
32
  private readonly reads;
29
- constructor({ llmClient, messages, tools, maxSteps, autoApprove, logger, history, summarizer, onTranscript, }: {
33
+ private readonly runs;
34
+ private readonly session;
35
+ constructor({ llmClient, messages, tools, maxSteps, autoApprove, logger, history, summarizer, onTranscript, writeRoots, compactor, }: {
30
36
  llmClient: LLMClient;
31
37
  messages?: Message[];
32
38
  tools?: AgentTool[];
@@ -49,6 +55,13 @@ export declare class LLMAgent implements Agent {
49
55
  summarizer?: LLMClient;
50
56
  /** Receives what the model saw in each run, once the run ended. */
51
57
  onTranscript?: TranscriptCallback;
58
+ /**
59
+ * The `write` roots of the agent's workspace: an agent it delegates to writes only within
60
+ * them. Empty when it only reads; without it, it sets no limit of its own.
61
+ */
62
+ writeRoots?: readonly string[];
63
+ /** How the context is compacted. Defaults to off: only a context that no longer fits is. */
64
+ compactor?: Compactor;
52
65
  });
53
66
  private followHistory;
54
67
  /**
@@ -57,6 +70,41 @@ export declare class LLMAgent implements Agent {
57
70
  */
58
71
  private summarizeUndone;
59
72
  private warnSummaryFailed;
73
+ private warnContextOutgrown;
74
+ private warnTriageFailed;
75
+ private warnCompactionFailed;
76
+ /**
77
+ * Warns that a summary the model was called for failed. A logger that throws ends the run,
78
+ * carrying what that call spent, which the run has not counted yet.
79
+ */
80
+ private warnSummaryCallFailed;
81
+ /**
82
+ * Has the agent's own model summarize the context before the harness compactor's cut, and
83
+ * records the summary in the run, with what it keeps word for word. With a triage, Jev chooses
84
+ * what is kept; when it fails, which it logs, the recent cut does. `compacted` is false when
85
+ * the model had answered too few times, which costs no call, when Jev kept every answer
86
+ * whole, unless the context no longer fits, or when the summary failed as `summarize` says.
87
+ */
88
+ private compactHarness;
89
+ /**
90
+ * Where the harness compactor cuts `context`, and what it keeps, as the summary request says
91
+ * it. `triaged` when Jev was asked and answered.
92
+ */
93
+ private harnessCut;
94
+ private recentChoice;
95
+ /**
96
+ * Has the agent's own model summarize what `cut` does not keep, and records the summary in the
97
+ * run. Once the threshold passed, the model reads `context`, the context as it was last sent,
98
+ * from its provider's cache; once the context no longer fit, it reads what it summarizes as
99
+ * text. `compacted` is false when the model wrote no summary or the provider failed in
100
+ * passing, which it logs; any other failure is thrown. The summary's usage is the run's either
101
+ * way.
102
+ *
103
+ * @param kept what `cut` keeps, as the summary request tells the model.
104
+ * @param endsRun whether a failure ends the run, which throws why instead of logging it: the
105
+ * run's context no longer fits. `outgrown` alone may be this call's own fallback.
106
+ */
107
+ private summarize;
60
108
  private llmCall;
61
109
  private narrateStep;
62
110
  /**
@@ -97,8 +145,11 @@ export declare class LLMAgent implements Agent {
97
145
  * Runs inside the context of the run that reached it, or in one of its own that it ends with
98
146
  * the run: completed, failed or cancelled. Only a run that owns its context names the history
99
147
  * run it recorded, since the run is over only when that context ends.
148
+ *
149
+ * @throws {RunInProgressError} when another run on this agent is still working.
100
150
  */
101
151
  run(prompt: string, options: RunOptions): Promise<AgentResponse>;
152
+ private runAlone;
102
153
  /**
103
154
  * What the run started ends here, but its result stands: the changes it recorded are already
104
155
  * on disk, so a record that could not be closed is reported, not turned into a failure.