squadrant 0.17.1 → 0.18.1

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.
@@ -60,22 +60,90 @@ var init_snapshot = __esm({
60
60
  });
61
61
 
62
62
  // packages/cli/src/squadrantd.ts
63
- import { join as join19, dirname as dirname5 } from "path";
63
+ import { join as join19, dirname as dirname4 } from "path";
64
64
  import { homedir as homedir14 } from "os";
65
65
  import { fileURLToPath as fileURLToPath3 } from "url";
66
- import { readFileSync as readFileSync14, statSync as statSync4 } from "fs";
66
+ import { readFileSync as readFileSync13, statSync as statSync4, existsSync as existsSync12 } from "fs";
67
67
 
68
68
  // packages/shared/dist/config.js
69
- import fs from "fs";
70
- import path from "path";
69
+ import path2 from "path";
71
70
  import os from "os";
72
71
  import chalk from "chalk";
73
- var CONFIG_DIR = path.join(os.homedir(), ".config", "squadrant");
74
- var DEFAULT_CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
72
+
73
+ // packages/shared/dist/lib/config-io.js
74
+ import fs from "fs";
75
+ import path from "path";
76
+ var didLogMigration = false;
77
+ var migrationRun = false;
78
+ function tightenModeSync(p, expectedMode) {
79
+ try {
80
+ if (!fs.existsSync(p))
81
+ return;
82
+ const stat = fs.statSync(p);
83
+ const currentMode = stat.mode & 4095;
84
+ if ((currentMode & ~expectedMode) !== 0) {
85
+ fs.chmodSync(p, expectedMode);
86
+ if (!didLogMigration) {
87
+ console.warn(`\x1B[33m\u26A0\x1B[0m squadrant: tightened config file permissions (security fix #668)`);
88
+ didLogMigration = true;
89
+ }
90
+ }
91
+ } catch {
92
+ }
93
+ }
94
+ function writeConfigFileSync(filePath, content) {
95
+ ensureDirSync(path.dirname(filePath));
96
+ fs.writeFileSync(filePath, content, { mode: 384 });
97
+ tightenModeSync(filePath, 384);
98
+ }
99
+ function ensureDirSync(dirPath) {
100
+ if (dirPath !== CONFIG_DIR && !dirPath.startsWith(CONFIG_DIR + path.sep)) {
101
+ fs.mkdirSync(dirPath, { recursive: true });
102
+ return;
103
+ }
104
+ fs.mkdirSync(dirPath, { recursive: true, mode: 448 });
105
+ let current = dirPath;
106
+ while (current === CONFIG_DIR || current.startsWith(CONFIG_DIR + path.sep)) {
107
+ tightenModeSync(current, 448);
108
+ if (current === CONFIG_DIR)
109
+ break;
110
+ const parent = path.dirname(current);
111
+ if (parent === current)
112
+ break;
113
+ current = parent;
114
+ }
115
+ }
116
+ function readConfigFileSync(filePath) {
117
+ return fs.readFileSync(filePath, "utf-8");
118
+ }
119
+ function migrateConfigPermsSync() {
120
+ if (migrationRun)
121
+ return;
122
+ migrationRun = true;
123
+ tightenModeSync(CONFIG_DIR, 448);
124
+ tightenModeSync(path.join(CONFIG_DIR, "config.json"), 384);
125
+ const projectsDir = path.join(CONFIG_DIR, "projects");
126
+ tightenModeSync(projectsDir, 448);
127
+ try {
128
+ if (fs.existsSync(projectsDir)) {
129
+ const files = fs.readdirSync(projectsDir);
130
+ for (const file of files) {
131
+ if (file.endsWith(".json")) {
132
+ tightenModeSync(path.join(projectsDir, file), 384);
133
+ }
134
+ }
135
+ }
136
+ } catch {
137
+ }
138
+ }
139
+
140
+ // packages/shared/dist/config.js
141
+ var DEFAULT_CONFIG_PATH = process.env.SQUADRANT_CONFIG || path2.join(os.homedir(), ".config", "squadrant", "config.json");
142
+ var CONFIG_DIR = path2.dirname(DEFAULT_CONFIG_PATH);
75
143
  function getDefaultConfig() {
76
144
  return {
77
145
  commandName: "\u{1F3DB}\uFE0F command",
78
- hubVault: path.join(os.homedir(), "squadrant-hub"),
146
+ hubVault: path2.join(os.homedir(), "squadrant-hub"),
79
147
  projects: {},
80
148
  agents: {
81
149
  claude: { cli: "claude", driver: "claude" }
@@ -119,13 +187,14 @@ function getDefaultConfig() {
119
187
  },
120
188
  metrics: {
121
189
  enabled: true,
122
- path: path.join(CONFIG_DIR, "metrics.json")
190
+ path: path2.join(CONFIG_DIR, "metrics.json")
123
191
  }
124
192
  };
125
193
  }
126
194
  function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
195
+ migrateConfigPermsSync();
127
196
  try {
128
- const raw = fs.readFileSync(configPath, "utf-8");
197
+ const raw = readConfigFileSync(configPath);
129
198
  const config = JSON.parse(raw);
130
199
  if (config.defaults.models && !config.defaults.roles) {
131
200
  const m = config.defaults.models;
@@ -150,27 +219,24 @@ function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
150
219
  }
151
220
  }
152
221
  function saveConfig(config, configPath = DEFAULT_CONFIG_PATH) {
153
- const dir = path.dirname(configPath);
154
- fs.mkdirSync(dir, { recursive: true });
155
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
222
+ writeConfigFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
156
223
  }
157
224
  function resolveHome(p) {
158
225
  return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
159
226
  }
160
227
 
161
228
  // packages/shared/dist/project-config.js
162
- import fs2 from "fs";
163
229
  import os2 from "os";
164
- import path2 from "path";
230
+ import path3 from "path";
165
231
  function defaultRoot() {
166
- return path2.join(os2.homedir(), ".config", "squadrant");
232
+ return path3.join(os2.homedir(), ".config", "squadrant");
167
233
  }
168
234
  function projectConfigPath(name, root = defaultRoot()) {
169
- return path2.join(root, "projects", `${name}.json`);
235
+ return path3.join(root, "projects", `${name}.json`);
170
236
  }
171
237
  function loadProjectOverride(name, root = defaultRoot()) {
172
238
  try {
173
- return JSON.parse(fs2.readFileSync(projectConfigPath(name, root), "utf-8"));
239
+ return JSON.parse(readConfigFileSync(projectConfigPath(name, root)));
174
240
  } catch {
175
241
  return {};
176
242
  }
@@ -187,8 +253,7 @@ function deepMerge(base, patch) {
187
253
  function saveProjectOverride(name, patch, root = defaultRoot()) {
188
254
  const merged = deepMerge(loadProjectOverride(name, root), patch);
189
255
  const file = projectConfigPath(name, root);
190
- fs2.mkdirSync(path2.dirname(file), { recursive: true });
191
- fs2.writeFileSync(file, JSON.stringify(merged, null, 2) + "\n");
256
+ writeConfigFileSync(file, JSON.stringify(merged, null, 2) + "\n");
192
257
  }
193
258
  var DEFAULT_NOTIFY = { active: false, cap: true, crew: "alert_only" };
194
259
  function resolveNotify(globalNotify, override) {
@@ -208,9 +273,9 @@ var TERMINAL_STATES = /* @__PURE__ */ new Set([
208
273
  ]);
209
274
 
210
275
  // packages/shared/dist/lib/cmux-autoconfig.js
211
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, rmSync as rmSync2 } from "fs";
276
+ import { existsSync as existsSync4, rmSync as rmSync2 } from "fs";
212
277
  import { homedir as homedir3 } from "os";
213
- import { dirname as dirname2, join as join4 } from "path";
278
+ import { join as join4 } from "path";
214
279
 
215
280
  // packages/shared/dist/lib/cmux-config.js
216
281
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
@@ -233,22 +298,22 @@ var MINIMAL_TEMPLATE = [
233
298
  ``
234
299
  ].join("\n");
235
300
  function ensureSocketAutomation(opts = {}) {
236
- const path19 = opts.path ?? defaultCmuxConfigPath();
237
- if (!existsSync(path19)) {
238
- mkdirSync(dirname(path19), { recursive: true });
239
- writeFileSync(path19, MINIMAL_TEMPLATE);
240
- return { path: path19, changed: true, alreadySet: false };
301
+ const path20 = opts.path ?? defaultCmuxConfigPath();
302
+ if (!existsSync(path20)) {
303
+ mkdirSync(dirname(path20), { recursive: true });
304
+ writeFileSync(path20, MINIMAL_TEMPLATE);
305
+ return { path: path20, changed: true, alreadySet: false };
241
306
  }
242
- const text = readFileSync(path19, "utf-8");
307
+ const text = readFileSync(path20, "utf-8");
243
308
  const current = parse(text)?.automation?.socketControlMode;
244
309
  if (current === AUTOMATION_MODE) {
245
- return { path: path19, changed: false, alreadySet: true };
310
+ return { path: path20, changed: false, alreadySet: true };
246
311
  }
247
312
  const edits = modify(text, [...SOCKET_CONTROL_MODE_PATH], AUTOMATION_MODE, {
248
313
  formattingOptions: { insertSpaces: true, tabSize: 2 }
249
314
  });
250
- writeFileSync(path19, applyEdits(text, edits));
251
- return { path: path19, changed: true, alreadySet: false };
315
+ writeFileSync(path20, applyEdits(text, edits));
316
+ return { path: path20, changed: true, alreadySet: false };
252
317
  }
253
318
 
254
319
  // packages/shared/dist/lib/cmux-probe.js
@@ -370,9 +435,9 @@ function sleep(ms) {
370
435
  function defaultStatePath() {
371
436
  return join4(homedir3(), ".config", "squadrant", "state", "cmux-autoconfig.json");
372
437
  }
373
- function readState(path19) {
438
+ function readState(path20) {
374
439
  try {
375
- return JSON.parse(readFileSync4(path19, "utf-8"));
440
+ return JSON.parse(readConfigFileSync(path20));
376
441
  } catch {
377
442
  return {};
378
443
  }
@@ -388,8 +453,7 @@ async function ensureCmuxAutoConfig(opts = {}) {
388
453
  if (needsRestart) {
389
454
  const already = readState(statePath3).promptedRestart === true;
390
455
  if (!already) {
391
- mkdirSync2(dirname2(statePath3), { recursive: true });
392
- writeFileSync3(statePath3, JSON.stringify({ promptedRestart: true }));
456
+ writeConfigFileSync(statePath3, JSON.stringify({ promptedRestart: true }));
393
457
  promptedThisRun = true;
394
458
  }
395
459
  } else if (verdict === "reachable") {
@@ -420,25 +484,25 @@ var compatManifest = {
420
484
  };
421
485
 
422
486
  // packages/shared/dist/lib/update-check.js
423
- import fs3 from "fs";
424
- import path3 from "path";
487
+ import fs2 from "fs";
488
+ import path4 from "path";
425
489
  import os3 from "os";
426
490
  import https from "https";
427
- var UPDATE_CHECK_STATE_PATH = path3.join(os3.homedir(), ".config", "squadrant", "update-check.json");
491
+ var UPDATE_CHECK_STATE_PATH = path4.join(os3.homedir(), ".config", "squadrant", "update-check.json");
428
492
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
429
493
  var FAILURE_RETRY_MS = 60 * 60 * 1e3;
430
494
 
431
495
  // packages/shared/dist/lib/git-worktree.js
432
496
  import { execFileSync as execFileSync2 } from "child_process";
433
- import fs4 from "fs";
434
- import path4 from "path";
497
+ import fs3 from "fs";
498
+ import path5 from "path";
435
499
 
436
500
  // packages/shared/dist/lib/resolve-text-input.js
437
- import fs5 from "fs";
501
+ import fs4 from "fs";
438
502
 
439
503
  // packages/shared/dist/lib/runtime-sync.js
440
- import fs6 from "fs";
441
- import path5 from "path";
504
+ import fs5 from "fs";
505
+ import path6 from "path";
442
506
 
443
507
  // packages/shared/dist/lib/tool-compat.js
444
508
  function parseSemVer(v) {
@@ -472,13 +536,13 @@ function checkToolCompat(name, rawVersion, entry) {
472
536
  }
473
537
 
474
538
  // packages/shared/dist/lib/canonical-source.js
475
- import fs7 from "fs";
476
- import path6 from "path";
539
+ import fs6 from "fs";
540
+ import path7 from "path";
477
541
 
478
542
  // packages/shared/dist/lib/daily-logs.js
479
543
  import { execSync } from "child_process";
480
- import fs8 from "fs";
481
- import path7 from "path";
544
+ import fs7 from "fs";
545
+ import path8 from "path";
482
546
  import matter from "gray-matter";
483
547
 
484
548
  // packages/core/dist/state-machine.js
@@ -507,7 +571,21 @@ function nextPendingMonitor(current, ev, now) {
507
571
  }
508
572
  function reduce(rec, ev, now) {
509
573
  if (ev.type === "task.reopened") {
510
- return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type };
574
+ return { ...rec, state: "working", question: void 0, error: void 0, lastHeartbeat: now, lastEvent: ev.type, workingStretchStartedAt: now };
575
+ }
576
+ if (ev.type === "crew.takeover.started") {
577
+ if (rec.operatorHold)
578
+ return { ...rec, lastHeartbeat: now, lastEvent: ev.type };
579
+ return {
580
+ ...rec,
581
+ operatorHold: { since: now, ...ev.note !== void 0 ? { note: ev.note } : {} },
582
+ lastHeartbeat: now,
583
+ lastEvent: ev.type
584
+ };
585
+ }
586
+ if (ev.type === "crew.takeover.ended") {
587
+ const { operatorHold: _dropped, ...rest } = rec;
588
+ return { ...rest, lastHeartbeat: now, lastEvent: ev.type };
511
589
  }
512
590
  if (TERMINAL_STATES.has(rec.state))
513
591
  return rec;
@@ -523,8 +601,9 @@ function reduce(rec, ev, now) {
523
601
  // resuming after a blocked→reply clears the question
524
602
  pendingTool: void 0,
525
603
  // #354: a new turn closes any prior tool window
526
- pendingMonitor: void 0
604
+ pendingMonitor: void 0,
527
605
  // #594a: same reset — a new turn moots any prior watch
606
+ workingStretchStartedAt: now
528
607
  };
529
608
  case "task.progress": {
530
609
  const pendingTool = nextPendingTool(rec.pendingTool, ev, now);
@@ -693,6 +772,8 @@ function firePush(deps, project, prev, next, event, lastCaptainTurnAt) {
693
772
  return;
694
773
  if (!ATTENTION_STATES.has(next.state))
695
774
  return;
775
+ if (next.operatorHold)
776
+ return;
696
777
  if (next.state === "awaiting-input" && lastCaptainTurnAt != null && deps.now() - lastCaptainTurnAt <= IDLE_DEBOUNCE_MS) {
697
778
  return;
698
779
  }
@@ -744,8 +825,11 @@ var KNOWN_EVENT_TYPES = /* @__PURE__ */ new Set([
744
825
  "task.reconcile-failed",
745
826
  "task.cancelled",
746
827
  "task.session.ended",
747
- "task.first-turn.confirmed"
828
+ "task.first-turn.confirmed",
748
829
  // #466: delivery confirmation
830
+ "crew.takeover.started",
831
+ "crew.takeover.ended"
832
+ // #649: operator takeover
749
833
  ]);
750
834
  function createDaemon(deps) {
751
835
  const { store, now } = deps;
@@ -927,9 +1011,32 @@ function createDaemon(deps) {
927
1011
  store.delete(r.project, r.id);
928
1012
  continue;
929
1013
  }
1014
+ if (r.operatorHold) {
1015
+ const threshold = (deps.takeoverNudgeHours ?? 6) * 36e5;
1016
+ const holdAge = t - r.operatorHold.since;
1017
+ if (holdAge > threshold) {
1018
+ const timeSinceLastNudge = t - (r.operatorHold.lastNudgeAt ?? 0);
1019
+ if (timeSinceLastNudge > threshold) {
1020
+ const hrs = Math.round(holdAge / 36e5);
1021
+ const tag = crewTag(r);
1022
+ const message = `CREW HELD-LONG ${tag} \u2014 held ${hrs}h. Ask the operator whether it is still in use. Do not release it yourself.`;
1023
+ store.put({ ...r, operatorHold: { ...r.operatorHold, lastNudgeAt: t } });
1024
+ if (deps.notify) {
1025
+ try {
1026
+ const p = deps.notify({ project: r.project, message, record: r, event: { type: "task.progress", id: r.id } });
1027
+ if (p && typeof p.catch === "function")
1028
+ p.catch(() => {
1029
+ });
1030
+ } catch {
1031
+ }
1032
+ }
1033
+ }
1034
+ }
1035
+ }
930
1036
  if (!TERMINAL_STATES.has(r.state) && !isStickyAttention(r.state)) {
931
1037
  const ceiling = deps.taskTimeoutMs ?? DEFAULT_TASK_TIMEOUT_MS;
932
- if (t - r.createdAt > ceiling) {
1038
+ const refTime = r.workingStretchStartedAt ?? r.createdAt;
1039
+ if (t - refTime > ceiling) {
933
1040
  const prevState = r.state;
934
1041
  const tag = crewTag(r);
935
1042
  const hrs = Math.round(ceiling / 36e5);
@@ -1048,7 +1155,7 @@ function createDaemon(deps) {
1048
1155
  }
1049
1156
 
1050
1157
  // packages/core/dist/mailbox.js
1051
- import { promises as fs9 } from "fs";
1158
+ import { promises as fs8 } from "fs";
1052
1159
  import { join as join5 } from "path";
1053
1160
  import { randomUUID } from "crypto";
1054
1161
  function inboxDir(stateRoot) {
@@ -1065,7 +1172,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
1065
1172
  const dir = inboxDir(stateRoot);
1066
1173
  let entries;
1067
1174
  try {
1068
- entries = await fs9.readdir(dir);
1175
+ entries = await fs8.readdir(dir);
1069
1176
  } catch {
1070
1177
  return [];
1071
1178
  }
@@ -1074,7 +1181,7 @@ async function listRotatedOldestFirst(stateRoot, project) {
1074
1181
  }
1075
1182
  async function readMaxSeqFromFile(file) {
1076
1183
  try {
1077
- const buf = await fs9.readFile(file, "utf-8");
1184
+ const buf = await fs8.readFile(file, "utf-8");
1078
1185
  if (!buf.trim())
1079
1186
  return 0;
1080
1187
  const lines = buf.trim().split("\n");
@@ -1116,12 +1223,12 @@ function withProjectLock(project, fn) {
1116
1223
  function appendEntry(stateRoot, project, build) {
1117
1224
  return withProjectLock(project, async () => {
1118
1225
  const dir = inboxDir(stateRoot);
1119
- await fs9.mkdir(dir, { recursive: true });
1226
+ await fs8.mkdir(dir, { recursive: true });
1120
1227
  const file = logPath(stateRoot, project);
1121
1228
  const lastSeq = await readMaxSeq(stateRoot, project);
1122
1229
  const seq = lastSeq + 1;
1123
1230
  const entry = build(seq);
1124
- await fs9.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
1231
+ await fs8.appendFile(file, JSON.stringify(entry) + "\n", { encoding: "utf-8" });
1125
1232
  return seq;
1126
1233
  });
1127
1234
  }
@@ -1152,7 +1259,7 @@ function cursorPath(stateRoot, project, subscriber) {
1152
1259
  async function readCursor(opts) {
1153
1260
  let buf;
1154
1261
  try {
1155
- buf = await fs9.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), "utf-8");
1262
+ buf = await fs8.readFile(cursorPath(opts.stateRoot, opts.project, opts.subscriber), "utf-8");
1156
1263
  } catch (e) {
1157
1264
  if (e.code === "ENOENT")
1158
1265
  return null;
@@ -1167,7 +1274,7 @@ async function readCursor(opts) {
1167
1274
  }
1168
1275
  }
1169
1276
  async function writeCursor(opts) {
1170
- await fs9.mkdir(inboxDir(opts.stateRoot), { recursive: true });
1277
+ await fs8.mkdir(inboxDir(opts.stateRoot), { recursive: true });
1171
1278
  const dest = cursorPath(opts.stateRoot, opts.project, opts.subscriber);
1172
1279
  const tmp = `${dest}.${process.pid}.${randomUUID()}.tmp`;
1173
1280
  const data = {
@@ -1175,7 +1282,7 @@ async function writeCursor(opts) {
1175
1282
  subscriber: opts.subscriber,
1176
1283
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1177
1284
  };
1178
- const handle = await fs9.open(tmp, "w");
1285
+ const handle = await fs8.open(tmp, "w");
1179
1286
  try {
1180
1287
  await handle.writeFile(JSON.stringify(data), { encoding: "utf-8" });
1181
1288
  await handle.sync();
@@ -1183,9 +1290,9 @@ async function writeCursor(opts) {
1183
1290
  await handle.close();
1184
1291
  }
1185
1292
  try {
1186
- await fs9.rename(tmp, dest);
1293
+ await fs8.rename(tmp, dest);
1187
1294
  } catch (e) {
1188
- await fs9.unlink(tmp).catch(() => {
1295
+ await fs8.unlink(tmp).catch(() => {
1189
1296
  });
1190
1297
  throw e;
1191
1298
  }
@@ -1196,7 +1303,7 @@ async function* readFromCursor(opts) {
1196
1303
  for (const file of files) {
1197
1304
  let buf;
1198
1305
  try {
1199
- buf = await fs9.readFile(file, "utf-8");
1306
+ buf = await fs8.readFile(file, "utf-8");
1200
1307
  } catch (e) {
1201
1308
  if (e.code === "ENOENT")
1202
1309
  continue;
@@ -1222,7 +1329,7 @@ async function mailboxStats(stateRoot, project) {
1222
1329
  let sizeBytes = 0;
1223
1330
  for (const f of [file, ...rotated]) {
1224
1331
  try {
1225
- sizeBytes += (await fs9.stat(f)).size;
1332
+ sizeBytes += (await fs8.stat(f)).size;
1226
1333
  } catch (e) {
1227
1334
  if (e.code !== "ENOENT")
1228
1335
  throw e;
@@ -1238,7 +1345,7 @@ async function mailboxStats(stateRoot, project) {
1238
1345
  }
1239
1346
  async function oldestEntryAgeMs(file) {
1240
1347
  try {
1241
- const buf = await fs9.readFile(file, "utf-8");
1348
+ const buf = await fs8.readFile(file, "utf-8");
1242
1349
  const firstLine = buf.split("\n").find((l) => l.trim());
1243
1350
  if (!firstLine)
1244
1351
  return 0;
@@ -1253,7 +1360,7 @@ async function rotateIfNeeded(opts) {
1253
1360
  const file = logPath(opts.stateRoot, opts.project);
1254
1361
  let size = 0;
1255
1362
  try {
1256
- size = (await fs9.stat(file)).size;
1363
+ size = (await fs8.stat(file)).size;
1257
1364
  } catch (e) {
1258
1365
  if (e.code === "ENOENT")
1259
1366
  return { rotated: false };
@@ -1269,29 +1376,29 @@ async function rotateIfNeeded(opts) {
1269
1376
  const dst = `${file}.${n + 1}`;
1270
1377
  if (n + 1 > opts.keepCount) {
1271
1378
  try {
1272
- await fs9.unlink(src);
1379
+ await fs8.unlink(src);
1273
1380
  } catch (e) {
1274
1381
  if (e.code !== "ENOENT")
1275
1382
  throw e;
1276
1383
  }
1277
1384
  } else {
1278
1385
  try {
1279
- await fs9.rename(src, dst);
1386
+ await fs8.rename(src, dst);
1280
1387
  } catch (e) {
1281
1388
  if (e.code !== "ENOENT")
1282
1389
  throw e;
1283
1390
  }
1284
1391
  }
1285
1392
  }
1286
- await fs9.rename(file, `${file}.1`);
1287
- await fs9.writeFile(file, "", { encoding: "utf-8" });
1393
+ await fs8.rename(file, `${file}.1`);
1394
+ await fs8.writeFile(file, "", { encoding: "utf-8" });
1288
1395
  return { rotated: true, from: file, to: `${file}.1` };
1289
1396
  });
1290
1397
  }
1291
1398
 
1292
1399
  // packages/core/dist/protocol.js
1293
1400
  import { createServer, createConnection } from "net";
1294
- import { existsSync as existsSync5, unlinkSync } from "fs";
1401
+ import { existsSync as existsSync5, unlinkSync, chmodSync } from "fs";
1295
1402
  var PROTOCOL_VERSION = 1;
1296
1403
  function encodeMsg(obj) {
1297
1404
  return JSON.stringify(obj) + "\n";
@@ -1405,6 +1512,12 @@ function startServer(sockPath, handlerOrCallbacks, onListenError = defaultListen
1405
1512
  });
1406
1513
  });
1407
1514
  server.on("error", onListenError);
1515
+ server.on("listening", () => {
1516
+ try {
1517
+ chmodSync(sockPath, 384);
1518
+ } catch {
1519
+ }
1520
+ });
1408
1521
  server.listen(sockPath);
1409
1522
  return server;
1410
1523
  }
@@ -1548,7 +1661,7 @@ function reconcileLiveness(prev, next) {
1548
1661
  }
1549
1662
 
1550
1663
  // packages/core/dist/store.js
1551
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync, renameSync, writeFileSync as writeFileSync4, existsSync as existsSync6, rmSync as rmSync3, statSync } from "fs";
1664
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync, renameSync, writeFileSync as writeFileSync3, existsSync as existsSync6, rmSync as rmSync3, statSync } from "fs";
1552
1665
  import { join as join6, resolve, sep } from "path";
1553
1666
  function safeSegment(kind, s) {
1554
1667
  if (typeof s !== "string" || s.length === 0) {
@@ -1574,10 +1687,10 @@ function createStore(root) {
1574
1687
  const taskFile = (p, id) => assertUnderRoot(join6(projDir(p), `${safeSegment("id", id)}.json`));
1575
1688
  return {
1576
1689
  put(rec) {
1577
- mkdirSync3(projDir(rec.project), { recursive: true });
1690
+ mkdirSync2(projDir(rec.project), { recursive: true });
1578
1691
  const dest = taskFile(rec.project, rec.id);
1579
1692
  const tmp = `${dest}.tmp`;
1580
- writeFileSync4(tmp, JSON.stringify(rec, null, 2));
1693
+ writeFileSync3(tmp, JSON.stringify(rec, null, 2));
1581
1694
  renameSync(tmp, dest);
1582
1695
  },
1583
1696
  get(project, id) {
@@ -1585,7 +1698,7 @@ function createStore(root) {
1585
1698
  if (!existsSync6(f))
1586
1699
  return void 0;
1587
1700
  try {
1588
- return JSON.parse(readFileSync5(f, "utf-8"));
1701
+ return JSON.parse(readFileSync4(f, "utf-8"));
1589
1702
  } catch {
1590
1703
  return void 0;
1591
1704
  }
@@ -1596,7 +1709,7 @@ function createStore(root) {
1596
1709
  return [];
1597
1710
  return readdirSync(d).filter((n) => n.endsWith(".json")).map((n) => {
1598
1711
  try {
1599
- return JSON.parse(readFileSync5(join6(d, n), "utf-8"));
1712
+ return JSON.parse(readFileSync4(join6(d, n), "utf-8"));
1600
1713
  } catch {
1601
1714
  return void 0;
1602
1715
  }
@@ -1630,7 +1743,7 @@ function createStore(root) {
1630
1743
  import { homedir as homedir4 } from "os";
1631
1744
  import { join as join7, resolve as resolve2, sep as sep2 } from "path";
1632
1745
  import { randomBytes } from "crypto";
1633
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync5, existsSync as existsSync7, rmSync as rmSync4, statSync as statSync2 } from "fs";
1746
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync4, existsSync as existsSync7, rmSync as rmSync4, statSync as statSync2 } from "fs";
1634
1747
  var WORK_ITEM_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
1635
1748
 
1636
1749
  // packages/core/dist/index.js
@@ -1638,9 +1751,9 @@ init_snapshot();
1638
1751
 
1639
1752
  // packages/core/dist/launchd.js
1640
1753
  import { execFileSync as execFileSync3 } from "child_process";
1641
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, readFileSync as readFileSync7, existsSync as existsSync8, openSync, writeSync, closeSync, unlinkSync as unlinkSync2, constants } from "fs";
1754
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync8, openSync, writeSync, closeSync, unlinkSync as unlinkSync2, constants } from "fs";
1642
1755
  import { homedir as homedir5 } from "os";
1643
- import { dirname as dirname3, join as join8 } from "path";
1756
+ import { dirname as dirname2, join as join8 } from "path";
1644
1757
  import { fileURLToPath } from "url";
1645
1758
 
1646
1759
  // packages/core/dist/crew-pane-reader.js
@@ -1711,10 +1824,10 @@ function makeGate(opts) {
1711
1824
  import { homedir as homedir6 } from "os";
1712
1825
  import { join as join9 } from "path";
1713
1826
  import { spawn as realSpawn } from "child_process";
1714
- import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
1827
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5 } from "fs";
1715
1828
 
1716
1829
  // packages/core/dist/daemon/liveness-registry.js
1717
- import { writeFileSync as writeFileSync7, readFileSync as readFileSync8, renameSync as renameSync3 } from "fs";
1830
+ import { writeFileSync as writeFileSync6, readFileSync as readFileSync7, renameSync as renameSync3 } from "fs";
1718
1831
  var LivenessRegistry = class {
1719
1832
  path;
1720
1833
  readFile;
@@ -1724,13 +1837,13 @@ var LivenessRegistry = class {
1724
1837
  this.path = opts.path;
1725
1838
  this.readFile = opts.readFile ?? ((p) => {
1726
1839
  try {
1727
- return readFileSync8(p, "utf-8");
1840
+ return readFileSync7(p, "utf-8");
1728
1841
  } catch {
1729
1842
  return void 0;
1730
1843
  }
1731
1844
  });
1732
1845
  this.writeFile = opts.writeFile ?? ((p, c) => {
1733
- writeFileSync7(`${p}.tmp`, c);
1846
+ writeFileSync6(`${p}.tmp`, c);
1734
1847
  renameSync3(`${p}.tmp`, p);
1735
1848
  });
1736
1849
  }
@@ -1791,14 +1904,16 @@ function buildContext(opts) {
1791
1904
  const sockPath = opts.sockPath ?? join9(homedir6(), ".config", "squadrant", "squadrant.sock");
1792
1905
  const store = createStore(stateRoot);
1793
1906
  const bootedAt = Date.now();
1794
- const taskTimeoutMs = loadConfig().defaults.taskTimeoutMs;
1907
+ const config = loadConfig();
1908
+ const taskTimeoutMs = config.defaults.taskTimeoutMs;
1909
+ const takeoverNudgeHours = config.defaults.takeoverNudgeHours;
1795
1910
  const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
1796
1911
  const spawn2 = opts.spawn ?? realSpawn;
1797
1912
  const resultsDir = join9(stateRoot, "_results");
1798
- mkdirSync6(resultsDir, { recursive: true });
1913
+ mkdirSync5(resultsDir, { recursive: true });
1799
1914
  const writeResult = (id, payload) => {
1800
1915
  const p = join9(resultsDir, `${id}.txt`);
1801
- writeFileSync8(p, payload);
1916
+ writeFileSync7(p, payload);
1802
1917
  return p;
1803
1918
  };
1804
1919
  const log = (m) => process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${m}
@@ -1811,6 +1926,7 @@ function buildContext(opts) {
1811
1926
  bootedAt,
1812
1927
  lastSweepAt: { value: null },
1813
1928
  taskTimeoutMs,
1929
+ takeoverNudgeHours,
1814
1930
  isPidAlive,
1815
1931
  spawn: spawn2,
1816
1932
  resultsDir,
@@ -1898,7 +2014,7 @@ function createAttach(ctx) {
1898
2014
  }
1899
2015
 
1900
2016
  // packages/core/dist/daemon/start.js
1901
- import { join as join11, dirname as dirname4 } from "path";
2017
+ import { join as join11, dirname as dirname3 } from "path";
1902
2018
  import { readdir } from "fs/promises";
1903
2019
 
1904
2020
  // packages/core/dist/daemon/interactive-probe.js
@@ -2492,7 +2608,7 @@ function createServer2(ctx, handlers) {
2492
2608
  // packages/core/dist/daemon/snapshot-gather.js
2493
2609
  import { fileURLToPath as fileURLToPath2 } from "url";
2494
2610
  import { join as join10 } from "path";
2495
- import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
2611
+ import { statSync as statSync3, openSync as openSync2, readSync, closeSync as closeSync2, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
2496
2612
  var SELF_PATH = fileURLToPath2(import.meta.url);
2497
2613
  function distBuiltAt() {
2498
2614
  try {
@@ -2501,10 +2617,10 @@ function distBuiltAt() {
2501
2617
  return 0;
2502
2618
  }
2503
2619
  }
2504
- function gatherLogStats(path19, now, windowMs) {
2620
+ function gatherLogStats(path20, now, windowMs) {
2505
2621
  let sizeBytes = 0;
2506
2622
  try {
2507
- sizeBytes = statSync3(path19).size;
2623
+ sizeBytes = statSync3(path20).size;
2508
2624
  } catch {
2509
2625
  return { errorCount: 0, sizeBytes: 0, windowMs };
2510
2626
  }
@@ -2515,7 +2631,7 @@ function gatherLogStats(path19, now, windowMs) {
2515
2631
  const len = sizeBytes - start;
2516
2632
  let text = "";
2517
2633
  try {
2518
- const fd = openSync2(path19, "r");
2634
+ const fd = openSync2(path20, "r");
2519
2635
  try {
2520
2636
  const buf = Buffer.alloc(len);
2521
2637
  readSync(fd, buf, 0, len, start);
@@ -2556,7 +2672,7 @@ function gatherStoreStats(store, stateRoot, project) {
2556
2672
  if (!n.endsWith(".json"))
2557
2673
  continue;
2558
2674
  try {
2559
- JSON.parse(readFileSync9(join10(dir, n), "utf-8"));
2675
+ JSON.parse(readFileSync8(join10(dir, n), "utf-8"));
2560
2676
  } catch {
2561
2677
  corruptCount++;
2562
2678
  }
@@ -2605,6 +2721,7 @@ function startDaemon(ctx, opts, pkgVersion) {
2605
2721
  isPidAlive,
2606
2722
  notify,
2607
2723
  taskTimeoutMs,
2724
+ takeoverNudgeHours: ctx.takeoverNudgeHours,
2608
2725
  isSurfaceAlive: surfaceProbe,
2609
2726
  resendFirstTurn: ctx.resendFirstTurn,
2610
2727
  launchHeadless: opts.launchHeadless,
@@ -2660,7 +2777,7 @@ function startDaemon(ctx, opts, pkgVersion) {
2660
2777
  return out;
2661
2778
  }
2662
2779
  async function gatherSnapshotInputs(now) {
2663
- const logPath2 = join11(dirname4(stateRoot), "squadrantd.log");
2780
+ const logPath2 = join11(dirname3(stateRoot), "squadrantd.log");
2664
2781
  const tier2Projects = opts.registeredProjects ?? Object.keys(loadConfig().projects);
2665
2782
  const projects = await Promise.all(tier2Projects.map(async (project) => {
2666
2783
  const cursor = await readCursor({ stateRoot, project, subscriber: CURSOR_SUBSCRIBER2 });
@@ -2849,8 +2966,8 @@ function startDaemon(ctx, opts, pkgVersion) {
2849
2966
 
2850
2967
  // packages/core/dist/session-freshness.js
2851
2968
  import crypto from "crypto";
2852
- import fs10 from "fs";
2853
- import path8 from "path";
2969
+ import fs9 from "fs";
2970
+ import path9 from "path";
2854
2971
 
2855
2972
  // packages/core/dist/crew-protocol.js
2856
2973
  function buildCompletionProtocol(taskId, project) {
@@ -3112,17 +3229,16 @@ function formatInbound(text) {
3112
3229
  }
3113
3230
 
3114
3231
  // packages/core/dist/telegram/state.js
3115
- import fs11 from "fs";
3116
- import path9 from "path";
3232
+ import path10 from "path";
3117
3233
  function statePath(stateRoot) {
3118
- return path9.join(stateRoot, "telegram-state.json");
3234
+ return path10.join(stateRoot, "telegram-state.json");
3119
3235
  }
3120
3236
  function topicKey(project, scope = "project") {
3121
3237
  return `${project}::${scope}`;
3122
3238
  }
3123
3239
  function loadState(stateRoot) {
3124
3240
  try {
3125
- const raw = fs11.readFileSync(statePath(stateRoot), "utf-8");
3241
+ const raw = readConfigFileSync(statePath(stateRoot));
3126
3242
  const data = JSON.parse(raw);
3127
3243
  const result = {
3128
3244
  offset: typeof data.offset === "number" ? data.offset : 0,
@@ -3137,8 +3253,7 @@ function loadState(stateRoot) {
3137
3253
  }
3138
3254
  }
3139
3255
  function saveState(stateRoot, s) {
3140
- fs11.mkdirSync(stateRoot, { recursive: true });
3141
- fs11.writeFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
3256
+ writeConfigFileSync(statePath(stateRoot), JSON.stringify(s, null, 2) + "\n");
3142
3257
  }
3143
3258
  function setTopic(stateRoot, project, topicId, scope = "project") {
3144
3259
  const s = loadState(stateRoot);
@@ -3229,7 +3344,7 @@ function createTelegramClient(opts) {
3229
3344
 
3230
3345
  // packages/core/dist/telegram/bridge.js
3231
3346
  import os4 from "os";
3232
- import path10 from "path";
3347
+ import path11 from "path";
3233
3348
 
3234
3349
  // packages/core/dist/telegram/panels.js
3235
3350
  var mark = (on, label) => on ? `\u2022 ${label}` : label;
@@ -3335,7 +3450,7 @@ function notifyToggle(text) {
3335
3450
  }
3336
3451
  function createTelegramBridge(opts) {
3337
3452
  const { cfg, stateRoot, client, appendCaptainMessage: appendCaptainMessage2, log, ensureCaptainAlive, runCommand, sendReply } = opts;
3338
- const configRoot = opts.configRoot ?? path10.join(os4.homedir(), ".config", "squadrant");
3453
+ const configRoot = opts.configRoot ?? path11.join(os4.homedir(), ".config", "squadrant");
3339
3454
  const pollMs = cfg.pollMs ?? 1e3;
3340
3455
  let running = false;
3341
3456
  let lastSuccessfulPollAt = null;
@@ -3466,14 +3581,14 @@ function createTelegramBridge(opts) {
3466
3581
  }
3467
3582
  function currentEffort() {
3468
3583
  try {
3469
- return loadConfig(path10.join(configRoot, "config.json")).defaults.effort ?? "balance";
3584
+ return loadConfig(path11.join(configRoot, "config.json")).defaults.effort ?? "balance";
3470
3585
  } catch {
3471
3586
  return "balance";
3472
3587
  }
3473
3588
  }
3474
3589
  function projectNames() {
3475
3590
  try {
3476
- return Object.keys(loadConfig(path10.join(configRoot, "config.json")).projects);
3591
+ return Object.keys(loadConfig(path11.join(configRoot, "config.json")).projects);
3477
3592
  } catch {
3478
3593
  return [];
3479
3594
  }
@@ -3685,7 +3800,7 @@ function createTelegramBridge(opts) {
3685
3800
  }
3686
3801
 
3687
3802
  // packages/core/dist/telegram/setup.js
3688
- import fs12 from "fs";
3803
+ import fs10 from "fs";
3689
3804
 
3690
3805
  // packages/core/dist/restart-daemon.js
3691
3806
  import { execFileSync as execFileSync4 } from "child_process";
@@ -3701,14 +3816,14 @@ import { join as join13 } from "path";
3701
3816
  var DEFAULT_SOCK_PATH2 = join13(homedir8(), ".config", "squadrant", "squadrant.sock");
3702
3817
 
3703
3818
  // packages/core/dist/side-session.js
3704
- import fs13 from "fs";
3819
+ import fs11 from "fs";
3705
3820
 
3706
3821
  // packages/core/dist/crew-spawn.js
3707
- import fs14 from "fs";
3822
+ import fs12 from "fs";
3708
3823
  import os5 from "os";
3709
- import path11 from "path";
3710
- var TEMPLATES_DIR = path11.join(os5.homedir(), ".config", "squadrant", "templates");
3711
- var STATE_ROOT = path11.join(os5.homedir(), ".config", "squadrant", "state");
3824
+ import path12 from "path";
3825
+ var TEMPLATES_DIR = path12.join(os5.homedir(), ".config", "squadrant", "templates");
3826
+ var STATE_ROOT = path12.join(os5.homedir(), ".config", "squadrant", "state");
3712
3827
 
3713
3828
  // packages/core/dist/lifecycle-source.js
3714
3829
  function reduceLifecycle(prev, next) {
@@ -3740,27 +3855,27 @@ import { execSync as execSync4 } from "child_process";
3740
3855
  import { execSync as execSync5 } from "child_process";
3741
3856
 
3742
3857
  // packages/agents/dist/drivers/launch-cmd.js
3743
- import fs15 from "fs";
3744
- import path12 from "path";
3858
+ import fs13 from "fs";
3859
+ import path13 from "path";
3745
3860
 
3746
3861
  // packages/agents/dist/projection/cursor.js
3747
3862
  import { mkdir, readFile, writeFile } from "fs/promises";
3748
- import path13 from "path";
3863
+ import path14 from "path";
3749
3864
  import os6 from "os";
3750
3865
 
3751
3866
  // packages/agents/dist/projection/codex.js
3752
3867
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3753
- import path14 from "path";
3868
+ import path15 from "path";
3754
3869
  import os7 from "os";
3755
3870
 
3756
3871
  // packages/agents/dist/projection/gemini.js
3757
3872
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
3758
- import path15 from "path";
3873
+ import path16 from "path";
3759
3874
  import os8 from "os";
3760
3875
 
3761
3876
  // packages/agents/dist/projection/opencode.js
3762
3877
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
3763
- import path16 from "path";
3878
+ import path17 from "path";
3764
3879
  import os9 from "os";
3765
3880
 
3766
3881
  // packages/agents/dist/codex/app-server-client.js
@@ -4528,7 +4643,7 @@ var OpencodeSseBridge = class {
4528
4643
 
4529
4644
  // packages/agents/dist/interactive/claude.js
4530
4645
  import { execSync as execSync6 } from "child_process";
4531
- import { readFileSync as readFileSync10 } from "fs";
4646
+ import { readFileSync as readFileSync9 } from "fs";
4532
4647
  import { homedir as homedir10 } from "os";
4533
4648
  import { join as join15 } from "path";
4534
4649
  var nextAskUserQuestionRequestId = Date.now();
@@ -5287,9 +5402,9 @@ var NotifierRegistry = class {
5287
5402
  };
5288
5403
 
5289
5404
  // packages/workspaces/dist/workspaces/obsidian.js
5290
- import fs16 from "fs/promises";
5405
+ import fs14 from "fs/promises";
5291
5406
  import { existsSync as existsSync10 } from "fs";
5292
- import path17 from "path";
5407
+ import path18 from "path";
5293
5408
 
5294
5409
  // packages/workspaces/dist/cmux-daemon/events-bridge.js
5295
5410
  import { spawn as nodeSpawn2 } from "child_process";
@@ -5426,7 +5541,7 @@ var CmuxEventsBridge = class {
5426
5541
  };
5427
5542
 
5428
5543
  // packages/workspaces/dist/cmux-daemon/daemon-cmux.js
5429
- import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
5544
+ import { readdirSync as readdirSync4, readFileSync as readFileSync10 } from "fs";
5430
5545
  import { join as join16 } from "path";
5431
5546
  import { homedir as homedir11 } from "os";
5432
5547
 
@@ -5555,14 +5670,14 @@ var DaemonCmux = class {
5555
5670
  } catch (e) {
5556
5671
  throw new Error(`liveness: could not read cmux state dir ${dir}: ${e.message}`);
5557
5672
  }
5558
- return readLivenessSnapshot(files, (f) => readFileSync11(join16(dir, f), "utf-8"), projects);
5673
+ return readLivenessSnapshot(files, (f) => readFileSync10(join16(dir, f), "utf-8"), projects);
5559
5674
  }
5560
5675
  };
5561
5676
 
5562
5677
  // packages/workspaces/dist/cmux-daemon/cmux-store-source.js
5563
5678
  import { join as join17 } from "path";
5564
5679
  import { homedir as homedir12 } from "os";
5565
- import { watch, readdirSync as readdirSync5, readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
5680
+ import { watch, readdirSync as readdirSync5, readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
5566
5681
  var CmuxStoreSource = class {
5567
5682
  name = "cmux-store";
5568
5683
  stateDir;
@@ -5716,9 +5831,9 @@ function defaultListFiles(dir) {
5716
5831
  return [];
5717
5832
  }
5718
5833
  }
5719
- function defaultReadFile(path19) {
5834
+ function defaultReadFile(path20) {
5720
5835
  try {
5721
- return readFileSync12(path19, "utf-8");
5836
+ return readFileSync11(path20, "utf-8");
5722
5837
  } catch {
5723
5838
  return void 0;
5724
5839
  }
@@ -5735,7 +5850,7 @@ function defaultWatchDir(dir, cb) {
5735
5850
  // packages/workspaces/dist/native-hooks/native-hook-source.js
5736
5851
  import { join as join18 } from "path";
5737
5852
  import { homedir as homedir13 } from "os";
5738
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
5853
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
5739
5854
  var CLAUDE_HOOK_EVENTS = [
5740
5855
  ["SessionStart", "session-start"],
5741
5856
  ["UserPromptSubmit", "prompt-submit"],
@@ -5915,16 +6030,16 @@ function extractDetail(sub, payload) {
5915
6030
  }
5916
6031
  return void 0;
5917
6032
  }
5918
- function defaultReadFile2(path19) {
6033
+ function defaultReadFile2(path20) {
5919
6034
  try {
5920
- return readFileSync13(path19, "utf-8");
6035
+ return readFileSync12(path20, "utf-8");
5921
6036
  } catch {
5922
6037
  return void 0;
5923
6038
  }
5924
6039
  }
5925
- function defaultWriteFile(path19, content) {
5926
- mkdirSync7(path19.replace(/\/[^/]+$/, ""), { recursive: true });
5927
- writeFileSync9(path19, content, "utf-8");
6040
+ function defaultWriteFile(path20, content) {
6041
+ mkdirSync6(path20.replace(/\/[^/]+$/, ""), { recursive: true });
6042
+ writeFileSync8(path20, content, "utf-8");
5928
6043
  }
5929
6044
 
5930
6045
  // packages/workspaces/dist/crew-pane.js
@@ -5995,17 +6110,16 @@ async function resendCrewFirstTurn(runtime, captainName, project, name, message)
5995
6110
  }
5996
6111
 
5997
6112
  // packages/cli/src/lib/daemon-restart-broadcast.ts
5998
- import fs17 from "fs";
5999
- import path18 from "path";
6113
+ import path19 from "path";
6000
6114
  function statePath2(stateRoot) {
6001
- return path18.join(stateRoot, "daemon-restart-state.json");
6115
+ return path19.join(stateRoot, "daemon-restart-state.json");
6002
6116
  }
6003
6117
  function computeRestartSignature(version, buildMtimeMs) {
6004
6118
  return `${version}::${buildMtimeMs}`;
6005
6119
  }
6006
6120
  function readPersistedRestartSignature(stateRoot) {
6007
6121
  try {
6008
- const raw = fs17.readFileSync(statePath2(stateRoot), "utf-8");
6122
+ const raw = readConfigFileSync(statePath2(stateRoot));
6009
6123
  const data = JSON.parse(raw);
6010
6124
  return typeof data.signature === "string" ? data.signature : null;
6011
6125
  } catch {
@@ -6013,8 +6127,7 @@ function readPersistedRestartSignature(stateRoot) {
6013
6127
  }
6014
6128
  }
6015
6129
  function writePersistedRestartSignature(stateRoot, signature) {
6016
- fs17.mkdirSync(stateRoot, { recursive: true });
6017
- fs17.writeFileSync(statePath2(stateRoot), JSON.stringify({ signature }, null, 2) + "\n");
6130
+ writeConfigFileSync(statePath2(stateRoot), JSON.stringify({ signature }, null, 2) + "\n");
6018
6131
  }
6019
6132
  function restartNotice(version, isDevRebuild) {
6020
6133
  const suffix = isDevRebuild ? " (dev build)" : "";
@@ -6048,12 +6161,12 @@ async function maybeBroadcastDaemonRestart(opts) {
6048
6161
 
6049
6162
  // packages/cli/src/squadrantd.ts
6050
6163
  var SELF_PATH2 = fileURLToPath3(import.meta.url);
6051
- var CLI_BIN = join19(dirname5(SELF_PATH2), "index.js");
6164
+ var CLI_BIN = join19(dirname4(SELF_PATH2), "index.js");
6052
6165
  var DAEMON_SOCK = join19(homedir14(), ".config", "squadrant", "squadrant.sock");
6053
6166
  function readPkgVersion() {
6054
6167
  try {
6055
- const pkgPath = join19(dirname5(SELF_PATH2), "..", "package.json");
6056
- return JSON.parse(readFileSync14(pkgPath, "utf-8")).version ?? "unknown";
6168
+ const pkgPath = join19(dirname4(SELF_PATH2), "..", "package.json");
6169
+ return JSON.parse(readFileSync13(pkgPath, "utf-8")).version ?? "unknown";
6057
6170
  } catch {
6058
6171
  return "unknown";
6059
6172
  }
@@ -6075,7 +6188,7 @@ function buildTelegramBridge(cfg, stateRoot, log) {
6075
6188
  return createTelegramBridge({
6076
6189
  cfg,
6077
6190
  stateRoot,
6078
- configRoot: dirname5(stateRoot),
6191
+ configRoot: dirname4(stateRoot),
6079
6192
  client,
6080
6193
  appendCaptainMessage,
6081
6194
  log,
@@ -6343,6 +6456,9 @@ function logCrashMarker(kind, err) {
6343
6456
  process.stderr.write(`[squadrantd] ${(/* @__PURE__ */ new Date()).toISOString()} ${kind} pid=${process.pid} error=${message}
6344
6457
  `);
6345
6458
  }
6459
+ function isMonorepoCheckout(scriptPath, dirExists = existsSync12) {
6460
+ return dirExists(join19(dirname4(scriptPath), "..", "packages"));
6461
+ }
6346
6462
  if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
6347
6463
  process.on("uncaughtException", (err) => {
6348
6464
  logCrashMarker("uncaughtException", err);
@@ -6358,6 +6474,13 @@ if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
6358
6474
  process.stdout.write("squadrantd: launchd-managed daemon entry (no CLI args). Use `squadrant` for commands.\n");
6359
6475
  process.exit(0);
6360
6476
  }
6477
+ if (isMonorepoCheckout(process.argv[1])) {
6478
+ process.stderr.write(
6479
+ `[squadrantd] refusing to start: '${process.argv[1]}' is a monorepo/worktree checkout, not an installed copy \u2014 it must never become the shared production daemon (#670). This entry takes no CLI flags/sockPath override; for local testing, import startSquadrantd({ sockPath, ... }) programmatically instead of running this entry directly, or run the test suite (\`pnpm test\`).
6480
+ `
6481
+ );
6482
+ process.exit(1);
6483
+ }
6361
6484
  const sock = join19(homedir14(), ".config", "squadrant", "squadrant.sock");
6362
6485
  if (await isDaemonSocketLive(sock)) {
6363
6486
  process.stderr.write(`[squadrantd] refusing to start: a live daemon already owns ${sock}
@@ -6375,6 +6498,7 @@ if (process.argv[1] && process.argv[1].endsWith("squadrantd.js")) {
6375
6498
  export {
6376
6499
  defaultIsPidAlive,
6377
6500
  discoverCaptainSurface,
6501
+ isMonorepoCheckout,
6378
6502
  startSquadrantd
6379
6503
  };
6380
6504
  //# sourceMappingURL=squadrantd.js.map