@corenel/sidecar 0.6.2 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +183 -19
  2. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -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;
@@ -28252,6 +28256,9 @@ async function readTranscript(files, crewRoot2, agent, runId) {
28252
28256
  }
28253
28257
 
28254
28258
  // src/session/manager.ts
28259
+ function isErrorStopReason(stopReason) {
28260
+ return stopReason === "error" || (stopReason ?? "").startsWith("error:");
28261
+ }
28255
28262
  var SessionManager = class {
28256
28263
  constructor(deps) {
28257
28264
  this.deps = deps;
@@ -28337,11 +28344,11 @@ var SessionManager = class {
28337
28344
  }
28338
28345
  this.deps.log?.(`session ${sessionId}: running agent "${agent}" inside the session`);
28339
28346
  const kept2 = this.transcriptOf(onEvent, opts?.keepTranscript === true);
28340
- 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);
28341
28348
  }
28342
28349
  this.deps.log?.(`session ${sessionId}: running agent "${agent}" on the host (isolation: none)`);
28343
28350
  const kept = this.transcriptOf(onEvent, opts?.keepTranscript === true);
28344
- 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);
28345
28352
  }
28346
28353
  /**
28347
28354
  * Run, then write one line about it — whatever happened.
@@ -28379,7 +28386,7 @@ var SessionManager = class {
28379
28386
  }
28380
28387
  };
28381
28388
  }
28382
- async recorded(session, agent, identity, go, transcript) {
28389
+ async recorded(session, agent, identity, go, transcript, source = "chat") {
28383
28390
  const startedAt = Date.now();
28384
28391
  const keep = (runId) => {
28385
28392
  if (!this.deps.recordTranscript || !transcript || transcript.length === 0) return;
@@ -28397,7 +28404,7 @@ var SessionManager = class {
28397
28404
  sessionId: session.id,
28398
28405
  isolated: this.isolated,
28399
28406
  kind: this.deps.backend.kind,
28400
- source: "chat",
28407
+ source,
28401
28408
  startedAt,
28402
28409
  endedAt: Date.now(),
28403
28410
  ok,
@@ -28409,7 +28416,7 @@ var SessionManager = class {
28409
28416
  void r2.done.then(
28410
28417
  (d2) => {
28411
28418
  keep(r2.runId);
28412
- write3(r2.runId, d2.stopReason !== "error", d2.stopReason);
28419
+ write3(r2.runId, !isErrorStopReason(d2.stopReason), d2.stopReason);
28413
28420
  },
28414
28421
  (e2) => {
28415
28422
  keep(r2.runId);
@@ -28731,6 +28738,7 @@ var LABEL_CREATED = "corenel.created";
28731
28738
  var LABEL_REPO_URL = "corenel.repo.url";
28732
28739
  var LABEL_REPO_REF = "corenel.repo.ref";
28733
28740
  var LABEL_REPO_COMMIT = "corenel.repo.commit";
28741
+ var LABEL_CREW_SESSION = "corenel.crew.session";
28734
28742
  function idFromSandboxName(name) {
28735
28743
  const m2 = /^corenel-(sess_\d+)$/.exec(name);
28736
28744
  return m2 ? m2[1] : null;
@@ -28896,7 +28904,8 @@ function makeMicrovmBackend(deps) {
28896
28904
  [LABEL_REPO_URL]: spec.source.url,
28897
28905
  [LABEL_REPO_COMMIT]: spec.source.commit,
28898
28906
  ...spec.source.ref ? { [LABEL_REPO_REF]: spec.source.ref } : {}
28899
- } : {}
28907
+ } : {},
28908
+ ...spec.sessionName ? { [LABEL_CREW_SESSION]: spec.sessionName } : {}
28900
28909
  }
28901
28910
  }), `create session ${id}`);
28902
28911
  const handle = {
@@ -28908,7 +28917,8 @@ function makeMicrovmBackend(deps) {
28908
28917
  // Kept so a run in this session looks for the agent where the creator's
28909
28918
  // workspace actually keeps it.
28910
28919
  ...spec.stateDir ? { stateDir: spec.stateDir } : {},
28911
- ...spec.source ? { source: spec.source } : {}
28920
+ ...spec.source ? { source: spec.source } : {},
28921
+ ...spec.sessionName ? { sessionName: spec.sessionName } : {}
28912
28922
  };
28913
28923
  sessions.set(id, handle);
28914
28924
  deps.log?.(
@@ -29011,9 +29021,28 @@ function makeMicrovmBackend(deps) {
29011
29021
  *
29012
29022
  * CAVEAT, deliberate: the log is the SANDBOX's, not this exec's. Entries
29013
29023
  * carry an `id` per invocation, but it is not known before the exec starts,
29014
- * so concurrent execs in one sandbox would interleave. That is acceptable
29015
- * because SessionManager admits one run per session at a time — and it is
29016
- * written down because it stops being acceptable the moment that changes.
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.
29017
29046
  */
