@geminixiang/mikan 1.0.0-beta.87 → 1.0.0-beta.89

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 (249) hide show
  1. package/CHANGELOG.md +44 -1370
  2. package/README.md +2 -2
  3. package/dist/adapters/commands/session-view.d.ts.map +1 -1
  4. package/dist/adapters/commands/session-view.js +4 -4
  5. package/dist/adapters/commands/session-view.js.map +1 -1
  6. package/dist/adapters/discord/bot.d.ts +4 -1
  7. package/dist/adapters/discord/bot.d.ts.map +1 -1
  8. package/dist/adapters/discord/bot.js +6 -3
  9. package/dist/adapters/discord/bot.js.map +1 -1
  10. package/dist/adapters/discord/context.d.ts.map +1 -1
  11. package/dist/adapters/discord/context.js +3 -1
  12. package/dist/adapters/discord/context.js.map +1 -1
  13. package/dist/adapters/discord/types.d.ts +1 -0
  14. package/dist/adapters/discord/types.d.ts.map +1 -1
  15. package/dist/adapters/discord/types.js.map +1 -1
  16. package/dist/adapters/github/activity.d.ts.map +1 -1
  17. package/dist/adapters/github/activity.js +7 -13
  18. package/dist/adapters/github/activity.js.map +1 -1
  19. package/dist/adapters/github/bot.d.ts +4 -1
  20. package/dist/adapters/github/bot.d.ts.map +1 -1
  21. package/dist/adapters/github/bot.js +3 -3
  22. package/dist/adapters/github/bot.js.map +1 -1
  23. package/dist/adapters/github/context.d.ts.map +1 -1
  24. package/dist/adapters/github/context.js +3 -1
  25. package/dist/adapters/github/context.js.map +1 -1
  26. package/dist/adapters/github/types.d.ts +0 -3
  27. package/dist/adapters/github/types.d.ts.map +1 -1
  28. package/dist/adapters/github/types.js.map +1 -1
  29. package/dist/adapters/intake.d.ts.map +1 -1
  30. package/dist/adapters/intake.js +12 -2
  31. package/dist/adapters/intake.js.map +1 -1
  32. package/dist/adapters/shared.d.ts +2 -1
  33. package/dist/adapters/shared.d.ts.map +1 -1
  34. package/dist/adapters/shared.js +7 -2
  35. package/dist/adapters/shared.js.map +1 -1
  36. package/dist/adapters/slack/assistant.d.ts +2 -4
  37. package/dist/adapters/slack/assistant.d.ts.map +1 -1
  38. package/dist/adapters/slack/assistant.js +10 -16
  39. package/dist/adapters/slack/assistant.js.map +1 -1
  40. package/dist/adapters/slack/bot.d.ts +5 -1
  41. package/dist/adapters/slack/bot.d.ts.map +1 -1
  42. package/dist/adapters/slack/bot.js +15 -11
  43. package/dist/adapters/slack/bot.js.map +1 -1
  44. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  45. package/dist/adapters/slack/response-lifecycle.js +1 -1
  46. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  47. package/dist/adapters/slack/task-status.d.ts.map +1 -1
  48. package/dist/adapters/slack/task-status.js +1 -2
  49. package/dist/adapters/slack/task-status.js.map +1 -1
  50. package/dist/adapters/telegram/bot.d.ts +4 -1
  51. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  52. package/dist/adapters/telegram/bot.js +3 -3
  53. package/dist/adapters/telegram/bot.js.map +1 -1
  54. package/dist/adapters/telegram/context.d.ts.map +1 -1
  55. package/dist/adapters/telegram/context.js +3 -1
  56. package/dist/adapters/telegram/context.js.map +1 -1
  57. package/dist/adapters/types.d.ts +3 -1
  58. package/dist/adapters/types.d.ts.map +1 -1
  59. package/dist/adapters/types.js.map +1 -1
  60. package/dist/adapters/web/admin/client-assets.js +1 -1
  61. package/dist/adapters/web/admin/client-assets.js.map +1 -1
  62. package/dist/adapters/web/admin/portal.d.ts +0 -1
  63. package/dist/adapters/web/admin/portal.d.ts.map +1 -1
  64. package/dist/adapters/web/admin/portal.js +26 -42
  65. package/dist/adapters/web/admin/portal.js.map +1 -1
  66. package/dist/adapters/web/admin/types.d.ts +0 -2
  67. package/dist/adapters/web/admin/types.d.ts.map +1 -1
  68. package/dist/adapters/web/admin/types.js.map +1 -1
  69. package/dist/adapters/web/login/portal.d.ts.map +1 -1
  70. package/dist/adapters/web/login/portal.js +0 -8
  71. package/dist/adapters/web/login/portal.js.map +1 -1
  72. package/dist/adapters/web/server.d.ts.map +1 -1
  73. package/dist/adapters/web/server.js +0 -1
  74. package/dist/adapters/web/server.js.map +1 -1
  75. package/dist/adapters/web/session-view/portal.d.ts +1 -2
  76. package/dist/adapters/web/session-view/portal.d.ts.map +1 -1
  77. package/dist/adapters/web/session-view/portal.js +38 -328
  78. package/dist/adapters/web/session-view/portal.js.map +1 -1
  79. package/dist/adapters/web/session-view/types.d.ts +4 -6
  80. package/dist/adapters/web/session-view/types.d.ts.map +1 -1
  81. package/dist/adapters/web/session-view/types.js.map +1 -1
  82. package/dist/env-manifest.d.ts +0 -4
  83. package/dist/env-manifest.d.ts.map +1 -1
  84. package/dist/env-manifest.js +5 -8
  85. package/dist/env-manifest.js.map +1 -1
  86. package/dist/events/index.d.ts +2 -9
  87. package/dist/events/index.d.ts.map +1 -1
  88. package/dist/events/index.js +0 -1
  89. package/dist/events/index.js.map +1 -1
  90. package/dist/events/scheduler.d.ts +0 -1
  91. package/dist/events/scheduler.d.ts.map +1 -1
  92. package/dist/events/scheduler.js +2 -7
  93. package/dist/events/scheduler.js.map +1 -1
  94. package/dist/harness/execution-env.d.ts +1 -1
  95. package/dist/harness/execution-env.d.ts.map +1 -1
  96. package/dist/harness/execution-env.js +32 -32
  97. package/dist/harness/execution-env.js.map +1 -1
  98. package/dist/harness/index.d.ts +2 -3
  99. package/dist/harness/index.d.ts.map +1 -1
  100. package/dist/harness/index.js +0 -1
  101. package/dist/harness/index.js.map +1 -1
  102. package/dist/harness/mcp-result.d.ts +4 -3
  103. package/dist/harness/mcp-result.d.ts.map +1 -1
  104. package/dist/harness/mcp-result.js +27 -13
  105. package/dist/harness/mcp-result.js.map +1 -1
  106. package/dist/harness/mcp.js +4 -4
  107. package/dist/harness/mcp.js.map +1 -1
  108. package/dist/harness/presenter.d.ts.map +1 -1
  109. package/dist/harness/presenter.js +20 -25
  110. package/dist/harness/presenter.js.map +1 -1
  111. package/dist/harness/prompt.d.ts +0 -1
  112. package/dist/harness/prompt.d.ts.map +1 -1
  113. package/dist/harness/prompt.js +3 -32
  114. package/dist/harness/prompt.js.map +1 -1
  115. package/dist/harness/run-events.d.ts.map +1 -1
  116. package/dist/harness/run-events.js +4 -12
  117. package/dist/harness/run-events.js.map +1 -1
  118. package/dist/harness/runner.d.ts.map +1 -1
  119. package/dist/harness/runner.js +35 -38
  120. package/dist/harness/runner.js.map +1 -1
  121. package/dist/harness/session.d.ts +37 -38
  122. package/dist/harness/session.d.ts.map +1 -1
  123. package/dist/harness/session.js +387 -388
  124. package/dist/harness/session.js.map +1 -1
  125. package/dist/harness/subagent.d.ts +5 -5
  126. package/dist/harness/subagent.d.ts.map +1 -1
  127. package/dist/harness/subagent.js +40 -45
  128. package/dist/harness/subagent.js.map +1 -1
  129. package/dist/harness/tools/codemode.d.ts +3 -0
  130. package/dist/harness/tools/codemode.d.ts.map +1 -0
  131. package/dist/harness/tools/codemode.js +173 -0
  132. package/dist/harness/tools/codemode.js.map +1 -0
  133. package/dist/harness/tools/history.d.ts +20 -0
  134. package/dist/harness/tools/history.d.ts.map +1 -0
  135. package/dist/harness/tools/history.js +293 -0
  136. package/dist/harness/tools/history.js.map +1 -0
  137. package/dist/harness/tools/pi-tools.d.ts +1 -1
  138. package/dist/harness/tools/pi-tools.d.ts.map +1 -1
  139. package/dist/harness/tools/pi-tools.js +47 -21
  140. package/dist/harness/tools/pi-tools.js.map +1 -1
  141. package/dist/harness/tools/secret-redaction.d.ts.map +1 -1
  142. package/dist/harness/tools/secret-redaction.js +46 -2
  143. package/dist/harness/tools/secret-redaction.js.map +1 -1
  144. package/dist/harness/tools/tool-search.d.ts +5 -0
  145. package/dist/harness/tools/tool-search.d.ts.map +1 -0
  146. package/dist/harness/tools/tool-search.js +84 -0
  147. package/dist/harness/tools/tool-search.js.map +1 -0
  148. package/dist/harness/types.d.ts +75 -15
  149. package/dist/harness/types.d.ts.map +1 -1
  150. package/dist/harness/types.js.map +1 -1
  151. package/dist/index.d.ts +3 -7
  152. package/dist/index.d.ts.map +1 -1
  153. package/dist/index.js +0 -3
  154. package/dist/index.js.map +1 -1
  155. package/dist/log.d.ts +0 -3
  156. package/dist/log.d.ts.map +1 -1
  157. package/dist/log.js +0 -15
  158. package/dist/log.js.map +1 -1
  159. package/dist/main.js +2 -2
  160. package/dist/main.js.map +1 -1
  161. package/dist/memory-capture/index.js +0 -1
  162. package/dist/memory-capture/index.js.map +1 -1
  163. package/dist/migrations/index.d.ts.map +1 -1
  164. package/dist/migrations/index.js +2 -0
  165. package/dist/migrations/index.js.map +1 -1
  166. package/dist/migrations/session-files.d.ts +3 -2
  167. package/dist/migrations/session-files.d.ts.map +1 -1
  168. package/dist/migrations/session-files.js.map +1 -1
  169. package/dist/migrations/session-v4.d.ts +77 -0
  170. package/dist/migrations/session-v4.d.ts.map +1 -0
  171. package/dist/migrations/session-v4.js +177 -0
  172. package/dist/migrations/session-v4.js.map +1 -0
  173. package/dist/migrations/sessions-sqlite.d.ts +3 -0
  174. package/dist/migrations/sessions-sqlite.d.ts.map +1 -0
  175. package/dist/migrations/sessions-sqlite.js +215 -0
  176. package/dist/migrations/sessions-sqlite.js.map +1 -0
  177. package/dist/migrations/sessions-v3.d.ts +2 -2
  178. package/dist/migrations/sessions-v3.d.ts.map +1 -1
  179. package/dist/migrations/sessions-v3.js +8 -19
  180. package/dist/migrations/sessions-v3.js.map +1 -1
  181. package/dist/migrations/types.d.ts +0 -5
  182. package/dist/migrations/types.d.ts.map +1 -1
  183. package/dist/migrations/types.js.map +1 -1
  184. package/dist/observability/sentry.d.ts +2 -9
  185. package/dist/observability/sentry.d.ts.map +1 -1
  186. package/dist/observability/sentry.js +12 -64
  187. package/dist/observability/sentry.js.map +1 -1
  188. package/dist/observability/types.d.ts +0 -16
  189. package/dist/observability/types.d.ts.map +1 -1
  190. package/dist/observability/types.js.map +1 -1
  191. package/dist/office/index.d.ts +1 -4
  192. package/dist/office/index.d.ts.map +1 -1
  193. package/dist/office/index.js +2 -14
  194. package/dist/office/index.js.map +1 -1
  195. package/dist/office/projection.d.ts.map +1 -1
  196. package/dist/office/projection.js +0 -1
  197. package/dist/office/projection.js.map +1 -1
  198. package/dist/office/types.d.ts +1 -2
  199. package/dist/office/types.d.ts.map +1 -1
  200. package/dist/office/types.js.map +1 -1
  201. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  202. package/dist/runtime/conversation-runtime.js +2 -16
  203. package/dist/runtime/conversation-runtime.js.map +1 -1
  204. package/dist/runtime/session-lifecycle.d.ts +1 -2
  205. package/dist/runtime/session-lifecycle.d.ts.map +1 -1
  206. package/dist/runtime/session-lifecycle.js +7 -16
  207. package/dist/runtime/session-lifecycle.js.map +1 -1
  208. package/dist/runtime/types.d.ts +0 -1
  209. package/dist/runtime/types.d.ts.map +1 -1
  210. package/dist/runtime/types.js.map +1 -1
  211. package/dist/sandbox/provisioner.d.ts.map +1 -1
  212. package/dist/sandbox/provisioner.js +3 -4
  213. package/dist/sandbox/provisioner.js.map +1 -1
  214. package/dist/sessions/chat-history-sync.d.ts +8 -6
  215. package/dist/sessions/chat-history-sync.d.ts.map +1 -1
  216. package/dist/sessions/chat-history-sync.js +70 -143
  217. package/dist/sessions/chat-history-sync.js.map +1 -1
  218. package/dist/sessions/compaction-summary.d.ts +4 -0
  219. package/dist/sessions/compaction-summary.d.ts.map +1 -0
  220. package/dist/sessions/compaction-summary.js +16 -0
  221. package/dist/sessions/compaction-summary.js.map +1 -0
  222. package/dist/sessions/session-key.d.ts +1 -0
  223. package/dist/sessions/session-key.d.ts.map +1 -1
  224. package/dist/sessions/session-key.js +3 -0
  225. package/dist/sessions/session-key.js.map +1 -1
  226. package/dist/sessions/session-store.d.ts +47 -29
  227. package/dist/sessions/session-store.d.ts.map +1 -1
  228. package/dist/sessions/session-store.js +578 -484
  229. package/dist/sessions/session-store.js.map +1 -1
  230. package/dist/sessions/types.d.ts +60 -46
  231. package/dist/sessions/types.d.ts.map +1 -1
  232. package/dist/sessions/types.js +1 -1
  233. package/dist/sessions/types.js.map +1 -1
  234. package/dist/settings/apply.js +2 -2
  235. package/dist/settings/apply.js.map +1 -1
  236. package/dist/types.d.ts +10 -3
  237. package/dist/types.d.ts.map +1 -1
  238. package/dist/types.js.map +1 -1
  239. package/dist/vault/index.d.ts.map +1 -1
  240. package/dist/vault/index.js +0 -3
  241. package/dist/vault/index.js.map +1 -1
  242. package/dist/vault/types.d.ts +0 -3
  243. package/dist/vault/types.d.ts.map +1 -1
  244. package/dist/vault/types.js.map +1 -1
  245. package/package.json +8 -5
  246. package/dist/sessions/store.d.ts +0 -17
  247. package/dist/sessions/store.d.ts.map +0 -1
  248. package/dist/sessions/store.js +0 -211
  249. package/dist/sessions/store.js.map +0 -1
