@mono-agent/agent-runtime 0.20.11 → 0.20.14

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 (101) hide show
  1. package/MIGRATION.md +264 -25
  2. package/README.md +139 -448
  3. package/package.json +7 -43
  4. package/src/agent/tools/agent-tool.js +5 -5
  5. package/src/agent/tools/bash.js +1 -24
  6. package/src/agent/tools/codex-subscription-search.js +1 -1
  7. package/src/agent/tools/index.js +7 -0
  8. package/src/agent/tools/monitor.js +140 -0
  9. package/src/agent/tools/pi-bridge.js +92 -7
  10. package/src/agent/tools/shared/bash-environment.js +31 -0
  11. package/src/agent/tools/shared/monitors.js +274 -0
  12. package/src/agent/tools/shared/process-runner.js +23 -5
  13. package/src/ai/cost.js +13 -68
  14. package/src/ai/index.js +4 -17
  15. package/src/ai/pi-interop.js +65 -1
  16. package/src/ai/pi-oauth-compat.js +1 -1
  17. package/src/ai/providers/codex/app-server-client.js +592 -0
  18. package/src/ai/providers/pi-models.js +18 -10
  19. package/src/ai/providers/pi-native/compaction-driver.js +49 -21
  20. package/src/ai/providers/pi-native/harness-adapter.js +338 -0
  21. package/src/ai/providers/pi-native/result-builder.js +10 -10
  22. package/src/ai/providers/pi-native/session-lifecycle.js +95 -40
  23. package/src/ai/providers/pi-native/stream-subscriber.js +23 -5
  24. package/src/ai/providers/pi-native/turn-runner.js +34 -15
  25. package/src/ai/providers/pi-native.js +48 -22
  26. package/src/ai/runtime/capabilities.js +11 -56
  27. package/src/ai/runtime/model-refs.js +118 -153
  28. package/src/ai/runtime/registry.js +22 -56
  29. package/src/ai/runtime/router.js +48 -408
  30. package/src/ai/runtime/session-liveness.js +3 -4
  31. package/src/ai/runtime/sessions.js +4 -5
  32. package/src/ai/runtime/tool-policy.js +0 -2
  33. package/src/ai/types.js +12 -108
  34. package/src/index.js +0 -6
  35. package/src/runtime.js +8 -14
  36. package/types/agent/tools/agent-tool.d.ts +1 -2
  37. package/types/agent/tools/codex-subscription-search.d.ts +1 -1
  38. package/types/agent/tools/index.d.ts +1 -0
  39. package/types/agent/tools/monitor.d.ts +44 -0
  40. package/types/agent/tools/pi-bridge.d.ts +3 -2
  41. package/types/agent/tools/shared/bash-environment.d.ts +4 -0
  42. package/types/agent/tools/shared/monitors.d.ts +83 -0
  43. package/types/agent/tools/shared/process-runner.d.ts +11 -2
  44. package/types/ai/cost.d.ts +1 -2
  45. package/types/ai/index.d.ts +1 -4
  46. package/types/ai/pi-interop.d.ts +20 -0
  47. package/types/ai/providers/codex/app-server-client.d.ts +37 -0
  48. package/types/ai/providers/pi-native/harness-adapter.d.ts +56 -0
  49. package/types/ai/providers/pi-native/result-builder.d.ts +3 -3
  50. package/types/ai/providers/pi-native/session-lifecycle.d.ts +2 -1
  51. package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
  52. package/types/ai/providers/pi-native/turn-runner.d.ts +33 -6
  53. package/types/ai/providers/pi-native.d.ts +21 -4
  54. package/types/ai/runtime/capabilities.d.ts +21 -70
  55. package/types/ai/runtime/model-refs.d.ts +0 -24
  56. package/types/ai/runtime/router.d.ts +3 -10
  57. package/types/ai/runtime/tool-policy.d.ts +0 -2
  58. package/types/ai/types.d.ts +28 -250
  59. package/types/index.d.ts +0 -1
  60. package/src/ai/providers/acp-client.js +0 -1149
  61. package/src/ai/providers/acp-privacy.js +0 -124
  62. package/src/ai/providers/acp-public.js +0 -21
  63. package/src/ai/providers/acp-session-tokens.js +0 -282
  64. package/src/ai/providers/acp-transport.js +0 -356
  65. package/src/ai/providers/acp.js +0 -543
  66. package/src/ai/providers/claude-cli.js +0 -883
  67. package/src/ai/providers/claude-sandbox.js +0 -71
  68. package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
  69. package/src/ai/providers/claude-sdk-discovery.js +0 -352
  70. package/src/ai/providers/claude-sdk.js +0 -1127
  71. package/src/ai/providers/claude-subagent-activity.js +0 -719
  72. package/src/ai/providers/claude-subagents.js +0 -88
  73. package/src/ai/providers/codex-app.js +0 -2946
  74. package/src/ai/providers/opencode-app.js +0 -1109
  75. package/src/ai/providers/opencode-discovery.js +0 -39
  76. package/src/ai/providers/opencode-server.js +0 -508
  77. package/src/ai/runtime/context-windows.js +0 -46
  78. package/src/ai/runtime/fast-mode.js +0 -8
  79. package/src/ai/streaming/codex-events.js +0 -146
  80. package/src/ai/streaming/opencode-events.js +0 -59
  81. package/types/ai/providers/acp-client.d.ts +0 -227
  82. package/types/ai/providers/acp-privacy.d.ts +0 -25
  83. package/types/ai/providers/acp-public.d.ts +0 -7
  84. package/types/ai/providers/acp-session-tokens.d.ts +0 -41
  85. package/types/ai/providers/acp-transport.d.ts +0 -45
  86. package/types/ai/providers/acp.d.ts +0 -93
  87. package/types/ai/providers/claude-cli.d.ts +0 -305
  88. package/types/ai/providers/claude-sandbox.d.ts +0 -79
  89. package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
  90. package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
  91. package/types/ai/providers/claude-sdk.d.ts +0 -138
  92. package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
  93. package/types/ai/providers/claude-subagents.d.ts +0 -18
  94. package/types/ai/providers/codex-app.d.ts +0 -151
  95. package/types/ai/providers/opencode-app.d.ts +0 -96
  96. package/types/ai/providers/opencode-discovery.d.ts +0 -4
  97. package/types/ai/providers/opencode-server.d.ts +0 -20
  98. package/types/ai/runtime/context-windows.d.ts +0 -9
  99. package/types/ai/runtime/fast-mode.d.ts +0 -2
  100. package/types/ai/streaming/codex-events.d.ts +0 -40
  101. package/types/ai/streaming/opencode-events.d.ts +0 -42
