@corenel/sidecar 0.6.1 → 0.7.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 +446 -48
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -14040,7 +14040,7 @@ var require_express_app = __commonJS({
|
|
|
14040
14040
|
this.name += this.ext = (this.defaultEngine[0] !== "." ? "." : "") + this.defaultEngine;
|
|
14041
14041
|
}
|
|
14042
14042
|
}
|
|
14043
|
-
NunjucksView.prototype.render = function
|
|
14043
|
+
NunjucksView.prototype.render = function render2(opts, cb) {
|
|
14044
14044
|
env.render(this.name, opts, cb);
|
|
14045
14045
|
};
|
|
14046
14046
|
app.set("view", NunjucksView);
|
|
@@ -14313,7 +14313,7 @@ var require_environment = __commonJS({
|
|
|
14313
14313
|
_proto.express = function express(app) {
|
|
14314
14314
|
return expressApp(this, app);
|
|
14315
14315
|
};
|
|
14316
|
-
_proto.render = function
|
|
14316
|
+
_proto.render = function render2(name, ctx, cb) {
|
|
14317
14317
|
if (lib.isFunction(ctx)) {
|
|
14318
14318
|
cb = ctx;
|
|
14319
14319
|
ctx = null;
|
|
@@ -14441,7 +14441,7 @@ var require_environment = __commonJS({
|
|
|
14441
14441
|
this.compiled = false;
|
|
14442
14442
|
}
|
|
14443
14443
|
};
|
|
14444
|
-
_proto3.render = function
|
|
14444
|
+
_proto3.render = function render2(ctx, parentFrame, cb) {
|
|
14445
14445
|
var _this6 = this;
|
|
14446
14446
|
if (typeof ctx === "function") {
|
|
14447
14447
|
cb = ctx;
|
|
@@ -15021,7 +15021,7 @@ var require_nunjucks = __commonJS({
|
|
|
15021
15021
|
}
|
|
15022
15022
|
return new Template(src2, env, path, eagerCompile);
|
|
15023
15023
|
},
|
|
15024
|
-
render: function
|
|
15024
|
+
render: function render2(name, ctx, cb) {
|
|
15025
15025
|
if (!e2) {
|
|
15026
15026
|
configure();
|
|
15027
15027
|
}
|
|
@@ -22488,6 +22488,10 @@ var SidecarServer = class {
|
|
|
22488
22488
|
* is the session layer's call, because the operator's permission lives
|
|
22489
22489
|
* there; this class would have to duplicate that rule to decide, and a
|
|
22490
22490
|
* duplicated permission rule is one that eventually disagrees. */
|
|
22491
|
+
// NOT from the wire: `allowMutations` is the daemon's own decision,
|
|
22492
|
+
// derived from an agent's `session:` block. A client that could set it
|
|
22493
|
+
// would grant itself mutating tools inside a sandbox. Deliberately
|
|
22494
|
+
// absent -- see session/guestRunner.ts.
|
|
22491
22495
|
this.opts.sessions.run(m2.sessionId, agent, input, onEvent2, identity, { keepTranscript: m2.keepTranscript === true })
|
|
22492
22496
|
) : (agent, input, onEvent2, identity) => this.opts.runner.run(agent, input, onEvent2, identity);
|
|
22493
22497
|
let runId;
|
|
@@ -23279,6 +23283,11 @@ function helpText() {
|
|
|
23279
23283
|
" roots list|add|remove [path]",
|
|
23280
23284
|
" edit the working directories in daemon.json (add stores the",
|
|
23281
23285
|
" resolved, absolute path)",
|
|
23286
|
+
" add-under list|add|remove [path]",
|
|
23287
|
+
" edit the areas a client may add a root under, same way",
|
|
23288
|
+
" config list|get|set|unset|add|remove [key] [value]",
|
|
23289
|
+
" read or change any daemon.json setting, including turning",
|
|
23290
|
+
" a capability back off (config set allow-shell false)",
|
|
23282
23291
|
" doctor report what this machine would serve, and what is wrong",
|
|
23283
23292
|
" (exits non-zero on a definite fault -- safe to script)",
|
|
23284
23293
|
" login|logout|auth manage the signed-in account",
|
|
@@ -28247,6 +28256,9 @@ async function readTranscript(files, crewRoot2, agent, runId) {
|
|
|
28247
28256
|
}
|
|
28248
28257
|
|
|
28249
28258
|
// src/session/manager.ts
|
|
28259
|
+
function isErrorStopReason(stopReason) {
|
|
28260
|
+
return stopReason === "error" || (stopReason ?? "").startsWith("error:");
|
|
28261
|
+
}
|
|
28250
28262
|
var SessionManager = class {
|
|
28251
28263
|
constructor(deps) {
|
|
28252
28264
|
this.deps = deps;
|
|
@@ -28332,11 +28344,11 @@ var SessionManager = class {
|
|
|
28332
28344
|
}
|
|
28333
28345
|
this.deps.log?.(`session ${sessionId}: running agent "${agent}" inside the session`);
|
|
28334
28346
|
const kept2 = this.transcriptOf(onEvent, opts?.keepTranscript === true);
|
|
28335
|
-
return this.recorded(session, agent, identity, () => this.deps.guestRunnerFor(sessionId).run(agent, input, kept2.tee, identity), kept2.buffer);
|
|
28347
|
+
return this.recorded(session, agent, identity, () => this.deps.guestRunnerFor(sessionId, { allowMutations: opts?.allowMutations === true }).run(agent, input, kept2.tee, identity), kept2.buffer, opts?.source);
|
|
28336
28348
|
}
|
|
28337
28349
|
this.deps.log?.(`session ${sessionId}: running agent "${agent}" on the host (isolation: none)`);
|
|
28338
28350
|
const kept = this.transcriptOf(onEvent, opts?.keepTranscript === true);
|
|
28339
|
-
return this.recorded(session, agent, identity, () => this.deps.runner.runFrom ? this.deps.runner.runFrom(session.workdir, agent, input, kept.tee, identity, session.stateDir) : this.deps.runner.run(agent, input, kept.tee, identity), kept.buffer);
|
|
28351
|
+
return this.recorded(session, agent, identity, () => this.deps.runner.runFrom ? this.deps.runner.runFrom(session.workdir, agent, input, kept.tee, identity, session.stateDir) : this.deps.runner.run(agent, input, kept.tee, identity, void 0, opts?.source), kept.buffer, opts?.source);
|
|
28340
28352
|
}
|
|
28341
28353
|
/**
|
|
28342
28354
|
* Run, then write one line about it — whatever happened.
|
|
@@ -28374,7 +28386,7 @@ var SessionManager = class {
|
|
|
28374
28386
|
}
|
|
28375
28387
|
};
|
|
28376
28388
|
}
|
|
28377
|
-
async recorded(session, agent, identity, go, transcript) {
|
|
28389
|
+
async recorded(session, agent, identity, go, transcript, source = "chat") {
|
|
28378
28390
|
const startedAt = Date.now();
|
|
28379
28391
|
const keep = (runId) => {
|
|
28380
28392
|
if (!this.deps.recordTranscript || !transcript || transcript.length === 0) return;
|
|
@@ -28392,7 +28404,7 @@ var SessionManager = class {
|
|
|
28392
28404
|
sessionId: session.id,
|
|
28393
28405
|
isolated: this.isolated,
|
|
28394
28406
|
kind: this.deps.backend.kind,
|
|
28395
|
-
source
|
|
28407
|
+
source,
|
|
28396
28408
|
startedAt,
|
|
28397
28409
|
endedAt: Date.now(),
|
|
28398
28410
|
ok,
|
|
@@ -28404,7 +28416,7 @@ var SessionManager = class {
|
|
|
28404
28416
|
void r2.done.then(
|
|
28405
28417
|
(d2) => {
|
|
28406
28418
|
keep(r2.runId);
|
|
28407
|
-
write3(r2.runId, d2.stopReason
|
|
28419
|
+
write3(r2.runId, !isErrorStopReason(d2.stopReason), d2.stopReason);
|
|
28408
28420
|
},
|
|
28409
28421
|
(e2) => {
|
|
28410
28422
|
keep(r2.runId);
|
|
@@ -28726,6 +28738,7 @@ var LABEL_CREATED = "corenel.created";
|
|
|
28726
28738
|
var LABEL_REPO_URL = "corenel.repo.url";
|
|
28727
28739
|
var LABEL_REPO_REF = "corenel.repo.ref";
|
|
28728
28740
|
var LABEL_REPO_COMMIT = "corenel.repo.commit";
|
|
28741
|
+
var LABEL_CREW_SESSION = "corenel.crew.session";
|
|
28729
28742
|
function idFromSandboxName(name) {
|
|
28730
28743
|
const m2 = /^corenel-(sess_\d+)$/.exec(name);
|
|
28731
28744
|
return m2 ? m2[1] : null;
|
|
@@ -28845,7 +28858,7 @@ function makeMicrovmBackend(deps) {
|
|
|
28845
28858
|
if (!live.length) return;
|
|
28846
28859
|
const r2 = await deps.run(listArgs());
|
|
28847
28860
|
if (r2.code !== 0) return;
|
|
28848
|
-
const present2 = new Set(parseSandboxList(r2.stdout).map((
|
|
28861
|
+
const present2 = new Set(parseSandboxList(r2.stdout).map((row2) => row2.name));
|
|
28849
28862
|
for (const s2 of live) {
|
|
28850
28863
|
if (present2.has(sandboxName(s2.id))) continue;
|
|
28851
28864
|
if (at2 - s2.createdAt < RECONCILE_MS) continue;
|
|
@@ -28891,7 +28904,8 @@ function makeMicrovmBackend(deps) {
|
|
|
28891
28904
|
[LABEL_REPO_URL]: spec.source.url,
|
|
28892
28905
|
[LABEL_REPO_COMMIT]: spec.source.commit,
|
|
28893
28906
|
...spec.source.ref ? { [LABEL_REPO_REF]: spec.source.ref } : {}
|
|
28894
|
-
} : {}
|
|
28907
|
+
} : {},
|
|
28908
|
+
...spec.sessionName ? { [LABEL_CREW_SESSION]: spec.sessionName } : {}
|
|
28895
28909
|
}
|
|
28896
28910
|
}), `create session ${id}`);
|
|
28897
28911
|
const handle = {
|
|
@@ -28903,7 +28917,8 @@ function makeMicrovmBackend(deps) {
|
|
|
28903
28917
|
// Kept so a run in this session looks for the agent where the creator's
|
|
28904
28918
|
// workspace actually keeps it.
|
|
28905
28919
|
...spec.stateDir ? { stateDir: spec.stateDir } : {},
|
|
28906
|
-
...spec.source ? { source: spec.source } : {}
|
|
28920
|
+
...spec.source ? { source: spec.source } : {},
|
|
28921
|
+
...spec.sessionName ? { sessionName: spec.sessionName } : {}
|
|
28907
28922
|
};
|
|
28908
28923
|
sessions.set(id, handle);
|
|
28909
28924
|
deps.log?.(
|
|
@@ -29006,9 +29021,28 @@ function makeMicrovmBackend(deps) {
|
|
|
29006
29021
|
*
|
|
29007
29022
|
* CAVEAT, deliberate: the log is the SANDBOX's, not this exec's. Entries
|
|
29008
29023
|
* carry an `id` per invocation, but it is not known before the exec starts,
|
|
29009
|
-
* so concurrent execs in one sandbox
|
|
29010
|
-
*
|
|
29011
|
-
*
|
|
29024
|
+
* so concurrent execs in one sandbox interleave.
|
|
29025
|
+
*
|
|
29026
|
+
* NOTHING PREVENTS THAT, and no earlier claim here should be believed.
|
|
29027
|
+
* This comment once said SessionManager admits one run per session at a
|
|
29028
|
+
* time; a correction then said a scheduled agent's runs "already chain on
|
|
29029
|
+
* that queue". BOTH ARE FALSE. `manager.ts` checks existence and status
|
|
29030
|
+
* only; `SessionStatus` is 'created' | 'paused' | 'destroyed' (types.ts)
|
|
29031
|
+
* with no 'running'; and `RunQueue` (runnerService.ts) wraps the HOST
|
|
29032
|
+
* executor, which the bound path never reaches -- `dispatcher.startInSession`
|
|
29033
|
+
* goes straight to `sessions.run` -> `crewSessions.ts` -> `manager.run`.
|
|
29034
|
+
* Nothing anywhere serializes two runs into one session.
|
|
29035
|
+
*
|
|
29036
|
+
* What NARROWS the window is weaker than a lock and worth stating exactly:
|
|
29037
|
+
* a bound agent gets its own box (spec D3), and the dispatcher dedupes on
|
|
29038
|
+
* an in-flight key of `owner:agent:trigger`.
|
|
29039
|
+
*
|
|
29040
|
+
* What is STILL OPEN, inside the scheduled path: an agent carrying both a
|
|
29041
|
+
* `schedule` and a `continuous` trigger holds two DISTINCT in-flight keys,
|
|
29042
|
+
* so both fire concurrently by design (dispatcher.ts says so in as many
|
|
29043
|
+
* words), and `resolveCrewSession` keys reuse on (name, workdir) only --
|
|
29044
|
+
* so both land in the same sandbox and interleave in one log. The browser
|
|
29045
|
+
* can likewise send two runs into one session.
|
|
29012
29046
|
*/
|
|
29013
29047
|
...deps.stream ? {
|
|
29014
29048
|
execStream(id, command, onLine, opts) {
|
|
@@ -29130,21 +29164,21 @@ function makeMicrovmBackend(deps) {
|
|
|
29130
29164
|
deps.log?.(`sessions: could not list existing sandboxes (msb exit ${r2.code}) \u2014 nothing adopted`);
|
|
29131
29165
|
return { adopted, unmanaged };
|
|
29132
29166
|
}
|
|
29133
|
-
for (const
|
|
29134
|
-
const id = idFromSandboxName(
|
|
29167
|
+
for (const row2 of parseSandboxList(r2.stdout)) {
|
|
29168
|
+
const id = idFromSandboxName(row2.name);
|
|
29135
29169
|
if (!id) continue;
|
|
29136
29170
|
seq2 = Math.max(seq2, seqOf(id));
|
|
29137
29171
|
if (sessions.has(id)) continue;
|
|
29138
|
-
const inspected = await deps.run(inspectArgs(
|
|
29172
|
+
const inspected = await deps.run(inspectArgs(row2.name)).catch(() => null);
|
|
29139
29173
|
const labels = inspected && inspected.code === 0 ? parseSandboxLabels(inspected.stdout) : {};
|
|
29140
29174
|
const workdir = labels[LABEL_WORKDIR];
|
|
29141
29175
|
if (!workdir) {
|
|
29142
|
-
unmanaged.push(
|
|
29176
|
+
unmanaged.push(row2.name);
|
|
29143
29177
|
continue;
|
|
29144
29178
|
}
|
|
29145
29179
|
if (!within(workdir)) {
|
|
29146
|
-
deps.log?.(`sessions: sandbox "${
|
|
29147
|
-
unmanaged.push(
|
|
29180
|
+
deps.log?.(`sessions: sandbox "${row2.name}" holds ${workdir}, outside this daemon's roots \u2014 not adopted`);
|
|
29181
|
+
unmanaged.push(row2.name);
|
|
29148
29182
|
continue;
|
|
29149
29183
|
}
|
|
29150
29184
|
const stamped = Number(labels[LABEL_CREATED]);
|
|
@@ -29152,13 +29186,15 @@ function makeMicrovmBackend(deps) {
|
|
|
29152
29186
|
const repoUrl = labels[LABEL_REPO_URL];
|
|
29153
29187
|
const repoCommit = labels[LABEL_REPO_COMMIT];
|
|
29154
29188
|
const repoRef = labels[LABEL_REPO_REF];
|
|
29189
|
+
const sessionName = labels[LABEL_CREW_SESSION];
|
|
29155
29190
|
sessions.set(id, {
|
|
29156
29191
|
id,
|
|
29157
|
-
status: statusFromSandbox(
|
|
29192
|
+
status: statusFromSandbox(row2.status),
|
|
29158
29193
|
workdir,
|
|
29159
|
-
createdAt: Number.isFinite(stamped) && stamped > 0 ? stamped :
|
|
29194
|
+
createdAt: Number.isFinite(stamped) && stamped > 0 ? stamped : row2.createdAt ?? now2(),
|
|
29160
29195
|
...owner ? { owner } : {},
|
|
29161
29196
|
...repoUrl && repoCommit ? { source: { url: repoUrl, commit: repoCommit, ...repoRef ? { ref: repoRef } : {} } } : {},
|
|
29197
|
+
...sessionName ? { sessionName } : {},
|
|
29162
29198
|
/* THE ONLY PLACE THIS IS EVER SET TRUE. `create()` never touches it,
|
|
29163
29199
|
* so a card can tell "this daemon booted it" from "this daemon
|
|
29164
29200
|
* found it already running" — procs.ts's own words: a running
|
|
@@ -29258,10 +29294,11 @@ function makeMicrovmBackend(deps) {
|
|
|
29258
29294
|
}
|
|
29259
29295
|
|
|
29260
29296
|
// src/session/guestRunner.ts
|
|
29261
|
-
var DEFAULT_GUEST_COMMAND = (agent, input) => [
|
|
29297
|
+
var DEFAULT_GUEST_COMMAND = (agent, input, opts) => [
|
|
29262
29298
|
"corenel",
|
|
29263
29299
|
"run-agent",
|
|
29264
29300
|
agent,
|
|
29301
|
+
...opts.allowMutations ? ["--allow-mutations"] : [],
|
|
29265
29302
|
"--input",
|
|
29266
29303
|
input
|
|
29267
29304
|
];
|
|
@@ -29306,7 +29343,7 @@ function makeGuestRunner(deps) {
|
|
|
29306
29343
|
const exec = deps.backend.exec.bind(deps.backend);
|
|
29307
29344
|
const runId = `grun_${++seq3}`;
|
|
29308
29345
|
deps.log?.(`session ${sessionId}: running "${agent}" inside the guest (${runId})`);
|
|
29309
|
-
const command = build(agent, input);
|
|
29346
|
+
const command = build(agent, input, { allowMutations: deps.allowMutations === true });
|
|
29310
29347
|
const timeout = deps.timeoutMs === void 0 ? void 0 : { timeoutMs: deps.timeoutMs };
|
|
29311
29348
|
const seen = [];
|
|
29312
29349
|
let skipped = 0;
|
|
@@ -29514,9 +29551,10 @@ async function wireSessions(opts) {
|
|
|
29514
29551
|
...opts.recordTranscript ? { recordTranscript: opts.recordTranscript } : {},
|
|
29515
29552
|
// One runner per session, built on demand: the manager knows which session a
|
|
29516
29553
|
// run belongs to, so nothing has to infer it.
|
|
29517
|
-
guestRunnerFor: (sessionId) => makeGuestRunner({
|
|
29554
|
+
guestRunnerFor: (sessionId, guestOpts) => makeGuestRunner({
|
|
29518
29555
|
backend,
|
|
29519
29556
|
sessionId: () => sessionId,
|
|
29557
|
+
allowMutations: guestOpts.allowMutations,
|
|
29520
29558
|
...opts.log ? { log: opts.log } : {}
|
|
29521
29559
|
}),
|
|
29522
29560
|
...opts.log ? { log: opts.log } : {}
|
|
@@ -29528,6 +29566,26 @@ async function wireSessions(opts) {
|
|
|
29528
29566
|
return { manager, backend, port: makeSessionsPort({ manager, backend, guestEnv, ...portExtras(opts) }) };
|
|
29529
29567
|
}
|
|
29530
29568
|
|
|
29569
|
+
// src/runner/crewSessions.ts
|
|
29570
|
+
function makeCrewSessions(src2, opts) {
|
|
29571
|
+
if (!src2?.manager.isolated) return void 0;
|
|
29572
|
+
const { manager, backend } = src2;
|
|
29573
|
+
return {
|
|
29574
|
+
list: () => backend.list(),
|
|
29575
|
+
adopt: () => backend.adopt?.() ?? Promise.resolve(void 0),
|
|
29576
|
+
create: async (spec) => {
|
|
29577
|
+
const env = await opts.guestEnv().catch(() => ({}));
|
|
29578
|
+
return manager.create({
|
|
29579
|
+
workdir: spec.workdir,
|
|
29580
|
+
sessionName: spec.sessionName,
|
|
29581
|
+
stateDir: opts.stateDir,
|
|
29582
|
+
env: { ...env, CORENEL_STATE_DIR: opts.stateDir }
|
|
29583
|
+
});
|
|
29584
|
+
},
|
|
29585
|
+
run: (sessionId, agent, input, onEvent, identity, runOpts) => manager.run(sessionId, agent, input, onEvent, identity, runOpts)
|
|
29586
|
+
};
|
|
29587
|
+
}
|
|
29588
|
+
|
|
29531
29589
|
// src/runner/owners.ts
|
|
29532
29590
|
import { readdir } from "node:fs/promises";
|
|
29533
29591
|
import { join as join8 } from "node:path";
|
|
@@ -30215,6 +30273,41 @@ var CREW_EXCLUDED_TOOLS = /* @__PURE__ */ new Set(["ask_user", "spawn_agent", "s
|
|
|
30215
30273
|
|
|
30216
30274
|
// ../crew/store.ts
|
|
30217
30275
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
30276
|
+
|
|
30277
|
+
// ../crew/sessionBinding.ts
|
|
30278
|
+
var SESSION_NAME_RE = /^[a-z0-9_-]{1,32}$/;
|
|
30279
|
+
function isValidSessionName(name) {
|
|
30280
|
+
return SESSION_NAME_RE.test(name);
|
|
30281
|
+
}
|
|
30282
|
+
function parseSessionBinding(meta) {
|
|
30283
|
+
const raw = meta.session;
|
|
30284
|
+
if (raw === void 0) return { kind: "absent" };
|
|
30285
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
30286
|
+
return { kind: "malformed", reason: "session: must be a block with `name:` and `workdir:`" };
|
|
30287
|
+
}
|
|
30288
|
+
const o2 = raw;
|
|
30289
|
+
const name = typeof o2.name === "string" ? o2.name : "";
|
|
30290
|
+
const workdir = typeof o2.workdir === "string" ? o2.workdir.trim() : "";
|
|
30291
|
+
if (!isValidSessionName(name)) {
|
|
30292
|
+
return {
|
|
30293
|
+
kind: "malformed",
|
|
30294
|
+
reason: `session.name ${JSON.stringify(name)} must be 1-32 characters of a-z, 0-9, "-" or "_"`
|
|
30295
|
+
};
|
|
30296
|
+
}
|
|
30297
|
+
if (!workdir) return { kind: "malformed", reason: "session.workdir is required" };
|
|
30298
|
+
return {
|
|
30299
|
+
kind: "binding",
|
|
30300
|
+
binding: {
|
|
30301
|
+
name,
|
|
30302
|
+
workdir,
|
|
30303
|
+
// Anything but an explicit `false` is true, matching how `enabled` is read
|
|
30304
|
+
// in store.ts — absent means the default, and the default is on.
|
|
30305
|
+
createIfMissing: o2.createIfMissing !== false
|
|
30306
|
+
}
|
|
30307
|
+
};
|
|
30308
|
+
}
|
|
30309
|
+
|
|
30310
|
+
// ../crew/store.ts
|
|
30218
30311
|
var TRIGGER_TYPES = [
|
|
30219
30312
|
"manual",
|
|
30220
30313
|
"file-change",
|
|
@@ -30300,6 +30393,7 @@ async function loadCrewAgentAt(files, root, name, scope = "workspace") {
|
|
|
30300
30393
|
if (!isValidAgentName(name)) throw new Error(`invalid crew agent name: ${name}`);
|
|
30301
30394
|
const dir = `${root}/${name}`;
|
|
30302
30395
|
const { meta, body: body2 } = parseFrontmatter(await readOr(files, `${dir}/AGENT.md`));
|
|
30396
|
+
const session = parseSessionBinding(meta);
|
|
30303
30397
|
return {
|
|
30304
30398
|
name,
|
|
30305
30399
|
jobTitle: typeof meta.jobTitle === "string" ? meta.jobTitle : "",
|
|
@@ -30314,7 +30408,9 @@ async function loadCrewAgentAt(files, root, name, scope = "workspace") {
|
|
|
30314
30408
|
policy: parsePolicyYaml(await readOr(files, `${dir}/POLICY.yaml`), name),
|
|
30315
30409
|
settings: parseSettings(await readOr(files, `${dir}/settings.json`, "{}")),
|
|
30316
30410
|
budget: parseBudget(meta),
|
|
30317
|
-
canCall: parseCanCall(meta)
|
|
30411
|
+
canCall: parseCanCall(meta),
|
|
30412
|
+
...session.kind === "binding" ? { session: session.binding } : {},
|
|
30413
|
+
...session.kind === "malformed" ? { sessionError: session.reason } : {}
|
|
30318
30414
|
};
|
|
30319
30415
|
}
|
|
30320
30416
|
async function loadCrewAgentAtIfExists(files, root, name, scope = "workspace") {
|
|
@@ -42613,6 +42709,26 @@ function makeTriggerSchedule(opts) {
|
|
|
42613
42709
|
return { due, nextWakeAfter, registrations };
|
|
42614
42710
|
}
|
|
42615
42711
|
|
|
42712
|
+
// src/session/crewSessionResolve.ts
|
|
42713
|
+
function sameDir(a2, b2) {
|
|
42714
|
+
const norm2 = (p2) => p2.split("\\").join("/").replace(/[/]+$/, "").toLowerCase();
|
|
42715
|
+
return norm2(a2) === norm2(b2);
|
|
42716
|
+
}
|
|
42717
|
+
function resolveCrewSession(binding, handles) {
|
|
42718
|
+
const matches = handles.filter((h2) => h2.status === "created" && h2.sessionName === binding.name && sameDir(h2.workdir, binding.workdir));
|
|
42719
|
+
if (matches.length) {
|
|
42720
|
+
const newest = matches.reduce((a2, b2) => b2.createdAt > a2.createdAt ? b2 : a2);
|
|
42721
|
+
return { kind: "reuse", sessionId: newest.id };
|
|
42722
|
+
}
|
|
42723
|
+
if (!binding.createIfMissing) {
|
|
42724
|
+
return {
|
|
42725
|
+
kind: "refuse",
|
|
42726
|
+
reason: `no live session "${binding.name}" on ${binding.workdir}, and createIfMissing is off`
|
|
42727
|
+
};
|
|
42728
|
+
}
|
|
42729
|
+
return { kind: "create", spec: { workdir: binding.workdir, sessionName: binding.name } };
|
|
42730
|
+
}
|
|
42731
|
+
|
|
42616
42732
|
// src/runner/dispatcher.ts
|
|
42617
42733
|
function hasBudget2(def) {
|
|
42618
42734
|
return def.budget?.maxUsd != null || def.budget?.maxTokens != null;
|
|
@@ -42636,11 +42752,17 @@ function makeDispatcher(opts) {
|
|
|
42636
42752
|
});
|
|
42637
42753
|
return next;
|
|
42638
42754
|
}
|
|
42639
|
-
async function recordFailedDispatch(d2, root, startedAt, runId, message) {
|
|
42755
|
+
async function recordFailedDispatch(d2, root, startedAt, runId, message, where) {
|
|
42640
42756
|
await appendRun(root.files, root.root, {
|
|
42641
42757
|
runId,
|
|
42642
42758
|
agent: d2.agent,
|
|
42643
|
-
where:
|
|
42759
|
+
/* PASSED, NOT ASSUMED. This said `where: 'host'` unconditionally, so
|
|
42760
|
+
* every D5 refusal -- no session backend, `createIfMissing: false`, a
|
|
42761
|
+
* `create` that threw, a malformed `session:` block -- told the operator
|
|
42762
|
+
* the run had happened on the host, which is the one thing this feature
|
|
42763
|
+
* guarantees did NOT happen. A bound agent that refuses files a
|
|
42764
|
+
* `where: 'session'` row: nothing ran, and nothing ran THERE. */
|
|
42765
|
+
where,
|
|
42644
42766
|
source: d2.trigger,
|
|
42645
42767
|
startedAt,
|
|
42646
42768
|
endedAt: now2(),
|
|
@@ -42648,6 +42770,35 @@ function makeDispatcher(opts) {
|
|
|
42648
42770
|
stopReason: message
|
|
42649
42771
|
}, log);
|
|
42650
42772
|
}
|
|
42773
|
+
async function startInSession(binding, d2) {
|
|
42774
|
+
const sessions = opts.sessions?.();
|
|
42775
|
+
if (!sessions) {
|
|
42776
|
+
throw new Error(
|
|
42777
|
+
`crew agent "${d2.agent}" is bound to session "${binding.name}", but this daemon has no session backend`
|
|
42778
|
+
);
|
|
42779
|
+
}
|
|
42780
|
+
try {
|
|
42781
|
+
await sessions.adopt?.();
|
|
42782
|
+
} catch {
|
|
42783
|
+
}
|
|
42784
|
+
const decision = resolveCrewSession(binding, sessions.list());
|
|
42785
|
+
if (decision.kind === "refuse") {
|
|
42786
|
+
throw new Error(`crew agent "${d2.agent}": ${decision.reason}`);
|
|
42787
|
+
}
|
|
42788
|
+
const sessionId = decision.kind === "reuse" ? decision.sessionId : (await sessions.create(decision.spec)).id;
|
|
42789
|
+
return sessions.run(
|
|
42790
|
+
sessionId,
|
|
42791
|
+
d2.agent,
|
|
42792
|
+
d2.input,
|
|
42793
|
+
() => {
|
|
42794
|
+
},
|
|
42795
|
+
{ owner: d2.owner },
|
|
42796
|
+
// `source` is the trigger, not 'chat': the session's own run record is
|
|
42797
|
+
// the only durable answer to "why did this happen", and it is written by
|
|
42798
|
+
// the session layer, not by this file's failure path.
|
|
42799
|
+
{ allowMutations: true, source: d2.trigger }
|
|
42800
|
+
);
|
|
42801
|
+
}
|
|
42651
42802
|
async function fire(d2) {
|
|
42652
42803
|
const dispatchedAt = now2();
|
|
42653
42804
|
const key = `${d2.owner}:${d2.agent}:${d2.trigger}`;
|
|
@@ -42656,6 +42807,7 @@ function makeDispatcher(opts) {
|
|
|
42656
42807
|
return;
|
|
42657
42808
|
}
|
|
42658
42809
|
inFlight.add(key);
|
|
42810
|
+
let where = "host";
|
|
42659
42811
|
try {
|
|
42660
42812
|
const roots = await opts.ownerRoots();
|
|
42661
42813
|
const match = roots.find((r2) => r2.owner === d2.owner);
|
|
@@ -42668,12 +42820,17 @@ function makeDispatcher(opts) {
|
|
|
42668
42820
|
log(`dispatcher: agent "${d2.agent}" not found (owner "${d2.owner}") -- skipped`);
|
|
42669
42821
|
return;
|
|
42670
42822
|
}
|
|
42823
|
+
where = def.session || def.sessionError ? "session" : "host";
|
|
42671
42824
|
if (d2.trigger === "continuous" && !hasBudget2(def)) {
|
|
42672
42825
|
log(`refusing continuous run for "${d2.agent}" (owner "${d2.owner}"): a budget (maxUsd or maxTokens) is required`);
|
|
42673
42826
|
return;
|
|
42674
42827
|
}
|
|
42675
42828
|
await withAgentStateLock(`${d2.owner}:${d2.agent}`, () => persistDispatch(d2, match));
|
|
42676
|
-
|
|
42829
|
+
if (def.sessionError) {
|
|
42830
|
+
throw new Error(`crew agent "${d2.agent}" has an unusable session: block -- ${def.sessionError}`);
|
|
42831
|
+
}
|
|
42832
|
+
const binding = def.session;
|
|
42833
|
+
const started = binding ? await startInSession(binding, d2) : await runner.run(d2.agent, d2.input, () => {
|
|
42677
42834
|
}, { owner: d2.owner }, d2.generation ?? 0, d2.trigger);
|
|
42678
42835
|
const startedAt = now2();
|
|
42679
42836
|
const announce = (extra) => {
|
|
@@ -42705,7 +42862,7 @@ function makeDispatcher(opts) {
|
|
|
42705
42862
|
announce({ endedAt: now2(), ok: false, stopReason: msg });
|
|
42706
42863
|
log(`dispatcher: run of "${d2.agent}" (${d2.trigger}) failed -- ${msg}`);
|
|
42707
42864
|
void settleSelfDirected(d2, match);
|
|
42708
|
-
void recordFailedDispatch(d2, match, startedAt, started.runId, msg);
|
|
42865
|
+
void recordFailedDispatch(d2, match, startedAt, started.runId, msg, where);
|
|
42709
42866
|
}
|
|
42710
42867
|
);
|
|
42711
42868
|
} catch (err) {
|
|
@@ -42714,7 +42871,7 @@ function makeDispatcher(opts) {
|
|
|
42714
42871
|
try {
|
|
42715
42872
|
const roots = await opts.ownerRoots();
|
|
42716
42873
|
const root = roots.find((r2) => r2.owner === d2.owner);
|
|
42717
|
-
if (root) await recordFailedDispatch(d2, root, dispatchedAt, `dispatch-${dispatchedAt}`, msg);
|
|
42874
|
+
if (root) await recordFailedDispatch(d2, root, dispatchedAt, `dispatch-${dispatchedAt}`, msg, where);
|
|
42718
42875
|
} catch {
|
|
42719
42876
|
}
|
|
42720
42877
|
} finally {
|
|
@@ -43404,6 +43561,9 @@ function startDaemonCore(opts) {
|
|
|
43404
43561
|
// it -- the same contract `schedule_self` has. `clock` is declared below
|
|
43405
43562
|
// and only read when this fires, long after it is initialized.
|
|
43406
43563
|
onScheduled: () => clock.wake(),
|
|
43564
|
+
// Read per fire(), so a daemon whose session layer comes up after this
|
|
43565
|
+
// core still routes bound agents into it.
|
|
43566
|
+
...opts.sessions ? { sessions: opts.sessions } : {},
|
|
43407
43567
|
log
|
|
43408
43568
|
});
|
|
43409
43569
|
const roomProgressSubs = /* @__PURE__ */ new Set();
|
|
@@ -45398,6 +45558,7 @@ async function resolveEndpoint(args, env = process.env) {
|
|
|
45398
45558
|
import { readFile as readFile2, writeFile, mkdir as mkdir3 } from "node:fs/promises";
|
|
45399
45559
|
import { dirname as dirname7, join as join18 } from "node:path";
|
|
45400
45560
|
var DAEMON_CONFIG_VERSION = 1;
|
|
45561
|
+
var SECRET_KEYS = ["acpAuth", "tunnelToken", "token"];
|
|
45401
45562
|
var KNOWN_KEYS = [
|
|
45402
45563
|
"version",
|
|
45403
45564
|
"port",
|
|
@@ -45514,6 +45675,10 @@ function layerConfig(args, explicit, file) {
|
|
|
45514
45675
|
from[key] = "file";
|
|
45515
45676
|
if (key === "endpoint") out.endpointExplicit = true;
|
|
45516
45677
|
}
|
|
45678
|
+
if (out.allowLan) {
|
|
45679
|
+
if (from.host === "default") out.host = "0.0.0.0";
|
|
45680
|
+
if (from.selfSigned === "default" && !out.tlsCert && !out.tlsKey) out.selfSigned = true;
|
|
45681
|
+
}
|
|
45517
45682
|
return { args: out, from, warnings };
|
|
45518
45683
|
}
|
|
45519
45684
|
|
|
@@ -45576,59 +45741,285 @@ async function runSetup(argv, io) {
|
|
|
45576
45741
|
return 0;
|
|
45577
45742
|
}
|
|
45578
45743
|
|
|
45579
|
-
// src/config/
|
|
45744
|
+
// src/config/pathList.ts
|
|
45580
45745
|
import { resolve as resolve10 } from "node:path";
|
|
45581
|
-
|
|
45746
|
+
function stripStateDir(argv) {
|
|
45747
|
+
const at2 = argv.indexOf("--state-dir");
|
|
45748
|
+
if (at2 < 0) return argv;
|
|
45749
|
+
return [...argv.slice(0, at2), ...argv.slice(at2 + (argv[at2 + 1] === void 0 ? 1 : 2))];
|
|
45750
|
+
}
|
|
45751
|
+
async function runPathList(raw, io, spec) {
|
|
45752
|
+
const argv = stripStateDir(raw);
|
|
45582
45753
|
const sub = argv[0];
|
|
45583
45754
|
const { config, unknown, corrupt } = await readDaemonConfig(io.stateDir);
|
|
45584
|
-
const
|
|
45755
|
+
const stored = config[spec.key];
|
|
45756
|
+
const list = Array.isArray(stored) ? stored : [];
|
|
45585
45757
|
if (corrupt && (sub === "add" || sub === "remove")) {
|
|
45586
45758
|
io.err(`daemon.json in ${io.stateDir} is not valid JSON; fix or delete it before writing.`);
|
|
45587
45759
|
return 2;
|
|
45588
45760
|
}
|
|
45589
45761
|
if (sub === "list") {
|
|
45590
|
-
if (
|
|
45591
|
-
io.out(
|
|
45762
|
+
if (list.length === 0) {
|
|
45763
|
+
io.out(spec.empty);
|
|
45592
45764
|
return 0;
|
|
45593
45765
|
}
|
|
45594
|
-
for (const
|
|
45766
|
+
for (const p2 of list) io.out(p2);
|
|
45595
45767
|
return 0;
|
|
45596
45768
|
}
|
|
45597
45769
|
if (sub === "add") {
|
|
45598
45770
|
const path = argv[1];
|
|
45599
45771
|
if (!path) {
|
|
45600
|
-
io.err(
|
|
45772
|
+
io.err(`${spec.command} add needs a path.`);
|
|
45601
45773
|
return 2;
|
|
45602
45774
|
}
|
|
45603
45775
|
const full = resolve10(path);
|
|
45604
|
-
if (
|
|
45605
|
-
io.out(`${full} is already a configured
|
|
45776
|
+
if (list.some((p2) => resolve10(p2) === full)) {
|
|
45777
|
+
io.out(`${full} is already a configured ${spec.noun}.`);
|
|
45606
45778
|
return 0;
|
|
45607
45779
|
}
|
|
45608
|
-
await writeDaemonConfig(io.stateDir, { ...config,
|
|
45780
|
+
await writeDaemonConfig(io.stateDir, { ...config, [spec.key]: [...list, full] }, unknown);
|
|
45609
45781
|
io.out(`Added ${full} to daemon.json. A running daemon picks this up on restart.`);
|
|
45610
45782
|
return 0;
|
|
45611
45783
|
}
|
|
45612
45784
|
if (sub === "remove") {
|
|
45613
45785
|
const path = argv[1];
|
|
45614
45786
|
if (!path) {
|
|
45615
|
-
io.err(
|
|
45787
|
+
io.err(`${spec.command} remove needs a path.`);
|
|
45616
45788
|
return 2;
|
|
45617
45789
|
}
|
|
45618
45790
|
const full = resolve10(path);
|
|
45619
|
-
if (!
|
|
45620
|
-
io.err(`${full} is not a configured
|
|
45791
|
+
if (!list.some((p2) => resolve10(p2) === full)) {
|
|
45792
|
+
io.err(`${full} is not a configured ${spec.noun}.`);
|
|
45621
45793
|
return 2;
|
|
45622
45794
|
}
|
|
45623
45795
|
await writeDaemonConfig(
|
|
45624
45796
|
io.stateDir,
|
|
45625
|
-
{ ...config,
|
|
45797
|
+
{ ...config, [spec.key]: list.filter((p2) => resolve10(p2) !== full) },
|
|
45626
45798
|
unknown
|
|
45627
45799
|
);
|
|
45628
45800
|
io.out(`Removed ${full} from daemon.json. A running daemon keeps it until restart.`);
|
|
45629
45801
|
return 0;
|
|
45630
45802
|
}
|
|
45631
|
-
io.err(`Unknown subcommand "${sub ?? ""}". Use:
|
|
45803
|
+
io.err(`Unknown subcommand "${sub ?? ""}". Use: ${spec.command} list | add <path> | remove <path>`);
|
|
45804
|
+
return 2;
|
|
45805
|
+
}
|
|
45806
|
+
|
|
45807
|
+
// src/config/roots.ts
|
|
45808
|
+
function runRoots(argv, io) {
|
|
45809
|
+
return runPathList(argv, io, {
|
|
45810
|
+
key: "roots",
|
|
45811
|
+
command: "corenel roots",
|
|
45812
|
+
noun: "working directory",
|
|
45813
|
+
empty: "No working directories are configured. Add one with: corenel roots add <path>"
|
|
45814
|
+
});
|
|
45815
|
+
}
|
|
45816
|
+
|
|
45817
|
+
// src/config/addUnder.ts
|
|
45818
|
+
function runAddUnder(argv, io) {
|
|
45819
|
+
return runPathList(argv, io, {
|
|
45820
|
+
key: "addUnder",
|
|
45821
|
+
command: "corenel add-under",
|
|
45822
|
+
noun: "area a client may add a root under",
|
|
45823
|
+
empty: "No areas are configured. Clients cannot add roots. Add one with: corenel add-under add <path>"
|
|
45824
|
+
});
|
|
45825
|
+
}
|
|
45826
|
+
|
|
45827
|
+
// src/config/keyTypes.ts
|
|
45828
|
+
var KEY_TYPES = {
|
|
45829
|
+
port: "number",
|
|
45830
|
+
host: "string",
|
|
45831
|
+
roots: "string[]",
|
|
45832
|
+
addUnder: "string[]",
|
|
45833
|
+
cloneUnder: "string[]",
|
|
45834
|
+
allowLan: "boolean",
|
|
45835
|
+
allowOrigins: "string[]",
|
|
45836
|
+
tlsCert: "string",
|
|
45837
|
+
tlsKey: "string",
|
|
45838
|
+
selfSigned: "boolean",
|
|
45839
|
+
tunnel: "string",
|
|
45840
|
+
tunnelName: "string",
|
|
45841
|
+
tunnelHostname: "string",
|
|
45842
|
+
endpoint: "string",
|
|
45843
|
+
allowShell: "boolean",
|
|
45844
|
+
keepTranscripts: "boolean",
|
|
45845
|
+
isolation: "string",
|
|
45846
|
+
msbBin: "string",
|
|
45847
|
+
guestImage: "string",
|
|
45848
|
+
maxChainDepth: "number",
|
|
45849
|
+
name: "string",
|
|
45850
|
+
appUrl: "string",
|
|
45851
|
+
defaultModel: "string"
|
|
45852
|
+
};
|
|
45853
|
+
var kebab = (k2) => k2.replace(/[A-Z]/g, (c2) => `-${c2.toLowerCase()}`);
|
|
45854
|
+
function resolveKeyName(input) {
|
|
45855
|
+
if (Object.prototype.hasOwnProperty.call(KEY_TYPES, input)) return input;
|
|
45856
|
+
for (const k2 of Object.keys(KEY_TYPES)) if (kebab(k2) === input) return k2;
|
|
45857
|
+
return null;
|
|
45858
|
+
}
|
|
45859
|
+
function isListKey(key) {
|
|
45860
|
+
return KEY_TYPES[key] === "string[]";
|
|
45861
|
+
}
|
|
45862
|
+
function coerceValue(key, raw) {
|
|
45863
|
+
const type = KEY_TYPES[key];
|
|
45864
|
+
if (!type) return { ok: false, reason: `"${key}" is not a daemon.json setting.` };
|
|
45865
|
+
if (type === "boolean") {
|
|
45866
|
+
if (raw === "true") return { ok: true, value: true };
|
|
45867
|
+
if (raw === "false") return { ok: true, value: false };
|
|
45868
|
+
return { ok: false, reason: `${key} must be true or false, not ${JSON.stringify(raw)}.` };
|
|
45869
|
+
}
|
|
45870
|
+
if (type === "number") {
|
|
45871
|
+
if (!/^-?\d+$/.test(raw)) return { ok: false, reason: `${key} must be a whole number, not ${JSON.stringify(raw)}.` };
|
|
45872
|
+
return { ok: true, value: Number(raw) };
|
|
45873
|
+
}
|
|
45874
|
+
return { ok: true, value: raw };
|
|
45875
|
+
}
|
|
45876
|
+
|
|
45877
|
+
// src/config/configCmd.ts
|
|
45878
|
+
var USAGE = "Use: corenel config list | get <key> | set <key> <value> | unset <key> | add <key> <value> | remove <key> <value>";
|
|
45879
|
+
function render(value) {
|
|
45880
|
+
if (Array.isArray(value)) return value.length ? value.join(", ") : "(none)";
|
|
45881
|
+
if (value === void 0) return "(unset)";
|
|
45882
|
+
return String(value);
|
|
45883
|
+
}
|
|
45884
|
+
function row(key, value, origin) {
|
|
45885
|
+
return ` ${key.padEnd(18)} ${render(value).padEnd(24)} (${origin})`;
|
|
45886
|
+
}
|
|
45887
|
+
function keyOrReason(name) {
|
|
45888
|
+
if (SECRET_KEYS.includes(name)) {
|
|
45889
|
+
return { reason: `${name} is a credential and does not belong in daemon.json. It goes in auth.json.` };
|
|
45890
|
+
}
|
|
45891
|
+
const key = resolveKeyName(name);
|
|
45892
|
+
if (!key) return { reason: `"${name}" is not a daemon.json setting. ${USAGE}` };
|
|
45893
|
+
return { key };
|
|
45894
|
+
}
|
|
45895
|
+
async function runConfig(raw, io) {
|
|
45896
|
+
const argv = stripStateDir(raw);
|
|
45897
|
+
const sub = argv[0];
|
|
45898
|
+
const { config, unknown, corrupt } = await readDaemonConfig(io.stateDir);
|
|
45899
|
+
const isWrite = sub === "set" || sub === "unset" || sub === "add" || sub === "remove";
|
|
45900
|
+
if (corrupt && isWrite) {
|
|
45901
|
+
io.err(`daemon.json in ${io.stateDir} is not valid JSON; fix or delete it before writing.`);
|
|
45902
|
+
return 2;
|
|
45903
|
+
}
|
|
45904
|
+
const read3 = (key) => {
|
|
45905
|
+
const value = config[key];
|
|
45906
|
+
return { value, origin: value === void 0 ? "default" : "file" };
|
|
45907
|
+
};
|
|
45908
|
+
const commit = async (next, said) => {
|
|
45909
|
+
await writeDaemonConfig(io.stateDir, next, unknown);
|
|
45910
|
+
io.out(`${said} A running daemon picks this up on restart.`);
|
|
45911
|
+
return 0;
|
|
45912
|
+
};
|
|
45913
|
+
if (sub === "list") {
|
|
45914
|
+
for (const key of Object.keys(KEY_TYPES).sort()) {
|
|
45915
|
+
const { value, origin } = read3(key);
|
|
45916
|
+
io.out(row(key, value, origin));
|
|
45917
|
+
}
|
|
45918
|
+
const extras = Object.keys(unknown);
|
|
45919
|
+
if (extras.length) {
|
|
45920
|
+
io.out("");
|
|
45921
|
+
io.out(" Keys this version does not manage (kept as they are):");
|
|
45922
|
+
for (const k2 of extras.sort()) io.out(` ${k2.padEnd(18)} ${render(unknown[k2])}`);
|
|
45923
|
+
}
|
|
45924
|
+
if (corrupt) io.err(`daemon.json in ${io.stateDir} is not valid JSON; these are the defaults, not what the file says.`);
|
|
45925
|
+
return 0;
|
|
45926
|
+
}
|
|
45927
|
+
if (sub === "get") {
|
|
45928
|
+
const name = argv[1];
|
|
45929
|
+
if (!name) {
|
|
45930
|
+
io.err(`corenel config get needs a key. ${USAGE}`);
|
|
45931
|
+
return 2;
|
|
45932
|
+
}
|
|
45933
|
+
const found = keyOrReason(name);
|
|
45934
|
+
if ("reason" in found) {
|
|
45935
|
+
io.err(found.reason);
|
|
45936
|
+
return 2;
|
|
45937
|
+
}
|
|
45938
|
+
const { value, origin } = read3(found.key);
|
|
45939
|
+
io.out(` ${render(value).padEnd(24)} (${origin})`);
|
|
45940
|
+
return 0;
|
|
45941
|
+
}
|
|
45942
|
+
if (sub === "set") {
|
|
45943
|
+
const name = argv[1];
|
|
45944
|
+
const values = argv.slice(2);
|
|
45945
|
+
if (!name) {
|
|
45946
|
+
io.err(`corenel config set needs a key. ${USAGE}`);
|
|
45947
|
+
return 2;
|
|
45948
|
+
}
|
|
45949
|
+
const found = keyOrReason(name);
|
|
45950
|
+
if ("reason" in found) {
|
|
45951
|
+
io.err(found.reason);
|
|
45952
|
+
return 2;
|
|
45953
|
+
}
|
|
45954
|
+
if (values.length === 0) {
|
|
45955
|
+
io.err(`corenel config set ${name} needs a value.`);
|
|
45956
|
+
return 2;
|
|
45957
|
+
}
|
|
45958
|
+
if (isListKey(found.key)) {
|
|
45959
|
+
await commit({ ...config, [found.key]: values }, `Set ${found.key} to ${values.join(", ")}.`);
|
|
45960
|
+
return 0;
|
|
45961
|
+
}
|
|
45962
|
+
if (values.length > 1) {
|
|
45963
|
+
io.err(`${found.key} holds one value; give just one.`);
|
|
45964
|
+
return 2;
|
|
45965
|
+
}
|
|
45966
|
+
const coerced = coerceValue(found.key, values[0]);
|
|
45967
|
+
if (!coerced.ok) {
|
|
45968
|
+
io.err(coerced.reason);
|
|
45969
|
+
return 2;
|
|
45970
|
+
}
|
|
45971
|
+
return commit({ ...config, [found.key]: coerced.value }, `Set ${found.key} to ${String(coerced.value)}.`);
|
|
45972
|
+
}
|
|
45973
|
+
if (sub === "unset") {
|
|
45974
|
+
const name = argv[1];
|
|
45975
|
+
if (!name) {
|
|
45976
|
+
io.err(`corenel config unset needs a key. ${USAGE}`);
|
|
45977
|
+
return 2;
|
|
45978
|
+
}
|
|
45979
|
+
const found = keyOrReason(name);
|
|
45980
|
+
if ("reason" in found) {
|
|
45981
|
+
io.err(found.reason);
|
|
45982
|
+
return 2;
|
|
45983
|
+
}
|
|
45984
|
+
const next = { ...config };
|
|
45985
|
+
delete next[found.key];
|
|
45986
|
+
return commit(next, `Unset ${found.key}; the default applies again.`);
|
|
45987
|
+
}
|
|
45988
|
+
if (sub === "add" || sub === "remove") {
|
|
45989
|
+
const name = argv[1];
|
|
45990
|
+
const value = argv[2];
|
|
45991
|
+
if (!name || !value) {
|
|
45992
|
+
io.err(`corenel config ${sub} needs a key and a value.`);
|
|
45993
|
+
return 2;
|
|
45994
|
+
}
|
|
45995
|
+
const found = keyOrReason(name);
|
|
45996
|
+
if ("reason" in found) {
|
|
45997
|
+
io.err(found.reason);
|
|
45998
|
+
return 2;
|
|
45999
|
+
}
|
|
46000
|
+
if (!isListKey(found.key)) {
|
|
46001
|
+
io.err(`${found.key} holds one value; change it with: corenel config set ${name} <value>`);
|
|
46002
|
+
return 2;
|
|
46003
|
+
}
|
|
46004
|
+
const raw2 = config[found.key];
|
|
46005
|
+
const list = Array.isArray(raw2) ? raw2 : [];
|
|
46006
|
+
if (sub === "add") {
|
|
46007
|
+
if (list.includes(value)) {
|
|
46008
|
+
io.out(`${value} is already in ${found.key}.`);
|
|
46009
|
+
return 0;
|
|
46010
|
+
}
|
|
46011
|
+
return commit({ ...config, [found.key]: [...list, value] }, `Added ${value} to ${found.key}.`);
|
|
46012
|
+
}
|
|
46013
|
+
if (!list.includes(value)) {
|
|
46014
|
+
io.err(`${value} is not in ${found.key}.`);
|
|
46015
|
+
return 2;
|
|
46016
|
+
}
|
|
46017
|
+
return commit(
|
|
46018
|
+
{ ...config, [found.key]: list.filter((v2) => v2 !== value) },
|
|
46019
|
+
`Removed ${value} from ${found.key}.`
|
|
46020
|
+
);
|
|
46021
|
+
}
|
|
46022
|
+
io.err(`Unknown subcommand "${sub ?? ""}". ${USAGE}`);
|
|
45632
46023
|
return 2;
|
|
45633
46024
|
}
|
|
45634
46025
|
|
|
@@ -47253,7 +47644,7 @@ async function main() {
|
|
|
47253
47644
|
await runAcpAgent(rawArgv.slice(1));
|
|
47254
47645
|
return;
|
|
47255
47646
|
}
|
|
47256
|
-
if (rawArgv[0] === "setup" || rawArgv[0] === "roots" || rawArgv[0] === "doctor") {
|
|
47647
|
+
if (rawArgv[0] === "setup" || rawArgv[0] === "roots" || rawArgv[0] === "add-under" || rawArgv[0] === "config" || rawArgv[0] === "doctor") {
|
|
47257
47648
|
const sub = rawArgv[0];
|
|
47258
47649
|
const rest = rawArgv.slice(1);
|
|
47259
47650
|
try {
|
|
@@ -47266,6 +47657,8 @@ async function main() {
|
|
|
47266
47657
|
};
|
|
47267
47658
|
if (sub === "setup") process.exit(await runSetup(rest, io));
|
|
47268
47659
|
if (sub === "roots") process.exit(await runRoots(rest, io));
|
|
47660
|
+
if (sub === "add-under") process.exit(await runAddUnder(rest, io));
|
|
47661
|
+
if (sub === "config") process.exit(await runConfig(rest, io));
|
|
47269
47662
|
const { args: flagArgs2, explicit: explicit2 } = parseArgsWithProvenance(rest);
|
|
47270
47663
|
const { config, corrupt } = await readDaemonConfig(io.stateDir);
|
|
47271
47664
|
const { args: args2, from, warnings } = layerConfig(flagArgs2, explicit2, config);
|
|
@@ -47419,6 +47812,7 @@ async function main() {
|
|
|
47419
47812
|
const attendance = makeAttendanceRegistry({ log: slog });
|
|
47420
47813
|
const runs = makeRunHub({ log: slog });
|
|
47421
47814
|
let core;
|
|
47815
|
+
let crewSessions;
|
|
47422
47816
|
const pauses = args.daemon ? await loadPauseStore(args.stateDir) : void 0;
|
|
47423
47817
|
let controlPlane = null;
|
|
47424
47818
|
const runner = args.daemon ? new NodeRunnerService({
|
|
@@ -47533,6 +47927,9 @@ async function main() {
|
|
|
47533
47927
|
// through the attendance registry -- there is no room-progress member on
|
|
47534
47928
|
// AttendanceRegistry (unlike runActivity/registrations), and adding one
|
|
47535
47929
|
// is out of this task's scope.
|
|
47930
|
+
// Bound crew agents run in the session layer built below -- read through
|
|
47931
|
+
// the holder, never captured by value, because it does not exist yet.
|
|
47932
|
+
sessions: () => crewSessions,
|
|
47536
47933
|
onRoomProgress: (room) => slog(`room ${room.roomId}: ${room.turns} turn(s), ${room.participants.length} participant(s)${room.closed ? ` -- closed (${room.closed.reason})` : ""}`)
|
|
47537
47934
|
});
|
|
47538
47935
|
}
|
|
@@ -47658,6 +48055,7 @@ async function main() {
|
|
|
47658
48055
|
git: createGitSource({ areas: args.cloneUnder, run: makeGitRunner(), log: slog }),
|
|
47659
48056
|
approveCheckout: (dir) => rootsController.addFromClone(dir)
|
|
47660
48057
|
}) : void 0;
|
|
48058
|
+
crewSessions = makeCrewSessions(sessions, { stateDir: stateDirName(), guestEnv });
|
|
47661
48059
|
const handle = await startSidecar({
|
|
47662
48060
|
port: args.port,
|
|
47663
48061
|
host: args.host,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corenel/sidecar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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,10 +36,10 @@
|
|
|
36
36
|
"esbuild": "^0.21.5",
|
|
37
37
|
"tsx": "^4.19.2",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
|
-
"@corenel/protocol": "0.6.1",
|
|
40
|
-
"@corenel/harness": "0.6.1",
|
|
41
39
|
"@corenel/tools-node": "0.3.1",
|
|
42
|
-
"@corenel/
|
|
40
|
+
"@corenel/protocol": "0.6.1",
|
|
41
|
+
"@corenel/crew": "0.0.0",
|
|
42
|
+
"@corenel/harness": "0.6.1"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|