larkin 0.4.2 → 0.4.6

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 (35) hide show
  1. package/README.md +5 -1
  2. package/artifacts/release/THIRD_PARTY_NOTICES.txt +218 -228
  3. package/assets/readme/larkin-github-cover.png +0 -0
  4. package/dist/agent/agent-state-store.cjs +1 -2
  5. package/dist/agent/agent-state-store.mjs +1 -2
  6. package/dist/app/agent-config.mjs +73 -6
  7. package/dist/app/agent-readiness.cjs +22 -2
  8. package/dist/app/agent-readiness.mjs +20 -2
  9. package/dist/app/binary-entry.mjs +6 -0
  10. package/dist/app/cli.mjs +4 -0
  11. package/dist/app/run.mjs +8 -0
  12. package/dist/app/runtime-process.mjs +13 -1
  13. package/dist/dashboard/dashboard-view-model.mjs +12 -3
  14. package/dist/feishu/host-shell.cjs +33 -10
  15. package/dist/feishu/host-shell.mjs +34 -11
  16. package/dist/platform/config.cjs +406 -33
  17. package/dist/platform/config.mjs +405 -33
  18. package/dist/platform/process-boundary-trace.cjs +108 -0
  19. package/dist/platform/process-boundary-trace.mjs +102 -0
  20. package/dist/runtime/pi-artifact-provenance.cjs +147 -0
  21. package/dist/runtime/pi-artifact-provenance.mjs +139 -0
  22. package/dist/runtime/pi-compaction-recovery.cjs +120 -40
  23. package/dist/runtime/pi-compaction-recovery.mjs +117 -40
  24. package/dist/runtime/pi-model-catalog.cjs +2 -2
  25. package/dist/runtime/pi-model-catalog.mjs +2 -2
  26. package/dist/runtime/pi-profile-migration.cjs +556 -0
  27. package/dist/runtime/pi-profile-migration.mjs +515 -0
  28. package/dist/runtime/pi-provider-config.cjs +1 -1
  29. package/dist/runtime/pi-provider-config.mjs +1 -1
  30. package/dist/runtime/runtime-adapters.mjs +97 -11
  31. package/dist/runtime/runtime-host.cjs +162 -39
  32. package/dist/runtime/runtime-host.mjs +163 -40
  33. package/dist/runtime/runtime-readiness.cjs +6 -4
  34. package/dist/runtime/runtime-readiness.mjs +6 -4
  35. package/package.json +3 -3
@@ -8,7 +8,7 @@ import { buildStrictProviderErrorInput, classifyStrictProviderError } from "./pr
8
8
  import { classifyRuntimePrerequisite, providerAuthenticationFailureReadiness, RuntimePrerequisiteError, } from "./runtime-readiness.mjs";
9
9
  import { resolveOfficialLarkCli } from "../app/official-lark-cli.mjs";
10
10
  import { assertAgentWorkspaceBound, managedLarkCliEnv } from "../app/agent-lark-cli-workspace.mjs";
