nolo-cli 0.179.0-alpha.1 → 0.180.0-alpha.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.
Files changed (76) hide show
  1. package/{_executeModel-ZP6M2EWK.js → _executeModel-ACI2KGWO.js} +19 -19
  2. package/{accountResolver-OHS53Q75.js → accountResolver-HPVXZJYD.js} +1 -1
  3. package/{agentDeleteCommand-7TH3MY5U.js → agentDeleteCommand-LVSLONDD.js} +21 -21
  4. package/{agentEmailCommands-LVSCPVGC.js → agentEmailCommands-BKH3Q6I6.js} +22 -22
  5. package/{agentListCommands-67HVR7KB.js → agentListCommands-JPKRBHMP.js} +10 -10
  6. package/{agentMachineCommands-36IZWYUV.js → agentMachineCommands-HRORJN73.js} +8 -8
  7. package/{agentRecordCommands-4AIFNXVY.js → agentRecordCommands-OSZF2EHL.js} +21 -21
  8. package/{agentRunCommand-3B224VOA.js → agentRunCommand-FCGNSB4D.js} +22 -22
  9. package/{agentRuntimeLocal-I4J6XDO7.js → agentRuntimeLocal-SWG5ULIJ.js} +5 -5
  10. package/{agentSlice-XYVD7VDW.js → agentSlice-FCDRLXVA.js} +1 -1
  11. package/{appDeleteCommands-VBI7C4IO.js → appDeleteCommands-YH3BYQYK.js} +9 -9
  12. package/{appDeployCommands-FYUG5XWE.js → appDeployCommands-F44TNQJG.js} +9 -9
  13. package/{appDeployStatusCommands-TYHJOMUA.js → appDeployStatusCommands-BFTGFVQY.js} +9 -9
  14. package/{appGetCommands-HINYK5UT.js → appGetCommands-SFLFIXPW.js} +9 -9
  15. package/{appListCommands-NWDNQT4A.js → appListCommands-RUO5SEAJ.js} +9 -9
  16. package/{builtinSkillRegistry-V76JTNCJ.js → builtinSkillRegistry-MRI4FNCR.js} +4 -4
  17. package/{chromeCommands-VANHVOCE.js → chromeCommands-BGL6JZMB.js} +1 -1
  18. package/{chromeOpsCommands-RY776THN.js → chromeOpsCommands-IWKUA4C3.js} +37 -10
  19. package/{chunk-KPSNHLFI.js → chunk-2D3YKEAQ.js} +1 -1
  20. package/{chunk-BAXCQF6H.js → chunk-4FZUUCXQ.js} +1 -1
  21. package/{chunk-CUVJE3L5.js → chunk-4KDYRX3O.js} +2 -2
  22. package/{chunk-3LNVSFTH.js → chunk-5LZBJZLT.js} +2 -2
  23. package/{chunk-Y3BUTWUJ.js → chunk-6PHS6ADB.js} +2 -2
  24. package/{chunk-GPA3EA3Q.js → chunk-CGKN34IJ.js} +15 -15
  25. package/{chunk-UVPKOBVG.js → chunk-COXNBVP7.js} +4 -4
  26. package/{chunk-RPZNVBA5.js → chunk-CTEEJJVR.js} +1 -1
  27. package/{chunk-UTZF3X4Z.js → chunk-DTA4JVXW.js} +3 -3
  28. package/{chunk-LY4HX6OD.js → chunk-DVSXZC7F.js} +9 -9
  29. package/{chunk-RUKAOMXD.js → chunk-EAWOT7M6.js} +1 -1
  30. package/{chunk-IAOVXFL7.js → chunk-GCAOOTIS.js} +3 -3
  31. package/{chunk-L7PTHI3I.js → chunk-GHKEUJ6I.js} +2 -2
  32. package/{chunk-YUXSWHQC.js → chunk-GKKIZRJ6.js} +29 -3
  33. package/{chunk-ENIQIEAF.js → chunk-H63VHVFY.js} +1 -1
  34. package/{chunk-PP4TMS3T.js → chunk-HS7JBDUJ.js} +3 -3
  35. package/{chunk-YOXGSXOV.js → chunk-HTSXDJHA.js} +8 -8
  36. package/{chunk-QWNMFO5E.js → chunk-KUGOIFXV.js} +3 -3
  37. package/{chunk-NYYFIKHI.js → chunk-MMKP2XWK.js} +35 -6
  38. package/{chunk-FSTHTTI6.js → chunk-NJI5HZQA.js} +3 -3
  39. package/{chunk-EEEBL4MX.js → chunk-OBKPOFOB.js} +7 -7
  40. package/{chunk-P34IX26T.js → chunk-OXYSTN6B.js} +1 -1
  41. package/{chunk-M7ZURZLR.js → chunk-PD4KNHVU.js} +3 -3
  42. package/{chunk-ZEOR6XPJ.js → chunk-POYXVOYU.js} +2 -2
  43. package/{chunk-CIM3IW2X.js → chunk-PT6335JZ.js} +2 -2
  44. package/{chunk-LRXTGAZ4.js → chunk-QOKJFYJK.js} +7 -7
  45. package/{chunk-X7YSFSY3.js → chunk-QUCQVMYZ.js} +4 -4
  46. package/{chunk-LOCHKPC4.js → chunk-SGATEZV5.js} +14 -14
  47. package/{chunk-WXOKJP3W.js → chunk-SK2UMOZC.js} +1 -1
  48. package/{chunk-LM7VJUSD.js → chunk-TCCBECTT.js} +1 -1
  49. package/{chunk-NQKFBJD4.js → chunk-WQAOAJ5Q.js} +2 -2
  50. package/{chunk-5EBLANFT.js → chunk-XJBWPOIL.js} +1 -1
  51. package/{chunk-IU4FJDRU.js → chunk-YAN7XTXO.js} +5 -5
  52. package/{chunk-2GRXE3YJ.js → chunk-ZEQ6L6MZ.js} +1 -1
  53. package/{commandRegistry-OW2MIVIT.js → commandRegistry-UQ4GKJ5B.js} +48 -44
  54. package/{contentThunks-WNBEFSCD.js → contentThunks-GZUPF67H.js} +3 -3
  55. package/{createAgentAutomationAction-NTL5ST3J.js → createAgentAutomationAction-ZOFCBZRE.js} +3 -3
  56. package/{createDialogAction-JWGDTE6S.js → createDialogAction-G7QCYQT7.js} +6 -6
  57. package/{createPageAction-FPM5RD4C.js → createPageAction-Z6JQZYS5.js} +3 -3
  58. package/{dialogCommands-D4MNLDBH.js → dialogCommands-MITLWDBY.js} +9 -9
  59. package/{dialogSlice-QVQO4JCU.js → dialogSlice-PKFXK5WM.js} +2 -2
  60. package/{docCreateCommands-765YJTNP.js → docCreateCommands-G3S3LZMZ.js} +8 -8
  61. package/{docDeleteCommands-G5U77546.js → docDeleteCommands-MLRCMLVB.js} +9 -9
  62. package/{docReadCommands-5YU7WGGT.js → docReadCommands-6A6SBCOP.js} +8 -8
  63. package/{docStore-VN2HSHLZ.js → docStore-NAEY4CEK.js} +1 -1
  64. package/{docUpdateCommands-H2AHPZRW.js → docUpdateCommands-UZQC2NDO.js} +8 -8
  65. package/{handleSendMessageAction-DMXH4YLX.js → handleSendMessageAction-HUQLERJP.js} +11 -11
  66. package/index.js +3 -3
  67. package/{localRuntimeAdapter-CQH4RFZC.js → localRuntimeAdapter-4DJDFM7A.js} +19 -19
  68. package/{machineCommands-TVCJZ5K5.js → machineCommands-R2IP5OVT.js} +321 -112
  69. package/{machineWsRunDispatch-663CSUA3.js → machineWsRunDispatch-CIJDSHTA.js} +20 -20
  70. package/package.json +1 -1
  71. package/{readlineWorkspace-PSJS3ZS2.js → readlineWorkspace-YCNAWRDL.js} +25 -25
  72. package/{runAgentClientLoop-J3CTCBSI.js → runAgentClientLoop-K7WRMT27.js} +17 -17
  73. package/{spaceCommands-2ESFYAZG.js → spaceCommands-L537SH5I.js} +8 -8
  74. package/{streamAgentChatTurn-Z4DGKLGP.js → streamAgentChatTurn-HKUZ7OCA.js} +19 -19
  75. package/{tools-A2L3DK2Y.js → tools-NS37KPGT.js} +13 -13
  76. package/{updateDialogTitleAction-WM5YYYFZ.js → updateDialogTitleAction-UUZ5HDM2.js} +3 -3
