@skaile/workspaces 4.17.0 → 4.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/{asset-feeds-MQKQDF3R.js → asset-feeds-VXK6SHWK.js} +4 -4
  3. package/dist/{asset-feeds-MQKQDF3R.js.map → asset-feeds-VXK6SHWK.js.map} +1 -1
  4. package/dist/asset-manager/index.js +1 -1
  5. package/dist/bridge/codex-app-server.js +2 -1
  6. package/dist/bridge/drivers/claude-sdk.js +21 -151
  7. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  8. package/dist/bridge/drivers/codex.js +4 -3
  9. package/dist/bridge/drivers/codex.js.map +1 -1
  10. package/dist/bridge/drivers/continue.js +4 -3
  11. package/dist/bridge/drivers/continue.js.map +1 -1
  12. package/dist/bridge/drivers/echo.js +1 -1
  13. package/dist/bridge/drivers/gemini.js +6 -5
  14. package/dist/bridge/drivers/goose.js +4 -3
  15. package/dist/bridge/drivers/goose.js.map +1 -1
  16. package/dist/bridge/drivers/omp.js +813 -40
  17. package/dist/bridge/drivers/omp.js.map +1 -1
  18. package/dist/bridge/drivers/opencode.js +4 -3
  19. package/dist/bridge/drivers/opencode.js.map +1 -1
  20. package/dist/bridge/drivers/qwen.js +6 -5
  21. package/dist/bridge/drivers/qwen.js.map +1 -1
  22. package/dist/bridge/index.js +5 -4
  23. package/dist/bridge/src/agent-exec.d.ts +6 -0
  24. package/dist/bridge/src/agent-exec.d.ts.map +1 -1
  25. package/dist/bridge/src/agent-transcript.d.ts +18 -0
  26. package/dist/bridge/src/agent-transcript.d.ts.map +1 -0
  27. package/dist/bridge/src/codex-app-server.d.ts.map +1 -1
  28. package/dist/bridge/src/drivers/_subprocess-cli.d.ts.map +1 -1
  29. package/dist/bridge/src/drivers/claude-sdk.d.ts +2 -0
  30. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  31. package/dist/bridge/src/drivers/omp.d.ts +287 -7
  32. package/dist/bridge/src/drivers/omp.d.ts.map +1 -1
  33. package/dist/bridge/src/drivers/scrub-transcript.d.ts +29 -0
  34. package/dist/bridge/src/drivers/scrub-transcript.d.ts.map +1 -1
  35. package/dist/bridge/src/index.d.ts +2 -0
  36. package/dist/bridge/src/index.d.ts.map +1 -1
  37. package/dist/bridge/src/normalizer.d.ts +5 -0
  38. package/dist/bridge/src/normalizer.d.ts.map +1 -1
  39. package/dist/{chunk-C3YE4REG.js → chunk-25BOOOSM.js} +3 -3
  40. package/dist/{chunk-C3YE4REG.js.map → chunk-25BOOOSM.js.map} +1 -1
  41. package/dist/{chunk-XRTOSEF3.js → chunk-66MJRB6K.js} +2 -2
  42. package/dist/{chunk-XRTOSEF3.js.map → chunk-66MJRB6K.js.map} +1 -1
  43. package/dist/{chunk-NYDTZ2MK.js → chunk-66WUZ3XL.js} +5 -5
  44. package/dist/{chunk-NYDTZ2MK.js.map → chunk-66WUZ3XL.js.map} +1 -1
  45. package/dist/{chunk-3W7AOGPY.js → chunk-6S5KL76M.js} +225 -46
  46. package/dist/chunk-6S5KL76M.js.map +1 -0
  47. package/dist/{chunk-4RKCOURU.js → chunk-6V3DMJPL.js} +3 -3
  48. package/dist/{chunk-4RKCOURU.js.map → chunk-6V3DMJPL.js.map} +1 -1
  49. package/dist/{chunk-6F4TIY7X.js → chunk-EBYSCP6J.js} +4 -4
  50. package/dist/{chunk-6F4TIY7X.js.map → chunk-EBYSCP6J.js.map} +1 -1
  51. package/dist/{chunk-D5CRFGDL.js → chunk-EEODEEKX.js} +16 -3
  52. package/dist/chunk-EEODEEKX.js.map +1 -0
  53. package/dist/{chunk-2AQIFJHK.js → chunk-EPSCLN5W.js} +3 -3
  54. package/dist/{chunk-2AQIFJHK.js.map → chunk-EPSCLN5W.js.map} +1 -1
  55. package/dist/{chunk-AAZDYM5D.js → chunk-J3K5O6XA.js} +3 -3
  56. package/dist/{chunk-AAZDYM5D.js.map → chunk-J3K5O6XA.js.map} +1 -1
  57. package/dist/{chunk-4F2L7WPU.js → chunk-K4O6L3WH.js} +5 -3
  58. package/dist/chunk-K4O6L3WH.js.map +1 -0
  59. package/dist/{chunk-JURHZYOY.js → chunk-P6S5K7AI.js} +3 -3
  60. package/dist/{chunk-JURHZYOY.js.map → chunk-P6S5K7AI.js.map} +1 -1
  61. package/dist/{chunk-3VGQLF3N.js → chunk-RGB45PIL.js} +2 -2
  62. package/dist/{chunk-3VGQLF3N.js.map → chunk-RGB45PIL.js.map} +1 -1
  63. package/dist/{chunk-AOFJ2OKW.js → chunk-RYIWFV67.js} +2 -2
  64. package/dist/{chunk-AOFJ2OKW.js.map → chunk-RYIWFV67.js.map} +1 -1
  65. package/dist/{chunk-WOE4LPGF.js → chunk-S4R3WNRF.js} +3 -3
  66. package/dist/{chunk-WOE4LPGF.js.map → chunk-S4R3WNRF.js.map} +1 -1
  67. package/dist/{chunk-Z7KDOGKF.js → chunk-T6QGP5HN.js} +6 -6
  68. package/dist/{chunk-Z7KDOGKF.js.map → chunk-T6QGP5HN.js.map} +1 -1
  69. package/dist/{chunk-7EAJNEQ5.js → chunk-VXY6LSQ3.js} +6 -6
  70. package/dist/{chunk-7EAJNEQ5.js.map → chunk-VXY6LSQ3.js.map} +1 -1
  71. package/dist/{chunk-3H4HP733.js → chunk-W2SDSDR7.js} +23 -13
  72. package/dist/chunk-W2SDSDR7.js.map +1 -0
  73. package/dist/chunk-X5O56UO4.js +151 -0
  74. package/dist/chunk-X5O56UO4.js.map +1 -0
  75. package/dist/{chunk-RAHLV6QG.js → chunk-XAKGLR4Y.js} +6 -6
  76. package/dist/{chunk-RAHLV6QG.js.map → chunk-XAKGLR4Y.js.map} +1 -1
  77. package/dist/chunk-YROR332M.js +25 -0
  78. package/dist/chunk-YROR332M.js.map +1 -0
  79. package/dist/{chunk-YXXEHY7D.js → chunk-YUH46KA7.js} +6 -4
  80. package/dist/chunk-YUH46KA7.js.map +1 -0
  81. package/dist/cli/index.js +33 -32
  82. package/dist/cli/index.js.map +1 -1
  83. package/dist/connectors/index.js +6 -5
  84. package/dist/{ensure-sources-6XKA46PN.js → ensure-sources-WYXFJWUO.js} +4 -4
  85. package/dist/{ensure-sources-6XKA46PN.js.map → ensure-sources-WYXFJWUO.js.map} +1 -1
  86. package/dist/factory-assets/connectors/box.js +6 -5
  87. package/dist/factory-assets/connectors/deploy.js +6 -5
  88. package/dist/factory-assets/connectors/devserver.js +6 -5
  89. package/dist/factory-assets/connectors/flow/adapter.js +6 -5
  90. package/dist/factory-assets/connectors/flow/run-flow.js +7 -6
  91. package/dist/factory-assets/connectors/flow.js +6 -5
  92. package/dist/factory-assets/connectors/git.js +6 -5
  93. package/dist/factory-assets/connectors/gmail.js +6 -5
  94. package/dist/factory-assets/connectors/googledrive.js +6 -5
  95. package/dist/factory-assets/connectors/local.js +6 -5
  96. package/dist/factory-assets/connectors/mattermost.js +6 -5
  97. package/dist/factory-assets/connectors/memory.js +6 -5
  98. package/dist/factory-assets/connectors/minio.js +6 -5
  99. package/dist/factory-assets/connectors/postgres.js +6 -5
  100. package/dist/factory-assets/connectors/s3.js +6 -5
  101. package/dist/factory-assets/connectors/sharepoint.js +6 -5
  102. package/dist/factory-assets/connectors/sqlite.js +6 -5
  103. package/dist/factory-assets/connectors/static-server.js +6 -5
  104. package/dist/factory-assets/connectors/tunnel.js +6 -5
  105. package/dist/factory-assets/connectors/webdav.js +6 -5
  106. package/dist/factory-assets/connectors/xstate-store.js +6 -5
  107. package/dist/factory-assets/connectors/xstate.js +6 -5
  108. package/dist/{helpers-6I4SY2S7.js → helpers-TANA2HQT.js} +4 -4
  109. package/dist/{helpers-6I4SY2S7.js.map → helpers-TANA2HQT.js.map} +1 -1
  110. package/dist/{library-publish-PPNFBAVH.js → library-publish-JQNTMKZG.js} +6 -6
  111. package/dist/{library-publish-PPNFBAVH.js.map → library-publish-JQNTMKZG.js.map} +1 -1
  112. package/dist/{open-library-64LO64NE.js → open-library-NKZ4HJJ6.js} +5 -5
  113. package/dist/{open-library-64LO64NE.js.map → open-library-NKZ4HJJ6.js.map} +1 -1
  114. package/dist/{project-3UFTV7DL.js → project-AEAM2ZEW.js} +5 -5
  115. package/dist/{project-3UFTV7DL.js.map → project-AEAM2ZEW.js.map} +1 -1
  116. package/dist/runner/index.js +9 -8
  117. package/dist/runner/src/serve-credentials.d.ts +2 -1
  118. package/dist/runner/src/serve-credentials.d.ts.map +1 -1
  119. package/dist/runner/src/serve.d.ts.map +1 -1
  120. package/dist/sdk/asset-manager.js +1 -1
  121. package/dist/sdk/bridge.js +5 -4
  122. package/dist/sdk/index.js +9 -8
  123. package/dist/sdk/index.js.map +1 -1
  124. package/dist/sdk/runner.js +9 -8
  125. package/dist/{setup-JGXLTJ57.js → setup-Q7ACRV72.js} +10 -9
  126. package/dist/setup-Q7ACRV72.js.map +1 -0
  127. package/dist/{source-NHEYY7WH.js → source-QXQ3NO46.js} +6 -6
  128. package/dist/{source-NHEYY7WH.js.map → source-QXQ3NO46.js.map} +1 -1
  129. package/dist/{source-manifest-publish-IJLVR3IL.js → source-manifest-publish-UKWCZA2M.js} +7 -7
  130. package/dist/{source-manifest-publish-IJLVR3IL.js.map → source-manifest-publish-UKWCZA2M.js.map} +1 -1
  131. package/dist/tui/index.js +9 -8
  132. package/dist/tui/index.js.map +1 -1
  133. package/dist/workspace-plugin/index.js +1 -1
  134. package/package.json +1 -1
  135. package/dist/chunk-3H4HP733.js.map +0 -1
  136. package/dist/chunk-3W7AOGPY.js.map +0 -1
  137. package/dist/chunk-4F2L7WPU.js.map +0 -1
  138. package/dist/chunk-D5CRFGDL.js.map +0 -1
  139. package/dist/chunk-YXXEHY7D.js.map +0 -1
  140. package/dist/setup-JGXLTJ57.js.map +0 -1
