@nowcrew/daemon 0.6.98-beta.4 → 0.6.98-beta.6
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/README.md +9 -0
- package/dist/automatic-daemon-installer.js +41 -10
- package/dist/cloud-report-upload.js +10 -1
- package/dist/computer-main.js +0 -0
- package/dist/computer-setup-client.js +22 -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/main.js +0 -0
- 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 +9 -8
- 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
package/README.md
CHANGED
|
@@ -110,6 +110,15 @@ to confirm Workspace, Machine, environment, profile, and storage identity. It do
|
|
|
110
110
|
existing `.crew` data. Existing `crew-daemon install --server-url ... --api-key ...` and profile lifecycle
|
|
111
111
|
commands remain available as compatibility and recovery entry points.
|
|
112
112
|
|
|
113
|
+
Profiles created by older installers may omit `workspaceId` and `machineId`. During setup, the installer
|
|
114
|
+
uses the newly authorized machine credential to call `POST /daemon/computer-setup/legacy-profile` with
|
|
115
|
+
the SHA-256 of the previous profile token. The server checks historical ownership within that exact
|
|
116
|
+
Workspace and Machine, including revoked credentials, without reactivating them. After the server,
|
|
117
|
+
profile name, Agent root and any existing identity fields match, normal installation persists both UUIDs.
|
|
118
|
+
A mismatch or failed verification stops before changing the local profile or service; do not delete a
|
|
119
|
+
profile or fill in UUIDs by guessing. Deploy the server endpoint before publishing the compatible daemon;
|
|
120
|
+
an older server cannot verify legacy profiles, and already published daemon packages remain unchanged.
|
|
121
|
+
|
|
113
122
|
## Machine Environment
|
|
114
123
|
|
|
115
124
|
The daemon loads exported variables from its OS account’s bash/zsh configuration on macOS/Linux,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { posix, win32 } from "node:path";
|
|
3
4
|
import { z } from "zod";
|
|
4
|
-
import { assertProfileAgentsRootUnique, listProfiles, loadProfile, MachineTokenSchema, removeProfile, saveProfile, windowsDpapiProtector, } from "./computer-profile.js";
|
|
5
|
+
import { assertProfileAgentsRootUnique, listProfiles, loadProfile, MachineTokenSchema, removeProfile, resolveAgentsRoot, saveProfile, windowsDpapiProtector, } from "./computer-profile.js";
|
|
5
6
|
import { buildServiceSpec, captureExactService, hardenWindowsProfile, replaceExactService, restoreExactService, serviceAction, serviceStatus, systemCommandRunner, uninstallService, } from "./computer-service.js";
|
|
6
7
|
import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
7
8
|
import { buildManagedServiceRecord, installManagedService, uninstallManagedService, } from "./managed-service-lifecycle.js";
|
|
@@ -15,8 +16,14 @@ import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
|
15
16
|
import { npmInvocation } from "./npm-command.js";
|
|
16
17
|
import { discoverExactLegacyService, } from "./automatic-service-migration.js";
|
|
17
18
|
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
18
|
-
import { serverHostIdentity } from "./server-url.js";
|
|
19
|
-
import { computerSetupRecoveryGuidance } from "./computer-setup-client.js";
|
|
19
|
+
import { serverHostIdentity, serverUrlsMatch } from "./server-url.js";
|
|
20
|
+
import { computerSetupRecoveryGuidance, createComputerSetupClient, } from "./computer-setup-client.js";
|
|
21
|
+
export const automaticDaemonInstallStageMessage = {
|
|
22
|
+
downloading: "Downloading the Daemon package...",
|
|
23
|
+
configuring: "Configuring the Daemon service...",
|
|
24
|
+
starting: "Starting the Daemon service...",
|
|
25
|
+
confirming: "Confirming the computer connection...",
|
|
26
|
+
};
|
|
20
27
|
function profileStorage(platform, userHome, runner) {
|
|
21
28
|
return {
|
|
22
29
|
platform,
|
|
@@ -57,6 +64,7 @@ function defaultDependencies() {
|
|
|
57
64
|
version: daemonVersion(),
|
|
58
65
|
...(process.env.PATH ? { runtimePath: process.env.PATH } : {}),
|
|
59
66
|
runner,
|
|
67
|
+
verifyLegacyProfileIdentity: (serverUrl, input) => createComputerSetupClient({ serverUrl }).verifyLegacyProfileIdentity(input),
|
|
60
68
|
captureProfile: async (target) => (await listProfiles(target.daemonHome)).includes(target.profile)
|
|
61
69
|
? loadProfile(target.profile, target.daemonHome, storage)
|
|
62
70
|
: null,
|
|
@@ -260,18 +268,37 @@ function setupIdentity(input) {
|
|
|
260
268
|
machineId: z.string().uuid().parse(input.machineId),
|
|
261
269
|
};
|
|
262
270
|
}
|
|
263
|
-
function assertExistingProfileIdentity(previous, identity) {
|
|
271
|
+
async function assertExistingProfileIdentity(previous, identity, target, machineToken, dependencies) {
|
|
264
272
|
if (previous === null || identity === null)
|
|
265
273
|
return;
|
|
266
|
-
if (previous.workspaceId
|
|
267
|
-
throw new Error(`Profile '${previous.name}' does not record its Workspace and Machine identity`);
|
|
268
|
-
}
|
|
269
|
-
if (previous.workspaceId !== identity.workspaceId) {
|
|
274
|
+
if (previous.workspaceId !== undefined && previous.workspaceId !== identity.workspaceId) {
|
|
270
275
|
throw new Error(`Profile '${previous.name}' belongs to a different Workspace identity`);
|
|
271
276
|
}
|
|
272
|
-
if (previous.machineId !== identity.machineId) {
|
|
277
|
+
if (previous.machineId !== undefined && previous.machineId !== identity.machineId) {
|
|
273
278
|
throw new Error(`Profile '${previous.name}' belongs to a different Machine identity`);
|
|
274
279
|
}
|
|
280
|
+
if (previous.workspaceId !== undefined && previous.machineId !== undefined)
|
|
281
|
+
return;
|
|
282
|
+
const rootIdentity = (root) => {
|
|
283
|
+
const resolved = resolveAgentsRoot(root, dependencies.userHome, dependencies.platform);
|
|
284
|
+
return dependencies.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
285
|
+
};
|
|
286
|
+
if (previous.name !== target.profile || !serverUrlsMatch(previous.serverUrl, target.serverUrl)
|
|
287
|
+
|| rootIdentity(previous.agentsRoot) !== rootIdentity(target.agentsRoot)) {
|
|
288
|
+
throw new Error(`Legacy profile '${previous.name}' does not match the requested server, profile, or Agent root`);
|
|
289
|
+
}
|
|
290
|
+
if (dependencies.verifyLegacyProfileIdentity === undefined) {
|
|
291
|
+
throw new Error(`Cannot verify legacy profile '${previous.name}' Workspace and Machine identity`);
|
|
292
|
+
}
|
|
293
|
+
// The old credential may already have been revoked by setup. Only the current credential
|
|
294
|
+
// authenticates this read-only request; the old hash proves historical ownership.
|
|
295
|
+
const verified = await dependencies.verifyLegacyProfileIdentity(target.serverUrl, {
|
|
296
|
+
machineToken,
|
|
297
|
+
previousTokenHash: createHash("sha256").update(previous.machineToken).digest("hex"),
|
|
298
|
+
});
|
|
299
|
+
if (verified.workspaceId !== identity.workspaceId || verified.machineId !== identity.machineId) {
|
|
300
|
+
throw new Error(`Legacy profile '${previous.name}' belongs to a different Workspace or Machine identity`);
|
|
301
|
+
}
|
|
275
302
|
}
|
|
276
303
|
export async function installDaemonAutomatically(input, dependencies = defaultDependencies()) {
|
|
277
304
|
const machineToken = MachineTokenSchema.parse(input.machineToken);
|
|
@@ -308,7 +335,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
|
|
|
308
335
|
});
|
|
309
336
|
const previousProfile = await dependencies.captureProfile(target);
|
|
310
337
|
await dependencies.assertProfileTarget(target, previousProfile);
|
|
311
|
-
assertExistingProfileIdentity(previousProfile, identity);
|
|
338
|
+
await assertExistingProfileIdentity(previousProfile, identity, target, machineToken, dependencies);
|
|
312
339
|
const previousService = await dependencies.captureService(spec, target);
|
|
313
340
|
if (previousService.installed && !previousService.exact) {
|
|
314
341
|
throw new Error(`Installed service '${spec.id}' does not match the requested identity`);
|
|
@@ -331,6 +358,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
|
|
|
331
358
|
});
|
|
332
359
|
let installFailure;
|
|
333
360
|
try {
|
|
361
|
+
input.onProgress?.("downloading");
|
|
334
362
|
await dependencies.installPackage(target.npmPrefix, dependencies.version, dependencies.platform, dependencies.runner, target.registryUrl);
|
|
335
363
|
}
|
|
336
364
|
catch (error) {
|
|
@@ -353,6 +381,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
|
|
|
353
381
|
legacyRemovalStarted = true;
|
|
354
382
|
await dependencies.removeLegacyService(legacyService);
|
|
355
383
|
}
|
|
384
|
+
input.onProgress?.("configuring");
|
|
356
385
|
profileSaved = true;
|
|
357
386
|
await dependencies.saveTargetProfile({
|
|
358
387
|
version: 1,
|
|
@@ -367,6 +396,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
|
|
|
367
396
|
}, target.daemonHome);
|
|
368
397
|
serviceReplacementStarted = true;
|
|
369
398
|
await dependencies.replaceService(spec, target);
|
|
399
|
+
input.onProgress?.("starting");
|
|
370
400
|
await dependencies.startService(spec);
|
|
371
401
|
const status = await dependencies.verifyService(spec);
|
|
372
402
|
if (!status.running)
|
|
@@ -380,6 +410,7 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
|
|
|
380
410
|
daemonHome: target.daemonHome,
|
|
381
411
|
agentsRoot: target.agentsRoot,
|
|
382
412
|
};
|
|
413
|
+
input.onProgress?.("confirming");
|
|
383
414
|
await input.verifyAfterStart?.(result);
|
|
384
415
|
return result;
|
|
385
416
|
}
|
|
@@ -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);
|
package/dist/computer-main.js
CHANGED
|
File without changes
|
|
@@ -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,7 +292,18 @@ 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)),
|
|
302
|
+
verifyLegacyProfileIdentity: async (input) => z.object({
|
|
303
|
+
workspaceId: z.string().uuid(), machineId: z.string().uuid(),
|
|
304
|
+
}).strict().parse(await request("POST", "/daemon/computer-setup/legacy-profile", {
|
|
305
|
+
previousTokenHash: input.previousTokenHash,
|
|
306
|
+
}, input.machineToken)),
|
|
287
307
|
start: async (input) => {
|
|
288
308
|
return parseStart(await request("POST", "/api/auth/computer-setup/start", {
|
|
289
309
|
...(input.workspace.kind === "id"
|
|
@@ -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
|
}
|