nolo-cli 0.180.0-alpha.1 → 0.181.0-alpha.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.
Files changed (72) hide show
  1. package/{_executeModel-ACI2KGWO.js → _executeModel-EJUKYBFV.js} +17 -17
  2. package/{agentDeleteCommand-LVSLONDD.js → agentDeleteCommand-OWDZDREL.js} +22 -21
  3. package/{agentEmailCommands-BKH3Q6I6.js → agentEmailCommands-RJGVOJXD.js} +22 -21
  4. package/{agentListCommands-JPKRBHMP.js → agentListCommands-GRJAY5ZE.js} +6 -5
  5. package/{agentMachineCommands-HRORJN73.js → agentMachineCommands-FNYZSZQ4.js} +3 -3
  6. package/{agentRecordCommands-OSZF2EHL.js → agentRecordCommands-EBDTISRS.js} +21 -20
  7. package/{agentRunCommand-FCGNSB4D.js → agentRunCommand-GIBVOCOK.js} +22 -21
  8. package/{agentRuntimeLocal-SWG5ULIJ.js → agentRuntimeLocal-AG7RCTM3.js} +2 -2
  9. package/{agentSlice-FCDRLXVA.js → agentSlice-LD6SEBSP.js} +1 -1
  10. package/{appDeleteCommands-YH3BYQYK.js → appDeleteCommands-DXF3IOFE.js} +7 -6
  11. package/{appDeployCommands-F44TNQJG.js → appDeployCommands-JVKZ3ATD.js} +7 -6
  12. package/{appDeployStatusCommands-BFTGFVQY.js → appDeployStatusCommands-VLGEMFV6.js} +7 -6
  13. package/{appGetCommands-SFLFIXPW.js → appGetCommands-JQN7RPBD.js} +7 -6
  14. package/{appListCommands-RUO5SEAJ.js → appListCommands-MFPVNCVT.js} +7 -6
  15. package/{authCommand-UIF5UTYV.js → authCommand-AX23Q2Q5.js} +6 -5
  16. package/{authCommands-BKDT4ZYB.js → authCommands-PIUTADO5.js} +2 -2
  17. package/{chromeOpsCommands-IWKUA4C3.js → chromeOpsCommands-2KFQEE3H.js} +24 -46
  18. package/{chunk-6MXCQLLB.js → chunk-53HMDUG6.js} +14 -583
  19. package/{chunk-OBKPOFOB.js → chunk-5AIXW7RW.js} +10 -10
  20. package/{chunk-HS7JBDUJ.js → chunk-5LK5OYUB.js} +3 -3
  21. package/{chunk-QUCQVMYZ.js → chunk-7O6YJN4K.js} +6 -6
  22. package/{chunk-4KDYRX3O.js → chunk-CFW7JLTH.js} +2 -2
  23. package/{chunk-PD4KNHVU.js → chunk-CJYL3N3T.js} +2 -2
  24. package/{chunk-NJI5HZQA.js → chunk-DS47C2AI.js} +2 -2
  25. package/{chunk-DVSXZC7F.js → chunk-EP2L3JUM.js} +18 -18
  26. package/{chunk-SGATEZV5.js → chunk-F55WKIWE.js} +18 -18
  27. package/chunk-G5LNJZJH.js +584 -0
  28. package/{chunk-H63VHVFY.js → chunk-HZG23JG6.js} +3 -3
  29. package/{chunk-PT6335JZ.js → chunk-I4ZVPTAI.js} +1 -1
  30. package/chunk-LKZPX6YH.js +0 -0
  31. package/{chunk-TCCBECTT.js → chunk-MAS5KGBB.js} +5 -5
  32. package/{chunk-HTSXDJHA.js → chunk-OC4NVQ5S.js} +12 -12
  33. package/{chunk-GHKEUJ6I.js → chunk-OES4RYIG.js} +2 -2
  34. package/{chunk-TDX2L4W2.js → chunk-PS3GU7YU.js} +1857 -1138
  35. package/{chunk-CGKN34IJ.js → chunk-QZRAVGW3.js} +33 -33
  36. package/{chunk-GCAOOTIS.js → chunk-SCDJOWJT.js} +2 -2
  37. package/{chunk-YAN7XTXO.js → chunk-THZHXIKZ.js} +2 -2
  38. package/{chunk-WQAOAJ5Q.js → chunk-UCE66MIJ.js} +1 -1
  39. package/{chunk-DTA4JVXW.js → chunk-UXJ7R3EP.js} +2 -2
  40. package/{chunk-QOKJFYJK.js → chunk-WQ4M3IYD.js} +9 -9
  41. package/{commandRegistry-A2RXELBX.js → commandRegistry-XRHKJCWS.js} +80 -65
  42. package/{contentThunks-GZUPF67H.js → contentThunks-FBGQIS5J.js} +6 -6
  43. package/{cooldownCommands-NS63RGBO.js → cooldownCommands-QFV3ERZX.js} +14 -28
  44. package/{createAgentAutomationAction-ZOFCBZRE.js → createAgentAutomationAction-ASIS3C6Q.js} +6 -6
  45. package/{createDialogAction-G7QCYQT7.js → createDialogAction-XJGVTBWO.js} +7 -7
  46. package/{createPageAction-Z6JQZYS5.js → createPageAction-2IWTNKUE.js} +8 -8
  47. package/{dialogCommands-MITLWDBY.js → dialogCommands-VKBNO7EI.js} +2 -2
  48. package/{dialogSlice-PKFXK5WM.js → dialogSlice-BE76QRR4.js} +4 -4
  49. package/{docCreateCommands-G3S3LZMZ.js → docCreateCommands-HFDVOS2K.js} +11 -38
  50. package/{docDeleteCommands-MLRCMLVB.js → docDeleteCommands-DMKHPD5R.js} +17 -37
  51. package/{docReadCommands-6A6SBCOP.js → docReadCommands-VAUSL2N7.js} +12 -28
  52. package/{docStore-NAEY4CEK.js → docStore-FOTFZMVS.js} +1 -1
  53. package/{docUpdateCommands-UZQC2NDO.js → docUpdateCommands-6QQUX2IN.js} +19 -73
  54. package/{handleSendMessageAction-HUQLERJP.js → handleSendMessageAction-U6DQUBLF.js} +10 -10
  55. package/index.js +17 -7
  56. package/{localRuntimeAdapter-4DJDFM7A.js → localRuntimeAdapter-DM7ERZ6Z.js} +20 -19
  57. package/{machineCommands-A3WO3FMB.js → machineCommands-OJPPEMNZ.js} +326 -113
  58. package/{machineWsRunDispatch-CIJDSHTA.js → machineWsRunDispatch-I5WWJ2EY.js} +21 -20
  59. package/package.json +1 -1
  60. package/{readlineWorkspace-YCNAWRDL.js → readlineWorkspace-RMG27VY3.js} +31 -29
  61. package/{runAgentClientLoop-K7WRMT27.js → runAgentClientLoop-QJL24CN5.js} +15 -15
  62. package/{secretCommands-7S6REMJV.js → secretCommands-XEQLKDB4.js} +33 -47
  63. package/{spaceCommands-L537SH5I.js → spaceCommands-Z325DDJU.js} +2 -2
  64. package/{streamAgentChatTurn-HKUZ7OCA.js → streamAgentChatTurn-4ZA46AEJ.js} +19 -19
  65. package/{tools-NS37KPGT.js → tools-GWQ4POVC.js} +12 -12
  66. package/{updateCommands-BXGFH4VX.js → updateCommands-KDYX32JR.js} +3 -1
  67. package/{updateDialogTitleAction-UUZ5HDM2.js → updateDialogTitleAction-VTE36JGF.js} +6 -6
  68. package/windowsSelfUpdate-IYQK4IDS.js +14 -0
  69. package/{agentPullCommand-UXXV5KKW.js → agentPullCommand-7LHBOEYH.js} +1 -1
  70. package/{chunk-COXNBVP7.js → chunk-FLX5EYLQ.js} +4 -4
  71. package/{chunk-RV4DPEAM.js → chunk-XLQ646HU.js} +3 -3
  72. package/{workloadLifecycle-MZ2V5L43.js → workloadLifecycle-2F5YKFGW.js} +7 -7
