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