@@ -1,7 +1,11 @@
1
- import { entrySurface } from '../../chunk-C3YE4REG.js';
1
+ import { checkToolInputSchema } from '../../chunk-X5O56UO4.js';
2
+ import { entrySurface } from '../../chunk-25BOOOSM.js';
3
+ import { dispatchCapability } from '../../chunk-TOXPRXAK.js';
2
4
  import { stripRunnerPrivateEnv, classifyGenericErrorMessage } from '../../chunk-BGY4KTU6.js';
3
- import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger, registryDriverConfig, entryCredentialValue, providerCredentialEnvName } from '../../chunk-3VGQLF3N.js';
5
+ import { wrapAgentCommand, resolveAgentExec } from '../../chunk-YROR332M.js';
6
+ import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger, registryDriverConfig, entryCredentialValue, providerCredentialEnvName } from '../../chunk-RGB45PIL.js';
4
7
  import '../../chunk-6E6PKKAD.js';
8
+ import { withActivityLabelJsonSchema } from '../../chunk-QQ3G5N7X.js';
5
9
  import '../../chunk-JMKB7IB4.js';
6
10
  import '../../chunk-DPLIN2RA.js';
7
11
  import { AI_PROVIDER_SURFACES, registryEntries, isEntryEnabled, parseModelRef, normalizeEntryBaseUrl, resolveEntryBaseUrl } from '../../chunk-GZZPKAV5.js';
@@ -365,6 +369,63 @@ function buildOmpToolArgs(tools) {
365
369
  if (effective.length === 0) return ["--no-tools"];
366
370
  return ["--tools", effective.join(",")];
367
371
  }
