squadrant 0.20.0 → 0.20.2

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/index.js CHANGED
@@ -11887,6 +11887,7 @@ var init_api_key_approval = __esm({
11887
11887
  var OpencodeHttpChannel;
11888
11888
  var init_http_channel = __esm({
11889
11889
  "packages/agents/dist/opencode/http-channel.js"() {
11890
+ init_dist2();
11890
11891
  OpencodeHttpChannel = class {
11891
11892
  name = "opencode-http";
11892
11893
  agent = "opencode";
@@ -11895,12 +11896,16 @@ var init_http_channel = __esm({
11895
11896
  fetchImpl;
11896
11897
  portFor;
11897
11898
  sessionFor;
11899
+ directoryFor;
11900
+ createdAfterFor;
11898
11901
  timeoutMs;
11899
11902
  log;
11900
11903
  constructor(deps) {
11901
11904
  this.fetchImpl = deps.fetchImpl ?? fetch;
11902
11905
  this.portFor = deps.portFor;
11903
11906
  this.sessionFor = deps.sessionFor;
11907
+ this.directoryFor = deps.directoryFor;
11908
+ this.createdAfterFor = deps.createdAfterFor;
11904
11909
  this.timeoutMs = deps.timeoutMs ?? 5e3;
11905
11910
  this.log = deps.log;
11906
11911
  }
@@ -11953,11 +11958,24 @@ var init_http_channel = __esm({
11953
11958
  * are tried. This is a capability probe, NOT a version comparison — the honest
11954
11959
  * check is "does this route answer", and neither path is a promised-stable
11955
11960
  * contract. Re-run the smoke suite when opencode is upgraded.
11961
+ *
11962
+ * #787: when the crew's directory is known, resolve by EXACT directory
11963
+ * (realpath-normalized) and never fall back to the project-wide newest.
11964
+ * GET /session is project-scoped (shared by every worktree of the repo), so
11965
+ * "newest" can be a sibling crew's — prompt_async at that id would run the
11966
+ * message in the wrong session.
11967
+ *
11968
+ * #789: opencode creates a session lazily (nothing exists until the first turn),
11969
+ * so a reused worktree dir can hold a prior crew's session. When the crew's
11970
+ * createdAt is known, also require the candidate to have been created at/after
11971
+ * it — otherwise a send resolves a stale session the live TUI is not showing.
11956
11972
  */
11957
11973
  async resolveSession(taskId, port) {
11958
11974
  const cached = this.sessionByTask.get(taskId);
11959
11975
  if (cached)
11960
11976
  return cached;
11977
+ const directory = this.directoryFor?.(taskId);
11978
+ const createdAfterMs = this.createdAfterFor?.(taskId);
11961
11979
  for (const path39 of ["/session?", "/api/session?"]) {
11962
11980
  let res;
11963
11981
  try {
@@ -11975,11 +11993,11 @@ var init_http_channel = __esm({
11975
11993
  }
11976
11994
  if (!Array.isArray(sessions) || sessions.length === 0)
11977
11995
  continue;
11978
- const newest = sessions.reduce((a, b) => (b.time?.updated ?? 0) > (a.time?.updated ?? 0) ? b : a);
11979
- if (!newest?.id)
11996
+ const resolved = directory ? newestSessionInDirectory(sessions, directory, createdAfterMs) : sessions.reduce((a, b) => (b.time?.updated ?? 0) > (a.time?.updated ?? 0) ? b : a).id;
11997
+ if (!resolved)
11980
11998
  continue;
11981
- this.sessionByTask.set(taskId, newest.id);
11982
- return newest.id;
11999
+ this.sessionByTask.set(taskId, resolved);
12000
+ return resolved;
11983
12001
  }
11984
12002
  return void 0;
11985
12003
  }
@@ -14659,7 +14677,57 @@ function writePerCrewOpencodeConfig(o) {
14659
14677
  websearch: "allow",
14660
14678
  task: "allow",
14661
14679
  lsp: "allow",
14662
- external_directory: { "**": "allow" }
14680
+ // unlike most permission keys, external_directory defaults to "ask" in
14681
+ // opencode. The catch-all is deliberately "ask" (not "allow") so unknown
14682
+ // external paths stay human-gated — least privilege. Trusted AI/config/
14683
+ // memory/tmp dirs are allow-listed explicitly below; each dir is listed
14684
+ // both as the bare path AND as `<dir>/**`, so the directory itself is
14685
+ // reachable, not only its contents.
14686
+ //
14687
+ // Ordering: opencode evaluates patterns last-match-wins, so the catch-all
14688
+ // goes FIRST and the specific allows follow. If a future version resolves
14689
+ // first-match instead, the failure mode is over-prompting (visible, safe)
14690
+ // rather than a silent allow-all (invisible, unsafe).
14691
+ external_directory: {
14692
+ "**": "ask",
14693
+ "/tmp": "allow",
14694
+ "/tmp/**": "allow",
14695
+ "/private/tmp": "allow",
14696
+ "/private/tmp/**": "allow",
14697
+ "/var/folders": "allow",
14698
+ "/var/folders/**": "allow",
14699
+ "~/.config/squadrant": "allow",
14700
+ "~/.config/squadrant/**": "allow",
14701
+ "~/.config/opencode": "allow",
14702
+ "~/.config/opencode/**": "allow",
14703
+ "~/.config/cmux": "allow",
14704
+ "~/.config/cmux/**": "allow",
14705
+ "~/.opencode": "allow",
14706
+ "~/.opencode/**": "allow",
14707
+ "~/.local/share/opencode": "allow",
14708
+ "~/.local/share/opencode/**": "allow",
14709
+ "~/.local/share/claude": "allow",
14710
+ "~/.local/share/claude/**": "allow",
14711
+ "~/.cache/opencode": "allow",
14712
+ "~/.cache/opencode/**": "allow",
14713
+ "~/.claude": "allow",
14714
+ "~/.claude/**": "allow",
14715
+ "~/.claude.json": "allow",
14716
+ "~/squadrant-hub": "allow",
14717
+ "~/squadrant-hub/**": "allow",
14718
+ "~/.claude-mem": "allow",
14719
+ "~/.claude-mem/**": "allow",
14720
+ "~/.agents": "allow",
14721
+ "~/.agents/**": "allow",
14722
+ "~/.cmuxterm": "allow",
14723
+ "~/.cmuxterm/**": "allow",
14724
+ "~/.codex": "allow",
14725
+ "~/.codex/**": "allow",
14726
+ "~/.cursor": "allow",
14727
+ "~/.cursor/**": "allow",
14728
+ "~/.gemini": "allow",
14729
+ "~/.gemini/**": "allow"
14730
+ }
14663
14731
  }
14664
14732
  };
14665
14733
  writeFileSync11(file, JSON.stringify(config, null, 2));
@@ -15974,6 +16042,14 @@ async function runCrewSend2(project, name, message, opts) {
15974
16042
  const controlChannels = [
15975
16043
  new OpencodeHttpChannel({
15976
16044
  portFor: (taskId) => tasks.find((t) => t.id === taskId)?.serverPort,
16045
+ // #787: scope resolution to the crew's own worktree (TaskRecord.cwd).
16046
+ // opencode's GET /session is project-scoped, so without this the channel
16047
+ // can target a newer sibling crew's / the captain's session.
16048
+ directoryFor: (taskId) => tasks.find((t) => t.id === taskId)?.cwd,
16049
+ // #789: opencode creates sessions lazily, so a reused worktree dir can hold
16050
+ // a prior crew's session. Gate on the crew's createdAt so a stale session
16051
+ // the live TUI is not showing is never selected.
16052
+ createdAfterFor: (taskId) => tasks.find((t) => t.id === taskId)?.createdAt,
15977
16053
  log: (m) => console.error(chalk10.dim(m))
15978
16054
  }),
15979
16055
  new ClaudePeerChannel({
@@ -18016,6 +18092,10 @@ var launchCommand = new Command14("launch").description(
18016
18092
  hadFailure = true;
18017
18093
  return;
18018
18094
  }
18095
+ let captainOpencodeConfigPath;
18096
+ if (isOpencodeCaptain && projectName) {
18097
+ captainOpencodeConfigPath = writePerCrewOpencodeConfig({ stateRoot, project: projectName, taskId: "captain" });
18098
+ }
18019
18099
  const priorRecord = projectName ? readCaptainAddress(stateRoot, projectName) : null;
18020
18100
  const captainPort = isOpencodeCaptain ? await getFreePort() : void 0;
18021
18101
  let opencodeResumeSessionId;
@@ -18036,7 +18116,7 @@ var launchCommand = new Command14("launch").description(
18036
18116
  }
18037
18117
  const captainBoot = isOpencodeCaptain ? { port: captainPort, sessionId: pickResumeSessionId(priorRecord, "opencode", forceFresh) } : void 0;
18038
18118
  opencodeResumeSessionId = captainBoot?.sessionId;
18039
- return buildAgentCmd(
18119
+ const baseCmd = buildAgentCmd(
18040
18120
  agentName,
18041
18121
  registry,
18042
18122
  role,
@@ -18049,6 +18129,7 @@ var launchCommand = new Command14("launch").description(
18049
18129
  thinking,
18050
18130
  captainBoot
18051
18131
  );
18132
+ return captainOpencodeConfigPath ? `OPENCODE_CONFIG=${captainOpencodeConfigPath} ${baseCmd}` : baseCmd;
18052
18133
  },
18053
18134
  initialPrompt,
18054
18135
  runtime,