@nowcrew/daemon 0.6.88-beta.8 → 0.6.88

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.
@@ -1,100 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { z } from "zod";
3
- import { isRecallablePending, PendingIdSchema } from "./pending-schema.js";
4
- import { normalizePendingQuery, pendingRecallScore } from "./pending-match.js";
5
- import { pendingDirectorySnapshot, pendingSourceError, PENDING_FILE_BYTES, readPendingSource } from "./pending-source.js";
6
- const PAGE_FILES = 128;
7
- const PAGE_BYTES = 1_000_000;
8
- const OUTPUT_BYTES = 12 * 1024;
9
- const CursorSchema = z.object({
10
- v: z.literal(1), queryHash: z.string().regex(/^[a-f0-9]{64}$/u), version: z.string().regex(/^[a-f0-9]{64}$/u),
11
- after: z.string().min(1).max(255), hadWarnings: z.boolean(),
12
- }).strict();
13
- const SearchSchema = z.object({
14
- agentDir: z.string().min(1), query: z.string().trim().min(1).max(256),
15
- cursor: z.string().max(2048).optional(), limit: z.number().int().min(1).max(40).default(10),
16
- }).strict();
17
- const excerpt = (text, cap) => Array.from(text).slice(0, cap).join("");
18
- export async function searchPendingMemoryPage(input) {
19
- const parsed = SearchSchema.safeParse(input);
20
- if (!parsed.success)
21
- throw new Error("memory_pending_arguments_invalid");
22
- const args = parsed.data;
23
- const queryHash = createHash("sha256").update(normalizePendingQuery(args.query)).digest("hex");
24
- let cursor;
25
- if (args.cursor !== undefined) {
26
- try {
27
- cursor = CursorSchema.parse(JSON.parse(Buffer.from(args.cursor, "base64url").toString("utf8")));
28
- }
29
- catch {
30
- throw new Error("memory_pending_cursor_invalid");
31
- }
32
- if (cursor.queryHash !== queryHash)
33
- throw new Error("memory_pending_cursor_invalid");
34
- }
35
- const snapshot = await pendingDirectorySnapshot(args.agentDir);
36
- if (cursor && cursor.version !== snapshot.version)
37
- throw new Error("memory_pending_source_changed");
38
- const initial = cursor ? snapshot.names.indexOf(cursor.after) + 1 : 0;
39
- if (cursor && initial === 0)
40
- throw new Error("memory_pending_cursor_invalid");
41
- const matches = [];
42
- const warnings = [];
43
- let position = initial;
44
- let scannedFiles = 0;
45
- let scannedBytes = 0;
46
- // 为游标、错误与元数据预留空间;达到输出上限前停止消费来源,不跳过未输出的命中。
47
- let outputBytes = 1600;
48
- while (position < snapshot.names.length && scannedFiles < PAGE_FILES && scannedBytes + PENDING_FILE_BYTES <= PAGE_BYTES) {
49
- const name = snapshot.names[position];
50
- let source;
51
- let warning;
52
- try {
53
- source = await readPendingSource(args.agentDir, name);
54
- }
55
- catch (error) {
56
- const code = pendingSourceError(error);
57
- if (code === "memory_pending_source_changed" || code === "memory_pending_not_found")
58
- throw new Error("memory_pending_source_changed");
59
- warning = { file: name, code };
60
- }
61
- const match = source && isRecallablePending(source.candidate) && pendingRecallScore(source.candidate, args.query) > 0
62
- ? { pendingId: source.candidate.pendingId, sha256: source.sha256, title: source.candidate.candidate.title,
63
- summary: excerpt(source.candidate.candidate.summary, 180), triggers: source.candidate.candidate.triggers.slice(0, 4),
64
- operation: "create", state: "capacity_deferred" }
65
- : null;
66
- const addition = match ?? warning;
67
- const size = addition ? Buffer.byteLength(JSON.stringify(addition), "utf8") + 2 : 0;
68
- if (outputBytes + size > OUTPUT_BYTES)
69
- break;
70
- if (match)
71
- matches.push(match);
72
- if (warning)
73
- warnings.push(warning);
74
- outputBytes += size;
75
- scannedFiles += 1;
76
- scannedBytes += source?.bytes ?? PENDING_FILE_BYTES;
77
- position += 1;
78
- if (matches.length >= args.limit)
79
- break;
80
- }
81
- if ((await pendingDirectorySnapshot(args.agentDir)).version !== snapshot.version)
82
- throw new Error("memory_pending_source_changed");
83
- const hasMore = position < snapshot.names.length;
84
- const hadWarnings = cursor?.hadWarnings === true || warnings.length > 0;
85
- const nextCursor = hasMore ? Buffer.from(JSON.stringify({ v: 1, queryHash, version: snapshot.version,
86
- after: snapshot.names[position - 1], hadWarnings })).toString("base64url") : null;
87
- return { matches, warnings, scannedFiles, scannedBytes, hasMore, nextCursor, complete: !hasMore && !hadWarnings };
88
- }
89
- export async function readPendingMemoryBody(input) {
90
- if (!PendingIdSchema.safeParse(input.pendingId).success)
91
- throw new Error("memory_pending_id_invalid");
92
- const source = await readPendingSource(input.agentDir, `${input.pendingId}.json`);
93
- if (input.ifHash !== undefined && input.ifHash !== source.sha256)
94
- throw new Error("memory_pending_source_changed");
95
- if (!isRecallablePending(source.candidate))
96
- throw new Error("memory_pending_not_recallable");
97
- const candidate = source.candidate.candidate;
98
- return { pendingId: source.candidate.pendingId, sha256: source.sha256, title: candidate.title,
99
- operation: "create", state: "capacity_deferred", content: candidate.detailBody };
100
- }
@@ -1,29 +0,0 @@
1
- import { z } from "zod";
2
- import { LocalMemoryChangeSchema, validateLocalMemoryChange } from "./candidate.js";
3
- // 索引会按分隔符展开触发词;16 个最多 80 UTF-16 单元的输入可产生 0..640 项。
4
- const ArchivedMemoryChangeSchema = LocalMemoryChangeSchema.extend({
5
- triggers: z.array(LocalMemoryChangeSchema.shape.triggers.element).max(640)
6
- .refine((triggers) => triggers.reduce((length, trigger) => length + trigger.length, 0) <= 16 * 80),
7
- });
8
- export const PendingIdSchema = z.string().max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
9
- export const PendingMemorySchema = z.object({
10
- schemaVersion: z.literal(1), pendingId: PendingIdSchema,
11
- createdAt: z.string().min(1).max(100), reason: z.literal("index_over_budget"),
12
- source: z.object({ executionId: z.string().min(1).max(500), pruneTraceId: z.string().min(1).max(500).nullable() }).strict(),
13
- candidate: ArchivedMemoryChangeSchema,
14
- }).strict();
15
- export function parsePendingMemoryCandidate(value) {
16
- const parsed = PendingMemorySchema.safeParse(value);
17
- if (!parsed.success)
18
- throw new Error("local_memory_pending_invalid");
19
- validateLocalMemoryChange(parsed.data.candidate);
20
- return parsed.data;
21
- }
22
- export function isRecallablePending(value) {
23
- try {
24
- return parsePendingMemoryCandidate(value).candidate.operation === "create";
25
- }
26
- catch {
27
- return false;
28
- }
29
- }
@@ -1,126 +0,0 @@
1
- import { constants } from "node:fs";
2
- import { lstat, open, opendir, realpath } from "node:fs/promises";
3
- import { createHash } from "node:crypto";
4
- import { join } from "node:path";
5
- import { TextDecoder } from "node:util";
6
- import { parsePendingMemoryCandidate, PendingIdSchema } from "./pending-schema.js";
7
- export const PENDING_FILE_BYTES = 128 * 1024;
8
- const DIRECTORY_ENTRY_CAP = 10_000;
9
- export function pendingSourceError(error) {
10
- if (error instanceof Error && /^memory_pending_[a-z_]+$/u.test(error.message))
11
- return error.message;
12
- return "memory_pending_file_invalid";
13
- }
14
- export async function pendingReadDirectory(agentDir) {
15
- const root = await realpath(agentDir);
16
- let directory = root;
17
- for (const part of [".crew", "memory", "pending"]) {
18
- directory = join(directory, part);
19
- let stat;
20
- try {
21
- stat = await lstat(directory);
22
- }
23
- catch (error) {
24
- if (error.code === "ENOENT")
25
- return { root, directory: null };
26
- throw new Error("memory_pending_directory_unreadable");
27
- }
28
- if (!stat.isDirectory() || stat.isSymbolicLink() || await realpath(directory) !== directory) {
29
- throw new Error("memory_pending_path_invalid");
30
- }
31
- }
32
- return { root, directory };
33
- }
34
- export async function pendingFileNames(directory) {
35
- if (directory === null)
36
- return [];
37
- const names = [];
38
- let inspected = 0;
39
- try {
40
- for await (const item of await opendir(directory)) {
41
- if (++inspected > DIRECTORY_ENTRY_CAP)
42
- throw new Error("memory_pending_directory_over_budget");
43
- if (item.name.endsWith(".json"))
44
- names.push(item.name);
45
- }
46
- }
47
- catch (error) {
48
- if (error instanceof Error && error.message.startsWith("memory_pending_"))
49
- throw error;
50
- throw new Error("memory_pending_directory_unreadable");
51
- }
52
- return names.sort();
53
- }
54
- function version(stat) {
55
- return [stat.dev, stat.ino, stat.mode, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
56
- }
57
- export async function pendingDirectorySnapshot(agentDir) {
58
- const location = await pendingReadDirectory(agentDir);
59
- const names = await pendingFileNames(location.directory);
60
- const digest = createHash("sha256").update(location.root);
61
- if (location.directory !== null)
62
- digest.update(version(await lstat(location.directory)));
63
- for (const name of names) {
64
- try {
65
- digest.update(JSON.stringify([name, version(await lstat(join(location.directory, name)))]));
66
- }
67
- catch {
68
- throw new Error("memory_pending_source_changed");
69
- }
70
- }
71
- return { ...location, names, version: digest.digest("hex") };
72
- }
73
- export async function readPendingSource(agentDir, name, byteBudget = PENDING_FILE_BYTES) {
74
- if (!name.endsWith(".json") || !PendingIdSchema.safeParse(name.slice(0, -5)).success) {
75
- throw new Error("memory_pending_id_invalid");
76
- }
77
- const { directory } = await pendingReadDirectory(agentDir);
78
- if (directory === null)
79
- throw new Error("memory_pending_not_found");
80
- const path = join(directory, name);
81
- const before = await lstat(path).catch(() => { throw new Error("memory_pending_not_found"); });
82
- if (!before.isFile() || before.isSymbolicLink())
83
- throw new Error("memory_pending_path_invalid");
84
- const limit = Math.min(byteBudget, PENDING_FILE_BYTES);
85
- if (before.size > limit)
86
- throw new Error("memory_pending_file_over_budget");
87
- const handle = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
88
- try {
89
- const opened = await handle.stat();
90
- if (!opened.isFile() || version(opened) !== version(before))
91
- throw new Error("memory_pending_source_changed");
92
- const chunks = [];
93
- let bytes = 0;
94
- for (;;) {
95
- const chunk = Buffer.alloc(Math.min(16 * 1024, limit + 1 - bytes));
96
- const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
97
- if (bytesRead === 0)
98
- break;
99
- bytes += bytesRead;
100
- if (bytes > limit)
101
- throw new Error("memory_pending_file_over_budget");
102
- chunks.push(chunk.subarray(0, bytesRead));
103
- }
104
- const after = await handle.stat();
105
- const currentDirectory = await pendingReadDirectory(agentDir);
106
- const current = await lstat(path);
107
- if (currentDirectory.directory !== directory || version(after) !== version(opened) || version(current) !== version(opened)) {
108
- throw new Error("memory_pending_source_changed");
109
- }
110
- const buffer = Buffer.concat(chunks);
111
- let parsed;
112
- try {
113
- const raw = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
114
- parsed = parsePendingMemoryCandidate(JSON.parse(raw));
115
- }
116
- catch {
117
- throw new Error("memory_pending_file_invalid");
118
- }
119
- if (`${parsed.pendingId}.json` !== name)
120
- throw new Error("memory_pending_id_invalid");
121
- return { candidate: parsed, bytes, sha256: createHash("sha256").update(buffer).digest("hex") };
122
- }
123
- finally {
124
- await handle.close();
125
- }
126
- }