@@ -24,7 +24,7 @@
24
24
  // on the caller-owned runState.compaction.
25
25
 
26
26
  import {
27
- buildSessionContext,
27
+ BACKGROUND_CONTEXT,
28
28
  calculateContextTokens,
29
29
  compact as compactPreparedContext,
30
30
  estimateContextTokens,
@@ -45,6 +45,7 @@ import {
45
45
  parseContextLimitFromError,
46
46
  } from "../pi-errors.js";
47
47
  import { appendStructuredOutputInstruction } from "./structured-output.js";
48
+ import { buildPiSessionContext } from "./harness-adapter.js";
48
49
  import { runHarnessPrompt } from "./turn-runner.js";
49
50
 
50
51
  // Per-process cache of real context-window ceilings discovered from overflow
@@ -157,9 +158,10 @@ async function estimateSessionMessageTokens(session) {
157
158
  }
158
159
  }
159
160
 
160
- function estimateBuiltContextTokens(branchEntries) {
161
+ async function estimateBuiltContextTokens(branchEntries) {
161
162
  try {
162
- return buildSessionContext(branchEntries).messages.reduce(
163
+ const messages = buildPiSessionContext(branchEntries);
164
+ return messages.reduce(
163
165
  (total, message) => total + (Number(estimateTokens(message)) || 0),
164
166
  0,
165
167
  );
@@ -190,16 +192,17 @@ export function piSummaryReserveTokens(summaryMaxTokens, isSplitTurn) {
190
192
  return reserve;
191
193
  }
192
194
 
193
- function previewCompactedContext(branchEntries, result) {
195
+ async function previewCompactedContext(branchEntries, result) {
194
196
  const previewEntry = {
195
197
  type: "compaction",
196
198
  id: "mono-agent-compaction-preview",
197
199
  parentId: branchEntries.at(-1)?.id || null,
198
- timestamp: new Date().toISOString(),
200
+ timestamp: Date.now(),
199
201
  summary: result.summary,
200
- firstKeptEntryId: result.firstKeptEntryId,
202
+ retainedTail: result.retainedTail,
201
203
  tokensBefore: result.tokensBefore,
202
204
  details: result.details,
205
+ usage: result.usage,
203
206
  fromHook: true,
204
207
  };
205
208
  return estimateBuiltContextTokens([...branchEntries, previewEntry]);
@@ -273,7 +276,7 @@ export async function tryCompact(harness, {
273
276
  model,
274
277
  });
275
278
  let effectivePolicy = policy || {};
276
- /** @type {null | {kind: string, tokensBefore?: number|null, tokensAfter?: number|null, savings?: number|null, error?: any}} */
279
+ /** @type {null | {kind: string, tokensBefore?: number|null, tokensAfter?: number|null, savings?: number|null, firstKeptEntryId?: string|null, error?: any}} */
277
280
  let hookDecision = null;
278
281
  let removeHook = null;
279
282
  try {
@@ -281,16 +284,20 @@ export async function tryCompact(harness, {
281
284
  contextWindow: typeof harness?.getModel === "function" ? harness.getModel()?.contextWindow : undefined,
282
285
  });
283
286
  effectivePolicy = { ...adaptivePolicy, ...(policy || {}) };
287
+ const compactionSettings = {
288
+ enabled: true,
289
+ reserveTokens: piSummaryReserveTokens(effectivePolicy.summaryMaxTokens, false),
290
+ keepRecentTokens: effectivePolicy.keepRecentTokens,
291
+ };
292
+ if (typeof harness?.setCompactionSettings === "function") {
293
+ await harness.setCompactionSettings(compactionSettings);
294
+ }
284
295
  if (typeof harness?.on !== "function") {
285
296
  throw new Error("Pi AgentHarness does not expose session_before_compact hooks");
286
297
  }
287
298
  removeHook = harness.on("session_before_compact", async (event) => {
288
299
  try {
289
- let settings = {
290
- enabled: true,
291
- reserveTokens: piSummaryReserveTokens(effectivePolicy.summaryMaxTokens, false),
292
- keepRecentTokens: effectivePolicy.keepRecentTokens,
293
- };
300
+ let settings = compactionSettings;
294
301
  let prepared = prepareCompaction(event.branchEntries, settings);
295
302
  if (prepared.ok === false) {
296
303
  hookDecision = { kind: "failed", error: prepared.error };
@@ -320,16 +327,22 @@ export async function tryCompact(harness, {
320
327
  harness.models,
321
328
  harness.getModel(),
322
329
  event.customInstructions,
323
- event.signal,
324
330
  typeof harness.getThinkingLevel === "function" ? harness.getThinkingLevel() : undefined,
331
+ undefined,
332
+ undefined,
333
+ event.context || BACKGROUND_CONTEXT,
325
334
  );
326
335
  if (compacted.ok === false) {
327
336
  hookDecision = { kind: "failed", error: compacted.error };
328
337
  return { cancel: true };
329
338
  }
330
- const tokensBefore = estimateBuiltContextTokens(event.branchEntries);
331
- const tokensAfter = previewCompactedContext(event.branchEntries, compacted.value);
339
+ const tokensBefore = await estimateBuiltContextTokens(event.branchEntries);
340
+ const tokensAfter = await previewCompactedContext(event.branchEntries, compacted.value);
332
341
  const savings = tokensBefore === null || tokensAfter === null ? null : tokensBefore - tokensAfter;
342
+ const firstRetainedMessage = prepared.value.retainedTail[0];
343
+ const firstKeptEntryId = firstRetainedMessage
344
+ ? event.branchEntries.find((entry) => entry?.type === "message" && entry.message === firstRetainedMessage)?.id || null
345
+ : null;
333
346
  if (savings === null || savings <= 0) {
334
347
  hookDecision = { kind: "not_reducible", tokensBefore, tokensAfter, savings };
335
348
  return { cancel: true };
@@ -338,7 +351,7 @@ export async function tryCompact(harness, {
338
351
  hookDecision = { kind: "insufficient_savings", tokensBefore, tokensAfter, savings };
339
352
  return { cancel: true };
340
353
  }
341
- hookDecision = { kind: "accepted", tokensBefore, tokensAfter, savings };
354
+ hookDecision = { kind: "accepted", tokensBefore, tokensAfter, savings, firstKeptEntryId };
342
355
  return { compaction: compacted.value };
343
356
  } catch (error) {
344
357
  hookDecision = { kind: "failed", error };
@@ -379,7 +392,10 @@ export async function tryCompact(harness, {
379
392
  model: model || null,
380
393
  tokens_before: tokensBefore,
381
394
  summary: result?.summary || "",
382
- first_kept_entry_id: result?.firstKeptEntryId || null,
395
+ // Pi 0.85 returns retained messages rather than their entry ids. The
396
+ // preparation still retains object identity, so preserve this legacy
397
+ // diagnostic when its first retained message came from a real entry.
398
+ first_kept_entry_id: hookDecision?.firstKeptEntryId || null,
383
399
  status: "succeeded",
384
400
  created_at: Date.now(),
385
401
  });
@@ -446,12 +462,15 @@ export async function tryCompact(harness, {
446
462
  : err;
447
463
  const message = effectiveError?.message || String(effectiveError);
448
464
  const code = effectiveError?.code;
449
- const nothingToCompact = code === "compaction" && /nothing to compact/i.test(message);
465
+ const tag = effectiveError?._tag;
466
+ const nothingToCompact = tag === "NothingToCompact"
467
+ || (code === "compaction" && /nothing to compact/i.test(message));
468
+ const busy = tag === "LaneBusy" || code === "busy";
450
469
  const warningKind = nothingToCompact
451
470
  ? "context_compaction_nothing_to_compact"
452
471
  : code === "auth"
453
472
  ? "context_compaction_auth_failed"
454
- : code === "busy"
473
+ : busy
455
474
  ? "context_compaction_busy"
456
475
  : "context_compaction_failed";
457
476
  runtimeWarnings?.push({ warning_kind: warningKind, source: "pi", trigger, message });
@@ -464,7 +483,7 @@ export async function tryCompact(harness, {
464
483
  ? "nothing_to_compact"
465
484
  : code === "auth"
466
485
  ? "authentication"
467
- : code === "busy"
486
+ : busy
468
487
  ? "busy"
469
488
  : code === "aborted"
470
489
  ? "cancelled"
@@ -474,7 +493,7 @@ export async function tryCompact(harness, {
474
493
  : {
475
494
  message: code === "auth"
476
495
  ? "Compaction authentication failed."
477
- : code === "busy"
496
+ : busy
478
497
  ? "Context was busy and could not be compacted."
479
498
  : code === "aborted"
480
499
  ? "Compaction was cancelled."
@@ -484,6 +503,15 @@ export async function tryCompact(harness, {
484
503
  return { applied: false, tokensBefore: null, tokensAfter: null, reduced: null, nothingToCompact };
485
504
  } finally {
486
505
  removeHook?.();
506
+ if (typeof harness?.setCompactionSettings === "function") {
507
+ try {
508
+ await harness.setCompactionSettings({
509
+ enabled: false,
510
+ reserveTokens: 16_384,
511
+ keepRecentTokens: 20_000,
512
+ });
513
+ } catch { /* best-effort */ }
514
+ }
487
515
  }
488
516
  }
489
517
 
@@ -0,0 +1,338 @@
1
+ // @ts-check
2
+ // Compatibility boundary between mono-agent's Pi-native bridge and the
3
+ // lane-based AgentHarness API introduced in pi-agent-core 0.85.
4
+
5
+ import {
6
+ AgentHarness,
7
+ BACKGROUND_CONTEXT,
8
+ createBranchSummaryMessage,
9
+ createCompactionSummaryMessage,
10
+ getOrThrow,
11
+ } from "@earendil-works/pi-agent-core";
12
+
13
+ export const PI_CONTEXT = BACKGROUND_CONTEXT;
14
+
15
+ function operationError(record, fallback) {
16
+ const error = new Error(record?.error?.message || fallback);
17
+ if (record?.error?.code) /** @type {any} */ (error).code = record.error.code;
18
+ return error;
19
+ }
20
+
21
+ function isContextMessage(message) {
22
+ return message?.role !== "assistant"
23
+ || !["error", "aborted", "deferred"].includes(message.stopReason);
24
+ }
25
+
26
+ /**
27
+ * Pi 0.85 no longer exports its session-context projector. Reproduce the
28
+ * public entry contract here so transcript accounting uses the same latest-
29
+ * compaction and failed-assistant filtering rules as the harness.
30
+ * @param {any[]} pathEntries
31
+ * @param {{includeFailed?: boolean}} [options]
32
+ */
33
+ export function buildPiSessionContext(pathEntries, { includeFailed = false } = {}) {
34
+ let start = 0;
35
+ for (let index = pathEntries.length - 1; index >= 0; index -= 1) {
36
+ if (pathEntries[index]?.type === "compaction") {
37
+ start = index;
38
+ break;
39
+ }
40
+ }
41
+ const entries = start > 0 ? pathEntries.slice(start) : pathEntries;
42
+ const messages = [];
43
+ for (const entry of entries) {
44
+ if (entry?.type === "message") {
45
+ if (includeFailed || isContextMessage(entry.message)) messages.push(entry.message);
46
+ } else if (entry?.type === "compaction") {
47
+ messages.push(createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp));
48
+ messages.push(...(includeFailed
49
+ ? (entry.retainedTail || [])
50
+ : (entry.retainedTail || []).filter(isContextMessage)));
51
+ } else if (entry?.type === "branch_summary" && entry.summary) {
52
+ messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
53
+ }
54
+ }
55
+ return messages;
56
+ }
57
+
58
+ /**
59
+ * Give the surrounding bridge the small session surface it used before Pi
60
+ * moved branch operations onto AgentLane. The raw session remains available to
61
+ * AgentHarness.create(), while all transcript reads and writes use the attached
62
+ * main lane once the harness exists.
63
+ * @param {any} rawSession
64
+ */
65
+ export function createPiSessionAdapter(rawSession) {
66
+ let lane = null;
67
+ let harness = null;
68
+ let closePromise = null;
69
+
70
+ const requireLane = () => {
71
+ if (!lane) throw new Error("Pi session lane is not attached");
72
+ return lane;
73
+ };
74
+
75
+ return {
76
+ rawSession,
77
+ get metadata() { return rawSession.metadata; },
78
+ attach(nextHarness, nextLane) {
79
+ harness = nextHarness;
80
+ lane = nextLane;
81
+ },
82
+ async buildContext() {
83
+ const entries = await requireLane().findEntries({ order: "oldestFirst" }, PI_CONTEXT);
84
+ // This bridge-facing transcript includes terminal error/abort messages so
85
+ // result classification can observe them. Pi filters those only when it
86
+ // constructs the next provider request.
87
+ return { messages: buildPiSessionContext(entries, { includeFailed: true }) };
88
+ },
89
+ getEntries() {
90
+ return requireLane().findEntries({ order: "oldestFirst" }, PI_CONTEXT);
91
+ },
92
+ getLeafId() {
93
+ return requireLane().getTipId(PI_CONTEXT);
94
+ },
95
+ appendMessage(message) {
96
+ return requireLane().appendMessage(message, PI_CONTEXT);
97
+ },
98
+ async moveTo(targetId) {
99
+ const result = getOrThrow(await requireLane().navigateTree(
100
+ targetId,
101
+ { summarize: false },
102
+ PI_CONTEXT,
103
+ ));
104
+ const record = result.navigation;
105
+ if (record.status === "failed") throw operationError(record, "Pi session navigation failed");
106
+ if (record.status !== "completed") throw operationError(record, "Pi session navigation was not completed");
107
+ return record.tipId;
108
+ },
109
+ getMetadata() {
110
+ return Promise.resolve(rawSession.metadata);
111
+ },
112
+ close() {
113
+ if (!closePromise) {
114
+ closePromise = (harness
115
+ ? harness.close(PI_CONTEXT)
116
+ : rawSession.close(PI_CONTEXT));
117
+ }
118
+ return closePromise;
119
+ },
120
+ };
121
+ }
122
+
123
+ function adaptTool(tool) {
124
+ return {
125
+ ...tool,
126
+ async execute(toolCallId, params, onUpdate, _toolContext, _invocation, context) {
127
+ return tool.execute(toolCallId, params, context.abortSignal, onUpdate);
128
+ },
129
+ };
130
+ }
131
+
132
+ const FORWARDED_EVENT_TYPES = [
133
+ "run_start",
134
+ "run_resume",
135
+ "run_suspend",
136
+ "operation_abort",
137
+ "run_end",
138
+ "fault",
139
+ "handler_error",
140
+ "turn_start",
141
+ "turn_end",
142
+ "retry_scheduled",
143
+ "retry_start",
144
+ "retry_end",
145
+ "message_start",
146
+ "message_update",
147
+ "message_end",
148
+ "tool_start",
149
+ "tool_update",
150
+ "tool_end",
151
+ "entry_added",
152
+ "queue_update",
153
+ "value_update",
154
+ "config_update",
155
+ "compaction_start",
156
+ "compaction_end",
157
+ "navigation_start",
158
+ "navigation_end",
159
+ "lane_created",
160
+ "usage",
161
+ ];
162
+
163
+ function legacyEvent(event) {
164
+ // appendMessage() emits runless lifecycle events for transcript seeding.
165
+ // They are persistence notifications, not output from the active provider
166
+ // run, and forwarding them would manufacture assistant boundaries/usage for
167
+ // prior history. Real run-owned message events always carry runId in Pi 0.85.
168
+ if ((event.type === "message_start" || event.type === "message_end") && !event.runId) {
169
+ return null;
170
+ }
171
+ if (event.type === "message_update") {
172
+ return { ...event, assistantMessageEvent: event.event };
173
+ }
174
+ if (event.type === "tool_start") return { ...event, type: "tool_execution_start" };
175
+ if (event.type === "tool_update") return { ...event, type: "tool_execution_update" };
176
+ if (event.type === "tool_end") return { ...event, type: "tool_execution_end" };
177
+ return event;
178
+ }
179
+
180
+ /**
181
+ * Attach Pi 0.85's harness to one session and expose the intentionally small
182
+ * surface consumed by mono-agent's turn runner.
183
+ * @param {any} session
184
+ * @param {any} options
185
+ */
186
+ export async function createPiHarnessAdapter(session, options) {
187
+ const originalTools = Array.isArray(options.tools) ? options.tools : [];
188
+ const adaptedTools = originalTools.map(adaptTool);
189
+ const activeToolNames = originalTools.map((tool) => tool.name);
190
+ // Pi 0.85 removed mixed per-tool scheduling from AgentHarness: its runner
191
+ // consults only this global setting. Preserve the safety contract by
192
+ // serializing the batch whenever any offered tool is stateful, mutating, or
193
+ // MCP-backed. Read-only-only tool sets can still overlap in safe-parallel.
194
+ const toolExecution = originalTools.some((tool) => tool?.executionMode === "sequential")
195
+ ? "sequential"
196
+ : "parallel";
197
+
198
+ /** @type {any} */
199
+ let created;
200
+ /** @type {any} */
201
+ let rawHarness;
202
+ /** @type {any} */
203
+ let lane;
204
+ try {
205
+ created = await AgentHarness.create({
206
+ ...options,
207
+ session: session.rawSession,
208
+ tools: adaptedTools,
209
+ activeToolNames,
210
+ toolExecution,
211
+ // mono-agent owns proactive/reactive compaction policy. The permanent hook
212
+ // below also declines Pi's overflow recovery so one bridge never runs two
213
+ // competing policies.
214
+ compaction: { enabled: false, reserveTokens: 16_384, keepRecentTokens: 20_000 },
215
+ }, PI_CONTEXT);
216
+ rawHarness = created.harness;
217
+ // Attach the harness before lane/configuration awaits so any later failure
218
+ // can close the partially constructed handle instead of wedging the repo.
219
+ session.attach(rawHarness, null);
220
+ lane = await rawHarness.lane("main", PI_CONTEXT);
221
+ session.attach(rawHarness, lane);
222
+
223
+ // A restored lane carries its prior configuration, so explicitly bind it to
224
+ // this run's model, effort, and available tools. Legacy v3 transcripts import
225
+ // with an empty active-tool list and are upgraded atomically by Pi on write.
226
+ await rawHarness.setTools(adaptedTools, PI_CONTEXT);
227
+ await lane.setModel({ provider: options.model.provider, modelId: options.model.id }, PI_CONTEXT);
228
+ await lane.setThinkingLevel(options.thinkingLevel ?? "off", PI_CONTEXT);
229
+ await lane.setActiveTools(activeToolNames, PI_CONTEXT);
230
+
231
+ rawHarness.hooks.on("before_compaction", (event) => (
232
+ event.reason === "manual" ? undefined : { decline: true }
233
+ ), { id: "mono-agent-compaction-owner" });
234
+ } catch (error) {
235
+ try { await session.close(); } catch { /* preserve the construction error */ }
236
+ throw error;
237
+ }
238
+
239
+ let closed = false;
240
+ let currentModel = options.model;
241
+ let currentThinkingLevel = options.thinkingLevel ?? "off";
242
+ let currentActiveToolNames = [...activeToolNames];
243
+ const manuallyAppendedEntryIds = new Set();
244
+
245
+ const adapter = {
246
+ models: options.models,
247
+ getModel: () => currentModel,
248
+ getThinkingLevel: () => currentThinkingLevel,
249
+ getActiveTools: () => originalTools.filter((tool) => currentActiveToolNames.includes(tool.name)),
250
+ async setActiveTools(names) {
251
+ await lane.setActiveTools(names, PI_CONTEXT);
252
+ currentActiveToolNames = [...names];
253
+ },
254
+ async setCompactionSettings(settings) {
255
+ await rawHarness.setCompactionSettings(settings, PI_CONTEXT);
256
+ },
257
+ async appendMessage(message) {
258
+ const entryId = await lane.appendMessage(message, PI_CONTEXT);
259
+ manuallyAppendedEntryIds.add(entryId);
260
+ return entryId;
261
+ },
262
+ async prompt(text, promptOptions) {
263
+ return getOrThrow(await lane.prompt(text, promptOptions?.images, PI_CONTEXT));
264
+ },
265
+ async steer(message) {
266
+ return getOrThrow(await lane.steer(message, undefined, PI_CONTEXT));
267
+ },
268
+ async abort() {
269
+ const result = await lane.abort(PI_CONTEXT);
270
+ // Aborting an already-idle lane is a benign race with prompt settlement.
271
+ if (!result.ok) {
272
+ const error = /** @type {{error: any}} */ (result).error;
273
+ if (error?._tag !== "NoActiveOperation") throw error;
274
+ }
275
+ },
276
+ waitForIdle() {
277
+ return lane.waitForIdle(PI_CONTEXT);
278
+ },
279
+ async compact() {
280
+ const value = getOrThrow(await lane.compact(undefined, PI_CONTEXT));
281
+ const record = value.compaction;
282
+ if (record.status === "failed") throw operationError(record, "Pi compaction failed");
283
+ if (record.status !== "completed") throw operationError(record, "Pi compaction cancelled");
284
+ const entry = record.tipId
285
+ ? await session.rawSession.getEntry(record.tipId, PI_CONTEXT)
286
+ : undefined;
287
+ if (!entry || entry.type !== "compaction") {
288
+ throw new Error("Pi compaction completed without a compaction entry");
289
+ }
290
+ return entry;
291
+ },
292
+ on(type, handler) {
293
+ if (type === "tool_result") {
294
+ return rawHarness.hooks.on("after_tool", (event) => handler(event));
295
+ }
296
+ if (type === "session_before_compact") {
297
+ return rawHarness.hooks.on("before_compaction", async (event, context) => {
298
+ const branchEntries = await lane.findEntries({ order: "oldestFirst" }, context);
299
+ const result = await handler({
300
+ ...event,
301
+ branchEntries,
302
+ signal: context.abortSignal,
303
+ context,
304
+ });
305
+ if (result?.cancel) return { decline: true };
306
+ return result?.compaction === undefined ? undefined : { compaction: result.compaction };
307
+ });
308
+ }
309
+ throw new Error(`Unsupported Pi harness hook: ${String(type)}`);
310
+ },
311
+ subscribe(listener) {
312
+ const removes = FORWARDED_EVENT_TYPES.map((type) => rawHarness.events.on(
313
+ /** @type {any} */ (type),
314
+ (event) => {
315
+ if (event.type === "message_end" && manuallyAppendedEntryIds.has(event.entryId)) return;
316
+ const converted = legacyEvent(event);
317
+ if (converted) listener(converted);
318
+ },
319
+ ));
320
+ return () => removes.forEach((remove) => remove());
321
+ },
322
+ async abortOpenOperations() {
323
+ if (!created.open.some((operation) => operation.lane === "main")) return;
324
+ // mono-agent tools predate Pi's invocation memo/checkpoint API, so replaying
325
+ // an interrupted durable operation could repeat an external side effect.
326
+ // Fail closed by settling it as aborted before admitting a new prompt.
327
+ getOrThrow(await lane.abort(PI_CONTEXT));
328
+ await lane.waitForIdle(PI_CONTEXT);
329
+ },
330
+ async close() {
331
+ if (closed) return;
332
+ closed = true;
333
+ await session.close();
334
+ },
335
+ };
336
+
337
+ return adapter;
338
+ }
@@ -118,14 +118,14 @@ export function emitUsageCostEvents({
118
118
  externalAbort,
119
119
  }) {
120
120
  if (usage.cacheRead > 0) {
121
- onEvent({ type: "cache_hit", sdk: resolved.sdk, model: reference, tokens: usage.cacheRead, source: "prompt_cache" });
121
+ onEvent({ type: "cache_hit", sdk: "pi", model: reference, tokens: usage.cacheRead, source: "prompt_cache" });
122
122
  }
123
123
  if (usage.cacheWrite > 0) {
124
- onEvent({ type: "cache_miss", sdk: resolved.sdk, model: reference, tokens: usage.cacheWrite, source: "prompt_cache" });
124
+ onEvent({ type: "cache_miss", sdk: "pi", model: reference, tokens: usage.cacheWrite, source: "prompt_cache" });
125
125
  }
126
126
  onEvent({
127
127
  type: "cost_accumulated",
128
- sdk: resolved.sdk,
128
+ sdk: "pi",
129
129
  model: reference,
130
130
  cumulativeUsd: Number(usage.cost) || Number(estimatedCost) || 0,
131
131
  tokens: {
@@ -137,7 +137,7 @@ export function emitUsageCostEvents({
137
137
  });
138
138
  onEvent({
139
139
  type: "provider_request_completed",
140
- sdk: resolved.sdk,
140
+ sdk: "pi",
141
141
  model: reference,
142
142
  runtime: "pi",
143
143
  timestamp: Date.now(),
@@ -167,9 +167,9 @@ export function abortedResult({ resolved, options, events, runtimeWarnings, star
167
167
  usage: {},
168
168
  durationMs: Date.now() - start,
169
169
  numTurns: 0,
170
- model: resolved?.reference || resolved?.model || null,
170
+ model: resolved?.reference || (resolved?.provider && resolved?.model ? `${resolved.provider}:${resolved.model}` : null),
171
171
  effort: options.effort || null,
172
- sdk: resolved?.sdk || "pi",
172
+ sdk: "pi",
173
173
  cancelled: true,
174
174
  error: null,
175
175
  failureKind: null,
@@ -226,9 +226,9 @@ export function buildSuccessResult(params) {
226
226
  },
227
227
  durationMs: Date.now() - start,
228
228
  numTurns: turnCount || runAssistantCount,
229
- model: resolved.reference || `pi:${resolved.provider}:${resolved.model}`,
229
+ model: resolved.reference || `${resolved.provider}:${resolved.model}`,
230
230
  effort: options.effort || null,
231
- sdk: resolved.sdk,
231
+ sdk: "pi",
232
232
  cancelled: externalAbort,
233
233
  error: errorMessage,
234
234
  errorDetails,
@@ -273,9 +273,9 @@ export function buildErrorResult(params) {
273
273
  usage: {},
274
274
  durationMs: Date.now() - start,
275
275
  numTurns: turnCount,
276
- model: resolved?.reference || resolved?.model || null,
276
+ model: resolved?.reference || (resolved?.provider && resolved?.model ? `${resolved.provider}:${resolved.model}` : null),
277
277
  effort: options.effort || null,
278
- sdk: resolved?.sdk || "pi",
278
+ sdk: "pi",
279
279
  cancelled: externalAbort,
280
280
  error: externalAbort ? null : errorMessage,
281
281
  errorDetails: externalAbort ? null : {