@nowcrew/daemon 0.6.88-beta.5 → 0.6.88-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/local-memory/candidate.js +43 -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 +13 -11
- package/dist/local-memory/read-cli.js +57 -8
- package/dist/local-memory/runtime.js +2 -2
- package/dist/local-memory/write.js +16 -48
- package/dist/main.js +0 -0
- package/dist/project-skills/capability.js +109 -0
- package/dist/project-skills/controller-convergence.js +57 -0
- package/dist/project-skills/projection-set-switch.js +419 -0
- package/dist/project-skills/projection-state-domain.js +170 -0
- package/dist/project-skills/projection-state-store.js +842 -0
- package/dist/project-skills/projection-state-transaction.js +322 -0
- package/dist/project-skills/projection-state.js +3 -0
- package/dist/project-skills/runtime-root-bootstrap.js +47 -0
- package/dist/project-skills/runtime-root-domain.js +268 -0
- package/dist/project-skills/runtime-root-gc.js +293 -0
- package/dist/project-skills/runtime-root-lease-artifact.js +46 -0
- package/dist/project-skills/runtime-root-leases.js +487 -0
- package/dist/project-skills/runtime-root-source-identity.js +60 -0
- package/dist/project-skills/runtime-root-startup.js +49 -0
- package/dist/project-skills/runtime-root-state-artifact-domain.js +143 -0
- package/dist/project-skills/runtime-root-state-index.js +356 -0
- package/dist/project-skills/runtime-root-store.js +722 -0
- package/dist/project-skills/serve-capability.js +28 -0
- package/dist/project-skills/serve-startup.js +37 -0
- package/package.json +9 -10
|
@@ -5,6 +5,7 @@ import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { parseArgs, TextDecoder } from "node:util";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { readPendingMemoryBody, searchPendingMemoryPage } from "./pending-query.js";
|
|
8
9
|
const OUTPUT_BYTES = 12 * 1024;
|
|
9
10
|
const FILE_BYTES = 32 * 1024 * 1024;
|
|
10
11
|
const FileSchema = z.string().min(1).refine((file) => {
|
|
@@ -33,14 +34,33 @@ const CommandSchema = z.discriminatedUnion("command", [
|
|
|
33
34
|
offset: z.coerce.number().int().min(0).max(Number.MAX_SAFE_INTEGER).default(0),
|
|
34
35
|
limit: z.coerce.number().int().min(1).max(4000).default(2000),
|
|
35
36
|
}).strict(),
|
|
37
|
+
z.object({
|
|
38
|
+
command: z.literal("search-pending"),
|
|
39
|
+
query: z.string().trim().min(1).max(256),
|
|
40
|
+
cursor: z.string().min(1).max(4096).optional(),
|
|
41
|
+
limit: z.coerce.number().int().min(1).max(40).default(10),
|
|
42
|
+
}).strict(),
|
|
43
|
+
z.object({
|
|
44
|
+
command: z.literal("read-pending"),
|
|
45
|
+
id: z.string().min(1).max(250).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
46
|
+
ifHash: Common.ifHash,
|
|
47
|
+
offset: z.coerce.number().int().min(0).max(Number.MAX_SAFE_INTEGER).default(0),
|
|
48
|
+
limit: z.coerce.number().int().min(1).max(4000).default(2000),
|
|
49
|
+
}).strict(),
|
|
36
50
|
]);
|
|
37
51
|
const USAGE = `只读记忆工具,默认输出 JSON,不修改任何记忆文件。
|
|
38
52
|
crew-memory search --query <关键词> [--file MEMORY.md|notes/文件.md] [--limit 10] [--after-line 0]
|
|
39
53
|
crew-memory read [--file MEMORY.md|notes/文件.md] [--offset 0] [--limit 2000]
|
|
40
|
-
|
|
54
|
+
crew-memory search-pending --query <关键词> [--cursor <游标>] [--limit 10]
|
|
55
|
+
crew-memory read-pending --id <候选ID> [--offset 0] [--limit 2000] [--if-hash <sha256>]
|
|
56
|
+
search 关键词按空白分隔,同一行须全部命中;返回完整文件中的行号和字符偏移。
|
|
41
57
|
读取的 offset/limit 按 Unicode 字符计数;用 nextOffset 继续,eof=true 表示结束。
|
|
42
58
|
后续页可传 --if-hash <前一页的 sha256>,文件变化时停止,避免混读不同版本。
|
|
43
|
-
|
|
59
|
+
search/read 读取 MEMORY.md 和 notes 原文,不过滤失效段落,也不扫描 pending。
|
|
60
|
+
search-pending 只查因容量延期的 create 候选;有 nextCursor 就继续,即使本页 matches 为空。
|
|
61
|
+
complete=true 才表示全部候选已检索完成且无遗漏;warnings 或非零退出码表示读取未完成,不能当作零命中。
|
|
62
|
+
read-pending 只返回候选正文;offset 非零时必须传 --if-hash,update/supersede 候选不可读取。
|
|
63
|
+
候选只供相关问题参考,未生效,不能覆盖有效规则;先读取当前有效索引与明细。`;
|
|
44
64
|
function inside(root, path) {
|
|
45
65
|
const value = relative(root, path);
|
|
46
66
|
return value !== ".." && !value.startsWith(`..${sep}`) && !isAbsolute(value);
|
|
@@ -152,8 +172,8 @@ function searchSource(text, sha256, command) {
|
|
|
152
172
|
nextAfterLine: hasMore ? matches.at(-1)?.line ?? null : null,
|
|
153
173
|
};
|
|
154
174
|
}
|
|
155
|
-
function readPage(text,
|
|
156
|
-
const metadata = {
|
|
175
|
+
function readPage(text, source, command) {
|
|
176
|
+
const metadata = { ...source, offset: command.offset, nextOffset: Number.MAX_SAFE_INTEGER, eof: false, content: "" };
|
|
157
177
|
let remaining = OUTPUT_BYTES - Buffer.byteLength(JSON.stringify(metadata), "utf8");
|
|
158
178
|
let position = 0;
|
|
159
179
|
let offset = 0;
|
|
@@ -175,8 +195,16 @@ function readPage(text, sha256, command) {
|
|
|
175
195
|
count += 1;
|
|
176
196
|
}
|
|
177
197
|
const eof = position === text.length;
|
|
198
|
+
if (!eof && count === 0)
|
|
199
|
+
throw new Error("memory_output_too_large");
|
|
178
200
|
return { ...metadata, content, eof, nextOffset: eof ? null : offset };
|
|
179
201
|
}
|
|
202
|
+
function writeResult(result, io) {
|
|
203
|
+
const output = JSON.stringify(result);
|
|
204
|
+
if (Buffer.byteLength(output, "utf8") > OUTPUT_BYTES)
|
|
205
|
+
throw new Error("memory_output_too_large");
|
|
206
|
+
io.out(output);
|
|
207
|
+
}
|
|
180
208
|
export async function runMemoryReadCommand(argv, io) {
|
|
181
209
|
let command;
|
|
182
210
|
try {
|
|
@@ -186,6 +214,7 @@ export async function runMemoryReadCommand(argv, io) {
|
|
|
186
214
|
query: { type: "string", short: "q" }, file: { type: "string" },
|
|
187
215
|
limit: { type: "string" }, offset: { type: "string" },
|
|
188
216
|
"after-line": { type: "string" }, "if-hash": { type: "string" },
|
|
217
|
+
id: { type: "string" }, cursor: { type: "string" },
|
|
189
218
|
json: { type: "boolean" }, help: { type: "boolean", short: "h" },
|
|
190
219
|
},
|
|
191
220
|
});
|
|
@@ -201,24 +230,44 @@ export async function runMemoryReadCommand(argv, io) {
|
|
|
201
230
|
...(afterLine === undefined ? {} : { afterLine }),
|
|
202
231
|
...(ifHash === undefined ? {} : { ifHash }),
|
|
203
232
|
});
|
|
233
|
+
if (command.command === "read-pending" && command.offset > 0 && command.ifHash === undefined) {
|
|
234
|
+
throw new Error("memory_hash_required");
|
|
235
|
+
}
|
|
204
236
|
}
|
|
205
237
|
catch {
|
|
206
|
-
io.err(`memory_arguments_invalid:
|
|
238
|
+
io.err(`memory_arguments_invalid: 参数不合法,记忆读取未完成。\n${USAGE}`);
|
|
207
239
|
return 2;
|
|
208
240
|
}
|
|
209
241
|
try {
|
|
242
|
+
if (!io.env.CREW_HOME)
|
|
243
|
+
throw new Error("memory_home_missing");
|
|
244
|
+
if (command.command === "search-pending") {
|
|
245
|
+
const result = await searchPendingMemoryPage({ agentDir: io.env.CREW_HOME, query: command.query,
|
|
246
|
+
...(command.cursor === undefined ? {} : { cursor: command.cursor }), limit: command.limit });
|
|
247
|
+
writeResult(result, io);
|
|
248
|
+
if (result.warnings.length > 0 || (!result.hasMore && !result.complete)) {
|
|
249
|
+
throw new Error("memory_pending_search_incomplete");
|
|
250
|
+
}
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
if (command.command === "read-pending") {
|
|
254
|
+
const { content, ...metadata } = await readPendingMemoryBody({ agentDir: io.env.CREW_HOME,
|
|
255
|
+
pendingId: command.id, ...(command.ifHash === undefined ? {} : { ifHash: command.ifHash }) });
|
|
256
|
+
writeResult(readPage(content, metadata, command), io);
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
210
259
|
const source = await readSource(io.env.CREW_HOME, command.file);
|
|
211
260
|
if (command.ifHash !== undefined && command.ifHash !== source.sha256)
|
|
212
261
|
throw new Error("memory_file_changed");
|
|
213
262
|
const result = command.command === "search"
|
|
214
263
|
? searchSource(source.text, source.sha256, command)
|
|
215
|
-
: readPage(source.text, source.sha256, command);
|
|
216
|
-
|
|
264
|
+
: readPage(source.text, { file: command.file, sha256: source.sha256 }, command);
|
|
265
|
+
writeResult(result, io);
|
|
217
266
|
return 0;
|
|
218
267
|
}
|
|
219
268
|
catch (error) {
|
|
220
269
|
const code = error.code;
|
|
221
|
-
const known = error instanceof Error && /^memory_[a-z_]+$/u.test(error.message) ? error.message : code ?? "memory_read_failed";
|
|
270
|
+
const known = error instanceof Error && /^(?:local_)?memory_[a-z_]+$/u.test(error.message) ? error.message : code ?? "memory_read_failed";
|
|
222
271
|
io.err(JSON.stringify({ error: known, message: "记忆读取未完成;检查路径、文件版本或大小,不要将此错误当作没有记忆。" }));
|
|
223
272
|
return 1;
|
|
224
273
|
}
|
|
@@ -69,8 +69,8 @@ outcome 只允许 no_change、proposed、conflict、failed。新增、更新、
|
|
|
69
69
|
\`\`\`
|
|
70
70
|
|
|
71
71
|
每个 changes 项须保留示例中的全部字段。operation=create|update|supersede;kind=procedural|semantic|episodic|preference;priority=high|medium|low;detailCategory=preferences|project-rules|testing|debugging|git|incidents|lessons。create 的 memoryId/baseRevision/supersedesMemoryId 必须为 null;update 按现有索引填写 memoryId/baseRevision,supersedesMemoryId 为 null;supersede 填 supersedesMemoryId/baseRevision,memoryId 为 null。
|
|
72
|
-
只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending
|
|
73
|
-
|
|
72
|
+
只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 归档中。容量暂存的 create 可按需检索参考但不能覆盖现行规则;有空间的后续 proposed 提交可提升。未应用的 update/supersede 不进入普通召回,必须重新核对目标版本后处理。
|
|
73
|
+
写入候选文件只表示提交候选,不能提前宣称已经记住或规则已经更新;daemon 在本轮结束后才处理,候选持久化与进入有效索引也不是同一结果。本结构化轮次覆盖前文提前清理工作日志的要求:保留 $CREW_TASK_LOG,不要清空或删除;daemon 将在处理成功后清理,失败时保留日志和候选用于排查或重试。
|
|
74
74
|
`;
|
|
75
75
|
}
|
|
76
76
|
/** Prevent ordinary V2 turns from racing the daemon-managed memory writer. */
|
|
@@ -2,29 +2,12 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { lstat, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import {
|
|
5
|
+
import { dslog } from "../slog.js";
|
|
6
|
+
import { LocalMemoryChangeSchema, LocalMemorySingleLineText, validateLocalMemoryChange, } from "./candidate.js";
|
|
6
7
|
import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderLocalMemoryIndexCard, selectLocalMemoryIndexEntries, } from "./index-budget.js";
|
|
7
8
|
import { persistPendingMemoryCandidate, readPendingMemoryCandidates, removePendingMemoryCandidate, } from "./pending.js";
|
|
8
|
-
const CategorySchema = z.enum([
|
|
9
|
-
"preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons",
|
|
10
|
-
]);
|
|
11
|
-
const SingleLineText = z.string().trim().min(1).max(500).regex(/^[^\r\n]+$/u);
|
|
12
|
-
const ChangeSchema = z.object({
|
|
13
|
-
operation: z.enum(["create", "update", "supersede"]),
|
|
14
|
-
memoryId: SingleLineText.regex(/^[^<>]+$/u).nullable(),
|
|
15
|
-
baseRevision: z.number().int().nonnegative().nullable(),
|
|
16
|
-
supersedesMemoryId: SingleLineText.regex(/^[^<>]+$/u).nullable(),
|
|
17
|
-
title: SingleLineText.max(120),
|
|
18
|
-
kind: z.enum(["procedural", "semantic", "episodic", "preference"]),
|
|
19
|
-
summary: SingleLineText,
|
|
20
|
-
triggers: z.array(SingleLineText.max(80)).min(1).max(16),
|
|
21
|
-
priority: z.enum(["high", "medium", "low"]),
|
|
22
|
-
detailCategory: CategorySchema,
|
|
23
|
-
detailBody: z.string().trim().min(1).max(12_000),
|
|
24
|
-
reviewAfter: SingleLineText.nullable(),
|
|
25
|
-
}).strict();
|
|
26
9
|
const StatusChangeSchema = z.object({
|
|
27
|
-
memoryId:
|
|
10
|
+
memoryId: LocalMemorySingleLineText,
|
|
28
11
|
expectedRevision: z.number().int().positive(),
|
|
29
12
|
expectedStatus: z.enum(["candidate", "active", "pinned", "stale", "superseded", "archived"]),
|
|
30
13
|
targetStatus: z.enum(["candidate", "active", "pinned", "stale", "archived"]),
|
|
@@ -35,7 +18,7 @@ const ResultSchema = z.object({
|
|
|
35
18
|
purpose: z.literal("memory_prune"),
|
|
36
19
|
baseCatalogRevision: z.number().int().nonnegative(),
|
|
37
20
|
outcome: z.enum(["no_change", "proposed", "conflict", "failed"]),
|
|
38
|
-
changes: z.array(
|
|
21
|
+
changes: z.array(LocalMemoryChangeSchema).max(50),
|
|
39
22
|
statusChanges: z.array(StatusChangeSchema).max(50),
|
|
40
23
|
}).strict();
|
|
41
24
|
const GENERATED_INDEX_PATTERN = /<!--\s*NOWWORK:MEMORY_INDEX:BEGIN\s+schema=1\s*-->[\s\S]*?<!--\s*NOWWORK:MEMORY_INDEX:END\s*-->/u;
|
|
@@ -43,8 +26,6 @@ const INDEX_HEADING = /^##\s+Knowledge Index\s*$/mu;
|
|
|
43
26
|
const NEXT_SECTION = /^##\s+(?!#)/mu;
|
|
44
27
|
const CARD_HEADING = /^###\s+(.+?)\s*$/gmu;
|
|
45
28
|
const FIELD_LINE = /^-\s+([a-z][a-z_]*)\s*:\s*(.*?)\s*$/iu;
|
|
46
|
-
const CONTROL_CHARACTER_PATTERN = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u;
|
|
47
|
-
const RESERVED_MEMORY_MARKER = /NOWWORK:(?:MEMORY_ENTRY|MEMORY_INDEX):/iu;
|
|
48
29
|
function codePointLength(value) {
|
|
49
30
|
return Array.from(value).length;
|
|
50
31
|
}
|
|
@@ -148,34 +129,12 @@ function replaceNote(content, id, replacement) {
|
|
|
148
129
|
const pattern = notePattern(id);
|
|
149
130
|
return pattern.test(content) ? content.replace(pattern, replacement) : null;
|
|
150
131
|
}
|
|
151
|
-
function containsReservedText(change) {
|
|
152
|
-
return RESERVED_MEMORY_MARKER.test([change.title, change.summary, ...change.triggers, change.detailBody].join("\n"));
|
|
153
|
-
}
|
|
154
132
|
function validateCandidate(result) {
|
|
155
133
|
if (result.outcome === "no_change" && (result.changes.length > 0 || result.statusChanges.length > 0)) {
|
|
156
134
|
throw new Error("local_memory_no_change_has_operations");
|
|
157
135
|
}
|
|
158
|
-
for (const change of result.changes)
|
|
159
|
-
|
|
160
|
-
if (containsLikelySecret(text))
|
|
161
|
-
throw new Error("local_memory_secret_candidate");
|
|
162
|
-
if (CONTROL_CHARACTER_PATTERN.test(text))
|
|
163
|
-
throw new Error("local_memory_control_character");
|
|
164
|
-
if (containsReservedText(change))
|
|
165
|
-
throw new Error("local_memory_reserved_marker");
|
|
166
|
-
if (change.operation === "create"
|
|
167
|
-
&& (change.memoryId !== null || change.baseRevision !== null || change.supersedesMemoryId !== null)) {
|
|
168
|
-
throw new Error("local_memory_create_revision_invalid");
|
|
169
|
-
}
|
|
170
|
-
if (change.operation === "update"
|
|
171
|
-
&& (change.memoryId === null || change.baseRevision === null || change.supersedesMemoryId !== null)) {
|
|
172
|
-
throw new Error("local_memory_update_revision_missing");
|
|
173
|
-
}
|
|
174
|
-
if (change.operation === "supersede"
|
|
175
|
-
&& (change.memoryId !== null || change.baseRevision === null || change.supersedesMemoryId === null)) {
|
|
176
|
-
throw new Error("local_memory_supersede_revision_missing");
|
|
177
|
-
}
|
|
178
|
-
}
|
|
136
|
+
for (const change of result.changes)
|
|
137
|
+
validateLocalMemoryChange(change);
|
|
179
138
|
}
|
|
180
139
|
export function parseLocalMemoryResult(raw) {
|
|
181
140
|
let value;
|
|
@@ -477,7 +436,16 @@ export async function applyLocalMemoryResult(input) {
|
|
|
477
436
|
// Pending create candidates are durable overflow, not dead ends. Promote only
|
|
478
437
|
// when the current catalog has room; supersede/update candidates stay pending
|
|
479
438
|
// until a fresh revision-aware decision can safely replay them.
|
|
480
|
-
const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir })
|
|
439
|
+
const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
|
|
440
|
+
if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
|
|
441
|
+
throw error;
|
|
442
|
+
// 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
|
|
443
|
+
dslog("local_memory.pending_promotion_skipped", "pending目录超出扫描预算,本轮跳过自动提升", {
|
|
444
|
+
level: "WARN", execution_id: input.executionId, prune_trace_id: input.pruneTraceId,
|
|
445
|
+
reason: "memory_pending_directory_over_budget",
|
|
446
|
+
});
|
|
447
|
+
return [];
|
|
448
|
+
});
|
|
481
449
|
for (const pending of pendingCandidatesOnDisk) {
|
|
482
450
|
const change = pending.candidate;
|
|
483
451
|
if (change.operation !== "create")
|
package/dist/main.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { PROJECT_SKILLS_CAPABILITY, PROJECT_SKILL_PROJECTION_V2_CAPABILITY, } from "./types.js";
|
|
2
|
+
export function projectSkillV2RolloutReady(platform, readiness) {
|
|
3
|
+
const platformReady = platform === "darwin"
|
|
4
|
+
|| platform === "linux"
|
|
5
|
+
|| (platform === "win32" && readiness.nativeWindowsSmokePassed);
|
|
6
|
+
return platformReady
|
|
7
|
+
&& readiness.runtimeRootStorePrimitiveReady
|
|
8
|
+
&& readiness.runtimeRootStoreRecoveryReady
|
|
9
|
+
&& readiness.leaseRecoveryReady
|
|
10
|
+
&& readiness.controllerReady
|
|
11
|
+
&& readiness.executionHandlerReady
|
|
12
|
+
&& readiness.codexAdapterReady
|
|
13
|
+
&& readiness.claudeAdapterReady;
|
|
14
|
+
}
|
|
15
|
+
export function effectiveProjectSkillCapabilities(capabilities, projectSkillsReady, projectSkillV2Ready) {
|
|
16
|
+
const projectSkillCapabilitiesReady = projectSkillsReady && projectSkillV2Ready;
|
|
17
|
+
if (!projectSkillCapabilitiesReady
|
|
18
|
+
&& !capabilities.includes(PROJECT_SKILLS_CAPABILITY)
|
|
19
|
+
&& !capabilities.includes(PROJECT_SKILL_PROJECTION_V2_CAPABILITY)
|
|
20
|
+
&& Object.isFrozen(capabilities))
|
|
21
|
+
return capabilities;
|
|
22
|
+
const filtered = capabilities.filter((capability) => capability !== PROJECT_SKILL_PROJECTION_V2_CAPABILITY
|
|
23
|
+
&& (projectSkillCapabilitiesReady || capability !== PROJECT_SKILLS_CAPABILITY));
|
|
24
|
+
if (!projectSkillCapabilitiesReady || !filtered.includes(PROJECT_SKILLS_CAPABILITY)) {
|
|
25
|
+
return Object.freeze(filtered);
|
|
26
|
+
}
|
|
27
|
+
const v1Index = filtered.indexOf(PROJECT_SKILLS_CAPABILITY);
|
|
28
|
+
return Object.freeze([
|
|
29
|
+
...filtered.slice(0, v1Index + 1),
|
|
30
|
+
PROJECT_SKILL_PROJECTION_V2_CAPABILITY,
|
|
31
|
+
...filtered.slice(v1Index + 1),
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
export function withProjectSkillCapabilityStatus(value, status) {
|
|
35
|
+
return {
|
|
36
|
+
...value,
|
|
37
|
+
projectSkillsStatus: status,
|
|
38
|
+
capabilities: status === "ready"
|
|
39
|
+
? value.capabilities
|
|
40
|
+
: effectiveProjectSkillCapabilities(value.capabilities, false, false),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const settleReadinessGate = async (operation) => {
|
|
44
|
+
try {
|
|
45
|
+
return await operation() === true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
export async function createProjectSkillConnectionCapabilitySnapshot(input) {
|
|
52
|
+
const runtimeRootStorePrimitiveReady = await settleReadinessGate(input.probeRuntimeRootStorePrimitive);
|
|
53
|
+
const controllerReady = await settleReadinessGate(input.initializeController);
|
|
54
|
+
const recoveryReady = await settleReadinessGate(input.recoverRuntimeRootsAndLeases);
|
|
55
|
+
const readiness = Object.freeze({
|
|
56
|
+
runtimeRootStorePrimitiveReady,
|
|
57
|
+
runtimeRootStoreRecoveryReady: recoveryReady,
|
|
58
|
+
leaseRecoveryReady: recoveryReady,
|
|
59
|
+
controllerReady,
|
|
60
|
+
executionHandlerReady: true,
|
|
61
|
+
codexAdapterReady: true,
|
|
62
|
+
claudeAdapterReady: true,
|
|
63
|
+
nativeWindowsSmokePassed: false,
|
|
64
|
+
});
|
|
65
|
+
const projectSkillsReady = projectSkillV2RolloutReady(input.platform, readiness);
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
capabilities: effectiveProjectSkillCapabilities(input.baseCapabilities, projectSkillsReady, projectSkillsReady),
|
|
68
|
+
projectSkillsReady,
|
|
69
|
+
readiness,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
export async function prepareProjectSkillConnectionCapabilities(input) {
|
|
73
|
+
const snapshot = await createProjectSkillConnectionCapabilitySnapshot({
|
|
74
|
+
...input,
|
|
75
|
+
baseCapabilities: [...input.baseCapabilities, ...await input.additionalCapabilities()],
|
|
76
|
+
});
|
|
77
|
+
input.applyReadiness(snapshot.projectSkillsReady);
|
|
78
|
+
return snapshot.capabilities;
|
|
79
|
+
}
|
|
80
|
+
export function createProjectSkillControllerReadiness(input) {
|
|
81
|
+
let status = "initializing";
|
|
82
|
+
let initialization = null;
|
|
83
|
+
const ensure = () => {
|
|
84
|
+
if (status === "ready")
|
|
85
|
+
return Promise.resolve(true);
|
|
86
|
+
if (initialization !== null)
|
|
87
|
+
return initialization;
|
|
88
|
+
status = "initializing";
|
|
89
|
+
const attempt = input.initialize().then(() => {
|
|
90
|
+
status = "ready";
|
|
91
|
+
return true;
|
|
92
|
+
}, (error) => {
|
|
93
|
+
status = "unavailable";
|
|
94
|
+
input.onUnavailable(error);
|
|
95
|
+
return false;
|
|
96
|
+
}).finally(() => {
|
|
97
|
+
if (initialization === attempt)
|
|
98
|
+
initialization = null;
|
|
99
|
+
input.onSettled();
|
|
100
|
+
});
|
|
101
|
+
initialization = attempt;
|
|
102
|
+
return attempt;
|
|
103
|
+
};
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
status: () => status,
|
|
106
|
+
ensure,
|
|
107
|
+
applyPreflight: (ready) => { status = ready ? "ready" : "unavailable"; },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ProjectProjectionError, } from "./reconciler.js";
|
|
2
|
+
export const MAX_REMEMBERED_PROJECT_SKILL_AGENTS = 64;
|
|
3
|
+
const compareBinary = (left, right) => {
|
|
4
|
+
if (left < right)
|
|
5
|
+
return -1;
|
|
6
|
+
if (left > right)
|
|
7
|
+
return 1;
|
|
8
|
+
return 0;
|
|
9
|
+
};
|
|
10
|
+
const copySnapshot = (snapshot) => Object.freeze({
|
|
11
|
+
bindings: Object.freeze(snapshot.bindings.map((binding) => Object.freeze({ ...binding }))),
|
|
12
|
+
generation: snapshot.generation,
|
|
13
|
+
});
|
|
14
|
+
export function createProjectSkillSnapshotEpoch(maxEntries = MAX_REMEMBERED_PROJECT_SKILL_AGENTS) {
|
|
15
|
+
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
16
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
17
|
+
}
|
|
18
|
+
const snapshots = new Map();
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
begin() {
|
|
21
|
+
snapshots.clear();
|
|
22
|
+
},
|
|
23
|
+
remember(handle, snapshot) {
|
|
24
|
+
const previous = snapshots.get(handle);
|
|
25
|
+
if (previous !== undefined && snapshot.generation < previous.generation)
|
|
26
|
+
return;
|
|
27
|
+
if (previous === undefined && snapshots.size >= maxEntries) {
|
|
28
|
+
throw new ProjectProjectionError("skill_projection_failed");
|
|
29
|
+
}
|
|
30
|
+
snapshots.set(handle, copySnapshot(snapshot));
|
|
31
|
+
},
|
|
32
|
+
forget(handle) {
|
|
33
|
+
snapshots.delete(handle);
|
|
34
|
+
},
|
|
35
|
+
entries() {
|
|
36
|
+
return Object.freeze([...snapshots.entries()]
|
|
37
|
+
.sort(([left], [right]) => compareBinary(left, right))
|
|
38
|
+
.map(([handle, snapshot]) => Object.freeze([handle, copySnapshot(snapshot)])));
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const stableErrorCode = (reason) => reason?.code === "skill_projection_snapshot_corrupt"
|
|
43
|
+
? "skill_projection_snapshot_corrupt"
|
|
44
|
+
: "skill_projection_failed";
|
|
45
|
+
export function toPathPrivateConvergenceResults(entries, results) {
|
|
46
|
+
return Object.freeze(entries.map(([handle], index) => {
|
|
47
|
+
const result = results[index];
|
|
48
|
+
if (result?.status === "fulfilled") {
|
|
49
|
+
return Object.freeze({ handle, status: "fulfilled" });
|
|
50
|
+
}
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
handle,
|
|
53
|
+
status: "rejected",
|
|
54
|
+
errorCode: stableErrorCode(result?.reason),
|
|
55
|
+
});
|
|
56
|
+
}));
|
|
57
|
+
}
|