@nowcrew/daemon 0.6.98 → 0.6.99-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 +43 -1
- package/dist/automatic-daemon-installer.js +41 -10
- package/dist/automatic-service-migration.js +7 -6
- package/dist/cloud-report-upload.js +1 -10
- package/dist/computer-service.js +40 -42
- package/dist/computer-setup-client.js +43 -9
- package/dist/computer-setup-existing.js +154 -0
- package/dist/computer-setup-orchestration.js +32 -6
- package/dist/computer-setup.js +37 -6
- package/dist/computer-token-setup.js +46 -0
- package/dist/execution-journal.js +3 -1
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +4 -1
- package/dist/i18n.js +16 -0
- package/dist/local-execution-prompt.js +8 -3
- package/dist/local-executor.js +64 -39
- package/dist/local-memory/commit-telemetry.js +30 -0
- package/dist/local-memory/execution-workspace.js +62 -0
- package/dist/local-memory/full-index-search.js +47 -0
- package/dist/local-memory/index-budget.js +9 -3
- package/dist/local-memory/initialization-identity.js +36 -0
- package/dist/local-memory/inventory-cli.js +52 -8
- package/dist/local-memory/inventory-source.js +15 -8
- package/dist/local-memory/inventory-v2.js +362 -0
- package/dist/local-memory/legacy-adapter.js +233 -0
- package/dist/local-memory/migration-access.js +242 -0
- package/dist/local-memory/migration-cli.js +54 -0
- package/dist/local-memory/migration-io.js +199 -0
- package/dist/local-memory/migration-pending.js +9 -0
- package/dist/local-memory/migration-plan.js +432 -0
- package/dist/local-memory/migration-schema.js +55 -0
- package/dist/local-memory/migration-state-invariants.js +65 -0
- package/dist/local-memory/migration-store.js +414 -0
- package/dist/local-memory/migration-tool-entry.js +21 -0
- package/dist/local-memory/pending-query.js +5 -2
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +16 -2
- package/dist/local-memory/prompt.js +21 -5
- package/dist/local-memory/read-cli.js +98 -76
- package/dist/local-memory/rollout.js +79 -11
- package/dist/local-memory/runtime.js +181 -13
- package/dist/local-memory/source-path.js +13 -0
- package/dist/local-memory/storage-format.js +249 -0
- package/dist/local-memory/write.js +189 -21
- package/dist/local-memory/writer-mode.js +13 -0
- package/dist/local-memory-diagnostics.js +96 -15
- package/dist/local-memory-telemetry.js +20 -3
- package/dist/machine-info.js +6 -3
- package/dist/marketplace-controller.js +8 -21
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/marketplace-serve.js +2 -4
- package/dist/memory-migration-controller.js +89 -0
- package/dist/memory-migration-inspect.js +108 -0
- package/dist/memory-migration-journal.js +14 -0
- package/dist/memory-migration-protocol.js +14 -0
- package/dist/memory-migration-runtime.js +305 -0
- package/dist/memory-prune-diagnostics.js +60 -4
- package/dist/message-file-preview.js +11 -1
- package/dist/npm-command.js +5 -5
- package/dist/powershell-command.js +12 -0
- package/dist/project-skills/machine-hello-publisher.js +95 -6
- package/dist/prompt.js +9 -17
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +25 -4
- package/dist/serve.js +92 -10
- package/dist/token.js +1 -0
- package/dist/windows-scheduled-task.js +3 -3
- package/dist/windows-task-principal.js +26 -0
- package/dist/workspace-control-handler.js +16 -3
- package/dist/workspace.js +13 -2
- package/package.json +1 -1
- package/dist/marketplace-maintenance-bridge.js +0 -79
- package/dist/marketplace-maintenance-protocol.js +0 -40
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { lstat, readFile, readdir, stat } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
import { capMemoryForInject, MEMORY_INJECT_CAP } from "./prompt.js";
|
|
@@ -8,6 +8,7 @@ const INJECTION_MARKERS = [
|
|
|
8
8
|
"## [注入] 你的 MEMORY.md(索引,只读参考)\n",
|
|
9
9
|
];
|
|
10
10
|
const LOCAL_MEMORY_HASH_CAP_BYTES = 10 * 1024 * 1024;
|
|
11
|
+
export const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 2_000;
|
|
11
12
|
function errorCode(error) {
|
|
12
13
|
const code = error?.code;
|
|
13
14
|
return typeof code === "string" ? code : error instanceof Error ? error.name : "unknown";
|
|
@@ -39,30 +40,75 @@ async function inspectFile(path, signal) {
|
|
|
39
40
|
return { ...base, error_code: errorCode(error) };
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
async function inspectPendingDirectory(path, signal) {
|
|
44
|
+
let metadata;
|
|
45
|
+
try {
|
|
46
|
+
metadata = await lstat(path);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const code = errorCode(error);
|
|
50
|
+
return code === "ENOENT" ? { exists: false, entry_count: 0 } : { exists: false, error_code: code };
|
|
51
|
+
}
|
|
52
|
+
const base = { exists: true, mtime_ms: metadata.mtimeMs };
|
|
53
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory())
|
|
54
|
+
return { ...base, hash_skipped_reason: "not_regular_file" };
|
|
55
|
+
try {
|
|
56
|
+
const names = (await readdir(path, { withFileTypes: true }))
|
|
57
|
+
.filter(entry => entry.name.endsWith(".json"))
|
|
58
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
59
|
+
if (names.length > 128)
|
|
60
|
+
return { ...base, entry_count: names.length, error_code: "entry_limit" };
|
|
61
|
+
const hash = createHash("sha256");
|
|
62
|
+
let size = 0;
|
|
63
|
+
for (const entry of names) {
|
|
64
|
+
if (signal.aborted)
|
|
65
|
+
throw signal.reason;
|
|
66
|
+
if (!entry.isFile() || entry.isSymbolicLink())
|
|
67
|
+
return { ...base, entry_count: names.length, error_code: "invalid_entry" };
|
|
68
|
+
const fact = await inspectFile(join(path, entry.name), signal);
|
|
69
|
+
if (!fact.exists || fact.sha256 === undefined || fact.error_code !== undefined
|
|
70
|
+
|| fact.hash_skipped_reason !== undefined)
|
|
71
|
+
return { ...base, entry_count: names.length, error_code: "incomplete_entry" };
|
|
72
|
+
size += fact.size_bytes ?? 0;
|
|
73
|
+
hash.update(entry.name).update("\0").update(fact.sha256).update("\0");
|
|
74
|
+
}
|
|
75
|
+
return { ...base, size_bytes: size, entry_count: names.length, sha256: hash.digest("hex") };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return { ...base, error_code: errorCode(error) };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function withinLocalMemoryDiagnosticsDeadline(operation, timeoutMs) {
|
|
43
82
|
const controller = new AbortController();
|
|
44
83
|
let timer;
|
|
45
|
-
const inspection = Promise.all([
|
|
46
|
-
inspectFile(join(homeDir, "MEMORY.md"), controller.signal),
|
|
47
|
-
inspectFile(join(homeDir, "notes", "lessons.md"), controller.signal),
|
|
48
|
-
]).then(([memory, lessons]) => ({ memory, lessons }));
|
|
49
84
|
const timeout = new Promise((_resolve, reject) => {
|
|
50
85
|
timer = setTimeout(() => {
|
|
51
|
-
|
|
52
|
-
reject(Object.assign(new Error("local memory diagnostics timed out"), {
|
|
86
|
+
const error = Object.assign(new Error("local memory diagnostics timed out"), {
|
|
53
87
|
code: "diagnostics_timeout",
|
|
54
|
-
})
|
|
88
|
+
});
|
|
89
|
+
controller.abort(error);
|
|
90
|
+
reject(error);
|
|
55
91
|
}, Math.max(1, timeoutMs));
|
|
56
92
|
timer.unref?.();
|
|
57
93
|
});
|
|
58
94
|
try {
|
|
59
|
-
return await Promise.race([
|
|
95
|
+
return await Promise.race([operation(controller.signal), timeout]);
|
|
60
96
|
}
|
|
61
97
|
finally {
|
|
62
98
|
if (timer !== undefined)
|
|
63
99
|
clearTimeout(timer);
|
|
64
100
|
}
|
|
65
101
|
}
|
|
102
|
+
export async function inspectLocalMemoryFilesWithinDeadline(homeDir, timeoutMs) {
|
|
103
|
+
return withinLocalMemoryDiagnosticsDeadline(async (signal) => {
|
|
104
|
+
const [memory, lessons, pending] = await Promise.all([
|
|
105
|
+
inspectFile(join(homeDir, "MEMORY.md"), signal),
|
|
106
|
+
inspectFile(join(homeDir, "notes", "lessons.md"), signal),
|
|
107
|
+
inspectPendingDirectory(join(homeDir, ".crew", "memory", "pending"), signal),
|
|
108
|
+
]);
|
|
109
|
+
return { memory, lessons, pending };
|
|
110
|
+
}, timeoutMs);
|
|
111
|
+
}
|
|
66
112
|
export function localMemoryContentFacts(content, maxScanBytes = LOCAL_MEMORY_HASH_CAP_BYTES) {
|
|
67
113
|
const sizeBytes = Buffer.byteLength(content, "utf8");
|
|
68
114
|
if (sizeBytes > Math.max(0, maxScanBytes)) {
|
|
@@ -76,12 +122,12 @@ export function localMemoryContentFacts(content, maxScanBytes = LOCAL_MEMORY_HAS
|
|
|
76
122
|
lessons_reference_count: content.match(/\bnotes[\\/]lessons\.md\b/gi)?.length ?? 0,
|
|
77
123
|
};
|
|
78
124
|
}
|
|
79
|
-
export function analyzeLocalMemoryInjection(systemPrompt, sourceMemory) {
|
|
125
|
+
export function analyzeLocalMemoryInjection(systemPrompt, sourceMemory, query = "") {
|
|
80
126
|
const sourceBytes = Buffer.byteLength(sourceMemory, "utf8");
|
|
81
127
|
if (sourceMemory.length === 0) {
|
|
82
128
|
return { state: "empty_source", source_bytes: 0, bounded_bytes: 0, injected_bytes: 0 };
|
|
83
129
|
}
|
|
84
|
-
const bounded = capMemoryForInject(sourceMemory);
|
|
130
|
+
const bounded = capMemoryForInject(sourceMemory, undefined, Number.POSITIVE_INFINITY, query);
|
|
85
131
|
const marker = INJECTION_MARKERS.find((candidate) => systemPrompt.includes(candidate));
|
|
86
132
|
if (marker === undefined) {
|
|
87
133
|
return {
|
|
@@ -217,15 +263,50 @@ function crewMemoryReadAccess(command, paths) {
|
|
|
217
263
|
return null;
|
|
218
264
|
}
|
|
219
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Decode only the shell wrapper shape emitted by supported runtimes. The body must be one complete
|
|
268
|
+
* quoted argument. Double-quoted bodies may escape only a quote or backslash; shell expansion and
|
|
269
|
+
* concatenated quoting stay unknown so telemetry never guesses what the shell actually executed.
|
|
270
|
+
*/
|
|
271
|
+
function unwrapLiteralShellCommand(command) {
|
|
272
|
+
const trimmed = command.trim();
|
|
273
|
+
const wrapper = /^\/bin\/(?:(?:zsh|bash)\s+-lc|sh\s+-c)\s+([\s\S]+)$/u.exec(trimmed);
|
|
274
|
+
if (wrapper === null)
|
|
275
|
+
return trimmed;
|
|
276
|
+
const quoted = wrapper[1];
|
|
277
|
+
const quote = quoted[0];
|
|
278
|
+
if ((quote !== "'" && quote !== '"') || quoted.at(-1) !== quote || quoted.length < 2)
|
|
279
|
+
return null;
|
|
280
|
+
const body = quoted.slice(1, -1);
|
|
281
|
+
if (quote === "'")
|
|
282
|
+
return body.includes("'") ? null : body;
|
|
283
|
+
let decoded = "";
|
|
284
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
285
|
+
const character = body[index];
|
|
286
|
+
if (character === '"' || character === "$" || character === "`")
|
|
287
|
+
return null;
|
|
288
|
+
if (character !== "\\") {
|
|
289
|
+
decoded += character;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const escaped = body[index + 1];
|
|
293
|
+
if (escaped !== '"' && escaped !== "\\")
|
|
294
|
+
return null;
|
|
295
|
+
decoded += escaped;
|
|
296
|
+
index += 1;
|
|
297
|
+
}
|
|
298
|
+
return decoded;
|
|
299
|
+
}
|
|
220
300
|
function commandAccesses(command, paths) {
|
|
221
|
-
|
|
301
|
+
const unwrapped = unwrapLiteralShellCommand(command);
|
|
302
|
+
if (unwrapped === null || /[;|&\n]/.test(unwrapped))
|
|
222
303
|
return [];
|
|
223
304
|
const accesses = new Map();
|
|
224
|
-
const memoryRead = crewMemoryReadAccess(
|
|
305
|
+
const memoryRead = crewMemoryReadAccess(unwrapped, paths);
|
|
225
306
|
if (memoryRead !== null) {
|
|
226
307
|
accesses.set(`${memoryRead.target}:${memoryRead.relative_path ?? ""}:read`, memoryRead);
|
|
227
308
|
}
|
|
228
|
-
const expanded =
|
|
309
|
+
const expanded = unwrapped
|
|
229
310
|
.replaceAll("${CREW_HOME}", paths.homeDir)
|
|
230
311
|
.replaceAll("$CREW_HOME", paths.homeDir)
|
|
231
312
|
.replace(/["']/g, "");
|
|
@@ -38,6 +38,7 @@ export function logLocalMemoryDiagnosticsFailure(identity, phase, error) {
|
|
|
38
38
|
function snapshotFields(snapshot) {
|
|
39
39
|
if (snapshot === null)
|
|
40
40
|
return {};
|
|
41
|
+
const pending = snapshot.pending ?? { exists: false, entry_count: 0 };
|
|
41
42
|
return {
|
|
42
43
|
memory_exists: snapshot.memory.exists,
|
|
43
44
|
memory_size_bytes: snapshot.memory.size_bytes,
|
|
@@ -54,6 +55,12 @@ function snapshotFields(snapshot) {
|
|
|
54
55
|
lessons_hash_skipped_reason: snapshot.lessons.hash_skipped_reason,
|
|
55
56
|
lessons_error_code: snapshot.lessons.error_code,
|
|
56
57
|
lessons_markdown_heading_count: snapshot.lessons.markdown_heading_count,
|
|
58
|
+
pending_exists: pending.exists,
|
|
59
|
+
pending_size_bytes: pending.size_bytes,
|
|
60
|
+
pending_mtime_ms: pending.mtime_ms,
|
|
61
|
+
pending_sha256: pending.sha256,
|
|
62
|
+
pending_error_code: pending.error_code,
|
|
63
|
+
pending_entry_count: pending.entry_count,
|
|
57
64
|
};
|
|
58
65
|
}
|
|
59
66
|
function promptDelivery(runtime, stableProtocolRuntime) {
|
|
@@ -103,7 +110,7 @@ export function createLocalMemoryTelemetry(options) {
|
|
|
103
110
|
contextPrepared(context) {
|
|
104
111
|
try {
|
|
105
112
|
const sourceFacts = localMemoryContentFacts(context.memory);
|
|
106
|
-
const injectionFacts = analyzeLocalMemoryInjection(context.systemPrompt, context.memory);
|
|
113
|
+
const injectionFacts = analyzeLocalMemoryInjection(context.systemPrompt, context.memory, context.memoryQuery);
|
|
107
114
|
safeLog("local_memory.context_prepared", "本地记忆上下文已准备", {
|
|
108
115
|
execution_id: options.executionId,
|
|
109
116
|
agent_handle: options.agentHandle,
|
|
@@ -183,10 +190,13 @@ export function createLocalMemoryTelemetry(options) {
|
|
|
183
190
|
const lessonsChanged = beforeSnapshot !== null && afterSnapshot !== null
|
|
184
191
|
? localMemoryFileChanged(beforeSnapshot.lessons, afterSnapshot.lessons)
|
|
185
192
|
: undefined;
|
|
193
|
+
const pendingChanged = beforeSnapshot !== null && afterSnapshot !== null
|
|
194
|
+
? localMemoryFileChanged(beforeSnapshot.pending ?? { exists: false }, afterSnapshot.pending ?? { exists: false })
|
|
195
|
+
: undefined;
|
|
186
196
|
const accessSummary = accessObserver.summary();
|
|
187
|
-
const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined
|
|
197
|
+
const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined || pendingChanged === undefined
|
|
188
198
|
? undefined
|
|
189
|
-
: memoryChanged || lessonsChanged;
|
|
199
|
+
: memoryChanged || lessonsChanged || pendingChanged;
|
|
190
200
|
safeLog("local_memory.execution_observed", "本地记忆执行观察已完成", {
|
|
191
201
|
execution_id: options.executionId,
|
|
192
202
|
agent_handle: options.agentHandle,
|
|
@@ -200,6 +210,7 @@ export function createLocalMemoryTelemetry(options) {
|
|
|
200
210
|
executor_outcome: executorCompleted ? "succeeded" : "failed",
|
|
201
211
|
memory_changed: memoryChanged,
|
|
202
212
|
lessons_changed: lessonsChanged,
|
|
213
|
+
pending_changed: pendingChanged,
|
|
203
214
|
shared_files_changed_during_execution: sharedFilesChanged,
|
|
204
215
|
unexpected_shared_write: options.isMemoryPrune
|
|
205
216
|
? undefined
|
|
@@ -224,6 +235,12 @@ export function createLocalMemoryTelemetry(options) {
|
|
|
224
235
|
lessons_after_hash_skipped_reason: afterSnapshot.lessons.hash_skipped_reason,
|
|
225
236
|
lessons_after_error_code: afterSnapshot.lessons.error_code,
|
|
226
237
|
lessons_after_markdown_heading_count: afterSnapshot.lessons.markdown_heading_count,
|
|
238
|
+
pending_after_exists: afterSnapshot.pending?.exists ?? false,
|
|
239
|
+
pending_after_size_bytes: afterSnapshot.pending?.size_bytes,
|
|
240
|
+
pending_after_mtime_ms: afterSnapshot.pending?.mtime_ms,
|
|
241
|
+
pending_after_sha256: afterSnapshot.pending?.sha256,
|
|
242
|
+
pending_after_error_code: afterSnapshot.pending?.error_code,
|
|
243
|
+
pending_after_entry_count: afterSnapshot.pending?.entry_count ?? 0,
|
|
227
244
|
}),
|
|
228
245
|
});
|
|
229
246
|
},
|
package/dist/machine-info.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MAINTENANCE_CAPABILITY } from "./marketplace-maintenance-protocol.js";
|
|
2
1
|
import { retryHarnessProbe } from "./runtimes/harness-probe-retry.js";
|
|
3
2
|
import { MARKETPLACE_CAPABILITY } from "./marketplace-protocol.js";
|
|
4
3
|
/**
|
|
@@ -26,6 +25,7 @@ import { RUNTIME_HEALTH_PROBE_CAPABILITY } from "./runtime-health.js";
|
|
|
26
25
|
import { probeDeepSeekHarnessAcp, deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
|
|
27
26
|
import { daemonHome } from "./computer-profile.js";
|
|
28
27
|
import { daemonReleaseProvenance } from "./release-provenance.js";
|
|
28
|
+
import { MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY } from "./memory-migration-protocol.js";
|
|
29
29
|
export { executableRuntimes } from "./runtime-capabilities.js";
|
|
30
30
|
const execFileP = promisify(execFile);
|
|
31
31
|
export const PROJECT_WORKSPACES_CAPABILITY = "project_workspaces_v1";
|
|
@@ -34,13 +34,14 @@ export const SELF_TRAINING_EXECUTION_CAPABILITY = "self_training_execution_v1";
|
|
|
34
34
|
export const EXECUTION_LARGE_ATTACHMENTS_CAPABILITY = "execution_attachments_300m_v1";
|
|
35
35
|
export const PROJECT_SKILL_PROJECTION_CAPABILITY = "project_skill_projection_v2";
|
|
36
36
|
export const MACHINE_ENVIRONMENT_CAPABILITY = "machine_environment_v1";
|
|
37
|
+
export const MACHINE_HELLO_ACK_CAPABILITY = "machine_hello_ack_v1";
|
|
37
38
|
export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
|
|
38
39
|
resolverReady: true,
|
|
39
40
|
nativeRuntimeAdaptersReady: true,
|
|
40
41
|
});
|
|
41
42
|
export const DAEMON_CAPABILITIES = Object.freeze([
|
|
42
43
|
MARKETPLACE_CAPABILITY,
|
|
43
|
-
|
|
44
|
+
MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY,
|
|
44
45
|
"scheduled_job_v1",
|
|
45
46
|
"reply_origin_v1",
|
|
46
47
|
"origin_decision_v1",
|
|
@@ -64,6 +65,7 @@ export const DAEMON_CAPABILITIES = Object.freeze([
|
|
|
64
65
|
"agent_ability_runtime_assets_v1",
|
|
65
66
|
"agent_environments_v1",
|
|
66
67
|
MACHINE_ENVIRONMENT_CAPABILITY,
|
|
68
|
+
MACHINE_HELLO_ACK_CAPABILITY,
|
|
67
69
|
]);
|
|
68
70
|
/** 能力只按平台过滤,进程存活期间静态(CLAUDE.md 红线)。Windows 在 junction/copy 真机 smoke 通过前不宣告 Project Skills。 */
|
|
69
71
|
export const daemonCapabilities = (runtimePlatform = process.platform, readiness = NATIVE_PROJECT_WORKSPACE_READINESS) => {
|
|
@@ -72,9 +74,10 @@ export const daemonCapabilities = (runtimePlatform = process.platform, readiness
|
|
|
72
74
|
const nativeWorkspaceReady = nativeWorkspacePlatform
|
|
73
75
|
&& readiness.resolverReady
|
|
74
76
|
&& readiness.nativeRuntimeAdaptersReady;
|
|
75
|
-
return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => ((
|
|
77
|
+
return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => ((capability !== MARKETPLACE_CAPABILITY || nativeWorkspacePlatform)
|
|
76
78
|
&& (capability !== PROJECT_WORKSPACES_CAPABILITY || nativeWorkspaceReady)
|
|
77
79
|
&& (capability !== "message_file_preview_v1" || nativeWorkspacePlatform)
|
|
80
|
+
&& (nativeWorkspacePlatform || ![MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY].includes(capability))
|
|
78
81
|
&& (nativeWorkspacePlatform
|
|
79
82
|
|| (capability !== "project_skills_v1"
|
|
80
83
|
&& capability !== "project_skill_projection_v2"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MaintenanceRequestSchema, MaintenanceReportSchema } from "./marketplace-maintenance-protocol.js";
|
|
2
1
|
import { MarketplaceRequestSchema, marketplaceActive } from "./marketplace-protocol.js";
|
|
3
2
|
import { resolveMarketplaceScopes, runMarketplaceScope } from "./marketplace-runner.js";
|
|
4
3
|
export function createMarketplaceController(deps) {
|
|
@@ -12,31 +11,22 @@ export function createMarketplaceController(deps) {
|
|
|
12
11
|
const deadline = now() + 10 * 60_000;
|
|
13
12
|
let lease = null;
|
|
14
13
|
try {
|
|
15
|
-
const scopes =
|
|
14
|
+
const scopes = await (deps.resolve ?? resolveMarketplaceScopes)(request.targets, deps.agentsRoot);
|
|
16
15
|
// 查询不使用任务执行槽;只有写入运行时共用市场/插件目录时才等待独占保护。
|
|
17
|
-
if (request.
|
|
16
|
+
if (request.mode === "update") {
|
|
18
17
|
while (!stopped && now() < deadline && lease === null) {
|
|
19
18
|
lease = await deps.acquire();
|
|
20
19
|
if (!lease)
|
|
21
20
|
await wait();
|
|
22
21
|
}
|
|
23
22
|
if (!lease || stopped) {
|
|
24
|
-
operations.set(id, {
|
|
23
|
+
operations.set(id, { operationId: id, status: "failed", results: [], errorCode: "runtime_busy" });
|
|
25
24
|
return;
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
if (stopped || now() >= deadline)
|
|
29
28
|
throw new Error("operation_deadline");
|
|
30
|
-
operations.set(id, {
|
|
31
|
-
if (request.type === "marketplace:maintenance") {
|
|
32
|
-
if (!deps.maintain)
|
|
33
|
-
throw new Error("maintenance_unavailable");
|
|
34
|
-
const result = MaintenanceReportSchema.parse(await deps.maintain(request));
|
|
35
|
-
if (result.operationId !== id)
|
|
36
|
-
throw new Error("operation_mismatch");
|
|
37
|
-
operations.set(id, result);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
29
|
+
operations.set(id, { operationId: id, status: "running", results: [], errorCode: null });
|
|
40
30
|
const results = [];
|
|
41
31
|
for (const scope of scopes) {
|
|
42
32
|
if (stopped || now() >= deadline)
|
|
@@ -51,9 +41,7 @@ export function createMarketplaceController(deps) {
|
|
|
51
41
|
}
|
|
52
42
|
catch {
|
|
53
43
|
const previous = operations.get(id);
|
|
54
|
-
operations.set(id,
|
|
55
|
-
? { ...previous, status: "unknown", errorCode: "operation_unknown" }
|
|
56
|
-
: { ...previous, status: previous.results.some(x => !x.errorCode) ? "partial" : "failed", errorCode: "execution_failed" });
|
|
44
|
+
operations.set(id, { ...previous, status: previous.results.some(x => !x.errorCode) ? "partial" : "failed", errorCode: "execution_failed" });
|
|
57
45
|
}
|
|
58
46
|
finally {
|
|
59
47
|
try {
|
|
@@ -67,23 +55,22 @@ export function createMarketplaceController(deps) {
|
|
|
67
55
|
};
|
|
68
56
|
return {
|
|
69
57
|
handle(raw) {
|
|
70
|
-
const parsed = MarketplaceRequestSchema.
|
|
58
|
+
const parsed = MarketplaceRequestSchema.safeParse(raw);
|
|
71
59
|
if (!parsed.success || stopped)
|
|
72
60
|
return { ok: false, error: "invalid_request" };
|
|
73
61
|
const request = parsed.data;
|
|
74
|
-
const version = request.type === "marketplace:maintenance" ? { version: 2 } : {};
|
|
75
62
|
const existing = operations.get(request.operationId);
|
|
76
63
|
if (existing)
|
|
77
64
|
return { ok: true, data: existing };
|
|
78
65
|
if (request.action === "status")
|
|
79
|
-
return { ok: true, data: {
|
|
66
|
+
return { ok: true, data: { operationId: request.operationId, status: "unknown", results: [], errorCode: "operation_unknown" } };
|
|
80
67
|
if (process.platform !== "darwin" && process.platform !== "linux")
|
|
81
68
|
return { ok: false, error: "unsupported_platform" };
|
|
82
69
|
if (current !== null || [...operations.values()].some(x => marketplaceActive(x.status)))
|
|
83
70
|
return { ok: false, error: "runtime_busy" };
|
|
84
71
|
if (operations.size >= 100)
|
|
85
72
|
operations.delete(operations.keys().next().value);
|
|
86
|
-
const accepted = {
|
|
73
|
+
const accepted = { operationId: request.operationId, status: "queued", results: [], errorCode: null };
|
|
87
74
|
operations.set(request.operationId, accepted);
|
|
88
75
|
current = execute(request).finally(() => { current = null; });
|
|
89
76
|
return { ok: true, data: accepted };
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
// 与另一个进程中的 marketplace-protocol.ts 保持一致;跨进程契约测试校验摘要。
|
|
3
3
|
export const MARKETPLACE_CAPABILITY = "marketplace_update_v1";
|
|
4
4
|
export const MARKETPLACE = "nowwork-pm";
|
|
5
|
-
export const PLUGIN = "
|
|
5
|
+
export const PLUGIN = "aiinterview-plugin";
|
|
6
6
|
export const MarketplaceModeSchema = z.enum(["inspect", "refresh", "update"]);
|
|
7
7
|
export const MarketplaceStatusSchema = z.enum(["queued", "running", "completed", "partial", "failed", "unknown"]);
|
|
8
8
|
export const MarketplaceTargetSchema = z.object({
|
|
@@ -10,7 +10,7 @@ const Target = z.object({
|
|
|
10
10
|
version: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/),
|
|
11
11
|
source: z.object({ source: z.literal("git-subdir"),
|
|
12
12
|
url: z.literal("git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git"),
|
|
13
|
-
path: z.literal("plugins/
|
|
13
|
+
path: z.literal("plugins/aiinterview-plugin"), sha: z.string().regex(/^[a-f0-9]{40}$/) }),
|
|
14
14
|
});
|
|
15
15
|
export async function readRemoteMarketplace(runtime, registered, env, command) {
|
|
16
16
|
const source = validateMarketplaceSource(registered.url, registered.ref);
|
|
@@ -12,7 +12,7 @@ import { readMarketplaceSource } from "./marketplace-source.js";
|
|
|
12
12
|
export { MarketplaceFailure, runMarketplaceCommand } from "./marketplace-command.js";
|
|
13
13
|
const SELECTOR = `${PLUGIN}@${MARKETPLACE}`;
|
|
14
14
|
const SOURCE = "git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git";
|
|
15
|
-
const SOURCE_PATH = "plugins/
|
|
15
|
+
const SOURCE_PATH = "plugins/aiinterview-plugin";
|
|
16
16
|
const obj = (x) => x !== null && typeof x === "object" && !Array.isArray(x) ? x : {};
|
|
17
17
|
const parse = (text) => { try {
|
|
18
18
|
return JSON.parse(text);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { runMaintenanceBridge } from "./marketplace-maintenance-bridge.js";
|
|
2
1
|
import { createMarketplaceController } from "./marketplace-controller.js";
|
|
3
2
|
import { prepareDaemonRestart } from "./daemon-updater.js";
|
|
4
|
-
export function createServeMarketplace(config, localSlots, hostCoordinator
|
|
3
|
+
export function createServeMarketplace(config, localSlots, hostCoordinator) {
|
|
5
4
|
const controller = createMarketplaceController({ agentsRoot: config.agentsRoot,
|
|
6
|
-
maintain: request => runMaintenanceBridge(config, request, acquireEnv),
|
|
7
5
|
acquire: async () => {
|
|
8
6
|
// Marketplace updates can mutate shared Codex/Claude homes across profiles.
|
|
9
7
|
const local = await prepareDaemonRestart({ localSlots });
|
|
@@ -31,7 +29,7 @@ export function createServeMarketplace(config, localSlots, hostCoordinator, acqu
|
|
|
31
29
|
} });
|
|
32
30
|
return {
|
|
33
31
|
handle(raw, send) {
|
|
34
|
-
if (!raw || typeof raw !== "object" || !("type" in raw) ||
|
|
32
|
+
if (!raw || typeof raw !== "object" || !("type" in raw) || raw.type !== "marketplace:operation")
|
|
35
33
|
return false;
|
|
36
34
|
const { reqId, ...request } = raw;
|
|
37
35
|
if (typeof reqId === "string")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createRuntimeCancellation } from "./runtime-cancellation.js";
|
|
3
|
+
import { MemoryMigrationStartSchema } from "./memory-migration-protocol.js";
|
|
4
|
+
import { runMemoryMigration } from "./memory-migration-runtime.js";
|
|
5
|
+
import { inspectMemoryForMigration } from "./memory-migration-inspect.js";
|
|
6
|
+
const Inspect = MemoryMigrationStartSchema.pick({ reqId: true, protocolVersion: true, workspaceId: true,
|
|
7
|
+
agentId: true, handle: true, machineId: true }).extend({ type: z.literal("memory:migration:inspect"), executionId: z.string().uuid().optional() }).strict();
|
|
8
|
+
export function createMemoryMigrationController(input) {
|
|
9
|
+
let identity = null;
|
|
10
|
+
const runs = new Map();
|
|
11
|
+
return {
|
|
12
|
+
setIdentity(value) { identity = value; },
|
|
13
|
+
clearIdentity() { identity = null; },
|
|
14
|
+
getIdentity() { return identity === null ? null : { ...identity }; },
|
|
15
|
+
cancel(executionId) { const run = runs.get(executionId); if (!run)
|
|
16
|
+
return false; run.cancel(); return true; },
|
|
17
|
+
async handle(raw) {
|
|
18
|
+
const type = raw?.type;
|
|
19
|
+
if (type !== "memory:migration:start" && type !== "memory:migration:inspect")
|
|
20
|
+
return false;
|
|
21
|
+
const parsed = type === "memory:migration:start" ? MemoryMigrationStartSchema.safeParse(raw) : Inspect.safeParse(raw);
|
|
22
|
+
if (!parsed.success) {
|
|
23
|
+
const reqId = raw.reqId;
|
|
24
|
+
if (typeof reqId === "string" && reqId.length <= 200)
|
|
25
|
+
input.send({ type: "fs:result", reqId, ok: false, error: "memory_migration_invalid_request" });
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const request = parsed.data;
|
|
29
|
+
if (!identity || request.workspaceId !== identity.workspaceId || request.machineId !== identity.machineId) {
|
|
30
|
+
input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_identity_mismatch" });
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
let journal;
|
|
34
|
+
try {
|
|
35
|
+
journal = input.journal();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_journal_unavailable" });
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (request.type === "memory:migration:inspect") {
|
|
42
|
+
try {
|
|
43
|
+
const data = await inspectMemoryForMigration(input.config.agentsRoot, {
|
|
44
|
+
...request,
|
|
45
|
+
serverUrl: input.config.serverUrl,
|
|
46
|
+
}, journal);
|
|
47
|
+
input.send({ type: "fs:result", reqId: request.reqId, ok: true, data });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_inspection_failed" });
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const existing = runs.get(request.executionId);
|
|
55
|
+
if (existing) {
|
|
56
|
+
input.send({ type: "fs:result", reqId: request.reqId, ok: true, data: { kind: "running" } });
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (input.isAdmissionClosed?.()) {
|
|
60
|
+
input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_storage_busy" });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
const cancellation = createRuntimeCancellation();
|
|
64
|
+
const promise = runMemoryMigration(request, {
|
|
65
|
+
agentsRoot: input.config.agentsRoot, coordinator: input.coordinator, journal,
|
|
66
|
+
cancellation: cancellation.cancellation, send: input.send,
|
|
67
|
+
control: async (step, body) => {
|
|
68
|
+
const base = `/daemon/memory-migrations/${encodeURIComponent(request.agentId)}`;
|
|
69
|
+
const path = step === "history" ? `${base}/history` : `${base}/${request.migrationId}/${step === "needs-review" ? "review" : step}`;
|
|
70
|
+
const response = await fetch(new URL(path, input.config.serverUrl), {
|
|
71
|
+
method: step === "history" ? "GET" : "POST", signal: AbortSignal.timeout(10_000),
|
|
72
|
+
headers: { authorization: `Bearer ${input.config.machineToken}`, "content-type": "application/json" },
|
|
73
|
+
...(step === "history" ? {} : { body: JSON.stringify(body) }),
|
|
74
|
+
});
|
|
75
|
+
const result = await response.json();
|
|
76
|
+
if (!response.ok || result.success !== true)
|
|
77
|
+
throw new Error("migration_control_unconfirmed");
|
|
78
|
+
return result.data;
|
|
79
|
+
},
|
|
80
|
+
}).then(data => input.send({ type: "fs:result", reqId: request.reqId, ok: true, data }))
|
|
81
|
+
.catch(() => input.send({ type: "fs:result", reqId: request.reqId, ok: false, error: "memory_migration_failed" }))
|
|
82
|
+
.finally(() => runs.delete(request.executionId));
|
|
83
|
+
runs.set(request.executionId, { promise, cancel: cancellation.request });
|
|
84
|
+
return true;
|
|
85
|
+
},
|
|
86
|
+
async stop() { for (const run of runs.values())
|
|
87
|
+
run.cancel(); await Promise.allSettled([...runs.values()].map(run => run.promise)); },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { lstat, readFile, realpath, readdir } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { withMemoryAccess } from "./local-memory/migration-access.js";
|
|
5
|
+
import { MigrationStateSchema } from "./local-memory/migration-schema.js";
|
|
6
|
+
import { assertMigrationStateIntegrity } from "./local-memory/migration-state-invariants.js";
|
|
7
|
+
import { detectLocalMemoryStorageVersion, parseFullMemoryIndex } from "./local-memory/storage-format.js";
|
|
8
|
+
import { resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
|
|
9
|
+
import { readMemoryInventorySource, inventoryLimits } from "./local-memory/inventory-source.js";
|
|
10
|
+
import { isLocalMemoryInitializationIdentity, localMemoryInitializationIdentity } from "./local-memory/initialization-identity.js";
|
|
11
|
+
export async function inspectMemoryForMigration(agentsRoot, input, journal) {
|
|
12
|
+
const parent = await realpath(agentsRoot);
|
|
13
|
+
const path = join(parent, input.handle);
|
|
14
|
+
const missingBase = { workspaceId: input.workspaceId, agentId: input.agentId, machineId: input.machineId,
|
|
15
|
+
storageRootFingerprint: createHash("sha256").update(resolve(path)).digest("hex"),
|
|
16
|
+
ownerIdentity: String((await lstat(parent)).uid) };
|
|
17
|
+
const prior = input.executionId ? await journal?.get(input.executionId) : null;
|
|
18
|
+
const stoppedExecutionId = prior?.requestedRuntime === "memory_migration" && (prior.state === "completed" || prior.state === "interrupted") ? prior.executionId : null;
|
|
19
|
+
const missing = { ...missingBase, storageVersion: null, classification: "empty", emptyWithoutResidue: true, localState: null, issueCodes: [], stoppedExecutionId };
|
|
20
|
+
try {
|
|
21
|
+
await lstat(path);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
if (e.code === "ENOENT")
|
|
25
|
+
return missing;
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
const root = await realpath(path);
|
|
29
|
+
const base = { ...missingBase,
|
|
30
|
+
storageRootFingerprint: createHash("sha256").update(root).digest("hex"),
|
|
31
|
+
ownerIdentity: String((await lstat(root)).uid) };
|
|
32
|
+
const empty = { ...missing, ...base };
|
|
33
|
+
try {
|
|
34
|
+
return await withMemoryAccess(path, async () => {
|
|
35
|
+
let localState = null;
|
|
36
|
+
try {
|
|
37
|
+
const record = await readMemoryInventorySource(path, ".crew/memory/migration.v1.json", inventoryLimits.fileBytes);
|
|
38
|
+
const state = MigrationStateSchema.parse(JSON.parse(record.content));
|
|
39
|
+
assertMigrationStateIntegrity(state, state.identity);
|
|
40
|
+
const expectedInitialization = state.origin === "initialized"
|
|
41
|
+
? await localMemoryInitializationIdentity(path, input.agentId, {
|
|
42
|
+
workspaceId: input.workspaceId,
|
|
43
|
+
machineId: input.machineId,
|
|
44
|
+
})
|
|
45
|
+
: null;
|
|
46
|
+
const expectedLegacyInitialization = state.origin === "initialized"
|
|
47
|
+
? await localMemoryInitializationIdentity(path, input.agentId)
|
|
48
|
+
: null;
|
|
49
|
+
const localInitialization = expectedInitialization !== null
|
|
50
|
+
&& (isLocalMemoryInitializationIdentity(state.identity, expectedInitialization)
|
|
51
|
+
|| expectedLegacyInitialization !== null
|
|
52
|
+
&& isLocalMemoryInitializationIdentity(state.identity, expectedLegacyInitialization));
|
|
53
|
+
const serverBoundIdentity = state.identity.workspaceId === input.workspaceId
|
|
54
|
+
&& state.identity.machineId === input.machineId;
|
|
55
|
+
const operationIdentityValid = state.origin === "initialized" ? localInitialization : serverBoundIdentity;
|
|
56
|
+
if (state.identity.rootFingerprint !== base.storageRootFingerprint
|
|
57
|
+
|| state.identity.ownerIdentity !== base.ownerIdentity
|
|
58
|
+
|| state.identity.agentId !== input.agentId || !operationIdentityValid) {
|
|
59
|
+
throw new Error("migration_identity_mismatch");
|
|
60
|
+
}
|
|
61
|
+
localState = { migrationId: state.migrationId, phase: state.phase, storageVersion: state.storageVersion,
|
|
62
|
+
sourceDigest: state.sourceDigest, planDigest: state.planDigest, origin: state.origin,
|
|
63
|
+
issues: state.issues.map(issue => issue.reason),
|
|
64
|
+
initializationIdentity: localInitialization ? {
|
|
65
|
+
agentId: state.identity.agentId, migrationId: state.identity.migrationId,
|
|
66
|
+
workspaceId: state.identity.workspaceId, machineId: state.identity.machineId,
|
|
67
|
+
executionId: state.identity.executionId, sourceStorageVersion: state.identity.sourceStorageVersion,
|
|
68
|
+
targetStorageVersion: state.identity.targetStorageVersion,
|
|
69
|
+
rootFingerprint: state.identity.rootFingerprint, ownerIdentity: state.identity.ownerIdentity,
|
|
70
|
+
} : null };
|
|
71
|
+
if (state.phase === "applying" || state.phase === "repair_required")
|
|
72
|
+
return {
|
|
73
|
+
...empty, localState, classification: "repair_required", emptyWithoutResidue: false, issueCodes: ["migration_recovery_required"],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
if (e.code !== "ENOENT")
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
let source;
|
|
81
|
+
try {
|
|
82
|
+
source = await readMemoryInventorySource(path, "MEMORY.md", inventoryLimits.fileBytes);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
if (e.code !== "ENOENT")
|
|
86
|
+
throw e;
|
|
87
|
+
const names = await readdir(path);
|
|
88
|
+
const nonMetadata = names.filter(name => name !== ".crew");
|
|
89
|
+
return { ...empty, localState, emptyWithoutResidue: nonMetadata.length === 0 && localState === null,
|
|
90
|
+
classification: nonMetadata.length === 0 && localState === null ? "empty" : "repair_required", issueCodes: ["memory_source_missing"] };
|
|
91
|
+
}
|
|
92
|
+
const storageVersion = detectLocalMemoryStorageVersion(source.content);
|
|
93
|
+
if (storageVersion === 2) {
|
|
94
|
+
parseFullMemoryIndex(source.content);
|
|
95
|
+
return { ...base, storageVersion, classification: "valid_v2", emptyWithoutResidue: false, localState, issueCodes: [], stoppedExecutionId };
|
|
96
|
+
}
|
|
97
|
+
const eligibility = await resolveLocalMemoryV2Rollout({ workspace: { dir: path, memory: source.content },
|
|
98
|
+
serverUrl: input.serverUrl, handle: input.handle, requested: true });
|
|
99
|
+
return { ...base, storageVersion, classification: storageVersion === 1 && eligibility.enabled ? "valid_v1"
|
|
100
|
+
: eligibility.legacyFallback || eligibility.enabled ? "legacy" : "repair_required",
|
|
101
|
+
emptyWithoutResidue: false, localState, stoppedExecutionId, issueCodes: eligibility.enabled || eligibility.legacyFallback ? [] : ["memory_source_incompatible"] };
|
|
102
|
+
}, { maintenance: true, waitMs: 0 });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
const code = error instanceof Error && /^[a-z_]+$/u.test(error.message) ? error.message : "memory_inspection_failed";
|
|
106
|
+
return { ...empty, emptyWithoutResidue: false, classification: code === "memory_migration_access_busy" ? "busy" : "repair_required", issueCodes: [code] };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { createExecutionJournal } from "./execution-journal.js";
|
|
3
|
+
import { dslog } from "./slog.js";
|
|
4
|
+
/** 复用journal的监管/恢复实现;旧daemon只读普通目录,不会解析维护类型。 */
|
|
5
|
+
export function createMemoryMigrationJournal(agentsRoot) {
|
|
6
|
+
return createExecutionJournal(join(agentsRoot, ".crew", "memory-migration-runtime"), { retainCompleted: true });
|
|
7
|
+
}
|
|
8
|
+
export async function replayExecutionJournals(normal, maintenance) {
|
|
9
|
+
const [ordinary, migration] = await Promise.all([normal.replay(), maintenance.replay().catch(() => {
|
|
10
|
+
dslog("memory_migration.journal_unavailable", "迁移日志暂不可读,保留普通执行能力", { level: "ERROR" });
|
|
11
|
+
return [];
|
|
12
|
+
})]);
|
|
13
|
+
return [...ordinary, ...migration];
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { MigrationRecordMappingSchema } from "./local-memory/migration-schema.js";
|
|
3
|
+
export const MEMORY_MIGRATION_CAPABILITY = "memory_migration_v1";
|
|
4
|
+
export const MEMORY_STORAGE_V2_CAPABILITY = "memory_storage_v2";
|
|
5
|
+
export const MEMORY_STATE_GUARD_CAPABILITY = "memory_state_guard_v1";
|
|
6
|
+
export const MemoryMigrationStartSchema = z.object({
|
|
7
|
+
type: z.literal("memory:migration:start"), reqId: z.string().min(1).max(200), protocolVersion: z.literal(1),
|
|
8
|
+
workspaceId: z.string().uuid(), agentId: z.string().uuid(), handle: z.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/u),
|
|
9
|
+
machineId: z.string().uuid(), executionId: z.string().uuid(), migrationId: z.string().uuid(),
|
|
10
|
+
expectedRevision: z.number().int().nonnegative(), attemptKey: z.string().min(1).max(200),
|
|
11
|
+
sourceStorageVersion: z.union([z.literal(0), z.literal(1)]), targetStorageVersion: z.literal(2),
|
|
12
|
+
action: z.enum(["migrate", "recover"]),
|
|
13
|
+
recordMappings: z.array(MigrationRecordMappingSchema).max(10_000).optional(),
|
|
14
|
+
}).strict();
|