@@ -1,43 +1,39 @@
1
- import { TODO_CONTEXT, OperationMismatch, getOrThrow, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, } from "@earendil-works/pi-agent-core";
1
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
+ import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
3
+ import { AgentDoc, DEFAULT_COMPACTION_POLICY, CompactionTask, GenerationTask, LiveDoc, ToolTask, defineExtension, hook, section, watchEvents, } from "@earendil-works/pi-durable";
2
4
  import * as log from "../log.js";
3
5
  import { ToolLoopGuard } from "./loop-guard.js";
4
6
  import { adaptAgentTool, isHarnessTool } from "./tools/pi-tools.js";
5
7
  import { START_TASK_TOOL, TASK_STATUS_TOOL } from "./tools/task.js";
8
+ import { createCodemodeTool } from "./tools/codemode.js";
9
+ import { createToolSearchTool } from "./tools/tool-search.js";
10
+ import { withSecretRedaction } from "./tools/secret-redaction.js";
6
11
  import { errorMessage } from "../unknown-values.js";
7
- const IDLE_COMPACTION_FRACTION_OF_PI_THRESHOLD = 0.8;
8
- const FORWARDED_EVENTS = [
9
- "run_start",
10
- "run_end",
11
- "message_start",
12
- "message_update",
13
- "entry_added",
14
- "turn_start",
15
- "turn_end",
16
- "tool_start",
17
- "tool_update",
18
- "tool_end",
19
- "retry_scheduled",
20
- "retry_end",
21
- "compaction_start",
22
- "compaction_end",
23
- "usage",
24
- "fault",
25
- ];
12
+ const context = BACKGROUND_CONTEXT;
13
+ const COMPACTION_REQUEST = Symbol("compaction-request");
14
+ function deferred() {
15
+ let resolve;
16
+ const promise = new Promise((done) => {
17
+ resolve = done;
18
+ });
19
+ return { promise, resolve };
20
+ }
26
21
  export class MikanAgentSession {
27
22
  constructor(options) {
28
23
  this.options = options;
29
24
  this.listeners = new Set();
30
- this.transcript = [];
31
25
  this.runActive = false;
32
26
  this.runAborted = false;
33
27
  this.runBudget = {};
34
28
  this.retryAttempt = 0;
35
- this.latestAssistantNeedsCall = false;
36
- this.latestAssistantErrored = false;
37
29
  this.runMessages = [];
38
30
  this.toolArgs = new Map();
39
31
  this.loopGuard = new ToolLoopGuard();
40
32
  this.loopNotices = new Map();
33
+ this.runStarted = false;
34
+ this.runEndSeen = false;
35
+ this.observedUsage = createEmptyUsage();
36
+ this.runEnded = deferred();
41
37
  this.tally = {
42
38
  usage: createEmptyUsage(),
43
39
  llmCalls: 0,
@@ -50,8 +46,8 @@ export class MikanAgentSession {
50
46
  this.systemPrompt = options.systemPrompt;
51
47
  this.settings = resolveHarnessSettings(options.settings);
52
48
  }
53
- get messages() {
54
- return this.transcript;
49
+ get lastRunMessages() {
50
+ return this.runMessages;
55
51
  }
56
52
  get isActiveRun() {
57
53
  return this.runActive;
@@ -71,6 +67,7 @@ export class MikanAgentSession {
71
67
  toolCallCounts: { ...this.tally.toolCallCounts },
72
68
  durationMs: this.tally.startedAt > 0 ? (this.tally.endedAt ?? Date.now()) - this.tally.startedAt : 0,
73
69
  budgetExceededReason: this.budgetExceededReason || undefined,
70
+ status: this.lastSessionRunStatus,
74
71
  };
75
72
  }
76
73
  captureExternalUsageSink() {
@@ -84,86 +81,38 @@ export class MikanAgentSession {
84
81
  await this.exceedBudget(reason);
85
82
  };
86
83
  }
87
- async foldExternalUsage(usage) {
88
- await this.captureExternalUsageSink()(usage);
89
- }
90
84
  subscribe(listener) {
91
85
  this.listeners.add(listener);
92
86
  return () => this.listeners.delete(listener);
93
87
  }
94
- async reloadFromSession() {
95
- this.transcript = (await this.sessionStore.buildSessionContext()).messages;
96
- return this.transcript.length;
97
- }
98
88
  async prompt(text, options) {
99
89
  await this.run(text, options);
100
90
  }
101
91
  async steer(text) {
102
- if (!this.runActive || this.runAborted || !this.lane)
92
+ if (!this.runActive || this.runAborted || !this.attached)
103
93
  return false;
104
- getOrThrow(await this.lane.steer(text, undefined, TODO_CONTEXT));
94
+ await this.attached.conversation.submit({ type: "input", content: text, whenBusy: "steer" }, context);
105
95
  return true;
106
96
  }
107
- async resume(options) {
108
- await this.run(undefined, options);
109
- }
110
- compactWhenNearLimit() {
111
- if (this.runActive || this.idleCompaction || !this.lane)
112
- return;
113
- const { enabled, reserveTokens } = this.settings.compaction;
114
- if (!enabled)
115
- return;
116
- const threshold = (this.model.contextWindow - reserveTokens) * IDLE_COMPACTION_FRACTION_OF_PI_THRESHOLD;
117
- const { tokens } = estimateContextTokens(this.transcript);
118
- if (tokens < threshold)
119
- return;
120
- const lane = this.lane;
121
- log.logInfo(`Idle compaction started ${JSON.stringify({ tokens, threshold })}`);
122
- const compaction = lane
123
- .compact(undefined, TODO_CONTEXT)
124
- .then(async (result) => {
125
- if (!result.ok) {
126
- log.logInfo(`Idle compaction skipped: ${result.error.message}`);
127
- return;
128
- }
129
- log.logInfo(`Idle compaction ${result.value.compaction.status}`);
130
- await this.reloadFromSession();
131
- })
132
- .catch((error) => {
133
- log.logWarning("Idle compaction failed", errorMessage(error));
134
- })
135
- .finally(() => {
136
- if (this.idleCompaction === compaction)
137
- this.idleCompaction = undefined;
138
- });
139
- this.idleCompaction = compaction;
140
- }
141
- async cancelIdleCompaction() {
142
- const compaction = this.idleCompaction;
143
- if (!compaction || !this.lane)
144
- return;
145
- const aborted = await this.lane.abort(TODO_CONTEXT);
146
- if (!aborted.ok)
147
- log.logInfo(`Idle compaction abort: ${aborted.error.message}`);
148
- await compaction;
149
- }
150
97
  async run(text, options) {
151
98
  if (this.runActive)
152
99
  throw new Error("Agent is already processing a prompt");
153
100
  this.runActive = true;
154
101
  this.runAborted = false;
155
- this.pendingProviderRequest = undefined;
156
- this.currentProviderRequest = undefined;
157
- this.activeProviderRequest = undefined;
158
102
  this.budgetExceededReason = undefined;
103
+ this.cancellation = undefined;
159
104
  this.cancellationError = undefined;
160
105
  this.retryAttempt = 0;
161
- this.latestAssistantNeedsCall = false;
162
- this.latestAssistantErrored = false;
163
106
  this.runMessages = [];
107
+ this.runStarted = false;
108
+ this.runEndSeen = false;
109
+ this.runEnded = deferred();
110
+ this.activeRequest = undefined;
111
+ this.requestRunId = undefined;
164
112
  this.loopGuard = new ToolLoopGuard();
165
113
  this.loopNotices.clear();
166
114
  this.runBudget = { ...this.settings.budget, ...options?.budget };
115
+ this.lastSessionRunStatus = undefined;
167
116
  this.tally = {
168
117
  usage: createEmptyUsage(),
169
118
  llmCalls: 0,
@@ -172,8 +121,9 @@ export class MikanAgentSession {
172
121
  startedAt: Date.now(),
173
122
  };
174
123
  let runFailure;
124
+ let stream;
125
+ let status = "completed";
175
126
  try {
176
- await this.idleCompaction;
177
127
  if (!(await this.checkCallBudget()))
178
128
  return;
179
129
  this.armDeadline();
@@ -182,57 +132,63 @@ export class MikanAgentSession {
182
132
  return;
183
133
  if (!auth)
184
134
  throw new Error(`No credentials for provider "${this.model.provider}". Set the provider API key environment variable.`);
185
- await this.initialize();
135
+ const attached = await this.attach();
186
136
  if (this.runAborted)
187
137
  return;
188
- const harness = this.harness;
189
- const lane = this.lane;
190
138
  const tools = this.toHarnessTools(options?.tools ?? this.options.tools).filter((tool) => (tool.name !== START_TASK_TOOL || options?.allowTaskHandoff === true) &&
191
139
  (tool.name !== TASK_STATUS_TOOL || options?.allowTaskStatus === true));
192
- await harness.setTools(tools, TODO_CONTEXT);
193
- await lane.setActiveTools(tools.map((tool) => tool.name), TODO_CONTEXT);
194
- await this.reloadFromSession();
140
+ await this.installRunTools(attached, tools);
195
141
  if (!(await this.checkCallBudget()))
196
142
  return;
197
- await this.driveOperation(lane, text, options?.images);
143
+ await this.sessionStore.recordRun({});
144
+ stream = await watchEvents(attached.harness, attached.conversation.id, context);
145
+ this.observedUsage = sumUsageState(stream.snapshot.usage);
146
+ stream.start((events) => this.handleDurableEvents(events));
147
+ status = await this.drive(attached, text, options?.images);
198
148
  }
199
149
  catch (error) {
200
150
  runFailure = { error };
151
+ status = "failed";
201
152
  throw error;
202
153
  }
203
154
  finally {
204
- await this.cleanupRun(runFailure);
155
+ if (status !== "failed" && this.runAborted)
156
+ status = "aborted";
157
+ await this.cleanupRun(runFailure, stream, status);
205
158
  }
206
159
  }
207
- async driveOperation(lane, text, images) {
208
- if (text === undefined) {
209
- const current = await lane.inspectExecution(TODO_CONTEXT);
210
- this.operationId = current.current?.id;
211
- if (this.runAborted)
212
- this.requestCancellation();
213
- const result = getOrThrow(await lane.resume(TODO_CONTEXT));
214
- if (result.status === "failed")
215
- throw new Error(result.error?.message ?? "Pi run failed");
216
- return;
160
+ async drive({ harness, conversation }, text, images) {
161
+ const interrupted = (await harness.snapshot(LiveDoc, conversation.id, context))?.run;
162
+ if (interrupted) {
163
+ log.logWarning("Aborting a run interrupted before this process started");
164
+ await conversation.abort(context);
217
165
  }
218
- const admission = getOrThrow(await lane.accept({ kind: "prompt", prompt: text, images }, TODO_CONTEXT));
219
- this.operationId = admission.operationId;
166
+ const content = images && images.length > 0 ? [{ type: "text", text }, ...images] : text;
167
+ const submission = await conversation.submit({ type: "input", content, whenBusy: "reject" }, context);
220
168
  if (this.runAborted)
221
169
  this.requestCancellation();
222
- const result = getOrThrow(await lane.drive({ operationId: admission.operationId, waitForRetry: true }, TODO_CONTEXT));
223
- if (result.kind === "settled" &&
224
- result.outcome.status === "failed" &&
225
- !this.latestAssistantErrored) {
226
- throw new Error(result.outcome.error?.message ?? "Pi run failed");
227
- }
228
- }
229
- async cleanupRun(runFailure) {
170
+ const settled = await submission.wait(context);
171
+ if (this.runStarted)
172
+ await this.runEnded.promise;
173
+ if (settled.status === "done")
174
+ return "completed";
175
+ if (this.runAborted || settled.reason === "aborted")
176
+ return "aborted";
177
+ if (settled.reason === "model_error")
178
+ return "failed";
179
+ throw new Error(`Pi run failed: ${settled.reason}`);
180
+ }
181
+ async cleanupRun(runFailure, stream, status) {
230
182
  clearTimeout(this.deadlineTimer);
231
183
  this.deadlineTimer = undefined;
232
184
  let cleanupFailure;
233
185
  try {
234
186
  await this.cancellation;
235
187
  await this.deadlineNotification;
188
+ await stream?.stop();
189
+ if (this.attached) {
190
+ await this.sessionStore.recordRun({ endedAt: Date.now(), status });
191
+ }
236
192
  if (this.cancellationError)
237
193
  throw this.cancellationError;
238
194
  }
@@ -242,13 +198,10 @@ export class MikanAgentSession {
242
198
  finally {
243
199
  this.toolArgs.clear();
244
200
  this.loopNotices.clear();
245
- this.pendingProviderRequest = undefined;
246
- this.currentProviderRequest = undefined;
247
- this.activeProviderRequest = undefined;
248
- this.operationId = undefined;
249
201
  this.cancellation = undefined;
250
202
  this.deadlineNotification = undefined;
251
203
  this.tally.endedAt = Date.now();
204
+ this.lastSessionRunStatus = status;
252
205
  this.runActive = false;
253
206
  }
254
207
  if (!cleanupFailure)
@@ -265,103 +218,71 @@ export class MikanAgentSession {
265
218
  this.runAborted = true;
266
219
  clearTimeout(this.deadlineTimer);
267
220
  this.deadlineTimer = undefined;
268
- if (firstAbort && this.activeProviderRequest) {
269
- log.logInfo(`LLM request aborted ${JSON.stringify({
270
- abort_reason: reason,
271
- run_id: this.activeProviderRequest.runId,
272
- })}`);
221
+ if (firstAbort && this.activeRequest !== undefined) {
222
+ log.logInfo(`LLM request aborted ${JSON.stringify({ abort_reason: reason, run_id: this.activeRequest.runId })}`);
273
223
  }
274
224
  this.requestCancellation();
275
225
  }
276
226
  requestCancellation() {
277
- if (!this.lane || !this.operationId || this.cancellation)
227
+ if (!this.attached || this.cancellation)
278
228
  return;
279
- this.cancellation = this.lane
280
- .requestAbort(this.operationId, TODO_CONTEXT)
281
- .then((result) => {
282
- if (!result.ok && !(result.error instanceof OperationMismatch))
283
- throw result.error;
284
- })
285
- .catch((error) => {
229
+ this.cancellation = this.attached.conversation.abort(context).catch((error) => {
286
230
  this.cancellationError = error;
287
231
  });
288
232
  }
289
- beginProviderRequest() {
290
- const request = this.pendingProviderRequest;
291
- this.pendingProviderRequest = undefined;
292
- if (request)
293
- this.currentProviderRequest = request;
294
- return request;
295
- }
296
- endProviderRequest(request) {
297
- if (!request)
298
- return;
299
- if (this.currentProviderRequest?.token === request.token) {
300
- this.currentProviderRequest = undefined;
301
- }
302
- if (this.activeProviderRequest?.token === request.token) {
303
- this.activeProviderRequest = undefined;
304
- }
233
+ async attach() {
234
+ if (this.attached)
235
+ return this.attached;
236
+ const { retry, compaction } = this.settings;
237
+ this.attached = await this.sessionStore.bindHarness({
238
+ models: this.options.models.models,
239
+ requestModels: this.trackedModels(this.options.models.models),
240
+ env: () => this.options.toolContext?.env,
241
+ settings: { retry, compaction },
242
+ onReport: (error) => log.logWarning("Durable harness report", errorMessage(error)),
243
+ });
244
+ return this.attached;
305
245
  }
306
- withProviderActivation(options, request) {
307
- if (!request)
308
- return options;
309
- const onPayload = options?.onPayload;
310
- return {
246
+ trackedModels(models) {
247
+ const track = (options, token) => {
248
+ const onPayload = options?.onPayload;
249
+ const tracked = {
250
+ ...options,
251
+ onPayload: async (...args) => {
252
+ const transformed = await onPayload?.(...args);
253
+ if (this.runActive) {
254
+ this.activeRequest = { token, runId: this.requestRunId ?? "background" };
255
+ }
256
+ return transformed;
257
+ },
258
+ };
259
+ return tracked;
260
+ };
261
+ const withSession = (options) => ({
311
262
  ...options,
312
- onPayload: async (payload, model) => {
313
- const transformed = await onPayload?.(payload, model);
314
- if (this.currentProviderRequest?.token === request.token) {
315
- this.activeProviderRequest = request;
316
- }
317
- return transformed;
318
- },
263
+ sessionId: options.sessionId ?? this.sessionStore.getSessionId(),
264
+ });
265
+ const finish = (token) => {
266
+ if (this.activeRequest?.token === token)
267
+ this.activeRequest = undefined;
268
+ };
269
+ const stream = (start) => {
270
+ const token = Symbol("provider-request");
271
+ const result = start(token);
272
+ void result.result().then(() => finish(token), () => finish(token));
273
+ return result;
319
274
  };
320
- }
321
- trackProviderStream(start) {
322
- const request = this.beginProviderRequest();
323
- try {
324
- const stream = start(request);
325
- if (request) {
326
- void stream.result().then(() => this.endProviderRequest(request), () => this.endProviderRequest(request));
327
- }
328
- return stream;
329
- }
330
- catch (error) {
331
- this.endProviderRequest(request);
332
- throw error;
333
- }
334
- }
335
- trackProviderPromise(start) {
336
- const request = this.beginProviderRequest();
337
- try {
338
- const promise = start(request);
339
- if (!request)
340
- return promise;
341
- return promise.then((result) => {
342
- this.endProviderRequest(request);
343
- return result;
344
- }, (error) => {
345
- this.endProviderRequest(request);
346
- throw error;
347
- });
348
- }
349
- catch (error) {
350
- this.endProviderRequest(request);
351
- throw error;
352
- }
353
- }
354
- trackedModels(models) {
355
275
  return new Proxy(models, {
356
276
  get: (target, property) => {
357
277
  if (property === "streamSimple") {
358
- return (...[model, context, options]) => this.trackProviderStream((request) => target.streamSimple(model, context, this.withProviderActivation(options, request)));
359
- }
360
- if (property === "completeSimple") {
361
- return (...[model, context, options]) => this.trackProviderPromise((request) => target.completeSimple(model, context, this.withProviderActivation(options, request)));
362
- }
363
- if (property === "streamDeferred") {
364
- return (...[model, handle, options]) => this.trackProviderStream((request) => target.streamDeferred(model, handle, this.withProviderActivation(options, request)));
278
+ return (...[model, request, options]) => {
279
+ const over = this.runActive ? this.callOverBudgetReason() : undefined;
280
+ if (over || this.runAborted)
281
+ return abortedStream(model, over ?? "Operation aborted");
282
+ if (this.runActive)
283
+ this.tally.llmCalls += 1;
284
+ return stream((token) => target.streamSimple(model, request, withSession(track(options, token))));
285
+ };
365
286
  }
366
287
  const value = Reflect.get(target, property, target);
367
288
  return typeof value === "function" ? value.bind(target) : value;
@@ -371,69 +292,130 @@ export class MikanAgentSession {
371
292
  toHarnessTools(tools) {
372
293
  return tools.map((tool) => (isHarnessTool(tool) ? tool : adaptAgentTool(tool)));
373
294
  }
374
- async initialize() {
375
- if (this.harness)
376
- return;
377
- this.harness = await this.sessionStore.createHarness({
378
- models: this.trackedModels(this.options.models.models),
379
- model: this.model,
295
+ async installRunTools(attached, selected) {
296
+ const tools = [...selected];
297
+ const deferredTools = tools.filter((tool) => tool.exposure === "deferred");
298
+ const authorized = new Set(deferredTools.map((tool) => tool.name));
299
+ const { harness, conversation, registry, extensionName } = attached;
300
+ const stored = (await harness.snapshot(AgentDoc, conversation.id, context))?.tools;
301
+ const loaded = new Set(Array.isArray(stored) ? stored.filter((name) => authorized.has(name)) : []);
302
+ if (tools.length > 0)
303
+ tools.push(withSecretRedaction(createCodemodeTool({
304
+ tools,
305
+ executeNested: (tool, call) => this.executeNestedTool(tool, call),
306
+ })));
307
+ if (deferredTools.length > 0) {
308
+ tools.push(withSecretRedaction(createToolSearchTool({
309
+ tools: deferredTools,
310
+ loaded,
311
+ load: async (names) => {
312
+ for (const name of names)
313
+ if (authorized.has(name))
314
+ loaded.add(name);
315
+ },
316
+ })));
317
+ }
318
+ const registered = tools;
319
+ registry.install(defineExtension({
320
+ name: extensionName,
321
+ tools: registered,
322
+ sections: [
323
+ section("mikan", () => this.sessionStore.withMcpInstructions(this.systemPrompt), {
324
+ tag: false,
325
+ }),
326
+ ],
327
+ hooks: [
328
+ hook(GenerationTask, {
329
+ beforeRequest: async (request, api) => {
330
+ await this.beforeRequest(String(api.taskId));
331
+ return { messages: attached.tagRequest(request.messages) };
332
+ },
333
+ }),
334
+ hook(CompactionTask, {
335
+ beforeCompact: (_compaction, api) => this.beforeCompact(String(api.taskId)),
336
+ }),
337
+ hook(ToolTask, {
338
+ beforeTool: async (call, api) => this.beforeTool(call, api),
339
+ afterTool: (call, result) => this.afterTool(call.id, result),
340
+ }),
341
+ ],
342
+ }));
343
+ await conversation.configure({
344
+ model: { provider: this.model.provider, modelId: this.model.id },
380
345
  thinkingLevel: this.options.thinkingLevel,
381
- tools: this.toHarnessTools(this.options.tools),
382
- toolContext: this.options.toolContext,
383
- systemPrompt: () => this.sessionStore.withMcpInstructions(this.systemPrompt),
384
- retry: this.settings.retry,
385
- compaction: this.settings.compaction,
386
- });
387
- this.lane = await this.harness.lane("main", TODO_CONTEXT);
388
- await this.lane.setModel({ provider: this.model.provider, modelId: this.model.id }, TODO_CONTEXT);
389
- await this.lane.setThinkingLevel(this.options.thinkingLevel, TODO_CONTEXT);
390
- this.harness.hooks.on("before_request", async (event) => {
391
- if (!(await this.checkCallBudget()))
392
- return undefined;
393
- this.pendingProviderRequest = { token: Symbol("provider-request"), runId: event.runId };
394
- this.tally.llmCalls += 1;
346
+ extensions: [registry.snapshot().extension(extensionName)],
347
+ tools: registered.filter((tool) => tool.exposure !== "deferred" || loaded.has(tool.name)),
348
+ }, context);
349
+ }
350
+ beforeCompact(taskId) {
351
+ if (!this.runActive)
395
352
  return undefined;
396
- });
397
- this.harness.hooks.on("before_tool", () => {
398
- const assistant = this.transcript.findLast((message) => message.role === "assistant");
399
- const calls = assistant?.role === "assistant"
400
- ? assistant.content.filter((part) => part.type === "toolCall")
401
- : [];
402
- if (calls.length > 1 && calls.some((call) => call.name === START_TASK_TOOL)) {
403
- return {
404
- block: {
405
- reason: `Call ${START_TASK_TOOL} alone, without other tools in the same batch.`,
406
- },
407
- };
408
- }
353
+ if (this.runAborted || this.budgetExceededReason || this.callOverBudgetReason()) {
354
+ return { decline: true };
355
+ }
356
+ this.tally.llmCalls += 1;
357
+ this.activeRequest = { token: COMPACTION_REQUEST, runId: taskId };
358
+ return undefined;
359
+ }
360
+ async beforeRequest(runId) {
361
+ if (!(await this.checkCallBudget()))
409
362
  return undefined;
410
- });
411
- this.harness.hooks.on("before_tool", async (event) => {
412
- const verdict = this.loopGuard.observe(event.toolName, event.args);
413
- switch (verdict.kind) {
414
- case "allow":
415
- return undefined;
416
- case "notice":
417
- this.loopNotices.set(event.toolCallId, verdict.text);
418
- return undefined;
419
- case "block":
420
- return { block: { reason: verdict.reason } };
421
- case "stop":
422
- await this.exceedBudget(verdict.reason);
423
- return { block: { reason: verdict.reason, terminate: true } };
424
- default:
425
- return verdict;
426
- }
427
- });
428
- this.harness.hooks.on("after_tool", (event) => {
429
- const notice = this.loopNotices.get(event.toolCallId);
430
- if (notice === undefined)
363
+ this.requestRunId = runId;
364
+ return undefined;
365
+ }
366
+ async beforeTool(call, api) {
367
+ const round = (await api.snapshot(LiveDoc, api.conversationId, context))?.tools ?? [];
368
+ if (round.length > 1 && round.some((slot) => slot.name === START_TASK_TOOL)) {
369
+ return { block: `Call ${START_TASK_TOOL} alone, without other tools in the same batch.` };
370
+ }
371
+ return this.checkToolLoop(call.id, call.name, call.arguments);
372
+ }
373
+ afterTool(callId, result) {
374
+ const notice = this.loopNotices.get(callId);
375
+ if (notice === undefined)
376
+ return undefined;
377
+ this.loopNotices.delete(callId);
378
+ return { ...result, content: [...(result.content ?? []), { type: "text", text: notice }] };
379
+ }
380
+ async executeNestedTool(tool, call) {
381
+ const [args, api, toolContext] = call;
382
+ const id = api.callId;
383
+ if (this.runAborted || toolContext.abortSignal?.aborted)
384
+ throw new Error("Operation aborted");
385
+ await this.emitToolStart(id, tool.name, args);
386
+ let result;
387
+ try {
388
+ const verdict = await this.checkToolLoop(id, tool.name, args);
389
+ if (verdict?.block)
390
+ throw new Error(verdict.block);
391
+ toolContext.abortSignal?.throwIfAborted();
392
+ result = await tool.execute(args, api, toolContext);
393
+ result = this.afterTool(id, result) ?? result;
394
+ }
395
+ catch (error) {
396
+ result = { content: [{ type: "text", text: errorMessage(error) }], isError: true };
397
+ }
398
+ finally {
399
+ this.loopNotices.delete(id);
400
+ }
401
+ await this.emitToolEnd(id, tool.name, { content: result.content ?? [], details: result.details }, result.isError === true);
402
+ return result;
403
+ }
404
+ async checkToolLoop(toolCallId, toolName, args) {
405
+ const verdict = this.loopGuard.observe(toolName, args);
406
+ switch (verdict.kind) {
407
+ case "allow":
431
408
  return undefined;
432
- this.loopNotices.delete(event.toolCallId);
433
- return { content: [...event.content, { type: "text", text: notice }] };
434
- });
435
- for (const type of FORWARDED_EVENTS) {
436
- this.harness.events.on(type, (event) => this.handlePiEvent(event));
409
+ case "notice":
410
+ this.loopNotices.set(toolCallId, verdict.text);
411
+ return undefined;
412
+ case "block":
413
+ return { block: verdict.reason };
414
+ case "stop":
415
+ await this.exceedBudget(verdict.reason);
416
+ return { block: verdict.reason };
417
+ default:
418
+ return verdict;
437
419
  }
438
420
  }
439
421
  async emit(event) {
@@ -446,174 +428,147 @@ export class MikanAgentSession {
446
428
  }
447
429
  }
448
430
  }
449
- async handlePiEvent(event) {
450
- if (!this.runActive)
451
- return;
452
- if ("lane" in event && event.lane !== undefined && event.lane !== "main")
453
- return;
431
+ async handleDurableEvents(events) {
432
+ for (const event of events) {
433
+ try {
434
+ await this.handleDurableEvent(event);
435
+ }
436
+ catch (error) {
437
+ log.logWarning("Durable event handling failed", errorMessage(error));
438
+ }
439
+ }
440
+ if (this.runEndSeen)
441
+ this.runEnded.resolve();
442
+ }
443
+ async handleDurableEvent(event) {
454
444
  switch (event.type) {
455
- case "entry_added":
456
- if (event.entry.type !== "message")
457
- return;
458
- this.transcript.push(event.entry.message);
459
- this.runMessages.push(event.entry.message);
460
- if (event.entry.message.role === "assistant") {
461
- const message = event.entry.message;
462
- this.latestAssistantErrored = message.stopReason === "error";
463
- this.latestAssistantNeedsCall =
464
- message.stopReason === "error" ||
465
- message.content.some((part) => part.type === "toolCall");
466
- }
467
- await this.emit({ type: "message_end", message: event.entry.message });
445
+ case "run_start":
446
+ this.runStarted = true;
447
+ return;
448
+ case "run_end":
449
+ await this.endRetry(this.runAborted ? "Retry cancelled" : undefined);
450
+ this.runEndSeen = true;
468
451
  return;
469
452
  case "message_start":
453
+ if (event.message.role === "system")
454
+ return;
470
455
  await this.emit({ type: "message_start", message: event.message });
471
456
  return;
472
457
  case "message_update":
473
- await this.emit({
474
- type: "message_update",
475
- message: event.message,
476
- assistantMessageEvent: event.event,
477
- });
478
- return;
479
- case "turn_start":
480
- await this.emit({ type: "turn_start" });
481
- return;
482
- case "turn_end":
483
- await this.emit({
484
- type: "turn_end",
485
- message: event.message,
486
- toolResults: event.toolResults,
487
- });
488
- return;
489
- case "tool_start":
490
- case "tool_update":
491
- case "tool_end":
492
- return this.handlePiToolEvent(event);
493
- case "usage":
494
- return this.recordUsage(event.row.usage);
495
- case "run_start":
496
- case "run_end":
497
- case "retry_scheduled":
498
- case "retry_end":
499
- case "compaction_start":
500
- case "compaction_end":
501
- return this.handlePiLifecycleEvent(event);
502
- case "fault":
503
- throw new Error(event.message);
504
- }
505
- }
506
- async recordUsage(usage) {
507
- addUsage(this.tally.usage, usage);
508
- const reason = (this.latestAssistantNeedsCall && this.callOverBudgetReason()) ||
509
- this.resourceOverBudgetReason();
510
- if (reason)
511
- await this.exceedBudget(reason);
512
- }
513
- async handlePiToolEvent(event) {
514
- switch (event.type) {
515
- case "tool_start":
516
- this.toolArgs.set(event.toolCallId, event.args);
517
- this.tally.toolCalls += 1;
518
- this.tally.toolCallCounts[event.toolName] =
519
- (this.tally.toolCallCounts[event.toolName] ?? 0) + 1;
520
- await this.emit({
521
- type: "tool_execution_start",
522
- toolCallId: event.toolCallId,
523
- toolName: event.toolName,
524
- args: event.args,
525
- });
458
+ for (const change of event.changes) {
459
+ if (change.type === "text_delta" && change.delta) {
460
+ await this.emit({ type: "text_delta", delta: change.delta });
461
+ }
462
+ }
526
463
  return;
527
- case "tool_update":
464
+ case "message_end":
465
+ return this.endMessage(event.entry.model?.[0]);
466
+ case "tool_execution_start":
467
+ return this.emitToolStart(event.toolCallId, event.toolName, event.args);
468
+ case "tool_execution_update":
469
+ if (event.details === undefined)
470
+ return;
528
471
  await this.emit({
529
472
  type: "tool_execution_update",
530
473
  toolCallId: event.toolCallId,
531
474
  toolName: event.toolName,
532
- args: this.toolArgs.get(event.toolCallId) ?? {},
533
- partialResult: event.partialResult,
475
+ details: event.details,
534
476
  });
535
477
  return;
536
- case "tool_end":
537
- this.toolArgs.delete(event.toolCallId);
538
- await this.emit({
539
- type: "tool_execution_end",
540
- toolCallId: event.toolCallId,
541
- toolName: event.toolName,
542
- result: event.result,
543
- isError: event.isError,
544
- });
545
- return;
546
- default:
547
- event;
548
- }
549
- }
550
- async handlePiLifecycleEvent(event) {
551
- switch (event.type) {
552
- case "run_start":
553
- this.operationId = event.runId;
554
- if (this.runAborted)
555
- this.requestCancellation();
556
- await this.emit({ type: "agent_start" });
478
+ case "tool_execution_end": {
479
+ const message = event.entry?.model?.[0];
480
+ const toolResult = message?.role === "toolResult" ? message : undefined;
481
+ await this.emitToolEnd(event.toolCallId, event.toolName, { content: toolResult?.content ?? [], details: toolResult?.details }, toolResult?.isError ?? true);
557
482
  return;
558
- case "retry_scheduled":
559
- this.retryAttempt = event.attempt - 1;
483
+ }
484
+ case "auto_retry_start":
485
+ this.retryAttempt = event.attempt;
560
486
  await this.emit({
561
487
  type: "auto_retry_start",
562
- attempt: this.retryAttempt,
563
- maxAttempts: event.maxAttempts - 1,
564
- delayMs: event.delayMs,
488
+ attempt: event.attempt,
489
+ maxAttempts: this.settings.retry.maxRetries,
490
+ delayMs: Math.max(0, event.at - Date.now()),
565
491
  errorMessage: event.errorMessage,
566
492
  });
567
493
  return;
568
- case "retry_end":
494
+ case "auto_retry_end":
569
495
  this.retryAttempt = 0;
570
496
  await this.emit({
571
497
  type: "auto_retry_end",
572
- attempt: event.attempt - 1,
573
- success: event.success,
574
- finalError: event.finalError,
498
+ attempt: event.attempt,
499
+ success: !this.runAborted,
500
+ finalError: this.runAborted ? "Retry cancelled" : undefined,
575
501
  });
576
502
  return;
577
503
  case "compaction_start":
578
504
  await this.emit({ type: "compaction_start", reason: event.reason });
579
505
  return;
580
506
  case "compaction_end": {
581
- const entry = event.status === "completed"
582
- ? await this.sessionStore.getEntry(event.entryId)
583
- : undefined;
584
- if (entry?.type === "compaction")
585
- await this.reloadFromSession();
507
+ if (this.activeRequest?.token === COMPACTION_REQUEST)
508
+ this.activeRequest = undefined;
509
+ const outcome = await this.compactionOutcome(event.taskId);
586
510
  await this.emit({
587
511
  type: "compaction_end",
588
512
  reason: event.reason,
589
- aborted: event.status === "aborted",
590
- errorMessage: event.status === "failed" ? event.error.message : undefined,
591
- result: entry?.type === "compaction"
592
- ? {
593
- summary: entry.summary,
594
- retainedMessages: entry.retainedTail.length,
595
- tokensBefore: entry.tokensBefore,
596
- }
597
- : undefined,
513
+ aborted: outcome?.status === "aborted",
514
+ errorMessage: outcome?.status === "failed" ? outcome.error.message : undefined,
598
515
  });
599
516
  return;
600
517
  }
601
- case "run_end":
602
- if (this.retryAttempt > 0) {
603
- await this.emit({
604
- type: "auto_retry_end",
605
- attempt: this.retryAttempt,
606
- success: false,
607
- finalError: event.status === "aborted" ? "Retry cancelled" : event.error?.message,
608
- });
609
- this.retryAttempt = 0;
610
- }
611
- await this.emit({ type: "agent_end", messages: this.runMessages });
518
+ case "usage_changed":
519
+ return this.recordUsage(event.usage);
520
+ case "task_failed":
521
+ log.logWarning(`Durable task ${event.kind} failed`, event.message);
612
522
  return;
613
523
  default:
614
- event;
524
+ return;
615
525
  }
616
526
  }
527
+ async compactionOutcome(taskId) {
528
+ const task = await this.attached?.harness.getTask(taskId, context);
529
+ return task?.state.outcome;
530
+ }
531
+ async recordUsage(state) {
532
+ const total = sumUsageState(state);
533
+ addUsage(this.tally.usage, subtractUsage(total, this.observedUsage));
534
+ this.observedUsage = total;
535
+ const reason = this.resourceOverBudgetReason();
536
+ if (reason)
537
+ await this.exceedBudget(reason);
538
+ }
539
+ async endRetry(finalError) {
540
+ if (this.retryAttempt === 0)
541
+ return;
542
+ await this.emit({
543
+ type: "auto_retry_end",
544
+ attempt: this.retryAttempt,
545
+ success: false,
546
+ finalError,
547
+ });
548
+ this.retryAttempt = 0;
549
+ }
550
+ async endMessage(message) {
551
+ if (!message || message.role === "system")
552
+ return;
553
+ this.runMessages.push(message);
554
+ await this.emit({ type: "message_end", message });
555
+ if (message.role !== "assistant")
556
+ return;
557
+ const needsCall = message.stopReason === "error" || message.content.some((part) => part.type === "toolCall");
558
+ const reason = (needsCall && this.callOverBudgetReason()) || this.resourceOverBudgetReason();
559
+ if (reason)
560
+ await this.exceedBudget(reason);
561
+ }
562
+ async emitToolStart(toolCallId, toolName, args) {
563
+ this.toolArgs.set(toolCallId, args);
564
+ this.tally.toolCalls += 1;
565
+ this.tally.toolCallCounts[toolName] = (this.tally.toolCallCounts[toolName] ?? 0) + 1;
566
+ await this.emit({ type: "tool_execution_start", toolCallId, toolName, args });
567
+ }
568
+ async emitToolEnd(toolCallId, toolName, result, isError) {
569
+ this.toolArgs.delete(toolCallId);
570
+ await this.emit({ type: "tool_execution_end", toolCallId, toolName, result, isError });
571
+ }
617
572
  async exceedBudget(reason) {
618
573
  if (this.budgetExceededReason)
619
574
  return;
@@ -669,6 +624,26 @@ export class MikanAgentSession {
669
624
  }, Math.min(Math.max(0, remaining), 2_147_483_647));
670
625
  }
671
626
  }
627
+ function abortedAssistant(model, reason) {
628
+ return {
629
+ role: "assistant",
630
+ content: [],
631
+ api: model.api,
632
+ provider: model.provider,
633
+ model: model.id,
634
+ usage: createEmptyUsage(),
635
+ stopReason: "aborted",
636
+ errorMessage: reason,
637
+ timestamp: Date.now(),
638
+ };
639
+ }
640
+ function abortedStream(model, reason) {
641
+ const stream = createAssistantMessageEventStream();
642
+ const error = abortedAssistant(model, reason);
643
+ stream.push({ type: "error", reason: "aborted", error });
644
+ stream.end(error);
645
+ return stream;
646
+ }
672
647
  export const DEFAULT_RETRY_SETTINGS = {
673
648
  enabled: true,
674
649
  maxRetries: 3,
@@ -682,7 +657,7 @@ export const DEFAULT_EVENT_BUDGET = {
682
657
  };
683
658
  export function resolveHarnessSettings(overrides) {
684
659
  return {
685
- compaction: { ...DEFAULT_COMPACTION_SETTINGS, ...overrides?.compaction },
660
+ compaction: { ...DEFAULT_COMPACTION_POLICY, ...overrides?.compaction },
686
661
  retry: { ...DEFAULT_RETRY_SETTINGS, ...overrides?.retry },
687
662
  budget: { ...DEFAULT_BUDGET_SETTINGS, ...overrides?.budget },
688
663
  };
@@ -715,6 +690,30 @@ function addUsage(total, usage) {
715
690
  total.reasoning = (total.reasoning ?? 0) + usage.reasoning;
716
691
  }
717
692
  }
693
+ function sumUsageState(state) {
694
+ const total = createEmptyUsage();
695
+ for (const usage of [...Object.values(state.models), ...Object.values(state.tools)]) {
696
+ addUsage(total, usage);
697
+ }
698
+ return total;
699
+ }
700
+ function subtractUsage(total, base) {
701
+ return {
702
+ input: total.input - base.input,
703
+ output: total.output - base.output,
704
+ cacheRead: total.cacheRead - base.cacheRead,
705
+ cacheWrite: total.cacheWrite - base.cacheWrite,
706
+ totalTokens: total.totalTokens - base.totalTokens,
707
+ cost: {
708
+ input: total.cost.input - base.cost.input,
709
+ output: total.cost.output - base.cost.output,
710
+ cacheRead: total.cost.cacheRead - base.cost.cacheRead,
711
+ cacheWrite: total.cost.cacheWrite - base.cost.cacheWrite,
712
+ total: total.cost.total - base.cost.total,
713
+ },
714
+ reasoning: total.reasoning === undefined ? undefined : total.reasoning - (base.reasoning ?? 0),
715
+ };
716
+ }
718
717
  export function copyUsage(usage) {
719
718
  return { ...usage, cost: { ...usage.cost } };
720
719
  }