@@ -2,7 +2,7 @@ import {
2
2
  buildMachinePermissionPromptBlock,
3
3
  handleConnectorRunMessage,
4
4
  resolveMachineRunPermissionPolicy
5
- } from "./chunk-QUCQVMYZ.js";
5
+ } from "./chunk-7O6YJN4K.js";
6
6
  import {
7
7
  MAX_CONNECTOR_TIMER_DELAY_MS,
8
8
  clampConnectorTimerDelayMs,
@@ -11,10 +11,11 @@ import {
11
11
  resolveConnectorProbeTimeoutMs,
12
12
  resolveConnectorWebSocketTarget
13
13
  } from "./chunk-BLVSFYQ3.js";
14
- import "./chunk-SGATEZV5.js";
14
+ import "./chunk-F55WKIWE.js";
15
15
  import "./chunk-D2J22QSE.js";
16
16
  import "./chunk-R4BMKAR5.js";
17
- import "./chunk-TDX2L4W2.js";
17
+ import "./chunk-LKZPX6YH.js";
18
+ import "./chunk-GKKIZRJ6.js";
18
19
  import {
19
20
  parsePositiveFiniteNumberOrFallback
20
21
  } from "./chunk-Y5GCAMGP.js";
@@ -23,28 +24,30 @@ import "./chunk-CTEEJJVR.js";
23
24
  import "./chunk-NBTY7YJF.js";
24
25
  import "./chunk-VL5KOY2C.js";
25
26
  import {
26
- detectMachineInfo
27
+ detectMachineInfo,
28
+ resolveMachineId
27
29
  } from "./chunk-S73EPYNC.js";
28
30
  import "./chunk-ZEZC7IIM.js";
29
31
  import "./chunk-VIWDGTDA.js";
30
32
  import "./chunk-HGDKT2D6.js";
31
- 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-GCAOOTIS.js";
36
+ import "./chunk-SCDJOWJT.js";
36
37
  import "./chunk-HS7MLR74.js";
37
- import "./chunk-CGKN34IJ.js";
38
+ import "./chunk-QZRAVGW3.js";
38
39
  import "./chunk-2D3YKEAQ.js";
39
40
  import "./chunk-KFHREHUQ.js";
40
41
  import "./chunk-OXME3PEE.js";
41
- import "./chunk-NJI5HZQA.js";
42
+ import "./chunk-BAWAKW7X.js";
43
+ import "./chunk-DS47C2AI.js";
42
44
  import "./chunk-NF64D32W.js";
43
45
  import {
44
46
  CORE_DRAIN_REASON
45
47
  } from "./chunk-3L2WKC3W.js";
46
48
  import "./chunk-VYNMQ6HW.js";
47
49
  import "./chunk-MYOH447Z.js";
50
+ import "./chunk-IF7V3CBU.js";
48
51
  import "./chunk-3L5MSPWX.js";
49
52
  import "./chunk-TDNAAXUQ.js";
50
53
  import "./chunk-VIDPVFP3.js";
@@ -52,20 +55,19 @@ import "./chunk-KSTEXKL6.js";
52
55
  import "./chunk-AGIM7IMJ.js";
53
56
  import "./chunk-63W5N6P3.js";
54
57
  import "./chunk-VF4K2QE6.js";
55
- import "./chunk-4KDYRX3O.js";
56
- import "./chunk-BAWAKW7X.js";
57
- import "./chunk-PD4KNHVU.js";
58
- import "./chunk-IF7V3CBU.js";
58
+ import "./chunk-CFW7JLTH.js";
59
+ import "./chunk-CJYL3N3T.js";
60
+ import "./chunk-FDLDMJTM.js";
59
61
  import "./chunk-YPG7TUQD.js";
60
- import "./chunk-TCCBECTT.js";
61
- import "./chunk-HTSXDJHA.js";
62
- import "./chunk-OFFL4DCR.js";
62
+ import "./chunk-MAS5KGBB.js";
63
+ import "./chunk-ZJ7UHBJS.js";
64
+ import "./chunk-OC4NVQ5S.js";
63
65
  import "./chunk-523DITKR.js";
66
+ import "./chunk-OFFL4DCR.js";
67
+ import "./chunk-BYXBJQAS.js";
64
68
  import "./chunk-QYPE3TS3.js";
65
- import "./chunk-HS7JBDUJ.js";
69
+ import "./chunk-5LK5OYUB.js";
66
70
  import "./chunk-4JH2UMJR.js";
67
- import "./chunk-ZJ7UHBJS.js";
68
- import "./chunk-BYXBJQAS.js";
69
71
  import "./chunk-ESMZW5PE.js";
70
72
  import "./chunk-5RGU3J7P.js";
71
73
  import "./chunk-OWYYDMGJ.js";
@@ -99,14 +101,14 @@ import {
99
101
  PATH_FIELD_ALIASES,
100
102
  createLocalWorkspaceToolExecutors,
101
103
  resolveRequestedWorkspacePath
102
- } from "./chunk-COXNBVP7.js";
104
+ } from "./chunk-FLX5EYLQ.js";
103
105
  import "./chunk-KMSMIOOL.js";
