@nowcrew/daemon 0.6.98-beta.4 → 0.6.98-beta.5

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 (65) hide show
  1. package/dist/automatic-daemon-installer.js +10 -0
  2. package/dist/cloud-report-upload.js +10 -1
  3. package/dist/computer-setup-client.js +17 -2
  4. package/dist/computer-setup-existing.js +154 -0
  5. package/dist/computer-setup-orchestration.js +21 -6
  6. package/dist/computer-setup.js +1 -0
  7. package/dist/computer-token-setup.js +2 -2
  8. package/dist/execution-journal.js +1 -3
  9. package/dist/execution-protocol.js +1 -1
  10. package/dist/execution-runner.js +29 -20
  11. package/dist/external-output.js +12 -1
  12. package/dist/i18n.js +6 -0
  13. package/dist/local-execution-prompt.js +3 -8
  14. package/dist/local-executor.js +52 -69
  15. package/dist/local-memory/index-budget.js +3 -9
  16. package/dist/local-memory/inventory-cli.js +8 -52
  17. package/dist/local-memory/inventory-source.js +8 -15
  18. package/dist/local-memory/pending-query.js +2 -5
  19. package/dist/local-memory/pending-schema.js +1 -1
  20. package/dist/local-memory/pending.js +2 -16
  21. package/dist/local-memory/prompt.js +5 -21
  22. package/dist/local-memory/read-cli.js +76 -98
  23. package/dist/local-memory/rollout.js +11 -79
  24. package/dist/local-memory/runtime.js +13 -181
  25. package/dist/local-memory/write.js +21 -189
  26. package/dist/local-memory-diagnostics.js +13 -94
  27. package/dist/local-memory-telemetry.js +2 -19
  28. package/dist/machine-info.js +0 -3
  29. package/dist/marketplace-protocol.js +1 -1
  30. package/dist/marketplace-remote.js +1 -1
  31. package/dist/marketplace-runner.js +1 -1
  32. package/dist/memory-prune-diagnostics.js +4 -60
  33. package/dist/message-file-preview.js +1 -11
  34. package/dist/prompt.js +2 -6
  35. package/dist/runner.js +1 -1
  36. package/dist/runtimes/claude-thread-runner.js +4 -25
  37. package/dist/runtimes/pi.js +85 -17
  38. package/dist/serve.js +3 -38
  39. package/dist/token.js +0 -1
  40. package/dist/workspace-control-handler.js +3 -16
  41. package/dist/workspace.js +2 -13
  42. package/package.json +1 -1
  43. package/dist/local-memory/commit-telemetry.js +0 -30
  44. package/dist/local-memory/execution-workspace.js +0 -62
  45. package/dist/local-memory/full-index-search.js +0 -47
  46. package/dist/local-memory/initialization-identity.js +0 -36
  47. package/dist/local-memory/inventory-v2.js +0 -362
  48. package/dist/local-memory/legacy-adapter.js +0 -233
  49. package/dist/local-memory/migration-access.js +0 -242
  50. package/dist/local-memory/migration-cli.js +0 -54
  51. package/dist/local-memory/migration-io.js +0 -199
  52. package/dist/local-memory/migration-pending.js +0 -9
  53. package/dist/local-memory/migration-plan.js +0 -432
  54. package/dist/local-memory/migration-schema.js +0 -55
  55. package/dist/local-memory/migration-state-invariants.js +0 -65
  56. package/dist/local-memory/migration-store.js +0 -414
  57. package/dist/local-memory/migration-tool-entry.js +0 -21
  58. package/dist/local-memory/source-path.js +0 -13
  59. package/dist/local-memory/storage-format.js +0 -152
  60. package/dist/local-memory/writer-mode.js +0 -13
  61. package/dist/memory-migration-controller.js +0 -89
  62. package/dist/memory-migration-inspect.js +0 -108
  63. package/dist/memory-migration-journal.js +0 -14
  64. package/dist/memory-migration-protocol.js +0 -14
  65. package/dist/memory-migration-runtime.js +0 -305
@@ -17,6 +17,12 @@ import { discoverExactLegacyService, } from "./automatic-service-migration.js";
17
17
  import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
18
18
  import { serverHostIdentity } from "./server-url.js";
19
19
  import { computerSetupRecoveryGuidance } from "./computer-setup-client.js";
20
+ export const automaticDaemonInstallStageMessage = {
21
+ downloading: "Downloading the Daemon package...",
22
+ configuring: "Configuring the Daemon service...",
23
+ starting: "Starting the Daemon service...",
24
+ confirming: "Confirming the computer connection...",
25
+ };
20
26
  function profileStorage(platform, userHome, runner) {
21
27
  return {
22
28
  platform,
@@ -331,6 +337,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
331
337
  });
332
338
  let installFailure;
333
339
  try {
340
+ input.onProgress?.("downloading");
334
341
  await dependencies.installPackage(target.npmPrefix, dependencies.version, dependencies.platform, dependencies.runner, target.registryUrl);
335
342
  }
