@corenel/sidecar 0.8.8 → 0.10.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/dist/cli.js +525 -79
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -17291,8 +17291,8 @@ ${yamlContent}` : yamlContent;
|
|
|
17291
17291
|
processedContent = content;
|
|
17292
17292
|
}
|
|
17293
17293
|
if (this.verbose) {
|
|
17294
|
-
const
|
|
17295
|
-
console.log(`[include] Loaded ${ext} file: ${filePath} (${
|
|
17294
|
+
const preview2 = processedContent.substring(0, 50).replace(/\n/g, "\\n");
|
|
17295
|
+
console.log(`[include] Loaded ${ext} file: ${filePath} (${preview2}...)`);
|
|
17296
17296
|
}
|
|
17297
17297
|
return {
|
|
17298
17298
|
src: processedContent,
|
|
@@ -17914,8 +17914,8 @@ ${content}` : parentData.content;
|
|
|
17914
17914
|
}
|
|
17915
17915
|
removedCount++;
|
|
17916
17916
|
if (verbose) {
|
|
17917
|
-
const
|
|
17918
|
-
console.log(` - Removing \`\`\`${lang} block: "${
|
|
17917
|
+
const preview2 = blockContent.substring(0, 50).replace(/\n/g, "\\n");
|
|
17918
|
+
console.log(` - Removing \`\`\`${lang} block: "${preview2}..."`);
|
|
17919
17919
|
}
|
|
17920
17920
|
return "";
|
|
17921
17921
|
});
|
|
@@ -18765,6 +18765,25 @@ ${firstUserMsg.content}`;
|
|
|
18765
18765
|
}
|
|
18766
18766
|
});
|
|
18767
18767
|
|
|
18768
|
+
// ../tools-node/toolGuidance.ts
|
|
18769
|
+
function nodeToolGuidance(toolNames) {
|
|
18770
|
+
const has3 = new Set(toolNames);
|
|
18771
|
+
const parts = [];
|
|
18772
|
+
if (has3.has(SHELL_TOOL)) parts.push(SHELL);
|
|
18773
|
+
if (PROC_TOOLS.some((t2) => has3.has(t2))) parts.push(PROCS);
|
|
18774
|
+
return parts.join("\n\n");
|
|
18775
|
+
}
|
|
18776
|
+
var SHELL_TOOL, PROC_TOOLS, SHELL, PROCS;
|
|
18777
|
+
var init_toolGuidance = __esm({
|
|
18778
|
+
"../tools-node/toolGuidance.ts"() {
|
|
18779
|
+
"use strict";
|
|
18780
|
+
SHELL_TOOL = "shell";
|
|
18781
|
+
PROC_TOOLS = ["proc_start", "proc_stop", "proc_list"];
|
|
18782
|
+
SHELL = `Running a command is often the shortest correct answer: use \`${SHELL_TOOL}\` for anything a command already does well -- listing, searching, git, package managers, build and test runners -- instead of reconstructing it from file reads. It waits for the command to finish and returns its output, so it is the wrong tool for anything that does not exit on its own.`;
|
|
18783
|
+
PROCS = `A command that keeps running (a dev server, a watcher, a queue worker) belongs to \`${PROC_TOOLS[0]}\`, not \`${SHELL_TOOL}\` -- starting one with \`${SHELL_TOOL}\` hangs the turn until it is killed. Use \`${PROC_TOOLS[2]}\` to see what is already running before starting anything, and \`${PROC_TOOLS[1]}\` to stop what you started. Do not stop a process you did not start.`;
|
|
18784
|
+
}
|
|
18785
|
+
});
|
|
18786
|
+
|
|
18768
18787
|
// ../tools-node/promptHost.ts
|
|
18769
18788
|
function backend(svc) {
|
|
18770
18789
|
return {
|
|
@@ -18822,8 +18841,22 @@ function nodePromptHost(workspace, project) {
|
|
|
18822
18841
|
sources[p2] = liveSrc;
|
|
18823
18842
|
return compile("", values, new HybridFileSystem(sources, backend(workspace)), "/" + p2);
|
|
18824
18843
|
},
|
|
18825
|
-
siteIndex: () => ""
|
|
18844
|
+
siteIndex: () => "",
|
|
18826
18845
|
// no site index in node (the help chat is browser-only)
|
|
18846
|
+
/* Who the agent says it IS. Without this, `composeAgentSystem` falls back
|
|
18847
|
+
* to its `'Prompd'` default and the `corenel` binary's agent introduced
|
|
18848
|
+
* itself as "the Prompd assistant" -- the same leak already fixed for the
|
|
18849
|
+
* browser help chat (composeHelpSystem) and missed here, because the node
|
|
18850
|
+
* host is the one PromptHost that never implemented the hook.
|
|
18851
|
+
*
|
|
18852
|
+
* Constant, not a parameter: every node consumer of this host (the CLI and
|
|
18853
|
+
* the sidecar) IS Corenel. A host that is not should implement its own. */
|
|
18854
|
+
appName: () => "Corenel",
|
|
18855
|
+
/* The product's guidance for the tools this run actually has -- the shell,
|
|
18856
|
+
* long-running processes, and deferred MCP tools. Gated per tool inside
|
|
18857
|
+
* nodeToolGuidance, because this same host serves the sidecar, whose shell
|
|
18858
|
+
* is behind --allow-shell. */
|
|
18859
|
+
toolGuidance: (names) => nodeToolGuidance(names)
|
|
18827
18860
|
};
|
|
18828
18861
|
}
|
|
18829
18862
|
var parser, compiler;
|
|
@@ -18832,6 +18865,7 @@ var init_promptHost = __esm({
|
|
|
18832
18865
|
"use strict";
|
|
18833
18866
|
init_dist();
|
|
18834
18867
|
init_protocol();
|
|
18868
|
+
init_toolGuidance();
|
|
18835
18869
|
parser = new PrompdParser();
|
|
18836
18870
|
compiler = new PrompdCompiler();
|
|
18837
18871
|
}
|
|
@@ -25434,6 +25468,11 @@ var SidecarServer = class {
|
|
|
25434
25468
|
/** The friendly label from the same `hello`, kept beside `clientId` so the
|
|
25435
25469
|
* dirName resolver is handed the identity the manifest keys on. */
|
|
25436
25470
|
clientName;
|
|
25471
|
+
/** This connection's attended-client object, retained so an answer it sends
|
|
25472
|
+
* can be attributed to it. Typed `unknown` because its only consumer is the
|
|
25473
|
+
* registry's own identity lookup, which takes `unknown` for the
|
|
25474
|
+
* dependency-direction reason on `SidecarAttendanceRegistry.answer`. */
|
|
25475
|
+
attended;
|
|
25437
25476
|
/** Detach function for this connection's attendance attachment, set on a
|
|
25438
25477
|
* successful `hello` when `opts.attendance` is wired. Called once, on channel
|
|
25439
25478
|
* close, so a dead connection stops receiving/counting toward asks. */
|
|
@@ -25545,7 +25584,7 @@ var SidecarServer = class {
|
|
|
25545
25584
|
return;
|
|
25546
25585
|
case "permission-answer":
|
|
25547
25586
|
if (!this.authed) return;
|
|
25548
|
-
this.opts.attendance?.answer(m2.id, m2.decision);
|
|
25587
|
+
this.opts.attendance?.answer(m2.id, m2.decision, this.attended);
|
|
25549
25588
|
return;
|
|
25550
25589
|
case "roots-browse":
|
|
25551
25590
|
if (!this.authed) {
|
|
@@ -25795,10 +25834,11 @@ var SidecarServer = class {
|
|
|
25795
25834
|
}
|
|
25796
25835
|
this.authed = true;
|
|
25797
25836
|
this.hostStateDir = m2.stateDir ?? ".corenel";
|
|
25837
|
+
let accounts = [];
|
|
25798
25838
|
if (typeof m2.clientId === "string" && m2.clientId) {
|
|
25799
25839
|
const wireId = m2.clientId.slice(0, MAX_IDENTITY_FIELD_LEN);
|
|
25800
25840
|
const claimed = Array.isArray(m2.accounts) ? m2.accounts : [];
|
|
25801
|
-
|
|
25841
|
+
accounts = claimed.length > MAX_CLAIMED_ACCOUNTS ? [] : claimed.filter((a2) => typeof a2 === "string" && a2.length <= MAX_IDENTITY_FIELD_LEN);
|
|
25802
25842
|
let clientId = wireId;
|
|
25803
25843
|
if (this.opts.clientKey) {
|
|
25804
25844
|
try {
|
|
@@ -25827,11 +25867,16 @@ var SidecarServer = class {
|
|
|
25827
25867
|
}
|
|
25828
25868
|
}
|
|
25829
25869
|
if (this.opts.attendance) {
|
|
25830
|
-
|
|
25870
|
+
const attended = {
|
|
25831
25871
|
send: (ask) => this.ch.send({ t: "permission-ask", ...ask }),
|
|
25832
|
-
resolved: (id, decision, by) => this.ch.send({ t: "permission-resolved", id, decision, by }),
|
|
25872
|
+
resolved: (id, decision, by, byClientId) => this.ch.send({ t: "permission-resolved", id, decision, by, ...byClientId ? { byClientId } : {} }),
|
|
25833
25873
|
runActivity: (activity) => this.ch.send({ t: "run-activity", activity }),
|
|
25834
25874
|
registrations: (registrations) => this.ch.send({ t: "registrations", registrations })
|
|
25875
|
+
};
|
|
25876
|
+
this.attended = attended;
|
|
25877
|
+
this.attendedDetach = this.opts.attendance.attach(attended, {
|
|
25878
|
+
clientId: this.clientId ?? "",
|
|
25879
|
+
accounts
|
|
25835
25880
|
});
|
|
25836
25881
|
}
|
|
25837
25882
|
this.opts.log?.(`paired \u2014 ${this.tools.size} ${this.tools.size === 1 ? "tool" : "tools"} advertised (host dir: ${this.hostStateDir})`);
|
|
@@ -26509,8 +26554,20 @@ var SidecarServer = class {
|
|
|
26509
26554
|
this.ch.send({ t: "room-open-result", id: m2.id, ok: false, problems: ["this sidecar does not host rooms"] });
|
|
26510
26555
|
return;
|
|
26511
26556
|
}
|
|
26557
|
+
if (this.opts.teamMode && this.clientId === void 0) {
|
|
26558
|
+
this.ch.send({
|
|
26559
|
+
t: "room-open-result",
|
|
26560
|
+
id: m2.id,
|
|
26561
|
+
ok: false,
|
|
26562
|
+
problems: ["This connection has no verified identity (no clientId from hello), so it cannot open a room."]
|
|
26563
|
+
});
|
|
26564
|
+
return;
|
|
26565
|
+
}
|
|
26566
|
+
const clientId = this.clientId;
|
|
26567
|
+
const owner = clientId === void 0 ? void 0 : m2.req.participants.find((p2) => p2.kind === "human" && p2.role === "owner");
|
|
26568
|
+
const req = owner && clientId !== void 0 ? { ...m2.req, participants: m2.req.participants.map((p2) => p2 === owner ? { ...p2, id: clientId } : p2) } : m2.req;
|
|
26512
26569
|
try {
|
|
26513
|
-
const r2 = await this.opts.rooms.open(
|
|
26570
|
+
const r2 = await this.opts.rooms.open(req);
|
|
26514
26571
|
this.ch.send(r2.ok ? { t: "room-open-result", id: m2.id, ok: true, room: r2.room } : { t: "room-open-result", id: m2.id, ok: false, problems: r2.problems });
|
|
26515
26572
|
} catch (err) {
|
|
26516
26573
|
this.ch.send({ t: "room-open-result", id: m2.id, ok: false, problems: [errMessage(err)] });
|
|
@@ -26522,7 +26579,9 @@ var SidecarServer = class {
|
|
|
26522
26579
|
return;
|
|
26523
26580
|
}
|
|
26524
26581
|
try {
|
|
26525
|
-
|
|
26582
|
+
const rooms = await this.opts.rooms.list();
|
|
26583
|
+
const mine = !this.opts.teamMode ? rooms : this.clientId === void 0 ? [] : rooms.filter((r2) => r2.participants.some((p2) => p2.id === this.clientId));
|
|
26584
|
+
this.ch.send({ t: "room-list-result", id: m2.id, ok: true, rooms: mine });
|
|
26526
26585
|
} catch (err) {
|
|
26527
26586
|
this.ch.send({ t: "room-list-result", id: m2.id, ok: false, error: errMessage(err) });
|
|
26528
26587
|
}
|
|
@@ -26534,6 +26593,26 @@ var SidecarServer = class {
|
|
|
26534
26593
|
}
|
|
26535
26594
|
try {
|
|
26536
26595
|
const found = await this.opts.rooms.read(m2.roomId);
|
|
26596
|
+
if (found && this.opts.teamMode) {
|
|
26597
|
+
if (this.clientId === void 0) {
|
|
26598
|
+
this.ch.send({
|
|
26599
|
+
t: "room-read-result",
|
|
26600
|
+
id: m2.id,
|
|
26601
|
+
ok: false,
|
|
26602
|
+
error: "This connection has no verified identity, so membership in this room cannot be proven."
|
|
26603
|
+
});
|
|
26604
|
+
return;
|
|
26605
|
+
}
|
|
26606
|
+
if (!found.room.participants.some((p2) => p2.id === this.clientId)) {
|
|
26607
|
+
this.ch.send({
|
|
26608
|
+
t: "room-read-result",
|
|
26609
|
+
id: m2.id,
|
|
26610
|
+
ok: false,
|
|
26611
|
+
error: `"${this.clientId}" is not a member of room "${m2.roomId}".`
|
|
26612
|
+
});
|
|
26613
|
+
return;
|
|
26614
|
+
}
|
|
26615
|
+
}
|
|
26537
26616
|
this.ch.send(found ? { t: "room-read-result", id: m2.id, ok: true, room: found.room, messages: found.messages, turnCosts: found.turnCosts } : { t: "room-read-result", id: m2.id, ok: true, room: null, messages: [], turnCosts: [] });
|
|
26538
26617
|
} catch (err) {
|
|
26539
26618
|
this.ch.send({ t: "room-read-result", id: m2.id, ok: false, error: errMessage(err) });
|
|
@@ -26551,13 +26630,45 @@ var SidecarServer = class {
|
|
|
26551
26630
|
this.ch.send({ t: "room-resume-result", id: m2.id, ok: false, error: errMessage(err) });
|
|
26552
26631
|
}
|
|
26553
26632
|
}
|
|
26633
|
+
/* ONE identity rule for every room verb.
|
|
26634
|
+
*
|
|
26635
|
+
* `room-read` judges membership on `this.clientId` -- the identity this
|
|
26636
|
+
* connection PROVED at `hello`. The mutating verbs used to hand
|
|
26637
|
+
* `m.participantId` straight off the wire to `requireRole`/`mayAct`, so one
|
|
26638
|
+
* branch enforced while another trusted the caller: a viewer could send the
|
|
26639
|
+
* owner's participantId and close the room. Under team mode every verb now
|
|
26640
|
+
* judges the caller the same way `room-read` does, and a connection with no
|
|
26641
|
+
* proven identity is refused rather than attributed to whatever it claimed.
|
|
26642
|
+
*
|
|
26643
|
+
* KNOWN, DELIBERATE CONSEQUENCE (R9): the observed `clientId` and the
|
|
26644
|
+
* participant ids a room is opened with are currently DIFFERENT NAMESPACES
|
|
26645
|
+
* for everyone but the owner (whose id `onRoomOpen` overwrites with the
|
|
26646
|
+
* observed one). Until those namespaces are reconciled, these verbs will
|
|
26647
|
+
* REFUSE non-owner participants under `--mode team`. That refusal is the
|
|
26648
|
+
* intended outcome -- fail closed, consistently -- and must NOT be "fixed"
|
|
26649
|
+
* by falling back to the wire id, which is the hole this closes.
|
|
26650
|
+
*
|
|
26651
|
+
* With `--mode team` OFF this is the pre-existing behaviour, untouched: the
|
|
26652
|
+
* wire id is attribution only, exactly as the spec knowingly accepted. */
|
|
26653
|
+
actingId(claimed) {
|
|
26654
|
+
if (!this.opts.teamMode) return { ok: true, id: claimed };
|
|
26655
|
+
if (this.clientId === void 0) {
|
|
26656
|
+
return { ok: false, error: "This connection has no verified identity (no clientId from hello), so it cannot act on a room." };
|
|
26657
|
+
}
|
|
26658
|
+
return { ok: true, id: this.clientId };
|
|
26659
|
+
}
|
|
26554
26660
|
async onRoomAnswer(m2) {
|
|
26555
26661
|
if (!this.opts.rooms) {
|
|
26556
26662
|
this.ch.send({ t: "room-answer-result", id: m2.id, ok: false, error: "this sidecar does not host rooms" });
|
|
26557
26663
|
return;
|
|
26558
26664
|
}
|
|
26559
26665
|
try {
|
|
26560
|
-
const
|
|
26666
|
+
const actor = this.actingId(m2.participantId);
|
|
26667
|
+
if (!actor.ok) {
|
|
26668
|
+
this.ch.send({ t: "room-answer-result", id: m2.id, ok: false, error: actor.error });
|
|
26669
|
+
return;
|
|
26670
|
+
}
|
|
26671
|
+
const room = await this.opts.rooms.answer(m2.roomId, actor.id, m2.text, m2.addressed);
|
|
26561
26672
|
this.ch.send({ t: "room-answer-result", id: m2.id, ok: true, room });
|
|
26562
26673
|
} catch (err) {
|
|
26563
26674
|
this.ch.send({ t: "room-answer-result", id: m2.id, ok: false, error: errMessage(err) });
|
|
@@ -26569,7 +26680,12 @@ var SidecarServer = class {
|
|
|
26569
26680
|
return;
|
|
26570
26681
|
}
|
|
26571
26682
|
try {
|
|
26572
|
-
const
|
|
26683
|
+
const actor = this.actingId(m2.participantId);
|
|
26684
|
+
if (!actor.ok) {
|
|
26685
|
+
this.ch.send({ t: "room-interject-result", id: m2.id, ok: false, error: actor.error });
|
|
26686
|
+
return;
|
|
26687
|
+
}
|
|
26688
|
+
const room = await this.opts.rooms.interject(m2.roomId, actor.id, m2.text);
|
|
26573
26689
|
this.ch.send({ t: "room-interject-result", id: m2.id, ok: true, room });
|
|
26574
26690
|
} catch (err) {
|
|
26575
26691
|
this.ch.send({ t: "room-interject-result", id: m2.id, ok: false, error: errMessage(err) });
|
|
@@ -26581,7 +26697,12 @@ var SidecarServer = class {
|
|
|
26581
26697
|
return;
|
|
26582
26698
|
}
|
|
26583
26699
|
try {
|
|
26584
|
-
const
|
|
26700
|
+
const actor = this.actingId(m2.participantId);
|
|
26701
|
+
if (!actor.ok) {
|
|
26702
|
+
this.ch.send({ t: "room-close-result", id: m2.id, ok: false, error: actor.error });
|
|
26703
|
+
return;
|
|
26704
|
+
}
|
|
26705
|
+
const room = await this.opts.rooms.close(m2.roomId, actor.id, m2.reason);
|
|
26585
26706
|
this.ch.send({ t: "room-close-result", id: m2.id, ok: true, room });
|
|
26586
26707
|
} catch (err) {
|
|
26587
26708
|
this.ch.send({ t: "room-close-result", id: m2.id, ok: false, error: errMessage(err) });
|
|
@@ -26680,7 +26801,12 @@ var SidecarServer = class {
|
|
|
26680
26801
|
return;
|
|
26681
26802
|
}
|
|
26682
26803
|
try {
|
|
26683
|
-
const
|
|
26804
|
+
const actor = this.actingId(m2.participantId);
|
|
26805
|
+
if (!actor.ok) {
|
|
26806
|
+
this.ch.send({ t: "room-finding-edit-result", id: m2.id, ok: false, error: actor.error });
|
|
26807
|
+
return;
|
|
26808
|
+
}
|
|
26809
|
+
const findings = await this.opts.rooms.editFinding(m2.roomId, actor.id, m2.edit);
|
|
26684
26810
|
this.ch.send({ t: "room-finding-edit-result", id: m2.id, ok: true, findings });
|
|
26685
26811
|
} catch (err) {
|
|
26686
26812
|
this.ch.send({ t: "room-finding-edit-result", id: m2.id, ok: false, error: errMessage(err) });
|
|
@@ -26983,6 +27109,7 @@ function startSidecar(opts) {
|
|
|
26983
27109
|
procs: opts.procs,
|
|
26984
27110
|
sessions: opts.sessions,
|
|
26985
27111
|
rooms: opts.rooms,
|
|
27112
|
+
teamMode: opts.teamMode,
|
|
26986
27113
|
pauses: opts.pauses,
|
|
26987
27114
|
clientDirName: opts.clientDirName,
|
|
26988
27115
|
hosts: opts.hosts,
|
|
@@ -27167,6 +27294,7 @@ var FLAGS = [
|
|
|
27167
27294
|
{ flag: "--allow-origin", arg: "<url>", help: "Allow a browser origin to connect. Repeatable." },
|
|
27168
27295
|
{ flag: "--allow-shell", help: "Enable the shell tool. Off by default." },
|
|
27169
27296
|
{ flag: "--allow-lan", help: "Serve the local network, not just this machine: binds all interfaces and serves wss with a self-signed cert. --host / --tls-cert override each half." },
|
|
27297
|
+
{ flag: "--mode", arg: "team", help: "Route a permission ask to the connection whose run raised it, plus the other devices that owner is signed in on, rather than to everyone attached. The identity a client sends is CLAIMED and this daemon cannot verify it: attribution, not authorization. An ask is never diverted away from its owner, so a false claim can join a prompt but never steal one." },
|
|
27170
27298
|
{ flag: "--tls-cert", arg: "<file>", help: "TLS certificate for wss://." },
|
|
27171
27299
|
{ flag: "--tls-key", arg: "<file>", help: "TLS private key for wss://." },
|
|
27172
27300
|
{ flag: "--self-signed", help: "Generate a self-signed certificate instead of supplying one." },
|
|
@@ -28130,6 +28258,7 @@ var KNOWN_KEYS = [
|
|
|
28130
28258
|
"maxChainDepth",
|
|
28131
28259
|
"name",
|
|
28132
28260
|
"appUrl",
|
|
28261
|
+
"mode",
|
|
28133
28262
|
"defaultModel",
|
|
28134
28263
|
"mcpServers",
|
|
28135
28264
|
"mcpAllow"
|
|
@@ -29290,7 +29419,11 @@ var STANDARD_POLICY = {
|
|
|
29290
29419
|
name: "standard",
|
|
29291
29420
|
description: "Read-only tools run automatically; writes ask for approval.",
|
|
29292
29421
|
onViolation: "stop",
|
|
29293
|
-
maxTurns
|
|
29422
|
+
// maxTurns counts iterations WITHIN one user message (one model call plus
|
|
29423
|
+
// its tool round-trip), not messages across a conversation -- 8 was
|
|
29424
|
+
// exhausted inside a single tool-heavy reply. 0 means unlimited (see
|
|
29425
|
+
// core/loop.ts); this default stays a finite backstop.
|
|
29426
|
+
maxTurns: 40,
|
|
29294
29427
|
permissions: {
|
|
29295
29428
|
"prompd:propose_edit": "allow",
|
|
29296
29429
|
// proposals are reviewed in the diff UI, not applied directly
|
|
@@ -29304,7 +29437,8 @@ var READ_ONLY_POLICY = {
|
|
|
29304
29437
|
name: "read-only",
|
|
29305
29438
|
description: "Inspection only \u2014 all writes and side effects are refused.",
|
|
29306
29439
|
onViolation: "stop",
|
|
29307
|
-
maxTurns:
|
|
29440
|
+
maxTurns: 40,
|
|
29441
|
+
// see STANDARD_POLICY: counts iterations within one reply, not conversation turns
|
|
29308
29442
|
tools: {
|
|
29309
29443
|
deny: [
|
|
29310
29444
|
"fs:write_file",
|
|
@@ -29347,7 +29481,9 @@ var AUTONOMOUS_POLICY = {
|
|
|
29347
29481
|
name: "autonomous",
|
|
29348
29482
|
description: "Everything runs automatically \u2014 no approval prompts (system config still asks).",
|
|
29349
29483
|
onViolation: "warn",
|
|
29350
|
-
|
|
29484
|
+
// Higher than the other builtins: nobody is watching an autonomous run turn
|
|
29485
|
+
// by turn, so it gets more headroom before the backstop kicks in.
|
|
29486
|
+
maxTurns: 80,
|
|
29351
29487
|
permissions: {
|
|
29352
29488
|
"system:*": "ask"
|
|
29353
29489
|
// even unattended, never silently reconfigure the harness
|
|
@@ -30080,6 +30216,10 @@ var forgetMemoryTool = {
|
|
|
30080
30216
|
return `Forgot memory "${name}".`;
|
|
30081
30217
|
}
|
|
30082
30218
|
};
|
|
30219
|
+
function preview(v2, max = 200) {
|
|
30220
|
+
if (v2.length <= max) return JSON.stringify(v2);
|
|
30221
|
+
return `${JSON.stringify(v2.slice(0, max))}... (${v2.length} chars total)`;
|
|
30222
|
+
}
|
|
30083
30223
|
var kvGetTool = {
|
|
30084
30224
|
name: "kv_get",
|
|
30085
30225
|
description: "Read a value you previously saved with kv_set, by key, from the local key/value store. This is a general note store \u2014 NOT a filesystem, working directory, or settings: use list_files/read_file for files.",
|
|
@@ -30099,10 +30239,42 @@ var kvSetTool = {
|
|
|
30099
30239
|
required: ["key", "value"]
|
|
30100
30240
|
},
|
|
30101
30241
|
// Permission is gated centrally by the loop's policy/permission layer.
|
|
30242
|
+
/*
|
|
30243
|
+
* THE RESULT STATES WHAT WAS STORED, AND WHAT IT REPLACED.
|
|
30244
|
+
*
|
|
30245
|
+
* This used to return `Set ${key}.` -- nothing else. Two consequences, both
|
|
30246
|
+
* observed in a real run:
|
|
30247
|
+
*
|
|
30248
|
+
* 1. "set bread=true" and "set bread=false" produced BYTE-IDENTICAL tool
|
|
30249
|
+
* results. An agent had no feedback that could distinguish a correct
|
|
30250
|
+
* write from a wrong one, so a mistaken value was indistinguishable from
|
|
30251
|
+
* a correct one for the rest of the conversation.
|
|
30252
|
+
*
|
|
30253
|
+
* 2. A LOST UPDATE was invisible. The common shape here is a list kept as
|
|
30254
|
+
* one blob under one key, updated read-modify-write: a later write
|
|
30255
|
+
* carrying a stale copy silently reverts an earlier one, and every
|
|
30256
|
+
* result still says "Set". A run marked two items done and only one
|
|
30257
|
+
* stayed done, with nothing anywhere to show the second had been undone.
|
|
30258
|
+
*
|
|
30259
|
+
* So: report the value, and name the value being replaced. An agent that
|
|
30260
|
+
* overwrites "apples done, bread done" with "apples done, bread pending"
|
|
30261
|
+
* now reads back, in the tool result it already has to process, exactly
|
|
30262
|
+
* what it just destroyed. The read-back is what makes this a check rather
|
|
30263
|
+
* than an echo of the argument -- a store that transformed, truncated or
|
|
30264
|
+
* dropped the write is caught here instead of at the summary.
|
|
30265
|
+
*/
|
|
30102
30266
|
async run(args, ctx) {
|
|
30103
30267
|
const key = str4(args, "key");
|
|
30104
|
-
|
|
30105
|
-
|
|
30268
|
+
const value = str4(args, "value");
|
|
30269
|
+
const before = await ctx.kv.get(key);
|
|
30270
|
+
await ctx.kv.set(key, value);
|
|
30271
|
+
const after = await ctx.kv.get(key);
|
|
30272
|
+
if (after !== value) {
|
|
30273
|
+
return `WRITE DID NOT TAKE. "${key}" was set to ${preview(value)} but reads back as ${after == null ? "(not set)" : preview(after)}. Do not rely on this key; do not report it as saved.`;
|
|
30274
|
+
}
|
|
30275
|
+
if (before == null) return `Set "${key}" = ${preview(value)} (new key).`;
|
|
30276
|
+
if (before === value) return `Set "${key}" = ${preview(value)} (unchanged).`;
|
|
30277
|
+
return `Set "${key}" = ${preview(value)}, REPLACING ${preview(before)}. If that previous value held anything you did not mean to discard, it is gone.`;
|
|
30106
30278
|
}
|
|
30107
30279
|
};
|
|
30108
30280
|
var recallResultTool = {
|
|
@@ -30604,6 +30776,18 @@ parameters:
|
|
|
30604
30776
|
type: string
|
|
30605
30777
|
description: The editor file / selection / compiled-output context block.
|
|
30606
30778
|
default: ""
|
|
30779
|
+
- name: skills
|
|
30780
|
+
type: string
|
|
30781
|
+
description: Instructions contributed by the skills enabled for this run.
|
|
30782
|
+
default: ""
|
|
30783
|
+
- name: memory
|
|
30784
|
+
type: string
|
|
30785
|
+
description: The index of notes saved to this run's memory plane.
|
|
30786
|
+
default: ""
|
|
30787
|
+
- name: run_context
|
|
30788
|
+
type: string
|
|
30789
|
+
description: Facts about THIS run the model cannot otherwise know \u2014 the date, and whatever the host adds (working directory, platform).
|
|
30790
|
+
default: ""
|
|
30607
30791
|
- name: app
|
|
30608
30792
|
type: string
|
|
30609
30793
|
description: Product name of the host application, used for the agent's own identity.
|
|
@@ -30611,13 +30795,22 @@ parameters:
|
|
|
30611
30795
|
---
|
|
30612
30796
|
{% if persona %}{{ persona }}
|
|
30613
30797
|
|
|
30614
|
-
{% endif %}You are the {{ app }} assistant. You help the user do real work
|
|
30798
|
+
{% endif %}You are the {{ app }} assistant. You help the user do real work: reading and changing files, composing and running prompts and workflows, and building software. Work from the context you were given below rather than assuming a particular screen.
|
|
30615
30799
|
|
|
30616
|
-
|
|
30800
|
+
Verify before you claim. Do not say something is done, fixed, passing or ready unless you checked it in this conversation and can point at what you checked. If you did not run it, say you did not run it. If a check failed, say so plainly and show what failed. A wrong claim of success costs the user far more than the check would have.
|
|
30617
30801
|
|
|
30618
30802
|
Say what you assumed and what you did not do. A request that leaves room for interpretation gets its decisions named, one line each, so the user can correct one without unpicking the rest. How much you decide for yourself versus ask about is the mode's call, below.
|
|
30619
30803
|
|
|
30620
|
-
|
|
30804
|
+
A tool call that comes back denied, blocked or unavailable is a decision, not a failure to route around. Do not retry it, do not reach for another tool to accomplish the same thing, and never describe the change as made. Say what you wanted to do and stop.
|
|
30805
|
+
|
|
30806
|
+
Prefer calling a tool over guessing: read the file rather than recalling it, run the command rather than predicting its output. Use the file tools to read and change files, and compile_prompd to preview rendered output. For a job with several steps, keep a visible task list with todo_write and mark each step as you finish it.
|
|
30807
|
+
|
|
30808
|
+
Be brief. Lead with the answer, then only what the user needs to act on or check it. Use headings, lists and tables where they carry something prose would not, and prose everywhere else. Do not restate the request back, do not narrate what you are about to do, and do not end by summarizing what the user has just read.
|
|
30809
|
+
|
|
30810
|
+
{% if run_context %}## This run
|
|
30811
|
+
{{ run_context }}
|
|
30812
|
+
|
|
30813
|
+
{% endif %}## Tools available this run
|
|
30621
30814
|
{{ tools }}
|
|
30622
30815
|
{% if tool_guidance %}
|
|
30623
30816
|
|
|
@@ -30628,6 +30821,14 @@ Say what you assumed and what you did not do. A request that leaves room for int
|
|
|
30628
30821
|
## Mode
|
|
30629
30822
|
{{ mode_hint }}
|
|
30630
30823
|
{% endif %}
|
|
30824
|
+
{% if skills %}
|
|
30825
|
+
|
|
30826
|
+
{{ skills }}
|
|
30827
|
+
{% endif %}
|
|
30828
|
+
{% if memory %}
|
|
30829
|
+
|
|
30830
|
+
{{ memory }}
|
|
30831
|
+
{% endif %}
|
|
30631
30832
|
{% if file_context %}
|
|
30632
30833
|
|
|
30633
30834
|
{{ file_context }}
|
|
@@ -31890,26 +32091,39 @@ function makeAgentMemoryProvider(getFiles, memoryRoot) {
|
|
|
31890
32091
|
};
|
|
31891
32092
|
}
|
|
31892
32093
|
|
|
32094
|
+
// ../crew/room/types.ts
|
|
32095
|
+
function roleOf(p2) {
|
|
32096
|
+
return p2.kind === "agent" ? null : p2.role ?? "contributor";
|
|
32097
|
+
}
|
|
32098
|
+
|
|
31893
32099
|
// ../crew/room/order.ts
|
|
31894
32100
|
var ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
31895
32101
|
function isValidParticipantId(id) {
|
|
31896
32102
|
return ID_RE.test(id);
|
|
31897
32103
|
}
|
|
32104
|
+
function rotating(state) {
|
|
32105
|
+
return state.mode === "conversation" ? state.participants.filter((p2) => p2.kind === "agent") : state.participants;
|
|
32106
|
+
}
|
|
31898
32107
|
function currentSpeaker(state) {
|
|
31899
32108
|
if (state.closed) return null;
|
|
31900
|
-
|
|
31901
|
-
|
|
32109
|
+
const roster = rotating(state);
|
|
32110
|
+
if (state.jump) {
|
|
32111
|
+
const jumped = roster.find((p2) => p2.id === state.jump);
|
|
32112
|
+
if (jumped) return jumped;
|
|
32113
|
+
}
|
|
32114
|
+
return roster[state.floor] ?? null;
|
|
31902
32115
|
}
|
|
31903
32116
|
function advance(state, addressed) {
|
|
31904
32117
|
if (state.closed) return state;
|
|
32118
|
+
const roster = rotating(state);
|
|
31905
32119
|
const speaker = currentSpeaker(state);
|
|
31906
32120
|
const spokeViaJump = state.jump != null;
|
|
31907
32121
|
const turns = state.turns + 1;
|
|
31908
32122
|
const mayAddress = !spokeViaJump;
|
|
31909
|
-
const target = mayAddress && addressed && addressed !== speaker?.id &&
|
|
31910
|
-
let floor = spokeViaJump ? state.floor : (state.floor + 1) %
|
|
31911
|
-
if (target != null &&
|
|
31912
|
-
floor = (floor + 1) %
|
|
32123
|
+
const target = mayAddress && addressed && addressed !== speaker?.id && roster.some((p2) => p2.id === addressed) ? addressed : null;
|
|
32124
|
+
let floor = spokeViaJump ? state.floor : (state.floor + 1) % roster.length;
|
|
32125
|
+
if (target != null && roster[floor]?.id === target) {
|
|
32126
|
+
floor = (floor + 1) % roster.length;
|
|
31913
32127
|
}
|
|
31914
32128
|
const closed = turns >= state.maxTurns ? { by: "room", reason: `Turn cap reached (${state.maxTurns}).`, at: (/* @__PURE__ */ new Date()).toISOString() } : null;
|
|
31915
32129
|
return { ...state, floor, jump: target, jumpBy: target ? speaker?.id ?? null : null, turns, closed };
|
|
@@ -31957,6 +32171,9 @@ function foldRoom(text) {
|
|
|
31957
32171
|
maxUsd: typeof line.maxUsd === "number" ? line.maxUsd : null,
|
|
31958
32172
|
spentUsd: 0,
|
|
31959
32173
|
closed: null,
|
|
32174
|
+
// Absent means a room written before conversation mode existed, and
|
|
32175
|
+
// every one of those is a rotation room.
|
|
32176
|
+
mode: line.mode ?? "rotation",
|
|
31960
32177
|
// Absent, rather than 0, on a line whose `at` is missing or not a
|
|
31961
32178
|
// number: "the log does not say" is not a date at the epoch.
|
|
31962
32179
|
...typeof line.at === "number" ? { openedAt: line.at } : {}
|
|
@@ -32036,6 +32253,7 @@ function foldSession(text) {
|
|
|
32036
32253
|
const turns = [];
|
|
32037
32254
|
let title = "";
|
|
32038
32255
|
let titleLocked;
|
|
32256
|
+
let movedTo;
|
|
32039
32257
|
let updatedAt = 0;
|
|
32040
32258
|
for (const raw of text.split("\n")) {
|
|
32041
32259
|
const s2 = raw.trim();
|
|
@@ -32057,6 +32275,9 @@ function foldSession(text) {
|
|
|
32057
32275
|
} else if (line.k === "turn" && Number.isInteger(line.i) && line.i >= 0 && typeof line.at === "number") {
|
|
32058
32276
|
turns[line.i] = line.m;
|
|
32059
32277
|
updatedAt = Math.max(updatedAt, line.at);
|
|
32278
|
+
} else if (line.k === "moved" && typeof line.roomId === "string" && typeof line.at === "number") {
|
|
32279
|
+
movedTo = { movedToRoom: line.roomId, at: line.at };
|
|
32280
|
+
updatedAt = Math.max(updatedAt, line.at);
|
|
32060
32281
|
}
|
|
32061
32282
|
}
|
|
32062
32283
|
if (!meta) return null;
|
|
@@ -32067,7 +32288,12 @@ function foldSession(text) {
|
|
|
32067
32288
|
updatedAt: updatedAt || meta.createdAt,
|
|
32068
32289
|
messages: turns.filter((t2) => t2 !== void 0),
|
|
32069
32290
|
...meta.surface ? { surface: meta.surface } : {},
|
|
32070
|
-
|
|
32291
|
+
// Only a STRING owner survives the fold: the line came from disk (or a
|
|
32292
|
+
// sidecar), so its declared type guarantees nothing, and a non-string here
|
|
32293
|
+
// would reach a comparison that decides how a row is drawn.
|
|
32294
|
+
...typeof meta.owner === "string" && meta.owner ? { owner: meta.owner } : {},
|
|
32295
|
+
...titleLocked !== void 0 ? { titleLocked } : {},
|
|
32296
|
+
...movedTo !== void 0 ? { movedTo } : {}
|
|
32071
32297
|
};
|
|
32072
32298
|
}
|
|
32073
32299
|
|
|
@@ -32477,9 +32703,37 @@ function validateRoomRequest(req) {
|
|
|
32477
32703
|
"A browser cannot host a room: the messages and outcome a turn produces never leave the browser tab's worker, so the room would rotate blindly with every hand-over and close silently lost. Choose the daemon runtime instead."
|
|
32478
32704
|
);
|
|
32479
32705
|
}
|
|
32706
|
+
if (req.mode === "conversation") {
|
|
32707
|
+
if (!req.participants.some((p2) => p2.kind === "agent")) {
|
|
32708
|
+
problems.push(
|
|
32709
|
+
"A conversation room needs at least one agent: humans never hold the floor in this mode, so a room without one has nobody who can ever take a turn."
|
|
32710
|
+
);
|
|
32711
|
+
}
|
|
32712
|
+
if (req.presence === "wait") {
|
|
32713
|
+
problems.push(
|
|
32714
|
+
'presence "wait" means waiting for a person to take their turn, and in a conversation room nobody does. Use "carry-on".'
|
|
32715
|
+
);
|
|
32716
|
+
}
|
|
32717
|
+
}
|
|
32480
32718
|
if (!Number.isInteger(req.maxTurns) || req.maxTurns < 1) {
|
|
32481
32719
|
problems.push("The turn cap must be a positive whole number.");
|
|
32482
32720
|
}
|
|
32721
|
+
const ROLES = ["owner", "contributor", "viewer"];
|
|
32722
|
+
const owners = req.participants.filter((p2) => p2.kind === "human" && p2.role === "owner");
|
|
32723
|
+
if (owners.length > 1) {
|
|
32724
|
+
problems.push(
|
|
32725
|
+
`A room has one owner; this request names ${owners.length}: ${owners.map((p2) => p2.name).join(", ")}.`
|
|
32726
|
+
);
|
|
32727
|
+
}
|
|
32728
|
+
for (const p2 of req.participants) {
|
|
32729
|
+
const role = p2.role;
|
|
32730
|
+
if (role === void 0) continue;
|
|
32731
|
+
if (p2.kind === "agent") {
|
|
32732
|
+
problems.push(`"${p2.name}" is an agent and cannot hold a role: agents are the work, not participants who are granted access.`);
|
|
32733
|
+
} else if (!ROLES.includes(role)) {
|
|
32734
|
+
problems.push(`"${role}" is not a role. Known roles: ${ROLES.join(", ")}.`);
|
|
32735
|
+
}
|
|
32736
|
+
}
|
|
32483
32737
|
return problems;
|
|
32484
32738
|
}
|
|
32485
32739
|
async function planOpen(req, reach, at2) {
|
|
@@ -32511,7 +32765,8 @@ async function planOpen(req, reach, at2) {
|
|
|
32511
32765
|
brief: (req.brief ?? "").trim(),
|
|
32512
32766
|
...req.maxUsd !== void 0 ? { maxUsd: req.maxUsd } : {},
|
|
32513
32767
|
runtime: req.runtime,
|
|
32514
|
-
presence: req.presence
|
|
32768
|
+
presence: req.presence,
|
|
32769
|
+
...req.mode !== void 0 ? { mode: req.mode } : {}
|
|
32515
32770
|
}
|
|
32516
32771
|
};
|
|
32517
32772
|
}
|
|
@@ -42367,6 +42622,44 @@ function formatOffloadRef(ref) {
|
|
|
42367
42622
|
${ref.preview}${ref.bytes > ref.preview.length ? "\n\u2026(truncated)" : ""}` : head;
|
|
42368
42623
|
}
|
|
42369
42624
|
|
|
42625
|
+
// ../harness/core/contextPressure.ts
|
|
42626
|
+
var CONTEXT_MARKER = "[context] ";
|
|
42627
|
+
var PRESSURE_BANDS = [0.6, 0.8, 0.9];
|
|
42628
|
+
var PRESSURE_HYSTERESIS = 0.05;
|
|
42629
|
+
var initialPressureGate = { crossed: 0 };
|
|
42630
|
+
function highestBandAtOrBelow(fill, slack = 0) {
|
|
42631
|
+
let best = 0;
|
|
42632
|
+
for (const b2 of PRESSURE_BANDS) if (fill >= b2 - slack) best = b2;
|
|
42633
|
+
return best;
|
|
42634
|
+
}
|
|
42635
|
+
function pressureDecision(fill, gate) {
|
|
42636
|
+
const current2 = highestBandAtOrBelow(fill);
|
|
42637
|
+
if (current2 > gate.crossed) return { band: current2, next: { crossed: current2 } };
|
|
42638
|
+
const held = highestBandAtOrBelow(fill, PRESSURE_HYSTERESIS);
|
|
42639
|
+
return { band: null, next: { crossed: Math.min(gate.crossed, held) } };
|
|
42640
|
+
}
|
|
42641
|
+
function pressureMessage(band, fill) {
|
|
42642
|
+
const pct = Math.round(fill * 100);
|
|
42643
|
+
switch (band) {
|
|
42644
|
+
case 0.6:
|
|
42645
|
+
return `${CONTEXT_MARKER}Context is ${pct}% full. Read files in ranges rather than whole, summarize tool output rather than quoting it back, and stop re-reading things already in this conversation.`;
|
|
42646
|
+
case 0.8:
|
|
42647
|
+
return `${CONTEXT_MARKER}Context is ${pct}% full. Finish the task in progress and report it; start no new exploration, and open nothing large. If the work does not fit, say what is left rather than beginning it.`;
|
|
42648
|
+
case 0.9:
|
|
42649
|
+
return `${CONTEXT_MARKER}Context is ${pct}% full. Stop gathering and answer now with what you already have, naming what you did not get to. Further reading will be lost.`;
|
|
42650
|
+
}
|
|
42651
|
+
}
|
|
42652
|
+
function contextWindowLine(contextWindow) {
|
|
42653
|
+
if (!contextWindow || contextWindow <= 0) return "";
|
|
42654
|
+
return `Your context window is ${contextWindow.toLocaleString("en-US")} tokens. You will be told when it is filling up.`;
|
|
42655
|
+
}
|
|
42656
|
+
function isContextNote(m2) {
|
|
42657
|
+
return m2.role === "assistant" && typeof m2.content === "string" && m2.content.startsWith(CONTEXT_MARKER);
|
|
42658
|
+
}
|
|
42659
|
+
function contextNote(band, fill) {
|
|
42660
|
+
return { role: "assistant", content: pressureMessage(band, fill) };
|
|
42661
|
+
}
|
|
42662
|
+
|
|
42370
42663
|
// ../harness/core/compactHistory.ts
|
|
42371
42664
|
var CHECKPOINT_MARKER = "[checkpoint] ";
|
|
42372
42665
|
var ANCHOR_MARKER = "[anchor] ";
|
|
@@ -42416,11 +42709,11 @@ async function compactHistory(messages, opts) {
|
|
|
42416
42709
|
const pending = tail3 && isUnansweredToolCall(tail3) ? tail3 : null;
|
|
42417
42710
|
const body2 = pending ? messages.slice(0, -1) : messages;
|
|
42418
42711
|
const system = body2.filter((m2) => m2.role === "system");
|
|
42419
|
-
const collapsed = body2.filter((m2) => m2.role !== "system" && m2.role !== "user");
|
|
42712
|
+
const collapsed = body2.filter((m2) => m2.role !== "system" && m2.role !== "user" && !isContextNote(m2));
|
|
42420
42713
|
const kept = [];
|
|
42421
42714
|
let answered = null;
|
|
42422
42715
|
for (const m2 of body2) {
|
|
42423
|
-
if (isOwnMarker(m2)) continue;
|
|
42716
|
+
if (isOwnMarker(m2) || isContextNote(m2)) continue;
|
|
42424
42717
|
if (m2.role === "assistant" && typeof m2.content === "string") answered = m2;
|
|
42425
42718
|
if (m2.role !== "user") continue;
|
|
42426
42719
|
if (opts.anchor) {
|
|
@@ -42574,9 +42867,11 @@ async function runAgent(args) {
|
|
|
42574
42867
|
const ctxWindow = compression?.contextWindow;
|
|
42575
42868
|
const compressionStrategy = compression?.strategy ?? "eager";
|
|
42576
42869
|
let fill = 0;
|
|
42870
|
+
let pressureGate = initialPressureGate;
|
|
42577
42871
|
let compactGate = { armed: true, floor: null };
|
|
42578
42872
|
const deferredOffloads = [];
|
|
42579
|
-
const maxTurns = policy.maxTurns
|
|
42873
|
+
const maxTurns = policy.maxTurns !== void 0 ? policy.maxTurns : args.maxTurns !== void 0 ? args.maxTurns : 40;
|
|
42874
|
+
const unlimitedTurns = maxTurns === 0;
|
|
42580
42875
|
const signal = args.signal ?? new AbortController().signal;
|
|
42581
42876
|
const emit = (e2) => args.onEvent?.(e2);
|
|
42582
42877
|
const gstate = initialState();
|
|
@@ -42608,9 +42903,8 @@ async function runAgent(args) {
|
|
|
42608
42903
|
const capped = (list) => list.length > MAX_PROVIDER_TOOLS ? list.slice(0, MAX_PROVIDER_TOOLS) : list;
|
|
42609
42904
|
const activeTools = () => capped(loader ? [...eager, ...loader.loaded()] : eager);
|
|
42610
42905
|
let sent = activeTools();
|
|
42611
|
-
const
|
|
42612
|
-
|
|
42613
|
-
${serverIndex(deferred)}` : serverIndex(deferred) : system;
|
|
42906
|
+
const systemParts = [system, loader ? serverIndex(deferred) : "", contextWindowLine(ctxWindow)].filter(Boolean);
|
|
42907
|
+
const indexedSystem = systemParts.length ? systemParts.join("\n\n") : system;
|
|
42614
42908
|
const filledSystem = indexedSystem ? fillToolsMarker(indexedSystem, sent) : indexedSystem;
|
|
42615
42909
|
if (filledSystem && !messages.some((m2) => m2.role === "system")) {
|
|
42616
42910
|
messages.unshift({ role: "system", content: filledSystem });
|
|
@@ -42630,7 +42924,7 @@ ${serverIndex(deferred)}` : serverIndex(deferred) : system;
|
|
|
42630
42924
|
let stopReason = "stop";
|
|
42631
42925
|
let lastToolSig = null;
|
|
42632
42926
|
let lastStreamErr = "";
|
|
42633
|
-
for (let turn = 1; turn <= maxTurns; turn++) {
|
|
42927
|
+
for (let turn = 1; unlimitedTurns || turn <= maxTurns; turn++) {
|
|
42634
42928
|
if (signal.aborted) {
|
|
42635
42929
|
stopReason = "aborted";
|
|
42636
42930
|
break;
|
|
@@ -42668,13 +42962,28 @@ ${serverIndex(deferred)}` : serverIndex(deferred) : system;
|
|
|
42668
42962
|
alog("compaction failed", e2 instanceof Error ? e2.message : String(e2));
|
|
42669
42963
|
}
|
|
42670
42964
|
}
|
|
42671
|
-
|
|
42672
|
-
const
|
|
42673
|
-
|
|
42674
|
-
|
|
42675
|
-
|
|
42676
|
-
|
|
42677
|
-
|
|
42965
|
+
const pressureFill = fill || estimateFill(messages, ctxWindow);
|
|
42966
|
+
const pressure = pressureDecision(pressureFill, pressureGate);
|
|
42967
|
+
pressureGate = pressure.next;
|
|
42968
|
+
if (pressure.band !== null && ctxWindow) {
|
|
42969
|
+
messages.push(contextNote(pressure.band, pressureFill));
|
|
42970
|
+
emit({
|
|
42971
|
+
type: "context-pressure",
|
|
42972
|
+
band: pressure.band,
|
|
42973
|
+
fill: pressureFill,
|
|
42974
|
+
contextWindow: ctxWindow,
|
|
42975
|
+
promptTokens: Math.round(pressureFill * ctxWindow)
|
|
42976
|
+
});
|
|
42977
|
+
}
|
|
42978
|
+
if (!unlimitedTurns) {
|
|
42979
|
+
gstate.turn = turn - 1;
|
|
42980
|
+
const turnGate = evaluateTurnStart(policy, gstate);
|
|
42981
|
+
if (!turnGate.ok) {
|
|
42982
|
+
emit({ type: "policy-violation", kind: turnGate.violation.kind, message: turnGate.violation.message, action: turnGate.action, tool: turnGate.violation.tool });
|
|
42983
|
+
if (turnGate.action === "stop") {
|
|
42984
|
+
stopReason = "policy";
|
|
42985
|
+
break;
|
|
42986
|
+
}
|
|
42678
42987
|
}
|
|
42679
42988
|
}
|
|
42680
42989
|
if (loader) {
|
|
@@ -42892,7 +43201,7 @@ ${serverIndex(deferred)}` : serverIndex(deferred) : system;
|
|
|
42892
43201
|
stopReason = "policy";
|
|
42893
43202
|
break;
|
|
42894
43203
|
}
|
|
42895
|
-
if (turn === maxTurns) stopReason = "max_turns";
|
|
43204
|
+
if (!unlimitedTurns && turn === maxTurns) stopReason = "max_turns";
|
|
42896
43205
|
}
|
|
42897
43206
|
reconcileDanglingToolCalls(messages);
|
|
42898
43207
|
for (const v2 of evaluateCompletion(policy, gstate)) {
|
|
@@ -42976,9 +43285,10 @@ function now() {
|
|
|
42976
43285
|
return lastTs;
|
|
42977
43286
|
}
|
|
42978
43287
|
var JsonlSessionStore = class _JsonlSessionStore {
|
|
42979
|
-
constructor(getFiles, root = sessionsRoot) {
|
|
43288
|
+
constructor(getFiles, root = sessionsRoot, owner = () => void 0) {
|
|
42980
43289
|
this.getFiles = getFiles;
|
|
42981
43290
|
this.root = root;
|
|
43291
|
+
this.owner = owner;
|
|
42982
43292
|
}
|
|
42983
43293
|
cursors = /* @__PURE__ */ new Map();
|
|
42984
43294
|
async files() {
|
|
@@ -43013,11 +43323,13 @@ var JsonlSessionStore = class _JsonlSessionStore {
|
|
|
43013
43323
|
async create(title, surface) {
|
|
43014
43324
|
const createdAt = now();
|
|
43015
43325
|
const id = uid("ses");
|
|
43016
|
-
const
|
|
43326
|
+
const owner = this.owner();
|
|
43327
|
+
const own = owner ? { owner } : {};
|
|
43328
|
+
const meta = { k: "meta", id, title, createdAt, ...surface ? { surface } : {}, ...own };
|
|
43017
43329
|
const text = encodeLine(meta);
|
|
43018
43330
|
await (await this.files()).write(this.path(id), text);
|
|
43019
43331
|
this.cursors.set(id, { text, turnJson: [], title });
|
|
43020
|
-
return { id, title, createdAt, updatedAt: createdAt, messages: [], ...surface ? { surface } : {} };
|
|
43332
|
+
return { id, title, createdAt, updatedAt: createdAt, messages: [], ...surface ? { surface } : {}, ...own };
|
|
43021
43333
|
}
|
|
43022
43334
|
async get(id) {
|
|
43023
43335
|
const text = await (await this.files()).read(this.path(id)).catch(() => null);
|
|
@@ -43160,7 +43472,8 @@ var JsonlSessionStore = class _JsonlSessionStore {
|
|
|
43160
43472
|
id: session.id,
|
|
43161
43473
|
title: session.title,
|
|
43162
43474
|
createdAt: session.createdAt,
|
|
43163
|
-
...session.surface ? { surface: session.surface } : {}
|
|
43475
|
+
...session.surface ? { surface: session.surface } : {},
|
|
43476
|
+
...session.owner ? { owner: session.owner } : {}
|
|
43164
43477
|
});
|
|
43165
43478
|
return { text: meta, turnJson: [], title: session.title, titleLocked: void 0 };
|
|
43166
43479
|
}
|
|
@@ -43347,30 +43660,43 @@ function pricedForUsd(rates) {
|
|
|
43347
43660
|
if (!rates) return false;
|
|
43348
43661
|
return rates.inPrice > 0 || rates.outPrice > 0;
|
|
43349
43662
|
}
|
|
43350
|
-
async function
|
|
43351
|
-
const table = /* @__PURE__ */ new Map();
|
|
43663
|
+
async function fetchAvailableModels(endpoint, getToken, fetchImpl) {
|
|
43352
43664
|
let token = null;
|
|
43353
43665
|
try {
|
|
43354
43666
|
token = await getToken();
|
|
43355
43667
|
} catch {
|
|
43356
43668
|
token = null;
|
|
43357
43669
|
}
|
|
43358
|
-
if (!token) return
|
|
43670
|
+
if (!token) return [];
|
|
43359
43671
|
try {
|
|
43360
43672
|
const res = await fetchImpl(`${endpoint.replace(/\/$/, "")}/llm-providers/available`, {
|
|
43361
43673
|
headers: { Authorization: `Bearer ${token}` }
|
|
43362
43674
|
});
|
|
43363
|
-
if (!res.ok) return
|
|
43675
|
+
if (!res.ok) return [];
|
|
43364
43676
|
const json = await res.json();
|
|
43365
|
-
|
|
43366
|
-
|
|
43367
|
-
|
|
43368
|
-
table.set(m2.model, { inPrice: m2.inputPrice, outPrice: m2.outputPrice });
|
|
43369
|
-
}
|
|
43370
|
-
}
|
|
43677
|
+
const out = [];
|
|
43678
|
+
for (const p2 of json.providers ?? []) out.push(...p2.models ?? []);
|
|
43679
|
+
return out;
|
|
43371
43680
|
} catch {
|
|
43681
|
+
return [];
|
|
43682
|
+
}
|
|
43683
|
+
}
|
|
43684
|
+
async function hydrateCatalog(endpoint, getToken, fetchImpl = fetch) {
|
|
43685
|
+
const prices = /* @__PURE__ */ new Map();
|
|
43686
|
+
const windows = /* @__PURE__ */ new Map();
|
|
43687
|
+
const models = await fetchAvailableModels(endpoint, getToken, fetchImpl);
|
|
43688
|
+
for (const m2 of models) {
|
|
43689
|
+
if (m2.inputPrice != null && m2.outputPrice != null) {
|
|
43690
|
+
prices.set(m2.model, { inPrice: m2.inputPrice, outPrice: m2.outputPrice });
|
|
43691
|
+
}
|
|
43692
|
+
if (typeof m2.contextWindow === "number" && m2.contextWindow > 0) {
|
|
43693
|
+
windows.set(m2.model, m2.contextWindow);
|
|
43694
|
+
}
|
|
43372
43695
|
}
|
|
43373
|
-
return
|
|
43696
|
+
return { prices, windows };
|
|
43697
|
+
}
|
|
43698
|
+
async function hydratePrices(endpoint, getToken, fetchImpl = fetch) {
|
|
43699
|
+
return (await hydrateCatalog(endpoint, getToken, fetchImpl)).prices;
|
|
43374
43700
|
}
|
|
43375
43701
|
|
|
43376
43702
|
// src/runner/permissionMessage.ts
|
|
@@ -43665,7 +43991,7 @@ function makeUnattendedPrompt(deps) {
|
|
|
43665
43991
|
for (; ; ) {
|
|
43666
43992
|
if (remaining <= 0) return { by: "timeout" };
|
|
43667
43993
|
const tickMs = Math.min(remaining, PARK_POLL_MS);
|
|
43668
|
-
if (deps.registry.isAttended()) {
|
|
43994
|
+
if (deps.registry.isAttended(deps.owner)) {
|
|
43669
43995
|
const ctrl2 = new AbortController();
|
|
43670
43996
|
const answered = deps.registry.broadcastAsk(ask, ctrl2.signal);
|
|
43671
43997
|
const ticked = sleep2(tickMs, ctrl2.signal).then(() => "tick");
|
|
@@ -43703,7 +44029,11 @@ function makeUnattendedPrompt(deps) {
|
|
|
43703
44029
|
agent: deps.agent,
|
|
43704
44030
|
tool: req.tool,
|
|
43705
44031
|
input: req.input,
|
|
43706
|
-
deadline: now2() + bound
|
|
44032
|
+
deadline: now2() + bound,
|
|
44033
|
+
// Spread-when-present rather than `owner: deps.owner`, so an ownerless
|
|
44034
|
+
// ask has no `owner` KEY at all -- the registry's `!ask.owner` test
|
|
44035
|
+
// stays unambiguous and a serialised ask does not carry a null.
|
|
44036
|
+
...deps.owner ? { owner: deps.owner } : {}
|
|
43707
44037
|
};
|
|
43708
44038
|
if (deps.unattended === "park") {
|
|
43709
44039
|
deps.onParkChange?.(true);
|
|
@@ -43723,7 +44053,7 @@ function makeUnattendedPrompt(deps) {
|
|
|
43723
44053
|
}
|
|
43724
44054
|
}
|
|
43725
44055
|
const fallthrough = deps.unattended === "allow" ? "allow" : "deny";
|
|
43726
|
-
if (deps.registry.isAttended()) {
|
|
44056
|
+
if (deps.registry.isAttended(deps.owner)) {
|
|
43727
44057
|
deps.onParkChange?.(true);
|
|
43728
44058
|
deps.onAskWait?.(ask);
|
|
43729
44059
|
try {
|
|
@@ -44125,6 +44455,21 @@ var NodeRunnerService = class {
|
|
|
44125
44455
|
registry: registry2,
|
|
44126
44456
|
runId: sessionId,
|
|
44127
44457
|
agent,
|
|
44458
|
+
/* WHOSE RUN THIS IS, for `--mode team`'s ask routing.
|
|
44459
|
+
*
|
|
44460
|
+
* Only the `{ clientId }` variant names a connection. `{ owner }` is
|
|
44461
|
+
* a dirName the daemon's own trigger dispatcher resolved from
|
|
44462
|
+
* filesystem enumeration -- there is no socket behind it, so an ask
|
|
44463
|
+
* it raises has nobody to route to and correctly falls back to
|
|
44464
|
+
* fan-out. Same for a `{ clientId }` of `undefined` (an older peer).
|
|
44465
|
+
*
|
|
44466
|
+
* Deliberately the RAW clientId rather than `owner` (the resolved
|
|
44467
|
+
* dirName computed above): this value is only ever compared against
|
|
44468
|
+
* the identity a connection ATTACHED with, which is the same
|
|
44469
|
+
* authenticated clientId the harness filed it under. It is never
|
|
44470
|
+
* used as a path, so the manifest resolution that `owner` needs
|
|
44471
|
+
* would not make it safer -- it would just make it fail to match. */
|
|
44472
|
+
..."owner" in identity || identity.clientId === void 0 ? {} : { owner: identity.clientId },
|
|
44128
44473
|
unattended: def.policy.unattended ?? "deny",
|
|
44129
44474
|
...def.policy.parkTimeoutMs != null ? { parkTimeoutMs: def.policy.parkTimeoutMs } : {},
|
|
44130
44475
|
remainingMs,
|
|
@@ -44906,6 +45251,20 @@ function evaluateRunComplete(completion, agents, maxChainDepth, log) {
|
|
|
44906
45251
|
}
|
|
44907
45252
|
|
|
44908
45253
|
// src/runner/roomService.ts
|
|
45254
|
+
function hasRecordedOwner(state) {
|
|
45255
|
+
return state.participants.some((p2) => p2.kind === "human" && p2.role === "owner");
|
|
45256
|
+
}
|
|
45257
|
+
function mayAct(state, participantId, verb) {
|
|
45258
|
+
const member = state.participants.find((p2) => p2.id === participantId);
|
|
45259
|
+
if (!member) return `"${participantId}" is not a member of this room.`;
|
|
45260
|
+
const role = roleOf(member);
|
|
45261
|
+
if (role === null) return `"${participantId}" is an agent, not a participant who acts on the room.`;
|
|
45262
|
+
if (verb === "close") {
|
|
45263
|
+
if (!hasRecordedOwner(state)) return null;
|
|
45264
|
+
return role === "owner" ? null : `Only the owner can ${verb} this room; you are a ${role}.`;
|
|
45265
|
+
}
|
|
45266
|
+
return role === "viewer" ? `You are a viewer in this room and cannot ${verb}.` : null;
|
|
45267
|
+
}
|
|
44909
45268
|
function isDepsFactory(d2) {
|
|
44910
45269
|
return typeof d2 === "function";
|
|
44911
45270
|
}
|
|
@@ -45027,6 +45386,11 @@ function makeRoomService(opts) {
|
|
|
45027
45386
|
}
|
|
45028
45387
|
async function open(req) {
|
|
45029
45388
|
return enqueue(req.roomId, async () => {
|
|
45389
|
+
if (req.fromSession && req.fromSession.messages.length !== req.fromSession.turns) {
|
|
45390
|
+
const message = `Room "${req.roomId}" was not opened: the promoted thread arrived with ${req.fromSession.messages.length} message(s) but ${req.fromSession.turns} were expected. Refusing to open a room missing part of the conversation.`;
|
|
45391
|
+
log(`room "${req.roomId}" could not be opened: ${message}`);
|
|
45392
|
+
return { ok: false, problems: [message] };
|
|
45393
|
+
}
|
|
45030
45394
|
let deps;
|
|
45031
45395
|
try {
|
|
45032
45396
|
deps = await resolveDeps(req.participants, req.maxUsd ?? null);
|
|
@@ -45037,6 +45401,15 @@ function makeRoomService(opts) {
|
|
|
45037
45401
|
}
|
|
45038
45402
|
const opened = await openRoom(req, opts.reachability, deps);
|
|
45039
45403
|
if (!opened.ok) return opened;
|
|
45404
|
+
if (req.fromSession) {
|
|
45405
|
+
try {
|
|
45406
|
+
await deps.store.appendTurns(req.roomId, 0, req.fromSession.messages, deps.now());
|
|
45407
|
+
} catch (err) {
|
|
45408
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
45409
|
+
log(`room "${req.roomId}" opened but its promoted transcript failed to append: ${message}`);
|
|
45410
|
+
return { ok: false, problems: [`Room "${req.roomId}" opened, but the promoted conversation could not be saved into it: ${message}`] };
|
|
45411
|
+
}
|
|
45412
|
+
}
|
|
45040
45413
|
depsByRoom.set(req.roomId, deps);
|
|
45041
45414
|
const room = summaryOf(opened.state, null, null, 0);
|
|
45042
45415
|
record(room);
|
|
@@ -45092,10 +45465,22 @@ function makeRoomService(opts) {
|
|
|
45092
45465
|
}
|
|
45093
45466
|
return fresh;
|
|
45094
45467
|
}
|
|
45468
|
+
async function requireRole(deps, roomId, participantId, verb) {
|
|
45469
|
+
const state = await deps.store.read(roomId);
|
|
45470
|
+
if (!state || state.closed) return;
|
|
45471
|
+
const refusal = mayAct(state, participantId, verb);
|
|
45472
|
+
if (refusal) throw new Error(refusal);
|
|
45473
|
+
}
|
|
45095
45474
|
async function interject(roomId, participantId, text) {
|
|
45096
45475
|
return enqueue(roomId, async () => {
|
|
45097
45476
|
const deps = await baseDeps();
|
|
45477
|
+
await requireRole(deps, roomId, participantId, "interject");
|
|
45098
45478
|
await interjectHuman(roomId, participantId, text, deps);
|
|
45479
|
+
const state = await deps.store.read(roomId);
|
|
45480
|
+
if (state && !state.closed && state.mode === "conversation") {
|
|
45481
|
+
const driveDeps = await depsFor(roomId);
|
|
45482
|
+
if (driveDeps) return driveRoom(roomId, driveDeps);
|
|
45483
|
+
}
|
|
45099
45484
|
const summary = await summaryNow(roomId);
|
|
45100
45485
|
record(summary);
|
|
45101
45486
|
return summary;
|
|
@@ -45104,6 +45489,7 @@ function makeRoomService(opts) {
|
|
|
45104
45489
|
async function closeRoom(roomId, participantId, reason) {
|
|
45105
45490
|
return enqueue(roomId, async () => {
|
|
45106
45491
|
const deps = await baseDeps();
|
|
45492
|
+
await requireRole(deps, roomId, participantId, "close");
|
|
45107
45493
|
await closeRoomByHuman(roomId, participantId, reason, deps);
|
|
45108
45494
|
const summary = await summaryNow(roomId);
|
|
45109
45495
|
record(summary);
|
|
@@ -45117,6 +45503,7 @@ function makeRoomService(opts) {
|
|
|
45117
45503
|
async function editFinding(roomId, participantId, edit) {
|
|
45118
45504
|
return enqueue(roomId, async () => {
|
|
45119
45505
|
const deps = await baseDeps();
|
|
45506
|
+
await requireRole(deps, roomId, participantId, "finding-edit");
|
|
45120
45507
|
const doc = await editFindingByHuman(roomId, participantId, edit, deps);
|
|
45121
45508
|
record(await summaryNow(roomId));
|
|
45122
45509
|
return doc;
|
|
@@ -45126,6 +45513,7 @@ function makeRoomService(opts) {
|
|
|
45126
45513
|
return enqueue(roomId, async () => {
|
|
45127
45514
|
const deps = await depsFor(roomId);
|
|
45128
45515
|
if (!deps) throw new Error(`No room "${roomId}".`);
|
|
45516
|
+
await requireRole(deps, roomId, participantId, "answer");
|
|
45129
45517
|
await answerHumanTurn(roomId, participantId, text, deps, addressed);
|
|
45130
45518
|
return driveRoom(roomId, deps);
|
|
45131
45519
|
});
|
|
@@ -45692,27 +46080,49 @@ function startDaemonCore(opts) {
|
|
|
45692
46080
|
function makeAttendanceRegistry(opts) {
|
|
45693
46081
|
const log = opts?.log ?? (() => {
|
|
45694
46082
|
});
|
|
46083
|
+
const teamMode = opts?.teamMode === true;
|
|
46084
|
+
const EMPTY = /* @__PURE__ */ new Set();
|
|
45695
46085
|
const clients = /* @__PURE__ */ new Set();
|
|
46086
|
+
const identities = /* @__PURE__ */ new Map();
|
|
45696
46087
|
let lastRegistrations = null;
|
|
45697
46088
|
const outstanding = /* @__PURE__ */ new Map();
|
|
45698
46089
|
let idCounter = 0;
|
|
45699
|
-
const
|
|
46090
|
+
const recipients = (ask) => {
|
|
46091
|
+
if (!teamMode || !ask.owner) return clients;
|
|
46092
|
+
const owners = [];
|
|
46093
|
+
const ownerAccounts = /* @__PURE__ */ new Set();
|
|
46094
|
+
for (const [client, id] of identities) {
|
|
46095
|
+
if (id.clientId !== ask.owner) continue;
|
|
46096
|
+
owners.push(client);
|
|
46097
|
+
for (const a2 of id.accounts) ownerAccounts.add(a2);
|
|
46098
|
+
}
|
|
46099
|
+
if (owners.length === 0) return EMPTY;
|
|
46100
|
+
const out = new Set(owners);
|
|
46101
|
+
if (ownerAccounts.size > 0) {
|
|
46102
|
+
for (const [client, id] of identities) {
|
|
46103
|
+
if (id.accounts.some((a2) => ownerAccounts.has(a2))) out.add(client);
|
|
46104
|
+
}
|
|
46105
|
+
}
|
|
46106
|
+
return out;
|
|
46107
|
+
};
|
|
46108
|
+
const notifyResolved = (id, decision, by, byClientId) => {
|
|
45700
46109
|
for (const c2 of clients) {
|
|
45701
|
-
c2.resolved(id, decision, by);
|
|
46110
|
+
c2.resolved(id, decision, by, byClientId);
|
|
45702
46111
|
}
|
|
45703
46112
|
};
|
|
45704
|
-
const resolveEntry = (id, decision, by) => {
|
|
46113
|
+
const resolveEntry = (id, decision, by, byClientId) => {
|
|
45705
46114
|
const entry = outstanding.get(id);
|
|
45706
46115
|
if (!entry) return;
|
|
45707
46116
|
outstanding.delete(id);
|
|
45708
46117
|
entry.resolve(decision);
|
|
45709
|
-
notifyResolved(id, decision, by);
|
|
46118
|
+
notifyResolved(id, decision, by, byClientId);
|
|
45710
46119
|
};
|
|
45711
46120
|
return {
|
|
45712
|
-
attach(client) {
|
|
46121
|
+
attach(client, identity) {
|
|
45713
46122
|
clients.add(client);
|
|
46123
|
+
if (identity) identities.set(client, identity);
|
|
45714
46124
|
for (const { ask } of outstanding.values()) {
|
|
45715
|
-
client.send(ask);
|
|
46125
|
+
if (recipients(ask).has(client)) client.send(ask);
|
|
45716
46126
|
}
|
|
45717
46127
|
if (lastRegistrations) {
|
|
45718
46128
|
try {
|
|
@@ -45725,6 +46135,7 @@ function makeAttendanceRegistry(opts) {
|
|
|
45725
46135
|
if (detached) return;
|
|
45726
46136
|
detached = true;
|
|
45727
46137
|
clients.delete(client);
|
|
46138
|
+
identities.delete(client);
|
|
45728
46139
|
if (clients.size === 0 && outstanding.size > 0) {
|
|
45729
46140
|
log(`attendance: last client detached with ${outstanding.size} ask(s) outstanding; resolving null`);
|
|
45730
46141
|
for (const [id, entry] of Array.from(outstanding.entries())) {
|
|
@@ -45734,14 +46145,22 @@ function makeAttendanceRegistry(opts) {
|
|
|
45734
46145
|
}
|
|
45735
46146
|
};
|
|
45736
46147
|
},
|
|
45737
|
-
|
|
45738
|
-
return
|
|
46148
|
+
attachedIdentities() {
|
|
46149
|
+
return Array.from(identities.values());
|
|
46150
|
+
},
|
|
46151
|
+
isAttended(owner) {
|
|
46152
|
+
if (!teamMode || !owner) return clients.size > 0;
|
|
46153
|
+
for (const id of identities.values()) {
|
|
46154
|
+
if (id.clientId === owner) return true;
|
|
46155
|
+
}
|
|
46156
|
+
return false;
|
|
45739
46157
|
},
|
|
45740
46158
|
outstanding() {
|
|
45741
46159
|
return Array.from(outstanding.values()).map((e2) => e2.ask);
|
|
45742
46160
|
},
|
|
45743
46161
|
broadcastAsk(ask, signal) {
|
|
45744
|
-
|
|
46162
|
+
const to = recipients(ask);
|
|
46163
|
+
if (to.size === 0) {
|
|
45745
46164
|
return Promise.resolve(null);
|
|
45746
46165
|
}
|
|
45747
46166
|
return new Promise((resolve14) => {
|
|
@@ -45759,7 +46178,7 @@ function makeAttendanceRegistry(opts) {
|
|
|
45759
46178
|
resolve14(decision);
|
|
45760
46179
|
}
|
|
45761
46180
|
});
|
|
45762
|
-
for (const c2 of
|
|
46181
|
+
for (const c2 of to) {
|
|
45763
46182
|
c2.send(ask);
|
|
45764
46183
|
}
|
|
45765
46184
|
if (signal) {
|
|
@@ -45789,8 +46208,9 @@ function makeAttendanceRegistry(opts) {
|
|
|
45789
46208
|
}
|
|
45790
46209
|
}
|
|
45791
46210
|
},
|
|
45792
|
-
answer(id, decision) {
|
|
45793
|
-
|
|
46211
|
+
answer(id, decision, by) {
|
|
46212
|
+
const named = by ? identities.get(by)?.clientId : void 0;
|
|
46213
|
+
resolveEntry(id, decision, "client", named);
|
|
45794
46214
|
},
|
|
45795
46215
|
settle(id, decision, by) {
|
|
45796
46216
|
resolveEntry(id, decision, by);
|
|
@@ -47188,6 +47608,7 @@ var FLAG_TO_KEY = {
|
|
|
47188
47608
|
"--clone-under": "cloneUnder",
|
|
47189
47609
|
"--allow-origin": "allowOrigins",
|
|
47190
47610
|
"--allow-lan": "allowLan",
|
|
47611
|
+
"--mode": "teamMode",
|
|
47191
47612
|
"--allow-shell": "allowShell",
|
|
47192
47613
|
"--keep-transcripts": "keepTranscripts",
|
|
47193
47614
|
"--isolation": "isolation",
|
|
@@ -47206,7 +47627,7 @@ var FLAG_TO_KEY = {
|
|
|
47206
47627
|
"--default-model": "defaultModel",
|
|
47207
47628
|
"--name": "name"
|
|
47208
47629
|
};
|
|
47209
|
-
var DEFAULT_GUEST_IMAGE2 = "prompd/corenel-sandbox:0.
|
|
47630
|
+
var DEFAULT_GUEST_IMAGE2 = "prompd/corenel-sandbox:0.10.0";
|
|
47210
47631
|
function parseArgsWithProvenance(argv) {
|
|
47211
47632
|
const explicit = /* @__PURE__ */ new Set();
|
|
47212
47633
|
for (const a2 of argv) {
|
|
@@ -47223,6 +47644,7 @@ function parseArgs(argv) {
|
|
|
47223
47644
|
allowOrigins: [],
|
|
47224
47645
|
allowShell: false,
|
|
47225
47646
|
allowLan: false,
|
|
47647
|
+
teamMode: false,
|
|
47226
47648
|
keepTranscripts: false,
|
|
47227
47649
|
isolation: "none",
|
|
47228
47650
|
msbBin: process.env.MSB_BIN ?? "msb",
|
|
@@ -47277,7 +47699,11 @@ function parseArgs(argv) {
|
|
|
47277
47699
|
throw new Error(`--snapshot-budget: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
47278
47700
|
}
|
|
47279
47701
|
} else if (a2 === "--allow-lan") out.allowLan = true;
|
|
47280
|
-
else if (a2 === "--
|
|
47702
|
+
else if (a2 === "--mode") {
|
|
47703
|
+
const mode = argv[++i2];
|
|
47704
|
+
if (mode !== "team") throw new Error(`unknown --mode "${mode}". Known modes: team`);
|
|
47705
|
+
out.teamMode = true;
|
|
47706
|
+
} else if (a2 === "--tls-cert") out.tlsCert = resolve10(argv[++i2]);
|
|
47281
47707
|
else if (a2 === "--tls-key") out.tlsKey = resolve10(argv[++i2]);
|
|
47282
47708
|
else if (a2 === "--self-signed") out.selfSigned = true;
|
|
47283
47709
|
else if (a2 === "--tunnel") out.tunnel = argv[++i2];
|
|
@@ -47399,6 +47825,17 @@ function layerConfig(args, explicit, file) {
|
|
|
47399
47825
|
from[key] = "file";
|
|
47400
47826
|
if (key === "endpoint") out.endpointExplicit = true;
|
|
47401
47827
|
}
|
|
47828
|
+
if (explicit.has("teamMode")) {
|
|
47829
|
+
from.teamMode = "flag";
|
|
47830
|
+
} else if (file.mode === void 0) {
|
|
47831
|
+
from.teamMode = "default";
|
|
47832
|
+
} else if (file.mode === "team") {
|
|
47833
|
+
out.teamMode = true;
|
|
47834
|
+
from.teamMode = "file";
|
|
47835
|
+
} else {
|
|
47836
|
+
warnings.push(`daemon.json: "mode" must be "team", got ${JSON.stringify(file.mode)} -- ignoring it, using the default`);
|
|
47837
|
+
from.teamMode = "default";
|
|
47838
|
+
}
|
|
47402
47839
|
if (out.allowLan) {
|
|
47403
47840
|
if (from.host === "default") out.host = "0.0.0.0";
|
|
47404
47841
|
if (from.selfSigned === "default" && !out.tlsCert && !out.tlsKey) out.selfSigned = true;
|
|
@@ -47580,6 +48017,11 @@ var KEY_TYPES = {
|
|
|
47580
48017
|
allowShell: "boolean",
|
|
47581
48018
|
keepTranscripts: "boolean",
|
|
47582
48019
|
isolation: "string",
|
|
48020
|
+
// A string, not a boolean, so the file spells it the way the flag does
|
|
48021
|
+
// (`--mode team`) and a second mode needs no second key. layer.ts refuses
|
|
48022
|
+
// any value but 'team', with a warning -- typed loosely here, validated
|
|
48023
|
+
// strictly there, the same split `isolation` has.
|
|
48024
|
+
mode: "string",
|
|
47583
48025
|
msbBin: "string",
|
|
47584
48026
|
guestImage: "string",
|
|
47585
48027
|
maxChainDepth: "number",
|
|
@@ -51071,7 +51513,7 @@ async function main() {
|
|
|
51071
51513
|
// schedule looks broken for a quarter of an hour.
|
|
51072
51514
|
onWrite: () => core?.wake()
|
|
51073
51515
|
});
|
|
51074
|
-
const attendance = makeAttendanceRegistry({ log: slog });
|
|
51516
|
+
const attendance = makeAttendanceRegistry({ log: slog, teamMode: args.teamMode });
|
|
51075
51517
|
const runs = makeRunHub({ log: slog });
|
|
51076
51518
|
let core;
|
|
51077
51519
|
let crewSessions;
|
|
@@ -51387,6 +51829,10 @@ async function main() {
|
|
|
51387
51829
|
// (roomCtx above) -- absent otherwise, so `welcome.features` omits
|
|
51388
51830
|
// 'rooms' exactly like every other optional daemon capability here.
|
|
51389
51831
|
rooms: core?.rooms,
|
|
51832
|
+
// Same operator flag `attendance.ts` already reads for ask routing --
|
|
51833
|
+
// `room-list` applies the identical private-by-default narrowing to
|
|
51834
|
+
// rooms once this is on (see `onRoomList`'s R7 comment).
|
|
51835
|
+
teamMode: args.teamMode,
|
|
51390
51836
|
// The pause control plane. Absent without --daemon, so 'agent-pause' is not
|
|
51391
51837
|
// advertised and a client correctly reports that this daemon holds none.
|
|
51392
51838
|
...pauses ? {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corenel/sidecar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"description": "Prompd sidecar — a local capability server. Run it next to Prompd (prompd.app) so the browser app can reach your machine: local LLMs (Ollama/LM Studio/vLLM) without CORS, your filesystem, and (opt-in) shell.",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"esbuild": "^0.21.5",
|
|
37
37
|
"tsx": "^4.19.2",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
|
-
"@corenel/config": "0.
|
|
40
|
-
"@corenel/mcp": "0.
|
|
41
|
-
"@corenel/protocol": "0.
|
|
42
|
-
"@corenel/
|
|
43
|
-
"@corenel/
|
|
39
|
+
"@corenel/config": "0.3.0",
|
|
40
|
+
"@corenel/mcp": "0.2.0",
|
|
41
|
+
"@corenel/protocol": "0.9.0",
|
|
42
|
+
"@corenel/harness": "0.10.0",
|
|
43
|
+
"@corenel/tools-node": "0.5.0",
|
|
44
44
|
"@corenel/crew": "0.0.0"
|
|
45
45
|
},
|
|
46
46
|
"repository": {
|