@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.
- package/CHANGELOG.md +26 -0
- package/dist/{asset-feeds-XBSARE5D.js → asset-feeds-KWWDOLAS.js} +4 -4
- package/dist/{asset-feeds-XBSARE5D.js.map → asset-feeds-KWWDOLAS.js.map} +1 -1
- package/dist/asset-manager/index.js +1 -1
- package/dist/bridge/drivers/claude-sdk.js +7 -150
- package/dist/bridge/drivers/claude-sdk.js.map +1 -1
- package/dist/bridge/drivers/codex.js +3 -2
- package/dist/bridge/drivers/codex.js.map +1 -1
- package/dist/bridge/drivers/continue.js +3 -3
- package/dist/bridge/drivers/echo.js +1 -1
- package/dist/bridge/drivers/gemini.js +5 -5
- package/dist/bridge/drivers/goose.js +3 -3
- package/dist/bridge/drivers/omp.js +811 -39
- package/dist/bridge/drivers/omp.js.map +1 -1
- package/dist/bridge/drivers/opencode.js +3 -3
- package/dist/bridge/drivers/qwen.js +5 -5
- package/dist/bridge/index.js +4 -4
- package/dist/bridge/src/agent-exec.d.ts +34 -0
- package/dist/bridge/src/agent-exec.d.ts.map +1 -0
- package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
- package/dist/bridge/src/drivers/codex.d.ts.map +1 -1
- package/dist/bridge/src/drivers/omp.d.ts +287 -7
- package/dist/bridge/src/drivers/omp.d.ts.map +1 -1
- package/dist/bridge/src/index.d.ts +1 -0
- package/dist/bridge/src/index.d.ts.map +1 -1
- package/dist/bridge/src/normalizer.d.ts +5 -0
- package/dist/bridge/src/normalizer.d.ts.map +1 -1
- package/dist/{chunk-4SIMGJIU.js → chunk-3W7AOGPY.js} +19 -4
- package/dist/chunk-3W7AOGPY.js.map +1 -0
- package/dist/{chunk-3MRCJJSH.js → chunk-4BOSWZIB.js} +3 -3
- package/dist/{chunk-3MRCJJSH.js.map → chunk-4BOSWZIB.js.map} +1 -1
- package/dist/{chunk-SNW3UR3K.js → chunk-4ZWAXIHA.js} +6 -6
- package/dist/{chunk-SNW3UR3K.js.map → chunk-4ZWAXIHA.js.map} +1 -1
- package/dist/{chunk-5OAMRXJZ.js → chunk-BDCABUDR.js} +3 -3
- package/dist/{chunk-5OAMRXJZ.js.map → chunk-BDCABUDR.js.map} +1 -1
- package/dist/{chunk-ZCKQQJ7K.js → chunk-C5SMPTNV.js} +2 -2
- package/dist/{chunk-ZCKQQJ7K.js.map → chunk-C5SMPTNV.js.map} +1 -1
- package/dist/{chunk-OWMHVP6Q.js → chunk-CFBKN3BP.js} +8 -8
- package/dist/{chunk-OWMHVP6Q.js.map → chunk-CFBKN3BP.js.map} +1 -1
- package/dist/{chunk-GGRHNBUP.js → chunk-F5UGKSCI.js} +3 -3
- package/dist/{chunk-GGRHNBUP.js.map → chunk-F5UGKSCI.js.map} +1 -1
- package/dist/{chunk-N5YUCSMA.js → chunk-FQ45HI34.js} +3 -3
- package/dist/{chunk-N5YUCSMA.js.map → chunk-FQ45HI34.js.map} +1 -1
- package/dist/{chunk-LPPRZAO5.js → chunk-HRHV52XT.js} +16 -3
- package/dist/chunk-HRHV52XT.js.map +1 -0
- package/dist/{chunk-W4AEVRUC.js → chunk-MYGLPCMR.js} +6 -6
- package/dist/{chunk-W4AEVRUC.js.map → chunk-MYGLPCMR.js.map} +1 -1
- package/dist/{chunk-7GCOHM2S.js → chunk-O4NAFJLZ.js} +2 -2
- package/dist/{chunk-7GCOHM2S.js.map → chunk-O4NAFJLZ.js.map} +1 -1
- package/dist/{chunk-BWWNJLXJ.js → chunk-QTC7I2KF.js} +6 -6
- package/dist/{chunk-BWWNJLXJ.js.map → chunk-QTC7I2KF.js.map} +1 -1
- package/dist/{chunk-B6IDJRRZ.js → chunk-R6DXYDPU.js} +3 -3
- package/dist/{chunk-B6IDJRRZ.js.map → chunk-R6DXYDPU.js.map} +1 -1
- package/dist/{chunk-UBDQFZZH.js → chunk-S3ZR5G45.js} +5 -5
- package/dist/{chunk-UBDQFZZH.js.map → chunk-S3ZR5G45.js.map} +1 -1
- package/dist/{chunk-VIX4AEVZ.js → chunk-UI4ICUTH.js} +4 -4
- package/dist/{chunk-VIX4AEVZ.js.map → chunk-UI4ICUTH.js.map} +1 -1
- package/dist/chunk-X5O56UO4.js +151 -0
- package/dist/chunk-X5O56UO4.js.map +1 -0
- package/dist/{chunk-WTUHM2NJ.js → chunk-XFYURHVP.js} +2 -2
- package/dist/{chunk-WTUHM2NJ.js.map → chunk-XFYURHVP.js.map} +1 -1
- package/dist/{chunk-I66MW6OR.js → chunk-XV6SM6LT.js} +3 -3
- package/dist/{chunk-I66MW6OR.js.map → chunk-XV6SM6LT.js.map} +1 -1
- package/dist/{chunk-76IGAJ47.js → chunk-ZHHIIIZU.js} +3 -3
- package/dist/{chunk-76IGAJ47.js.map → chunk-ZHHIIIZU.js.map} +1 -1
- package/dist/cli/index.js +32 -32
- package/dist/connectors/index.js +5 -5
- package/dist/{ensure-sources-PTZJZE56.js → ensure-sources-ZURLP3YP.js} +4 -4
- package/dist/{ensure-sources-PTZJZE56.js.map → ensure-sources-ZURLP3YP.js.map} +1 -1
- package/dist/factory-assets/connectors/box.js +5 -5
- package/dist/factory-assets/connectors/deploy.js +5 -5
- package/dist/factory-assets/connectors/devserver.js +5 -5
- package/dist/factory-assets/connectors/flow/adapter.js +5 -5
- package/dist/factory-assets/connectors/flow/run-flow.js +6 -6
- package/dist/factory-assets/connectors/flow.js +5 -5
- package/dist/factory-assets/connectors/git.js +5 -5
- package/dist/factory-assets/connectors/gmail.js +5 -5
- package/dist/factory-assets/connectors/googledrive.js +5 -5
- package/dist/factory-assets/connectors/local.js +5 -5
- package/dist/factory-assets/connectors/mattermost.js +5 -5
- package/dist/factory-assets/connectors/memory.js +5 -5
- package/dist/factory-assets/connectors/minio.js +5 -5
- package/dist/factory-assets/connectors/postgres.js +5 -5
- package/dist/factory-assets/connectors/s3.js +5 -5
- package/dist/factory-assets/connectors/sharepoint.js +5 -5
- package/dist/factory-assets/connectors/sqlite.js +5 -5
- package/dist/factory-assets/connectors/static-server.js +5 -5
- package/dist/factory-assets/connectors/tunnel.js +5 -5
- package/dist/factory-assets/connectors/webdav.js +5 -5
- package/dist/factory-assets/connectors/xstate-store.js +5 -5
- package/dist/factory-assets/connectors/xstate.js +5 -5
- package/dist/{helpers-TPVJ6XTD.js → helpers-5KGGPDGO.js} +4 -4
- package/dist/{helpers-TPVJ6XTD.js.map → helpers-5KGGPDGO.js.map} +1 -1
- package/dist/{library-publish-4XE5BQTE.js → library-publish-ZQXODG4U.js} +6 -6
- package/dist/{library-publish-4XE5BQTE.js.map → library-publish-ZQXODG4U.js.map} +1 -1
- package/dist/{open-library-WFTFVJ67.js → open-library-FWUMCU7A.js} +5 -5
- package/dist/{open-library-WFTFVJ67.js.map → open-library-FWUMCU7A.js.map} +1 -1
- package/dist/{project-FI5A4E7X.js → project-UCWVE4YV.js} +5 -5
- package/dist/{project-FI5A4E7X.js.map → project-UCWVE4YV.js.map} +1 -1
- package/dist/runner/index.js +8 -8
- package/dist/sdk/asset-manager.js +1 -1
- package/dist/sdk/bridge.js +4 -4
- package/dist/sdk/index.js +8 -8
- package/dist/sdk/runner.js +8 -8
- package/dist/{setup-EDSTI5LM.js → setup-ACVYCGYQ.js} +9 -9
- package/dist/{setup-EDSTI5LM.js.map → setup-ACVYCGYQ.js.map} +1 -1
- package/dist/{source-7VNEUEGO.js → source-55ORZZU7.js} +6 -6
- package/dist/{source-7VNEUEGO.js.map → source-55ORZZU7.js.map} +1 -1
- package/dist/{source-manifest-publish-SQ2SXJEA.js → source-manifest-publish-2GMXNX6M.js} +7 -7
- package/dist/{source-manifest-publish-SQ2SXJEA.js.map → source-manifest-publish-2GMXNX6M.js.map} +1 -1
- package/dist/tui/index.js +8 -8
- package/dist/workspace-plugin/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-4SIMGJIU.js.map +0 -1
- package/dist/chunk-LPPRZAO5.js.map +0 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
631
|
-
*
|
|
632
|
-
*
|
|
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
|
|
1234
|
+
async captureState(options = {}) {
|
|
1235
|
+
const announceSession = options.announceSession ?? true;
|
|
635
1236
|
try {
|
|
636
|
-
const
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|