@@ -2,7 +2,7 @@ import {
2
2
  buildMachinePermissionPromptBlock,
3
3
  handleConnectorRunMessage,
4
4
  resolveMachineRunPermissionPolicy
5
- } from "./chunk-X7YSFSY3.js";
5
+ } from "./chunk-QUCQVMYZ.js";
6
6
  import {
7
7
  MAX_CONNECTOR_TIMER_DELAY_MS,
8
8
  clampConnectorTimerDelayMs,
@@ -11,7 +11,7 @@ import {
11
11
  resolveConnectorProbeTimeoutMs,
12
12
  resolveConnectorWebSocketTarget
13
13
  } from "./chunk-BLVSFYQ3.js";
14
- import "./chunk-LOCHKPC4.js";
14
+ import "./chunk-SGATEZV5.js";
15
15
  import "./chunk-D2J22QSE.js";
16
16
  import "./chunk-R4BMKAR5.js";
17
17
  import "./chunk-TDX2L4W2.js";
@@ -19,26 +19,27 @@ import {
19
19
  parsePositiveFiniteNumberOrFallback
20
20
  } from "./chunk-Y5GCAMGP.js";
21
21
  import "./chunk-VP3BINY7.js";
22
- import "./chunk-RPZNVBA5.js";
22
+ import "./chunk-CTEEJJVR.js";
23
23
  import "./chunk-NBTY7YJF.js";
24
24
  import "./chunk-VL5KOY2C.js";
25
25
  import {
26
- detectMachineInfo
26
+ detectMachineInfo,
27
+ resolveMachineId
27
28
  } from "./chunk-S73EPYNC.js";
28
29
  import "./chunk-ZEZC7IIM.js";
29
30
  import "./chunk-VIWDGTDA.js";
30
31
  import "./chunk-HGDKT2D6.js";
31
- import "./chunk-YUXSWHQC.js";
32
+ import "./chunk-GKKIZRJ6.js";
32
33
  import "./chunk-W6E5IXIP.js";
33
34
  import "./chunk-ATNTHIZI.js";
34
35
  import "./chunk-INFHX73M.js";
35
- import "./chunk-IAOVXFL7.js";
36
+ import "./chunk-GCAOOTIS.js";
36
37
  import "./chunk-HS7MLR74.js";
37
- import "./chunk-GPA3EA3Q.js";
38
- import "./chunk-KPSNHLFI.js";
38
+ import "./chunk-CGKN34IJ.js";
39
+ import "./chunk-2D3YKEAQ.js";
39
40
  import "./chunk-KFHREHUQ.js";
40
41
  import "./chunk-OXME3PEE.js";
41
- import "./chunk-FSTHTTI6.js";
42
+ import "./chunk-NJI5HZQA.js";
42
43
  import "./chunk-NF64D32W.js";
43
44
  import {
44
45
  CORE_DRAIN_REASON
@@ -52,17 +53,17 @@ import "./chunk-KSTEXKL6.js";
52
53
  import "./chunk-AGIM7IMJ.js";
53
54
  import "./chunk-63W5N6P3.js";
54
55
  import "./chunk-VF4K2QE6.js";
55
- import "./chunk-CUVJE3L5.js";
56
+ import "./chunk-4KDYRX3O.js";
56
57
  import "./chunk-BAWAKW7X.js";
57
- import "./chunk-M7ZURZLR.js";
58
+ import "./chunk-PD4KNHVU.js";
58
59
  import "./chunk-IF7V3CBU.js";
59
60
  import "./chunk-YPG7TUQD.js";
60
- import "./chunk-LM7VJUSD.js";
61
- import "./chunk-YOXGSXOV.js";
61
+ import "./chunk-TCCBECTT.js";
62
+ import "./chunk-HTSXDJHA.js";
62
63
  import "./chunk-OFFL4DCR.js";
63
64
  import "./chunk-523DITKR.js";
64
65
  import "./chunk-QYPE3TS3.js";
65
- import "./chunk-PP4TMS3T.js";
66
+ import "./chunk-HS7JBDUJ.js";
66
67
  import "./chunk-4JH2UMJR.js";
67
68
  import "./chunk-ZJ7UHBJS.js";
68
69
  import "./chunk-BYXBJQAS.js";
@@ -88,8 +89,8 @@ import "./chunk-QQ56R7KS.js";
88
89
  import "./chunk-OW765XBU.js";
89
90
  import "./chunk-PXYBUFDB.js";
90
91
  import "./chunk-D2MDMY2M.js";
91
- import "./chunk-3LNVSFTH.js";
92
- import "./chunk-WXOKJP3W.js";
92
+ import "./chunk-5LZBJZLT.js";
93
+ import "./chunk-SK2UMOZC.js";
93
94
  import "./chunk-EUGTFFO6.js";
94
95
  import "./chunk-AETZ7AD6.js";
95
96
  import "./chunk-Y6ORVLSS.js";
@@ -99,7 +100,7 @@ import {
99
100
  PATH_FIELD_ALIASES,
100
101
  createLocalWorkspaceToolExecutors,
101
102
  resolveRequestedWorkspacePath
102
- } from "./chunk-UVPKOBVG.js";
103
+ } from "./chunk-COXNBVP7.js";
103
104
  import "./chunk-KMSMIOOL.js";
104
105
  import "./chunk-ZNFJWEN4.js";
105
106
  import "./chunk-YO776XHS.js";
@@ -113,10 +114,10 @@ import "./chunk-P2BGRAW7.js";
113
114
  import "./chunk-G6OOUQWJ.js";
114
115
  import "./chunk-56GBUMO5.js";
115
116
  import "./chunk-WJZOQR2J.js";
116
- import "./chunk-ZEOR6XPJ.js";
117
+ import "./chunk-POYXVOYU.js";
117
118
  import "./chunk-JDCX5RWD.js";
118
119
  import "./chunk-E2BESSAW.js";
119
- import "./chunk-2GRXE3YJ.js";
120
+ import "./chunk-ZEQ6L6MZ.js";
120
121
  import "./chunk-JLR5NWP2.js";
121
122
  import "./chunk-XAFCJN32.js";
122
123
  import "./chunk-2MBTWINN.js";
@@ -133,8 +134,8 @@ import "./chunk-7DPNBQSV.js";
133
134
  import "./chunk-ZWQUWF6W.js";
134
135
  import "./chunk-VEA7H5FG.js";
135
136
  import "./chunk-4VJAC32I.js";
136
- import "./chunk-5EBLANFT.js";
137
- import "./chunk-NYYFIKHI.js";
137
+ import "./chunk-XJBWPOIL.js";
138
+ import "./chunk-MMKP2XWK.js";
138
139
  import "./chunk-LOEO7LRC.js";
139
140
  import "./chunk-M2EJHDJP.js";
140
141
  import "./chunk-5EMSBTQ6.js";
@@ -194,11 +195,169 @@ async function runHeartbeatLoop(options) {
194
195
  }
195
196
  }
