@skydiveai/pi-extensions 0.1.256-beta.1 → 0.1.256

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 (2) hide show
  1. package/dist/index.mjs +1355 -100
  2. package/package.json +5 -2
package/dist/index.mjs CHANGED
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
2
2
  import { DefaultExecutionEventBusManager, DefaultRequestHandler, InMemoryTaskStore } from "@a2a-js/sdk/server";
3
3
  import { UserBuilder, restHandler } from "@a2a-js/sdk/server/express";
4
4
  import { buildAgentCard, chainMiddleware, composeHandlers, createAgentExecutor, createProtocolHandlers, getCurrentTraceparent, logger, mountAt, requestHeaders, requestUrl, webHandlerToMiddleware } from "@skydiveai/pi-server";
5
- import { mkdir, open, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
6
- import { basename, dirname, join, relative, resolve } from "node:path";
5
+ import { mkdir, mkdtemp, open, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
6
+ import path, { basename, dirname, join, relative, resolve } from "node:path";
7
7
  import { pathToFileURL } from "node:url";
8
8
  import { SettingsManager, createBashToolDefinition, createEditToolDefinition, createLocalBashOperations, getAgentDir } from "@earendil-works/pi-coding-agent";
9
9
  import { z } from "zod";
@@ -24,12 +24,16 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
24
24
  import { Resource } from "@opentelemetry/resources";
25
25
  import { BatchSpanProcessor, NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
26
26
  import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
27
- import { execFile } from "node:child_process";
28
- import { availableParallelism } from "node:os";
27
+ import { execFile, spawn } from "node:child_process";
28
+ import os, { availableParallelism } from "node:os";
29
29
  import { promisify } from "node:util";
30
30
  import { parse } from "yaml";
31
31
  import { getModels } from "@earendil-works/pi-ai";
32
+ import { clampThinkingLevel, createAssistantMessageEventStream } from "@earendil-works/pi-ai-runtime";
33
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
32
34
  import { createHash, randomUUID } from "node:crypto";
35
+ import { createSdkMcpServer, query, tool } from "@anthropic-ai/claude-agent-sdk";
36
+ import { adjustMaxTokensForThinking, clampMaxTokensToContext } from "@earendil-works/pi-ai-runtime/api/simple-options";
33
37
  //#region src/platform-env-middleware.ts
34
38
  const ENVD_TOKEN_HEADER = "x-e2b-envd-token";
35
39
  const ENVD_URL = "http://localhost:49983/envs";
@@ -260,7 +264,7 @@ function createHealthHandler({ metadata }) {
260
264
  const CAPABILITY_SOUL_NUDGE = "New capability gained — once the current task is done, if this changes what you can do for the user, record it in `soul.md` so it carries into future conversations rather than being rediscovered from scratch (then commit and push).";
261
265
  //#endregion
262
266
  //#region src/extensions/tool-call-summary.ts
263
- const log$17 = logger.child({ module: "tool-call-summary-extension" });
267
+ const log$19 = logger.child({ module: "tool-call-summary-extension" });
264
268
  /**
265
269
  * The injected parameter name. Deliberately markdown-safe: the previous wire
266
270
  * name (`__skydive_summary__`, now `TOOL_CALL_SUMMARY_FIELD_LEGACY`) carried
@@ -494,7 +498,7 @@ function withSummaryStrippedBeforeValidation(tool) {
494
498
  try {
495
499
  stripped = withoutInjectedSummary(args);
496
500
  } catch (err) {
497
- log$17.error({
501
+ log$19.error({
498
502
  err,
499
503
  event: "tool_call_summary_prepare_strip_failed",
500
504
  toolName: tool.name
@@ -525,7 +529,7 @@ function stripInjectedSummary(pi, toolName, input) {
525
529
  if (toolDeclaresSummaryParam(pi, toolName)) return;
526
530
  for (const field of TOOL_CALL_SUMMARY_FIELDS) delete input[field];
527
531
  } catch (err) {
528
- log$17.error({
532
+ log$19.error({
529
533
  err,
530
534
  event: "tool_call_summary_strip_failed",
531
535
  toolName
@@ -541,7 +545,7 @@ function buildInjectedPayload(pi, payload) {
541
545
  try {
542
546
  return injectToolCallSummary(payload, getStrictToolNames(pi));
543
547
  } catch (err) {
544
- log$17.error({
548
+ log$19.error({
545
549
  err,
546
550
  event: "tool_call_summary_injection_failed"
547
551
  }, "tool_call_summary injection failed; passing payload through unchanged");
@@ -560,7 +564,7 @@ const toolCallSummaryExtension = (pi) => {
560
564
  pi.registerTool(withSummaryStrippedBeforeValidation(createEditToolDefinition(ctx.cwd)));
561
565
  editOverrideRegistered = true;
562
566
  } catch (err) {
563
- log$17.error({
567
+ log$19.error({
564
568
  err,
565
569
  event: "tool_call_summary_edit_override_failed"
566
570
  }, "failed to register summary-tolerant edit tool; built-in remains active");
@@ -591,7 +595,7 @@ const toolCallSummaryExtension = (pi) => {
591
595
  * or `ToolDefinition[]`. Files starting with `_` or `.` are skipped, so
592
596
  * `tools/_example.ts` documents the shape without registering.
593
597
  */
594
- const log$16 = logger.child({ module: "local-tools-extension" });
598
+ const log$18 = logger.child({ module: "local-tools-extension" });
595
599
  const TOOLS_DIRNAME = "tools";
596
600
  const fileState = /* @__PURE__ */ new Map();
597
601
  let pendingLocalToolsUpdate = null;
@@ -735,7 +739,7 @@ async function reconcileAndQueue({ pi, dir, reason }) {
735
739
  dir
736
740
  });
737
741
  if (reason !== "session_start" && summaryHasChanges$1(summary)) pendingLocalToolsUpdate = summary;
738
- log$16.info({
742
+ log$18.info({
739
743
  event: "local_tools_reconcile",
740
744
  reason,
741
745
  total_tools: summary.totalTools,
@@ -757,7 +761,7 @@ const localToolsExtension = (pi) => {
757
761
  reason: "session_start"
758
762
  });
759
763
  } catch (err) {
760
- log$16.error({
764
+ log$18.error({
761
765
  err,
762
766
  event: "local_tools_reconcile_failed"
763
767
  }, "local tools reconcile failed");
@@ -769,7 +773,7 @@ const localToolsExtension = (pi) => {
769
773
  try {
770
774
  current = await listToolFiles(dir);
771
775
  } catch (err) {
772
- log$16.warn({
776
+ log$18.warn({
773
777
  err,
774
778
  event: "local_tools_listing_failed"
775
779
  }, "tools/ listing failed");
@@ -791,7 +795,7 @@ const localToolsExtension = (pi) => {
791
795
  reason: "auto_reload"
792
796
  });
793
797
  } catch (err) {
794
- log$16.error({
798
+ log$18.error({
795
799
  err,
796
800
  event: "local_tools_auto_reload_failed"
797
801
  }, "auto-reload after tools/ change failed");
@@ -1329,6 +1333,135 @@ async function loadMcpConfig(path) {
1329
1333
  return { servers };
1330
1334
  }
1331
1335
  //#endregion
1336
+ //#region src/extensions/mcp/schema-refs.ts
1337
+ const STRIPPED_KEYWORDS = new Set([
1338
+ "$defs",
1339
+ "definitions",
1340
+ "$schema",
1341
+ "$id"
1342
+ ]);
1343
+ const NAME_MAP_KEYWORDS = new Set([
1344
+ "properties",
1345
+ "patternProperties",
1346
+ "dependentSchemas",
1347
+ "dependencies"
1348
+ ]);
1349
+ function isRecord(value) {
1350
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1351
+ }
1352
+ /** RFC 6901 token decoding; null when the percent-escape is malformed. */
1353
+ function decodePointerToken(token) {
1354
+ try {
1355
+ return decodeURIComponent(token).replace(/~1/g, "/").replace(/~0/g, "~");
1356
+ } catch {
1357
+ return null;
1358
+ }
1359
+ }
1360
+ function isLocalRef(ref) {
1361
+ return ref === "#" || ref.startsWith("#/");
1362
+ }
1363
+ /**
1364
+ * Resolve a local JSON pointer against the root schema. Steps through arrays
1365
+ * by index too: the MCP SDK emits refs like `#/properties/first/anyOf/0`
1366
+ * when a Zod schema is reused.
1367
+ */
1368
+ function resolveLocalRef({ root, ref }) {
1369
+ const tokens = ref === "#" ? [] : ref.slice(2).split("/");
1370
+ let node = root;
1371
+ for (const rawToken of tokens) {
1372
+ const token = decodePointerToken(rawToken);
1373
+ if (token === null) return { error: `malformed $ref '${ref}'` };
1374
+ if (Array.isArray(node)) {
1375
+ const index = /^(0|[1-9]\d*)$/.test(token) ? Number(token) : -1;
1376
+ node = index >= 0 ? node[index] : void 0;
1377
+ } else if (isRecord(node)) node = node[token];
1378
+ else node = void 0;
1379
+ if (node === void 0) return { error: `unresolvable $ref '${ref}'` };
1380
+ }
1381
+ return isRecord(node) ? { node } : { error: `$ref '${ref}' does not point at a schema object` };
1382
+ }
1383
+ function toProviderSafeSchema(inputSchema) {
1384
+ if (!isRecord(inputSchema)) return {
1385
+ ok: false,
1386
+ reason: "inputSchema is not an object"
1387
+ };
1388
+ const root = inputSchema;
1389
+ let inlinedRefs = 0;
1390
+ let unsupported = null;
1391
+ const visitValue = ({ value, stack, inNameMap }) => {
1392
+ if (Array.isArray(value)) return value.map((item) => visitValue({
1393
+ value: item,
1394
+ stack,
1395
+ inNameMap: false
1396
+ }));
1397
+ if (!isRecord(value)) return value;
1398
+ return visitSchema({
1399
+ node: value,
1400
+ stack,
1401
+ inNameMap
1402
+ });
1403
+ };
1404
+ const visitSchema = ({ node, stack, inNameMap }) => {
1405
+ const ref = node.$ref;
1406
+ if (!inNameMap && typeof ref === "string") {
1407
+ if (!isLocalRef(ref)) {
1408
+ unsupported ??= `non-local $ref '${ref}'`;
1409
+ return node;
1410
+ }
1411
+ if (stack.includes(ref)) {
1412
+ unsupported ??= `cyclic $ref '${ref}'`;
1413
+ return node;
1414
+ }
1415
+ const resolved = resolveLocalRef({
1416
+ root,
1417
+ ref
1418
+ });
1419
+ if ("error" in resolved) {
1420
+ unsupported ??= resolved.error;
1421
+ return node;
1422
+ }
1423
+ inlinedRefs++;
1424
+ const { $ref: _ref, ...siblings } = node;
1425
+ return {
1426
+ ...visitSchema({
1427
+ node: resolved.node,
1428
+ stack: [...stack, ref],
1429
+ inNameMap: false
1430
+ }),
1431
+ ...visitSchema({
1432
+ node: siblings,
1433
+ stack,
1434
+ inNameMap: false
1435
+ })
1436
+ };
1437
+ }
1438
+ const out = {};
1439
+ for (const [key, value] of Object.entries(node)) {
1440
+ if (!inNameMap && STRIPPED_KEYWORDS.has(key)) continue;
1441
+ out[key] = visitValue({
1442
+ value,
1443
+ stack,
1444
+ inNameMap: !inNameMap && NAME_MAP_KEYWORDS.has(key)
1445
+ });
1446
+ }
1447
+ return out;
1448
+ };
1449
+ const schema = visitSchema({
1450
+ node: root,
1451
+ stack: [],
1452
+ inNameMap: false
1453
+ });
1454
+ if (unsupported !== null) return {
1455
+ ok: false,
1456
+ reason: unsupported
1457
+ };
1458
+ return {
1459
+ ok: true,
1460
+ schema,
1461
+ inlinedRefs
1462
+ };
1463
+ }
1464
+ //#endregion
1332
1465
  //#region src/extensions/mcp/index.ts
1333
1466
  /**
1334
1467
  * MCP adapter as a pi extension.
@@ -1356,7 +1489,7 @@ async function loadMcpConfig(path) {
1356
1489
  * Clients are keyed by JSON-stringified config and reused across
1357
1490
  * reloads — only changed configs reconnect.
1358
1491
  */
1359
- const log$15 = logger.child({ module: "mcp-extension" });
1492
+ const log$17 = logger.child({ module: "mcp-extension" });
1360
1493
  async function closeConnected(connected) {
1361
1494
  try {
1362
1495
  await connected.client.close();
@@ -1493,6 +1626,45 @@ function buildToolPromptSnippet({ description, inputSchema, serverId }) {
1493
1626
  const enriched = constraintHint.length > 0 ? `${description}${description.length > 0 ? "\n\n" : ""}${constraintHint}` : description;
1494
1627
  return enriched.length > 0 ? enriched : `MCP tool from server "${serverId}".`;
1495
1628
  }
1629
+ /**
1630
+ * Rewrite each tool's inputSchema into a self-contained one (local `$ref`s
1631
+ * inlined, `$defs` stripped — see schema-refs.ts) and set aside the tools
1632
+ * that cannot be made provider-safe. Skipping is deliberate: a strict
1633
+ * upstream rejects the WHOLE request over one bad tool schema, so
1634
+ * registering it would cost the agent every turn, not just that tool.
1635
+ */
1636
+ function providerSafeTools({ serverId, tools }) {
1637
+ const safe = [];
1638
+ const skipped = [];
1639
+ for (const tool of tools) {
1640
+ const result = toProviderSafeSchema(tool.inputSchema);
1641
+ if (!result.ok) {
1642
+ log$17.warn({
1643
+ event: "mcp_tool_schema_unsupported",
1644
+ server_id: serverId,
1645
+ tool_name: tool.name,
1646
+ reason: result.reason
1647
+ }, "skipping MCP tool whose input schema cannot be made provider-safe");
1648
+ skipped.push({
1649
+ name: tool.name,
1650
+ reason: result.reason
1651
+ });
1652
+ continue;
1653
+ }
1654
+ const inputSchema = {
1655
+ ...result.schema,
1656
+ type: "object"
1657
+ };
1658
+ safe.push({
1659
+ ...tool,
1660
+ inputSchema
1661
+ });
1662
+ }
1663
+ return {
1664
+ tools: safe,
1665
+ skipped
1666
+ };
1667
+ }
1496
1668
  var McpExtension = class {
1497
1669
  mcpClients = /* @__PURE__ */ new Map();
1498
1670
  registeredMcpToolNames = /* @__PURE__ */ new Set();
@@ -1624,33 +1796,37 @@ var McpExtension = class {
1624
1796
  else if (outcome.change === "refreshed") summary.refreshed.push(id);
1625
1797
  }
1626
1798
  const alloc = outcomes.filter((o) => Boolean(o.tools)).map((o) => {
1627
- const advertised = o.tools.list.length;
1799
+ const { tools: list, skipped } = providerSafeTools({
1800
+ serverId: o.id,
1801
+ tools: o.tools.list
1802
+ });
1628
1803
  return {
1629
1804
  id: o.id,
1630
1805
  client: o.tools.client,
1631
- list: o.tools.list,
1632
- advertised,
1633
- room: Math.min(advertised, limits.maxToolsPerServer),
1806
+ list,
1807
+ advertised: o.tools.list.length,
1808
+ skipped,
1809
+ room: Math.min(list.length, limits.maxToolsPerServer),
1634
1810
  registered: 0,
1635
1811
  serverTokens: 0,
1636
1812
  droppedReason: null
1637
1813
  };
1638
1814
  });
1639
- const maxAdvertised = alloc.reduce((m, s) => Math.max(m, s.advertised), 0);
1815
+ const maxAdvertised = alloc.reduce((m, s) => Math.max(m, s.list.length), 0);
1640
1816
  outer: for (let i = 0; i < maxAdvertised; i++) for (const s of alloc) {
1641
1817
  if (summary.totalTools >= limits.maxTotalTools) {
1642
- for (const t of alloc) if (t.registered < t.advertised && t.droppedReason === null) t.droppedReason = "total";
1818
+ for (const t of alloc) if (t.registered < t.list.length && t.droppedReason === null) t.droppedReason = "total";
1643
1819
  break outer;
1644
1820
  }
1645
1821
  if (i >= s.room) {
1646
- if (s.registered < s.advertised && s.droppedReason === null) s.droppedReason = "per_server";
1822
+ if (s.registered < s.list.length && s.droppedReason === null) s.droppedReason = "per_server";
1647
1823
  continue;
1648
1824
  }
1649
1825
  const tool = s.list[i];
1650
1826
  if (tool === void 0) continue;
1651
1827
  const cost = estimateToolTokens(tool);
1652
1828
  if (summary.tokensUsed + cost > tokenBudget && summary.totalTools > 0) {
1653
- for (const t of alloc) if (t.registered < t.advertised && t.droppedReason === null) t.droppedReason = "tokens";
1829
+ for (const t of alloc) if (t.registered < t.list.length && t.droppedReason === null) t.droppedReason = "tokens";
1654
1830
  break outer;
1655
1831
  }
1656
1832
  this.registerMcpTool({
@@ -1665,14 +1841,15 @@ var McpExtension = class {
1665
1841
  summary.tokensUsed += cost;
1666
1842
  }
1667
1843
  for (const s of alloc) {
1668
- const dropped = s.advertised - s.registered;
1844
+ const dropped = s.list.length - s.registered;
1669
1845
  const droppedReason = dropped > 0 ? s.droppedReason : null;
1670
1846
  if (dropped > 0) summary.droppedTools += dropped;
1671
1847
  summary.toolCounts[s.id] = {
1672
1848
  advertised: s.advertised,
1673
1849
  registered: s.registered,
1674
1850
  tokens: s.serverTokens,
1675
- droppedReason
1851
+ droppedReason,
1852
+ skipped: s.skipped
1676
1853
  };
1677
1854
  }
1678
1855
  return summary;
@@ -1928,7 +2105,7 @@ var McpExtension = class {
1928
2105
  });
1929
2106
  this.lastConfigMtimeMs = await readConfigMtimeMs(configPath);
1930
2107
  if (reason !== "session_start" && summaryHasChanges(summary)) this.pendingMcpUpdate = summary;
1931
- log$15.info({
2108
+ log$17.info({
1932
2109
  event: "mcp_reconcile",
1933
2110
  reason,
1934
2111
  total_tools: summary.totalTools,
@@ -1955,7 +2132,7 @@ var McpExtension = class {
1955
2132
  contextWindow: ctx.model?.contextWindow
1956
2133
  });
1957
2134
  } catch (err) {
1958
- log$15.error({
2135
+ log$17.error({
1959
2136
  err,
1960
2137
  event: "mcp_reconcile_failed"
1961
2138
  }, "MCP reconcile failed");
@@ -1969,7 +2146,7 @@ var McpExtension = class {
1969
2146
  for (const legacy of renamed) {
1970
2147
  if (this.loggedLegacyToolNames.has(legacy)) continue;
1971
2148
  this.loggedLegacyToolNames.add(legacy);
1972
- log$15.info({
2149
+ log$17.info({
1973
2150
  event: "mcp_tool_legacy_name",
1974
2151
  legacy_name: legacy,
1975
2152
  tool_name: this.legacyToolNames.get(legacy)
@@ -1983,7 +2160,7 @@ var McpExtension = class {
1983
2160
  try {
1984
2161
  mtime = await readConfigMtimeMs(configPath);
1985
2162
  } catch (err) {
1986
- log$15.warn({
2163
+ log$17.warn({
1987
2164
  err,
1988
2165
  event: "mcp_mtime_check_failed"
1989
2166
  }, "mtime check on mcp.config.json failed");
@@ -1998,7 +2175,7 @@ var McpExtension = class {
1998
2175
  contextWindow: ctx.model?.contextWindow
1999
2176
  });
2000
2177
  } catch (err) {
2001
- log$15.error({
2178
+ log$17.error({
2002
2179
  err,
2003
2180
  event: "mcp_auto_reload_failed"
2004
2181
  }, "auto-reload after mcp.config.json change failed");
@@ -2113,10 +2290,24 @@ function truncationLines(summary) {
2113
2290
  }
2114
2291
  return lines;
2115
2292
  }
2293
+ /**
2294
+ * Lines naming tools that were left out because their input schema cannot be
2295
+ * sent to the model. Kept apart from `truncationLines`: this is not a budget
2296
+ * problem the agent can fix by pruning, the server has to publish a
2297
+ * self-contained schema.
2298
+ */
2299
+ function skippedSchemaLines(summary) {
2300
+ const entries = Object.entries(summary.toolCounts).filter(([, count]) => count.skipped.length > 0);
2301
+ if (entries.length === 0) return [];
2302
+ const lines = [` WARNING: ${entries.reduce((n, [, count]) => n + count.skipped.length, 0)} MCP tool(s) were NOT registered because their input schema cannot be sent to the model (unsupported $ref). The server must publish a self-contained schema; there is nothing to fix in mcp.config.json.`];
2303
+ for (const [id, count] of entries) for (const { name, reason } of count.skipped) lines.push(` - ${id}/${name}: ${reason}`);
2304
+ return lines;
2305
+ }
2116
2306
  function summaryText(summary) {
2117
2307
  const lines = [];
2118
2308
  lines.push(`MCP reconcile complete: ${summary.totalTools} tool(s) live.`);
2119
2309
  lines.push(...truncationLines(summary));
2310
+ lines.push(...skippedSchemaLines(summary));
2120
2311
  if (summary.added.length > 0) lines.push(` Added: ${summary.added.join(", ")}`);
2121
2312
  if (summary.refreshed.length > 0) lines.push(` Refreshed: ${summary.refreshed.join(", ")}`);
2122
2313
  if (summary.removed.length > 0) lines.push(` Removed: ${summary.removed.join(", ")}`);
@@ -2160,6 +2351,11 @@ function formatMcpUpdateMessage(summary) {
2160
2351
  lines.push("");
2161
2352
  lines.push(...truncationLines(summary));
2162
2353
  }
2354
+ const skippedLines = skippedSchemaLines(summary);
2355
+ if (skippedLines.length > 0) {
2356
+ lines.push("");
2357
+ lines.push(...skippedLines);
2358
+ }
2163
2359
  const pending = pendingAuthEntries(summary);
2164
2360
  if (pending.length > 0) {
2165
2361
  lines.push("");
@@ -2335,7 +2531,7 @@ const HEARTBEAT_THROTTLE_MS = 6e4;
2335
2531
  const TOOL_HEARTBEAT_INTERVAL_MS = 5e3;
2336
2532
  const MAX_TOOL_HEARTBEATS = 1440 * 60 * 1e3 / TOOL_HEARTBEAT_INTERVAL_MS;
2337
2533
  const DAEMON_URL = "http://localhost:38994";
2338
- const log$14 = logger.child({ module: "platform-ext" });
2534
+ const log$16 = logger.child({ module: "platform-ext" });
2339
2535
  function sandboxClient() {
2340
2536
  const apiUrl = apiBaseUrl();
2341
2537
  if (!apiUrl) return null;
@@ -2403,7 +2599,7 @@ async function fetchHarnessFlags() {
2403
2599
  try {
2404
2600
  const res = await client["feature-flags"].$get();
2405
2601
  if (!res.ok) {
2406
- log$14.debug({
2602
+ log$16.debug({
2407
2603
  status: res.status,
2408
2604
  event: "feature_flags_fetch_failed"
2409
2605
  }, "feature-flags fetch failed");
@@ -2415,7 +2611,7 @@ async function fetchHarnessFlags() {
2415
2611
  closingText: body.closingText ?? null
2416
2612
  };
2417
2613
  } catch (err) {
2418
- log$14.debug({
2614
+ log$16.debug({
2419
2615
  err,
2420
2616
  event: "feature_flags_fetch_error"
2421
2617
  }, "feature-flags request errored");
@@ -2426,7 +2622,7 @@ function postHeartbeat({ messageId }) {
2426
2622
  const client = sandboxClient();
2427
2623
  if (!client) return;
2428
2624
  client.heartbeat.$post({ json: { messageId } }).catch((err) => {
2429
- log$14.debug({
2625
+ log$16.debug({
2430
2626
  err,
2431
2627
  event: "heartbeat_failed"
2432
2628
  }, "heartbeat failed");
@@ -2438,7 +2634,7 @@ async function resolveConversationFromApi(messageId) {
2438
2634
  try {
2439
2635
  const res = await client["message-conversation"].$get({ query: { messageId } });
2440
2636
  if (!res.ok) {
2441
- log$14.warn({
2637
+ log$16.warn({
2442
2638
  status: res.status,
2443
2639
  messageId,
2444
2640
  event: "resolve_conversation_failed"
@@ -2447,7 +2643,7 @@ async function resolveConversationFromApi(messageId) {
2447
2643
  }
2448
2644
  return (await res.json()).conversationId ?? null;
2449
2645
  } catch (err) {
2450
- log$14.warn({
2646
+ log$16.warn({
2451
2647
  err,
2452
2648
  messageId,
2453
2649
  event: "resolve_conversation_error"
@@ -2472,13 +2668,13 @@ async function putBackgroundTaskJournalSpec({ messageId, spec }) {
2472
2668
  messageId,
2473
2669
  spec
2474
2670
  } });
2475
- if (!res.ok) log$14.warn({
2671
+ if (!res.ok) log$16.warn({
2476
2672
  status: res.status,
2477
2673
  taskId: spec.id,
2478
2674
  event: "bg_journal_put_failed"
2479
2675
  }, "bg-task journal PUT failed");
2480
2676
  } catch (err) {
2481
- log$14.warn({
2677
+ log$16.warn({
2482
2678
  err,
2483
2679
  taskId: spec.id,
2484
2680
  event: "bg_journal_put_failed"
@@ -2494,7 +2690,7 @@ async function deleteBackgroundTaskJournalSpec({ messageId, taskId }) {
2494
2690
  taskId
2495
2691
  } });
2496
2692
  } catch (err) {
2497
- log$14.debug({
2693
+ log$16.debug({
2498
2694
  err,
2499
2695
  taskId,
2500
2696
  event: "bg_journal_delete_failed"
@@ -2509,7 +2705,7 @@ async function listBackgroundTaskJournalSpecs({ messageId }) {
2509
2705
  if (!res.ok) return [];
2510
2706
  return (await res.json()).specs ?? [];
2511
2707
  } catch (err) {
2512
- log$14.debug({
2708
+ log$16.debug({
2513
2709
  err,
2514
2710
  event: "bg_journal_list_failed"
2515
2711
  }, "bg-task journal GET failed");
@@ -2523,7 +2719,7 @@ function postBackgroundTasksSnapshot({ messageId, tasks }) {
2523
2719
  messageId,
2524
2720
  tasks
2525
2721
  } }).catch((err) => {
2526
- log$14.debug({
2722
+ log$16.debug({
2527
2723
  err,
2528
2724
  event: "bg_tasks_snapshot_failed"
2529
2725
  }, "bg-tasks snapshot publish failed");
@@ -2621,7 +2817,7 @@ function createToolHeartbeat({ messageId }) {
2621
2817
  }
2622
2818
  heartbeatCount++;
2623
2819
  if (heartbeatCount > MAX_TOOL_HEARTBEATS) {
2624
- log$14.warn({
2820
+ log$16.warn({
2625
2821
  heartbeatCount,
2626
2822
  activeToolCalls: [...activeToolCalls]
2627
2823
  }, "tool heartbeat max reached, stopping");
@@ -2652,7 +2848,7 @@ function postToDaemon(path, body) {
2652
2848
  headers: { "content-type": "application/json" },
2653
2849
  body: JSON.stringify(body)
2654
2850
  }).catch((err) => {
2655
- log$14.debug({
2851
+ log$16.debug({
2656
2852
  err,
2657
2853
  path,
2658
2854
  event: "daemon_post_failed"
@@ -2661,7 +2857,7 @@ function postToDaemon(path, body) {
2661
2857
  }
2662
2858
  function createPlatformExtensions({ sessionId, channelContext }) {
2663
2859
  return (pi) => {
2664
- log$14.info({
2860
+ log$16.info({
2665
2861
  sessionId,
2666
2862
  hasChannelContext: Boolean(channelContext)
2667
2863
  }, "platform extension initialized");
@@ -2710,7 +2906,7 @@ function createPlatformExtensions({ sessionId, channelContext }) {
2710
2906
  });
2711
2907
  });
2712
2908
  pi.on("agent_end", () => {
2713
- log$14.info({ sessionId }, "session ending");
2909
+ log$16.info({ sessionId }, "session ending");
2714
2910
  postToDaemon("/session/end", { sessionId });
2715
2911
  });
2716
2912
  };
@@ -2846,7 +3042,7 @@ const toolCallEnvExtension = (pi) => {
2846
3042
  * semantics; once promoted, the command inherits background-task lifecycle
2847
3043
  * (its own controller) and the foreground kill timeout no longer applies.
2848
3044
  */
2849
- const log$13 = logger.child({ module: "background-tasks-ext" });
3045
+ const log$15 = logger.child({ module: "background-tasks-ext" });
2850
3046
  const ops = createLocalBashOperations();
2851
3047
  const STEER_BACKGROUND_DEFAULT_SECONDS = 60;
2852
3048
  const STEER_BACKGROUND_ENV_KEYS = ["BASH_STEER_BACKGROUND_SECONDS", "SKYDIVE_BASH_STEER_BACKGROUND_SECONDS"];
@@ -2861,7 +3057,7 @@ function steerBackgroundThresholdMs() {
2861
3057
  if (raw === void 0 || raw.trim() === "") continue;
2862
3058
  const parsed = Number(raw);
2863
3059
  if (!Number.isFinite(parsed) || parsed < 0) {
2864
- log$13.warn({
3060
+ log$15.warn({
2865
3061
  envKey: key,
2866
3062
  raw
2867
3063
  }, "invalid BASH_STEER_BACKGROUND_SECONDS, using default");
@@ -2890,7 +3086,7 @@ async function notifyBashSteerArrival(args) {
2890
3086
  try {
2891
3087
  await listener();
2892
3088
  } catch (err) {
2893
- log$13.error({
3089
+ log$15.error({
2894
3090
  err,
2895
3091
  sessionId: args.sessionId
2896
3092
  }, "bash steer promotion failed");
@@ -2985,14 +3181,14 @@ function watchdogTick(now) {
2985
3181
  const overSize = meta.logBytes > MAX_LOG_BYTES;
2986
3182
  if (!overRuntime && !overSize) continue;
2987
3183
  const reason = overSize ? `output exceeded ${MAX_LOG_BYTES / (1024 * 1024)}MiB` : `exceeded ${KEEPALIVE_MAX_MS / 6e4}m max runtime`;
2988
- log$13.warn({
3184
+ log$15.warn({
2989
3185
  taskId: meta.id,
2990
3186
  reason
2991
3187
  }, "watchdog killing bg task");
2992
3188
  try {
2993
3189
  killTask(meta, reason);
2994
3190
  } catch (err) {
2995
- log$13.warn({
3191
+ log$15.warn({
2996
3192
  err,
2997
3193
  taskId: meta.id
2998
3194
  }, "watchdog kill failed");
@@ -3051,7 +3247,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
3051
3247
  const now = Date.now();
3052
3248
  const promotable = [...foregroundBash.values()].filter((call) => now - call.startedAt >= thresholdMs);
3053
3249
  if (promotable.length === 0) return;
3054
- log$13.info({
3250
+ log$15.info({
3055
3251
  event: "bash_steer_promotion",
3056
3252
  count: promotable.length,
3057
3253
  thresholdSeconds: Math.round(thresholdMs / 1e3),
@@ -3076,7 +3272,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
3076
3272
  try {
3077
3273
  await mkdir(tasksDir(), { recursive: true });
3078
3274
  } catch (err) {
3079
- log$13.error({ err }, "bg task adopt: mkdir failed");
3275
+ log$15.error({ err }, "bg task adopt: mkdir failed");
3080
3276
  }
3081
3277
  const logStream = createWriteStream(logPath(id), { flags: "a" });
3082
3278
  logStream.on("error", () => {});
@@ -3103,7 +3299,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
3103
3299
  tasks.set(id, meta);
3104
3300
  ensureWatchdog();
3105
3301
  schedulePublishSnapshot();
3106
- log$13.info({
3302
+ log$15.info({
3107
3303
  taskId: id,
3108
3304
  conversationId,
3109
3305
  startedAt: args.startedAt
@@ -3256,7 +3452,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
3256
3452
  try {
3257
3453
  shellPath = SettingsManager.create(ctx.cwd, getAgentDir()).getShellPath();
3258
3454
  } catch (err) {
3259
- log$13.warn({
3455
+ log$15.warn({
3260
3456
  err,
3261
3457
  cwd: ctx.cwd
3262
3458
  }, "bash steer promotion: shell settings unavailable, using default shell");
@@ -3271,7 +3467,7 @@ function createBackgroundTasksExtension({ channelContext, sessionId }) {
3271
3467
  ...definition,
3272
3468
  description: `${definition.description}\n\nLong-running bash commands are automatically moved to a background task when a new message arrives while the command has already been running for at least a minute: you get the task id immediately, are woken on completion, and must not depend on the command's output until that wake arrives. Shorter commands finish in the foreground like they always have.`
3273
3469
  });
3274
- log$13.info({
3470
+ log$15.info({
3275
3471
  event: "bash_steer_promotion_enabled",
3276
3472
  thresholdSeconds: Math.round(thresholdMs / 1e3)
3277
3473
  }, "supervised bash tool registered (steer-triggered backgrounding)");
@@ -3314,7 +3510,7 @@ or if you were explicitly waiting to report it.`,
3314
3510
  triggerTurn: true,
3315
3511
  deliverAs: "followUp"
3316
3512
  });
3317
- log$13.info({
3513
+ log$15.info({
3318
3514
  taskId: meta.id,
3319
3515
  conversationId
3320
3516
  }, "bg task completion delivered live (followUp)");
@@ -3322,7 +3518,7 @@ or if you were explicitly waiting to report it.`,
3322
3518
  }
3323
3519
  if (meta.messageId) {
3324
3520
  meta.notified = true;
3325
- log$13.info({
3521
+ log$15.info({
3326
3522
  taskId: meta.id,
3327
3523
  messageId: meta.messageId
3328
3524
  }, "posting idle bg-task-done wake");
@@ -3332,12 +3528,12 @@ or if you were explicitly waiting to report it.`,
3332
3528
  content: message.content
3333
3529
  }).catch((err) => {
3334
3530
  meta.notified = false;
3335
- log$13.warn({
3531
+ log$15.warn({
3336
3532
  err,
3337
3533
  taskId: meta.id
3338
3534
  }, "bg-task-done wake failed; will retry at next session_start");
3339
3535
  });
3340
- } else log$13.info({ taskId: meta.id }, "bg task completed idle with no origin message; deferring to next session_start");
3536
+ } else log$15.info({ taskId: meta.id }, "bg task completed idle with no origin message; deferring to next session_start");
3341
3537
  }
3342
3538
  const SNAPSHOT_TAIL_LINES = 40;
3343
3539
  let lastPublishedSignature = null;
@@ -3374,7 +3570,7 @@ or if you were explicitly waiting to report it.`,
3374
3570
  tasks: snapshot
3375
3571
  });
3376
3572
  } catch (err) {
3377
- log$13.debug({
3573
+ log$15.debug({
3378
3574
  err,
3379
3575
  event: "bg_tasks_snapshot_build_failed"
3380
3576
  }, "building bg-tasks snapshot failed");
@@ -3417,7 +3613,7 @@ or if you were explicitly waiting to report it.`,
3417
3613
  logStream.end();
3418
3614
  await finished(logStream).catch(() => {});
3419
3615
  meta.running = false;
3420
- log$13.info({
3616
+ log$15.info({
3421
3617
  taskId: meta.id,
3422
3618
  exitCode: meta.exitCode
3423
3619
  }, "bg task finished");
@@ -3434,14 +3630,14 @@ or if you were explicitly waiting to report it.`,
3434
3630
  try {
3435
3631
  await mkdir(tasksDir(), { recursive: true });
3436
3632
  } catch (err) {
3437
- log$13.error({
3633
+ log$15.error({
3438
3634
  err,
3439
3635
  taskId: id
3440
3636
  }, "background task dir unavailable");
3441
3637
  }
3442
3638
  const logStream = createWriteStream(logPath(id), { flags: "a" });
3443
3639
  logStream.on("error", (err) => {
3444
- log$13.warn({
3640
+ log$15.warn({
3445
3641
  err,
3446
3642
  taskId: id
3447
3643
  }, "bg task log write failed");
@@ -3491,7 +3687,7 @@ or if you were explicitly waiting to report it.`,
3491
3687
  }).catch((err) => {
3492
3688
  if (!meta.controller.signal.aborted) {
3493
3689
  meta.error = err instanceof Error ? err.message : String(err);
3494
- log$13.warn({
3690
+ log$15.warn({
3495
3691
  err,
3496
3692
  taskId: id
3497
3693
  }, "bg task exec error");
@@ -3500,7 +3696,7 @@ or if you were explicitly waiting to report it.`,
3500
3696
  await finishTask(meta, logStream);
3501
3697
  });
3502
3698
  ensureWatchdog();
3503
- log$13.info({
3699
+ log$15.info({
3504
3700
  taskId: id,
3505
3701
  conversationId
3506
3702
  }, "bg task started");
@@ -3519,7 +3715,7 @@ or if you were explicitly waiting to report it.`,
3519
3715
  for (const spec of specs) {
3520
3716
  const live = tasks.get(spec.id);
3521
3717
  if (live && sameScope(live, scopeKey())) continue;
3522
- log$13.info({
3718
+ log$15.info({
3523
3719
  taskId: spec.id,
3524
3720
  conversationId
3525
3721
  }, "relaunching journaled resumable bg task after restart");
@@ -3537,7 +3733,7 @@ or if you were explicitly waiting to report it.`,
3537
3733
  stream.write(`<resumed-after-restart>requeued and relaunched on a new sandbox after the previous one was drained; this is a fresh execution of the command from the start, not a continuation</resumed-after-restart>\n`);
3538
3734
  stream.end();
3539
3735
  } catch (err) {
3540
- log$13.debug({
3736
+ log$15.debug({
3541
3737
  err,
3542
3738
  taskId: meta.id
3543
3739
  }, "resume banner write failed");
@@ -3557,7 +3753,7 @@ or if you were explicitly waiting to report it.`,
3557
3753
  meta.notified = true;
3558
3754
  pi.sendMessage(await taskDoneMessage(meta));
3559
3755
  }
3560
- if (unnotified.length > 0) log$13.info({
3756
+ if (unnotified.length > 0) log$15.info({
3561
3757
  conversationId,
3562
3758
  count: unnotified.length
3563
3759
  }, "injected completed bg tasks at session_start");
@@ -3752,7 +3948,7 @@ or if you were explicitly waiting to report it.`,
3752
3948
  * read on the hot path before every LLM call), it falls back to the default
3753
3949
  * for that knob and logs once.
3754
3950
  */
3755
- const log$12 = logger.child({ module: "context-management-config" });
3951
+ const log$14 = logger.child({ module: "context-management-config" });
3756
3952
  const DEFAULT_CONTEXT_MANAGEMENT_CONFIG = {
3757
3953
  enabled: false,
3758
3954
  perResultMaxBytes: 16 * 1024,
@@ -3797,7 +3993,7 @@ function resolveContextManagementConfig(env = process.env) {
3797
3993
  clearAtLeastTokens: env.SKYDIVE_CTX_CLEAR_AT_LEAST_TOKENS
3798
3994
  });
3799
3995
  if (!parsed.success) {
3800
- log$12.warn({
3996
+ log$14.warn({
3801
3997
  event: "context_management_config_invalid",
3802
3998
  err: parsed.error
3803
3999
  }, "falling back to default context-management config");
@@ -3881,7 +4077,7 @@ function hasFlagSource(env = process.env) {
3881
4077
  * alive and an indeterminate result (no api url / transient failure) leaves the
3882
4078
  * last-known values untouched so a blip can't silently flip behavior.
3883
4079
  */
3884
- const log$11 = logger.child({ module: "feature-flags-poll" });
4080
+ const log$13 = logger.child({ module: "feature-flags-poll" });
3885
4081
  const FLAG_POLL_INTERVAL_MS = 6e4;
3886
4082
  let contextManagement = null;
3887
4083
  let closingText = null;
@@ -3921,7 +4117,7 @@ function apply(name, next) {
3921
4117
  if (next !== prev) for (const cb of subscribers[name]) try {
3922
4118
  cb(next);
3923
4119
  } catch (err) {
3924
- log$11.warn({
4120
+ log$13.warn({
3925
4121
  err,
3926
4122
  flag: name
3927
4123
  }, "flag subscriber threw");
@@ -3934,7 +4130,7 @@ async function pollOnce() {
3934
4130
  apply("contextManagement", flags.contextManagement ?? null);
3935
4131
  apply("closingText", flags.closingText ?? null);
3936
4132
  } catch (err) {
3937
- log$11.debug({ err }, "feature-flag poll threw");
4133
+ log$13.debug({ err }, "feature-flag poll threw");
3938
4134
  }
3939
4135
  }
3940
4136
  /**
@@ -4136,7 +4332,7 @@ const bashDefaultTimeoutExtension = (pi) => {
4136
4332
  * turn over an observability feature.
4137
4333
  */
4138
4334
  const execFileAsync = promisify(execFile);
4139
- const log$10 = logger.child({ module: "resource-pressure-warning" });
4335
+ const log$12 = logger.child({ module: "resource-pressure-warning" });
4140
4336
  const POLL_INTERVAL_MS = 1e4;
4141
4337
  function envOverride(name) {
4142
4338
  for (const prefix of ["SKYDIVE_", "ANYONE_"]) {
@@ -4333,7 +4529,7 @@ const resourcePressureWarningExtension = (pi) => {
4333
4529
  warnedDiskThisTurn = true;
4334
4530
  }
4335
4531
  if (trigger === null) return;
4336
- log$10.warn({
4532
+ log$12.warn({
4337
4533
  trigger,
4338
4534
  mem,
4339
4535
  cpuPct,
@@ -4371,7 +4567,7 @@ const resourcePressureWarningExtension = (pi) => {
4371
4567
  if (!timer) {
4372
4568
  timer = setInterval(() => {
4373
4569
  checkOnce().catch((err) => {
4374
- log$10.error({ err }, "resource pressure check failed");
4570
+ log$12.error({ err }, "resource pressure check failed");
4375
4571
  });
4376
4572
  }, POLL_INTERVAL_MS);
4377
4573
  timer.unref?.();
@@ -4387,7 +4583,7 @@ const resourcePressureWarningExtension = (pi) => {
4387
4583
  };
4388
4584
  //#endregion
4389
4585
  //#region src/extensions/disk-guard.ts
4390
- const log$9 = logger.child({ module: "disk-guard" });
4586
+ const log$11 = logger.child({ module: "disk-guard" });
4391
4587
  /**
4392
4588
  * In-band bypass. The guard is a safety net, not a jail: when the agent knows
4393
4589
  * a flagged command is genuinely safe (writing to a different mount, a tiny
@@ -4527,7 +4723,7 @@ const diskGuardExtension = (pi) => {
4527
4723
  if (!isSpaceHungryCommand(command)) return;
4528
4724
  const diskPct = await readDiskUsePct();
4529
4725
  if (!shouldBlockForDisk(command, diskPct)) return;
4530
- log$9.warn({
4726
+ log$11.warn({
4531
4727
  diskPct,
4532
4728
  command: command.slice(0, 200)
4533
4729
  }, "blocked space-hungry bash command on near-full disk");
@@ -4539,7 +4735,7 @@ const diskGuardExtension = (pi) => {
4539
4735
  };
4540
4736
  //#endregion
4541
4737
  //#region src/extensions/noop-loop-guard.ts
4542
- const log$8 = logger.child({ module: "noop-loop-guard" });
4738
+ const log$10 = logger.child({ module: "noop-loop-guard" });
4543
4739
  /** Length of the rolling window that counts silence commands across streak resets. */
4544
4740
  const SILENCE_WINDOW_MS = 300 * 1e3;
4545
4741
  /** Bare shell no-op: `true` or `:` (case-insensitive). */
@@ -4691,7 +4887,7 @@ const noopLoopGuardExtension = (pi) => {
4691
4887
  if (bypassed) return;
4692
4888
  if (streak < 3 && windowCount < 8) return;
4693
4889
  const aborted = streak >= 25 || windowCount >= 40;
4694
- log$8.warn({
4890
+ log$10.warn({
4695
4891
  streak,
4696
4892
  windowCount,
4697
4893
  aborted,
@@ -4758,7 +4954,7 @@ const noopLoopGuardExtension = (pi) => {
4758
4954
  * embedding a script).
4759
4955
  */
4760
4956
  const execFileP = promisify(execFile);
4761
- const log$7 = logger.child({ module: "sleep-guard" });
4957
+ const log$9 = logger.child({ module: "sleep-guard" });
4762
4958
  /** How long the `platform automation create` call may take before we fall back to guidance. */
4763
4959
  const SCHEDULE_TIMEOUT_MS = 15e3;
4764
4960
  /** Title for the one-shot wakes this guard schedules. Visible in the Routines UI. */
@@ -5002,7 +5198,7 @@ async function scheduleSleepWake(seconds, ledger) {
5002
5198
  ], { timeout: SCHEDULE_TIMEOUT_MS });
5003
5199
  return at;
5004
5200
  } catch (err) {
5005
- log$7.warn({ err }, "failed to auto-schedule sleep wake; falling back to guidance");
5201
+ log$9.warn({ err }, "failed to auto-schedule sleep wake; falling back to guidance");
5006
5202
  return null;
5007
5203
  }
5008
5204
  }
@@ -5023,7 +5219,7 @@ const sleepGuardExtension = (pi) => {
5023
5219
  if (typeof command !== "string" || command.length === 0) return;
5024
5220
  const violation = shouldBlockForSleep(command);
5025
5221
  if (!violation) return;
5026
- log$7.warn({
5222
+ log$9.warn({
5027
5223
  seconds: violation.seconds,
5028
5224
  leading: violation.leading,
5029
5225
  unboundedLoop: violation.unboundedLoop,
@@ -5032,7 +5228,7 @@ const sleepGuardExtension = (pi) => {
5032
5228
  if (!violation.unboundedLoop && violation.seconds <= 86400 && isPureSleepCommand(blankQuotedRegions(command))) {
5033
5229
  const at = await scheduleSleepWake(violation.seconds, wakeLedger);
5034
5230
  if (at) {
5035
- log$7.info({
5231
+ log$9.info({
5036
5232
  at,
5037
5233
  seconds: violation.seconds
5038
5234
  }, "auto-scheduled sleep wake");
@@ -5140,7 +5336,7 @@ function transformContextMessages(messages, config, now) {
5140
5336
  }
5141
5337
  //#endregion
5142
5338
  //#region src/extensions/context-management.ts
5143
- const log$6 = logger.child({ module: "context-management-extension" });
5339
+ const log$8 = logger.child({ module: "context-management-extension" });
5144
5340
  function isAnthropicMessagesPayload(payload) {
5145
5341
  if (typeof payload !== "object" || payload === null) return false;
5146
5342
  const candidate = payload;
@@ -5205,13 +5401,13 @@ function createContextManagementExtension() {
5205
5401
  setContextManagementFlagOverride(getPolledFlag("contextManagement"));
5206
5402
  onFlagChange("contextManagement", (enabled) => {
5207
5403
  setContextManagementFlagOverride(enabled);
5208
- log$6.info({
5404
+ log$8.info({
5209
5405
  event: "context_management_flag_update",
5210
5406
  enabled
5211
5407
  }, "context-management flag updated from platform");
5212
5408
  });
5213
5409
  startFeatureFlagPoller();
5214
- log$6.info({
5410
+ log$8.info({
5215
5411
  event: "context_management_registered",
5216
5412
  enabled: initial.enabled,
5217
5413
  flagSource: hasFlagSource(),
@@ -5223,13 +5419,13 @@ function createContextManagementExtension() {
5223
5419
  const { messages } = event;
5224
5420
  try {
5225
5421
  const result = transformContextIfEnabled(messages, getContextManagementConfig(), Date.now());
5226
- if (result.stats && (result.stats.clearedResults > 0 || result.stats.trimmedResults > 0)) log$6.info({
5422
+ if (result.stats && (result.stats.clearedResults > 0 || result.stats.trimmedResults > 0)) log$8.info({
5227
5423
  event: "context_management_applied",
5228
5424
  ...result.stats
5229
5425
  }, "trimmed/cleared tool output before LLM call");
5230
5426
  return { messages: result.messages };
5231
5427
  } catch (err) {
5232
- log$6.error({
5428
+ log$8.error({
5233
5429
  err,
5234
5430
  event: "context_management_transform_failed"
5235
5431
  }, "context transform failed; passing messages through unchanged");
@@ -5241,7 +5437,7 @@ function createContextManagementExtension() {
5241
5437
  }
5242
5438
  //#endregion
5243
5439
  //#region src/extensions/current-time.ts
5244
- const log$5 = logger.child({ module: "current-time-extension" });
5440
+ const log$7 = logger.child({ module: "current-time-extension" });
5245
5441
  const PI_DATE_LINE = /^Current date:.*$/m;
5246
5442
  function formatCurrentTimeLine(now) {
5247
5443
  return `Current date: ${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")} (${new Intl.DateTimeFormat("en-US", {
@@ -5254,7 +5450,7 @@ const currentTimeExtension = (pi) => {
5254
5450
  const line = formatCurrentTimeLine(/* @__PURE__ */ new Date());
5255
5451
  const base = event.systemPrompt;
5256
5452
  if (PI_DATE_LINE.test(base)) {
5257
- log$5.info({ event: "pi_date_line_present" }, "pi base prompt carries its own 'Current date:' line again; replacing it in place (pi prompt format may have changed)");
5453
+ log$7.info({ event: "pi_date_line_present" }, "pi base prompt carries its own 'Current date:' line again; replacing it in place (pi prompt format may have changed)");
5258
5454
  return { systemPrompt: base.replace(PI_DATE_LINE, line) };
5259
5455
  }
5260
5456
  return { systemPrompt: `${base}\n${line}` };
@@ -5642,7 +5838,7 @@ async function migrateMemory({ cwd }) {
5642
5838
  }
5643
5839
  //#endregion
5644
5840
  //#region src/extensions/memory.ts
5645
- const log$4 = logger.child({ module: "memory-extension" });
5841
+ const log$6 = logger.child({ module: "memory-extension" });
5646
5842
  /**
5647
5843
  * The standing instructions for the memory system. Always injected (even with
5648
5844
  * no `MEMORY.md`) so the agent knows it can persist notes and how. `users/` is
@@ -5679,13 +5875,13 @@ const memoryExtension = (pi) => {
5679
5875
  pi.on("session_start", async (_event, ctx) => {
5680
5876
  try {
5681
5877
  const migrated = await migrateMemory({ cwd: ctx.cwd });
5682
- if (migrated !== null) log$4.info({
5878
+ if (migrated !== null) log$6.info({
5683
5879
  event: "memory_migrated",
5684
5880
  from: migrated.from,
5685
5881
  to: migrated.to
5686
5882
  }, "migrated memory layout to current version");
5687
5883
  } catch (err) {
5688
- log$4.warn({
5884
+ log$6.warn({
5689
5885
  err,
5690
5886
  event: "memory_migration_failed"
5691
5887
  }, "memory migration failed; continuing with existing index");
@@ -5697,7 +5893,7 @@ const memoryExtension = (pi) => {
5697
5893
  index
5698
5894
  });
5699
5895
  } catch (err) {
5700
- log$4.warn({
5896
+ log$6.warn({
5701
5897
  err,
5702
5898
  event: "memory_index_failed"
5703
5899
  }, "memory index read failed; injecting instructions only");
@@ -5711,7 +5907,7 @@ const memoryExtension = (pi) => {
5711
5907
  };
5712
5908
  //#endregion
5713
5909
  //#region src/extensions/platform-memory.ts
5714
- const log$3 = logger.child({ module: "platform-memory-extension" });
5910
+ const log$5 = logger.child({ module: "platform-memory-extension" });
5715
5911
  /**
5716
5912
  * Resolve the human on this turn via the API, keyed by the message id.
5717
5913
  * `/sandbox/channel-context` only returns a sender for a platform-known
@@ -5724,13 +5920,13 @@ const log$3 = logger.child({ module: "platform-memory-extension" });
5724
5920
  async function resolveTurnUser(messageId) {
5725
5921
  const client = sandboxClient();
5726
5922
  if (!client) {
5727
- log$3.debug({ event: "resolve_turn_user_no_api_url" }, "no API url in env; withholding user memory");
5923
+ log$5.debug({ event: "resolve_turn_user_no_api_url" }, "no API url in env; withholding user memory");
5728
5924
  return null;
5729
5925
  }
5730
5926
  try {
5731
5927
  const res = await client["channel-context"].$get({ query: { messageId } });
5732
5928
  if (!res.ok) {
5733
- log$3.warn({
5929
+ log$5.warn({
5734
5930
  event: "resolve_turn_user_failed",
5735
5931
  status: res.status
5736
5932
  }, "channel-context returned non-ok; withholding user memory");
@@ -5743,7 +5939,7 @@ async function resolveTurnUser(messageId) {
5743
5939
  displayName: sender.displayName
5744
5940
  };
5745
5941
  } catch (err) {
5746
- log$3.warn({
5942
+ log$5.warn({
5747
5943
  err,
5748
5944
  event: "resolve_turn_user_failed"
5749
5945
  }, "failed to resolve current user; withholding user memory");
@@ -5790,7 +5986,7 @@ function createPlatformMemoryExtension({ channelContext }) {
5790
5986
  user
5791
5987
  });
5792
5988
  } catch (err) {
5793
- log$3.warn({
5989
+ log$5.warn({
5794
5990
  err,
5795
5991
  event: "user_memory_index_failed"
5796
5992
  }, "user memory index build failed; skipping injection");
@@ -5821,6 +6017,1064 @@ const providerApiCompatExtension = (pi) => {
5821
6017
  })) });
5822
6018
  };
5823
6019
  //#endregion
6020
+ //#region src/extensions/claude-seat-history.ts
6021
+ const imageType = z.enum([
6022
+ "image/jpeg",
6023
+ "image/png",
6024
+ "image/gif",
6025
+ "image/webp"
6026
+ ]);
6027
+ function userContent(content) {
6028
+ if (typeof content === "string") return content;
6029
+ return content.map((block) => {
6030
+ if (block.type === "text") return {
6031
+ type: "text",
6032
+ text: block.text
6033
+ };
6034
+ return {
6035
+ type: "image",
6036
+ source: {
6037
+ type: "base64",
6038
+ media_type: imageType.parse(block.mimeType),
6039
+ data: block.data
6040
+ }
6041
+ };
6042
+ });
6043
+ }
6044
+ /** Shared Anthropic message projection for SDK replay and provider hooks. */
6045
+ function seatHistoryMessage({ message, model, toolName }) {
6046
+ let converted;
6047
+ switch (message.role) {
6048
+ case "user":
6049
+ converted = {
6050
+ role: "user",
6051
+ content: userContent(message.content)
6052
+ };
6053
+ break;
6054
+ case "toolResult":
6055
+ converted = {
6056
+ role: "user",
6057
+ content: [{
6058
+ type: "tool_result",
6059
+ tool_use_id: message.toolCallId,
6060
+ content: userContent(message.content),
6061
+ is_error: message.isError
6062
+ }]
6063
+ };
6064
+ break;
6065
+ case "assistant": {
6066
+ const content = message.content.flatMap((block) => {
6067
+ switch (block.type) {
6068
+ case "text": return block.text ? [{
6069
+ type: "text",
6070
+ text: block.text
6071
+ }] : [];
6072
+ case "toolCall": return [{
6073
+ type: "tool_use",
6074
+ id: block.id,
6075
+ name: toolName(block.name),
6076
+ input: block.arguments
6077
+ }];
6078
+ case "thinking": return message.provider === model.provider && message.model === model.id && block.thinkingSignature ? [{
6079
+ type: "thinking",
6080
+ thinking: block.thinking,
6081
+ signature: block.thinkingSignature
6082
+ }] : [];
6083
+ default: throw new Error(`Unsupported history block: ${String(block)}`);
6084
+ }
6085
+ });
6086
+ if (content.length === 0) return null;
6087
+ converted = {
6088
+ role: "assistant",
6089
+ content
6090
+ };
6091
+ break;
6092
+ }
6093
+ default: throw new Error(`Unsupported history message: ${String(message)}`);
6094
+ }
6095
+ return converted;
6096
+ }
6097
+ /** Keep history roles and content intact at the pinned Claude Code resume boundary. */
6098
+ async function prepareSeatHistory({ context, configDir, cwd, toolName, model }) {
6099
+ const messages = [...context.messages];
6100
+ const last = messages.at(-1);
6101
+ const prompt = last?.role === "user" ? userContent(last.content) : "Continue from the existing conversation and tool results.";
6102
+ if (last?.role === "user") messages.pop();
6103
+ if (messages.length === 0) return {
6104
+ prompt,
6105
+ resume: null
6106
+ };
6107
+ const sessionId = randomUUID();
6108
+ let parentUuid = null;
6109
+ const entries = messages.flatMap((message) => {
6110
+ const converted = seatHistoryMessage({
6111
+ message,
6112
+ model,
6113
+ toolName
6114
+ });
6115
+ if (!converted) return [];
6116
+ const uuid = randomUUID();
6117
+ const entry = {
6118
+ type: converted.role,
6119
+ uuid,
6120
+ parentUuid,
6121
+ sessionId,
6122
+ cwd,
6123
+ isSidechain: false,
6124
+ timestamp: new Date(message.timestamp).toISOString(),
6125
+ message: message.role === "assistant" ? {
6126
+ ...converted,
6127
+ id: `msg_${uuid}`,
6128
+ type: "message",
6129
+ model: message.model,
6130
+ stop_reason: message.stopReason === "toolUse" ? "tool_use" : "end_turn",
6131
+ stop_sequence: null,
6132
+ usage: {
6133
+ input_tokens: 0,
6134
+ output_tokens: 0
6135
+ }
6136
+ } : converted
6137
+ };
6138
+ parentUuid = uuid;
6139
+ return [entry];
6140
+ });
6141
+ if (entries.length === 0) return {
6142
+ prompt,
6143
+ resume: null
6144
+ };
6145
+ const resume = join(configDir, "history.jsonl");
6146
+ await writeFile(resume, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`, { mode: 384 });
6147
+ return {
6148
+ prompt,
6149
+ resume
6150
+ };
6151
+ }
6152
+ //#endregion
6153
+ //#region src/extensions/claude-seat-input.ts
6154
+ /** Resolve a send only after the SDK has consumed it, before releasing tool results. */
6155
+ var SeatInput = class {
6156
+ pending = [];
6157
+ deliveries = /* @__PURE__ */ new Set();
6158
+ wake = null;
6159
+ closed = false;
6160
+ send(message) {
6161
+ if (this.closed) throw new Error("Claude seat input is closed");
6162
+ return new Promise((resolve) => {
6163
+ const delivered = () => {
6164
+ this.deliveries.delete(delivered);
6165
+ resolve();
6166
+ };
6167
+ this.deliveries.add(delivered);
6168
+ this.pending.push({
6169
+ message,
6170
+ delivered
6171
+ });
6172
+ this.wake?.();
6173
+ });
6174
+ }
6175
+ close() {
6176
+ this.closed = true;
6177
+ this.pending.length = 0;
6178
+ for (const delivered of this.deliveries) delivered();
6179
+ this.wake?.();
6180
+ }
6181
+ async *messages(initial) {
6182
+ yield initial;
6183
+ while (!this.closed) {
6184
+ const item = this.pending.shift();
6185
+ if (item) try {
6186
+ yield item.message;
6187
+ } finally {
6188
+ item.delivered();
6189
+ }
6190
+ else {
6191
+ await new Promise((resolve) => {
6192
+ this.wake = resolve;
6193
+ });
6194
+ this.wake = null;
6195
+ }
6196
+ }
6197
+ }
6198
+ };
6199
+ //#endregion
6200
+ //#region src/extensions/claude-seat-usage.ts
6201
+ function mapUsage(usage) {
6202
+ const input = usage.input_tokens ?? 0;
6203
+ const output = usage.output_tokens ?? 0;
6204
+ const cacheRead = usage.cache_read_input_tokens ?? 0;
6205
+ const cacheWrite = usage.cache_creation_input_tokens ?? 0;
6206
+ return {
6207
+ input,
6208
+ output,
6209
+ cacheRead,
6210
+ cacheWrite,
6211
+ totalTokens: input + output + cacheRead + cacheWrite,
6212
+ cost: {
6213
+ input: 0,
6214
+ output: 0,
6215
+ cacheRead: 0,
6216
+ cacheWrite: 0,
6217
+ total: 0
6218
+ }
6219
+ };
6220
+ }
6221
+ /** Preserve nonnegative step usage while making inconsistent SDK totals observable. */
6222
+ function remainingSeatUsage({ total, reported, log, sessionId, model }) {
6223
+ const remaining = {
6224
+ input_tokens: total.input - reported.input,
6225
+ output_tokens: total.output - reported.output,
6226
+ cache_read_input_tokens: total.cacheRead - reported.cacheRead,
6227
+ cache_creation_input_tokens: total.cacheWrite - reported.cacheWrite
6228
+ };
6229
+ if (Object.values(remaining).some((count) => count < 0)) log.warn({
6230
+ event: "claude_seat_usage_drift",
6231
+ sessionId,
6232
+ model,
6233
+ remaining,
6234
+ reportedUsage: reported,
6235
+ sdkUsage: total
6236
+ }, "Claude seat SDK usage total is below reported steps");
6237
+ return mapUsage({
6238
+ input_tokens: Math.max(0, remaining.input_tokens),
6239
+ output_tokens: Math.max(0, remaining.output_tokens),
6240
+ cache_read_input_tokens: Math.max(0, remaining.cache_read_input_tokens),
6241
+ cache_creation_input_tokens: Math.max(0, remaining.cache_creation_input_tokens)
6242
+ });
6243
+ }
6244
+ //#endregion
6245
+ //#region src/extensions/claude-seat-reasoning.ts
6246
+ const effortSchema = z.enum([
6247
+ "low",
6248
+ "medium",
6249
+ "high",
6250
+ "xhigh",
6251
+ "max"
6252
+ ]);
6253
+ /** Keep the SDK's defaults from overriding Pi's requested reasoning budget. */
6254
+ function seatReasoningOptions({ model, context, options }) {
6255
+ const level = clampThinkingLevel(model, options.reasoning ?? "off");
6256
+ let maxTokens = clampMaxTokensToContext(model, context, options.maxTokens ?? model.maxTokens);
6257
+ const sdk = { thinking: { type: "disabled" } };
6258
+ if (level === "off") return {
6259
+ ...sdk,
6260
+ maxTokens
6261
+ };
6262
+ if (model.thinkingLevelMap) {
6263
+ sdk.thinking = { type: "adaptive" };
6264
+ sdk.effort = effortSchema.parse(model.thinkingLevelMap?.[level] ?? (level === "minimal" ? "low" : level));
6265
+ return {
6266
+ ...sdk,
6267
+ maxTokens
6268
+ };
6269
+ }
6270
+ const adjusted = adjustMaxTokensForThinking(maxTokens, model.maxTokens, level, options.thinkingBudgets);
6271
+ maxTokens = clampMaxTokensToContext(model, context, adjusted.maxTokens);
6272
+ const budgetTokens = Math.min(adjusted.thinkingBudget, Math.max(0, maxTokens - 1024));
6273
+ if (budgetTokens >= 1024) sdk.thinking = {
6274
+ type: "enabled",
6275
+ budgetTokens
6276
+ };
6277
+ return {
6278
+ ...sdk,
6279
+ maxTokens
6280
+ };
6281
+ }
6282
+ //#endregion
6283
+ //#region src/extensions/claude-seat-payload.ts
6284
+ const payloadSchema = z.strictObject({
6285
+ model: z.string(),
6286
+ messages: z.array(z.unknown()),
6287
+ tools: z.array(z.object({
6288
+ name: z.string(),
6289
+ description: z.string(),
6290
+ input_schema: z.record(z.string(), z.json())
6291
+ })),
6292
+ context_management: z.object({ edits: z.array(z.object({ exclude_tools: z.array(z.string()).optional() }).catchall(z.json())) }).catchall(z.json()).optional()
6293
+ });
6294
+ /**
6295
+ * Apply Pi's tool-schema and native context-edit hooks to the SDK inputs on
6296
+ * every model step, before MCP name mapping. This is an input projection;
6297
+ * Claude Code owns the wire payload. Unsupported rewrites fail explicitly.
6298
+ */
6299
+ async function prepareSeatRequest({ model, context, options }) {
6300
+ const payload = {
6301
+ model: model.id,
6302
+ messages: structuredClone(context.messages.flatMap((message) => {
6303
+ const converted = seatHistoryMessage({
6304
+ message,
6305
+ model,
6306
+ toolName: (name) => name
6307
+ });
6308
+ return converted ? [converted] : [];
6309
+ })),
6310
+ tools: (context.tools ?? []).map((tool) => ({
6311
+ name: tool.name,
6312
+ description: tool.description,
6313
+ input_schema: structuredClone(tool.parameters)
6314
+ }))
6315
+ };
6316
+ const unchanged = JSON.stringify([payload.model, payload.messages]);
6317
+ const updated = await options.onPayload?.(payload, model);
6318
+ const transformed = payloadSchema.parse(updated === void 0 ? payload : updated);
6319
+ if (JSON.stringify([transformed.model, transformed.messages]) !== unchanged) throw new Error("Claude seat SDK request hooks support tool schemas and context_management only; use the context hook to edit history");
6320
+ return {
6321
+ model,
6322
+ options,
6323
+ context: {
6324
+ ...context,
6325
+ tools: transformed.tools.map((tool) => ({
6326
+ name: tool.name,
6327
+ description: tool.description,
6328
+ parameters: Type.Unsafe(tool.input_schema)
6329
+ }))
6330
+ },
6331
+ contextManagement: transformed.context_management ?? null
6332
+ };
6333
+ }
6334
+ /** Native edits address the same MCP names as the SDK's outgoing tools. */
6335
+ function seatContextManagement({ contextManagement, toolName }) {
6336
+ if (!contextManagement) return null;
6337
+ return {
6338
+ ...contextManagement,
6339
+ edits: contextManagement.edits.map((edit) => ({
6340
+ ...edit,
6341
+ ...edit.exclude_tools ? { exclude_tools: edit.exclude_tools.map(toolName) } : {}
6342
+ }))
6343
+ };
6344
+ }
6345
+ //#endregion
6346
+ //#region src/extensions/claude-seat-query.ts
6347
+ const SEAT_SDK_API = "claude-agent-sdk";
6348
+ /** Bound each wait without limiting the total duration of a progressing turn. */
6349
+ const SEAT_QUERY_TIMEOUTS = {
6350
+ message: 5 * 6e4,
6351
+ input: 3e4,
6352
+ tools: 30 * 6e4
6353
+ };
6354
+ function fingerprint(value) {
6355
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
6356
+ }
6357
+ function requestKey({ model, context, options, contextManagement }) {
6358
+ return fingerprint({
6359
+ api: model.api,
6360
+ provider: model.provider,
6361
+ id: model.id,
6362
+ baseUrl: model.baseUrl,
6363
+ modelHeaders: model.headers,
6364
+ apiKey: options.apiKey,
6365
+ headers: options.headers,
6366
+ maxTokens: options.maxTokens,
6367
+ reasoning: options.reasoning,
6368
+ thinkingBudgets: options.thinkingBudgets,
6369
+ modelReasoning: model.reasoning,
6370
+ thinkingLevelMap: model.thinkingLevelMap,
6371
+ modelMaxTokens: model.maxTokens,
6372
+ contextWindow: model.contextWindow,
6373
+ systemPrompt: context.systemPrompt,
6374
+ tools: context.tools,
6375
+ contextManagement
6376
+ });
6377
+ }
6378
+ const PI_TOOLS_SERVER = "custom-tools";
6379
+ const log$4 = logger.child({ module: "claude-seat-sdk" });
6380
+ const toolMetadata = z.object({ _meta: z.object({ "claudecode/toolUseId": z.string().min(1) }) });
6381
+ /** Preserve existing MCP names; only ordinary Pi tools need a server prefix. */
6382
+ function toolBindings(context) {
6383
+ const parsed = (context.tools ?? []).map((definition) => {
6384
+ const [, server, name] = /^mcp__(.+?)__(.+)$/.exec(definition.name) ?? [];
6385
+ return {
6386
+ definition,
6387
+ server: server ?? null,
6388
+ name: name ?? null
6389
+ };
6390
+ });
6391
+ const existingServers = new Set(parsed.map((binding) => binding.server));
6392
+ let localServer = PI_TOOLS_SERVER;
6393
+ for (let suffix = 1; existingServers.has(localServer); suffix++) localServer = `${PI_TOOLS_SERVER}-${suffix}`;
6394
+ return parsed.map(({ definition, server, name }) => {
6395
+ const serverName = server ?? localServer;
6396
+ const toolName = name ?? definition.name;
6397
+ return {
6398
+ definition,
6399
+ serverName,
6400
+ toolName,
6401
+ sdkName: `mcp__${serverName}__${toolName}`
6402
+ };
6403
+ });
6404
+ }
6405
+ function platformHeaders({ model, options, sessionId }) {
6406
+ const headers = new Headers(model.headers);
6407
+ for (const [name, value] of Object.entries(options.headers ?? {})) if (value === null) headers.delete(name);
6408
+ else headers.set(name, value);
6409
+ if (!headers.has("x-skydive-session-id")) headers.set("x-skydive-session-id", sessionId);
6410
+ headers.set("x-skydive-served-via", SEAT_SDK_API);
6411
+ return [...headers].filter(([name]) => name.startsWith("x-skydive-") || name.startsWith("x-anyone-") || [
6412
+ "traceparent",
6413
+ "tracestate",
6414
+ "baggage"
6415
+ ].includes(name)).map(([name, value]) => `${name}: ${value}`).join("\n");
6416
+ }
6417
+ /** One SDK query, including tool rendezvous and its cancellable output queue. */
6418
+ var SeatQuery = class {
6419
+ controller = new AbortController();
6420
+ tools = /* @__PURE__ */ new Map();
6421
+ messages = [];
6422
+ reportedUsage = mapUsage({});
6423
+ input = new SeatInput();
6424
+ deliveredUsers = /* @__PURE__ */ new Map();
6425
+ wake = null;
6426
+ closed = false;
6427
+ failure = null;
6428
+ signals = /* @__PURE__ */ new Map();
6429
+ key;
6430
+ deliveredHistory;
6431
+ pendingTools = [];
6432
+ deadline = null;
6433
+ terminateChild = null;
6434
+ constructor(request, timeouts = SEAT_QUERY_TIMEOUTS) {
6435
+ this.request = request;
6436
+ this.timeouts = timeouts;
6437
+ this.key = requestKey(request);
6438
+ const { context } = request;
6439
+ this.deliveredHistory = {
6440
+ length: context.messages.length,
6441
+ key: fingerprint(context.messages)
6442
+ };
6443
+ for (const message of context.messages) {
6444
+ if (message.role !== "user") continue;
6445
+ const key = fingerprint(message);
6446
+ this.deliveredUsers.set(key, (this.deliveredUsers.get(key) ?? 0) + 1);
6447
+ }
6448
+ }
6449
+ /** Only the unclaimed continuation of this exact tool batch can reuse the query. */
6450
+ claimContinuation(request) {
6451
+ if (this.closed || this.controller.signal.aborted || this.pendingTools.length === 0) return false;
6452
+ const calls = request.context.messages.findLast((message) => message.role === "assistant")?.content.flatMap((block) => block.type === "toolCall" ? [block.id] : []) ?? [];
6453
+ if (calls.length !== this.pendingTools.length || !this.pendingTools.every((id) => calls.includes(id) && request.context.messages.some((message) => message.role === "toolResult" && message.toolCallId === id))) return false;
6454
+ if (this.key !== requestKey(request) || this.deliveredHistory.key !== fingerprint(request.context.messages.slice(0, this.deliveredHistory.length))) {
6455
+ this.abort();
6456
+ return false;
6457
+ }
6458
+ this.pendingTools = [];
6459
+ return true;
6460
+ }
6461
+ park(message) {
6462
+ this.pendingTools = message.content.flatMap((block) => block.type === "toolCall" ? [block.id] : []);
6463
+ for (const id of this.pendingTools) this.toolSlot(id);
6464
+ this.reportedUsage.input += message.usage.input;
6465
+ this.reportedUsage.output += message.usage.output;
6466
+ this.reportedUsage.cacheRead += message.usage.cacheRead;
6467
+ this.reportedUsage.cacheWrite += message.usage.cacheWrite;
6468
+ this.setDeadline("tools");
6469
+ }
6470
+ setDeadline(phase) {
6471
+ if (this.deadline) clearTimeout(this.deadline);
6472
+ if (this.controller.signal.aborted) return;
6473
+ this.deadline = setTimeout(() => {
6474
+ const err = /* @__PURE__ */ new Error(`Claude seat SDK timed out waiting for ${phase}`);
6475
+ err.name = "TimeoutError";
6476
+ log$4.error({
6477
+ err,
6478
+ phase
6479
+ }, "Claude seat SDK wait deadline exceeded");
6480
+ this.abort(err);
6481
+ }, this.timeouts[phase]);
6482
+ this.deadline.unref();
6483
+ }
6484
+ bindSignal(signal) {
6485
+ if (!signal || this.signals.has(signal)) return;
6486
+ const abort = () => this.abort();
6487
+ this.signals.set(signal, abort);
6488
+ signal.addEventListener("abort", abort, { once: true });
6489
+ if (signal.aborted) abort();
6490
+ }
6491
+ toolSlot(id) {
6492
+ let slot = this.tools.get(id);
6493
+ if (!slot) {
6494
+ slot = {
6495
+ result: null,
6496
+ resolve: null
6497
+ };
6498
+ this.tools.set(id, slot);
6499
+ }
6500
+ return slot;
6501
+ }
6502
+ async deliverResults(context) {
6503
+ this.deliveredHistory = {
6504
+ length: context.messages.length,
6505
+ key: fingerprint(context.messages)
6506
+ };
6507
+ const occurrences = /* @__PURE__ */ new Map();
6508
+ for (const message of context.messages) {
6509
+ if (message.role !== "user") continue;
6510
+ const key = fingerprint(message);
6511
+ const count = (occurrences.get(key) ?? 0) + 1;
6512
+ occurrences.set(key, count);
6513
+ if (count <= (this.deliveredUsers.get(key) ?? 0)) continue;
6514
+ this.setDeadline("input");
6515
+ await this.input.send({
6516
+ type: "user",
6517
+ message: {
6518
+ role: "user",
6519
+ content: userContent(message.content)
6520
+ },
6521
+ parent_tool_use_id: null,
6522
+ priority: "next"
6523
+ });
6524
+ this.controller.signal.throwIfAborted();
6525
+ this.deliveredUsers.set(key, count);
6526
+ }
6527
+ this.setDeadline("message");
6528
+ for (const message of context.messages) {
6529
+ if (message.role !== "toolResult") continue;
6530
+ const slot = this.tools.get(message.toolCallId);
6531
+ if (!slot || slot.result) continue;
6532
+ slot.result = {
6533
+ content: message.content,
6534
+ isError: message.isError
6535
+ };
6536
+ slot.resolve?.(slot.result);
6537
+ slot.resolve = null;
6538
+ }
6539
+ }
6540
+ abort(reason = null) {
6541
+ if (this.deadline) clearTimeout(this.deadline);
6542
+ this.deadline = null;
6543
+ this.terminateChild?.();
6544
+ this.controller.abort(reason ?? void 0);
6545
+ this.input.close();
6546
+ for (const [signal, abort] of this.signals) signal.removeEventListener("abort", abort);
6547
+ this.signals.clear();
6548
+ for (const slot of this.tools.values()) {
6549
+ slot.resolve?.({
6550
+ content: [{
6551
+ type: "text",
6552
+ text: "run aborted"
6553
+ }],
6554
+ isError: true
6555
+ });
6556
+ slot.resolve = null;
6557
+ }
6558
+ this.wake?.();
6559
+ }
6560
+ async next() {
6561
+ while (true) {
6562
+ this.controller.signal.throwIfAborted();
6563
+ const message = this.messages.shift();
6564
+ if (message) return message;
6565
+ if (this.failure) throw this.failure;
6566
+ if (this.closed) throw new Error("Claude Code query closed without a result");
6567
+ this.setDeadline("message");
6568
+ await new Promise((resolve) => {
6569
+ this.wake = resolve;
6570
+ });
6571
+ this.wake = null;
6572
+ }
6573
+ }
6574
+ async run({ sessionId, cwd }) {
6575
+ const { model, context, options } = this.request;
6576
+ let configDir = null;
6577
+ try {
6578
+ if (!options.apiKey) throw new Error("Claude seat SDK requires a seat credential sentinel");
6579
+ this.controller.signal.throwIfAborted();
6580
+ configDir = await mkdtemp(path.join(os.tmpdir(), "claude-seat-sdk-"));
6581
+ this.controller.signal.throwIfAborted();
6582
+ const bindings = toolBindings(context);
6583
+ const servers = /* @__PURE__ */ new Map();
6584
+ for (const { definition, serverName, toolName } of bindings) {
6585
+ const schema = z.fromJSONSchema(z.record(z.string(), z.json()).parse(definition.parameters));
6586
+ if (!(schema instanceof z.ZodObject)) throw new Error(`Tool ${definition.name} must have an object schema`);
6587
+ const tool$1 = tool(toolName, definition.description, schema.shape, async (_args, extra) => {
6588
+ const parsed = toolMetadata.safeParse(extra);
6589
+ if (!parsed.success) throw new Error("Claude SDK MCP request is missing its tool-use ID");
6590
+ this.controller.signal.throwIfAborted();
6591
+ const slot = this.toolSlot(parsed.data._meta["claudecode/toolUseId"]);
6592
+ if (slot.result) return slot.result;
6593
+ return new Promise((resolve) => {
6594
+ slot.resolve = resolve;
6595
+ });
6596
+ });
6597
+ const serverTools = servers.get(serverName) ?? [];
6598
+ serverTools.push(tool$1);
6599
+ servers.set(serverName, serverTools);
6600
+ }
6601
+ const history = await prepareSeatHistory({
6602
+ context,
6603
+ configDir,
6604
+ cwd,
6605
+ model,
6606
+ toolName: (name) => {
6607
+ const binding = bindings.find((candidate) => candidate.definition.name === name);
6608
+ if (binding) return binding.sdkName;
6609
+ if (name.startsWith("mcp__")) return name;
6610
+ return `mcp__${bindings.find((candidate) => !candidate.definition.name.startsWith("mcp__"))?.serverName ?? PI_TOOLS_SERVER}__${name}`;
6611
+ }
6612
+ });
6613
+ const { maxTokens, ...reasoning } = seatReasoningOptions(this.request);
6614
+ const contextManagement = seatContextManagement({
6615
+ contextManagement: this.request.contextManagement,
6616
+ toolName: (name) => bindings.find((binding) => binding.definition.name === name)?.sdkName ?? name
6617
+ });
6618
+ const sdkQuery = query({
6619
+ prompt: this.input.messages({
6620
+ type: "user",
6621
+ message: {
6622
+ role: "user",
6623
+ content: history.prompt
6624
+ },
6625
+ parent_tool_use_id: null
6626
+ }),
6627
+ options: {
6628
+ model: model.id,
6629
+ ...reasoning,
6630
+ ...history.resume ? { resume: history.resume } : {},
6631
+ verbatimPrompts: true,
6632
+ systemPrompt: {
6633
+ type: "preset",
6634
+ preset: "claude_code",
6635
+ append: context.systemPrompt ?? ""
6636
+ },
6637
+ tools: [],
6638
+ mcpServers: Object.fromEntries([...servers].map(([name, tools]) => [name, createSdkMcpServer({
6639
+ name,
6640
+ tools,
6641
+ timeout: this.timeouts.tools + this.timeouts.message + this.timeouts.input
6642
+ })])),
6643
+ allowedTools: bindings.map((binding) => binding.sdkName),
6644
+ permissionMode: "default",
6645
+ settingSources: [],
6646
+ cwd,
6647
+ includePartialMessages: true,
6648
+ persistSession: false,
6649
+ abortController: this.controller,
6650
+ spawnClaudeCodeProcess: ({ command, args, cwd, env }) => {
6651
+ const child = spawn(command, args, {
6652
+ cwd,
6653
+ env,
6654
+ stdio: [
6655
+ "pipe",
6656
+ "pipe",
6657
+ "inherit"
6658
+ ]
6659
+ });
6660
+ const abort = () => {
6661
+ child.kill("SIGKILL");
6662
+ };
6663
+ this.terminateChild = abort;
6664
+ this.controller.signal.addEventListener("abort", abort, { once: true });
6665
+ child.once("exit", () => {
6666
+ this.terminateChild = null;
6667
+ this.controller.signal.removeEventListener("abort", abort);
6668
+ });
6669
+ if (this.controller.signal.aborted) abort();
6670
+ return child;
6671
+ },
6672
+ env: buildStdioEnv({
6673
+ ANTHROPIC_AUTH_TOKEN: options.apiKey,
6674
+ ANTHROPIC_API_KEY: "",
6675
+ ANTHROPIC_BASE_URL: model.baseUrl,
6676
+ ANTHROPIC_CUSTOM_HEADERS: platformHeaders({
6677
+ model,
6678
+ options,
6679
+ sessionId
6680
+ }),
6681
+ CLAUDE_CONFIG_DIR: configDir,
6682
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
6683
+ CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(maxTokens),
6684
+ CLAUDE_CODE_EXTRA_BODY: JSON.stringify(contextManagement ? { context_management: contextManagement } : {})
6685
+ })
6686
+ }
6687
+ });
6688
+ try {
6689
+ for await (const message of sdkQuery) {
6690
+ this.messages.push(message);
6691
+ this.wake?.();
6692
+ if (message.type === "result") break;
6693
+ }
6694
+ } finally {
6695
+ sdkQuery.close();
6696
+ }
6697
+ } catch (err) {
6698
+ this.failure = err;
6699
+ if (!this.controller.signal.aborted) log$4.error({
6700
+ err,
6701
+ sessionId
6702
+ }, "Claude seat SDK query failed");
6703
+ } finally {
6704
+ this.closed = true;
6705
+ if (this.deadline) clearTimeout(this.deadline);
6706
+ this.deadline = null;
6707
+ this.input.close();
6708
+ this.wake?.();
6709
+ if (configDir) try {
6710
+ await rm(configDir, {
6711
+ recursive: true,
6712
+ force: true
6713
+ });
6714
+ } catch (err) {
6715
+ log$4.error({
6716
+ err,
6717
+ sessionId
6718
+ }, "Could not remove Claude seat SDK config");
6719
+ }
6720
+ }
6721
+ }
6722
+ };
6723
+ //#endregion
6724
+ //#region src/extensions/claude-seat-response.ts
6725
+ const toolInput = z.record(z.string(), z.unknown());
6726
+ function piToolName(name, context) {
6727
+ const binding = toolBindings(context).find((binding) => binding.sdkName === name);
6728
+ if (!binding) throw new Error(`Claude requested a tool outside the Pi bridge: ${name}`);
6729
+ return binding.definition.name;
6730
+ }
6731
+ /** Translates SDK block messages and raw deltas into one Pi model response. */
6732
+ var ModelResponse = class {
6733
+ message;
6734
+ live = /* @__PURE__ */ new Map();
6735
+ streamed = /* @__PURE__ */ new Set();
6736
+ canonicalIndex = 0;
6737
+ stopReason = null;
6738
+ constructor(model, context, events) {
6739
+ this.context = context;
6740
+ this.events = events;
6741
+ this.message = {
6742
+ role: "assistant",
6743
+ content: [],
6744
+ api: model.api,
6745
+ provider: "anthropic",
6746
+ model: model.id,
6747
+ usage: mapUsage({}),
6748
+ stopReason: "stop",
6749
+ timestamp: Date.now()
6750
+ };
6751
+ events.push({
6752
+ type: "start",
6753
+ partial: this.message
6754
+ });
6755
+ }
6756
+ startBlock(block, index) {
6757
+ this.events.push({
6758
+ type: block.type === "toolCall" ? "toolcall_start" : `${block.type}_start`,
6759
+ contentIndex: index,
6760
+ partial: this.message
6761
+ });
6762
+ }
6763
+ endBlock(block, index) {
6764
+ const event = {
6765
+ contentIndex: index,
6766
+ partial: this.message
6767
+ };
6768
+ switch (block.type) {
6769
+ case "text":
6770
+ this.events.push({
6771
+ ...event,
6772
+ type: "text_end",
6773
+ content: block.text
6774
+ });
6775
+ break;
6776
+ case "thinking":
6777
+ this.events.push({
6778
+ ...event,
6779
+ type: "thinking_end",
6780
+ content: block.thinking
6781
+ });
6782
+ break;
6783
+ case "toolCall":
6784
+ this.events.push({
6785
+ ...event,
6786
+ type: "toolcall_end",
6787
+ toolCall: block
6788
+ });
6789
+ break;
6790
+ default: throw new Error(`Unsupported block: ${String(block)}`);
6791
+ }
6792
+ }
6793
+ emitBlock(block, index) {
6794
+ this.startBlock(block, index);
6795
+ this.events.push({
6796
+ type: block.type === "toolCall" ? "toolcall_delta" : `${block.type}_delta`,
6797
+ contentIndex: index,
6798
+ delta: block.type === "toolCall" ? JSON.stringify(block.arguments) : block.type === "text" ? block.text : block.thinking,
6799
+ partial: this.message
6800
+ });
6801
+ this.endBlock(block, index);
6802
+ }
6803
+ assistant(message) {
6804
+ if (message.parent_tool_use_id) return;
6805
+ if (message.error) throw new Error(`Claude SDK assistant error: ${message.error}`);
6806
+ for (const sdkBlock of message.message.content) {
6807
+ let block;
6808
+ switch (sdkBlock.type) {
6809
+ case "text":
6810
+ block = {
6811
+ type: "text",
6812
+ text: sdkBlock.text
6813
+ };
6814
+ break;
6815
+ case "thinking":
6816
+ block = {
6817
+ type: "thinking",
6818
+ thinking: sdkBlock.thinking,
6819
+ thinkingSignature: sdkBlock.signature
6820
+ };
6821
+ break;
6822
+ case "tool_use":
6823
+ block = {
6824
+ type: "toolCall",
6825
+ id: sdkBlock.id,
6826
+ name: piToolName(sdkBlock.name, this.context),
6827
+ arguments: toolInput.parse(sdkBlock.input)
6828
+ };
6829
+ break;
6830
+ default: continue;
6831
+ }
6832
+ const index = this.canonicalIndex++;
6833
+ this.message.content[index] = block;
6834
+ if (!this.streamed.has(index)) this.emitBlock(block, index);
6835
+ }
6836
+ this.message.usage = mapUsage(message.message.usage);
6837
+ this.stopReason = message.message.stop_reason;
6838
+ }
6839
+ partial(message) {
6840
+ if (message.parent_tool_use_id) return false;
6841
+ const event = message.event;
6842
+ const partial = this.message;
6843
+ switch (event.type) {
6844
+ case "message_start":
6845
+ this.message.usage = mapUsage(event.message.usage);
6846
+ break;
6847
+ case "content_block_start": {
6848
+ const index = partial.content.length;
6849
+ const sdkBlock = event.content_block;
6850
+ let block;
6851
+ if (sdkBlock.type === "text") block = {
6852
+ type: "text",
6853
+ text: sdkBlock.text
6854
+ };
6855
+ else if (sdkBlock.type === "thinking") block = {
6856
+ type: "thinking",
6857
+ thinking: sdkBlock.thinking
6858
+ };
6859
+ else if (sdkBlock.type === "tool_use") block = {
6860
+ type: "toolCall",
6861
+ id: sdkBlock.id,
6862
+ name: piToolName(sdkBlock.name, this.context),
6863
+ arguments: {}
6864
+ };
6865
+ else break;
6866
+ this.live.set(event.index, {
6867
+ index,
6868
+ json: ""
6869
+ });
6870
+ this.streamed.add(index);
6871
+ partial.content[index] = block;
6872
+ this.startBlock(block, index);
6873
+ break;
6874
+ }
6875
+ case "content_block_delta": {
6876
+ const live = this.live.get(event.index);
6877
+ if (!live) break;
6878
+ const block = partial.content[live.index];
6879
+ const delta = event.delta;
6880
+ if (delta.type === "text_delta" && block?.type === "text") {
6881
+ block.text += delta.text;
6882
+ this.events.push({
6883
+ type: "text_delta",
6884
+ contentIndex: live.index,
6885
+ delta: delta.text,
6886
+ partial
6887
+ });
6888
+ } else if (delta.type === "thinking_delta" && block?.type === "thinking") {
6889
+ block.thinking += delta.thinking;
6890
+ this.events.push({
6891
+ type: "thinking_delta",
6892
+ contentIndex: live.index,
6893
+ delta: delta.thinking,
6894
+ partial
6895
+ });
6896
+ } else if (delta.type === "input_json_delta" && block?.type === "toolCall") {
6897
+ live.json += delta.partial_json;
6898
+ this.events.push({
6899
+ type: "toolcall_delta",
6900
+ contentIndex: live.index,
6901
+ delta: delta.partial_json,
6902
+ partial
6903
+ });
6904
+ }
6905
+ break;
6906
+ }
6907
+ case "content_block_stop": {
6908
+ const live = this.live.get(event.index);
6909
+ if (!live) break;
6910
+ const block = partial.content[live.index];
6911
+ if (block?.type === "toolCall" && live.json) block.arguments = toolInput.parse(JSON.parse(live.json));
6912
+ if (block) this.endBlock(block, live.index);
6913
+ this.live.delete(event.index);
6914
+ break;
6915
+ }
6916
+ case "message_delta":
6917
+ this.stopReason = event.delta.stop_reason;
6918
+ this.message.usage = mapUsage({
6919
+ input_tokens: event.usage.input_tokens ?? this.message.usage.input,
6920
+ output_tokens: event.usage.output_tokens,
6921
+ cache_read_input_tokens: event.usage.cache_read_input_tokens ?? this.message.usage.cacheRead,
6922
+ cache_creation_input_tokens: event.usage.cache_creation_input_tokens ?? this.message.usage.cacheWrite
6923
+ });
6924
+ break;
6925
+ case "message_stop": return true;
6926
+ }
6927
+ return false;
6928
+ }
6929
+ done(reason) {
6930
+ const stopReason = this.stopReason === "max_tokens" ? "length" : reason;
6931
+ this.message.stopReason = stopReason;
6932
+ this.events.push({
6933
+ type: "done",
6934
+ reason: stopReason,
6935
+ message: this.message
6936
+ });
6937
+ }
6938
+ };
6939
+ //#endregion
6940
+ //#region src/extensions/claude-seat-sdk.ts
6941
+ /**
6942
+ * Adapts a Claude Code query to Pi's model-step interface. A query stays alive
6943
+ * across tool rounds, but Pi executes every tool. Results are forwarded from
6944
+ * the next model context, after all Pi tool_result extensions have run.
6945
+ * Each user turn resumes a private structured snapshot of the Pi transcript.
6946
+ * Initially adapted from elidickinson/pi-claude-bridge (MIT).
6947
+ */
6948
+ const log$3 = logger.child({ module: "claude-seat-sdk" });
6949
+ /** Session-scoped adapter; only the flagged provider dispatches here. */
6950
+ var SeatSdkBridge = class {
6951
+ queries = /* @__PURE__ */ new Set();
6952
+ controller = new AbortController();
6953
+ constructor(deps, timeouts = SEAT_QUERY_TIMEOUTS) {
6954
+ this.deps = deps;
6955
+ this.timeouts = timeouts;
6956
+ }
6957
+ abort() {
6958
+ this.controller.abort();
6959
+ this.controller = new AbortController();
6960
+ for (const query of this.queries) query.abort();
6961
+ }
6962
+ stream(model, context, options = {}) {
6963
+ const events = createAssistantMessageEventStream();
6964
+ const response = new ModelResponse(model, context, events);
6965
+ const signal = AbortSignal.any([this.controller.signal, ...options.signal ? [options.signal] : []]);
6966
+ (async () => {
6967
+ let seatQuery = null;
6968
+ let keepQuery = false;
6969
+ try {
6970
+ const preparation = new AbortController();
6971
+ const request = await Promise.race([prepareSeatRequest({
6972
+ model,
6973
+ context,
6974
+ options
6975
+ }), setTimeout$1(this.timeouts.input, void 0, {
6976
+ signal: AbortSignal.any([signal, preparation.signal]),
6977
+ ref: false
6978
+ }).then(() => {
6979
+ const err = /* @__PURE__ */ new Error("Claude seat SDK timed out preparing request");
6980
+ err.name = "TimeoutError";
6981
+ throw err;
6982
+ })]).finally(() => preparation.abort());
6983
+ signal.throwIfAborted();
6984
+ const continuation = [...this.queries].find((query) => query.claimContinuation(request));
6985
+ const query = continuation ?? new SeatQuery(request, this.timeouts);
6986
+ seatQuery = query;
6987
+ query.bindSignal(signal);
6988
+ if (!continuation) {
6989
+ this.queries.add(query);
6990
+ query.run(this.deps).finally(() => this.queries.delete(query));
6991
+ }
6992
+ await seatQuery.deliverResults(context);
6993
+ while (true) {
6994
+ const message = await seatQuery.next();
6995
+ if (message.type === "assistant") response.assistant(message);
6996
+ else if (message.type === "stream_event") {
6997
+ if (response.partial(message) && response.message.content.some((block) => block.type === "toolCall")) {
6998
+ seatQuery.park(response.message);
6999
+ keepQuery = true;
7000
+ response.done("toolUse");
7001
+ return;
7002
+ }
7003
+ } else if (message.type === "result") {
7004
+ if (message.is_error) throw new Error("errors" in message ? message.errors.join("; ") : message.result);
7005
+ const usage = mapUsage(message.usage);
7006
+ response.message.usage = remainingSeatUsage({
7007
+ total: usage,
7008
+ reported: seatQuery.reportedUsage,
7009
+ log: log$3,
7010
+ sessionId: this.deps.sessionId,
7011
+ model: model.id
7012
+ });
7013
+ response.done("stop");
7014
+ return;
7015
+ } else if (message.type === "rate_limit_event") {
7016
+ const info = message.rate_limit_info;
7017
+ await options.onResponse?.({
7018
+ status: info.status === "rejected" ? 429 : 200,
7019
+ headers: {
7020
+ "anthropic-ratelimit-unified-status": info.status,
7021
+ ...info.resetsAt === void 0 ? {} : { "anthropic-ratelimit-unified-reset": String(info.resetsAt) },
7022
+ ...info.utilization === void 0 ? {} : { "anthropic-ratelimit-unified-overall-percent": String(Math.round(info.utilization * 100)) }
7023
+ }
7024
+ }, model);
7025
+ } else if (message.type === "system" && message.subtype === "api_retry" && message.error_status === 401) throw new Error("Claude seat authentication failed (401)");
7026
+ }
7027
+ } catch (err) {
7028
+ const reason = (seatQuery?.controller.signal.aborted || signal.aborted) && !(err instanceof Error && err.name === "TimeoutError") ? "aborted" : "error";
7029
+ if (reason === "error") log$3.error({
7030
+ err,
7031
+ sessionId: this.deps.sessionId
7032
+ }, "Claude seat SDK response failed");
7033
+ events.push({
7034
+ type: "error",
7035
+ reason,
7036
+ error: {
7037
+ ...response.message,
7038
+ stopReason: reason,
7039
+ errorMessage: err instanceof Error ? err.message : String(err)
7040
+ }
7041
+ });
7042
+ } finally {
7043
+ if (!keepQuery) seatQuery?.abort();
7044
+ }
7045
+ })();
7046
+ return events;
7047
+ }
7048
+ };
7049
+ function createClaudeSeatSdkExtension(deps) {
7050
+ return (pi) => {
7051
+ let bridge = null;
7052
+ pi.registerProvider("anthropic", {
7053
+ api: SEAT_SDK_API,
7054
+ streamSimple: (model, context, options) => {
7055
+ if (!bridge) throw new Error("Claude seat SDK session has not started");
7056
+ return bridge.stream(model, context, options);
7057
+ }
7058
+ });
7059
+ pi.on("session_start", async (_event, ctx) => {
7060
+ bridge?.abort();
7061
+ bridge = new SeatSdkBridge({
7062
+ ...deps,
7063
+ cwd: ctx.cwd
7064
+ });
7065
+ });
7066
+ pi.on("agent_end", async () => {
7067
+ bridge?.abort();
7068
+ });
7069
+ pi.on("session_before_switch", async () => {
7070
+ bridge?.abort();
7071
+ });
7072
+ pi.on("session_shutdown", async () => {
7073
+ bridge?.abort();
7074
+ });
7075
+ };
7076
+ }
7077
+ //#endregion
5824
7078
  //#region src/extensions/self-trace.ts
5825
7079
  const log$2 = logger.child({ module: "self-trace-extension" });
5826
7080
  /**
@@ -6333,6 +7587,7 @@ const all = [
6333
7587
  function platformExtensions({ sessionId, channelContext }) {
6334
7588
  return [
6335
7589
  providerApiCompatExtension,
7590
+ createClaudeSeatSdkExtension({ sessionId }),
6336
7591
  createPlatformExtensions({
6337
7592
  sessionId,
6338
7593
  channelContext