@nowcrew/daemon 0.6.101-beta.6 → 0.6.101-beta.8
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/cloud-report-upload.js +10 -1
- package/dist/execution-journal.js +1 -3
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +1 -4
- package/dist/local-execution-prompt.js +3 -8
- package/dist/local-executor.js +55 -78
- package/dist/local-memory/index-budget.js +3 -9
- package/dist/local-memory/inventory-cli.js +8 -52
- package/dist/local-memory/inventory-source.js +8 -15
- package/dist/local-memory/pending-query.js +2 -5
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +2 -16
- package/dist/local-memory/prompt.js +5 -21
- package/dist/local-memory/read-cli.js +76 -98
- package/dist/local-memory/rollout.js +11 -79
- package/dist/local-memory/runtime.js +17 -191
- package/dist/local-memory/write.js +21 -190
- package/dist/local-memory-diagnostics.js +15 -96
- package/dist/local-memory-telemetry.js +3 -31
- package/dist/machine-info.js +0 -3
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/memory-prune-diagnostics.js +4 -74
- package/dist/message-file-preview.js +1 -11
- package/dist/prompt.js +17 -9
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +4 -25
- package/dist/serve.js +3 -38
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +3 -16
- package/dist/workspace.js +4 -23
- package/package.json +1 -1
- package/dist/local-memory/commit-telemetry.js +0 -30
- package/dist/local-memory/execution-workspace.js +0 -117
- package/dist/local-memory/full-index-search.js +0 -47
- package/dist/local-memory/initialization-identity.js +0 -36
- package/dist/local-memory/inventory-v2.js +0 -362
- package/dist/local-memory/legacy-adapter.js +0 -233
- package/dist/local-memory/migration-access.js +0 -242
- package/dist/local-memory/migration-cli.js +0 -54
- package/dist/local-memory/migration-io.js +0 -199
- package/dist/local-memory/migration-pending.js +0 -9
- package/dist/local-memory/migration-plan.js +0 -432
- package/dist/local-memory/migration-schema.js +0 -58
- package/dist/local-memory/migration-state-invariants.js +0 -65
- package/dist/local-memory/migration-store.js +0 -414
- package/dist/local-memory/migration-tool-entry.js +0 -21
- package/dist/local-memory/release-artifact-guard.js +0 -55
- package/dist/local-memory/source-path.js +0 -13
- package/dist/local-memory/storage-format.js +0 -249
- package/dist/local-memory/writer-mode.js +0 -13
- package/dist/memory-migration-controller.js +0 -89
- package/dist/memory-migration-inspect.js +0 -116
- package/dist/memory-migration-journal.js +0 -14
- package/dist/memory-migration-protocol.js +0 -14
- package/dist/memory-migration-runtime.js +0 -367
|
@@ -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);
|
|
@@ -7,25 +7,10 @@ import { dslog } from "./slog.js";
|
|
|
7
7
|
const TRACE_PATTERN = /\[memory-prune trace_id=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\]/i;
|
|
8
8
|
export const MEMORY_PRUNE_HASH_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
9
9
|
export const MEMORY_PRUNE_NOTES_MANIFEST_LIMIT = 128;
|
|
10
|
-
export const MEMORY_PRUNE_PENDING_MANIFEST_LIMIT = 128;
|
|
11
10
|
export const MEMORY_PRUNE_NOTE_CHANGE_LIMIT = 128;
|
|
12
11
|
function compareCodeUnits(left, right) {
|
|
13
12
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
14
13
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Release evidence is a task artifact, not a durable memory note. Keep this
|
|
17
|
-
* deliberately narrow: only conventional delivery-artifact filename families
|
|
18
|
-
* are blocked; unrelated note names are still handled by the normal candidate
|
|
19
|
-
* protocol instead of being deleted or rewritten.
|
|
20
|
-
*/
|
|
21
|
-
const RELEASE_ARTIFACT_NOTE_PATTERN = /^(?:release|publish|build|deploy|artifact)(?:[-_.].*)?\.md$/iu;
|
|
22
|
-
export function isReleaseArtifactNotePath(relativePath) {
|
|
23
|
-
const basename = relativePath.split("/").pop() ?? relativePath;
|
|
24
|
-
return RELEASE_ARTIFACT_NOTE_PATTERN.test(basename);
|
|
25
|
-
}
|
|
26
|
-
export function releaseArtifactNoteChanges(diff) {
|
|
27
|
-
return diff.changes.filter((change) => isReleaseArtifactNotePath(change.relative_path));
|
|
28
|
-
}
|
|
29
14
|
export function parseMemoryPruneTraceId(wakePrompt) {
|
|
30
15
|
return TRACE_PATTERN.exec(wakePrompt)?.[1] ?? null;
|
|
31
16
|
}
|
|
@@ -207,50 +192,6 @@ export async function inspectMemoryPruneNotes(homeDir, signal) {
|
|
|
207
192
|
...(signal?.aborted ? { error: "ABORT_ERR" } : {}),
|
|
208
193
|
};
|
|
209
194
|
}
|
|
210
|
-
export async function inspectMemoryPrunePending(homeDir, signal) {
|
|
211
|
-
const pendingDir = join(homeDir, ".crew", "memory", "pending");
|
|
212
|
-
try {
|
|
213
|
-
const metadata = await lstat(pendingDir);
|
|
214
|
-
if (metadata.isSymbolicLink())
|
|
215
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: "symbolic_link" };
|
|
216
|
-
if (!metadata.isDirectory())
|
|
217
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: "ENOTDIR" };
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
if (error.code === "ENOENT")
|
|
221
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false };
|
|
222
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
223
|
-
}
|
|
224
|
-
let names;
|
|
225
|
-
try {
|
|
226
|
-
names = (await readdir(pendingDir, { withFileTypes: true }))
|
|
227
|
-
.filter(entry => (entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".json"))
|
|
228
|
-
.map(entry => entry.name).sort(compareCodeUnits);
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
232
|
-
}
|
|
233
|
-
const selected = names.slice(0, MEMORY_PRUNE_PENDING_MANIFEST_LIMIT);
|
|
234
|
-
const files = [];
|
|
235
|
-
for (const name of selected) {
|
|
236
|
-
if (signal?.aborted)
|
|
237
|
-
break;
|
|
238
|
-
const path = join(pendingDir, name);
|
|
239
|
-
try {
|
|
240
|
-
if ((await lstat(path)).isSymbolicLink()) {
|
|
241
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, exists: false, error: "symbolic_link" });
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, exists: false, error: diagnosticError(error) });
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, ...await inspectFile(path, false, undefined, signal) });
|
|
250
|
-
}
|
|
251
|
-
return { files, total_count: names.length, omitted_count: Math.max(0, names.length - selected.length),
|
|
252
|
-
truncated: names.length > selected.length, ...(signal?.aborted ? { error: "ABORT_ERR" } : {}) };
|
|
253
|
-
}
|
|
254
195
|
async function inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal) {
|
|
255
196
|
const cwdIsHome = runDir === homeDir;
|
|
256
197
|
const [memory, lessons, cwdMemory, cwdLessons, workLog] = await Promise.all([
|
|
@@ -269,17 +210,15 @@ async function inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal
|
|
|
269
210
|
};
|
|
270
211
|
}
|
|
271
212
|
export async function inspectMemoryPruneFiles(homeDir, runDir, workLogPath, signal) {
|
|
272
|
-
const [fixedFiles, notesManifest
|
|
213
|
+
const [fixedFiles, notesManifest] = await Promise.all([
|
|
273
214
|
inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal),
|
|
274
215
|
inspectMemoryPruneNotes(homeDir, signal),
|
|
275
|
-
inspectMemoryPrunePending(homeDir, signal),
|
|
276
216
|
]);
|
|
277
|
-
return { ...fixedFiles, notes_manifest: notesManifest
|
|
217
|
+
return { ...fixedFiles, notes_manifest: notesManifest };
|
|
278
218
|
}
|
|
279
219
|
export async function inspectMemoryPruneFilesWithinDeadline(homeDir, runDir, workLogPath, timeoutMs) {
|
|
280
220
|
const fixedFiles = await withinMemoryPruneDiagnosticsDeadline((signal) => inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal), timeoutMs);
|
|
281
221
|
let notesManifest;
|
|
282
|
-
let pendingManifest;
|
|
283
222
|
try {
|
|
284
223
|
notesManifest = await withinMemoryPruneDiagnosticsDeadline((signal) => inspectMemoryPruneNotes(homeDir, signal), timeoutMs);
|
|
285
224
|
}
|
|
@@ -292,13 +231,7 @@ export async function inspectMemoryPruneFilesWithinDeadline(homeDir, runDir, wor
|
|
|
292
231
|
error: diagnosticError(error),
|
|
293
232
|
};
|
|
294
233
|
}
|
|
295
|
-
|
|
296
|
-
pendingManifest = await withinMemoryPruneDiagnosticsDeadline(signal => inspectMemoryPrunePending(homeDir, signal), timeoutMs);
|
|
297
|
-
}
|
|
298
|
-
catch (error) {
|
|
299
|
-
pendingManifest = { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
300
|
-
}
|
|
301
|
-
return { ...fixedFiles, notes_manifest: notesManifest, pending_manifest: pendingManifest };
|
|
234
|
+
return { ...fixedFiles, notes_manifest: notesManifest };
|
|
302
235
|
}
|
|
303
236
|
export async function withinMemoryPruneDiagnosticsDeadline(operation, timeoutMs) {
|
|
304
237
|
const controller = new AbortController();
|
|
@@ -428,7 +361,6 @@ export function evaluateMemoryPrunePostcondition(before, after, execution) {
|
|
|
428
361
|
&& (after.cwd_memory.lessons_reference_count ?? 0) > 0,
|
|
429
362
|
cwd_lessons_changed: !after.cwd_lessons.same_as_shared_path
|
|
430
363
|
&& fileChanged(before.cwd_lessons, after.cwd_lessons),
|
|
431
|
-
pending_changed: diffMemoryPruneNotes(before.pending_manifest ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }, after.pending_manifest ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }).change_count > 0,
|
|
432
364
|
};
|
|
433
365
|
let outcome;
|
|
434
366
|
if (!execution.executorCompleted)
|
|
@@ -456,8 +388,6 @@ export function evaluateMemoryPrunePostcondition(before, after, execution) {
|
|
|
456
388
|
outcome = "promoted_without_index_change";
|
|
457
389
|
else if (facts.shared_memory_changed)
|
|
458
390
|
outcome = "shared_memory_updated";
|
|
459
|
-
else if (facts.pending_changed)
|
|
460
|
-
outcome = "pending_updated";
|
|
461
391
|
else
|
|
462
392
|
outcome = "no_files_changed";
|
|
463
393
|
return { outcome, ...facts };
|
|
@@ -478,7 +408,7 @@ function memoryPruneFileFactFields(fileFacts) {
|
|
|
478
408
|
}
|
|
479
409
|
return fields;
|
|
480
410
|
}
|
|
481
|
-
export function memoryPruneSnapshotFields({ notes_manifest: _notesManifest,
|
|
411
|
+
export function memoryPruneSnapshotFields({ notes_manifest: _notesManifest, ...fileFacts }) {
|
|
482
412
|
return memoryPruneFileFactFields(fileFacts);
|
|
483
413
|
}
|
|
484
414
|
export function logMemoryPruneDiagnosticsFailure(input, traceId, phase, error) {
|
|
@@ -9,7 +9,6 @@ import { z } from "zod";
|
|
|
9
9
|
import { isInternalEnvironmentPath } from "./workspace-fs.js";
|
|
10
10
|
import { opaqueKey, safeKey } from "./workspace.js";
|
|
11
11
|
import { AgentHandleSchema } from "./execution-protocol.js";
|
|
12
|
-
import { withMemoryAccess } from "./local-memory/migration-access.js";
|
|
13
12
|
const MAX_BYTES = 256 * 1024;
|
|
14
13
|
const MAX_BINARY_BYTES = 8 * 1024 * 1024;
|
|
15
14
|
const MAX_CONCURRENT_READS = 4;
|
|
@@ -232,16 +231,7 @@ export async function readSharedMessageFile(agentsRoot, rawRequest, registry) {
|
|
|
232
231
|
throw new SharedFileError("file_preview_busy");
|
|
233
232
|
activeReads++;
|
|
234
233
|
try {
|
|
235
|
-
|
|
236
|
-
const agentRoot = await realpath(resolve(agentsRoot, parsed.data.handle)).catch((error) => {
|
|
237
|
-
if (error.code === "ENOENT")
|
|
238
|
-
return undefined;
|
|
239
|
-
throw error;
|
|
240
|
-
});
|
|
241
|
-
// 记忆保护只用于该Agent根内资料,独立项目文件仍按原执行授权读取。
|
|
242
|
-
return await (agentRoot && inside(agentRoot, candidate.path)
|
|
243
|
-
? withMemoryAccess(agentRoot, () => readCandidate(candidate))
|
|
244
|
-
: readCandidate(candidate));
|
|
234
|
+
return await readCandidate(await resolveCandidate(agentsRoot, parsed.data, registry));
|
|
245
235
|
}
|
|
246
236
|
catch (error) {
|
|
247
237
|
if (error instanceof SharedFileError)
|
package/dist/prompt.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* claim-before-work、freshness/draft、"做完所有事再停"、inbox notice 语义、
|
|
6
6
|
* 分层记忆与压缩安全、协作礼仪。措辞为本项目原创。
|
|
7
7
|
*/
|
|
8
|
-
import { detectLocalMemoryStorageVersion, selectLegacyMemoryPreview, selectLocalMemoryPreview, } from "./local-memory/storage-format.js";
|
|
9
8
|
/** work-log 注入上限(字符):防冷启动注入随文件膨胀。超限保头(目标/结论)+尾(最近进展),
|
|
10
9
|
* 截中段并提示读全文——全文仍在盘上,只限"默认推送量"不限"可获取量"。纯函数。 */
|
|
11
10
|
export const WORKLOG_INJECT_CAP = 8000;
|
|
@@ -68,14 +67,23 @@ export function capWorkLogForInject(workLog, cap = WORKLOG_INJECT_CAP) {
|
|
|
68
67
|
const tail = workLog.slice(-WORKLOG_TAIL);
|
|
69
68
|
return `${head}\n…(work-log 过长已截去中段,全文用 Read 读 $CREW_TASK_LOG)\n${tail}`;
|
|
70
69
|
}
|
|
71
|
-
export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY
|
|
72
|
-
if (
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY) {
|
|
71
|
+
if (memory.length <= cap && Buffer.byteLength(memory, "utf8") <= maxBytes)
|
|
72
|
+
return memory;
|
|
73
|
+
const notice = "[MEMORY.md preview truncated; use crew-memory search/read for more.]\n";
|
|
74
|
+
const noticeBytes = Buffer.byteLength(notice, "utf8");
|
|
75
|
+
if (notice.length > cap || noticeBytes > maxBytes)
|
|
76
|
+
return "";
|
|
77
|
+
let end = 0;
|
|
78
|
+
let bytes = noticeBytes;
|
|
79
|
+
for (const character of memory) {
|
|
80
|
+
const size = Buffer.byteLength(character, "utf8");
|
|
81
|
+
if (notice.length + end + character.length > cap || bytes + size > maxBytes)
|
|
82
|
+
break;
|
|
83
|
+
end += character.length;
|
|
84
|
+
bytes += size;
|
|
77
85
|
}
|
|
78
|
-
return memory;
|
|
86
|
+
return notice + memory.slice(0, end);
|
|
79
87
|
}
|
|
80
88
|
export function buildSystemPrompt(ctx) {
|
|
81
89
|
const product = ctx.productName ?? "nowwork";
|
|
@@ -334,7 +342,7 @@ ${interactiveTaskRules}${freshnessRule}${externalReplyRule}${collaborationEtique
|
|
|
334
342
|
|
|
335
343
|
## Workspace 与分层记忆(CRITICAL — 索引+按需,配合上面的并行规则)
|
|
336
344
|
你的持久记忆在 \`$CREW_HOME\`(跨你所有任务共享),按“常驻索引 + 功能明细 + 当前工作”分层:
|
|
337
|
-
1. **MEMORY.md = 常驻索引 +
|
|
345
|
+
1. **MEMORY.md = 常驻索引 + 角色**:提示词末尾可能只有它的有界预览,预算不足时也可能省略。预览未命中不等于没有记忆。索引条目包含 \`file/type/scope/summary/triggers/priority/updated\`,其中 file 是相对于 \`$CREW_HOME\` 的路径(例如 \`notes/testing.md\`);需要历史约束、经验或偏好时先检索完整索引,再按需分段读取。
|
|
338
346
|
2. **notes/<topic>.md = 功能明细**:按用户偏好、项目规则、测试、调试、Git、事故复盘等主题拆分。优先读取与当前任务匹配的文件;\`notes/lessons.md\` 只保留未分类经验和历史兼容内容,不是所有新经验的默认目的地。普通任务默认只读不写,但当前用户明确要求时可以按最小范围更新;任务结束后的专门收尾执行仍按收尾指令复核。
|
|
339
347
|
- 对旧版 MEMORY.md,如果条目没有完整结构化字段但明确指向 \`notes/<file>.md\`,把它视为 legacy 索引;仍需结合条目标题/链接和当前请求判断相关性,不要因为缺少 summary/triggers 就假定正文已注入。
|
|
340
348
|
3. **本任务 work-log = \`$CREW_TASK_LOG\`**:每任务一份(别的运行不会碰),记当前阶段/下一步/卡点/关键结论。系统已把它的当前内容注入末尾让你续上;有进展就更新它——这是你**唯一**该在任务中持续写的记忆文件。**work-log 是现状快照,不是流水账**:保持 ≤100 行,更新时重写全文替换过时内容,不要无限追加(过长会被注入截断)。
|
package/dist/runner.js
CHANGED
|
@@ -80,7 +80,7 @@ async function runAgentWithMachineEnvironment(config, input, onActivity = defaul
|
|
|
80
80
|
? { scheduledExternalNotificationPolicy: input.scheduled.externalNotificationPolicy }
|
|
81
81
|
: {}),
|
|
82
82
|
...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
|
|
83
|
-
memory: capMemoryForInject(workspace.memory
|
|
83
|
+
memory: capMemoryForInject(workspace.memory),
|
|
84
84
|
...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
|
|
85
85
|
collaborationToolsEnabled: config.codexCollaborationTools && runtime === "codex",
|
|
86
86
|
localMemoryV2Enabled: workspace.localMemoryV2Enabled ?? localMemoryV2Enabled,
|
|
@@ -12,14 +12,6 @@ const InputSchema = z.object({ bin: z.string().min(1), args: z.array(z.string())
|
|
|
12
12
|
wakePrompt: z.string().min(1), sessionId: z.string().min(1).optional(), resume: z.boolean() }).strict();
|
|
13
13
|
class PreflightError extends Error {
|
|
14
14
|
}
|
|
15
|
-
// Claude waits for every merged SessionStart hook before acknowledging SDK
|
|
16
|
-
// initialization. Those hooks may legitimately outlive the old 30s adapter
|
|
17
|
-
// deadline. Cap this phase below the daemon's default 120s fail-closed startup
|
|
18
|
-
// window so wrapper failure propagation and supervisor-owned tree cleanup retain
|
|
19
|
-
// margin. The 100s cap leaves a nominal 20s outside this phase; this runner's
|
|
20
|
-
// local TERM/KILL cleanup is itself bounded to 2s.
|
|
21
|
-
// Resume and cold recovery share this one budget; hook activity never extends it.
|
|
22
|
-
export const CLAUDE_INITIALIZE_TIMEOUT_MS = 100_000;
|
|
23
15
|
function hookCommand(path, nonce) {
|
|
24
16
|
const source = import.meta.url.endsWith(".ts");
|
|
25
17
|
const hook = fileURLToPath(new URL(`./claude-session-start-hook.${source ? "ts" : "js"}`, import.meta.url));
|
|
@@ -60,9 +52,6 @@ async function bounded(promise, milliseconds, label) {
|
|
|
60
52
|
clearTimeout(timer);
|
|
61
53
|
}
|
|
62
54
|
}
|
|
63
|
-
export function waitForClaudeInitialize(initialized, timeoutMs = CLAUDE_INITIALIZE_TIMEOUT_MS) {
|
|
64
|
-
return bounded(initialized, timeoutMs, "claude_initialize_timeout");
|
|
65
|
-
}
|
|
66
55
|
async function attempt(input, sessionId, resume, options) {
|
|
67
56
|
const proofDir = await mkdtemp(join(tmpdir(), "nowwork-claude-session-"));
|
|
68
57
|
const proofPath = join(proofDir, "proof.json");
|
|
@@ -199,7 +188,7 @@ async function attempt(input, sessionId, resume, options) {
|
|
|
199
188
|
try {
|
|
200
189
|
try {
|
|
201
190
|
await send({ type: "control_request", request_id: initId, request: { subtype: "initialize" } });
|
|
202
|
-
await
|
|
191
|
+
await bounded(initialized.promise, options.initializeTimeoutMs ?? 30_000, "claude_initialize_timeout");
|
|
203
192
|
const proof = JSON.parse(await readFile(proofPath, "utf8"));
|
|
204
193
|
if (proof.nonce !== nonce || proof.sessionId !== sessionId || proof.source !== (resume ? "resume" : "startup")) {
|
|
205
194
|
throw new Error("claude_session_proof_mismatch");
|
|
@@ -249,25 +238,15 @@ async function attempt(input, sessionId, resume, options) {
|
|
|
249
238
|
}
|
|
250
239
|
export async function runClaudeThread(input, options = {}) {
|
|
251
240
|
const sessionId = input.sessionId ?? randomUUID();
|
|
252
|
-
const initializeBudgetMs = options.initializeTimeoutMs ?? CLAUDE_INITIALIZE_TIMEOUT_MS;
|
|
253
|
-
const now = options.now ?? (() => performance.now());
|
|
254
|
-
const initializeDeadline = now() + Math.max(0, initializeBudgetMs);
|
|
255
|
-
const runAttempt = (attemptSessionId, resume) => {
|
|
256
|
-
const remainingMs = Math.max(0, Math.ceil(initializeDeadline - now()));
|
|
257
|
-
if (remainingMs === 0)
|
|
258
|
-
return Promise.reject(new PreflightError("claude_native_session_unconfirmed"));
|
|
259
|
-
return attempt(input, attemptSessionId, resume, { ...options, initializeTimeoutMs: remainingMs });
|
|
260
|
-
};
|
|
261
241
|
try {
|
|
262
|
-
return await
|
|
242
|
+
return await attempt(input, sessionId, input.resume, options);
|
|
263
243
|
}
|
|
264
244
|
catch (error) {
|
|
265
245
|
// The failed attempt never received a business user turn or entered its
|
|
266
|
-
// model/tool loop. Native startup hooks may run again on cold recovery
|
|
267
|
-
// within the original aggregate initialize deadline.
|
|
246
|
+
// model/tool loop. Native startup hooks may run again on cold recovery.
|
|
268
247
|
if (!input.resume || !(error instanceof PreflightError || error instanceof ThreadDeliveryColdStartRequired))
|
|
269
248
|
throw error;
|
|
270
|
-
return
|
|
249
|
+
return attempt(input, randomUUID(), false, options);
|
|
271
250
|
}
|
|
272
251
|
}
|
|
273
252
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
package/dist/serve.js
CHANGED
|
@@ -14,7 +14,6 @@ import { reconcileExecutionJournal } from "./execution-recovery.js";
|
|
|
14
14
|
import { initReleaseSlog, reportDaemonConnectionClose, reportReleaseError } from "./release-channel-guard.js";
|
|
15
15
|
import { handleWorkspaceControlRequest } from "./workspace-control-handler.js";
|
|
16
16
|
import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
|
|
17
|
-
import { withMemoryAccess } from "./local-memory/migration-access.js";
|
|
18
17
|
import { normalizeScheduledContext } from "./scheduled-report.js";
|
|
19
18
|
import { formatDaemonLogLine } from "./log-format.js";
|
|
20
19
|
import { reportAgentRunComplete } from "./scheduled-run-report.js";
|
|
@@ -52,8 +51,6 @@ import { createServeMarketplace } from "./marketplace-serve.js";
|
|
|
52
51
|
import { createServeRuntimeToolUpdates } from "./runtime-tool-update-serve.js";
|
|
53
52
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
54
53
|
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
55
|
-
import { createMemoryMigrationController } from "./memory-migration-controller.js";
|
|
56
|
-
import { createMemoryMigrationJournal, replayExecutionJournals } from "./memory-migration-journal.js";
|
|
57
54
|
import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
|
|
58
55
|
export { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
59
56
|
// normalize.ts 的活动种类 → activity 枚举
|
|
@@ -79,8 +76,6 @@ export function serve(config, opts = {}) {
|
|
|
79
76
|
let reconnectTimer = null;
|
|
80
77
|
let stopPromise = null;
|
|
81
78
|
let executionJournal;
|
|
82
|
-
let migrationExecutionJournal;
|
|
83
|
-
let migrationJournalReady = false;
|
|
84
79
|
const injectedExecutionJournal = opts.execution?.journal;
|
|
85
80
|
const agentMemory = opts.execution?.dependencies?.agentMemory
|
|
86
81
|
?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
|
|
@@ -95,15 +90,6 @@ export function serve(config, opts = {}) {
|
|
|
95
90
|
startupGapMs: config.executionLimits.startupGapMs,
|
|
96
91
|
});
|
|
97
92
|
const localSlots = createSharedSlotManager(config.executionLimits);
|
|
98
|
-
const memoryMigrations = createMemoryMigrationController({ config, coordinator: hostCoordinator,
|
|
99
|
-
isAdmissionClosed: () => stopped || storageIdentityAdmission.isAdmissionClosed(),
|
|
100
|
-
journal: () => { if (!migrationJournalReady)
|
|
101
|
-
throw new Error("memory_migration_journal_unavailable"); return migrationExecutionJournal; }, send: frame => {
|
|
102
|
-
if (frame.type === "execution:completed")
|
|
103
|
-
completionRetransmitter.track(frame);
|
|
104
|
-
else if (ws?.readyState === WebSocket.OPEN)
|
|
105
|
-
ws.send(JSON.stringify(frame));
|
|
106
|
-
} });
|
|
107
93
|
const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
|
|
108
94
|
const concurrencyTelemetry = createExecutionConcurrencyTelemetry(sharedSlots, {
|
|
109
95
|
...(opts.execution?.concurrencySnapshotIntervalMs === undefined
|
|
@@ -286,7 +272,7 @@ export function serve(config, opts = {}) {
|
|
|
286
272
|
return null;
|
|
287
273
|
};
|
|
288
274
|
const sendSnapshot = async (reqId) => {
|
|
289
|
-
const entries = (await
|
|
275
|
+
const entries = (await executionJournal.replay()).map(snapshotEntry).filter((entry) => entry !== null);
|
|
290
276
|
safeExecutionSend(ExecutionSnapshotSchema.parse({
|
|
291
277
|
type: "execution:snapshot", protocolVersion: 1, reqId, entries,
|
|
292
278
|
}));
|
|
@@ -368,12 +354,6 @@ export function serve(config, opts = {}) {
|
|
|
368
354
|
machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
|
|
369
355
|
opts.onOpen?.(ws);
|
|
370
356
|
void sendSnapshot(`reconnect-${randomUUID()}`);
|
|
371
|
-
// 普通回执不等待维护日志IO;两类记录分别重放到同一发送器。
|
|
372
|
-
void migrationExecutionJournal.replay().then(entries => {
|
|
373
|
-
for (const entry of entries)
|
|
374
|
-
if (entry.completion && !entry.completionAcknowledged)
|
|
375
|
-
completionRetransmitter.track(entry.completion);
|
|
376
|
-
}).catch(() => dslog("memory_migration.journal_unavailable", "维护回执重放失败,普通回执继续", { level: "ERROR" }));
|
|
377
357
|
void executionJournal.replay()
|
|
378
358
|
.then((entries) => {
|
|
379
359
|
for (const entry of entries) {
|
|
@@ -453,8 +433,7 @@ export function serve(config, opts = {}) {
|
|
|
453
433
|
return;
|
|
454
434
|
if (frame.type === "execution:completion-ack") {
|
|
455
435
|
try {
|
|
456
|
-
|
|
457
|
-
await journal.acknowledgeCompletion(frame.executionId);
|
|
436
|
+
await executionJournal.acknowledgeCompletion(frame.executionId);
|
|
458
437
|
const attempts = completionRetransmitter.acknowledge(frame.executionId);
|
|
459
438
|
dslog("execution.completion_acknowledged", "execution completion ACK 已持久化", {
|
|
460
439
|
execution_id: frame.executionId,
|
|
@@ -480,8 +459,6 @@ export function serve(config, opts = {}) {
|
|
|
480
459
|
return;
|
|
481
460
|
}
|
|
482
461
|
if (frame.type === "execution:cancel") {
|
|
483
|
-
if (memoryMigrations.cancel(frame.executionId))
|
|
484
|
-
return;
|
|
485
462
|
if (!knownExecutionHashes.has(frame.executionId)
|
|
486
463
|
&& !executionRuns.has(frame.executionId)
|
|
487
464
|
&& await executionJournal.get(frame.executionId) === null)
|
|
@@ -714,7 +691,6 @@ export function serve(config, opts = {}) {
|
|
|
714
691
|
const execution = executeProtocol(config, spec, {
|
|
715
692
|
...opts.execution?.dependencies,
|
|
716
693
|
machineEnvironment: machineRuntime.provider,
|
|
717
|
-
...(memoryMigrations.getIdentity() === null ? {} : { controlIdentity: memoryMigrations.getIdentity() }),
|
|
718
694
|
capabilities: capabilities.executionRunner,
|
|
719
695
|
projectSkills: opts.execution?.dependencies?.projectSkills ?? initializedProjectSkills,
|
|
720
696
|
abilityRelease: opts.execution?.dependencies?.abilityRelease ?? agentAbilityRuntime.materializer,
|
|
@@ -805,8 +781,6 @@ export function serve(config, opts = {}) {
|
|
|
805
781
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
806
782
|
const r = msg;
|
|
807
783
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
808
|
-
if (r.workspaceId && r.machineId)
|
|
809
|
-
memoryMigrations.setIdentity({ workspaceId: r.workspaceId, machineId: r.machineId });
|
|
810
784
|
// 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。ackSupported() 依赖它判断是否重试,
|
|
811
785
|
// 必须在任何 republish() 之前就位;否则本轮 republish 看到空集合→不排重试→整套 ack/重连机制哑火。
|
|
812
786
|
serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
|
|
@@ -881,8 +855,6 @@ export function serve(config, opts = {}) {
|
|
|
881
855
|
}
|
|
882
856
|
catch { /* server 会按请求超时处理 */ } }))
|
|
883
857
|
return;
|
|
884
|
-
if (await memoryMigrations.handle(msg))
|
|
885
|
-
return;
|
|
886
858
|
if (await agentAbilityRuntime.tryHandleControlMessage(msg, serverCapabilities, (frame) => ws?.send(JSON.stringify(frame))))
|
|
887
859
|
return;
|
|
888
860
|
// 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
|
|
@@ -897,7 +869,7 @@ export function serve(config, opts = {}) {
|
|
|
897
869
|
try {
|
|
898
870
|
const data2 = req.type === "raft:inspect"
|
|
899
871
|
? await inspectRaftWorkspace(req.path)
|
|
900
|
-
: await
|
|
872
|
+
: await importRaftWorkspace(req.path, join(config.agentsRoot, req.handle));
|
|
901
873
|
if (req.type === "raft:import") {
|
|
902
874
|
log(`📦 已导入 raft 工作区 → ${req.handle}: 复制 ${data2.copied.length} 项`);
|
|
903
875
|
}
|
|
@@ -1290,7 +1262,6 @@ export function serve(config, opts = {}) {
|
|
|
1290
1262
|
return;
|
|
1291
1263
|
// server 能力随连接失效;下个连接的 ready 帧重新声明(防降级重连后沿用过期能力)。
|
|
1292
1264
|
serverCapabilities = new Set();
|
|
1293
|
-
memoryMigrations.clearIdentity();
|
|
1294
1265
|
completionRetransmitter.pause();
|
|
1295
1266
|
// 4001 = 控制面应用级「鉴权失败」关闭码(见 server control-plane.ts)。即使上面的
|
|
1296
1267
|
// error 帧因 close 抢先而丢失,也能据关闭码识别这是凭证失效——退避拉满,不再每秒热循环。
|
|
@@ -1343,9 +1314,6 @@ export function serve(config, opts = {}) {
|
|
|
1343
1314
|
writeStderr: (line) => process.stderr.write(line),
|
|
1344
1315
|
});
|
|
1345
1316
|
await runProjectSkillStartupCleanup(config.agentsRoot, executionJournal, dslog);
|
|
1346
|
-
migrationExecutionJournal = createMemoryMigrationJournal(config.agentsRoot);
|
|
1347
|
-
await migrationExecutionJournal.reconcileAfterRestart().then(() => { migrationJournalReady = true; })
|
|
1348
|
-
.catch(() => dslog("memory_migration.journal_unavailable", "迁移日志恢复失败,普通执行继续,迁移请求单独报错", { level: "ERROR" }));
|
|
1349
1317
|
await connect();
|
|
1350
1318
|
})();
|
|
1351
1319
|
const stop = () => {
|
|
@@ -1363,7 +1331,6 @@ export function serve(config, opts = {}) {
|
|
|
1363
1331
|
const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
|
|
1364
1332
|
const pending = [
|
|
1365
1333
|
machineRuntime.stop(),
|
|
1366
|
-
memoryMigrations.stop(),
|
|
1367
1334
|
webSocketClosed,
|
|
1368
1335
|
updateController.drain(),
|
|
1369
1336
|
marketplace.stop(),
|
|
@@ -1383,8 +1350,6 @@ export function serve(config, opts = {}) {
|
|
|
1383
1350
|
if (executionJournal !== undefined) {
|
|
1384
1351
|
await executionJournal.close({ signal: deadline.signal });
|
|
1385
1352
|
}
|
|
1386
|
-
if (migrationExecutionJournal !== undefined)
|
|
1387
|
-
await migrationExecutionJournal.close({ signal: deadline.signal });
|
|
1388
1353
|
}
|
|
1389
1354
|
finally {
|
|
1390
1355
|
deadline.dispose();
|
package/dist/token.js
CHANGED
|
@@ -7,7 +7,6 @@ export async function mintAgentToken(serverUrl, machineToken, handle, displayNam
|
|
|
7
7
|
},
|
|
8
8
|
// 把本轮唤醒的线程根钉到 per-launch 令牌:服务端据此让 task/消息默认绑/回当前线程。
|
|
9
9
|
body: JSON.stringify({
|
|
10
|
-
memoryStateGuardVersion: 1,
|
|
11
10
|
handle,
|
|
12
11
|
...(displayName ? { displayName } : {}),
|
|
13
12
|
...(options.wakeThreadRoot ? { wakeThreadRoot: options.wakeThreadRoot } : {}),
|
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
/** 控制面文件/技能查看的按需处理,独立于执行生命周期。 */
|
|
2
|
-
import {
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { join } from "node:path";
|
|
4
3
|
import { readSharedMessageFile } from "./message-file-preview.js";
|
|
5
4
|
import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
|
|
6
5
|
import { listSkills } from "./skills.js";
|
|
7
6
|
import { listRuntimeModels } from "./list-models.js";
|
|
8
|
-
import { withMemoryAccess } from "./local-memory/migration-access.js";
|
|
9
|
-
async function listProtectedWorkspace(root, path) {
|
|
10
|
-
try {
|
|
11
|
-
await lstat(root);
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
if (error.code !== "ENOENT")
|
|
15
|
-
throw error;
|
|
16
|
-
return { root: resolve(root), path: path.replace(/^\/+/, ""), entries: [] };
|
|
17
|
-
}
|
|
18
|
-
return withMemoryAccess(root, () => listWorkspace(root, path));
|
|
19
|
-
}
|
|
20
7
|
export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, models = listRuntimeModels) {
|
|
21
8
|
if (!raw || typeof raw !== "object")
|
|
22
9
|
return null;
|
|
@@ -28,8 +15,8 @@ export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, m
|
|
|
28
15
|
try {
|
|
29
16
|
const root = join(agentsRoot, request.handle);
|
|
30
17
|
const data = request.type === "fs:read-shared" ? await readSharedMessageFile(agentsRoot, raw, registry)
|
|
31
|
-
: request.type === "fs:list" ? await
|
|
32
|
-
: request.type === "fs:read" ? await
|
|
18
|
+
: request.type === "fs:list" ? await listWorkspace(root, request.path)
|
|
19
|
+
: request.type === "fs:read" ? await readWorkspaceFile(root, request.path)
|
|
33
20
|
: request.type === "skills:list" ? await listSkills(request.runtime)
|
|
34
21
|
: { models: await models(request.runtime ?? "") };
|
|
35
22
|
return { type: "fs:result", reqId: request.reqId, ok: true, data };
|
package/dist/workspace.js
CHANGED
|
@@ -8,23 +8,12 @@
|
|
|
8
8
|
*
|
|
9
9
|
* spawn runtime 时把 <ws>/.crew 置于 PATH 最前,于是 agent 的 `crew ...` 命中 wrapper。
|
|
10
10
|
*/
|
|
11
|
-
import { mkdir, writeFile, readFile, chmod, access, lstat
|
|
11
|
+
import { mkdir, writeFile, readFile, chmod, access, lstat } from "node:fs/promises";
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15
15
|
import { createRequire } from "node:module";
|
|
16
16
|
import { dslog } from "./slog.js";
|
|
17
|
-
import { MigrationStateSchema } from "./local-memory/migration-schema.js";
|
|
18
|
-
import { assertMigrationStateIntegrity } from "./local-memory/migration-state-invariants.js";
|
|
19
|
-
import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
|
|
20
|
-
export class LocalMemoryWorkspacePrepareError extends Error {
|
|
21
|
-
cause;
|
|
22
|
-
constructor(code, cause) {
|
|
23
|
-
super(code);
|
|
24
|
-
this.name = "LocalMemoryWorkspacePrepareError";
|
|
25
|
-
this.cause = cause;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
17
|
/** 文件系统安全的 taskKey:仅留 [\w.-],其余转 _,截断,避免路径穿越/超长。 */
|
|
29
18
|
export function safeKey(key) {
|
|
30
19
|
return key.replace(/[^\w.-]+/g, "_").replace(/^[-.]+/, "").slice(0, 80) || "default";
|
|
@@ -60,15 +49,7 @@ async function hasLocalMemoryV2Marker(agentDir) {
|
|
|
60
49
|
const stat = await lstat(markerPath);
|
|
61
50
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
62
51
|
return false;
|
|
63
|
-
|
|
64
|
-
return false;
|
|
65
|
-
const root = await realpath(agentDir);
|
|
66
|
-
const state = MigrationStateSchema.parse(JSON.parse(await readFile(join(memoryDir, "migration.v1.json"), "utf8")));
|
|
67
|
-
assertMigrationStateIntegrity(state, state.identity);
|
|
68
|
-
return state.phase === "committed" && state.storageVersion === 2
|
|
69
|
-
&& state.identity.rootFingerprint === createHash("sha256").update(root).digest("hex")
|
|
70
|
-
&& state.identity.ownerIdentity === String((await lstat(root)).uid)
|
|
71
|
-
&& detectLocalMemoryStorageVersion(await readFile(join(root, "MEMORY.md"), "utf8")) === 2;
|
|
52
|
+
return (await readFile(markerPath, "utf8")) === "1\n";
|
|
72
53
|
}
|
|
73
54
|
catch {
|
|
74
55
|
return false;
|
|
@@ -92,7 +73,7 @@ export async function prepareWorkspace(input) {
|
|
|
92
73
|
}
|
|
93
74
|
catch (error) {
|
|
94
75
|
logMemoryPrepareFailure(input, "memory_seed_write", error);
|
|
95
|
-
throw
|
|
76
|
+
throw error;
|
|
96
77
|
}
|
|
97
78
|
}
|
|
98
79
|
let memory = "";
|
|
@@ -102,7 +83,7 @@ export async function prepareWorkspace(input) {
|
|
|
102
83
|
}
|
|
103
84
|
catch (error) {
|
|
104
85
|
logMemoryPrepareFailure(input, "memory_read", error);
|
|
105
|
-
throw
|
|
86
|
+
throw error;
|
|
106
87
|
}
|
|
107
88
|
}
|
|
108
89
|
// 系统提示词按 execution 隔离;省略 identity 的兼容调用每次使用随机路径。
|
package/package.json
CHANGED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { dslog } from "../slog.js";
|
|
2
|
-
function boundedIds(ids) {
|
|
3
|
-
return {
|
|
4
|
-
value: (ids ?? []).slice(0, 100).join(","),
|
|
5
|
-
count: ids?.length ?? 0,
|
|
6
|
-
truncated: (ids?.length ?? 0) > 100,
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
export function logLocalMemoryCommitApplied(input) {
|
|
10
|
-
const { committed } = input;
|
|
11
|
-
const created = boundedIds(committed.createdIds);
|
|
12
|
-
const updated = boundedIds(committed.updatedIds);
|
|
13
|
-
const statusChanged = boundedIds(committed.statusChangedIds);
|
|
14
|
-
const pending = boundedIds(committed.pendingIds);
|
|
15
|
-
dslog("local_memory.commit_applied", "结构化本地记忆提交已验证", {
|
|
16
|
-
execution_id: input.executionId, agent_handle: input.agentHandle, prune_trace_id: input.pruneTraceId,
|
|
17
|
-
storage_version_before: committed.storageVersionBefore,
|
|
18
|
-
storage_version_after: committed.storageVersionAfter,
|
|
19
|
-
committed_state: committed.committedState,
|
|
20
|
-
writer_mode: committed.pendingReason === "legacy_requires_migration"
|
|
21
|
-
? "pending_only"
|
|
22
|
-
: committed.storageVersionAfter === 2 && committed.committedState ? "v2" : "legacy",
|
|
23
|
-
created_id_count: created.count, created_ids: created.value, created_ids_truncated: created.truncated,
|
|
24
|
-
updated_id_count: updated.count, updated_ids: updated.value, updated_ids_truncated: updated.truncated,
|
|
25
|
-
status_changed_id_count: statusChanged.count, status_changed_ids: statusChanged.value,
|
|
26
|
-
status_changed_ids_truncated: statusChanged.truncated,
|
|
27
|
-
pending_id_count: pending.count, pending_ids: pending.value, pending_ids_truncated: pending.truncated,
|
|
28
|
-
pending_reason: committed.pendingReason,
|
|
29
|
-
});
|
|
30
|
-
}
|