196
197
 
198
+ // packages/cli/connectorInstanceLock.ts
199
+ import {
200
+ closeSync,
201
+ mkdirSync,
202
+ openSync,
203
+ readFileSync,
204
+ renameSync,
205
+ statSync,
206
+ unlinkSync,
207
+ writeFileSync
208
+ } from "node:fs";
209
+ import { homedir } from "node:os";
210
+ import { join } from "node:path";
211
+ function resolveConnectorStateDir(args = {}) {
212
+ const homeDir = args.homeDir?.trim();
213
+ if (homeDir) return join(homeDir, ".nolo", "connector");
214
+ const noloHome = args.noloHome?.trim() || process.env.NOLO_HOME?.trim();
215
+ if (noloHome) return join(noloHome, "connector");
216
+ return join(homedir(), ".nolo", "connector");
217
+ }
218
+ function sanitizeMachineId(machineId) {
219
+ const safe = String(machineId ?? "").replace(/[^A-Za-z0-9._-]/g, "_");
220
+ return safe || "unknown";
221
+ }
222
+ function connectorInstanceLockPath(args) {
223
+ return join(resolveConnectorStateDir(args), `${sanitizeMachineId(args.machineId)}.lock`);
224
+ }
225
+ function connectorAliveSignalPath(args) {
226
+ return join(resolveConnectorStateDir(args), `${sanitizeMachineId(args.machineId)}.alive`);
227
+ }
228
+ function isProcessAlive(pid) {
229
+ if (!Number.isInteger(pid) || pid <= 0) return false;
230
+ try {
231
+ process.kill(pid, 0);
232
+ return true;
233
+ } catch (error) {
234
+ return error?.code === "EPERM";
235
+ }
236
+ }
237
+ function readLockPid(lockPath) {
238
+ let raw;
239
+ try {
240
+ raw = readFileSync(lockPath, "utf8");
241
+ } catch {
242
+ return null;
243
+ }
244
+ try {
245
+ const parsed = JSON.parse(raw);
246
+ const pid = Number(parsed?.pid);
247
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
248
+ } catch {
249
+ return null;
250
+ }
251
+ }
252
+ function isEexist(error) {
253
+ return error?.code === "EEXIST";
254
+ }
255
+ var CONNECTOR_LOCK_CORRUPT_RECLAIM_SECONDS = 300;
256
+ function inspectCorruptLock(lockPath, now = Date.now()) {
257
+ try {
258
+ const stat2 = statSync(lockPath);
259
+ const ageMs = now - stat2.mtimeMs;
260
+ return ageMs > CONNECTOR_LOCK_CORRUPT_RECLAIM_SECONDS * 1e3 ? "expired" : "fresh";
261
+ } catch (error) {
262
+ const code = error?.code;
263
+ if (code === "ENOENT") return "gone";
264
+ return "fresh";
265
+ }
266
+ }
267
+ function acquireConnectorInstanceLock(args) {
268
+ const lockPath = connectorInstanceLockPath(args);
269
+ mkdirSync(resolveConnectorStateDir(args), { recursive: true });
270
+ let reclaimed;
271
+ for (let attempt = 0; attempt < 2; attempt += 1) {
272
+ let fd = null;
273
+ try {
274
+ fd = openSync(lockPath, "wx");
275
+ } catch (error) {
276
+ if (!isEexist(error)) throw error;
277
+ const ownerPid2 = readLockPid(lockPath);
278
+ if (ownerPid2 !== null) {
279
+ if (isProcessAlive(ownerPid2)) {
280
+ return { ok: false, pid: ownerPid2 };
281
+ }
282
+ try {
283
+ unlinkSync(lockPath);
284
+ } catch {
285
+ }
286
+ continue;
287
+ }
288
+ const state = inspectCorruptLock(lockPath);
289
+ if (state === "gone") {
290
+ continue;
291
+ }
292
+ if (state === "fresh") {
293
+ return { ok: false, pid: 0 };
294
+ }
295
+ try {
296
+ unlinkSync(lockPath);
297
+ } catch {
298
+ }
299
+ reclaimed = "corrupt";
300
+ continue;
301
+ }
302
+ try {
303
+ writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), "utf8");
304
+ } finally {
305
+ closeSync(fd);
306
+ }
307
+ return {
308
+ ok: true,
309
+ release: () => releaseConnectorInstanceLock(lockPath),
310
+ ...reclaimed ? { reclaimed } : {}
311
+ };
312
+ }
313
+ const ownerPid = readLockPid(lockPath);
314
+ return { ok: false, pid: ownerPid ?? 0 };
315
+ }
316
+ function releaseConnectorInstanceLock(lockPath) {
317
+ const ownerPid = readLockPid(lockPath);
318
+ if (ownerPid !== process.pid) return;
319
+ try {
320
+ unlinkSync(lockPath);
321
+ } catch {
322
+ }
323
+ }
324
+ function markConnectorAlive(args) {
325
+ const path = connectorAliveSignalPath(args);
326
+ try {
327
+ mkdirSync(resolveConnectorStateDir(args), { recursive: true });
328
+ const tmpPath = `${path}.${process.pid}.tmp`;
329
+ writeFileSync(tmpPath, String(Date.now()), "utf8");
330
+ try {
331
+ renameSync(tmpPath, path);
332
+ } catch (error) {
333
+ try {
334
+ unlinkSync(tmpPath);
335
+ } catch {
336
+ }
337
+ throw error;
338
+ }
339
+ } catch {
340
+ }
341
+ }
342
+ function clearConnectorAlive(args) {
343
+ try {
344
+ unlinkSync(connectorAliveSignalPath(args));
345
+ } catch {
346
+ }
347
+ }
348
+ function withConnectorAliveSignal(sendHeartbeat, args) {
349
+ return async () => {
350
+ const result = await sendHeartbeat();
351
+ markConnectorAlive(args);
352
+ return result;
353
+ };
354
+ }
355
+
197
356
  // packages/cli/machineDaemonCommands.ts
