@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.
- package/dist/automatic-daemon-installer.js +10 -0
- package/dist/cloud-report-upload.js +10 -1
- package/dist/computer-setup-client.js +17 -2
- package/dist/computer-setup-existing.js +154 -0
- package/dist/computer-setup-orchestration.js +21 -6
- package/dist/computer-setup.js +1 -0
- package/dist/computer-token-setup.js +2 -2
- package/dist/execution-journal.js +1 -3
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +29 -20
- package/dist/external-output.js +12 -1
- package/dist/i18n.js +6 -0
- package/dist/local-execution-prompt.js +3 -8
- package/dist/local-executor.js +52 -69
- package/dist/local-memory/index-budget.js +3 -9
- package/dist/local-memory/inventory-cli.js +8 -52
- package/dist/local-memory/inventory-source.js +8 -15
- package/dist/local-memory/pending-query.js +2 -5
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +2 -16
- package/dist/local-memory/prompt.js +5 -21
- package/dist/local-memory/read-cli.js +76 -98
- package/dist/local-memory/rollout.js +11 -79
- package/dist/local-memory/runtime.js +13 -181
- package/dist/local-memory/write.js +21 -189
- package/dist/local-memory-diagnostics.js +13 -94
- package/dist/local-memory-telemetry.js +2 -19
- package/dist/machine-info.js +0 -3
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/memory-prune-diagnostics.js +4 -60
- package/dist/message-file-preview.js +1 -11
- package/dist/prompt.js +2 -6
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +4 -25
- package/dist/runtimes/pi.js +85 -17
- package/dist/serve.js +3 -38
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +3 -16
- package/dist/workspace.js +2 -13
- package/package.json +1 -1
- package/dist/local-memory/commit-telemetry.js +0 -30
- package/dist/local-memory/execution-workspace.js +0 -62
- package/dist/local-memory/full-index-search.js +0 -47
- package/dist/local-memory/initialization-identity.js +0 -36
- package/dist/local-memory/inventory-v2.js +0 -362
- package/dist/local-memory/legacy-adapter.js +0 -233
- package/dist/local-memory/migration-access.js +0 -242
- package/dist/local-memory/migration-cli.js +0 -54
- package/dist/local-memory/migration-io.js +0 -199
- package/dist/local-memory/migration-pending.js +0 -9
- package/dist/local-memory/migration-plan.js +0 -432
- package/dist/local-memory/migration-schema.js +0 -55
- package/dist/local-memory/migration-state-invariants.js +0 -65
- package/dist/local-memory/migration-store.js +0 -414
- package/dist/local-memory/migration-tool-entry.js +0 -21
- package/dist/local-memory/source-path.js +0 -13
- package/dist/local-memory/storage-format.js +0 -152
- package/dist/local-memory/writer-mode.js +0 -13
- package/dist/memory-migration-controller.js +0 -89
- package/dist/memory-migration-inspect.js +0 -108
- package/dist/memory-migration-journal.js +0 -14
- package/dist/memory-migration-protocol.js +0 -14
- 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
|
-
|
|
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(""", '"').replaceAll("'", "'")
|
|
17
|
+
.replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
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
|
|
7
|
-
return new Error(message
|
|
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
|
}
|
package/dist/computer-setup.js
CHANGED
|
@@ -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", "
|
|
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:
|
|
322
|
+
runtime: RuntimeSchema,
|
|
323
323
|
model: z.string().optional(),
|
|
324
324
|
resumed: z.boolean(),
|
|
325
325
|
finalText: z.string().optional(),
|
package/dist/execution-runner.js
CHANGED
|
@@ -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 (
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
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
|
|
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;
|
package/dist/external-output.js
CHANGED
|
@@ -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
|
-
*
|
|
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")
|
|
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");
|