336
343
  catch (error) {
@@ -353,6 +360,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
353
360
  legacyRemovalStarted = true;
354
361
  await dependencies.removeLegacyService(legacyService);
355
362
  }
363
+ input.onProgress?.("configuring");
356
364
  profileSaved = true;
357
365
  await dependencies.saveTargetProfile({
358
366
  version: 1,
@@ -367,6 +375,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
367
375
  }, target.daemonHome);
368
376
  serviceReplacementStarted = true;
369
377
  await dependencies.replaceService(spec, target);
378
+ input.onProgress?.("starting");
370
379
  await dependencies.startService(spec);
371
380
  const status = await dependencies.verifyService(spec);
372
381
  if (!status.running)
@@ -380,6 +389,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
380
389
  daemonHome: target.daemonHome,
381
390
  agentsRoot: target.agentsRoot,
382
391
  };
392
+ input.onProgress?.("confirming");
383
393
  await input.verifyAfterStart?.(result);
384
394
  return result;
385
395
  }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir, hostname, platform } from "node:os";
3
+ import { join } from "node:path";
3
4
  const ALIYUN_MARKER = /alibaba|aliyun|alicloud/iu;
4
5
  function readMarker(path) {
5
6
  try {
@@ -28,7 +29,15 @@ export function detectCloudProvider(env = process.env, platformName = platform()
28
29
  }
29
30
  export function findOssUploadScript(env = process.env, exists = existsSync) {
30
31
  const explicit = env.CREW_OSS_UPLOAD_SCRIPT?.trim();
31
- return explicit && exists(explicit) ? explicit : undefined;
32
+ if (explicit)
33
+ return exists(explicit) ? explicit : undefined;
34
+ const home = env.HOME?.trim() || homedir();
35
+ const candidates = [
36
+ join(home, ".claude", "skills", "oss-upload", "scripts", "oss_upload.py"),
37
+ join(home, ".codex", "skills", "oss-upload", "scripts", "oss_upload.py"),
38
+ join(home, ".agents", "skills", "oss-upload", "scripts", "oss_upload.py"),
39
+ ];
40
+ return candidates.find(exists);
32
41
  }
33
42
  export function cloudReportUploadFacts(env = process.env, platformName = platform(), hostName = hostname(), readMarkerValue, exists) {
34
43
  const provider = detectCloudProvider(env, platformName, hostName, readMarkerValue);
@@ -52,6 +52,13 @@ const SetupTokenTargetSchema = SetupBootstrapSchema.omit({ setupId: true, machin
52
52
  credentialMatchesConnection: z.boolean(),
53
53
  }).strict(),
54
54
  }).strict();
55
+ const SetupReceiptSchema = z.object({
56
+ setupId: z.string().uuid(),
57
+ credentialState: z.enum(["pending", "finalized"]),
58
+ target: SetupTokenTargetSchema.omit({ connection: true }),
59
+ connection: SetupTokenTargetSchema.shape.connection,
60
+ connectionConfirmed: z.boolean(),
61
+ }).strict();
55
62
  const SetupPendingSchema = z.object({
56
63
  status: z.enum(["authorization_pending", "pending"]),
57
64
  intervalSeconds: z.number().int().positive().max(300).optional(),
@@ -89,7 +96,8 @@ export class ComputerSetupHttpError extends Error {
89
96
  }
90
97
  }
91
98
  export function redactSetupSecrets(value, secrets = []) {
92
- let redacted = value.replace(/\bsk_machine_[A-Za-z0-9_-]+\b/gu, "<redacted>");
99
+ let redacted = value.replace(/\bsk_machine_[A-Za-z0-9_-]+\b/gu, "<redacted>")
100
+ .replace(/(--setup-token(?:=|\s+))[A-Za-z0-9_-]+/gu, "$1<redacted>");
93
101
  for (const secret of secrets) {
94
102
  if (secret.length > 0)
95
103
  redacted = redacted.replaceAll(secret, "<redacted>");
@@ -251,7 +259,8 @@ export function createComputerSetupClient(options) {
251
259
  }));
252
260
  const now = options.now ?? Date.now;
253
261
  const request = async (method, path, body, bearer) => {
254
- const secretInputs = [bearer ?? "", body?.deviceCode ?? ""];
262
+ const secretInputs = [bearer ?? "", body?.deviceCode ?? "",
263
+ body?.setupToken ?? ""];
255
264
  const controller = new AbortController();
256
265
  const timeoutId = setTimeout(() => controller.abort(), requestTimeoutMs);
257
266
  try {
@@ -283,6 +292,12 @@ export function createComputerSetupClient(options) {
283
292
  }
284
293
  };
285
294
  return {
295
+ receipt: async ({ setupToken, machineToken }) => {
296
+ const receipt = SetupReceiptSchema.safeParse(await request("POST", "/daemon/computer-setup/receipt", { setupToken }, machineToken));
297
+ if (!receipt.success)
298
+ throw new Error("Invalid computer setup receipt response");
299
+ return receipt.data;
300
+ },
286
301
  tokenTarget: async (machineToken) => SetupTokenTargetSchema.parse(await request("GET", "/daemon/computer-setup/target", undefined, machineToken)),
287
302
  start: async (input) => {
288
303
  return parseStart(await request("POST", "/api/auth/computer-setup/start", {
@@ -0,0 +1,154 @@
1
+ import { access, readFile, readdir } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { posix, win32 } from "node:path";
4
+ import { resolveAutomaticInstallTarget } from "./automatic-install-target.js";
5
+ import { stableDaemonEntry } from "./automatic-daemon-installer.js";
6
+ import { resolveDaemonLocalHome } from "./daemon-local-home.js";
7
+ import { daemonGlobalInstallation } from "./daemon-installation.js";
8
+ import { isDaemonReleaseVersion } from "./daemon-release-version.js";
9
+ import { listProfiles, loadProfile, profileIsPrivate, profilePath } from "./computer-profile.js";
10
+ import { buildServiceSpec, captureExactService, systemCommandRunner, windowsProfileIsPrivate } from "./computer-service.js";
11
+ import { buildManagedServiceRecord } from "./managed-service-lifecycle.js";
12
+ import { managedServiceIdentityMatches, readManagedServiceRegistry } from "./managed-service-registry.js";
13
+ import { serverUrlsMatch } from "./server-url.js";
14
+ import { assertBootstrapMatches, localPathsMatch } from "./computer-setup-orchestration.js";
15
+ const storageKeys = ["NOWWORK_HOME", "NOWWORK_ENV_TAG", "NOWWORK_STORAGE_POLICY_PATH", "NOWWORK_LOCAL_HOME", "NOWWORK_LOCAL_HOME_MODE"];
16
+ const decodeXml = (value) => value.replaceAll("&quot;", '"').replaceAll("&apos;", "'")
17
+ .replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
18
+ // Only recover the generated environment fields; the rebuilt descriptor must still match byte-for-byte.
19
+ function descriptorEnvironment(descriptor, platform) {
20
+ return Object.fromEntries(storageKeys.flatMap((key) => {
21
+ const value = platform === "darwin"
22
+ ? new RegExp(`<key>${key}</key><string>([^<]*)</string>`, "u").exec(descriptor)?.[1]
23
+ : new RegExp(`^Environment="${key}=((?:[^"\\\\]|\\\\.)*)"$`, "mu").exec(descriptor)?.[1];
24
+ return value === undefined ? [] : [[key, platform === "darwin" ? decodeXml(value) : value.replace(/\\(.)/gu, "$1").replaceAll("%%", "%")]];
25
+ }));
26
+ }
27
+ async function windowsNodePath(serviceId, runner) {
28
+ const script = "$ErrorActionPreference='Stop';$n=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($args[0]));"
29
+ + "$t=@(Get-ScheduledTask -ErrorAction Stop|Where-Object{$_.TaskPath -eq '\\' -and $_.TaskName -ceq $n});"
30
+ + "if($t.Count -ne 1){throw 'scheduled task identity unknown'};$a=@($t[0].Actions);"
31
+ + "if($a.Count -ne 1){throw 'scheduled task action unknown'};$a[0].Execute|ConvertTo-Json -Compress;";
32
+ const result = await runner("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script,
33
+ Buffer.from(serviceId, "utf16le").toString("base64")]);
34
+ const path = result.exitCode === 0 ? JSON.parse(result.stdout) : null;
35
+ if (typeof path !== "string" || !win32.isAbsolute(path))
36
+ throw new Error("Scheduled task executable unavailable");
37
+ return path;
38
+ }
39
+ /** One-shot inspection only. Never consumes credentials or changes local/service state. */
40
+ export async function inspectExistingComputerSetup(input, client, options = {}) {
41
+ if (input.args.setupToken === undefined)
42
+ return { status: "unavailable" };
43
+ const platform = options.platform ?? process.platform;
44
+ const userHome = options.userHome ?? homedir();
45
+ const environment = options.environment ?? process.env;
46
+ const runner = options.runner ?? systemCommandRunner;
47
+ const paths = platform === "win32" ? win32 : posix;
48
+ try {
49
+ const requestedLocalHome = environment.NOWWORK_LOCAL_HOME;
50
+ const requestedLocalHomeMode = environment.NOWWORK_LOCAL_HOME_MODE ?? environment.NOWWORK_STORAGE_MODE;
51
+ const localOptions = {
52
+ ...(requestedLocalHome === undefined ? {} : { requestedLocalHome }),
53
+ ...(requestedLocalHomeMode === undefined ? {} : { requestedLocalHomeMode }),
54
+ };
55
+ const configuredHome = resolveDaemonLocalHome({ userHome, platform, profile: "in", mode: "nowwork",
56
+ ...(requestedLocalHome === undefined ? {} : { explicit: requestedLocalHome }) }).daemonHome;
57
+ const legacyRoot = paths.join(userHome, ".crew");
58
+ const legacyEntries = await readdir(legacyRoot, { withFileTypes: true }).catch((error) => {
59
+ if (error.code === "ENOENT")
60
+ return [];
61
+ throw error;
62
+ });
63
+ const homes = [...new Set([configuredHome, paths.join(userHome, ".nowwork", "config"),
64
+ ...legacyEntries.filter((entry) => entry.isDirectory() && /^daemon(?:-[a-z0-9_-]+)?$/u.test(entry.name))
65
+ .map((entry) => paths.join(legacyRoot, entry.name))])];
66
+ const candidates = [];
67
+ for (const home of homes) {
68
+ for (const name of await listProfiles(home)) {
69
+ const privateProfile = platform === "win32"
70
+ ? await windowsProfileIsPrivate(profilePath(name, home), runner)
71
+ : await profileIsPrivate(name, home, platform);
72
+ if (!privateProfile)
73
+ continue;
74
+ const profile = await loadProfile(name, home, { platform });
75
+ if (profile.name !== name || profile.machineId !== input.args.machineId || profile.workspaceId === undefined
76
+ || !serverUrlsMatch(profile.serverUrl, input.serverUrl)
77
+ || (input.args.workspace.kind === "id" && profile.workspaceId !== input.args.workspace.value))
78
+ continue;
79
+ const target = resolveAutomaticInstallTarget({ serverUrl: input.serverUrl, requestedProfile: name,
80
+ userHome, platform, ...localOptions });
81
+ if (!localPathsMatch(target.daemonHome, home, platform))
82
+ continue;
83
+ if (profile.agentsRoot === undefined || !localPathsMatch(profile.agentsRoot, target.agentsRoot, platform))
84
+ continue;
85
+ candidates.push({ profile, target });
86
+ }
87
+ }
88
+ if (candidates.length !== 1)
89
+ return { status: "unavailable" };
90
+ const { profile, target } = candidates[0];
91
+ const receipt = await client.receipt({ setupToken: input.args.setupToken, machineToken: profile.machineToken });
92
+ assertBootstrapMatches(receipt.target, input);
93
+ if (receipt.target.workspaceId !== profile.workspaceId || receipt.target.profile !== profile.name)
94
+ return { status: "unavailable" };
95
+ if (receipt.credentialState === "pending")
96
+ return { status: "pending" };
97
+ const connection = receipt.connection;
98
+ if (receipt.credentialState !== "finalized" || !receipt.connectionConfirmed || connection.status !== "online"
99
+ || !connection.credentialMatchesConnection || connection.epoch <= receipt.target.minimumConnectionEpoch
100
+ || connection.profile !== profile.name || connection.daemonHome === null || connection.agentsRoot === null
101
+ || !localPathsMatch(connection.daemonHome, target.daemonHome, platform)
102
+ || !localPathsMatch(connection.agentsRoot, target.agentsRoot, platform)
103
+ || (receipt.target.storageId !== undefined && receipt.target.storageId !== connection.storageId))
104
+ return { status: "unavailable" };
105
+ const localHome = paths.resolve(target.npmPrefix, "..", "..");
106
+ const entryPath = stableDaemonEntry(target, platform);
107
+ const installation = daemonGlobalInstallation(entryPath, platform);
108
+ if (installation === null)
109
+ return { status: "unavailable" };
110
+ await access(entryPath);
111
+ const packageJson = JSON.parse(await readFile(paths.join(installation.packageRoot, "package.json"), "utf8"));
112
+ if (!packageJson || typeof packageJson !== "object" || !("name" in packageJson) || packageJson.name !== "@nowcrew/daemon"
113
+ || !("version" in packageJson) || typeof packageJson.version !== "string" || !isDaemonReleaseVersion(packageJson.version))
114
+ return { status: "unavailable" };
115
+ const base = { platform, profile: profile.name, profileHome: target.daemonHome, userHome,
116
+ uid: options.uid ?? process.getuid?.(), entryPath };
117
+ let spec;
118
+ if (platform === "win32") {
119
+ const identity = buildServiceSpec({ ...base, nodePath: process.execPath, environment: {} });
120
+ const nodePath = await windowsNodePath(identity.id, runner);
121
+ await access(nodePath);
122
+ spec = buildServiceSpec({ ...base, nodePath, environment: {} });
123
+ }
124
+ else {
125
+ const records = (await readManagedServiceRegistry({ userHome, localHome })).filter((record) => record.profile === profile.name
126
+ && localPathsMatch(record.daemonHome, target.daemonHome, platform));
127
+ if (records.length !== 1)
128
+ return { status: "unavailable" };
129
+ const record = records[0];
130
+ await access(record.nodePath);
131
+ const descriptor = await readFile(record.descriptorPath, "utf8");
132
+ spec = buildServiceSpec({ ...base, nodePath: record.nodePath, entryPath: record.entryPath,
133
+ environment: descriptorEnvironment(descriptor, platform) });
134
+ const expected = buildManagedServiceRecord({ spec, daemonHome: target.daemonHome, agentsRoot: target.agentsRoot,
135
+ nodePath: record.nodePath, entryPath, ...installation });
136
+ if (!managedServiceIdentityMatches(record, expected))
137
+ return { status: "unavailable" };
138
+ }
139
+ const service = await captureExactService(spec, runner);
140
+ if (!service.installed || !service.exact || !service.running)
141
+ return { status: "unavailable" };
142
+ return { status: "connected", result: {
143
+ workspaceId: receipt.target.workspaceId, workspaceSlug: receipt.target.workspaceSlug, machineId: receipt.target.machineId,
144
+ environment: receipt.target.environment, profile: profile.name, daemonVersion: packageJson.version,
145
+ serviceId: spec.id, serviceStatus: "running", helloConfirmed: true, localNowworkHome: localHome,
146
+ daemonHome: target.daemonHome, agentsRoot: target.agentsRoot,
147
+ ...(connection.storageId === undefined ? {} : { storageId: connection.storageId }),
148
+ } };
149
+ }
150
+ catch {
151
+ // Failed inspection is not proof of installation, connection, or credential ownership.
152
+ return { status: "unavailable" };
153
+ }
154
+ }
@@ -1,10 +1,12 @@
1
1
  import { computerSetupRecoveryGuidance, redactSetupSecrets, } from "./computer-setup-client.js";
2
+ import { automaticDaemonInstallStageMessage } from "./automatic-daemon-installer.js";
3
+ import { inspectExistingComputerSetup } from "./computer-setup-existing.js";
2
4
  import { isProductionServerHostname, serverUrlsMatch } from "./server-url.js";
3
5
  import { posix, win32 } from "node:path";
4
6
  import { resolveAgentsRoot } from "./computer-profile.js";
5
- function safeError(error, machineToken) {
6
- const message = redactSetupSecrets(error instanceof Error ? error.message : String(error), machineToken === undefined ? [] : [machineToken]);
7
- return new Error(message, error instanceof Error ? { cause: error } : undefined);
7
+ function safeError(error, machineToken, setupToken) {
8
+ const message = redactSetupSecrets(error instanceof Error ? error.message : String(error), [machineToken ?? "", setupToken ?? ""]);
9
+ return new Error(message);
8
10
  }
9
11
  export function assertBootstrapMatches(bootstrap, input) {
10
12
  if (bootstrap.machineId !== input.args.machineId) {
@@ -55,6 +57,18 @@ async function obtainBootstrap(input, deps) {
55
57
  deps.reportPairing("Checking computer connection information...");
56
58
  const result = await deps.client.poll(input.args.setupToken);
57
59
  if (result.status !== "authorized") {
60
+ deps.reportPairing("Checking the existing computer connection...");
61
+ const inspection = await (deps.inspectExistingSetup ?? ((candidate, client) => inspectExistingComputerSetup(candidate, client, {
62
+ ...(deps.platform === undefined ? {} : { platform: deps.platform }),
63
+ ...(deps.environment === undefined ? {} : { environment: deps.environment }),
64
+ })))(input, deps.client);
65
+ if (inspection.status === "connected") {
66
+ deps.reportPairing("This computer is already connected. No installation changes are needed.");
67
+ return inspection.result;
68
+ }
69
+ if (inspection.status === "pending") {
70
+ throw new Error("Setup is still in progress; check the original terminal and wait for it to finish");
71
+ }
58
72
  throw new Error("Setup command expired or unavailable; regenerate it in NowWork and run the new command");
59
73
  }
60
74
  return result.bootstrap;
@@ -120,12 +134,14 @@ export async function runComputerSetupOrchestration(input, deps) {
120
134
  };
121
135
  try {
122
136
  const authorized = await obtainBootstrap(input, deps);
137
+ if ("helloConfirmed" in authorized)
138
+ return authorized;
123
139
  const token = authorized.machineToken;
124
140
  bootstrap = authorized;
125
141
  machineToken = token;
126
142
  assertBootstrapMatches(authorized, input);
127
- deps.reportPairing("Downloading, installing, and starting the Daemon service...");
128
143
  const installation = await deps.installAutomatically({
144
+ onProgress: (stage) => deps.reportPairing(automaticDaemonInstallStageMessage[stage]),
129
145
  serverUrl: authorized.serverUrl,
130
146
  machineToken: token,
131
147
  workspaceId: authorized.workspaceId,
@@ -137,7 +153,6 @@ export async function runComputerSetupOrchestration(input, deps) {
137
153
  ...(authorized.registryUrl === undefined ? {} : { requestedRegistryUrl: authorized.registryUrl }),
138
154
  beforeRollback: confirmRollback,
139
155
  verifyAfterStart: async (candidate) => {
140
- deps.reportPairing("Confirming the computer connection...");
141
156
  const hello = await deps.client.waitForHello({
142
157
  machineToken: token,
143
158
  machineId: authorized.machineId,
@@ -209,6 +224,6 @@ export async function runComputerSetupOrchestration(input, deps) {
209
224
  + `local recovery blocked. ${computerSetupRecoveryGuidance(bootstrap)}`, { cause: error });
210
225
  }
211
226
  }
212
- throw safeError(error, machineToken);
227
+ throw safeError(error, machineToken, input.args.setupToken);
213
228
  }
214
229
  }
@@ -258,6 +258,7 @@ export async function runComputerSetupCommand(argv, overrides = {}) {
258
258
  reportPairing: (message) => deps[args.json === true ? "stderr" : "stdout"](`${localizedPairingMessage(lang, message)}\n`),
259
259
  now: deps.now,
260
260
  platform: deps.platform,
261
+ environment: deps.env,
261
262
  };
262
263
  const result = args.machineToken === undefined
263
264
  ? await runComputerSetupOrchestration({ args, serverUrl }, orchestrationDeps)
@@ -1,3 +1,4 @@
1
+ import { automaticDaemonInstallStageMessage } from "./automatic-daemon-installer.js";
1
2
  import { assertBootstrapMatches, localPathsMatch } from "./computer-setup-orchestration.js";
2
3
  import { redactSetupSecrets } from "./computer-setup-client.js";
3
4
  export async function runMachineTokenSetup(input, deps, machineToken) {
@@ -5,14 +6,13 @@ export async function runMachineTokenSetup(input, deps, machineToken) {
5
6
  deps.reportPairing("Checking computer connection information...");
6
7
  const target = await deps.client.tokenTarget(machineToken);
7
8
  assertBootstrapMatches(target, input);
8
- deps.reportPairing("Downloading, installing, and starting the Daemon service...");
9
9
  const installation = await deps.installAutomatically({
10
+ onProgress: (stage) => deps.reportPairing(automaticDaemonInstallStageMessage[stage]),
10
11
  serverUrl: target.serverUrl, machineToken, workspaceId: target.workspaceId, machineId: target.machineId,
11
12
  requestedProfile: target.profile,
12
13
  ...(target.releaseTag === undefined ? {} : { requestedReleaseTag: target.releaseTag }),
13
14
  ...(target.registryUrl === undefined ? {} : { requestedRegistryUrl: target.registryUrl }),
14
15
  verifyAfterStart: async (candidate) => {
15
- deps.reportPairing("Confirming the computer connection...");
16
16
  const deadline = deps.now() + 120_000;
17
17
  for (;;) {
18
18
  const current = await deps.client.tokenTarget(machineToken);
@@ -8,7 +8,7 @@ import { provePosixProcessGroupStopped } from "./execution-posix-stop-proof.js";
8
8
  export { JournalLockedError, JournalLockCorruptionError } from "./execution-journal-lock.js";
9
9
  const ExecutionIdSchema = z.string().uuid();
10
10
  const TimestampSchema = z.string().datetime({ offset: true });
11
- const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "memory_migration", "pi"]);
11
+ const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi"]);
12
12
  const RUNTIME_READY_SUFFIX = ".runtime-ready";
13
13
  const RawJournalEntrySchema = z.object({
14
14
  executionId: ExecutionIdSchema,
@@ -523,8 +523,6 @@ export function createExecutionJournal(agentsRoot, options = {}) {
523
523
  }
524
524
  };
525
525
  const pruneInternal = async () => {
526
- if (options.retainCompleted)
527
- return;
528
526
  const protectedExecutionIds = new Set(options.protectedExecutionIds?.() ?? []);
529
527
  const entries = await readAll();
530
528
  const acknowledged = entries
@@ -319,7 +319,7 @@ const RawExecutionCompletedSchema = z.object({
319
319
  providerErrorCategory: ProviderErrorCategorySchema.optional(),
320
320
  providerHttpStatus: ProviderHttpStatusSchema.optional(),
321
321
  providerErrorCode: ProviderErrorCodeSchema.optional(),
322
- runtime: z.union([RuntimeSchema, z.literal("memory_migration")]),
322
+ runtime: RuntimeSchema,
323
323
  model: z.string().optional(),
324
324
  resumed: z.boolean(),
325
325
  finalText: z.string().optional(),
@@ -1,7 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { isLocalMemoryV2Enabled, localMemoryV2Environment, resolveReposRoot } from "./config.js";
4
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
5
4
  import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
6
5
  import { JournalConflictError } from "./execution-journal.js";
7
6
  import { boundExecutionFrame } from "./execution-event-limit.js";
@@ -588,6 +587,26 @@ export async function runExecution(config, input, dependencies) {
588
587
  let activitySequence = 0;
589
588
  let consoleSequence = 0;
590
589
  let externalOutputSequence = 0;
590
+ const bufferedPiExternalOutput = [];
591
+ const reportExternalOutput = (text) => {
592
+ if (firstOutputTimer !== undefined) {
593
+ clearTimeout(firstOutputTimer);
594
+ firstOutputTimer = undefined;
595
+ }
596
+ const frame = DaemonToServerExecutionFrameSchema.parse({
597
+ type: "execution:output",
598
+ protocolVersion: 1,
599
+ executionId: spec.executionId,
600
+ channel: "external_answer",
601
+ text,
602
+ seq: externalOutputSequence++,
603
+ at: now().toISOString(),
604
+ });
605
+ try {
606
+ telemetry.enqueue(boundExecutionFrame(frame, config.executionLimits.maxEventBytes));
607
+ }
608
+ catch { /* final completion remains the authoritative repair */ }
609
+ };
591
610
  const callbacks = {
592
611
  onRuntimeStarting: () => {
593
612
  dependencies.onRuntimePhase?.("starting", spec.runtime.name);
@@ -712,23 +731,11 @@ export async function runExecution(config, input, dependencies) {
712
731
  } : {}),
713
732
  ...(spec.context.externalResponseSessionId || spec.context.answerStream ? {
714
733
  onExternalOutput: (text) => {
715
- if (firstOutputTimer !== undefined) {
716
- clearTimeout(firstOutputTimer);
717
- firstOutputTimer = undefined;
718
- }
719
- const frame = DaemonToServerExecutionFrameSchema.parse({
720
- type: "execution:output",
721
- protocolVersion: 1,
722
- executionId: spec.executionId,
723
- channel: "external_answer",
724
- text,
725
- seq: externalOutputSequence++,
726
- at: now().toISOString(),
727
- });
728
- try {
729
- telemetry.enqueue(boundExecutionFrame(frame, config.executionLimits.maxEventBytes));
734
+ if (spec.runtime.name === "pi") {
735
+ bufferedPiExternalOutput.push(text);
736
+ return;
730
737
  }
731
- catch { /* final completion remains the authoritative repair */ }
738
+ reportExternalOutput(text);
732
739
  },
733
740
  } : {}),
734
741
  };
@@ -839,9 +846,8 @@ export async function runExecution(config, input, dependencies) {
839
846
  const localMemoryGuardInBase = localMemoryV2Environment(config.serverUrl) === "custom";
840
847
  const boundedSystemPrompt = (context) => {
841
848
  const enabled = context.workspace.localMemoryV2Enabled ?? localMemoryV2Enabled;
842
- const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase && detectLocalMemoryStorageVersion(context.workspace.memory) !== 2 ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
849
+ const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
843
850
  ...(selfTraining ? { isolateLocalMemory: true } : {}),
844
- memoryQuery: spec.instructions.wakePrompt,
845
851
  collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
846
852
  });
847
853
  const base = typeof systemPromptWithLocalFacts === "string"
@@ -881,7 +887,6 @@ export async function runExecution(config, input, dependencies) {
881
887
  serverUrl: config.serverUrl,
882
888
  token: credential.token,
883
889
  agentId: credential.agentId,
884
- ...(dependencies.controlIdentity === undefined ? {} : dependencies.controlIdentity),
885
890
  agentsRoot: config.agentsRoot,
886
891
  reposRoot: resolveReposRoot(config.agentsRoot, config.reposRoot),
887
892
  ...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
@@ -912,6 +917,10 @@ export async function runExecution(config, input, dependencies) {
912
917
  clearTimeout(timeout);
913
918
  if (firstOutputTimer !== undefined)
914
919
  clearTimeout(firstOutputTimer);
920
+ if (spec.runtime.name === "pi" && result.exitCode === 0 && result.externalAnswer !== null) {
921
+ for (const text of bufferedPiExternalOutput)
922
+ reportExternalOutput(text);
923
+ }
915
924
  const finishedAt = now().toISOString();
916
925
  if (result.exitCode === 0 && result.finalText?.trim())
917
926
  memoryCaptureFinalText = result.finalText;
@@ -110,6 +110,7 @@ export class ExternalAnswerDecoder {
110
110
  pending = "";
111
111
  block = "";
112
112
  closed = null;
113
+ rejected = false;
113
114
  push(text) {
114
115
  if (!text)
115
116
  return [];
@@ -125,7 +126,7 @@ export class ExternalAnswerDecoder {
125
126
  }
126
127
  /** 整轮里最后一个完整闭合的标签块(已 trim);没有闭合过任何块则为 null。 */
127
128
  get lastClosedBlock() {
128
- return this.closed;
129
+ return this.rejected ? null : this.closed;
129
130
  }
130
131
  consume(value, output) {
131
132
  this.pending = "";
@@ -154,12 +155,21 @@ export class ExternalAnswerDecoder {
154
155
  this.block = "";
155
156
  this.state = "inside";
156
157
  }
158
+ else if (!tag.closing && this.state === "inside") {
159
+ // 嵌套答案标记说明模型正在讨论协议或混入了思考文本;
160
+ // 不要把内层短片段误当成最终答案。
161
+ this.rejected = true;
162
+ this.block = "";
163
+ this.state = "outside";
164
+ }
157
165
  else {
166
+ this.rejected = true;
158
167
  this.block = "";
159
168
  this.state = tag.closing ? "outside" : "inside";
160
169
  }
161
170
  }
162
171
  else if (tag.kind === "suspicious" || tag.kind === "known") {
172
+ this.rejected = true;
163
173
  this.block = "";
164
174
  this.state = "outside";
165
175
  }
@@ -171,6 +181,7 @@ export class ExternalAnswerDecoder {
171
181
  else if (this.pending.length > MAX_MARKER_TAG_LENGTH + 2) {
172
182
  const tag = classifyMarkerTag(this.pending.slice(1));
173
183
  if (tag.kind === "suspicious" || tag.kind === "known") {
184
+ this.rejected = true;
174
185
  this.block = "";
175
186
  this.state = "outside";
176
187
  }
package/dist/i18n.js CHANGED
@@ -38,6 +38,12 @@ const zh = {
38
38
  "Starting computer setup pairing...": "正在发起电脑接入授权…",
39
39
  "Token setup does not require browser authorization. CREW_MACHINE_TOKEN is also supported.": "Token 接入无需浏览器授权,也支持 CREW_MACHINE_TOKEN。",
40
40
  "Checking computer connection information...": "正在检查接入信息…",
41
+ "Checking the existing computer connection...": "正在检查已有电脑连接…",
42
+ "Downloading the Daemon package...": "正在下载 Daemon 安装包…",
43
+ "Configuring the Daemon service...": "正在配置 Daemon 服务…",
44
+ "Starting the Daemon service...": "正在启动 Daemon 服务…",
45
+ "This computer is already connected. No installation changes are needed.": "此电脑已经连接,无需修改安装。",
46
+ "Setup is still in progress; check the original terminal and wait for it to finish": "接入仍在进行中,请查看原来的终端并等待接入完成。",
41
47
  "Downloading, installing, and starting the Daemon service...": "正在下载、安装并启动 Daemon 服务…",
42
48
  "Confirming the computer connection...": "正在确认电脑连接…",
43
49
  "Connected to {{workspace}}.": "已接入 {{workspace}}。",
@@ -2,9 +2,8 @@
2
2
  * 本机执行提示词的合成与预算裁剪。
3
3
  *
4
4
  * server 提示词是权威部分,绝不裁剪;项目工作区提示词是绑定执行的强制部分,放不下就直接失败;
5
- * 本机事实与旧格式预览可按预算省略;schema2身份和置顶规则必须保留,放不下则拒绝执行。
5
+ * 只有本机事实(CREW_HOME / work-log / MEMORY 注入)是可选增强,按整段丢弃或按字节截断。
6
6
  */
7
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
8
7
  import { buildCollaborationToolsPrompt, buildRuntimeWorkspacePrompt, capMemoryForInject, capWorkLogForInject, } from "./prompt.js";
9
8
  const trainingProjectJson = (project) => JSON.stringify({
10
9
  repositoryKey: project.repositoryKey,
@@ -58,14 +57,10 @@ export function withLocalExecutionFacts(serverPrompt, maxBytes, options = {}) {
58
57
  }
59
58
  const remaining = maxBytes - requiredBytes - Math.max(0, reservedPromptBytes);
60
59
  const factsBytes = Buffer.byteLength(localFacts, "utf8");
61
- if (factsBytes > remaining) {
62
- if (!options.isolateLocalMemory && detectLocalMemoryStorageVersion(workspace.memory) === 2) {
63
- throw new Error("local_memory_identity_budget_exceeded");
64
- }
60
+ if (factsBytes > remaining)
65
61
  return `${serverPrompt}${collaborationPrompt}${mandatoryWorkspacePrompt}`;
66
- }
67
62
  const memoryHeading = "\n\n## Injected MEMORY.md (bounded local context)\n";
68
- const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory, undefined, remaining - factsBytes - Buffer.byteLength(memoryHeading, "utf8"), options.memoryQuery);
63
+ const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory, undefined, remaining - factsBytes - Buffer.byteLength(memoryHeading, "utf8"));
69
64
  const memorySection = memory === "" ? "" : memoryHeading + memory;
70
65
  const workLogSection = workLog === "" ? "" : `\n\n## Injected work-log (bounded local context)\n${workLog}`;
71
66
  const workLogBudget = remaining - factsBytes - Buffer.byteLength(memorySection, "utf8");