198
357
  import { spawn } from "node:child_process";
199
- import { existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync } from "node:fs";
200
- import { homedir } from "node:os";
201
- import { dirname, isAbsolute, join, relative, resolve } from "node:path";
358
+ import { existsSync, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync2, readdirSync, realpathSync } from "node:fs";
359
+ import { homedir as homedir2 } from "node:os";
360
+ import { dirname, isAbsolute, join as join2, relative, resolve } from "node:path";
202
361
  import { fileURLToPath } from "node:url";
203
362
  var CONNECTOR_RUNTIME_WORKSPACE_PACKAGES = /* @__PURE__ */ new Set([
204
363
  "connector-experimental",
@@ -206,7 +365,7 @@ var CONNECTOR_RUNTIME_WORKSPACE_PACKAGES = /* @__PURE__ */ new Set([
206
365
  ]);
207
366
  function readJsonFile(path) {
208
367
  try {
209
- return JSON.parse(readFileSync(path, "utf8"));
368
+ return JSON.parse(readFileSync2(path, "utf8"));
210
369
  } catch {
211
370
  return null;
212
371
  }
@@ -226,7 +385,7 @@ function isInsideOrSame(path, parent) {
226
385
  function findWorkspaceRoot(startPath) {
227
386
  let current = resolve(startPath);
228
387
  while (true) {
229
- const packageJsonPath = join(current, "package.json");
388
+ const packageJsonPath = join2(current, "package.json");
230
389
  if (existsSync(packageJsonPath)) {
231
390
  const packageJson = readJsonFile(packageJsonPath);
232
391
  if (packageJson && getWorkspacePatterns(packageJson).length > 0) {
@@ -249,7 +408,7 @@ function listWorkspacePackageJsonFiles(workspaceParent) {
249
408
  for (const entry of entries) {
250
409
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
251
410
  const relativePath = `${entry.name}/package.json`;
252
- if (existsSync(join(workspaceParent, relativePath))) {
411
+ if (existsSync(join2(workspaceParent, relativePath))) {
253
412
  results.push(relativePath);
254
413
  }
255
414
  }
@@ -258,21 +417,21 @@ function listWorkspacePackageJsonFiles(workspaceParent) {
258
417
  function validateConnectorWorkspaceLinks(cwd) {
259
418
  const repoRoot = findWorkspaceRoot(cwd);
260
419
  if (!repoRoot) return [];
261
- const rootPackageJson = readJsonFile(join(repoRoot, "package.json"));
420
+ const rootPackageJson = readJsonFile(join2(repoRoot, "package.json"));
262
421
  if (!rootPackageJson) return [];
263
422
  const normalizedRepoRoot = normalizePath(realpathSync(repoRoot));
264
423
  const errors = [];
265
424
  for (const pattern of getWorkspacePatterns(rootPackageJson)) {
266
425
  if (!pattern.endsWith("/*")) continue;
267
- const workspaceParent = join(repoRoot, pattern.slice(0, -2));
426
+ const workspaceParent = join2(repoRoot, pattern.slice(0, -2));
268
427
  if (!existsSync(workspaceParent)) continue;
269
428
  for (const entry of listWorkspacePackageJsonFiles(workspaceParent)) {
270
- const packageDir = join(workspaceParent, dirname(entry));
271
- const packageJson = readJsonFile(join(workspaceParent, entry));
429
+ const packageDir = join2(workspaceParent, dirname(entry));
430
+ const packageJson = readJsonFile(join2(workspaceParent, entry));
272
431
  if (!packageJson?.name || !CONNECTOR_RUNTIME_WORKSPACE_PACKAGES.has(packageJson.name)) {
273
432
  continue;
274
433
  }
275
- const linkPath = join(repoRoot, "node_modules", ...packageJson.name.split("/"));
434
+ const linkPath = join2(repoRoot, "node_modules", ...packageJson.name.split("/"));
276
435
  if (!existsSync(linkPath)) {
277
436
  errors.push(`${linkPath} is missing. Run bun install in ${repoRoot}.`);
278
437
  continue;
@@ -308,7 +467,7 @@ async function checkConnectorWorkspaceLinks(cwd, output, validateWorkspaceLinks)
308
467
  return false;
309
468
  }
310
469
  function resolveDaemonLogPath(env) {
311
- return env.NOLO_CONNECT_LOG || join(homedir(), ".nolo", "logs", "connector.log");
470
+ return env.NOLO_CONNECT_LOG || join2(homedir2(), ".nolo", "logs", "connector.log");
312
471
  }
313
472
  function buildDaemonCommand(cliEntrypointPath) {
314
473
  const execPath = process.execPath;
@@ -319,8 +478,8 @@ function buildDaemonCommand(cliEntrypointPath) {
319
478
  return [execPath, entrypoint, "connect", "--ws"];
320
479
  }
321
480
  function defaultSpawnDaemon(args) {
322
- mkdirSync(dirname(args.logPath), { recursive: true });
323
- const out = openSync(args.logPath, "a");
481
+ mkdirSync2(dirname(args.logPath), { recursive: true });
482
+ const out = openSync2(args.logPath, "a");
324
483
  const env = Object.fromEntries(
325
484
  Object.entries(args.env).filter((entry) => typeof entry[1] === "string")
326
485
  );
@@ -340,6 +499,20 @@ async function runMachineDaemonCommand(deps = {}) {
340
499
  if (!await checkConnectorWorkspaceLinks(process.cwd(), output, validateWorkspaceLinks)) {
341
500
  return 1;
342
501
  }
502
+ const probe = acquireConnectorInstanceLock({
503
+ machineId: deps.machineId ?? resolveMachineId(),
504
+ homeDir: deps.connectorHomeDir,
505
+ noloHome: env.NOLO_HOME,
506
+ kind: "daemon"
507
+ });
508
+ if (!probe.ok) {
509
+ output.write(
510
+ `[nolo] A connector for this machine is already running (pid=${probe.pid}). Do not start two connectors at the same time \u2014 stop the running one first (or check whether it is still healthy) before starting a new one.
511
+ `
512
+ );
513
+ return 3;
514
+ }
515
+ probe.release();
343
516
  const logPath = resolveDaemonLogPath(env);
344
517
  const result = (deps.spawnDaemon ?? defaultSpawnDaemon)({
345
518
  cmd: buildDaemonCommand(deps.cliEntrypointPath),
@@ -400,8 +573,8 @@ async function runMachineWatchCommand(deps) {
400
573
 
401
574
  // packages/cli/machineToolInvokeDispatch.ts
402
575
  import { appendFile, lstat, mkdir, realpath, stat } from "node:fs/promises";
403
- import { homedir as homedir2 } from "node:os";
404
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2, sep } from "node:path";
576
+ import { homedir as homedir3 } from "node:os";
577
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2, sep } from "node:path";
405
578
 
406
579
  // packages/agent-runtime/machineToolGrants.ts
407
580
  function isRecord2(value) {
@@ -464,8 +637,8 @@ function isMachineToolGranted(grants, toolName, agentKey) {
464
637
  }
465
638
  function resolveMachineToolAuditLogPath() {
466
639
  const noloHome = process.env.NOLO_HOME?.trim();
467
- const base = noloHome ? noloHome : join2(homedir2(), ".nolo");
468
- return join2(base, "logs", "machine-tool-audit.jsonl");
640
+ const base = noloHome ? noloHome : join3(homedir3(), ".nolo");
641
+ return join3(base, "logs", "machine-tool-audit.jsonl");
469
642
  }
470
643
  function auditTargetFor(executorArgs) {
471
644
  const target = extractMachineToolRequestPath(executorArgs) ?? "";
@@ -1261,11 +1434,13 @@ async function runMachineConnectCommand(args, deps = {}) {
1261
1434
  output,
1262
1435
  cliEntrypointPath: deps.cliEntrypointPath,
1263
1436
  validateWorkspaceLinks: deps.validateWorkspaceLinks,
1264
- spawnDaemon: deps.spawnDaemon
1437
+ spawnDaemon: deps.spawnDaemon,
1438
+ connectorHomeDir: deps.connectorHomeDir
1265
1439
  });
1266
1440
  }
1267
1441
  const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
1268
- const sendHeartbeat = () => postMachineHeartbeat({
1442
+ const machineId = machine.machineId;
1443
+ const sendHeartbeatBase = () => postMachineHeartbeat({
1269
1444
  fetchImpl,
1270
1445
  serverUrl,
1271
1446
  authToken,
@@ -1277,96 +1452,130 @@ async function runMachineConnectCommand(args, deps = {}) {
1277
1452
  if (!await checkConnectorWorkspaceLinks(process.cwd(), output, validateWorkspaceLinks)) {
1278
1453
  return 1;
1279
1454
  }
1280
- const maxAttempts = deps.maxConnectorAttempts ?? Infinity;
1281
- const sleep = deps.sleep ?? defaultSleep2;
1282
- const reconnectDelayMs = resolveConnectorReconnectDelayMs(env);
1283
- const toolGrants = readMachineToolGrants(env);
1284
- const toolInvokeHandlers = createMachineToolInvokeHandlers({
1285
- workspaceRoot: process.cwd(),
1286
- // M2 修复:机器侧授权复核与握手上报共用同一来源——本次 `deps.env ??
1287
- // process.env`。grants 快照与 WS URL 上报的那份逐字节相同;未配置时也
1288
- // 显式传 env,工厂不再回落到 process.env(调用方经 deps.env 注入环境时
1289
- // 两端曾不一致)。
1290
- env,
1291
- ...toolGrants ? { grants: toolGrants } : {}
1455
+ const instanceLock = acquireConnectorInstanceLock({
1456
+ machineId,
1457
+ homeDir: deps.connectorHomeDir,
1458
+ noloHome: env.NOLO_HOME,
1459
+ kind: "ws"
1292
1460
  });
1293
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1294
- if (deps.signal?.aborted) {
1295
- return 0;
1296
- }
1297
- const runtimeServerUrl = env.NOLO_CONNECT_RUNTIME_SERVER_URL || env.NOLO_RUNTIME_SERVER_URL || serverUrl;
1298
- const sessionResult = await runMachineWsSession({
1461
+ if (!instanceLock.ok) {
1462
+ const owner = instanceLock.pid > 0 ? `pid=${instanceLock.pid}` : "owner pid unknown";
1463
+ output.write(
1464
+ `[nolo] A connector for this machine is already running (${owner}). Do not start two connectors at the same time \u2014 stop the running one first (or check whether it is still healthy) before starting a new one.
1465
+ `
1466
+ );
1467
+ return 3;
1468
+ }
1469
+ if (instanceLock.reclaimed === "corrupt") {
1470
+ output.write(
1471
+ `[nolo] Reclaimed a corrupt connector lock (no readable pid, older than ${CONNECTOR_LOCK_CORRUPT_RECLAIM_SECONDS}s) \u2014 a previous connector likely crashed while writing it.
1472
+ `
1473
+ );
1474
+ }
1475
+ const sendHeartbeat = withConnectorAliveSignal(sendHeartbeatBase, {
1476
+ machineId,
1477
+ homeDir: deps.connectorHomeDir,
1478
+ noloHome: env.NOLO_HOME
1479
+ });
1480
+ try {
1481
+ const maxAttempts = deps.maxConnectorAttempts ?? Infinity;
1482
+ const sleep = deps.sleep ?? defaultSleep2;
1483
+ const reconnectDelayMs = resolveConnectorReconnectDelayMs(env);
1484
+ const toolGrants = readMachineToolGrants(env);
1485
+ const toolInvokeHandlers = createMachineToolInvokeHandlers({
1486
+ workspaceRoot: process.cwd(),
1487
+ // M2 修复:机器侧授权复核与握手上报共用同一来源——本次 `deps.env ??
1488
+ // process.env`。grants 快照与 WS URL 上报的那份逐字节相同;未配置时也
1489
+ // 显式传 env,工厂不再回落到 process.env(调用方经 deps.env 注入环境时
1490
+ // 两端曾不一致)。
1299
1491
  env,
1300
- output,
1301
- signal: deps.signal,
1302
- machine,
1303
- sendHeartbeat,
1304
- runHeartbeatLoop: deps.runHeartbeatLoop ?? runHeartbeatLoop,
1305
- connectWebSocket: deps.connectWebSocket ?? defaultConnectWebSocket,
1306
- serverUrl,
1307
- authToken,
1308
- resolveConnectorWebSocketTarget: async (options) => appendToolGrantsToWsUrl(
1309
- await resolveConnectorWebSocketTarget({
1310
- ...options,
1311
- fetchImpl
1312
- }),
1313
- toolGrants
1314
- ),
1315
- onMessage: (message, send) => handleMachineWsMessageDemux(
1316
- message,
1317
- send,
1318
- { toolInvoke: toolInvokeHandlers },
1319
- () => handleConnectorRunMessage(
1492
+ ...toolGrants ? { grants: toolGrants } : {}
1493
+ });
1494
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1495
+ if (deps.signal?.aborted) {
1496
+ return 0;
1497
+ }
1498
+ const runtimeServerUrl = env.NOLO_CONNECT_RUNTIME_SERVER_URL || env.NOLO_RUNTIME_SERVER_URL || serverUrl;
1499
+ const sessionResult = await runMachineWsSession({
1500
+ env,
1501
+ output,
1502
+ signal: deps.signal,
1503
+ machine,
1504
+ sendHeartbeat,
1505
+ runHeartbeatLoop: deps.runHeartbeatLoop ?? runHeartbeatLoop,
1506
+ connectWebSocket: deps.connectWebSocket ?? defaultConnectWebSocket,
1507
+ serverUrl,
1508
+ authToken,
1509
+ resolveConnectorWebSocketTarget: async (options) => appendToolGrantsToWsUrl(
1510
+ await resolveConnectorWebSocketTarget({
1511
+ ...options,
1512
+ fetchImpl
1513
+ }),
1514
+ toolGrants
1515
+ ),
1516
+ onMessage: (message, send) => handleMachineWsMessageDemux(
1320
1517
  message,
1321
1518
  send,
1322
- deps.executeCli ?? defaultExecuteCli,
1323
- {
1324
- ...env,
1325
- NOLO_SERVER: runtimeServerUrl,
1326
- NOLO_SERVER_URL: runtimeServerUrl,
1327
- BASE_URL: runtimeServerUrl,
1328
- AUTH_TOKEN: authToken,
1329
- NOLO_MACHINE_API_KEY: authToken
1330
- },
1331
- fetchImpl,
1332
- {
1333
- buildConnectorCliPrompt
1334
- }
1519
+ { toolInvoke: toolInvokeHandlers },
1520
+ () => handleConnectorRunMessage(
1521
+ message,
1522
+ send,
1523
+ deps.executeCli ?? defaultExecuteCli,
1524
+ {
1525
+ ...env,
1526
+ NOLO_SERVER: runtimeServerUrl,
1527
+ NOLO_SERVER_URL: runtimeServerUrl,
1528
+ BASE_URL: runtimeServerUrl,
1529
+ AUTH_TOKEN: authToken,
1530
+ NOLO_MACHINE_API_KEY: authToken
1531
+ },
1532
+ fetchImpl,
1533
+ {
1534
+ buildConnectorCliPrompt
1535
+ }
1536
+ )
1335
1537
  )
1336
- )
1337
- });
1338
- const exitCode = sessionResult.exitCode;
1339
- if (exitCode === 2) return 1;
1340
- if (deps.signal?.aborted) {
1341
- return 0;
1342
- }
1343
- if (attempt >= maxAttempts) return exitCode;
1344
- const retryDelayMs = Math.min(
1345
- sessionResult.retryAfterMs ?? reconnectDelayMs,
1346
- MAX_CONNECTOR_TIMER_DELAY_MS
1347
- );
1348
- const reconnectContext = sessionResult.reconnectReason === CORE_DRAIN_REASON ? " during core draining" : "";
1349
- output.write(
1350
- `[nolo] Connector websocket disconnected${reconnectContext}. Reconnecting in ${retryDelayMs}ms.
1538
+ });
1539
+ const exitCode = sessionResult.exitCode;
1540
+ if (exitCode === 2) return 1;
1541
+ if (deps.signal?.aborted) {
1542
+ return 0;
1543
+ }
1544
+ if (attempt >= maxAttempts) return exitCode;
1545
+ const retryDelayMs = Math.min(
1546
+ sessionResult.retryAfterMs ?? reconnectDelayMs,
1547
+ MAX_CONNECTOR_TIMER_DELAY_MS
1548
+ );
1549
+ const reconnectContext = sessionResult.reconnectReason === CORE_DRAIN_REASON ? " during core draining" : "";
1550
+ output.write(
1551
+ `[nolo] Connector websocket disconnected${reconnectContext}. Reconnecting in ${retryDelayMs}ms.
1351
1552
  `
1352
- );
1353
- await sleepWithAbort(retryDelayMs, sleep, deps.signal);
1553
+ );
1554
+ await sleepWithAbort(retryDelayMs, sleep, deps.signal);
1555
+ }
1556
+ return 0;
1557
+ } finally {
1558
+ instanceLock.release();
1559
+ clearConnectorAlive({
1560
+ machineId,
1561
+ homeDir: deps.connectorHomeDir,
1562
+ noloHome: env.NOLO_HOME
1563
+ });
1354
1564
  }
1355
- return 0;
1356
1565
  }
1357
1566
  if (hasFlag(args, "--watch")) {
1358
1567
  return runMachineWatchCommand({
1359
1568
  env,
1360
1569
  output,
1361
1570
  machine,
1362
- sendHeartbeat,
1571
+ sendHeartbeat: sendHeartbeatBase,
1363
1572
  runHeartbeatLoop: deps.runHeartbeatLoop ?? runHeartbeatLoop
1364
1573
  });
1365
1574
  }
1366
1575
  return runMachineHeartbeatConnectCommand({
1367
1576
  output,
1368
1577
  machine,
1369
- sendHeartbeat
1578
+ sendHeartbeat: sendHeartbeatBase
1370
1579
  });
1371
1580
  }
1372
1581
  export {