openmausbot 0.1.87 → 0.1.89
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/README.md +2 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-BTiuL79F.js → abnfDiagram-O67JEVCF-BwaMaZEm.js} +1 -1
- package/dist/assets/{arc-C88Blc2v.js → arc-Be-8GKxd.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-BZsJIZrb.js → architectureDiagram-NJMV4G6O-Dp_U2Kv1.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CVeMe9Ah.js → blockDiagram-BEXU5L5S-DQjUjYUk.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-Cedk3Bhx.js → c4Diagram-YGBWAQC7-Bd3aE4Kc.js} +1 -1
- package/dist/assets/channel-DqY_gMLg.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-D-VRqzTB.js → chunk-2Q5K7J3B-BijjTBeJ.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BChov01Y.js → chunk-3FUC2YCW-CMGdwvzW.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-C-TfwvHy.js → chunk-5VM5RSS4-0G09BfsZ.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Db7CGj5Y.js → chunk-GTNCS2PH-FOp3vSHe.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-CNvIfqKT.js → chunk-GWA4HPMP-Dv-20VIn.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-C_bEp5tq.js → chunk-JWPE2WC7-CRgrdFcz.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-Coi4BOls.js → chunk-NETBCI7D-3hP4f7QB.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CzylEuL3.js → chunk-WEXAMYUT-CG98IjlG.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-m7p7ztaD.js → chunk-XXDRQBXY-BrSTC9xG.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BMy6o9x2.js → classDiagram-v2-NBCMYWYE-rpQQQDFX.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BDQ__0_J.js → cose-bilkent-JH36ORCC-G6RSafHA.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-Djv4ghUb.js → cynefin-OW5HDTMX-CXjv-IM2.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BULrlGYH.js → cynefinDiagram-VND7K2PF-Dw07MR9f.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-D-jrT_hU.js → dagre-6A5THRUB-BwPnlTg2.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-p2UhQmfL.js → diagram-22UHCM2B-Cm6xcMiz.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-DHvGWZ7q.js → diagram-3UASUU5V-CDpogPbE.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-ChhKDot-.js → diagram-ATOU4E4O-BV8z3Pnc.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-B-T7A43R.js → diagram-CDSNMT55-CKCQhOOC.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-CJm8ENNY.js → diagram-MLGK6HIB-FGvJg69C.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-510tlza8.js → diagram-MPIPVDR6-CwATvgnS.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-PkvRPHel.js → ebnfDiagram-ZINNZB2B-CH_c3SSC.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BVBVPoX3.js → elk-276RUBZZ-3guZi71R.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BgYMVm7L.js → erDiagram-OPXOYQCR-BMHphR2n.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-BGqsnc_3.js → flowDiagram-KWPJA3E3-Vm5DkW5x.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-D_-UeReH.js → ganttDiagram-FUAMR5RP-DO-1ct07.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-D0uooONx.js → gitGraphDiagram-X574FWY7-nxeksEIN.js} +1 -1
- package/dist/assets/index-BIMdIbaw.css +1 -0
- package/dist/assets/index-BVPdbq2r.js +583 -0
- package/dist/assets/{index-zRsJERGJ.js → index-Oxk9x45i.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-B9e1z77f.js → infoDiagram-VRGFBTTK-Dkcvr0p7.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BB4vNwvD.js → ishikawaDiagram-OU5B5YK6-BwVibkoO.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DpkosOTB.js → journeyDiagram-ZHPQQLJL-ByiBZqgG.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DbDP4kUu.js → kanban-definition-PNTS6WVX-qqjgXBF3.js} +1 -1
- package/dist/assets/{linear-DBJGPmjT.js → linear-CtJnKe6A.js} +1 -1
- package/dist/assets/{mermaid.core-DWPRi-ue.js → mermaid.core-BEiDklrY.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-nszBbJmx.js → mindmap-definition-NLK3R4M7-DfIYHia9.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-CWykJWu_.js → pegDiagram-GJSIUBJH-CYyRqa1a.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-g3Da-Vnv.js → pieDiagram-5QR66LMP-hOToy_zH.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Wzrlpzgh.js → quadrantDiagram-O4NWA36T-DKAsNN9I.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-moHTFMC0.js → railroadDiagram-XR7U4H2S-BSA-d8JU.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CwfXdFH8.js → requirementDiagram-PLB6GJNP-CSmlvb-p.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-3ZROYvnq.js → sankeyDiagram-IPEJSGJF-CD5F6fnG.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-E1TBebP8.js → sequenceDiagram-PO4LG4MO-JUq5sx6d.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-DHKEU5pu.js → sizeCapture-INFHLROL-BjIS-Stk.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BguEj09D.js → stateDiagram-v2-GCMORJYK-DklkUxOg.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-C7IDYnhd.js → swimlanes-2SLR337P-CsjIU6be.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-Bk14M0_B.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-DCW-F5PW.js → timeline-definition-EJHVYXUP-BkFqK7AB.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-xBMFtWYV.js → usecaseDiagram-POWQR4AR-70gJB1EU.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-B5nIkuH8.js → vennDiagram-UO4OBE2U-83Tm62dN.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DYFAhdbr.js → wardleyDiagram-VNRHLVJA-tTmseoX9.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DeXwX6Cz.js → xychartDiagram-PMCCYNJV-R1LBA55a.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/companion/src/routes.js +8 -1
- package/dist-server/connector-proxy.js +105 -1
- package/dist-server/container-mcp.js +11 -1
- package/dist-server/drivers/agents-proxy.js +277 -22
- package/dist-server/index.js +23722 -20272
- package/dist-server/local-computer-proxy.js +2 -0
- package/dist-server/mcp-gate.js +2 -0
- package/dist-server/mcp-server.js +5 -3
- package/dist-server/message-search.worker.js +52 -0
- package/dist-server/openmausbot.js +755 -404
- package/dist-server/pair-cli.js +755 -404
- package/dist-server/server/admission.js +81 -0
- package/dist-server/server/aside-queue.js +294 -0
- package/dist-server/server/attachments.js +43 -1
- package/dist-server/server/automatic-recovery.js +15 -0
- package/dist-server/server/bot-attachment.js +107 -0
- package/dist-server/server/bot-overview.js +19 -0
- package/dist-server/server/composio.js +297 -1
- package/dist-server/server/config.js +34 -3
- package/dist-server/server/connector-advertisement.js +165 -0
- package/dist-server/server/connector-proxy.js +14 -2
- package/dist-server/server/connector-verdict.js +31 -4
- package/dist-server/server/container-computer.js +70 -0
- package/dist-server/server/contracts.js +32 -0
- package/dist-server/server/drivers/acp/core.js +148 -23
- package/dist-server/server/drivers/acp/cursor.js +3 -7
- package/dist-server/server/drivers/acp/droid.js +4 -7
- package/dist-server/server/drivers/acp/gemini.js +4 -4
- package/dist-server/server/drivers/acp/grok.js +4 -9
- package/dist-server/server/drivers/acp/hermes.js +3 -5
- package/dist-server/server/drivers/acp/opencode-go.js +2 -5
- package/dist-server/server/drivers/acp/qwen.js +12 -15
- package/dist-server/server/drivers/agents-call.js +137 -10
- package/dist-server/server/drivers/agents-catalog.js +100 -6
- package/dist-server/server/drivers/boxagent.js +3 -17
- package/dist-server/server/drivers/claude.js +31 -17
- package/dist-server/server/drivers/codex-catalog.js +14 -6
- package/dist-server/server/drivers/codex-identity.js +2 -1
- package/dist-server/server/drivers/codex-release.js +48 -0
- package/dist-server/server/drivers/codex.js +7 -25
- package/dist-server/server/drivers/openai-chat.js +31 -3
- package/dist-server/server/drivers/pi.js +237 -30
- package/dist-server/server/drivers/prompt-split.js +32 -9
- package/dist-server/server/drivers/session-idle.js +29 -0
- package/dist-server/server/env-path.js +15 -0
- package/dist-server/server/guarded-requests.js +2 -2
- package/dist-server/server/harness-capabilities.js +61 -0
- package/dist-server/server/index.js +1173 -262
- package/dist-server/server/message-db.js +22 -42
- package/dist-server/server/message-file.js +9 -1
- package/dist-server/server/message-search-query.js +43 -0
- package/dist-server/server/message-search-worker.js +80 -0
- package/dist-server/server/message-search.worker.js +21 -0
- package/dist-server/server/model-requests.js +248 -0
- package/dist-server/server/options-card.js +21 -0
- package/dist-server/server/org-library.js +15 -1
- package/dist-server/server/peer-delivery.js +60 -0
- package/dist-server/server/peer-roster.js +57 -0
- package/dist-server/server/presets.js +32 -32
- package/dist-server/server/profile-requests.js +66 -39
- package/dist-server/server/profile-revision.js +9 -1
- package/dist-server/server/profile-versions.js +6 -3
- package/dist-server/server/routine-requests.js +29 -7
- package/dist-server/server/skill-fetch.js +72 -14
- package/dist-server/server/skills.js +1 -0
- package/dist-server/server/store.js +32 -2
- package/dist-server/server/system-prompt.js +37 -2
- package/dist-server/server/team-setup-requests.js +124 -23
- package/dist-server/server/thread-events.js +3 -1
- package/dist-server/server/tightening-requests.js +405 -0
- package/dist-server/server/tts/chatterbox.js +27 -5
- package/dist-server/server/usage-ledger.js +7 -1
- package/dist-server/server/webhooks.js +21 -3
- package/dist-server/server/workspace-backup.js +40 -3
- package/dist-server/server/workspace-backup.worker.js +10 -0
- package/dist-server/shared/ask-question.js +19 -0
- package/dist-server/shared/credential-request.js +5 -1
- package/dist-server/shared/line-diff.js +23 -0
- package/dist-server/shared/model-request.js +1 -0
- package/dist-server/shared/options-card.js +51 -0
- package/dist-server/shared/profile-request.js +5 -4
- package/dist-server/shared/tightening-request.js +87 -0
- package/dist-server/src/lib/composer-attachments.js +42 -3
- package/dist-server/vps-container-mcp.js +11 -1
- package/dist-server/workspace-backup.worker.js +19482 -0
- package/package.json +1 -1
- package/dist/assets/channel-taCNRlUU.js +0 -1
- package/dist/assets/index-BwdMAQ6p.css +0 -1
- package/dist/assets/index-ByJennr7.js +0 -581
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +0 -8
|
@@ -26,6 +26,7 @@ import { join } from "node:path";
|
|
|
26
26
|
import { PROVIDER_CREDENTIAL_ENV, stripWorkspaceCredentialEnv } from "../config.js";
|
|
27
27
|
import { augmentedPath } from "../env-path.js";
|
|
28
28
|
import { describeSpawnFailure, execCli, killCliTree, spawnCli } from "../procs.js";
|
|
29
|
+
import { deletePromptSplitReceipt, promptHalves, readPromptSplitReceipt, splitSessionPrompt, writePromptSplitReceipt, } from "./prompt-split.js";
|
|
29
30
|
import { SPAWNED_PROXIES } from "../proxy-paths.js";
|
|
30
31
|
import { commandSummary, toolDetailPreview } from "../tool-summary.js";
|
|
31
32
|
import { EFFORT_LEVELS } from "../../shared/wire.js";
|
|
@@ -37,6 +38,21 @@ const DRIVER_KIND = "piAgent";
|
|
|
37
38
|
const PI_ARGS = ["--mode", "rpc", "--no-session"];
|
|
38
39
|
const PI_MODEL_UPDATE_ARGS = ["update", "--models", "--no-approve"];
|
|
39
40
|
const NODE_ENV_FLAG = { ELECTRON_RUN_AS_NODE: "1" };
|
|
41
|
+
/** After this many bare turns on one pi session the full prompt rides
|
|
42
|
+
* again even without a compaction event: any history rewrite the events
|
|
43
|
+
* miss (an older pi, a missed line) still loses at most this many turns
|
|
44
|
+
* of standing instructions instead of the rest of the session. */
|
|
45
|
+
const PI_PROMPT_RE_ANCHOR_TURNS = 8;
|
|
46
|
+
/** How long the driver keeps listening after a finished turn_end for the
|
|
47
|
+
* run's terminal agent_end before settling anyway: a runtime that never
|
|
48
|
+
* emits agent_end still completes, while one that does cancels the wait. */
|
|
49
|
+
const PI_AGENT_END_GRACE_MS = 60_000;
|
|
50
|
+
/** pi answered a command with success:false — an explicit refusal from a
|
|
51
|
+
* live runtime, not a transport failure. Only steer branches on the
|
|
52
|
+
* difference today ("refused": provably not delivered, safe to re-queue);
|
|
53
|
+
* the handshake commands keep their catch-all ignore. */
|
|
54
|
+
class PiRpcRefusalError extends Error {
|
|
55
|
+
}
|
|
40
56
|
function readPiPromptImages(turn) {
|
|
41
57
|
return (turn.images ?? []).map((image) => ({
|
|
42
58
|
type: "image",
|
|
@@ -431,6 +447,13 @@ export const PiDriver = {
|
|
|
431
447
|
// closure (send, child) alive until it fires.
|
|
432
448
|
const askTimers = new Set();
|
|
433
449
|
let settled = false;
|
|
450
|
+
// the last turn_end's outcome, replayed when the run's terminal
|
|
451
|
+
// agent_end (or the grace fallback) settles the turn
|
|
452
|
+
let endStopReason;
|
|
453
|
+
let endUsage;
|
|
454
|
+
let endErrorMessage;
|
|
455
|
+
// hang insurance for runtimes with no agent_end frame at all
|
|
456
|
+
let agentEndTimer = null;
|
|
434
457
|
// pi's RPC surface accepts image content directly. Read before spawning
|
|
435
458
|
// so an attachment that disappeared produces one clear dispatch error
|
|
436
459
|
// instead of starting a child that can never receive its prompt.
|
|
@@ -493,8 +516,17 @@ export const PiDriver = {
|
|
|
493
516
|
})();
|
|
494
517
|
let buf = "";
|
|
495
518
|
let assistantText = "";
|
|
519
|
+
// set when a compaction event arrives this turn; gates the receipt
|
|
520
|
+
// write below so a compacted-around delivery is never claimed
|
|
521
|
+
let compactionObserved = false;
|
|
496
522
|
// resolve one-shot RPC responses (new_session / switch_session / set_model)
|
|
497
523
|
const responseWaiters = new Map();
|
|
524
|
+
// Steer frames correlate by a unique per-request id, not the shared
|
|
525
|
+
// "steer" command name: two concurrent steers would otherwise overwrite
|
|
526
|
+
// each other's waiter, and a refusal response could reject the wrong
|
|
527
|
+
// caller — requeueing words pi already accepted. Every other command
|
|
528
|
+
// keeps its command-name key.
|
|
529
|
+
let steerSeq = 0;
|
|
498
530
|
const rejectWaiters = (err) => {
|
|
499
531
|
for (const waiter of responseWaiters.values()) {
|
|
500
532
|
clearTimeout(waiter.timer);
|
|
@@ -502,13 +534,13 @@ export const PiDriver = {
|
|
|
502
534
|
}
|
|
503
535
|
responseWaiters.clear();
|
|
504
536
|
};
|
|
505
|
-
const awaitResponse = (command, timeoutMs = 20_000) => new Promise((resolve, reject) => {
|
|
537
|
+
const awaitResponse = (command, timeoutMs = 20_000, key = command) => new Promise((resolve, reject) => {
|
|
506
538
|
const timer = setTimeout(() => {
|
|
507
|
-
responseWaiters.delete(
|
|
539
|
+
responseWaiters.delete(key);
|
|
508
540
|
reject(new Error(`pi ${command} timed out`));
|
|
509
541
|
}, timeoutMs);
|
|
510
542
|
timer.unref?.();
|
|
511
|
-
responseWaiters.set(
|
|
543
|
+
responseWaiters.set(key, { resolve, reject, timer });
|
|
512
544
|
});
|
|
513
545
|
child.stdin.on("error", () => rejectWaiters(new Error("pi stdin closed")));
|
|
514
546
|
const send = (obj) => {
|
|
@@ -527,6 +559,10 @@ export const PiDriver = {
|
|
|
527
559
|
if (settled)
|
|
528
560
|
return;
|
|
529
561
|
settled = true;
|
|
562
|
+
if (agentEndTimer) {
|
|
563
|
+
clearTimeout(agentEndTimer);
|
|
564
|
+
agentEndTimer = null;
|
|
565
|
+
}
|
|
530
566
|
// The turn is over: drop unanswered asks so their 15-minute
|
|
531
567
|
// fail-safe timers are cancelled outright instead of no-oping on a
|
|
532
568
|
// dead child while holding the ask closure alive.
|
|
@@ -564,6 +600,23 @@ export const PiDriver = {
|
|
|
564
600
|
}
|
|
565
601
|
active.delete(threadId);
|
|
566
602
|
};
|
|
603
|
+
/** Settle from the last turn_end's recorded outcome. Errors surface
|
|
604
|
+
* here, at run completion, so a failed run reports exactly once and
|
|
605
|
+
* only after any scheduled maintenance events had their chance to
|
|
606
|
+
* arrive. */
|
|
607
|
+
const finishRun = () => {
|
|
608
|
+
const sr = endStopReason;
|
|
609
|
+
if (sr === "error" || sr === "failed") {
|
|
610
|
+
emit({
|
|
611
|
+
...base(threadId, turnId),
|
|
612
|
+
type: "runtime.error",
|
|
613
|
+
message: String(endErrorMessage ?? "pi turn failed").slice(0, 2_000),
|
|
614
|
+
});
|
|
615
|
+
settle(false, "failed", endUsage);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
settle(true, sr === "cancelled" || sr === "aborted" ? "cancelled" : "end_turn", endUsage);
|
|
619
|
+
};
|
|
567
620
|
const stop = () => {
|
|
568
621
|
try {
|
|
569
622
|
send({ type: "abort" });
|
|
@@ -579,19 +632,53 @@ export const PiDriver = {
|
|
|
579
632
|
}
|
|
580
633
|
settle(true, "cancelled");
|
|
581
634
|
};
|
|
582
|
-
|
|
635
|
+
// Mid-turn input rides pi's native steer frame — the runtime queues
|
|
636
|
+
// it into the running agent without aborting the in-flight step, and
|
|
637
|
+
// answers success only after session.steer() accepted it. A success:
|
|
638
|
+
// false response is an explicit refusal (nothing consumed the words:
|
|
639
|
+
// safe to re-queue), while a death or timeout after the frame was
|
|
640
|
+
// written leaves the outcome unknowable — those are "indeterminate"
|
|
641
|
+
// so the caller can never run the words twice. Each frame carries a
|
|
642
|
+
// unique id that pi echoes on its response, so concurrent steers on
|
|
643
|
+
// the same turn never share or overwrite a waiter.
|
|
644
|
+
const steerActiveTurn = async (text) => {
|
|
645
|
+
if (settled || child.exitCode !== null || child.signalCode !== null)
|
|
646
|
+
return "refused";
|
|
647
|
+
let ack;
|
|
648
|
+
try {
|
|
649
|
+
const id = `steer:${++steerSeq}`;
|
|
650
|
+
ack = awaitResponse("steer", 20_000, id);
|
|
651
|
+
send({ type: "steer", id, message: text });
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
// stdin closed before the frame was written: provably undelivered.
|
|
655
|
+
return "refused";
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
await ack;
|
|
659
|
+
return "steered";
|
|
660
|
+
}
|
|
661
|
+
catch (error) {
|
|
662
|
+
return error instanceof PiRpcRefusalError ? "refused" : "indeterminate";
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
active.set(threadId, { stop, turnId, pending, child, steer: steerActiveTurn });
|
|
583
666
|
const onEvent = (evt) => {
|
|
584
667
|
appendNative(threadId, { dir: "in", source: "pi.rpc", msg: evt });
|
|
585
668
|
switch (evt.type) {
|
|
586
669
|
case "response": {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
670
|
+
// Resolve the echoed request id first (unique per steer);
|
|
671
|
+
// fall back to the command name for the handshake-style call
|
|
672
|
+
// sites, whose responses carry no id.
|
|
673
|
+
const key = typeof evt.id === "string" && responseWaiters.has(evt.id) ? evt.id : evt.command;
|
|
674
|
+
if (key && responseWaiters.has(key)) {
|
|
675
|
+
const waiter = responseWaiters.get(key);
|
|
676
|
+
responseWaiters.delete(key);
|
|
590
677
|
clearTimeout(waiter.timer);
|
|
591
678
|
if (evt.success)
|
|
592
679
|
waiter.resolve(evt.data);
|
|
593
680
|
else
|
|
594
|
-
waiter.reject(new
|
|
681
|
+
waiter.reject(new PiRpcRefusalError(`pi ${evt.command} failed`));
|
|
595
682
|
}
|
|
596
683
|
return;
|
|
597
684
|
}
|
|
@@ -715,24 +802,81 @@ export const PiDriver = {
|
|
|
715
802
|
}
|
|
716
803
|
return;
|
|
717
804
|
}
|
|
805
|
+
case "compaction_start":
|
|
806
|
+
case "compaction_end":
|
|
807
|
+
case "auto_compaction_start":
|
|
808
|
+
case "auto_compaction_end": {
|
|
809
|
+
// pi compaction (manual or automatic) summarizes older
|
|
810
|
+
// messages, which can absorb the turn that carried this
|
|
811
|
+
// session's standing prompt. Both events ride the RPC surface
|
|
812
|
+
// for every compaction kind, so the moment either arrives the
|
|
813
|
+
// receipt is dropped and the next turn re-establishes the full
|
|
814
|
+
// prompt. The rest of the compacting turn may still answer
|
|
815
|
+
// without its standing instructions; that single turn is the
|
|
816
|
+
// unavoidable cost of summarizing history mid-session.
|
|
817
|
+
if (sessionReady && sessionFile) {
|
|
818
|
+
compactionObserved = true;
|
|
819
|
+
deletePromptSplitReceipt("pi", JSON.stringify([threadId, sessionFile]));
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
718
823
|
case "turn_end":
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
824
|
+
{
|
|
825
|
+
// a killed child can still flush one buffered frame after the
|
|
826
|
+
// turn settled (the abort path's cancelled turn_end is the
|
|
827
|
+
// common one); arming the grace timer then would leak it past
|
|
828
|
+
// a turn that is already complete.
|
|
829
|
+
if (settled)
|
|
830
|
+
return;
|
|
831
|
+
const sr = evt.message?.stopReason;
|
|
832
|
+
// toolUse means pi ran a tool and auto-continues next turn to
|
|
833
|
+
// answer — the run is not over, and the next turn_end carries
|
|
834
|
+
// the real outcome.
|
|
835
|
+
if (sr === "toolUse" || sr === "tool_use" || sr === "tool_calls")
|
|
836
|
+
return;
|
|
837
|
+
endStopReason = sr;
|
|
838
|
+
endUsage = evt.usage ?? evt.message?.usage;
|
|
839
|
+
endErrorMessage = evt.message?.errorMessage;
|
|
840
|
+
// agent_end, not turn_end, closes the run: pi can schedule
|
|
841
|
+
// post-run maintenance (overflow-recovery compaction) after
|
|
842
|
+
// the final turn_end, and only the terminal agent_end proves
|
|
843
|
+
// the session is done rewriting itself. The grace timer
|
|
844
|
+
// settles a runtime that never sends agent_end at all.
|
|
845
|
+
if (agentEndTimer)
|
|
846
|
+
clearTimeout(agentEndTimer);
|
|
847
|
+
agentEndTimer = setTimeout(finishRun, PI_AGENT_END_GRACE_MS);
|
|
848
|
+
agentEndTimer.unref?.();
|
|
724
849
|
return;
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
850
|
+
}
|
|
851
|
+
case "agent_end": {
|
|
852
|
+
// Two dialects mark a non-terminal agent_end: upstream pi sets
|
|
853
|
+
// willRetry: true when an automatic retry follows (overflow
|
|
854
|
+
// recovery runs compaction and retries as a fresh run), and the
|
|
855
|
+
// omp fork sets isTerminal: false while maintenance or async
|
|
856
|
+
// delivery has more work scheduled. Either marker means the
|
|
857
|
+
// session may still rewrite the turn that carried this
|
|
858
|
+
// session's standing prompt — keep listening so those events
|
|
859
|
+
// can drop the prompt-split receipt.
|
|
860
|
+
if (evt.isTerminal === false || evt.willRetry === true)
|
|
733
861
|
return;
|
|
862
|
+
if (agentEndTimer) {
|
|
863
|
+
clearTimeout(agentEndTimer);
|
|
864
|
+
agentEndTimer = null;
|
|
865
|
+
}
|
|
866
|
+
finishRun();
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
case "agent_settled": {
|
|
870
|
+
// upstream pi's explicit fully-settled guarantee: no automatic
|
|
871
|
+
// retry, compaction retry, or queued continuation remains.
|
|
872
|
+
// Usually settles one frame after the final agent_end already
|
|
873
|
+
// did; kept as its own terminal signal so the driver never
|
|
874
|
+
// depends on which dialect the configured cli speaks.
|
|
875
|
+
if (agentEndTimer) {
|
|
876
|
+
clearTimeout(agentEndTimer);
|
|
877
|
+
agentEndTimer = null;
|
|
734
878
|
}
|
|
735
|
-
|
|
879
|
+
finishRun();
|
|
736
880
|
return;
|
|
737
881
|
}
|
|
738
882
|
default:
|
|
@@ -774,6 +918,7 @@ export const PiDriver = {
|
|
|
774
918
|
// sessionFile, which switch_session expects as `sessionPath`.
|
|
775
919
|
const sessionPath = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
776
920
|
let sessionFile = sessionPath;
|
|
921
|
+
let sessionReady = false;
|
|
777
922
|
try {
|
|
778
923
|
const command = sessionPath ? "switch_session" : "new_session";
|
|
779
924
|
const hsPromise = awaitResponse(command);
|
|
@@ -781,6 +926,7 @@ export const PiDriver = {
|
|
|
781
926
|
const hs = (await hsPromise);
|
|
782
927
|
if (hs?.sessionFile)
|
|
783
928
|
sessionFile = hs.sessionFile;
|
|
929
|
+
sessionReady = true;
|
|
784
930
|
emit({
|
|
785
931
|
...base(threadId, turnId),
|
|
786
932
|
type: "session.started",
|
|
@@ -816,17 +962,71 @@ export const PiDriver = {
|
|
|
816
962
|
/* keep going on the engine default */
|
|
817
963
|
}
|
|
818
964
|
}
|
|
819
|
-
//
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
|
|
965
|
+
// The stable/volatile split: the full prompt rides only the turn that
|
|
966
|
+
// establishes - or re-instructs, after a soul edit - this pi session,
|
|
967
|
+
// which previously re-sent the whole system prompt on every turn and
|
|
968
|
+
// let it accumulate in the session file once per turn. pi compacts
|
|
969
|
+
// long sessions by summarizing older messages, which can absorb the
|
|
970
|
+
// turn that carried the prompt, so the driver watches pi's
|
|
971
|
+
// compaction events and drops the receipt the moment one arrives,
|
|
972
|
+
// and re-anchors the full prompt every PI_PROMPT_RE_ANCHOR_TURNS
|
|
973
|
+
// bare turns as a backstop for anything the events miss. Receipts
|
|
974
|
+
// are durable because the session file outlives both the per-turn
|
|
975
|
+
// child and this process. Without a session the prompt is the
|
|
976
|
+
// model's only context, so that turn keeps the full block and writes
|
|
977
|
+
// no receipt.
|
|
978
|
+
const halves = promptHalves(turn);
|
|
979
|
+
let message;
|
|
980
|
+
let pendingReceipt = null;
|
|
981
|
+
if (halves.stable !== null && sessionReady && sessionFile) {
|
|
982
|
+
const receiptKey = JSON.stringify([threadId, sessionFile]);
|
|
983
|
+
const composed = splitSessionPrompt(halves.stable, halves.volatile, readPromptSplitReceipt("pi", receiptKey), turn.system, turn.text, Boolean(turn.mentionTurn), PI_PROMPT_RE_ANCHOR_TURNS);
|
|
984
|
+
message = composed.text;
|
|
985
|
+
pendingReceipt = { key: receiptKey, receipt: composed.receipt };
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
message = turn.system ? `${turn.system}\n\n${turn.text}` : turn.text;
|
|
989
|
+
}
|
|
990
|
+
// events cannot interleave with this synchronous stretch, so any
|
|
991
|
+
// compaction recorded above already invalidated what compose read
|
|
826
992
|
try {
|
|
993
|
+
// pi answers the prompt with an authoritative response only after
|
|
994
|
+
// preflight accepts it - and rejects it outright while a
|
|
995
|
+
// compaction runs - so the receipt commits only after that
|
|
996
|
+
// acceptance: a rejected or compacted-around send redelivers on
|
|
997
|
+
// the next turn. The wait is long enough to survive a compaction
|
|
998
|
+
// queue; a dead child rejects it immediately.
|
|
999
|
+
// sendTurn still resolves once the prompt is written: a child that
|
|
1000
|
+
// dies before replying settles the turn through the close handler,
|
|
1001
|
+
// whose waiter sweep can precede this registration.
|
|
1002
|
+
const accepted = awaitResponse("prompt", 120_000);
|
|
827
1003
|
send({ type: "prompt", message, ...(images.length ? { images } : {}) });
|
|
1004
|
+
void accepted.then(() => {
|
|
1005
|
+
if (!pendingReceipt || compactionObserved)
|
|
1006
|
+
return;
|
|
1007
|
+
try {
|
|
1008
|
+
writePromptSplitReceipt("pi", pendingReceipt.key, pendingReceipt.receipt);
|
|
1009
|
+
}
|
|
1010
|
+
catch {
|
|
1011
|
+
/* an unwritten receipt only re-delivers the full prompt next turn */
|
|
1012
|
+
}
|
|
1013
|
+
}, (err) => {
|
|
1014
|
+
if (settled)
|
|
1015
|
+
return;
|
|
1016
|
+
emit({
|
|
1017
|
+
...base(threadId, turnId),
|
|
1018
|
+
type: "runtime.error",
|
|
1019
|
+
message: String(err?.message ?? err).slice(0, 2_000),
|
|
1020
|
+
});
|
|
1021
|
+
settle(false);
|
|
1022
|
+
});
|
|
828
1023
|
}
|
|
829
|
-
catch {
|
|
1024
|
+
catch (err) {
|
|
1025
|
+
emit({
|
|
1026
|
+
...base(threadId, turnId),
|
|
1027
|
+
type: "runtime.error",
|
|
1028
|
+
message: String(err?.message ?? err).slice(0, 2_000),
|
|
1029
|
+
});
|
|
830
1030
|
settle(false);
|
|
831
1031
|
}
|
|
832
1032
|
return { turnId };
|
|
@@ -899,6 +1099,9 @@ export const PiDriver = {
|
|
|
899
1099
|
// xhigh/max only land on models that expose them; pi rejects an
|
|
900
1100
|
// unsupported level and the turn keeps the engine default.
|
|
901
1101
|
effortLevels: EFFORT_LEVELS,
|
|
1102
|
+
// pi's RPC mode takes a mid-turn steer frame the runtime queues
|
|
1103
|
+
// into the running agent without aborting the current step.
|
|
1104
|
+
queueing: true,
|
|
902
1105
|
},
|
|
903
1106
|
sendTurn,
|
|
904
1107
|
interruptTurn: async (threadId) => active.get(threadId)?.stop(),
|
|
@@ -918,6 +1121,10 @@ export const PiDriver = {
|
|
|
918
1121
|
});
|
|
919
1122
|
return decision.behavior === "allow" ? "allowed-once" : decision.behavior === "answer" ? "answered" : "rejected";
|
|
920
1123
|
},
|
|
1124
|
+
steer: async (threadId, text) => {
|
|
1125
|
+
const entry = active.get(threadId);
|
|
1126
|
+
return entry?.steer ? await entry.steer(text) : "refused";
|
|
1127
|
+
},
|
|
921
1128
|
hasSession: (threadId) => active.has(threadId),
|
|
922
1129
|
stopAll: async () => {
|
|
923
1130
|
for (const { stop } of active.values())
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// work, recent work) legitimately changes mid-conversation and reaches
|
|
6
6
|
// the model inside the turn that changed it, after the cacheable prefix.
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { mkdirSync, readFileSync } from "node:fs";
|
|
8
|
+
import { mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { DATA_DIR } from "../config.js";
|
|
11
11
|
import { writeFileAtomic } from "../atomic.js";
|
|
@@ -53,7 +53,11 @@ export function readPromptSplitReceipt(scope, key) {
|
|
|
53
53
|
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
54
54
|
const record = raw;
|
|
55
55
|
if (typeof record.stable === "string" && typeof record.volatile === "string") {
|
|
56
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
stable: record.stable,
|
|
58
|
+
volatile: record.volatile,
|
|
59
|
+
...(typeof record.turnsSinceFull === "number" ? { turnsSinceFull: record.turnsSinceFull } : {}),
|
|
60
|
+
};
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
}
|
|
@@ -66,6 +70,18 @@ export function writePromptSplitReceipt(scope, key, receipt) {
|
|
|
66
70
|
mkdirSync(join(DATA_DIR, "prompt-split"), { recursive: true });
|
|
67
71
|
writeFileAtomic(receiptPath(scope, key), JSON.stringify(receipt), { mode: 0o600 });
|
|
68
72
|
}
|
|
73
|
+
/** Drop a session's receipt: its history was rewritten (pi compaction
|
|
74
|
+
* summarizes older messages), so what it last carried is unknown and the
|
|
75
|
+
* next turn re-delivers the full prompt. A missing receipt already reads
|
|
76
|
+
* as unknown, so deleting one is always safe. */
|
|
77
|
+
export function deletePromptSplitReceipt(scope, key) {
|
|
78
|
+
try {
|
|
79
|
+
rmSync(receiptPath(scope, key));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
/* a missing receipt is already unknown */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
69
85
|
/** Compose the prompt for a session that persists its own history (ACP
|
|
70
86
|
* agents, pi): the full system block rides only when this native session
|
|
71
87
|
* has not carried it - or carried a different one, which re-delivers the
|
|
@@ -74,14 +90,21 @@ export function writePromptSplitReceipt(scope, key, receipt) {
|
|
|
74
90
|
* through bare, so an ordinary memory write neither appends a second copy
|
|
75
91
|
* of the prompt to the session nor re-prices its cached prefix.
|
|
76
92
|
* perTurnVolatile marks a turn whose volatile half describes this very
|
|
77
|
-
* turn (a mention): its note is delivered even when the text is unchanged.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
* turn (a mention): its note is delivered even when the text is unchanged.
|
|
94
|
+
* reAnchorAfterTurns, when positive, re-sends the full prompt after that
|
|
95
|
+
* many consecutive bare turns: a session that rewrites its own history
|
|
96
|
+
* without an observable event (an older pi, a missed compaction) still
|
|
97
|
+
* gets its standing instructions back within a bounded number of turns. */
|
|
98
|
+
export function splitSessionPrompt(stable, volatile, previous, fullSystem, text, perTurnVolatile = false, reAnchorAfterTurns = 0) {
|
|
99
|
+
const fingerprints = promptSplitFingerprints(stable, volatile);
|
|
100
|
+
// Legacy receipts predate the turn counter; count them from zero.
|
|
101
|
+
const turnsSinceFull = typeof previous?.turnsSinceFull === "number" ? previous.turnsSinceFull : 0;
|
|
102
|
+
const reAnchor = reAnchorAfterTurns > 0 && previous !== null && turnsSinceFull >= reAnchorAfterTurns;
|
|
103
|
+
if (previous === null || previous.stable !== fingerprints.stable || reAnchor) {
|
|
104
|
+
return { text: fullSystem ? fullSystem + "\n\n" + text : text, receipt: { ...fingerprints, turnsSinceFull: 0 } };
|
|
82
105
|
}
|
|
83
|
-
const note = previous.volatile ===
|
|
106
|
+
const note = previous.volatile === fingerprints.volatile && !perTurnVolatile
|
|
84
107
|
? ""
|
|
85
108
|
: volatileContextNote(volatile, previous.volatile !== EMPTY_FINGERPRINT);
|
|
86
|
-
return { text: withContextNote(note, text), receipt };
|
|
109
|
+
return { text: withContextNote(note, text), receipt: { ...fingerprints, turnsSinceFull: turnsSinceFull + 1 } };
|
|
87
110
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// One session-idle policy for every pooled harness process. The Claude
|
|
2
|
+
// driver and the ACP core grew byte-identical copies of this computation
|
|
3
|
+
// with different env prefixes; the policy lives here so the default, the
|
|
4
|
+
// floor, and the env names cannot drift apart again.
|
|
5
|
+
const DEFAULT_IDLE_MS = 10 * 60_000;
|
|
6
|
+
const MINIMUM_IDLE_MS = 10_000;
|
|
7
|
+
/** setTimeout treats anything above 2^31-1 ms as 1 ms, so a larger delay
|
|
8
|
+
* would close the session immediately instead of much later. */
|
|
9
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
10
|
+
function usableMilliseconds(raw) {
|
|
11
|
+
const value = Number(raw);
|
|
12
|
+
return Number.isFinite(value) && value > 0 && value <= MAX_TIMER_MS ? value : null;
|
|
13
|
+
}
|
|
14
|
+
/** The idle policy for one harness. The per-harness names that preceded the
|
|
15
|
+
* unified ones (OMB_CLAUDE_SESSION_IDLE_*, OMB_ACP_SESSION_IDLE_*) win when
|
|
16
|
+
* both are set, so a harness-specific override keeps working alongside a
|
|
17
|
+
* global OMB_SESSION_IDLE_MS / OMB_SESSION_IDLE_MIN_MS; the unified names
|
|
18
|
+
* are the default for harnesses that did not set their own. A delay that is
|
|
19
|
+
* not a positive finite number of at most 2^31-1 ms is validated away before
|
|
20
|
+
* the floor applies — the old arithmetic coerced Infinity and larger values
|
|
21
|
+
* into an immediately-firing 1 ms timer. The floor is the greater of the
|
|
22
|
+
* built-in ten seconds and any configured minimum, so a per-harness minimum
|
|
23
|
+
* can raise the floor but never lower it, and it applies to whatever delay
|
|
24
|
+
* wins. */
|
|
25
|
+
export function sessionIdlePolicy(driverPrefix, env = process.env) {
|
|
26
|
+
const minimumMs = Math.max(MINIMUM_IDLE_MS, usableMilliseconds(env[`OMB_${driverPrefix}_SESSION_IDLE_MIN_MS`] ?? env.OMB_SESSION_IDLE_MIN_MS) ?? 0);
|
|
27
|
+
const configured = usableMilliseconds(env[`OMB_${driverPrefix}_SESSION_IDLE_MS`] ?? env.OMB_SESSION_IDLE_MS) ?? DEFAULT_IDLE_MS;
|
|
28
|
+
return { idleMs: Math.max(minimumMs, configured), minimumMs };
|
|
29
|
+
}
|
|
@@ -41,6 +41,7 @@ function knownDirs() {
|
|
|
41
41
|
join(home, ".volta", "bin"),
|
|
42
42
|
join(home, ".bun", "bin"),
|
|
43
43
|
join(home, ".asdf", "shims"),
|
|
44
|
+
join(home, ".local", "share", "mise", "shims"), // mise installer
|
|
44
45
|
join(home, ".deno", "bin"),
|
|
45
46
|
join(home, "bin"),
|
|
46
47
|
...nvmBinDirs(),
|
|
@@ -144,6 +145,20 @@ export function resetPathCacheForTests() {
|
|
|
144
145
|
loginShellPath = null;
|
|
145
146
|
registeredDirs.length = 0;
|
|
146
147
|
}
|
|
148
|
+
/** The user's home directory as this platform defines it. Windows keeps the
|
|
149
|
+
* real profile in USERPROFILE; a HOME that leaks in from a POSIX-flavored
|
|
150
|
+
* shell is not where Windows CLIs keep their state, so USERPROFILE wins there
|
|
151
|
+
* and HOME wins everywhere else. */
|
|
152
|
+
export function userHome(env = process.env) {
|
|
153
|
+
return process.platform === "win32"
|
|
154
|
+
? env.USERPROFILE || env.HOME || homedir()
|
|
155
|
+
: env.HOME || env.USERPROFILE || homedir();
|
|
156
|
+
}
|
|
157
|
+
/** `<user home>/.<name>` — the per-harness state directory every CLI keeps
|
|
158
|
+
* (`.qwen`, `.grok`, `.codex`…), Windows-correct everywhere. */
|
|
159
|
+
export function harnessHome(name, env = process.env) {
|
|
160
|
+
return join(userHome(env), `.${name}`);
|
|
161
|
+
}
|
|
147
162
|
/** Every `name` binary on the augmented PATH as absolute paths, in PATH
|
|
148
163
|
* order (first = what a bare name would run). Used by the Engines panel's
|
|
149
164
|
* "detected" dropdown and the /api/cli-candidates endpoint. A path-ish
|
|
@@ -54,7 +54,7 @@ export function requestSourceForCard(messages, messageId) {
|
|
|
54
54
|
return undefined;
|
|
55
55
|
}
|
|
56
56
|
export function requestNeedsInput(message) {
|
|
57
|
-
return Boolean((message.card?.requestId && !message.card.answered && !message.card.dismissed) ||
|
|
57
|
+
return Boolean((message.card?.requestId && !message.card.answered && !message.card.dismissed && !message.card.expired) ||
|
|
58
58
|
(message.connector && !message.connector.dismissed && !message.connector.resumed && message.connector.status !== "connected") ||
|
|
59
|
-
(message.secret && !message.secret.provided && !message.secret.dismissed));
|
|
59
|
+
(message.secret && !message.secret.provided && !message.secret.dismissed && !message.secret.superseded));
|
|
60
60
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// What a bot gains or loses when its default engine (instanceId, hence
|
|
2
|
+
// driver) changes. Two per-driver facts decide it: whether the driver mounts
|
|
3
|
+
// the agents tool catalog at all (adapter capabilities.agentsMcp — a bot on
|
|
4
|
+
// a driver without it is never even told about the team tools), and which
|
|
5
|
+
// approval levels the destination driver implements. Turn-scoped catalog
|
|
6
|
+
// flags are identical on both sides of a switch, so the comparison below
|
|
7
|
+
// uses one envelope profile and lets the per-driver capability decide the
|
|
8
|
+
// delta; the tool names themselves come from the real catalog, so this
|
|
9
|
+
// never drifts from what a turn is actually shown.
|
|
10
|
+
import { APPROVAL_MODES, supportsApprovalMode as driverKindSupportsApprovalMode } from "../shared/approval-mode.js";
|
|
11
|
+
import { availableTools } from "./drivers/agents-catalog.js";
|
|
12
|
+
/** The widest turn the harness can build: every optional integration on and
|
|
13
|
+
* a coordinating (room) turn, where the room-only coordination tools exist.
|
|
14
|
+
* Both sides of a comparison get the same profile; only the driver's own
|
|
15
|
+
* capability can differ. */
|
|
16
|
+
const ENVELOPE = {
|
|
17
|
+
externalRuntime: false,
|
|
18
|
+
coordinating: true,
|
|
19
|
+
ownThreadCreation: true,
|
|
20
|
+
skillAuthoring: true,
|
|
21
|
+
sharedComputers: true,
|
|
22
|
+
voiceNotes: true,
|
|
23
|
+
botId: "",
|
|
24
|
+
};
|
|
25
|
+
/** Extra card lines for an engine switch, empty when nothing changes. */
|
|
26
|
+
export function harnessCapabilityLines(from, to) {
|
|
27
|
+
if (!from || !to)
|
|
28
|
+
return [];
|
|
29
|
+
const lines = [];
|
|
30
|
+
if (from.agentsMcp !== to.agentsMcp) {
|
|
31
|
+
const names = availableTools(ENVELOPE).map((tool) => tool.name);
|
|
32
|
+
const coordination = ["coordinate_bots", "ask_bot", "delegate_bot"].filter((name) => names.includes(name));
|
|
33
|
+
lines.push(to.agentsMcp
|
|
34
|
+
? "Gains peer coordination and the other team tools: " + names.length + " tools including " + coordination.join(", ") + "."
|
|
35
|
+
: "Loses peer coordination and every team tool: " + names.length + " tools including " + coordination.join(", ") + " go away.");
|
|
36
|
+
}
|
|
37
|
+
if (from.driverKind !== to.driverKind) {
|
|
38
|
+
const lost = APPROVAL_MODES.filter((mode) => driverKindSupportsApprovalMode(from.driverKind, mode) && !driverKindSupportsApprovalMode(to.driverKind, mode));
|
|
39
|
+
const gained = APPROVAL_MODES.filter((mode) => !driverKindSupportsApprovalMode(from.driverKind, mode) && driverKindSupportsApprovalMode(to.driverKind, mode));
|
|
40
|
+
if (lost.length)
|
|
41
|
+
lines.push("Loses approval levels: " + lost.join(", ") + ".");
|
|
42
|
+
if (gained.length)
|
|
43
|
+
lines.push("Gains approval levels: " + gained.join(", ") + ".");
|
|
44
|
+
}
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
/** Binds the registry to the approval-support check so each gate passes its
|
|
48
|
+
* model selection — or a driver view it already holds — and stops
|
|
49
|
+
* unwrapping registry.cliTarget(...)?.driverKind at every call site. The
|
|
50
|
+
* lookup prefers shadow state, so a mid-reconfig instance answers with the
|
|
51
|
+
* driver it would actually run. */
|
|
52
|
+
export function createApprovalModeSupport(registry) {
|
|
53
|
+
return function supportsApprovalMode(target, mode) {
|
|
54
|
+
const driverKind = target === undefined
|
|
55
|
+
? undefined
|
|
56
|
+
: "driverKind" in target
|
|
57
|
+
? target.driverKind
|
|
58
|
+
: registry.cliTarget(target.instanceId)?.driverKind;
|
|
59
|
+
return driverKindSupportsApprovalMode(driverKind, mode);
|
|
60
|
+
};
|
|
61
|
+
}
|