29018
29047
  ...deps.stream ? {
29019
29048
  execStream(id, command, onLine, opts) {
@@ -29157,6 +29186,7 @@ function makeMicrovmBackend(deps) {
29157
29186
  const repoUrl = labels[LABEL_REPO_URL];
29158
29187
  const repoCommit = labels[LABEL_REPO_COMMIT];
29159
29188
  const repoRef = labels[LABEL_REPO_REF];
29189
+ const sessionName = labels[LABEL_CREW_SESSION];
29160
29190
  sessions.set(id, {
29161
29191
  id,
29162
29192
  status: statusFromSandbox(row2.status),
@@ -29164,6 +29194,7 @@ function makeMicrovmBackend(deps) {
29164
29194
  createdAt: Number.isFinite(stamped) && stamped > 0 ? stamped : row2.createdAt ?? now2(),
29165
29195
  ...owner ? { owner } : {},
29166
29196
  ...repoUrl && repoCommit ? { source: { url: repoUrl, commit: repoCommit, ...repoRef ? { ref: repoRef } : {} } } : {},
29197
+ ...sessionName ? { sessionName } : {},
29167
29198
  /* THE ONLY PLACE THIS IS EVER SET TRUE. `create()` never touches it,
29168
29199
  * so a card can tell "this daemon booted it" from "this daemon
29169
29200
  * found it already running" — procs.ts's own words: a running
@@ -29263,10 +29294,11 @@ function makeMicrovmBackend(deps) {
29263
29294
  }
29264
29295
 
29265
29296
  // src/session/guestRunner.ts
29266
- var DEFAULT_GUEST_COMMAND = (agent, input) => [
29297
+ var DEFAULT_GUEST_COMMAND = (agent, input, opts) => [
29267
29298
  "corenel",
29268
29299
  "run-agent",
29269
29300
  agent,
29301
+ ...opts.allowMutations ? ["--allow-mutations"] : [],
29270
29302
  "--input",
29271
29303
  input
29272
29304
  ];
@@ -29311,7 +29343,7 @@ function makeGuestRunner(deps) {
29311
29343
  const exec = deps.backend.exec.bind(deps.backend);
29312
29344
  const runId = `grun_${++seq3}`;
29313
29345
  deps.log?.(`session ${sessionId}: running "${agent}" inside the guest (${runId})`);
29314
- const command = build(agent, input);
29346
+ const command = build(agent, input, { allowMutations: deps.allowMutations === true });
29315
29347
  const timeout = deps.timeoutMs === void 0 ? void 0 : { timeoutMs: deps.timeoutMs };
29316
29348
  const seen = [];
29317
29349
  let skipped = 0;
@@ -29519,9 +29551,10 @@ async function wireSessions(opts) {
29519
29551
  ...opts.recordTranscript ? { recordTranscript: opts.recordTranscript } : {},
29520
29552
  // One runner per session, built on demand: the manager knows which session a
29521
29553
  // run belongs to, so nothing has to infer it.
29522
- guestRunnerFor: (sessionId) => makeGuestRunner({
29554
+ guestRunnerFor: (sessionId, guestOpts) => makeGuestRunner({
29523
29555
  backend,
29524
29556
  sessionId: () => sessionId,
29557
+ allowMutations: guestOpts.allowMutations,
29525
29558
  ...opts.log ? { log: opts.log } : {}
29526
29559
  }),
29527
29560
  ...opts.log ? { log: opts.log } : {}
@@ -29533,6 +29566,26 @@ async function wireSessions(opts) {
29533
29566
  return { manager, backend, port: makeSessionsPort({ manager, backend, guestEnv, ...portExtras(opts) }) };
29534
29567
  }
29535
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
+
29536
29589
  // src/runner/owners.ts
29537
29590
  import { readdir } from "node:fs/promises";
29538
29591
  import { join as join8 } from "node:path";
@@ -30220,6 +30273,41 @@ var CREW_EXCLUDED_TOOLS = /* @__PURE__ */ new Set(["ask_user", "spawn_agent", "s
30220
30273
 
30221
30274
  // ../crew/store.ts
30222
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
30223
30311
  var TRIGGER_TYPES = [
30224
30312
  "manual",
30225
30313
  "file-change",
@@ -30305,6 +30393,7 @@ async function loadCrewAgentAt(files, root, name, scope = "workspace") {
30305
30393
  if (!isValidAgentName(name)) throw new Error(`invalid crew agent name: ${name}`);
30306
30394
  const dir = `${root}/${name}`;
30307
30395
  const { meta, body: body2 } = parseFrontmatter(await readOr(files, `${dir}/AGENT.md`));
30396
+ const session = parseSessionBinding(meta);
30308
30397
  return {
30309
30398
  name,
30310
30399
  jobTitle: typeof meta.jobTitle === "string" ? meta.jobTitle : "",
@@ -30319,7 +30408,9 @@ async function loadCrewAgentAt(files, root, name, scope = "workspace") {
30319
30408
  policy: parsePolicyYaml(await readOr(files, `${dir}/POLICY.yaml`), name),
30320
30409
  settings: parseSettings(await readOr(files, `${dir}/settings.json`, "{}")),
30321
30410
  budget: parseBudget(meta),
30322
- canCall: parseCanCall(meta)
30411
+ canCall: parseCanCall(meta),
30412
+ ...session.kind === "binding" ? { session: session.binding } : {},
30413
+ ...session.kind === "malformed" ? { sessionError: session.reason } : {}
30323
30414
  };
30324
30415
  }
30325
30416
  async function loadCrewAgentAtIfExists(files, root, name, scope = "workspace") {
@@ -42618,6 +42709,26 @@ function makeTriggerSchedule(opts) {
42618
42709
  return { due, nextWakeAfter, registrations };
42619
42710
  }
42620
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
+
42621
42732
  // src/runner/dispatcher.ts
42622
42733
  function hasBudget2(def) {
42623
42734
  return def.budget?.maxUsd != null || def.budget?.maxTokens != null;
@@ -42641,11 +42752,17 @@ function makeDispatcher(opts) {
42641
42752
  });
42642
42753
  return next;
42643
42754
  }
42644
- async function recordFailedDispatch(d2, root, startedAt, runId, message) {
42755
+ async function recordFailedDispatch(d2, root, startedAt, runId, message, where) {
42645
42756
  await appendRun(root.files, root.root, {
42646
42757
  runId,
42647
42758
  agent: d2.agent,
42648
- where: "host",
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,
42649
42766
  source: d2.trigger,
42650
42767
  startedAt,
42651
42768
  endedAt: now2(),
@@ -42653,6 +42770,35 @@ function makeDispatcher(opts) {
42653
42770
  stopReason: message
42654
42771
  }, log);
42655
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
+ }
42656
42802
  async function fire(d2) {
42657
42803
  const dispatchedAt = now2();
42658
42804
  const key = `${d2.owner}:${d2.agent}:${d2.trigger}`;
@@ -42661,6 +42807,7 @@ function makeDispatcher(opts) {
42661
42807
  return;
42662
42808
  }
42663
42809
  inFlight.add(key);
42810
+ let where = "host";
42664
42811
  try {
42665
42812
  const roots = await opts.ownerRoots();
42666
42813
  const match = roots.find((r2) => r2.owner === d2.owner);
@@ -42673,12 +42820,17 @@ function makeDispatcher(opts) {
42673
42820
  log(`dispatcher: agent "${d2.agent}" not found (owner "${d2.owner}") -- skipped`);
42674
42821
  return;
42675
42822
  }
42823
+ where = def.session || def.sessionError ? "session" : "host";
42676
42824
  if (d2.trigger === "continuous" && !hasBudget2(def)) {
42677
42825
  log(`refusing continuous run for "${d2.agent}" (owner "${d2.owner}"): a budget (maxUsd or maxTokens) is required`);
42678
42826
  return;
42679
42827
  }
42680
42828
  await withAgentStateLock(`${d2.owner}:${d2.agent}`, () => persistDispatch(d2, match));
42681
- const started = await runner.run(d2.agent, d2.input, () => {
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, () => {
42682
42834
  }, { owner: d2.owner }, d2.generation ?? 0, d2.trigger);
42683
42835
  const startedAt = now2();
42684
42836
  const announce = (extra) => {
@@ -42710,7 +42862,7 @@ function makeDispatcher(opts) {
42710
42862
  announce({ endedAt: now2(), ok: false, stopReason: msg });
42711
42863
  log(`dispatcher: run of "${d2.agent}" (${d2.trigger}) failed -- ${msg}`);
42712
42864
  void settleSelfDirected(d2, match);
42713
- void recordFailedDispatch(d2, match, startedAt, started.runId, msg);
42865
+ void recordFailedDispatch(d2, match, startedAt, started.runId, msg, where);
42714
42866
  }
42715
42867
  );
42716
42868
  } catch (err) {
@@ -42719,7 +42871,7 @@ function makeDispatcher(opts) {
42719
42871
  try {
42720
42872
  const roots = await opts.ownerRoots();
42721
42873
  const root = roots.find((r2) => r2.owner === d2.owner);
42722
- if (root) await recordFailedDispatch(d2, root, dispatchedAt, `dispatch-${dispatchedAt}`, msg);
42874
+ if (root) await recordFailedDispatch(d2, root, dispatchedAt, `dispatch-${dispatchedAt}`, msg, where);
42723
42875
  } catch {
42724
42876
  }
42725
42877
  } finally {
@@ -43409,6 +43561,9 @@ function startDaemonCore(opts) {
43409
43561
  // it -- the same contract `schedule_self` has. `clock` is declared below
43410
43562
  // and only read when this fires, long after it is initialized.
43411
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 } : {},
43412
43567
  log
43413
43568
  });
43414
43569
  const roomProgressSubs = /* @__PURE__ */ new Set();
@@ -45520,6 +45675,10 @@ function layerConfig(args, explicit, file) {
45520
45675
  from[key] = "file";
45521
45676
  if (key === "endpoint") out.endpointExplicit = true;
45522
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
+ }
45523
45682
  return { args: out, from, warnings };
45524
45683
  }
45525
45684
 
@@ -47653,6 +47812,7 @@ async function main() {
47653
47812
  const attendance = makeAttendanceRegistry({ log: slog });
47654
47813
  const runs = makeRunHub({ log: slog });
47655
47814
  let core;
47815
+ let crewSessions;
47656
47816
  const pauses = args.daemon ? await loadPauseStore(args.stateDir) : void 0;
47657
47817
  let controlPlane = null;
47658
47818
  const runner = args.daemon ? new NodeRunnerService({
@@ -47767,6 +47927,9 @@ async function main() {
47767
47927
  // through the attendance registry -- there is no room-progress member on
47768
47928
  // AttendanceRegistry (unlike runActivity/registrations), and adding one
47769
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,
47770
47933
  onRoomProgress: (room) => slog(`room ${room.roomId}: ${room.turns} turn(s), ${room.participants.length} participant(s)${room.closed ? ` -- closed (${room.closed.reason})` : ""}`)
47771
47934
  });
47772
47935
  }
@@ -47892,6 +48055,7 @@ async function main() {
47892
48055
  git: createGitSource({ areas: args.cloneUnder, run: makeGitRunner(), log: slog }),
47893
48056
  approveCheckout: (dir) => rootsController.addFromClone(dir)
47894
48057
  }) : void 0;
48058
+ crewSessions = makeCrewSessions(sessions, { stateDir: stateDirName(), guestEnv });
47895
48059
  const handle = await startSidecar({
47896
48060
  port: args.port,
47897
48061
  host: args.host,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corenel/sidecar",
3
- "version": "0.6.2",
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/crew": "0.0.0"
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",