104
106
  import "./chunk-ZNFJWEN4.js";
105
107
  import "./chunk-YO776XHS.js";
106
108
  import "./chunk-HNUWUO75.js";
109
+ import "./chunk-HIVDQPQR.js";
107
110
  import "./chunk-CHOZJMJS.js";
108
111
  import "./chunk-PWTNI4N7.js";
109
- import "./chunk-HIVDQPQR.js";
110
112
  import "./chunk-CR5WXQX7.js";
111
113
  import "./chunk-2WZXXAQD.js";
112
114
  import "./chunk-P2BGRAW7.js";
@@ -143,7 +145,6 @@ import "./chunk-U6VC4MU4.js";
143
145
  import "./chunk-CI5DVWU7.js";
144
146
  import "./chunk-PJNFNEJE.js";
145
147
  import "./chunk-PSCNXBYA.js";
146
- import "./chunk-FDLDMJTM.js";
147
148
  import "./chunk-GLKOYSRU.js";
148
149
  import "./chunk-EYLGAEB4.js";
149
150
  import {
@@ -155,6 +156,9 @@ import {
155
156
  import "./chunk-LCBAMY2M.js";
156
157
  import "./chunk-R5GO3YZP.js";
157
158
  import "./chunk-Y6ROFCZO.js";
159
+ import {
160
+ t
161
+ } from "./chunk-PS3GU7YU.js";
158
162
  import "./chunk-HLAL63BZ.js";
159
163
  import "./chunk-JOUFPRE4.js";
160
164
  import "./chunk-WWC7I6TA.js";
@@ -194,11 +198,169 @@ async function runHeartbeatLoop(options) {
194
198
  }
195
199
  }
196
200
 
201
+ // packages/cli/connectorInstanceLock.ts
202
+ import {
203
+ closeSync,
204
+ mkdirSync,
205
+ openSync,
206
+ readFileSync,
207
+ renameSync,
208
+ statSync,
209
+ unlinkSync,
210
+ writeFileSync
211
+ } from "node:fs";
212
+ import { homedir } from "node:os";
213
+ import { join } from "node:path";
214
+ function resolveConnectorStateDir(args = {}) {
215
+ const homeDir = args.homeDir?.trim();
216
+ if (homeDir) return join(homeDir, ".nolo", "connector");
217
+ const noloHome = args.noloHome?.trim() || process.env.NOLO_HOME?.trim();
218
+ if (noloHome) return join(noloHome, "connector");
219
+ return join(homedir(), ".nolo", "connector");
220
+ }
221
+ function sanitizeMachineId(machineId) {
222
+ const safe = String(machineId ?? "").replace(/[^A-Za-z0-9._-]/g, "_");
223
+ return safe || "unknown";
224
+ }
225
+ function connectorInstanceLockPath(args) {
226
+ return join(resolveConnectorStateDir(args), `${sanitizeMachineId(args.machineId)}.lock`);
227
+ }
228
+ function connectorAliveSignalPath(args) {
229
+ return join(resolveConnectorStateDir(args), `${sanitizeMachineId(args.machineId)}.alive`);
230
+ }
231
+ function isProcessAlive(pid) {
232
+ if (!Number.isInteger(pid) || pid <= 0) return false;
233
+ try {
234
+ process.kill(pid, 0);
235
+ return true;
236
+ } catch (error) {
237
+ return error?.code === "EPERM";
238
+ }
239
+ }
240
+ function readLockPid(lockPath) {
241
+ let raw;
242
+ try {
243
+ raw = readFileSync(lockPath, "utf8");
244
+ } catch {
245
+ return null;
246
+ }
247
+ try {
248
+ const parsed = JSON.parse(raw);
249
+ const pid = Number(parsed?.pid);
250
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+ function isEexist(error) {
256
+ return error?.code === "EEXIST";
257
+ }
258
+ var CONNECTOR_LOCK_CORRUPT_RECLAIM_SECONDS = 300;
259
+ function inspectCorruptLock(lockPath, now = Date.now()) {
260
+ try {
261
+ const stat2 = statSync(lockPath);
262
+ const ageMs = now - stat2.mtimeMs;
263
+ return ageMs > CONNECTOR_LOCK_CORRUPT_RECLAIM_SECONDS * 1e3 ? "expired" : "fresh";
264
+ } catch (error) {
265
+ const code = error?.code;
266
+ if (code === "ENOENT") return "gone";
267
+ return "fresh";
268
+ }
269
+ }
270
+ function acquireConnectorInstanceLock(args) {
271
+ const lockPath = connectorInstanceLockPath(args);
272
+ mkdirSync(resolveConnectorStateDir(args), { recursive: true });
273
+ let reclaimed;
274
+ for (let attempt = 0; attempt < 2; attempt += 1) {
275
+ let fd = null;
276
+ try {
277
+ fd = openSync(lockPath, "wx");
278
+ } catch (error) {
279
+ if (!isEexist(error)) throw error;
280
+ const ownerPid2 = readLockPid(lockPath);
281
+ if (ownerPid2 !== null) {
282
+ if (isProcessAlive(ownerPid2)) {
283
+ return { ok: false, pid: ownerPid2 };
284
+ }
285
+ try {
286
+ unlinkSync(lockPath);
287
+ } catch {
288
+ }
289
+ continue;
290
+ }
291
+ const state = inspectCorruptLock(lockPath);
292
+ if (state === "gone") {
293
+ continue;
294
+ }
295
+ if (state === "fresh") {
296
+ return { ok: false, pid: 0 };
297
+ }
298
+ try {
299
+ unlinkSync(lockPath);
300
+ } catch {
301
+ }
302
+ reclaimed = "corrupt";
303
+ continue;
304
+ }
305
+ try {
306
+ writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), "utf8");
307
+ } finally {
308
+ closeSync(fd);
309
+ }
310
+ return {
311
+ ok: true,
312
+ release: () => releaseConnectorInstanceLock(lockPath),
313
+ ...reclaimed ? { reclaimed } : {}
314
+ };
315
+ }
316
+ const ownerPid = readLockPid(lockPath);
317
+ return { ok: false, pid: ownerPid ?? 0 };
318
+ }
319
+ function releaseConnectorInstanceLock(lockPath) {
320
+ const ownerPid = readLockPid(lockPath);
321
+ if (ownerPid !== process.pid) return;
322
+ try {
323
+ unlinkSync(lockPath);
324
+ } catch {
325
+ }
326
+ }
327
+ function markConnectorAlive(args) {
328
+ const path = connectorAliveSignalPath(args);
329
+ try {
330
+ mkdirSync(resolveConnectorStateDir(args), { recursive: true });
331
+ const tmpPath = `${path}.${process.pid}.tmp`;
332
+ writeFileSync(tmpPath, String(Date.now()), "utf8");
333
+ try {
334
+ renameSync(tmpPath, path);
335
+ } catch (error) {
336
+ try {
337
+ unlinkSync(tmpPath);
338
+ } catch {
339
+ }
340
+ throw error;
341
+ }
342
+ } catch {
343
+ }
344
+ }
345
+ function clearConnectorAlive(args) {
346
+ try {
347
+ unlinkSync(connectorAliveSignalPath(args));
348
+ } catch {
349
+ }
350
+ }
351
+ function withConnectorAliveSignal(sendHeartbeat, args) {
352
+ return async () => {
353
+ const result = await sendHeartbeat();
354
+ markConnectorAlive(args);
355
+ return result;
356
+ };
357
+ }
358
+
197
359
  // packages/cli/machineDaemonCommands.ts
198
360
  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";
361
+ import { existsSync, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync2, readdirSync, realpathSync } from "node:fs";
362
+ import { homedir as homedir2 } from "node:os";
363
+ import { dirname, isAbsolute, join as join2, relative, resolve } from "node:path";
202
364
  import { fileURLToPath } from "node:url";
203
365
  var CONNECTOR_RUNTIME_WORKSPACE_PACKAGES = /* @__PURE__ */ new Set([
204
366
  "connector-experimental",
@@ -206,7 +368,7 @@ var CONNECTOR_RUNTIME_WORKSPACE_PACKAGES = /* @__PURE__ */ new Set([
206
368
  ]);
207
369
  function readJsonFile(path) {
208
370
  try {
209
- return JSON.parse(readFileSync(path, "utf8"));
371
+ return JSON.parse(readFileSync2(path, "utf8"));
210
372
  } catch {
211
373
  return null;
212
374
  }
@@ -226,7 +388,7 @@ function isInsideOrSame(path, parent) {
226
388
  function findWorkspaceRoot(startPath) {
227
389
  let current = resolve(startPath);
228
390
  while (true) {
229
- const packageJsonPath = join(current, "package.json");
391
+ const packageJsonPath = join2(current, "package.json");
230
392
  if (existsSync(packageJsonPath)) {
231
393
  const packageJson = readJsonFile(packageJsonPath);
232
394
  if (packageJson && getWorkspacePatterns(packageJson).length > 0) {
@@ -249,7 +411,7 @@ function listWorkspacePackageJsonFiles(workspaceParent) {
249
411
  for (const entry of entries) {
250
412
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
251
413
  const relativePath = `${entry.name}/package.json`;
252
- if (existsSync(join(workspaceParent, relativePath))) {
414
+ if (existsSync(join2(workspaceParent, relativePath))) {
253
415
  results.push(relativePath);
254
416
  }
255
417
  }
@@ -258,21 +420,21 @@ function listWorkspacePackageJsonFiles(workspaceParent) {
258
420
  function validateConnectorWorkspaceLinks(cwd) {
259
421
  const repoRoot = findWorkspaceRoot(cwd);
260
422
  if (!repoRoot) return [];
261
- const rootPackageJson = readJsonFile(join(repoRoot, "package.json"));
423
+ const rootPackageJson = readJsonFile(join2(repoRoot, "package.json"));
262
424
  if (!rootPackageJson) return [];
263
425
  const normalizedRepoRoot = normalizePath(realpathSync(repoRoot));
264
426
  const errors = [];
265
427
  for (const pattern of getWorkspacePatterns(rootPackageJson)) {
266
428
  if (!pattern.endsWith("/*")) continue;
267
- const workspaceParent = join(repoRoot, pattern.slice(0, -2));
429
+ const workspaceParent = join2(repoRoot, pattern.slice(0, -2));
268
430
  if (!existsSync(workspaceParent)) continue;
269
431
  for (const entry of listWorkspacePackageJsonFiles(workspaceParent)) {
270
- const packageDir = join(workspaceParent, dirname(entry));
271
- const packageJson = readJsonFile(join(workspaceParent, entry));
432
+ const packageDir = join2(workspaceParent, dirname(entry));
433
+ const packageJson = readJsonFile(join2(workspaceParent, entry));
272
434
  if (!packageJson?.name || !CONNECTOR_RUNTIME_WORKSPACE_PACKAGES.has(packageJson.name)) {
273
435
  continue;
274
436
  }
275
- const linkPath = join(repoRoot, "node_modules", ...packageJson.name.split("/"));
437
+ const linkPath = join2(repoRoot, "node_modules", ...packageJson.name.split("/"));
276
438
  if (!existsSync(linkPath)) {
277
439
  errors.push(`${linkPath} is missing. Run bun install in ${repoRoot}.`);
278
440
  continue;
@@ -308,7 +470,7 @@ async function checkConnectorWorkspaceLinks(cwd, output, validateWorkspaceLinks)
308
470
  return false;
309
471
  }
310
472
  function resolveDaemonLogPath(env) {
311
- return env.NOLO_CONNECT_LOG || join(homedir(), ".nolo", "logs", "connector.log");
473
+ return env.NOLO_CONNECT_LOG || join2(homedir2(), ".nolo", "logs", "connector.log");
312
474
  }
313
475
  function buildDaemonCommand(cliEntrypointPath) {
314
476
  const execPath = process.execPath;
@@ -319,8 +481,8 @@ function buildDaemonCommand(cliEntrypointPath) {
319
481
  return [execPath, entrypoint, "connect", "--ws"];
320
482
  }
321
483
  function defaultSpawnDaemon(args) {
322
- mkdirSync(dirname(args.logPath), { recursive: true });
323
- const out = openSync(args.logPath, "a");
484
+ mkdirSync2(dirname(args.logPath), { recursive: true });
485
+ const out = openSync2(args.logPath, "a");
324
486
  const env = Object.fromEntries(
325
487
  Object.entries(args.env).filter((entry) => typeof entry[1] === "string")
326
488
  );
@@ -340,6 +502,20 @@ async function runMachineDaemonCommand(deps = {}) {
340
502
  if (!await checkConnectorWorkspaceLinks(process.cwd(), output, validateWorkspaceLinks)) {
341
503
  return 1;
342
504
  }
505
+ const probe = acquireConnectorInstanceLock({
506
+ machineId: deps.machineId ?? resolveMachineId(),
507
+ homeDir: deps.connectorHomeDir,
508
+ noloHome: env.NOLO_HOME,
509
+ kind: "daemon"
510
+ });
511
+ if (!probe.ok) {
512
+ output.write(
513
+ `[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.
514
+ `
515
+ );
516
+ return 3;
517
+ }
518
+ probe.release();
343
519
  const logPath = resolveDaemonLogPath(env);
344
520
  const result = (deps.spawnDaemon ?? defaultSpawnDaemon)({
345
521
  cmd: buildDaemonCommand(deps.cliEntrypointPath),
@@ -400,8 +576,8 @@ async function runMachineWatchCommand(deps) {
400
576
 
401
577
  // packages/cli/machineToolInvokeDispatch.ts
402
578
  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";
579
+ import { homedir as homedir3 } from "node:os";
580
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve2, sep } from "node:path";
405
581
 
406
582
  // packages/agent-runtime/machineToolGrants.ts
407
583
  function isRecord2(value) {
@@ -464,8 +640,8 @@ function isMachineToolGranted(grants, toolName, agentKey) {
464
640
  }
465
641
  function resolveMachineToolAuditLogPath() {
466
642
  const noloHome = process.env.NOLO_HOME?.trim();
467
- const base = noloHome ? noloHome : join2(homedir2(), ".nolo");
468
- return join2(base, "logs", "machine-tool-audit.jsonl");
643
+ const base = noloHome ? noloHome : join3(homedir3(), ".nolo");
644
+ return join3(base, "logs", "machine-tool-audit.jsonl");
469
645
  }
470
646
  function auditTargetFor(executorArgs) {
471
647
  const target = extractMachineToolRequestPath(executorArgs) ?? "";
@@ -1099,7 +1275,8 @@ function resolveAuthToken2(env) {
1099
1275
  }
1100
1276
  function writeAuthMissing2(output) {
1101
1277
  output.write(
1102
- "[nolo] Machine commands require an auth token. Run `nolo login` or set AUTH_TOKEN.\n"
1278
+ `${t("cliMachineAuthMissing")}
1279
+ `
1103
1280
  );
1104
1281
  }
1105
1282
  function hasFlag(args, flag) {
@@ -1261,11 +1438,13 @@ async function runMachineConnectCommand(args, deps = {}) {
1261
1438
  output,
1262
1439
  cliEntrypointPath: deps.cliEntrypointPath,
1263
1440
  validateWorkspaceLinks: deps.validateWorkspaceLinks,
1264
- spawnDaemon: deps.spawnDaemon
1441
+ spawnDaemon: deps.spawnDaemon,
1442
+ connectorHomeDir: deps.connectorHomeDir
1265
1443
  });
1266
1444
  }
1267
1445
  const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
1268
- const sendHeartbeat = () => postMachineHeartbeat({
1446
+ const machineId = machine.machineId;
1447
+ const sendHeartbeatBase = () => postMachineHeartbeat({
1269
1448
  fetchImpl,
1270
1449
  serverUrl,
1271
1450
  authToken,
@@ -1277,96 +1456,130 @@ async function runMachineConnectCommand(args, deps = {}) {
1277
1456
  if (!await checkConnectorWorkspaceLinks(process.cwd(), output, validateWorkspaceLinks)) {
1278
1457
  return 1;
1279
1458
  }
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 } : {}
1459
+ const instanceLock = acquireConnectorInstanceLock({
1460
+ machineId,
1461
+ homeDir: deps.connectorHomeDir,
1462
+ noloHome: env.NOLO_HOME,
1463
+ kind: "ws"
1292
1464
  });
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({
1465
+ if (!instanceLock.ok) {
1466
+ const owner = instanceLock.pid > 0 ? `pid=${instanceLock.pid}` : "owner pid unknown";
1467
+ output.write(
1468
+ `[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.
1469
+ `
1470
+ );
1471
+ return 3;
1472
+ }
1473
+ if (instanceLock.reclaimed === "corrupt") {
1474
+ output.write(
1475
+ `[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.
1476
+ `
1477
+ );
1478
+ }
1479
+ const sendHeartbeat = withConnectorAliveSignal(sendHeartbeatBase, {
1480
+ machineId,
1481
+ homeDir: deps.connectorHomeDir,
1482
+ noloHome: env.NOLO_HOME
1483
+ });
1484
+ try {
1485
+ const maxAttempts = deps.maxConnectorAttempts ?? Infinity;
1486
+ const sleep = deps.sleep ?? defaultSleep2;
1487
+ const reconnectDelayMs = resolveConnectorReconnectDelayMs(env);
1488
+ const toolGrants = readMachineToolGrants(env);
1489
+ const toolInvokeHandlers = createMachineToolInvokeHandlers({
1490
+ workspaceRoot: process.cwd(),
1491
+ // M2 修复:机器侧授权复核与握手上报共用同一来源——本次 `deps.env ??
1492
+ // process.env`。grants 快照与 WS URL 上报的那份逐字节相同;未配置时也
1493
+ // 显式传 env,工厂不再回落到 process.env(调用方经 deps.env 注入环境时
1494
+ // 两端曾不一致)。
1299
1495
  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(
1496
+ ...toolGrants ? { grants: toolGrants } : {}
1497
+ });
1498
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
1499
+ if (deps.signal?.aborted) {
1500
+ return 0;
1501
+ }
1502
+ const runtimeServerUrl = env.NOLO_CONNECT_RUNTIME_SERVER_URL || env.NOLO_RUNTIME_SERVER_URL || serverUrl;
1503
+ const sessionResult = await runMachineWsSession({
1504
+ env,
1505
+ output,
1506
+ signal: deps.signal,
1507
+ machine,
1508
+ sendHeartbeat,
1509
+ runHeartbeatLoop: deps.runHeartbeatLoop ?? runHeartbeatLoop,
1510
+ connectWebSocket: deps.connectWebSocket ?? defaultConnectWebSocket,
1511
+ serverUrl,
1512
+ authToken,
1513
+ resolveConnectorWebSocketTarget: async (options) => appendToolGrantsToWsUrl(
1514
+ await resolveConnectorWebSocketTarget({
1515
+ ...options,
1516
+ fetchImpl
1517
+ }),
1518
+ toolGrants
1519
+ ),
1520
+ onMessage: (message, send) => handleMachineWsMessageDemux(
1320
1521
  message,
1321
1522
  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
- }
1523
+ { toolInvoke: toolInvokeHandlers },
1524
+ () => handleConnectorRunMessage(
1525
+ message,
1526
+ send,
1527
+ deps.executeCli ?? defaultExecuteCli,
1528
+ {
1529
+ ...env,
1530
+ NOLO_SERVER: runtimeServerUrl,
1531
+ NOLO_SERVER_URL: runtimeServerUrl,
1532
+ BASE_URL: runtimeServerUrl,
1533
+ AUTH_TOKEN: authToken,
1534
+ NOLO_MACHINE_API_KEY: authToken
1535
+ },
1536
+ fetchImpl,
1537
+ {
1538
+ buildConnectorCliPrompt
1539
+ }
1540
+ )
1335
1541
  )
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.
1542
+ });
1543
+ const exitCode = sessionResult.exitCode;
1544
+ if (exitCode === 2) return 1;
1545
+ if (deps.signal?.aborted) {
1546
+ return 0;
1547
+ }
1548
+ if (attempt >= maxAttempts) return exitCode;
1549
+ const retryDelayMs = Math.min(
1550
+ sessionResult.retryAfterMs ?? reconnectDelayMs,
1551
+ MAX_CONNECTOR_TIMER_DELAY_MS
1552
+ );
1553
+ const reconnectContext = sessionResult.reconnectReason === CORE_DRAIN_REASON ? " during core draining" : "";
1554
+ output.write(
1555
+ `[nolo] Connector websocket disconnected${reconnectContext}. Reconnecting in ${retryDelayMs}ms.
1351
1556
  `
1352
- );
1353
- await sleepWithAbort(retryDelayMs, sleep, deps.signal);
1557
+ );
1558
+ await sleepWithAbort(retryDelayMs, sleep, deps.signal);
1559
+ }
1560
+ return 0;
1561
+ } finally {
1562
+ instanceLock.release();
1563
+ clearConnectorAlive({
1564
+ machineId,
1565
+ homeDir: deps.connectorHomeDir,
1566
+ noloHome: env.NOLO_HOME
1567
+ });
1354
1568
  }
1355
- return 0;
1356
1569
  }
1357
1570
  if (hasFlag(args, "--watch")) {
1358
1571
  return runMachineWatchCommand({
1359
1572
  env,
1360
1573
  output,
1361
1574
  machine,
1362
- sendHeartbeat,
1575
+ sendHeartbeat: sendHeartbeatBase,
1363
1576
  runHeartbeatLoop: deps.runHeartbeatLoop ?? runHeartbeatLoop
1364
1577
  });
1365
1578
  }
1366
1579
  return runMachineHeartbeatConnectCommand({
1367
1580
  output,
1368
1581
  machine,
1369
- sendHeartbeat
1582
+ sendHeartbeat: sendHeartbeatBase
1370
1583
  });
1371
1584
  }
1372
1585
  export {