372
+ var OMP_HOST_TOOL_NAME_MAX = 64;
373
+ function buildOmpHostTools(tools) {
374
+ const taken = new Set(OMP_BUILTIN_TOOLS);
375
+ const out = { definitions: [], capabilityNames: /* @__PURE__ */ new Map(), dropped: [] };
376
+ for (const tool of tools) {
377
+ const parameters = { ...withActivityLabelJsonSchema(tool.parameters), type: "object" };
378
+ const reason = checkToolInputSchema(parameters);
379
+ if (reason) {
380
+ out.dropped.push({ tool: tool.name, reason });
381
+ continue;
382
+ }
383
+ const base = tool.name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, OMP_HOST_TOOL_NAME_MAX) || "capability";
384
+ let name = base;
385
+ for (let n = 2; taken.has(name); n++) {
386
+ const suffix = `_${n}`;
387
+ name = `${base.slice(0, OMP_HOST_TOOL_NAME_MAX - suffix.length)}${suffix}`;
388
+ }
389
+ taken.add(name);
390
+ out.capabilityNames.set(name, tool.name);
391
+ out.definitions.push({
392
+ name,
393
+ label: tool.name,
394
+ description: tool.description,
395
+ parameters,
396
+ loadMode: "essential"
397
+ });
398
+ }
399
+ return out;
400
+ }
401
+ var OMP_THINKING_LEVELS = [
402
+ "off",
403
+ "minimal",
404
+ "low",
405
+ "medium",
406
+ "high",
407
+ "xhigh",
408
+ "max",
409
+ "auto"
410
+ ];
411
+ var OMP_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
412
+ function ompThinkingLevel(cfg) {
413
+ const level = (() => {
414
+ if (cfg.thinking === "disabled") return "off";
415
+ if (cfg.effort) return cfg.effort;
416
+ if (cfg.thinking === "enabled") return "medium";
417
+ if (cfg.thinking === "adaptive") return "auto";
418
+ return void 0;
419
+ })();
420
+ return level !== void 0 && OMP_THINKING_LEVELS.includes(level) ? level : void 0;
421
+ }
422
+ function splitOmpModel(model) {
423
+ const slash = model.indexOf("/");
424
+ if (slash > 0 && slash < model.length - 1) {
425
+ return { provider: model.slice(0, slash), modelId: model.slice(slash + 1) };
426
+ }
427
+ return void 0;
428
+ }
368
429
  var OmpDriver = class extends AgentDriver {
369
430
  driverInfo = {
370
431
  id: "omp",
@@ -383,19 +444,169 @@ var OmpDriver = class extends AgentDriver {
383
444
  prevText = "";
384
445
  /** OMP session ID captured via `get_state` after each turn. */
385
446
  _ompSessionId;
447
+ /**
448
+ * Usage of the most recent turn, summed over every model call in it (a tool
449
+ * loop is one prompt but many assistant messages). Cleared when a turn
450
+ * starts, like claude-sdk, so it never mixes two turns.
451
+ */
452
+ _lastTokens = null;
453
+ /**
454
+ * Prompt size of the most recent model call: the real context fill, which
455
+ * `_lastTokens` (turn-cumulative) overstates on a tool loop. omp's own
456
+ * `get_state.contextUsage.tokens` replaces it once that reply lands.
457
+ */
458
+ _lastContextTokens = null;
459
+ /** Context window of the active model, from `get_state.contextUsage`. */
460
+ _contextWindow = null;
461
+ getTokenUsage() {
462
+ return this._lastTokens;
463
+ }
464
+ getContextTokens() {
465
+ return this._lastContextTokens;
466
+ }
467
+ getContextWindow() {
468
+ return this._contextWindow;
469
+ }
386
470
  /** Per-turn settlement handlers; non-null only while a prompt is in flight. */
387
471
  turnResolve = null;
388
472
  turnReject = null;
473
+ /** omp host tool name → capability name for the last `set_host_tools` sent. */
474
+ hostToolCapabilities = /* @__PURE__ */ new Map();
475
+ /** In-flight `host_tool_call`s by omp request id; aborted by `host_tool_cancel`. */
476
+ hostToolCalls = /* @__PURE__ */ new Map();
477
+ /** Requests awaiting a `response`, by request id; failed when the process goes away. */
478
+ pendingRequests = /* @__PURE__ */ new Map();
479
+ /** Unsubscribes the capability registry listener; set while a process runs. */
480
+ unsubscribeToolsChanged = null;
481
+ /** Request ids whose failed `response` is logged by the caller, not surfaced as an error event. */
482
+ quietRequestIds = /* @__PURE__ */ new Set();
483
+ /** Capability tools already warned about, so a re-send logs each once. */
484
+ warnedDroppedHostTools = /* @__PURE__ */ new Set();
485
+ /** The `ask` (or `select` fallback) omp is parked on, until answered, cancelled or the process ends. */
486
+ pendingQuestion = null;
487
+ /**
488
+ * Tail of the FIFO of prompts: each `prompt()`/`promptInput()` runs only after
489
+ * the previous one settled, so one turn at a time is ever in flight (omp runs
490
+ * a single agent loop). Never rejects.
491
+ */
492
+ promptTail = Promise.resolve();
389
493
  get runtimeSessionId() {
390
494
  return this._ompSessionId;
391
495
  }
496
+ /** Slash commands from omp's last `available_commands_update`. */
497
+ cachedCommands = [];
498
+ getSlashCommands() {
499
+ return this.cachedCommands;
500
+ }
392
501
  getModel() {
393
502
  return this.config.model;
394
503
  }
504
+ /**
505
+ * What the running omp process was last told, by config snapshot: `model`,
506
+ * `thinking` and `effort` are the config values those settings came from (also
507
+ * the rollback targets when omp rejects a change), `level` the omp thinking
508
+ * level sent. Set at spawn, updated on each successful `set_*`.
509
+ */
510
+ ompApplied = {};
511
+ /** Serialises live reconfigure RPCs so concurrent patches never interleave. */
512
+ reconfigureChain = Promise.resolve();
513
+ /**
514
+ * Apply `model` / `thinking` / `effort` to this driver and, when the process
515
+ * is up and idle, to the running omp session (`set_model` /
516
+ * `set_thinking_level`). The base contract is synchronous and void, so the
517
+ * RPCs are fire-and-forget: a rejected change is surfaced as an `error`
518
+ * agent event and the config rolls back, keeping {@link getModel} honest.
519
+ *
520
+ * A patch that arrives mid-turn or before `ready` is not lost: config is the
521
+ * source of truth, flushed before the next `prompt()` and read by the next
522
+ * spawn. A model `set_model` cannot express live (no `provider/` prefix) is
523
+ * rejected the same way as one omp refuses: an `error` event and a rollback,
524
+ * since the running process cannot switch to it. In registry mode the patch
525
+ * also replaces `modelRef`, so a respawn (crash, `resetSession()`) starts the
526
+ * reconfigured model; models.yml already lists every enabled provider.
527
+ */
528
+ reconfigure(patch) {
529
+ if (patch.model !== void 0) {
530
+ this.config.model = patch.model;
531
+ if (this.config.modelRef !== void 0) this.config.modelRef = patch.model;
532
+ }
533
+ if (patch.thinking !== void 0) this.config.thinking = patch.thinking;
534
+ if (patch.effort !== void 0) this.config.effort = patch.effort;
535
+ void this.syncReconfigure();
536
+ }
537
+ syncReconfigure() {
538
+ this.reconfigureChain = this.reconfigureChain.then(() => this.applyReconfigure());
539
+ return this.reconfigureChain;
540
+ }
541
+ /** Push config drift to the running process. Never rejects. */
542
+ async applyReconfigure() {
543
+ if (!this.proc || !this.ready || this.turnResolve) return;
544
+ const applied = this.ompApplied;
545
+ const model = this.config.model;
546
+ if (model !== void 0 && model !== applied.model) {
547
+ const target = splitOmpModel(model);
548
+ if (!target) {
549
+ this.log.warn("model has no provider prefix; set_model cannot apply it live", { model });
550
+ this.emit("agent-event", {
551
+ type: "error",
552
+ error: `set_model cannot apply "${model}" live (no provider/ prefix); model unchanged`
553
+ });
554
+ this.rollbackModel(model);
555
+ } else if (await this.sendReconfigure({ type: "set_model", ...target })) {
556
+ applied.model = model;
557
+ applied.modelRef = this.config.modelRef;
558
+ } else {
559
+ this.rollbackModel(model);
560
+ }
561
+ }
562
+ await this.applyThinking();
563
+ }
564
+ /**
565
+ * Restore the model the running process holds after a failed live switch —
566
+ * unless a newer patch already replaced `model` (it will be applied next).
567
+ */
568
+ rollbackModel(attempted) {
569
+ if (this.config.model !== attempted) return;
570
+ this.config.model = this.ompApplied.model;
571
+ this.config.modelRef = this.ompApplied.modelRef;
572
+ }
573
+ /** Push thinking/effort drift to the running process. Never rejects. */
574
+ async applyThinking() {
575
+ const applied = this.ompApplied;
576
+ const { thinking, effort } = this.config;
577
+ const level = ompThinkingLevel({ thinking, effort });
578
+ if (level !== void 0 && level !== applied.level) {
579
+ if (await this.sendReconfigure({ type: "set_thinking_level", level })) {
580
+ applied.level = level;
581
+ applied.thinking = thinking;
582
+ applied.effort = effort;
583
+ } else if (this.config.thinking === thinking && this.config.effort === effort) {
584
+ this.config.thinking = applied.thinking;
585
+ this.config.effort = applied.effort;
586
+ }
587
+ }
588
+ }
589
+ /**
590
+ * Send one live-reconfigure request. A failed `response` is already emitted
591
+ * as an `error` event by {@link handleEvent}; a transport failure (timeout,
592
+ * process gone) is emitted here. Returns whether omp accepted it.
593
+ */
594
+ async sendReconfigure(msg) {
595
+ try {
596
+ const resp = await this.request(msg);
597
+ if (resp.success) return true;
598
+ this.log.warn("omp rejected reconfigure", { type: msg.type, error: resp.error });
599
+ } catch (err) {
600
+ const error = err instanceof Error ? err.message : String(err);
601
+ this.log.warn("reconfigure request failed", { type: msg.type, error });
602
+ this.emit("agent-event", { type: "error", error: `${msg.type} failed: ${error}` });
603
+ }
604
+ return false;
605
+ }
395
606
  /**
396
607
  * @param config - Driver configuration. omp-relevant fields:
397
608
  * - `cwd` — working directory; omp session files are stored under `<cwd>/.omp/sessions/`.
398
- * - `model` / `provider` — forwarded as `--model [provider/]model`.
609
+ * - `model` — forwarded as `--model <selector>`. In registry mode `modelRef` takes precedence at spawn; `reconfigure({ model })` keeps both in step.
399
610
  * - `systemPrompt` — written to `<cwd>/.omp/system.md` and appended via `--append-system-prompt`.
400
611
  * - `agentDir` — sets `PI_CODING_AGENT_DIR`; omp reads agent identity from this directory.
401
612
  * - `sshKeyPath` — injected as `GIT_SSH_COMMAND` for authenticated git operations.
@@ -406,13 +617,13 @@ var OmpDriver = class extends AgentDriver {
406
617
  this.config = config;
407
618
  }
408
619
  async start() {
409
- if (this.proc) return;
410
620
  if (this.startPromise) return this.startPromise;
621
+ if (this.proc) return;
411
622
  this.startPromise = this._start();
412
623
  return this.startPromise;
413
624
  }
414
625
  async _start() {
415
- const args = ["--mode", "rpc"];
626
+ const args = ["--mode", "rpc-ui", "--no-ui"];
416
627
  const sessionDir = path.join(this.config.cwd, ".omp", "sessions");
417
628
  await fs.mkdir(sessionDir, { recursive: true });
418
629
  args.push("--session-dir", sessionDir);
@@ -438,6 +649,15 @@ var OmpDriver = class extends AgentDriver {
438
649
  }
439
650
  const modelArg = providers?.modelArg ?? this.config.model;
440
651
  if (modelArg) args.push("--model", modelArg);
652
+ const thinkingLevel = ompThinkingLevel(this.config);
653
+ if (thinkingLevel) args.push("--thinking", thinkingLevel);
654
+ this.ompApplied = {
655
+ model: this.config.model,
656
+ modelRef: this.config.modelRef,
657
+ thinking: this.config.thinking,
658
+ effort: this.config.effort,
659
+ level: thinkingLevel
660
+ };
441
661
  const toolArgs = buildOmpToolArgs(this.config.tools);
442
662
  if (toolArgs.length > 0) {
443
663
  this.log.info("applying tool restrictions", { args: toolArgs });
@@ -465,7 +685,7 @@ var OmpDriver = class extends AgentDriver {
465
685
  const overridePreArgs = process.env.OMP_BRIDGE_PREARGS ? process.env.OMP_BRIDGE_PREARGS.split(/\s+/).filter(Boolean) : [];
466
686
  const spawnBin = overrideBin || "omp";
467
687
  const spawnArgs = overrideBin ? [...overridePreArgs, ...args] : args;
468
- return new Promise((resolve, reject) => {
688
+ const spawned = await new Promise((resolve, reject) => {
469
689
  this.log.info("spawning omp", {
470
690
  bin: spawnBin,
471
691
  args: spawnArgs,
@@ -474,7 +694,8 @@ var OmpDriver = class extends AgentDriver {
474
694
  if (env.PI_CODING_AGENT_DIR)
475
695
  this.log.debug("env: PI_CODING_AGENT_DIR set", { value: env.PI_CODING_AGENT_DIR });
476
696
  if (env.GIT_SSH_COMMAND) this.log.debug("env: GIT_SSH_COMMAND set");
477
- const proc = spawn(spawnBin, spawnArgs, {
697
+ const target = wrapAgentCommand(spawnBin, spawnArgs, resolveAgentExec());
698
+ const proc = spawn(target.command, target.args, {
478
699
  cwd: this.config.cwd,
479
700
  env,
480
701
  stdio: ["pipe", "pipe", "pipe"]
@@ -482,6 +703,11 @@ var OmpDriver = class extends AgentDriver {
482
703
  this.proc = proc;
483
704
  const procEmitter = proc;
484
705
  procEmitter.on("error", (err) => {
706
+ if (this.proc !== proc) {
707
+ clearTimeout(readyTimeout);
708
+ reject(err);
709
+ return;
710
+ }
485
711
  const detail = this.classifyProcessError(err);
486
712
  this.log.error("process error", err, { detail });
487
713
  this.emit("agent-event", { type: "error", error: detail.message, detail });
@@ -490,6 +716,12 @@ var OmpDriver = class extends AgentDriver {
490
716
  });
491
717
  procEmitter.on("exit", (code, signal) => {
492
718
  this.log.info("process exited", { code, signal });
719
+ if (this.proc !== proc) {
720
+ clearTimeout(readyTimeout);
721
+ reject(new Error("omp process killed before it was ready"));
722
+ if (this.proc === null) this.emit("exit", { code, signal });
723
+ return;
724
+ }
493
725
  const lastStderr = this.stderrBuffer.slice(-10).join("\n");
494
726
  const crashed = code !== null && code !== 0;
495
727
  if (crashed) {
@@ -532,21 +764,341 @@ var OmpDriver = class extends AgentDriver {
532
764
  }
533
765
  const startTime = Date.now();
534
766
  const readyTimeout = setTimeout(() => {
535
- if (!this.ready) {
767
+ if (!this.ready && this.proc === proc) {
536
768
  this.log.warn("ready timeout, proceeding", { timeoutMs: 5e3 });
537
769
  this.ready = true;
538
- resolve();
770
+ resolve(proc);
539
771
  }
540
772
  }, 5e3);
541
773
  this.once("ready", () => {
542
774
  clearTimeout(readyTimeout);
543
- if (!this.ready) {
775
+ if (!this.ready && this.proc === proc) {
544
776
  this.log.info("process ready", { elapsedMs: Date.now() - startTime });
545
777
  this.ready = true;
546
- resolve();
778
+ resolve(proc);
547
779
  }
548
780
  });
549
781
  });
782
+ const assertCurrent = () => {
783
+ if (this.proc !== spawned) throw new Error("omp process was killed during start");
784
+ };
785
+ assertCurrent();
786
+ const hooks = this.config.capabilities;
787
+ if (hooks) {
788
+ this.unsubscribeToolsChanged = hooks.onToolsChanged?.(() => {
789
+ void this.syncHostTools();
790
+ }) ?? null;
791
+ }
792
+ await Promise.all([this.enableAskDialog(), hooks ? this.syncHostTools() : void 0]);
793
+ assertCurrent();
794
+ void this.captureState({ announceSession: false });
795
+ }
796
+ /**
797
+ * Register the runner's capabilities with omp as host tools.
798
+ *
799
+ * Sends the full set every time: `set_host_tools` replaces omp's previous
800
+ * host-owned set, so a registry change that removes a capability takes
801
+ * effect too. Best-effort — a failure is logged and the session runs
802
+ * without host tools rather than failing to start.
803
+ */
804
+ async syncHostTools() {
805
+ const hooks = this.config.capabilities;
806
+ if (!hooks || !this.proc) return;
807
+ const set = buildOmpHostTools(hooks.composeTools());
808
+ for (const { tool, reason } of set.dropped) {
809
+ const key = `${tool}\0${reason}`;
810
+ if (this.warnedDroppedHostTools.has(key)) continue;
811
+ this.warnedDroppedHostTools.add(key);
812
+ this.log.warn("host tool dropped: invalid input schema", { tool, reason });
813
+ }
814
+ this.hostToolCapabilities = set.capabilityNames;
815
+ try {
816
+ const response = await this.request(
817
+ { type: "set_host_tools", tools: set.definitions },
818
+ { quiet: true }
819
+ );
820
+ if (response.success) {
821
+ const data = response.data;
822
+ this.log.info("registered omp host tools", { toolNames: data?.toolNames });
823
+ } else {
824
+ this.log.error("omp rejected host tools", new Error(response.error ?? "unknown error"), {
825
+ tools: set.definitions.map((t) => t.name)
826
+ });
827
+ }
828
+ } catch (err) {
829
+ this.log.error("registering omp host tools failed", err);
830
+ }
831
+ }
832
+ /**
833
+ * Execute a capability omp asked the host to run (`host_tool_call`) and
834
+ * answer with `host_tool_result`. Goes through {@link dispatchCapability}
835
+ * so validation, fire-and-forget and render semantics match claude-sdk.
836
+ *
837
+ * Emits `tool_call` / `tool_execution_end` around the dispatch: the
838
+ * normalizer does not read omp's own tool frames, so without these the
839
+ * call — and its `activity_label` — would never reach the chat.
840
+ */
841
+ handleHostToolCall(event) {
842
+ const id = event.id;
843
+ if (typeof id !== "string") return;
844
+ const hostName = typeof event.toolName === "string" ? event.toolName : "";
845
+ const name = this.hostToolCapabilities.get(hostName) ?? hostName;
846
+ const callId = typeof event.toolCallId === "string" ? event.toolCallId : id;
847
+ const input = event.arguments ?? {};
848
+ const hooks = this.config.capabilities;
849
+ const controller = new AbortController();
850
+ const call = { controller, name };
851
+ this.hostToolCalls.set(id, call);
852
+ this.emit("agent-event", {
853
+ type: "tool_call",
854
+ name,
855
+ tool: { name },
856
+ toolCallId: callId,
857
+ input
858
+ });
859
+ void (async () => {
860
+ let text;
861
+ let isError = false;
862
+ try {
863
+ if (!hooks) throw new Error(`no capability host for ${name}`);
864
+ const dispatch = await dispatchCapability(
865
+ hooks,
866
+ callId,
867
+ name,
868
+ input,
869
+ () => this.log.warn("background capability handler failed", {
870
+ name,
871
+ callId
872
+ // Nothing from the error: its message can quote the call's content.
873
+ }),
874
+ controller.signal
875
+ );
876
+ text = typeof dispatch.result === "string" ? dispatch.result : JSON.stringify(dispatch.result ?? {});
877
+ } catch (err) {
878
+ isError = true;
879
+ text = `Error: ${err?.message ?? String(err)}`;
880
+ }
881
+ if (this.hostToolCalls.get(id) !== call) return;
882
+ this.hostToolCalls.delete(id);
883
+ this.emit("agent-event", { type: "tool_execution_end", toolName: name });
884
+ try {
885
+ this.send({
886
+ type: "host_tool_result",
887
+ id,
888
+ result: { content: [{ type: "text", text }] },
889
+ ...isError ? { isError: true } : {}
890
+ });
891
+ } catch (err) {
892
+ this.log.warn("host tool result undeliverable", { name, callId, error: String(err) });
893
+ }
894
+ })();
895
+ }
896
+ /**
897
+ * `host_tool_cancel`: omp aborted the call, so abort the capability handler
898
+ * and close the call's chat activity (the handler's later settlement is
899
+ * ignored, so this is the only `tool_execution_end` the call gets).
900
+ */
901
+ handleHostToolCancel(event) {
902
+ const targetId = event.targetId;
903
+ if (typeof targetId !== "string") return;
904
+ this.abortHostToolCall(targetId);
905
+ }
906
+ /** Abort one in-flight host tool call and end its activity; no-op if already settled. */
907
+ abortHostToolCall(id) {
908
+ const call = this.hostToolCalls.get(id);
909
+ if (!call) return;
910
+ this.hostToolCalls.delete(id);
911
+ call.controller.abort();
912
+ this.emit("agent-event", { type: "tool_execution_end", toolName: call.name });
913
+ }
914
+ // ── Interactive questions (`ask`) ──────────────────────────────────────────
915
+ /**
916
+ * `extension_ui_request`: omp asks the host something. Only `ask` (and the
917
+ * `select` omp falls back to when `set_ask_dialog` is unavailable) can carry
918
+ * a user question; both park until {@link answerQuestion}. Presentation
919
+ * frames expect no reply. Every other kind is a dialog nobody here can
920
+ * answer, so it is cancelled at once — an unanswered dialog would leave the
921
+ * tool call, and the turn, waiting forever.
922
+ */
923
+ handleExtensionUiRequest(event) {
924
+ const method = event.method;
925
+ if (method === "cancel") {
926
+ if (typeof event.targetId === "string" && this.pendingQuestion?.id === event.targetId) {
927
+ this.pendingQuestion = null;
928
+ }
929
+ return;
930
+ }
931
+ if (method === "notify" || method === "setStatus" || method === "setWidget" || method === "setTitle" || method === "set_editor_text" || method === "open_url") {
932
+ return;
933
+ }
934
+ const id = event.id;
935
+ if (typeof id !== "string") return;
936
+ let parsed = null;
937
+ if (method === "ask") parsed = this.parseAskRequest(id, event);
938
+ else if (method === "select") parsed = this.parseSelectRequest(id, event);
939
+ if (!parsed) {
940
+ this.log.debug("cancelling unsupported omp dialog", { method: String(method) });
941
+ this.sendUiResponse({ id, cancelled: true });
942
+ return;
943
+ }
944
+ this.cancelPendingQuestion();
945
+ this.pendingQuestion = parsed;
946
+ this.announceQuestion(parsed, event);
947
+ }
948
+ parseAskRequest(id, event) {
949
+ if (!Array.isArray(event.questions) || event.questions.length === 0) return null;
950
+ const questions = [];
951
+ for (const raw of event.questions) {
952
+ const q = raw;
953
+ if (!q || typeof q.id !== "string" || typeof q.question !== "string" || !q.question) {
954
+ return null;
955
+ }
956
+ const options = (Array.isArray(q.options) ? q.options : []).flatMap((o) => {
957
+ let label = o;
958
+ if (o && typeof o === "object" && "label" in o) label = o.label;
959
+ return typeof label === "string" ? [label] : [];
960
+ });
961
+ questions.push({ id: q.id, question: q.question, options, multi: q.multi === true });
962
+ }
963
+ return { id, method: "ask", questions, answers: questions.map(() => void 0) };
964
+ }
965
+ parseSelectRequest(id, event) {
966
+ if (typeof event.title !== "string" || !event.title || !Array.isArray(event.options)) {
967
+ return null;
968
+ }
969
+ const options = event.options.filter((o) => typeof o === "string");
970
+ if (options.length === 0) return null;
971
+ return {
972
+ id,
973
+ method: "select",
974
+ questions: [{ id, question: event.title, options, multi: false }],
975
+ answers: [void 0]
976
+ };
977
+ }
978
+ /**
979
+ * Publish the parked question the way claude-sdk does for `AskUserQuestion`:
980
+ * an assistant `tool_use` block the normalizer turns into one `question`
981
+ * event per question, plus the matching `tool_call`. Option descriptions and
982
+ * `header`s survive in the tool input for consumers that render them.
983
+ */
984
+ announceQuestion(pending, event) {
985
+ const rawQuestions = Array.isArray(event.questions) ? event.questions : [];
986
+ const input = {
987
+ requestId: pending.id,
988
+ questions: pending.questions.map((q, i) => {
989
+ const raw = rawQuestions[i];
990
+ return {
991
+ question: q.question,
992
+ ...typeof raw?.header === "string" ? { header: raw.header } : {},
993
+ multiSelect: q.multi,
994
+ options: q.options.map((label, j) => {
995
+ const description = raw?.options?.[j]?.description;
996
+ return { label, ...typeof description === "string" ? { description } : {} };
997
+ })
998
+ };
999
+ })
1000
+ };
1001
+ const toolCallId = `omp-ask:${pending.id}`;
1002
+ this.emit("agent-event", {
1003
+ type: "message_start",
1004
+ message: {
1005
+ role: "assistant",
1006
+ content: [{ type: "tool_use", name: "AskUserQuestion", id: toolCallId, input }]
1007
+ }
1008
+ });
1009
+ this.emit("agent-event", {
1010
+ type: "tool_call",
1011
+ name: "AskUserQuestion",
1012
+ tool: { name: "AskUserQuestion" },
1013
+ toolCallId,
1014
+ input
1015
+ });
1016
+ this.log.debug("paused on omp ask", { questions: pending.questions.length });
1017
+ }
1018
+ hasPendingQuestion() {
1019
+ return this.pendingQuestion !== null;
1020
+ }
1021
+ /**
1022
+ * While omp is parked on a question a reply that is not an answer must not
1023
+ * become a prompt: the turn is still running, and a second prompt would
1024
+ * collide with it.
1025
+ */
1026
+ get acceptsReplyAsPrompt() {
1027
+ return this.pendingQuestion === null;
1028
+ }
1029
+ answerQuestion(answer, question, requestId) {
1030
+ const pending = this.pendingQuestion;
1031
+ if (!pending || typeof answer !== "string") return false;
1032
+ if (requestId !== void 0 && requestId !== pending.id) return false;
1033
+ let index = question !== void 0 ? pending.questions.findIndex((q) => q.question === question) : -1;
1034
+ if (index === -1) index = pending.answers.findIndex((a) => a === void 0);
1035
+ if (index === -1) return false;
1036
+ pending.answers[index] = answer;
1037
+ if (pending.answers.some((a) => a === void 0)) return true;
1038
+ this.pendingQuestion = null;
1039
+ this.sendUiResponse(this.buildQuestionResponse(pending));
1040
+ this.log.debug("answered omp ask", { questions: pending.questions.length });
1041
+ return true;
1042
+ }
1043
+ /** Shape the collected answers as the `extension_ui_response` payload for the request's method. */
1044
+ buildQuestionResponse(pending) {
1045
+ const find = (options, text) => options.find((o) => o === text) ?? options.find((o) => o.toLowerCase() === text.toLowerCase());
1046
+ if (pending.method === "select") {
1047
+ const match = find(pending.questions[0].options, (pending.answers[0] ?? "").trim());
1048
+ if (match !== void 0) return { id: pending.id, value: match };
1049
+ this.log.warn("select answer matches no option; cancelling it");
1050
+ return { id: pending.id, cancelled: true };
1051
+ }
1052
+ const answers = pending.questions.map((q, i) => {
1053
+ const text = (pending.answers[i] ?? "").trim();
1054
+ const whole = find(q.options, text);
1055
+ if (whole !== void 0) return { id: q.id, selectedOptions: [whole] };
1056
+ if (q.multi) {
1057
+ const parts = text.split(",").map((p) => p.trim()).filter(Boolean);
1058
+ const matched = parts.map((p) => find(q.options, p));
1059
+ if (parts.length > 0 && matched.every((m) => m !== void 0)) {
1060
+ return { id: q.id, selectedOptions: [...new Set(matched)] };
1061
+ }
1062
+ }
1063
+ return { id: q.id, selectedOptions: [], ...text ? { customInput: text } : {} };
1064
+ });
1065
+ return { id: pending.id, answers };
1066
+ }
1067
+ /** Write an `extension_ui_response`; undeliverable (process gone) is logged, not thrown. */
1068
+ sendUiResponse(payload) {
1069
+ try {
1070
+ this.send({ type: "extension_ui_response", ...payload });
1071
+ } catch (err) {
1072
+ this.log.warn("extension ui response undeliverable", { error: String(err) });
1073
+ }
1074
+ }
1075
+ /** Drop the pending question and tell omp it was cancelled, so its tool call unwinds. */
1076
+ cancelPendingQuestion() {
1077
+ const pending = this.pendingQuestion;
1078
+ if (!pending) return;
1079
+ this.pendingQuestion = null;
1080
+ this.sendUiResponse({ id: pending.id, cancelled: true });
1081
+ }
1082
+ /**
1083
+ * Opt in to omp's single `ask` dialog request (off by default per process).
1084
+ * Best-effort: a build without the command answers with a failed response and
1085
+ * its `ask` keeps degrading to one `select` per question, which
1086
+ * {@link handleExtensionUiRequest} still surfaces as a question.
1087
+ */
1088
+ async enableAskDialog() {
1089
+ try {
1090
+ const response = await this.request(
1091
+ { type: "set_ask_dialog", enabled: true },
1092
+ { quiet: true }
1093
+ );
1094
+ if (!response.success) {
1095
+ this.log.warn("omp rejected set_ask_dialog; ask falls back to select", {
1096
+ error: response.error
1097
+ });
1098
+ }
1099
+ } catch (err) {
1100
+ this.log.warn("set_ask_dialog failed", { error: String(err) });
1101
+ }
550
1102
  }
551
1103
  extractMessageText(msg) {
552
1104
  if (!msg) return "";
@@ -556,19 +1108,59 @@ var OmpDriver = class extends AgentDriver {
556
1108
  return c.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("");
557
1109
  return "";
558
1110
  }
1111
+ /**
1112
+ * `available_commands_update`: replace the cached slash-command list and
1113
+ * publish it. Names carry no leading slash (omp's already don't), matching
1114
+ * claude-sdk's `commands_available`. Entries without a name are skipped.
1115
+ */
1116
+ handleCommandsUpdate(event) {
1117
+ const raw = Array.isArray(event.commands) ? event.commands : [];
1118
+ const commands = [];
1119
+ for (const entry of raw) {
1120
+ if (!entry || typeof entry !== "object") continue;
1121
+ const c = entry;
1122
+ if (typeof c.name !== "string" || !c.name) continue;
1123
+ const hint = c.input && typeof c.input === "object" ? c.input.hint : void 0;
1124
+ commands.push({
1125
+ name: c.name,
1126
+ description: typeof c.description === "string" ? c.description : "",
1127
+ argumentHint: typeof hint === "string" && hint ? hint : void 0
1128
+ });
1129
+ }
1130
+ this.cachedCommands = commands;
1131
+ this.emit("agent-event", { type: "commands_available", commands });
1132
+ this.log.debug("discovered slash commands", { count: commands.length });
1133
+ }
559
1134
  handleEvent(event) {
560
1135
  if (event.type === "ready") {
561
1136
  this.emit("ready");
562
1137
  return;
563
1138
  }
564
1139
  if (event.type === "response") {
565
- if (!event.success && event.error) {
1140
+ const quiet = typeof event.id === "string" && this.quietRequestIds.delete(event.id);
1141
+ if (!event.success && event.error && !quiet) {
566
1142
  const detail = this.classifyErrorMessage(event.error);
567
1143
  this.emit("agent-event", { type: "error", error: event.error, detail });
568
1144
  }
569
1145
  this.emit(`response:${event.id}`, event);
570
1146
  return;
571
1147
  }
1148
+ if (event.type === "host_tool_call") {
1149
+ this.handleHostToolCall(event);
1150
+ return;
1151
+ }
1152
+ if (event.type === "host_tool_cancel") {
1153
+ this.handleHostToolCancel(event);
1154
+ return;
1155
+ }
1156
+ if (event.type === "extension_ui_request") {
1157
+ this.handleExtensionUiRequest(event);
1158
+ return;
1159
+ }
1160
+ if (event.type === "available_commands_update") {
1161
+ this.handleCommandsUpdate(event);
1162
+ return;
1163
+ }
572
1164
  if (event.type === "error" && event.error) {
573
1165
  const detail = this.classifyErrorMessage(event.error);
574
1166
  this.emit("agent-event", { ...event, detail });
@@ -583,7 +1175,13 @@ var OmpDriver = class extends AgentDriver {
583
1175
  delta = text;
584
1176
  }
585
1177
  this.prevText = text;
586
- this.emit("agent-event", { ...event, _textDelta: delta });
1178
+ const sub = event.assistantMessageEvent;
1179
+ const thinking = sub && typeof sub === "object" && "type" in sub && sub.type === "thinking_delta" ? "delta" in sub && typeof sub.delta === "string" ? sub.delta : "" : "";
1180
+ this.emit("agent-event", {
1181
+ ...event,
1182
+ _textDelta: delta,
1183
+ ...thinking ? { _thinkingDelta: thinking } : {}
1184
+ });
587
1185
  return;
588
1186
  }
589
1187
  if (event.type === "message_start") {
@@ -592,6 +1190,7 @@ var OmpDriver = class extends AgentDriver {
592
1190
  if (event.type === "message_end" || event.type === "agent_end") {
593
1191
  this.prevText = "";
594
1192
  }
1193
+ if (event.type === "message_end") this.recordMessageUsage(event.message);
595
1194
  if (event.type === "message_end" && event.message?.role === "assistant" && event.message.stopReason === "error") {
596
1195
  const error = event.message.errorMessage || "omp request failed";
597
1196
  const detail = this.classifyErrorMessage(error);
@@ -599,7 +1198,7 @@ var OmpDriver = class extends AgentDriver {
599
1198
  this.emit("agent-event", { type: "error", error, detail });
600
1199
  }
601
1200
  if (event.type === "agent_end") {
602
- void this.captureSessionId();
1201
+ void this.captureState();
603
1202
  }
604
1203
  this.emit("agent-event", event);
605
1204
  if (event.type === "agent_end") {
@@ -627,31 +1226,23 @@ var OmpDriver = class extends AgentDriver {
627
1226
  reject?.(err);
628
1227
  }
629
1228
  /**
630
- * Query OMP for the current session ID via `get_state` RPC.
631
- * Emits a `session_info` event on success so the forge layer can persist the ID.
632
- * Failures are silently swallowed — session tracking is best-effort.
1229
+ * Query OMP's current state via the `get_state` RPC.
1230
+ *
1231
+ * Always refreshes the context fill from `contextUsage`. With
1232
+ * `announceSession` (the default, used after each turn) it also records the
1233
+ * native session ID and emits a `session_info` event so the forge layer can
1234
+ * persist it. Failures are silently swallowed — both are best-effort.
633
1235
  */
634
- async captureSessionId() {
1236
+ async captureState(options = {}) {
1237
+ const announceSession = options.announceSession ?? true;
635
1238
  try {
636
- const id = this.nextId();
637
- const response = await new Promise((resolve, reject) => {
638
- const timer = setTimeout(() => reject(new Error("get_state timeout")), 5e3);
639
- this.once(`response:${id}`, (resp) => {
640
- clearTimeout(timer);
641
- resolve(resp);
642
- });
643
- try {
644
- this.send({ id, type: "get_state" });
645
- } catch (err) {
646
- clearTimeout(timer);
647
- reject(err);
648
- }
649
- });
1239
+ const response = await this.request({ type: "get_state" }, { quiet: !announceSession });
650
1240
  if (response.success && response.data) {
651
1241
  const data = response.data;
1242
+ this.captureContextUsage(data.contextUsage);
652
1243
  const sessionId = data.sessionId;
653
1244
  const sessionFile = data.sessionFile;
654
- if (sessionId) {
1245
+ if (announceSession && sessionId) {
655
1246
  this._ompSessionId = sessionId;
656
1247
  this.log.debug("omp session id assigned", { sessionId });
657
1248
  this.emit("agent-event", {
@@ -664,9 +1255,99 @@ var OmpDriver = class extends AgentDriver {
664
1255
  } catch {
665
1256
  }
666
1257
  }
1258
+ /** `get_state.contextUsage` → context fill + window; absent or malformed values are ignored. */
1259
+ captureContextUsage(raw) {
1260
+ if (!raw || typeof raw !== "object") return;
1261
+ const tokens = "tokens" in raw ? raw.tokens : void 0;
1262
+ const contextWindow = "contextWindow" in raw ? raw.contextWindow : void 0;
1263
+ if (typeof tokens === "number" && Number.isFinite(tokens) && tokens >= 0) {
1264
+ this._lastContextTokens = tokens;
1265
+ }
1266
+ if (typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0) {
1267
+ this._contextWindow = contextWindow;
1268
+ }
1269
+ }
1270
+ /** Forget the previous turn's usage; called as a new turn begins. */
1271
+ beginUsageTurn() {
1272
+ this._lastTokens = null;
1273
+ this._lastContextTokens = null;
1274
+ }
1275
+ /**
1276
+ * Fold one finished assistant message into the turn's usage. Only the
1277
+ * `message_end` value is final (`message_start` and partials carry running
1278
+ * zeros). A message without usage, or an errored one reporting all zeros,
1279
+ * bills nothing and must neither create a reading nor wipe a good one.
1280
+ */
1281
+ recordMessageUsage(message) {
1282
+ if (message?.role !== "assistant" || !message.usage) return;
1283
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1284
+ const input = count(message.usage.input);
1285
+ const output = count(message.usage.output);
1286
+ const cacheRead = count(message.usage.cacheRead);
1287
+ const cacheWrite = count(message.usage.cacheWrite);
1288
+ const reasoning = count(message.usage.reasoningTokens);
1289
+ const total = count(message.usage.totalTokens);
1290
+ const callContext = (input ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0);
1291
+ if (callContext + (output ?? 0) + (total ?? 0) === 0) return;
1292
+ const turn = { ...this._lastTokens };
1293
+ const add = (key, value) => {
1294
+ if (value !== void 0) turn[key] = (turn[key] ?? 0) + value;
1295
+ };
1296
+ add("inputTokens", input);
1297
+ add("outputTokens", output);
1298
+ add("cacheReadTokens", cacheRead);
1299
+ add("cacheCreationTokens", cacheWrite);
1300
+ add("reasoningTokens", reasoning);
1301
+ add("totalTokens", total);
1302
+ this._lastTokens = turn;
1303
+ if (callContext > 0) this._lastContextTokens = callContext;
1304
+ }
667
1305
  nextId() {
668
1306
  return `req-${++this.reqId}`;
669
1307
  }
1308
+ /**
1309
+ * Send a command and await its `response` frame.
1310
+ *
1311
+ * @param options.quiet - Leave a failed response to the caller instead of
1312
+ * surfacing it as an `error` agent event.
1313
+ * @throws When the process is not running or no response arrives within
1314
+ * `options.timeoutMs` (default 5 s).
1315
+ */
1316
+ request(msg, options = {}) {
1317
+ const id = this.nextId();
1318
+ const event = `response:${id}`;
1319
+ let resolve;
1320
+ let reject;
1321
+ const promise = new Promise((res, rej) => {
1322
+ resolve = res;
1323
+ reject = rej;
1324
+ });
1325
+ const onResponse = (resp) => {
1326
+ clearTimeout(timer);
1327
+ this.pendingRequests.delete(id);
1328
+ resolve(resp);
1329
+ };
1330
+ const fail = (err, keepQuiet = false) => {
1331
+ clearTimeout(timer);
1332
+ this.pendingRequests.delete(id);
1333
+ this.removeListener(event, onResponse);
1334
+ if (!keepQuiet) this.quietRequestIds.delete(id);
1335
+ reject(err);
1336
+ };
1337
+ const timer = setTimeout(
1338
+ () => fail(new Error(`${msg.type} timeout`), true),
1339
+ options.timeoutMs ?? 5e3
1340
+ );
1341
+ this.once(event, onResponse);
1342
+ this.pendingRequests.set(id, fail);
1343
+ if (options.quiet) this.quietRequestIds.add(id);
1344
+ try {
1345
+ this.send({ id, ...msg });
1346
+ } catch (err) {
1347
+ fail(err);
1348
+ }
1349
+ return promise;
1350
+ }
670
1351
  send(msg) {
671
1352
  if (!this.proc?.stdin?.writable) {
672
1353
  const detail = {
@@ -694,15 +1375,94 @@ var OmpDriver = class extends AgentDriver {
694
1375
  * @throws {Error} When the omp process is not running, its stdin is closed, or
695
1376
  * the child crashes/exits before the turn completes.
696
1377
  */
697
- async prompt(message) {
1378
+ prompt(message) {
1379
+ return this.enqueuePrompt(() => this.runPrompt({ message }));
1380
+ }
1381
+ /**
1382
+ * Run one prompt after every earlier one settled. A question omp is parked on
1383
+ * is cancelled first, synchronously and ahead of the queue: the parked turn
1384
+ * can only finish once its `ask` is released, so the new prompt (the user
1385
+ * typing instead of answering) must not wait behind it. The runner does not
1386
+ * serialise `prompt` commands, so the driver does, as claude-sdk does.
1387
+ */
1388
+ enqueuePrompt(run) {
1389
+ this.cancelPendingQuestion();
1390
+ const queued = this.promptTail.then(run);
1391
+ this.promptTail = queued.then(
1392
+ () => void 0,
1393
+ () => void 0
1394
+ );
1395
+ return queued;
1396
+ }
1397
+ /**
1398
+ * Structured prompt: text plus image/file/skill attachments. Images travel as
1399
+ * omp `ImageContent` (`prompt.images`, base64 + mimeType); file and skill
1400
+ * attachments are named in a text preamble, because omp reads files itself.
1401
+ * Turn settlement is shared with {@link prompt}.
1402
+ */
1403
+ promptInput(input) {
1404
+ return this.enqueuePrompt(async () => this.runPrompt(await this.buildPromptPayload(input)));
1405
+ }
1406
+ /** Resolve attachments into the `message` + `images` fields of an omp `prompt` frame. */
1407
+ async buildPromptPayload(input) {
1408
+ const attachments = input.attachments ?? [];
1409
+ if (attachments.length === 0) return { message: input.text };
1410
+ const mimeByExt = {
1411
+ ".png": "image/png",
1412
+ ".jpg": "image/jpeg",
1413
+ ".jpeg": "image/jpeg",
1414
+ ".gif": "image/gif",
1415
+ ".webp": "image/webp"
1416
+ };
1417
+ const images = [];
1418
+ const preamble = [];
1419
+ let root;
1420
+ for (const attachment of attachments) {
1421
+ if (attachment.type !== "image") {
1422
+ preamble.push(`[Attached ${attachment.type}: ${attachment.name} (${attachment.path})]`);
1423
+ continue;
1424
+ }
1425
+ root ??= await fs.realpath(this.config.cwd);
1426
+ const resolved = await fs.realpath(path.resolve(root, attachment.path));
1427
+ const rel = path.relative(root, resolved);
1428
+ const escapes = rel === ".." || rel.startsWith(`..${path.sep}`);
1429
+ if (rel === "" || escapes || path.isAbsolute(rel)) {
1430
+ throw new Error(`Image attachment is outside the workspace: ${attachment.path}`);
1431
+ }
1432
+ const mimeType = mimeByExt[path.extname(resolved).toLowerCase()];
1433
+ if (!mimeType) {
1434
+ throw new Error(`Unsupported image type for attachment: ${attachment.path}`);
1435
+ }
1436
+ const { size } = await fs.stat(resolved);
1437
+ if (size > OMP_MAX_IMAGE_BYTES) {
1438
+ throw new Error(`Image attachment too large (${size} bytes > 5 MB): ${attachment.path}`);
1439
+ }
1440
+ images.push({
1441
+ type: "image",
1442
+ data: (await fs.readFile(resolved)).toString("base64"),
1443
+ mimeType
1444
+ });
1445
+ }
1446
+ const message = preamble.length ? `${preamble.join("\n")}
1447
+
1448
+ ${input.text}` : input.text;
1449
+ return { message, ...images.length ? { images } : {} };
1450
+ }
1451
+ /**
1452
+ * Send one `prompt` frame and resolve when the turn completes (`agent_end`).
1453
+ * Rejects if the child dies first.
1454
+ */
1455
+ async runPrompt(payload) {
698
1456
  await this.start();
1457
+ await this.syncReconfigure();
699
1458
  const id = this.nextId();
700
1459
  const turnPromise = new Promise((resolve, reject) => {
701
1460
  this.turnResolve = resolve;
702
1461
  this.turnReject = reject;
703
1462
  });
1463
+ this.beginUsageTurn();
704
1464
  try {
705
- this.send({ id, type: "prompt", message });
1465
+ this.send({ id, type: "prompt", ...payload });
706
1466
  } catch (err) {
707
1467
  this.turnResolve = null;
708
1468
  this.turnReject = null;
@@ -721,6 +1481,7 @@ var OmpDriver = class extends AgentDriver {
721
1481
  if (!this.proc) return;
722
1482
  const id = this.nextId();
723
1483
  this.send({ id, type: "abort" });
1484
+ this.cancelPendingQuestion();
724
1485
  }
725
1486
  /** `true` when the omp child process is alive and has not been killed. */
726
1487
  get isRunning() {
@@ -733,14 +1494,18 @@ var OmpDriver = class extends AgentDriver {
733
1494
  * `OmpDriver` to start a fresh session.
734
1495
  */
735
1496
  kill() {
736
- if (this.proc) {
737
- this.proc.kill("SIGTERM");
738
- this.cleanup();
739
- }
1497
+ const proc = this.proc;
1498
+ if (!proc) return;
1499
+ this.rejectTurn(new Error("omp process killed"));
1500
+ proc.kill("SIGTERM");
1501
+ this.cleanup();
740
1502
  }
741
1503
  async resetSession() {
742
1504
  this.kill();
743
1505
  this.config = { ...this.config, resumeSessionId: void 0 };
1506
+ this._lastTokens = null;
1507
+ this._lastContextTokens = null;
1508
+ this._contextWindow = null;
744
1509
  await this.start();
745
1510
  }
746
1511
  classifyProcessError(err) {
@@ -803,6 +1568,14 @@ var OmpDriver = class extends AgentDriver {
803
1568
  this.ready = false;
804
1569
  this.startPromise = null;
805
1570
  this.stderrBuffer = [];
1571
+ this.unsubscribeToolsChanged?.();
1572
+ this.unsubscribeToolsChanged = null;
1573
+ this.hostToolCapabilities = /* @__PURE__ */ new Map();
1574
+ for (const id of [...this.hostToolCalls.keys()]) this.abortHostToolCall(id);
1575
+ for (const fail of [...this.pendingRequests.values()]) fail(new Error("omp process ended"));
1576
+ this.pendingRequests.clear();
1577
+ this.quietRequestIds.clear();
1578
+ this.pendingQuestion = null;
806
1579
  }
807
1580
  };
808
1581
  var ompDriverTarget = {
@@ -819,6 +1592,6 @@ var ompDriverTarget = {
819
1592
  }).listModels()
820
1593
  };
821
1594
 
822
- export { OMP_BUILTIN_TOOLS, OMP_DEFAULT_TOOLS, OmpDriver, buildOmpChildEnv, buildOmpToolArgs, ompDriverTarget };
1595
+ export { OMP_BUILTIN_TOOLS, OMP_DEFAULT_TOOLS, OMP_THINKING_LEVELS, OmpDriver, buildOmpChildEnv, buildOmpHostTools, buildOmpToolArgs, ompDriverTarget, ompThinkingLevel, splitOmpModel };
823
1596
  //# sourceMappingURL=omp.js.map
824
1597
  //# sourceMappingURL=omp.js.map