@nowcrew/daemon 0.6.89 → 0.6.91-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -3
- package/dist/agent-ability/materializer.js +21 -10
- package/dist/agent-environment-contract.js +55 -0
- package/dist/agent-environment-control.js +24 -0
- package/dist/agent-environment-execution.js +98 -0
- package/dist/agent-environments.js +275 -0
- package/dist/automatic-daemon-installer.js +49 -1
- package/dist/computer-main.js +18 -0
- package/dist/computer-profile.js +17 -4
- package/dist/computer-setup-client.js +318 -0
- package/dist/computer-setup-orchestration.js +203 -0
- package/dist/computer-setup.js +244 -0
- package/dist/config.js +30 -12
- package/dist/console-formatter.js +3 -3
- package/dist/console.js +15 -2
- package/dist/daemon-update-eligibility.js +1 -1
- package/dist/daemon-updater.js +2 -12
- package/dist/execution-journal.js +9 -6
- package/dist/execution-protocol.js +1 -0
- package/dist/execution-runner.js +13 -8
- package/dist/i18n.js +19 -0
- package/dist/list-models.js +12 -12
- package/dist/local-execution-lease.js +22 -0
- package/dist/local-executor.js +57 -84
- package/dist/local-memory/candidate.js +43 -0
- package/dist/local-memory/index-budget.js +8 -0
- package/dist/local-memory/inventory-cli.js +61 -0
- package/dist/local-memory/inventory-preview.js +159 -0
- package/dist/local-memory/inventory-source.js +159 -0
- package/dist/local-memory/pending-match.js +29 -0
- package/dist/local-memory/pending-query.js +100 -0
- package/dist/local-memory/pending-schema.js +29 -0
- package/dist/local-memory/pending-source.js +126 -0
- package/dist/local-memory/pending.js +34 -84
- package/dist/local-memory/prompt.js +19 -12
- package/dist/local-memory/read-cli.js +97 -12
- package/dist/local-memory/rollout.js +82 -0
- package/dist/local-memory/runtime.js +23 -17
- package/dist/local-memory/work-log-execution.js +31 -0
- package/dist/local-memory/work-log.js +58 -0
- package/dist/local-memory/write.js +27 -58
- package/dist/local-runtime-environment.js +50 -0
- package/dist/machine-environment-cache.js +147 -0
- package/dist/machine-environment-contract.js +10 -0
- package/dist/machine-environment-control.js +27 -0
- package/dist/machine-environment-policy.js +75 -0
- package/dist/machine-environment-process.js +110 -0
- package/dist/machine-info.js +70 -25
- package/dist/machine-runtime-environment.js +189 -0
- package/dist/machine-shell-environment-capture.js +135 -0
- package/dist/machine-windows-environment-capture.js +215 -0
- package/dist/machine-windows-environment-script.js +158 -0
- package/dist/main.js +4 -69
- package/dist/marketplace-command.js +21 -0
- package/dist/marketplace-controller.js +80 -0
- package/dist/marketplace-protocol.js +38 -0
- package/dist/marketplace-remote.js +48 -0
- package/dist/marketplace-runner.js +235 -0
- package/dist/marketplace-serve.js +41 -0
- package/dist/marketplace-source.js +103 -0
- package/dist/message-file-preview.js +246 -0
- package/dist/pre-admission-rejection.js +64 -0
- package/dist/project-skills/execution-adapter.js +3 -2
- package/dist/project-skills/runtime-launch.js +14 -0
- package/dist/provider-env.js +26 -15
- package/dist/runner.js +17 -4
- package/dist/runtime-environment-merge.js +16 -0
- package/dist/runtime-health-handler.js +10 -0
- package/dist/runtime-health.js +3 -1
- package/dist/runtime-path.js +22 -12
- package/dist/runtime-probe.js +35 -6
- package/dist/runtimes/acp-probe-transport.js +69 -0
- package/dist/runtimes/claude-models.js +20 -9
- package/dist/runtimes/deepseek-harness-launch.js +10 -0
- package/dist/runtimes/deepseek-harness-profile.js +92 -0
- package/dist/runtimes/deepseek-harness-runner.js +226 -0
- package/dist/runtimes/deepseek-harness.js +76 -15
- package/dist/runtimes/harness-probe-retry.js +42 -0
- package/dist/runtimes/hermes-models.js +2 -1
- package/dist/runtimes/kimi-acp-runner.js +78 -13
- package/dist/runtimes/opencode.js +2 -2
- package/dist/serve.js +88 -178
- package/dist/storage-identity.js +0 -3
- package/dist/storage-serve-adapter.js +6 -72
- package/dist/supervised-runtime.js +17 -2
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +27 -0
- package/dist/workspace-fs.js +66 -15
- package/dist/workspace.js +1 -1
- package/package.json +13 -12
- package/dist/codex-home-launch.js +0 -7
- package/dist/codex-home-mode-controller.js +0 -68
- package/dist/daemon-migration-controller.js +0 -211
- package/dist/daemon-migration-wiring.js +0 -22
- package/dist/runtimes/codex-home-migration-cli.js +0 -26
- package/dist/runtimes/codex-home-migration.js +0 -112
- package/dist/runtimes/codex-home.js +0 -236
- package/dist/storage-control.js +0 -212
- package/dist/storage-copy.js +0 -628
- package/dist/storage-credential-guard.js +0 -21
- package/dist/storage-doctor.js +0 -228
- package/dist/storage-manifest.js +0 -1027
- package/dist/storage-migration-controller.js +0 -440
- package/dist/storage-migration-manifest.js +0 -105
- package/dist/storage-migration-policy.js +0 -100
- package/dist/storage-runtime-controller.js +0 -381
- package/dist/storage-runtime-readiness.js +0 -241
- package/dist/storage-safe-fs.js +0 -302
- package/dist/storage-status-delivery.js +0 -224
- package/dist/storage-status-replay.js +0 -140
- package/dist/storage-target-confirmation.js +0 -129
- package/dist/storage-worker-command.js +0 -37
- package/dist/storage-worker-executor.js +0 -447
- package/dist/storage-worker-handoff.js +0 -151
- package/dist/storage-worker-operation.js +0 -294
- package/dist/storage-worker-status.js +0 -259
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { computerSetupRecoveryGuidance, redactSetupSecrets, } from "./computer-setup-client.js";
|
|
2
|
+
import { isProductionServerHostname, serverUrlsMatch } from "./server-url.js";
|
|
3
|
+
import { posix, win32 } from "node:path";
|
|
4
|
+
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);
|
|
8
|
+
}
|
|
9
|
+
function assertBootstrapMatches(bootstrap, input) {
|
|
10
|
+
if (bootstrap.machineId !== input.args.machineId) {
|
|
11
|
+
throw new Error("setup bootstrap machine does not match --machine");
|
|
12
|
+
}
|
|
13
|
+
const workspaceMatches = input.args.workspace.kind === "id"
|
|
14
|
+
? input.args.workspace.value === bootstrap.workspaceId
|
|
15
|
+
: bootstrap.workspaceSlug !== undefined && input.args.workspace.value === bootstrap.workspaceSlug;
|
|
16
|
+
if (!workspaceMatches)
|
|
17
|
+
throw new Error("setup bootstrap Workspace does not match the requested Workspace");
|
|
18
|
+
const bootstrapUrl = new URL(bootstrap.serverUrl);
|
|
19
|
+
const requestedUrl = new URL(input.serverUrl);
|
|
20
|
+
if (!serverUrlsMatch(bootstrapUrl.origin, requestedUrl.origin)) {
|
|
21
|
+
throw new Error("setup bootstrap server does not match the requested server context");
|
|
22
|
+
}
|
|
23
|
+
const hostname = bootstrapUrl.hostname.toLowerCase();
|
|
24
|
+
const knownEnvironment = hostname === "nowwork-dev.nowcoder.com"
|
|
25
|
+
? "dev"
|
|
26
|
+
: isProductionServerHostname(hostname)
|
|
27
|
+
? "production"
|
|
28
|
+
: null;
|
|
29
|
+
if (knownEnvironment !== null) {
|
|
30
|
+
const expectedReleaseTag = knownEnvironment === "dev" ? "beta" : "latest";
|
|
31
|
+
if (bootstrap.environment !== knownEnvironment) {
|
|
32
|
+
throw new Error("setup bootstrap environment does not match the server hostname");
|
|
33
|
+
}
|
|
34
|
+
if (bootstrap.releaseTag !== undefined && bootstrap.releaseTag !== expectedReleaseTag) {
|
|
35
|
+
throw new Error("setup bootstrap release tag does not match the server hostname");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function secondsUntil(expiresAt, now) {
|
|
40
|
+
const remaining = Date.parse(expiresAt) - now();
|
|
41
|
+
return Math.max(0, Math.ceil(remaining / 1_000));
|
|
42
|
+
}
|
|
43
|
+
function localPathsMatch(left, right, platform) {
|
|
44
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
45
|
+
if (!paths.isAbsolute(left) || !paths.isAbsolute(right))
|
|
46
|
+
return false;
|
|
47
|
+
const normalize = (value) => {
|
|
48
|
+
const resolved = resolveAgentsRoot(value, undefined, platform);
|
|
49
|
+
return platform === "win32" ? win32.normalize(resolved).toLocaleLowerCase("en-US") : resolved;
|
|
50
|
+
};
|
|
51
|
+
return normalize(left) === normalize(right);
|
|
52
|
+
}
|
|
53
|
+
async function obtainBootstrap(input, deps) {
|
|
54
|
+
const pairing = await deps.client.start({
|
|
55
|
+
workspace: input.args.workspace,
|
|
56
|
+
machineId: input.args.machineId,
|
|
57
|
+
});
|
|
58
|
+
deps.reportPairing(`Open ${pairing.verificationUri}`);
|
|
59
|
+
deps.reportPairing(`Code ${pairing.userCode}`);
|
|
60
|
+
try {
|
|
61
|
+
await deps.openBrowser(pairing.verificationUri);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
deps.reportPairing(`Browser open skipped: ${redactSetupSecrets(error.message)}`);
|
|
65
|
+
}
|
|
66
|
+
let intervalSeconds = pairing.intervalSeconds;
|
|
67
|
+
for (;;) {
|
|
68
|
+
const remainingSeconds = secondsUntil(pairing.expiresAt, deps.now);
|
|
69
|
+
if (remainingSeconds <= 0)
|
|
70
|
+
throw new Error("computer setup pairing expired");
|
|
71
|
+
await deps.sleep(Math.min(intervalSeconds * 1_000, remainingSeconds * 1_000));
|
|
72
|
+
const result = await deps.client.poll(pairing.deviceCode);
|
|
73
|
+
if (result.status === "authorization_pending" || result.status === "slow_down") {
|
|
74
|
+
intervalSeconds = result.intervalSeconds;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (result.status === "access_denied")
|
|
78
|
+
throw new Error("computer setup pairing was denied");
|
|
79
|
+
if (result.status === "expired_token")
|
|
80
|
+
throw new Error("computer setup pairing expired");
|
|
81
|
+
return result.bootstrap;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function runComputerSetupOrchestration(input, deps) {
|
|
85
|
+
let machineToken;
|
|
86
|
+
let bootstrap;
|
|
87
|
+
let credentialFinalized = false;
|
|
88
|
+
let verifiedStorageId;
|
|
89
|
+
let rollbackAttempt;
|
|
90
|
+
const confirmRollback = () => {
|
|
91
|
+
// Reuse both success and failure so an outer catch cannot change the recovery decision.
|
|
92
|
+
rollbackAttempt ??= (async () => {
|
|
93
|
+
if (credentialFinalized)
|
|
94
|
+
throw new Error("computer setup credentials already finalized");
|
|
95
|
+
if (bootstrap === undefined || machineToken === undefined)
|
|
96
|
+
throw new Error("computer setup credentials unavailable");
|
|
97
|
+
for (let attempt = 0;; attempt += 1) {
|
|
98
|
+
try {
|
|
99
|
+
const confirmed = await deps.client.rollback({ setupId: bootstrap.setupId, machineToken });
|
|
100
|
+
if (confirmed !== true)
|
|
101
|
+
throw new Error("computer setup rollback not confirmed");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (attempt >= 2)
|
|
106
|
+
throw error;
|
|
107
|
+
await deps.sleep(1_000);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
return rollbackAttempt;
|
|
112
|
+
};
|
|
113
|
+
try {
|
|
114
|
+
const authorized = await obtainBootstrap(input, deps);
|
|
115
|
+
const token = authorized.machineToken;
|
|
116
|
+
bootstrap = authorized;
|
|
117
|
+
machineToken = token;
|
|
118
|
+
assertBootstrapMatches(authorized, input);
|
|
119
|
+
const installation = await deps.installAutomatically({
|
|
120
|
+
serverUrl: authorized.serverUrl,
|
|
121
|
+
machineToken: token,
|
|
122
|
+
workspaceId: authorized.workspaceId,
|
|
123
|
+
machineId: authorized.machineId,
|
|
124
|
+
...(authorized.profile === undefined ? {} : { requestedProfile: authorized.profile }),
|
|
125
|
+
...(authorized.releaseTag === "latest" || authorized.releaseTag === "beta"
|
|
126
|
+
? { requestedReleaseTag: authorized.releaseTag }
|
|
127
|
+
: {}),
|
|
128
|
+
...(authorized.registryUrl === undefined ? {} : { requestedRegistryUrl: authorized.registryUrl }),
|
|
129
|
+
beforeRollback: confirmRollback,
|
|
130
|
+
verifyAfterStart: async (candidate) => {
|
|
131
|
+
const hello = await deps.client.waitForHello({
|
|
132
|
+
machineToken: token,
|
|
133
|
+
machineId: authorized.machineId,
|
|
134
|
+
workspaceId: authorized.workspaceId,
|
|
135
|
+
profile: candidate.profile,
|
|
136
|
+
setupId: authorized.setupId,
|
|
137
|
+
minimumConnectionEpoch: authorized.minimumConnectionEpoch,
|
|
138
|
+
});
|
|
139
|
+
if (hello.machineId !== authorized.machineId)
|
|
140
|
+
throw new Error("machine hello identity mismatch");
|
|
141
|
+
if (hello.workspaceId !== authorized.workspaceId)
|
|
142
|
+
throw new Error("machine hello workspace identity mismatch");
|
|
143
|
+
if (hello.environment !== authorized.environment)
|
|
144
|
+
throw new Error("machine hello environment identity mismatch");
|
|
145
|
+
if (hello.profile !== candidate.profile)
|
|
146
|
+
throw new Error("machine hello profile identity mismatch");
|
|
147
|
+
const platform = deps.platform ?? process.platform;
|
|
148
|
+
if (!localPathsMatch(hello.daemonHome, candidate.daemonHome, platform))
|
|
149
|
+
throw new Error("machine hello daemon home mismatch");
|
|
150
|
+
if (!localPathsMatch(hello.agentsRoot, candidate.agentsRoot, platform))
|
|
151
|
+
throw new Error("machine hello agents root mismatch");
|
|
152
|
+
if (authorized.storageId !== undefined && hello.storageId !== authorized.storageId) {
|
|
153
|
+
throw new Error("machine hello storage identity mismatch");
|
|
154
|
+
}
|
|
155
|
+
verifiedStorageId = hello.storageId;
|
|
156
|
+
let finalizeError;
|
|
157
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
158
|
+
try {
|
|
159
|
+
await deps.client.finalize({ setupId: authorized.setupId, machineToken: token });
|
|
160
|
+
credentialFinalized = true;
|
|
161
|
+
finalizeError = undefined;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
finalizeError = error;
|
|
166
|
+
if (attempt < 2)
|
|
167
|
+
await deps.sleep(1_000);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!credentialFinalized) {
|
|
171
|
+
throw finalizeError ?? new Error("computer setup credential finalization failed");
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
workspaceId: authorized.workspaceId,
|
|
177
|
+
machineId: authorized.machineId,
|
|
178
|
+
environment: authorized.environment,
|
|
179
|
+
profile: installation.profile,
|
|
180
|
+
daemonVersion: installation.version,
|
|
181
|
+
serviceId: installation.serviceId,
|
|
182
|
+
serviceStatus: "running",
|
|
183
|
+
helloConfirmed: true,
|
|
184
|
+
...(authorized.environmentId === undefined ? {} : { environmentId: authorized.environmentId }),
|
|
185
|
+
...(verifiedStorageId === undefined ? {} : { storageId: verifiedStorageId }),
|
|
186
|
+
localNowworkHome: installation.localHome,
|
|
187
|
+
daemonHome: installation.daemonHome,
|
|
188
|
+
agentsRoot: installation.agentsRoot,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (bootstrap !== undefined && machineToken !== undefined && !credentialFinalized && rollbackAttempt === undefined) {
|
|
193
|
+
try {
|
|
194
|
+
await confirmRollback();
|
|
195
|
+
}
|
|
196
|
+
catch (rollbackError) {
|
|
197
|
+
error = new Error(`${error.message}; computer setup rollback failed: ${rollbackError.message}; `
|
|
198
|
+
+ `local recovery blocked. ${computerSetupRecoveryGuidance(bootstrap)}`, { cause: error });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
throw safeError(error, machineToken);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { DEFAULT_SERVER_URL } from "./config.js";
|
|
4
|
+
import { installDaemonAutomatically, } from "./automatic-daemon-installer.js";
|
|
5
|
+
import { createComputerSetupClient, openSetupBrowser, redactSetupSecrets, } from "./computer-setup-client.js";
|
|
6
|
+
import { runComputerSetupOrchestration, } from "./computer-setup-orchestration.js";
|
|
7
|
+
import { detectDaemonLang, formatDaemonText } from "./i18n.js";
|
|
8
|
+
import { systemCommandRunner } from "./computer-service.js";
|
|
9
|
+
const WorkspaceSlugSchema = z.string()
|
|
10
|
+
.min(2)
|
|
11
|
+
.max(40)
|
|
12
|
+
.regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u);
|
|
13
|
+
const WorkspaceIdSchema = z.string().uuid();
|
|
14
|
+
const MachineIdSchema = z.string().uuid();
|
|
15
|
+
const ServerUrlSchema = z.string().url().refine((value) => {
|
|
16
|
+
const url = new URL(value);
|
|
17
|
+
return (url.protocol === "http:" || url.protocol === "https:")
|
|
18
|
+
&& url.username.length === 0
|
|
19
|
+
&& url.password.length === 0
|
|
20
|
+
&& (url.pathname === "" || url.pathname === "/")
|
|
21
|
+
&& url.search.length === 0
|
|
22
|
+
&& url.hash.length === 0;
|
|
23
|
+
}, "server URL must be an HTTP(S) origin without embedded credentials");
|
|
24
|
+
function stringValue(values, key) {
|
|
25
|
+
const value = values[key];
|
|
26
|
+
return typeof value === "string" ? value : undefined;
|
|
27
|
+
}
|
|
28
|
+
function booleanValue(values, key) {
|
|
29
|
+
return values[key] === true;
|
|
30
|
+
}
|
|
31
|
+
function optionOccurrences(argv, name) {
|
|
32
|
+
const prefix = `--${name}`;
|
|
33
|
+
const values = [];
|
|
34
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
35
|
+
const value = argv[index];
|
|
36
|
+
if (value === prefix) {
|
|
37
|
+
const next = argv[index + 1];
|
|
38
|
+
if (next !== undefined && !next.startsWith("--"))
|
|
39
|
+
values.push(next);
|
|
40
|
+
index += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (value?.startsWith(`${prefix}=`))
|
|
44
|
+
values.push(value.slice(prefix.length + 1));
|
|
45
|
+
}
|
|
46
|
+
return values;
|
|
47
|
+
}
|
|
48
|
+
function assertRepeatedOptionValuesAgree(argv, name, normalize) {
|
|
49
|
+
const normalized = optionOccurrences(argv, name).map(normalize);
|
|
50
|
+
if (new Set(normalized).size > 1) {
|
|
51
|
+
throw new Error(`Repeated --${name} values conflict`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function normalizedServerOrigin(value) {
|
|
55
|
+
const parsed = new URL(ServerUrlSchema.parse(value));
|
|
56
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
57
|
+
}
|
|
58
|
+
function workspaceFromSlug(value) {
|
|
59
|
+
if (!value.startsWith("/") || value.startsWith("//")) {
|
|
60
|
+
throw new Error("Workspace slug must use exactly one leading slash, for example /nowcoder-app");
|
|
61
|
+
}
|
|
62
|
+
return WorkspaceSlugSchema.parse(value.slice(1));
|
|
63
|
+
}
|
|
64
|
+
function workspaceFromAlias(value) {
|
|
65
|
+
return value.startsWith("/") ? workspaceFromSlug(value) : WorkspaceSlugSchema.parse(value);
|
|
66
|
+
}
|
|
67
|
+
export function parseSetupArgs(argv) {
|
|
68
|
+
assertRepeatedOptionValuesAgree(argv, "workspace", (value) => workspaceFromAlias(value));
|
|
69
|
+
assertRepeatedOptionValuesAgree(argv, "workspace-id", (value) => WorkspaceIdSchema.parse(value).toLowerCase());
|
|
70
|
+
assertRepeatedOptionValuesAgree(argv, "machine", (value) => MachineIdSchema.parse(value).toLowerCase());
|
|
71
|
+
assertRepeatedOptionValuesAgree(argv, "machine-id", (value) => MachineIdSchema.parse(value).toLowerCase());
|
|
72
|
+
assertRepeatedOptionValuesAgree(argv, "server-url", normalizedServerOrigin);
|
|
73
|
+
const parsed = parseArgs({
|
|
74
|
+
args: [...argv],
|
|
75
|
+
allowPositionals: true,
|
|
76
|
+
strict: true,
|
|
77
|
+
options: {
|
|
78
|
+
workspace: { type: "string" },
|
|
79
|
+
"workspace-id": { type: "string" },
|
|
80
|
+
machine: { type: "string" },
|
|
81
|
+
"machine-id": { type: "string" },
|
|
82
|
+
"server-url": { type: "string" },
|
|
83
|
+
json: { type: "boolean", default: false },
|
|
84
|
+
"no-browser": { type: "boolean", default: false },
|
|
85
|
+
help: { type: "boolean", short: "h", default: false },
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const values = parsed.values;
|
|
89
|
+
const [command, ...positionals] = parsed.positionals;
|
|
90
|
+
if (command !== "setup")
|
|
91
|
+
throw new Error("Expected 'setup'");
|
|
92
|
+
if (booleanValue(values, "help"))
|
|
93
|
+
throw new Error(setupUsage());
|
|
94
|
+
if (positionals.length > 1)
|
|
95
|
+
throw new Error("Setup accepts one Workspace argument");
|
|
96
|
+
const workspaceAlias = stringValue(values, "workspace");
|
|
97
|
+
const workspaceIdAlias = stringValue(values, "workspace-id");
|
|
98
|
+
if (workspaceAlias !== undefined && workspaceIdAlias !== undefined) {
|
|
99
|
+
throw new Error("Choose one Workspace source: positional, --workspace, or --workspace-id");
|
|
100
|
+
}
|
|
101
|
+
const positionalWorkspace = positionals[0] === undefined ? undefined : workspaceFromSlug(positionals[0]);
|
|
102
|
+
const aliasWorkspace = workspaceAlias === undefined ? undefined : workspaceFromAlias(workspaceAlias);
|
|
103
|
+
if (positionalWorkspace !== undefined && workspaceIdAlias !== undefined) {
|
|
104
|
+
throw new Error("A positional Workspace slug cannot be combined with --workspace-id");
|
|
105
|
+
}
|
|
106
|
+
if (positionalWorkspace !== undefined && aliasWorkspace !== undefined && positionalWorkspace !== aliasWorkspace) {
|
|
107
|
+
throw new Error("Positional Workspace conflicts with --workspace");
|
|
108
|
+
}
|
|
109
|
+
const workspace = positionalWorkspace !== undefined
|
|
110
|
+
? Object.freeze({ kind: "slug", value: positionalWorkspace })
|
|
111
|
+
: aliasWorkspace !== undefined
|
|
112
|
+
? Object.freeze({ kind: "slug", value: aliasWorkspace })
|
|
113
|
+
: workspaceIdAlias === undefined
|
|
114
|
+
? (() => { throw new Error("Missing Workspace; use /<slug> or --workspace-id <uuid>"); })()
|
|
115
|
+
: Object.freeze({ kind: "id", value: WorkspaceIdSchema.parse(workspaceIdAlias).toLowerCase() });
|
|
116
|
+
const machineAlias = stringValue(values, "machine");
|
|
117
|
+
const machineIdAlias = stringValue(values, "machine-id");
|
|
118
|
+
const parsedMachine = MachineIdSchema.parse(machineAlias ?? machineIdAlias ?? (() => {
|
|
119
|
+
throw new Error("Missing --machine <uuid>");
|
|
120
|
+
})()).toLowerCase();
|
|
121
|
+
if (machineAlias !== undefined && machineIdAlias !== undefined
|
|
122
|
+
&& parsedMachine !== MachineIdSchema.parse(machineIdAlias).toLowerCase()) {
|
|
123
|
+
throw new Error("--machine conflicts with --machine-id");
|
|
124
|
+
}
|
|
125
|
+
const machineId = parsedMachine;
|
|
126
|
+
const serverUrl = stringValue(values, "server-url");
|
|
127
|
+
return Object.freeze({
|
|
128
|
+
command: "setup",
|
|
129
|
+
workspace,
|
|
130
|
+
machineId,
|
|
131
|
+
...(serverUrl === undefined ? {} : { serverUrl: ServerUrlSchema.parse(serverUrl) }),
|
|
132
|
+
...(booleanValue(values, "json") ? { json: true } : {}),
|
|
133
|
+
...(booleanValue(values, "no-browser") ? { noBrowser: true } : {}),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function resolveSetupServerUrl(args, env) {
|
|
137
|
+
const configured = args.serverUrl ?? env.CREW_SERVER_URL?.trim();
|
|
138
|
+
if (configured !== undefined && configured.length > 0)
|
|
139
|
+
return normalizedServerOrigin(configured);
|
|
140
|
+
if (env.NODE_ENV === "development" || env.NODE_ENV === "test")
|
|
141
|
+
return DEFAULT_SERVER_URL;
|
|
142
|
+
throw new Error("server URL is required; pass --server-url or set CREW_SERVER_URL");
|
|
143
|
+
}
|
|
144
|
+
export function setupUsage(td = (message) => message) {
|
|
145
|
+
return [
|
|
146
|
+
td("Usage:"),
|
|
147
|
+
" nowwork-computer setup /workspace --machine <uuid>",
|
|
148
|
+
" nowwork-computer setup --workspace /workspace --machine <uuid>",
|
|
149
|
+
" nowwork-computer setup --workspace-id <uuid> --machine-id <uuid>",
|
|
150
|
+
"",
|
|
151
|
+
"Options: --server-url <url> --json --no-browser",
|
|
152
|
+
].join("\n") + "\n";
|
|
153
|
+
}
|
|
154
|
+
function defaults() {
|
|
155
|
+
return {
|
|
156
|
+
env: process.env,
|
|
157
|
+
platform: process.platform,
|
|
158
|
+
fetch: globalThis.fetch,
|
|
159
|
+
stdout: (text) => process.stdout.write(text),
|
|
160
|
+
stderr: (text) => process.stderr.write(text),
|
|
161
|
+
createClient: (serverUrl) => createComputerSetupClient({ serverUrl }),
|
|
162
|
+
installAutomatically: installDaemonAutomatically,
|
|
163
|
+
openBrowser: (url) => openSetupBrowser(url, process.platform, systemCommandRunner),
|
|
164
|
+
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
165
|
+
now: Date.now,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function humanSummary(result, td) {
|
|
169
|
+
return [
|
|
170
|
+
td("Workspace: {{value}}", { value: result.workspaceId }),
|
|
171
|
+
td("Machine: {{value}}", { value: result.machineId }),
|
|
172
|
+
td("Environment: {{value}}", { value: result.environment }),
|
|
173
|
+
td("Daemon: profile {{profile}} / v{{version}} / {{status}}", {
|
|
174
|
+
profile: result.profile, version: result.daemonVersion, status: result.serviceStatus,
|
|
175
|
+
}),
|
|
176
|
+
td("Local: {{value}}", { value: result.localNowworkHome }),
|
|
177
|
+
td("Config: {{value}}", { value: result.daemonHome }),
|
|
178
|
+
td("Agents: {{value}}", { value: result.agentsRoot }),
|
|
179
|
+
...(result.storageId === undefined ? [] : [td("Storage ID: {{value}}", { value: result.storageId })]),
|
|
180
|
+
td(result.helloConfirmed ? "Status: connected and ready" : "Status: not ready"),
|
|
181
|
+
].join("\n") + "\n";
|
|
182
|
+
}
|
|
183
|
+
function localizedPairingMessage(lang, message) {
|
|
184
|
+
if (message.startsWith("Open ")) {
|
|
185
|
+
return formatDaemonText(lang, "Open {{url}}", { url: message.slice("Open ".length) });
|
|
186
|
+
}
|
|
187
|
+
if (message.startsWith("Code ")) {
|
|
188
|
+
return formatDaemonText(lang, "Code {{code}}", { code: message.slice("Code ".length) });
|
|
189
|
+
}
|
|
190
|
+
if (message.startsWith("Browser open skipped: ")) {
|
|
191
|
+
return formatDaemonText(lang, "Browser open skipped: {{reason}}", {
|
|
192
|
+
reason: message.slice("Browser open skipped: ".length),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return formatDaemonText(lang, message);
|
|
196
|
+
}
|
|
197
|
+
export async function runComputerSetupCommand(argv, overrides = {}) {
|
|
198
|
+
if (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h")) {
|
|
199
|
+
const deps = { ...defaults(), ...overrides };
|
|
200
|
+
const lang = detectDaemonLang(deps.env);
|
|
201
|
+
deps.stdout(setupUsage((message, values) => formatDaemonText(lang, message, values)));
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
if (argv[0] !== "setup")
|
|
205
|
+
return null;
|
|
206
|
+
const deps = { ...defaults(), ...overrides };
|
|
207
|
+
const lang = detectDaemonLang(deps.env);
|
|
208
|
+
try {
|
|
209
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
210
|
+
deps.stdout(setupUsage((message, values) => formatDaemonText(lang, message, values)));
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
let args;
|
|
214
|
+
try {
|
|
215
|
+
args = parseSetupArgs(argv);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
deps.stderr(`nowwork-computer: ${redactSetupSecrets(error.message)}\n`);
|
|
219
|
+
return 2;
|
|
220
|
+
}
|
|
221
|
+
if (args.json === true) {
|
|
222
|
+
deps.stderr(`${formatDaemonText(lang, "Starting computer setup pairing...")}\n`);
|
|
223
|
+
}
|
|
224
|
+
const serverUrl = resolveSetupServerUrl(args, deps.env);
|
|
225
|
+
const createClient = overrides.createClient
|
|
226
|
+
?? ((value) => createComputerSetupClient({ serverUrl: value, fetch: deps.fetch }));
|
|
227
|
+
const result = await runComputerSetupOrchestration({ args, serverUrl }, {
|
|
228
|
+
client: createClient(serverUrl),
|
|
229
|
+
installAutomatically: deps.installAutomatically,
|
|
230
|
+
openBrowser: args.noBrowser === true ? async () => { } : deps.openBrowser,
|
|
231
|
+
sleep: deps.sleep,
|
|
232
|
+
reportPairing: (message) => deps[args.json === true ? "stderr" : "stdout"](`${localizedPairingMessage(lang, message)}\n`),
|
|
233
|
+
now: deps.now,
|
|
234
|
+
});
|
|
235
|
+
deps.stdout(args.json === true
|
|
236
|
+
? `${JSON.stringify(result)}\n`
|
|
237
|
+
: humanSummary(result, (message, values) => formatDaemonText(lang, message, values)));
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
deps.stderr(`nowwork-computer: ${redactSetupSecrets(error.message)}\n`);
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -76,12 +76,32 @@ function defaultCliPath() {
|
|
|
76
76
|
return resolve(here, "../../cli/dist/main.js");
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
//
|
|
80
|
-
// 测试完成后删除此常量,恢复未配置时默认关闭的行为。
|
|
79
|
+
// 保留本地开发的原默认名单;远端 dev 全量不再依赖该集合。
|
|
81
80
|
const TEMP_LOCAL_MEMORY_V2_TEST_HANDLES = [
|
|
82
|
-
"94533bf3-b5b7-4a1e-9c41-6a1ab0a0e1ea", // memory-v2-test-
|
|
83
|
-
"967ab9ce-724c-4f22-a664-2f855cd798af", // memory-v2-test-
|
|
81
|
+
"94533bf3-b5b7-4a1e-9c41-6a1ab0a0e1ea", // memory-v2-test-b
|
|
82
|
+
"967ab9ce-724c-4f22-a664-2f855cd798af", // memory-v2-test-a
|
|
84
83
|
];
|
|
84
|
+
export function localMemoryV2Environment(serverUrl) {
|
|
85
|
+
try {
|
|
86
|
+
const url = new URL(serverUrl);
|
|
87
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
88
|
+
return "custom";
|
|
89
|
+
const hostname = url.hostname.toLowerCase().replace(/\.$/u, "");
|
|
90
|
+
if (hostname === "nowwork-dev.nowcoder.com")
|
|
91
|
+
return "dev";
|
|
92
|
+
if (hostname === "nowwork-in.nowcoder.com" || hostname === "nowwork.nowcoder.com")
|
|
93
|
+
return "production";
|
|
94
|
+
}
|
|
95
|
+
catch { /* 其它地址保留原配置行为。 */ }
|
|
96
|
+
return "custom";
|
|
97
|
+
}
|
|
98
|
+
/** 环境资格不代表旧格式已兼容;实际格式由本轮工作区准备阶段核对。 */
|
|
99
|
+
export function isLocalMemoryV2Enabled(config, handle) {
|
|
100
|
+
const environment = localMemoryV2Environment(config.serverUrl);
|
|
101
|
+
if (environment !== "custom")
|
|
102
|
+
return environment === "dev";
|
|
103
|
+
return config.localMemoryV2Handles?.has(handle) === true;
|
|
104
|
+
}
|
|
85
105
|
function isTemporaryDevServer(serverUrl) {
|
|
86
106
|
try {
|
|
87
107
|
const hostname = new URL(serverUrl).hostname.toLowerCase();
|
|
@@ -111,13 +131,11 @@ function localMemoryV2Handles(env, serverUrl) {
|
|
|
111
131
|
return new Set(handles);
|
|
112
132
|
}
|
|
113
133
|
export const DEFAULT_SERVER_URL = "http://127.0.0.1:3001";
|
|
114
|
-
function
|
|
115
|
-
const value = env.
|
|
116
|
-
if (value ===
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
return "private";
|
|
120
|
-
throw new ConfigError("CREW_CODEX_HOME_MODE must be shared or private");
|
|
134
|
+
function machineEnvironmentModeEnv(env) {
|
|
135
|
+
const value = env.NOWWORK_MACHINE_ENV_MODE ?? "auto";
|
|
136
|
+
if (value === "auto" || value === "zsh" || value === "bash" || value === "windows" || value === "off")
|
|
137
|
+
return value;
|
|
138
|
+
throw new ConfigError("NOWWORK_MACHINE_ENV_MODE must be auto, zsh, bash, windows, or off");
|
|
121
139
|
}
|
|
122
140
|
function codexCollaborationToolsEnv(env) {
|
|
123
141
|
const value = (env.CREW_CODEX_COLLABORATION_TOOLS ?? "").trim().toLowerCase();
|
|
@@ -168,7 +186,7 @@ export function loadConfig(env = process.env) {
|
|
|
168
186
|
: defaultQaReportRoot,
|
|
169
187
|
cliPath: env.CREW_CLI_PATH ?? defaultCliPath(),
|
|
170
188
|
runtimeBin: env.CREW_RUNTIME ?? "claude",
|
|
171
|
-
|
|
189
|
+
machineEnvironmentMode: machineEnvironmentModeEnv(env),
|
|
172
190
|
codexCollaborationTools: codexCollaborationToolsEnv(env),
|
|
173
191
|
dangerous: env.CREW_RUNTIME_SAFE !== "1", // 默认开启 (headless agent 在自有 workspace 内运行)
|
|
174
192
|
resume: env.CREW_RESUME !== "off" && env.CREW_RESUME !== "0", // 默认开启;一键回退现状用 CREW_RESUME=off
|
|
@@ -22,7 +22,7 @@ const clip = (text) => text.length > TOOL_RESULT_CAP
|
|
|
22
22
|
? `${text.slice(0, TOOL_RESULT_CAP)}… (+${text.length - TOOL_RESULT_CAP})`
|
|
23
23
|
: text;
|
|
24
24
|
const SCRIPT_TOOLS = new Set(["Bash", "PowerShell", "Shell"]);
|
|
25
|
-
export function createConsoleFormatter() {
|
|
25
|
+
export function createConsoleFormatter(runtime) {
|
|
26
26
|
const pending = new Map();
|
|
27
27
|
// listId → 上一条 todos 快照的指纹;仅用于丢弃「连续两次完全相同」的重复事件(无损去重)。
|
|
28
28
|
// 只比对相邻快照,不做跨快照合并——进度推进的中间态必须逐条下发,否则前端进度条会停在旧值。
|
|
@@ -86,7 +86,7 @@ export function createConsoleFormatter() {
|
|
|
86
86
|
const filler = consumePendingThinking();
|
|
87
87
|
if (filler) {
|
|
88
88
|
const content = message.content.map((block) => block.type === "thinking" && !block.thinking?.trim() ? { ...block, thinking: filler } : block);
|
|
89
|
-
return dedupeTodos(toConsoleLines({ ...rec, message: { ...message, content } }));
|
|
89
|
+
return dedupeTodos(toConsoleLines({ ...rec, message: { ...message, content } }, runtime));
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -146,7 +146,7 @@ export function createConsoleFormatter() {
|
|
|
146
146
|
}
|
|
147
147
|
return out;
|
|
148
148
|
}
|
|
149
|
-
return dedupeTodos(toConsoleLines(event));
|
|
149
|
+
return dedupeTodos(toConsoleLines(event, runtime));
|
|
150
150
|
},
|
|
151
151
|
};
|
|
152
152
|
}
|
package/dist/console.js
CHANGED
|
@@ -263,7 +263,7 @@ function codexItemChunks(item, td) {
|
|
|
263
263
|
return [];
|
|
264
264
|
}
|
|
265
265
|
/** 把一个 stream-json 事件转成 0..N 条 console 行(完全透传)。 */
|
|
266
|
-
export function toConsoleLines(event) {
|
|
266
|
+
export function toConsoleLines(event, runtime) {
|
|
267
267
|
const e = (event ?? {});
|
|
268
268
|
const lang = detectDaemonLang();
|
|
269
269
|
const td = (message) => translateDaemon(lang, message);
|
|
@@ -284,7 +284,20 @@ export function toConsoleLines(event) {
|
|
|
284
284
|
return [{ stream: "system", text: `● ${td("Claude session started")}` }];
|
|
285
285
|
}
|
|
286
286
|
if (e.type === "thread.started") {
|
|
287
|
-
|
|
287
|
+
// thread.started is shared by Codex and ACP adapters; its shape does not
|
|
288
|
+
// identify the runtime. Use the executor-owned selection, not event fields.
|
|
289
|
+
const messages = {
|
|
290
|
+
claude: "Claude session started",
|
|
291
|
+
codex: "Codex session started",
|
|
292
|
+
kimi: "Kimi session started",
|
|
293
|
+
hermes: "Hermes session started",
|
|
294
|
+
opencode: "OpenCode session started",
|
|
295
|
+
"deepseek-harness": "DeepSeek Harness session started",
|
|
296
|
+
};
|
|
297
|
+
const message = runtime && Object.hasOwn(messages, runtime)
|
|
298
|
+
? messages[runtime]
|
|
299
|
+
: "Session started";
|
|
300
|
+
return [{ stream: "system", text: `● ${td(message)}` }];
|
|
288
301
|
}
|
|
289
302
|
if (e.type === "turn.plan.updated") {
|
|
290
303
|
const plan = boundedCodexPlan(e.plan);
|
|
@@ -14,7 +14,7 @@ import { assertRegistryCoversDescriptors, listManagedServiceDescriptorPaths, man
|
|
|
14
14
|
export async function managedDaemonCapabilities(eligibility) {
|
|
15
15
|
try {
|
|
16
16
|
return (await eligibility()).eligible
|
|
17
|
-
? ["daemon_update_v1", "daemon_restart_v1", "daemon_update_registry_v1"
|
|
17
|
+
? ["daemon_update_v1", "daemon_restart_v1", "daemon_update_registry_v1"]
|
|
18
18
|
: [];
|
|
19
19
|
}
|
|
20
20
|
catch {
|
package/dist/daemon-updater.js
CHANGED
|
@@ -11,15 +11,12 @@ function registryArgs(registry) {
|
|
|
11
11
|
function isRegistryAuthFailure(stderr) {
|
|
12
12
|
return /(?:\bE401\b|\b401\b.*\bUnauthorized\b)/iu.test(stderr);
|
|
13
13
|
}
|
|
14
|
+
// Managed eligibility guarantees an exclusive installation and native service per profile.
|
|
15
|
+
// Other profiles share compute capacity, but do not share the package being replaced.
|
|
14
16
|
export async function prepareDaemonRestart(input) {
|
|
15
17
|
const local = input.localSlots.tryAcquireExclusive();
|
|
16
18
|
if (local === null)
|
|
17
19
|
return { ok: false, errorCode: "runtime_busy" };
|
|
18
|
-
const host = await input.hostCoordinator.tryAcquireExclusiveExecution();
|
|
19
|
-
if (host === null) {
|
|
20
|
-
local.release();
|
|
21
|
-
return { ok: false, errorCode: "runtime_busy" };
|
|
22
|
-
}
|
|
23
20
|
let released = false;
|
|
24
21
|
return {
|
|
25
22
|
ok: true,
|
|
@@ -27,7 +24,6 @@ export async function prepareDaemonRestart(input) {
|
|
|
27
24
|
if (released)
|
|
28
25
|
return;
|
|
29
26
|
released = true;
|
|
30
|
-
await host.release();
|
|
31
27
|
local.release();
|
|
32
28
|
},
|
|
33
29
|
};
|
|
@@ -74,17 +70,11 @@ export async function installExactDaemonUpdate(input) {
|
|
|
74
70
|
const local = input.localSlots.tryAcquireExclusive();
|
|
75
71
|
if (local === null)
|
|
76
72
|
return { ok: false, errorCode: "runtime_busy" };
|
|
77
|
-
const host = await input.hostCoordinator.tryAcquireExclusiveExecution();
|
|
78
|
-
if (host === null) {
|
|
79
|
-
local.release();
|
|
80
|
-
return { ok: false, errorCode: "runtime_busy" };
|
|
81
|
-
}
|
|
82
73
|
let released = false;
|
|
83
74
|
const release = async () => {
|
|
84
75
|
if (released)
|
|
85
76
|
return;
|
|
86
77
|
released = true;
|
|
87
|
-
await host.release();
|
|
88
78
|
local.release();
|
|
89
79
|
};
|
|
90
80
|
const lockedInstallation = daemonGlobalInstallation(input.currentEntryPath ?? process.argv[1] ?? "", input.platform ?? process.platform);
|
|
@@ -878,23 +878,26 @@ export function createExecutionJournal(agentsRoot, options = {}) {
|
|
|
878
878
|
},
|
|
879
879
|
acknowledgeCompletion: async (executionId) => {
|
|
880
880
|
validateExecutionId(executionId);
|
|
881
|
-
const
|
|
881
|
+
const result = await serializedExecution(executionId, "acknowledge_completion", async () => {
|
|
882
882
|
const entry = await requireRecord(executionId);
|
|
883
883
|
if (entry.state !== "completed" && entry.state !== "interrupted") {
|
|
884
884
|
throw new JournalTransitionError(`Cannot acknowledge ${entry.state} execution`);
|
|
885
885
|
}
|
|
886
|
-
if (entry.completionAcknowledged)
|
|
887
|
-
return confirmDurable(entry);
|
|
886
|
+
if (entry.completionAcknowledged) {
|
|
887
|
+
return { entry: await confirmDurable(entry), newlyAcknowledged: false };
|
|
888
|
+
}
|
|
888
889
|
const updated = JournalEntrySchema.parse({
|
|
889
890
|
...entry,
|
|
890
891
|
completionAcknowledged: true,
|
|
891
892
|
updatedAt: now().toISOString(),
|
|
892
893
|
});
|
|
893
894
|
await writeRecord(updated);
|
|
894
|
-
return updated;
|
|
895
|
+
return { entry: updated, newlyAcknowledged: true };
|
|
895
896
|
});
|
|
896
|
-
|
|
897
|
-
|
|
897
|
+
// 重复 ACK 仍确认持久化,但不能反复全目录清理并阻塞后续控制帧。
|
|
898
|
+
if (result.newlyAcknowledged)
|
|
899
|
+
await serializedDirectory(pruneInternal);
|
|
900
|
+
return result.entry;
|
|
898
901
|
},
|
|
899
902
|
get: async (executionId) => {
|
|
900
903
|
validateExecutionId(executionId);
|
|
@@ -264,6 +264,7 @@ export const ExecutionRejectedSchema = z.object({
|
|
|
264
264
|
reason: RejectionReasonSchema,
|
|
265
265
|
message: z.string().optional(),
|
|
266
266
|
at: TimestampSchema,
|
|
267
|
+
preAdmissionProof: z.object({ specHash: z.string().regex(/^[a-f0-9]{64}$/u) }).strict().optional(),
|
|
267
268
|
}).strict();
|
|
268
269
|
export const ExecutionStartedSchema = z.object({
|
|
269
270
|
type: z.literal("execution:started"),
|