11
- import { PiCompactionBreaker, PiCompactionRecoveryMachine } from "./pi-compaction-recovery.mjs";
11
+ import { isPiNativeCompactionRequired, PiCompactionBreaker, PiCompactionRecoveryMachine, } from "./pi-compaction-recovery.mjs";
12
12
  export class RuntimeSessionResetError extends Error {
13
13
  code;
14
14
  pendingCount;
@@ -388,18 +388,22 @@ export function createRuntimeHost(options) {
388
388
  };
389
389
  const resultFor = (agent, record, result) => {
390
390
  if (result.status === "accepted") {
391
- delete record.reason;
391
+ if (record.errorCategory !== "context_window") {
392
+ delete record.reason;
393
+ delete record.errorCategory;
394
+ }
392
395
  delete record.retryable;
393
- delete record.errorCategory;
394
396
  const finalRecord = setRecord(agent, record, "accepted");
395
397
  if (finalRecord.status !== "consumed")
396
398
  emit({ type: "delivery", agentId: agent.config.agentId,
397
399
  deliveryId: record.deliveryId, messageId: record.messageId, status: "accepted" });
398
400
  return { status: "accepted", deliveryId: record.deliveryId };
399
401
  }
400
- delete record.errorCategory;
402
+ if (record.errorCategory !== "context_window")
403
+ delete record.errorCategory;
401
404
  const retryable = result.status === "deferred" || result.retryable;
402
- record.reason = result.reason;
405
+ if (record.errorCategory !== "context_window")
406
+ record.reason = result.reason;
403
407
  record.retryable = retryable;
404
408
  const finalRecord = setRecord(agent, record, retryable ? "pending" : "error");
405
409
  if (finalRecord.status === "consumed")
@@ -411,6 +415,22 @@ export function createRuntimeHost(options) {
411
415
  : { status: "error", deliveryId: record.deliveryId, reason: result.reason, retryable: false };
412
416
  };
413
417
  const submit = async (agent, record, busy) => {
418
+ const proactive = agent.piProactiveCompaction
419
+ && agent.piProactiveCompactionGeneration === agent.generation
420
+ && agent.piProactiveCompactionSession === agent.session
421
+ ? agent.piProactiveCompaction : null;
422
+ if (proactive && await proactive === "failed") {
423
+ return { status: "deferred", deliveryId: record.deliveryId, reason: "Pi proactive compaction failed for the current session generation" };
424
+ }
425
+ if (agent.adapter.id === "pi" && agent.piProactiveCompactionFailedGeneration === agent.generation) {
426
+ return { status: "deferred", deliveryId: record.deliveryId, reason: "Pi proactive compaction failed for the current session generation" };
427
+ }
428
+ if (agent.adapter.id === "pi" && !busy && !agent.busy && !agent.turnInProgress && agent.session) {
429
+ const idleGate = proactivelyCompactPiAtIdle(agent, agent.session);
430
+ if (idleGate && await idleGate === "failed") {
431
+ return { status: "deferred", deliveryId: record.deliveryId, reason: "Pi proactive compaction failed for the current session generation" };
432
+ }
433
+ }
414
434
  agent.submitting = true;
415
435
  if (!busy)
416
436
  agent.busy = true; // Reserve the turn before prompt() can yield.
@@ -450,9 +470,11 @@ export function createRuntimeHost(options) {
450
470
  if (!busy)
451
471
  agent.busy = false;
452
472
  const reason = error instanceof Error ? error.message : String(error);
453
- record.reason = reason;
473
+ if (record.errorCategory !== "context_window") {
474
+ record.reason = reason;
475
+ delete record.errorCategory;
476
+ }
454
477
  record.retryable = true;
455
- delete record.errorCategory;
456
478
  setRecord(agent, record, "pending");
457
479
  emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: record.deliveryId, messageId: record.messageId, status: "deferred", reason });
458
480
  return { status: "deferred", deliveryId: record.deliveryId, reason };
@@ -466,16 +488,41 @@ export function createRuntimeHost(options) {
466
488
  }
467
489
  };
468
490
  const retryPending = async (agent) => {
469
- reconcileExternalConsumption(agent);
470
- if (agent.submitting) {
471
- agent.retryAfterSubmit = true;
472
- return;
491
+ if (agent.retryPendingInFlight)
492
+ return agent.retryPendingInFlight;
493
+ const run = (async () => {
494
+ reconcileExternalConsumption(agent);
495
+ const proactive = agent.piProactiveCompaction
496
+ && agent.piProactiveCompactionGeneration === agent.generation
497
+ && agent.piProactiveCompactionSession === agent.session
498
+ ? agent.piProactiveCompaction : null;
499
+ if (proactive && await proactive === "failed")
500
+ return;
501
+ if (agent.submitting || agent.starting) {
502
+ agent.retryAfterSubmit = true;
503
+ return;
504
+ }
505
+ if (agent.adapter.id === "pi" && agent.piProactiveCompactionFailedGeneration === agent.generation)
506
+ return;
507
+ if (agent.busy || agent.turnInProgress || !agent.session)
508
+ return;
509
+ if (agent.adapter.id === "pi") {
510
+ const idleGate = proactivelyCompactPiAtIdle(agent, agent.session);
511
+ if (idleGate && await idleGate === "failed")
512
+ return;
513
+ }
514
+ const record = [...agent.records.values()].find((candidate) => candidate.status === "pending" || candidate.status === "submitting");
515
+ if (record)
516
+ await submit(agent, record, false);
517
+ })();
518
+ agent.retryPendingInFlight = run;
519
+ try {
520
+ await run;
521
+ }
522
+ finally {
523
+ if (agent.retryPendingInFlight === run)
524
+ agent.retryPendingInFlight = null;
473
525
  }
474
- if (agent.busy || agent.turnInProgress || !agent.session)
475
- return;
476
- const record = [...agent.records.values()].find((candidate) => candidate.status === "pending" || candidate.status === "submitting");
477
- if (record)
478
- await submit(agent, record, false);
479
526
  };
480
527
  const scheduleRecreate = (agent, reason) => {
481
528
  if (agent.stopped || agent.retryTimer || agent.starting || agent.session)
@@ -493,8 +540,12 @@ export function createRuntimeHost(options) {
493
540
  agent.retryTimer = null;
494
541
  if (agent.stopped)
495
542
  return;
496
- void ensureSession(agent).then(() => {
497
- return retryPending(agent);
543
+ void ensureSession(agent).then(async () => {
544
+ const session = agent.session;
545
+ const proactive = session ? proactivelyCompactPiAtIdle(agent, session) : null;
546
+ if (proactive && await proactive === "failed")
547
+ return;
548
+ await retryPending(agent);
498
549
  }).catch((error) => {
499
550
  const readiness = error instanceof RuntimePrerequisiteError ? error.readiness
500
551
  : classifyRuntimePrerequisite(agent.adapter.id, error);
@@ -605,7 +656,6 @@ export function createRuntimeHost(options) {
605
656
  machine.retrySubmitted();
606
657
  record.status = "pending";
607
658
  record.retryable = true;
608
- record.reason = "Pi manual compaction succeeded; retrying the same delivery input";
609
659
  record.updatedAt = now();
610
660
  persist(agent);
611
661
  queueMicrotask(() => { if (!agent.stopped)
@@ -614,14 +664,19 @@ export function createRuntimeHost(options) {
614
664
  else if (action === "fresh_session_fallback") {
615
665
  agent.busy = false;
616
666
  agent.turnInProgress = false;
617
- if (record.status !== "error") {
618
- record.reason = machine.recordSnapshot.fallbackReason || "Pi compaction recovery fallback";
667
+ const recoveryReason = machine.recordSnapshot.fallbackReason || "Pi compaction recovery fallback";
668
+ // Delivery proof must remain the exact provider-classified context error.
669
+ // Generic Pi recovery text belongs only in the breaker and logs.
670
+ if (record.errorCategory === "context_window" && typeof record.reason === "string") {
619
671
  record.retryable = false;
620
- record.errorCategory = "context_window";
621
672
  setRecord(agent, record, "error");
673
+ if (!agent.compactionRecoveryInFlight.has(key)) {
674
+ agent.compactionRecoveryInFlight.add(key);
675
+ void recoverContextOverflowInternal(agent.config.agentId, key, recoveryReason, key)
676
+ .catch((error) => log("Pi context fallback failed", String(error)))
677
+ .finally(() => agent.compactionRecoveryInFlight.delete(key));
678
+ }
622
679
  }
623
- void recoverContextOverflowInternal(agent.config.agentId, key, machine.recordSnapshot.fallbackReason || "Pi compaction recovery fallback", key)
624
- .catch((error) => log("Pi context fallback failed", String(error)));
625
680
  }
626
681
  },
627
682
  });
@@ -656,6 +711,8 @@ export function createRuntimeHost(options) {
656
711
  });
657
712
  };
658
713
  const recoverStalePiCompaction = async (agent) => {
714
+ if (!agent.stateStore)
715
+ return;
659
716
  const breaker = agent.compactionBreaker;
660
717
  if (!breaker)
661
718
  return;
@@ -670,14 +727,59 @@ export function createRuntimeHost(options) {
670
727
  || candidate.input.inputId === stale.inputId);
671
728
  if (!record)
672
729
  return;
673
- record.reason = stale.fallbackReason || "Pi compaction recovery resumed after restart";
730
+ const recoveryReason = stale.fallbackReason || "Pi compaction recovery resumed after restart";
731
+ if (record.errorCategory !== "context_window" || typeof record.reason !== "string")
732
+ return;
674
733
  record.retryable = false;
675
- record.errorCategory = "context_window";
676
734
  setRecord(agent, record, "error");
677
- breaker.forceFallback(stale.key, record.reason);
735
+ breaker.forceFallback(stale.key, recoveryReason);
678
736
  agent.busy = false;
679
737
  agent.turnInProgress = false;
680
- await recoverContextOverflowInternal(agent.config.agentId, stale.key, record.reason, stale.key);
738
+ await recoverContextOverflowInternal(agent.config.agentId, stale.key, recoveryReason, stale.key);
739
+ };
740
+ const proactivelyCompactPiAtIdle = (agent, session) => {
741
+ if (agent.adapter.id !== "pi" || session.piPolicyManaged === false
742
+ || !session.getContextUsage || !session.compact)
743
+ return null;
744
+ if (agent.session !== session || agent.stopped || agent.busy || agent.turnInProgress || agent.submitting || agent.starting
745
+ || agent.compactionRecoveryInFlight.size > 0
746
+ || agent.compactionMachines.size > 0 || agent.piOverflowCompactionFailed.size > 0)
747
+ return null;
748
+ if (agent.piProactiveCompactionFailedGeneration === agent.generation)
749
+ return null;
750
+ const generation = agent.generation;
751
+ if (agent.piProactiveCompaction && agent.piProactiveCompactionGeneration === generation
752
+ && agent.piProactiveCompactionSession === session)
753
+ return agent.piProactiveCompaction;
754
+ const task = (async () => {
755
+ const before = await session.getContextUsage();
756
+ if (!before || agent.session !== session || agent.stopped || agent.busy || agent.turnInProgress || agent.submitting
757
+ || agent.compactionRecoveryInFlight.size > 0 || agent.compactionMachines.size > 0
758
+ || agent.piOverflowCompactionFailed.size > 0)
759
+ return "noop";
760
+ if (!isPiNativeCompactionRequired(before.tokens, before.contextWindow))
761
+ return "noop";
762
+ await session.compact();
763
+ const after = await session.getContextUsage();
764
+ if (!after || after.contextWindow !== before.contextWindow || after.tokens >= before.tokens) {
765
+ throw new Error("Pi proactive compaction did not reduce the verified context usage");
766
+ }
767
+ return "succeeded";
768
+ })();
769
+ const tracked = task.catch((error) => {
770
+ agent.piProactiveCompactionFailedGeneration = generation;
771
+ emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
772
+ error: "Pi proactive compaction failed; the current session generation is degraded" });
773
+ log("Pi proactive compaction failed", String(error));
774
+ return "failed";
775
+ }).finally(() => {
776
+ if (agent.piProactiveCompaction === tracked)
777
+ agent.piProactiveCompaction = null;
778
+ });
779
+ agent.piProactiveCompactionGeneration = generation;
780
+ agent.piProactiveCompactionSession = session;
781
+ agent.piProactiveCompaction = tracked;
782
+ return tracked;
681
783
  };
682
784
  const observe = (agent, session, event) => {
683
785
  if (agent.session !== session)
@@ -719,7 +821,6 @@ export function createRuntimeHost(options) {
719
821
  agent.config.sessionId = event.sessionId;
720
822
  emit({ type: "session", agentId: agent.config.agentId, runtime: agent.adapter.id, sessionId: event.sessionId, launchId: agent.launchId,
721
823
  ...(event.model ? { model: event.model } : {}), ...(event.reasoningEffort ? { reasoningEffort: event.reasoningEffort } : {}) });
722
- void retryPending(agent);
723
824
  }
724
825
  else if (event.type === "turn-start") {
725
826
  agent.busy = true;
@@ -755,7 +856,11 @@ export function createRuntimeHost(options) {
755
856
  };
756
857
  emit({ type: "agent-status", agentId: agent.config.agentId, status: "active", readiness: agent.readiness });
757
858
  }
758
- void retryPending(agent);
859
+ const proactive = proactivelyCompactPiAtIdle(agent, session);
860
+ void (proactive ?? Promise.resolve("noop")).then((outcome) => {
861
+ if (outcome !== "failed")
862
+ return retryPending(agent);
863
+ });
759
864
  }
760
865
  else if (event.type === "activity") {
761
866
  if (agent.turnInProgress && event.activity !== "internal")
@@ -781,8 +886,13 @@ export function createRuntimeHost(options) {
781
886
  if (agent.piOverflowCompactionFailed.has(record.input.inputId)) {
782
887
  agent.busy = false;
783
888
  agent.turnInProgress = false;
784
- void recoverContextOverflowInternal(agent.config.agentId, `${record.deliveryId}:${record.input.inputId}`, "native Pi overflow compaction failed", `${record.deliveryId}:${record.input.inputId}`)
785
- .catch((error) => log("Pi native compaction fallback failed", String(error)));
889
+ const recoveryKey = `${record.deliveryId}:${record.input.inputId}`;
890
+ if (!agent.compactionRecoveryInFlight.has(recoveryKey)) {
891
+ agent.compactionRecoveryInFlight.add(recoveryKey);
892
+ void recoverContextOverflowInternal(agent.config.agentId, recoveryKey, "native Pi overflow compaction failed", recoveryKey)
893
+ .catch((error) => log("Pi native compaction fallback failed", String(error)))
894
+ .finally(() => agent.compactionRecoveryInFlight.delete(recoveryKey));
895
+ }
786
896
  return;
787
897
  }
788
898
  // Pi emits this terminal error only after agent_settled; release the
@@ -967,7 +1077,6 @@ export function createRuntimeHost(options) {
967
1077
  continue;
968
1078
  candidate.status = "pending";
969
1079
  candidate.retryable = true;
970
- candidate.reason = "context-window recovery rearmed the retained Inbox delivery";
971
1080
  candidate.updatedAt = now();
972
1081
  }
973
1082
  };
@@ -1013,7 +1122,11 @@ export function createRuntimeHost(options) {
1013
1122
  }
1014
1123
  emit({ type: "agent-status", agentId, status: "active", readiness });
1015
1124
  await oldSession.close("Pi context fallback committed").catch((error) => log("previous Pi session close after fallback failed", String(error)));
1016
- void retryPending(agent);
1125
+ const proactive = proactivelyCompactPiAtIdle(agent, fresh);
1126
+ void (proactive ?? Promise.resolve("noop")).then((outcome) => {
1127
+ if (outcome !== "failed")
1128
+ return retryPending(agent);
1129
+ });
1017
1130
  return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
1018
1131
  runtimeReady: true, pendingCount: remainingPendingCount, rearmedCount,
1019
1132
  replayStatus: remainingPendingCount > 0 ? "pending" : "consumed", sessionId: fresh.sessionId };
@@ -1325,7 +1438,6 @@ export function createRuntimeHost(options) {
1325
1438
  continue;
1326
1439
  record.status = "pending";
1327
1440
  record.retryable = true;
1328
- record.reason = "context-window recovery rearmed the retained Inbox delivery";
1329
1441
  record.updatedAt = now();
1330
1442
  }
1331
1443
  });
@@ -1340,7 +1452,11 @@ export function createRuntimeHost(options) {
1340
1452
  durableRollback = null;
1341
1453
  await oldSession.close("context-window recovery committed")
1342
1454
  .catch((error) => log("previous runtime close after context-window recovery failed", String(error)));
1343
- void retryPending(agent);
1455
+ const proactive = proactivelyCompactPiAtIdle(agent, fresh);
1456
+ void (proactive ?? Promise.resolve("noop")).then((outcome) => {
1457
+ if (outcome !== "failed")
1458
+ return retryPending(agent);
1459
+ });
1344
1460
  return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
1345
1461
  runtimeReady: true, pendingCount: rearmed.remainingPendingCount, rearmedCount: rearmed.rearmedCount,
1346
1462
  replayStatus: rearmed.remainingPendingCount > 0 ? "pending" : "consumed", sessionId: fresh.sessionId };
@@ -1408,16 +1524,19 @@ export function createRuntimeHost(options) {
1408
1524
  : readDeliveryFile(config.stateDir ? path.join(config.stateDir, "runtime-deliveries.json") : null);
1409
1525
  const agent = { config, adapter: options.adapterFor(config.runtime), session: null,
1410
1526
  launchId: crypto.randomUUID(), busy: false, submitting: false, starting: null, retryAfterSubmit: false,
1527
+ retryPendingInFlight: null,
1411
1528
  records: new Map(persisted.records.map((record) => [record.deliveryId, record])),
1412
1529
  byMessage: new Map(persisted.records.map((record) => [record.messageId, record.deliveryId])),
1413
1530
  generation: 0, poller: null, retryTimer: null, recreateAttempts: 0,
1414
1531
  stabilityTimer: null, recreateReason: null, stopped: false, disabledReason: null,
1415
1532
  configurationRecovery: null, stateStore,
1416
- compactionBreaker: config.runtime === "pi"
1533
+ compactionBreaker: config.runtime === "pi" && stateStore
1417
1534
  ? new PiCompactionBreaker(config.stateDir ?? path.join(config.workspaceDir, ".larkin", "agents", config.agentId), {
1418
- withLock: stateStore ? (operation) => stateStore.withInboxTransaction(operation) : undefined,
1535
+ withLock: (operation) => stateStore.withInboxTransaction(operation),
1419
1536
  }) : undefined,
1420
- compactionMachines: new Map(), piOverflowCompactionFailed: new Set(), readiness: null,
1537
+ compactionMachines: new Map(), compactionRecoveryInFlight: new Set(), piOverflowCompactionFailed: new Set(),
1538
+ piProactiveCompaction: null, piProactiveCompactionGeneration: null, piProactiveCompactionSession: null,
1539
+ piProactiveCompactionFailedGeneration: null, readiness: null,
1421
1540
  turnInProgress: false, turnHadFailure: false, turnHadAuthenticatedOutput: false, authFailureActive: false };
1422
1541
  const startupConsumed = [];
1423
1542
  const startupQuarantined = [];
@@ -1450,8 +1569,12 @@ export function createRuntimeHost(options) {
1450
1569
  try {
1451
1570
  await ensureSession(agent);
1452
1571
  await recoverStalePiCompaction(agent);
1572
+ const startupSession = agent.session;
1573
+ const proactive = startupSession ? proactivelyCompactPiAtIdle(agent, startupSession) : null;
1574
+ const proactiveOutcome = proactive ? await proactive : "noop";
1453
1575
  activeCount += 1;
1454
- await retryPending(agent);
1576
+ if (proactiveOutcome !== "failed")
1577
+ await retryPending(agent);
1455
1578
  }
1456
1579
  catch (error) {
1457
1580
  const reason = error instanceof Error ? error.message : String(error);
@@ -45,6 +45,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
45
45
  const node_path_1 = __importDefault(require("node:path"));
46
46
  const node_child_process_1 = require("node:child_process");
47
47
  const pi_provider_config_js_1 = require("./pi-provider-config.cjs");
48
+ const process_boundary_trace_js_1 = require("../platform/process-boundary-trace.cjs");
48
49
  function safeProviderLabel(value) {
49
50
  const provider = typeof value === "string" ? value.trim() : "";
50
51
  return /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(provider) ? provider : null;
@@ -122,8 +123,8 @@ function selectedCommand(options) {
122
123
  return options.env?.LARKIN_CODEX_COMMAND || process.env.LARKIN_CODEX_COMMAND || "codex";
123
124
  return options.env?.LARKIN_CLAUDE_COMMAND || process.env.LARKIN_CLAUDE_COMMAND || "claude";
124
125
  }
125
- function executableVersion(executable, env) {
126
- const result = (0, node_child_process_1.spawnSync)(executable, ["--version"], { env, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
126
+ function executableVersion(executable, env, commandArgs = []) {
127
+ const result = (0, node_child_process_1.spawnSync)(executable, [...commandArgs, "--version"], { env, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
127
128
  if (result.status !== 0)
128
129
  return undefined;
129
130
  return String(result.stdout || result.stderr || "").replace(/[\r\n]+/g, " ").trim().slice(0, 120) || undefined;
@@ -139,6 +140,7 @@ async function probeNativeRuntimeReadiness(options) {
139
140
  return { runtime: "pi", state: "ready", executable: process.execPath, version: `official-pi ${pi_provider_config_js_1.BUNDLED_PI_VERSION} (bundled)` };
140
141
  }
141
142
  catch (error) {
143
+ (0, process_boundary_trace_js_1.traceProcessBoundary)(env, "readiness:builtin-pi-failure", { configDir: env.LARKIN_CONFIG_DIR, agentId: options.agentId, targetDir: options.agentId && env.LARKIN_CONFIG_DIR ? (0, pi_provider_config_js_1.piAgentDirectory)(env.LARKIN_CONFIG_DIR, options.agentId) : undefined, error });
142
144
  return { runtime: "pi", state: "unauthenticated", reason: error instanceof Error ? error.message : String(error),
143
145
  nextAction: "重新运行 larkin setup,选择内置 Pi 并配置有效 API Key。" };
144
146
  }
@@ -153,11 +155,11 @@ async function probeNativeRuntimeReadiness(options) {
153
155
  ? "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND."
154
156
  : `Install ${options.runtime} and ensure it is on PATH.`,
155
157
  };
156
- const version = executableVersion(executable, env);
158
+ const version = executableVersion(executable, env, options.commandArgs);
157
159
  try {
158
160
  if (options.runtime === "pi") {
159
161
  const { discoverPiModelCatalog } = await Promise.resolve().then(() => __importStar(require("./pi-model-catalog.cjs")));
160
- await discoverPiModelCatalog({ cwd: options.cwd, command: executable, env });
162
+ await discoverPiModelCatalog({ cwd: options.cwd, command: executable, commandArgs: options.commandArgs, env });
161
163
  }
162
164
  else if (options.runtime === "codex") {
163
165
  const { discoverCodexModelCatalog } = await Promise.resolve().then(() => __importStar(require("./codex-model-catalog.cjs")));
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { assertBuiltinPiAgentDirectory, BUNDLED_PI_VERSION, piAgentDirectory } from "./pi-provider-config.mjs";
5
+ import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
5
6
  function safeProviderLabel(value) {
6
7
  const provider = typeof value === "string" ? value.trim() : "";
7
8
  return /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(provider) ? provider : null;
@@ -78,8 +79,8 @@ function selectedCommand(options) {
78
79
  return options.env?.LARKIN_CODEX_COMMAND || process.env.LARKIN_CODEX_COMMAND || "codex";
79
80
  return options.env?.LARKIN_CLAUDE_COMMAND || process.env.LARKIN_CLAUDE_COMMAND || "claude";
80
81
  }
81
- function executableVersion(executable, env) {
82
- const result = spawnSync(executable, ["--version"], { env, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
82
+ function executableVersion(executable, env, commandArgs = []) {
83
+ const result = spawnSync(executable, [...commandArgs, "--version"], { env, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
83
84
  if (result.status !== 0)
84
85
  return undefined;
85
86
  return String(result.stdout || result.stderr || "").replace(/[\r\n]+/g, " ").trim().slice(0, 120) || undefined;
@@ -95,6 +96,7 @@ export async function probeNativeRuntimeReadiness(options) {
95
96
  return { runtime: "pi", state: "ready", executable: process.execPath, version: `official-pi ${BUNDLED_PI_VERSION} (bundled)` };
96
97
  }
97
98
  catch (error) {
99
+ traceProcessBoundary(env, "readiness:builtin-pi-failure", { configDir: env.LARKIN_CONFIG_DIR, agentId: options.agentId, targetDir: options.agentId && env.LARKIN_CONFIG_DIR ? piAgentDirectory(env.LARKIN_CONFIG_DIR, options.agentId) : undefined, error });
98
100
  return { runtime: "pi", state: "unauthenticated", reason: error instanceof Error ? error.message : String(error),
99
101
  nextAction: "重新运行 larkin setup,选择内置 Pi 并配置有效 API Key。" };
100
102
  }
@@ -109,11 +111,11 @@ export async function probeNativeRuntimeReadiness(options) {
109
111
  ? "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND."
110
112
  : `Install ${options.runtime} and ensure it is on PATH.`,
111
113
  };
112
- const version = executableVersion(executable, env);
114
+ const version = executableVersion(executable, env, options.commandArgs);
113
115
  try {
114
116
  if (options.runtime === "pi") {
115
117
  const { discoverPiModelCatalog } = await import("./pi-model-catalog.mjs");
116
- await discoverPiModelCatalog({ cwd: options.cwd, command: executable, env });
118
+ await discoverPiModelCatalog({ cwd: options.cwd, command: executable, commandArgs: options.commandArgs, env });
117
119
  }
118
120
  else if (options.runtime === "codex") {
119
121
  const { discoverCodexModelCatalog } = await import("./codex-model-catalog.mjs");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkin",
3
- "version": "0.4.2",
3
+ "version": "0.4.6",
4
4
  "private": false,
5
5
  "larkinPackageRole": "npm-published",
6
6
  "files": [
@@ -84,14 +84,14 @@
84
84
  "test:live:agent-cli-routing-codex": "bun run build && LARKIN_RUN_AGENT_CLI_ROUTING_CODEX_LIVE=1 bun test --max-concurrency 1 test/live/agent-cli-routing-codex-live.test.mjs"
85
85
  },
86
86
  "dependencies": {
87
- "@earendil-works/pi-coding-agent": "0.83.0",
88
- "@tintinweb/pi-subagents": "0.14.3",
87
+ "@earendil-works/pi-coding-agent": "0.84.2",
89
88
  "@larksuite/channel": "^0.4.0",
90
89
  "@opentelemetry/api": "^1.9.1",
91
90
  "@opentelemetry/resources": "^2.10.0",
92
91
  "@opentelemetry/sdk-trace-base": "^2.10.0",
93
92
  "@opentelemetry/sdk-trace-node": "^2.10.0",
94
93
  "@radix-ui/react-dialog": "^1.1.21",
94
+ "@tintinweb/pi-subagents": "0.14.3",
95
95
  "lucide-react": "^1.26.0",
96
96
  "proper-lockfile": "4.1.2",
97
97
  "qrcode-terminal": "^0.12.0",