@veewo/claw 0.1.99 → 0.2.3
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 +83 -83
- package/dist/cli.js +636 -132
- package/dist/cli.js.map +1 -1
- package/dist/codex-driver.js +4 -0
- package/dist/codex-driver.js.map +1 -1
- package/dist/invocation-host.d.ts +1 -1
- package/dist/invocation-host.js +2 -1
- package/dist/invocation-host.js.map +1 -1
- package/dist/opencode-runner.js +2 -2
- package/dist/opencode-runner.js.map +1 -1
- package/package.json +41 -41
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
-
import { buildDirectWorkflowGuidance, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, shouldUsePlanHostIntegration, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob,
|
|
8
|
+
import { buildDirectWorkflowGuidance, buildKnowledgeDelegateDispatch, buildKnowledgeAssignmentTemplate, buildKnowledgeWriterAssignments, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, shouldUsePlanHostIntegration, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolvePlanEffectiveConfig, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, doneKnowledgeFinalizationJob, readKnowledgeFinalizationJob, waitForKnowledgeFinalizationJobReady, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, recordKnowledgeFinalizationResult, unbindSession, writePlan, } from "@veewo/claw-core";
|
|
9
9
|
import { buildCodexDriverEnvelope } from "./codex-driver.js";
|
|
10
10
|
import { checkCodexRuntime, resolveCodexSdkEntryPath } from "./codex-runtime.js";
|
|
11
11
|
import { extractLatestFinalAssistantMessage, extractTaskDoneConclusions, } from "./codex-transcript.js";
|
|
@@ -25,6 +25,7 @@ const TOP_LEVEL_COMMANDS = [
|
|
|
25
25
|
{ name: "subplan create [options]", summary: "Create a subplan nested under a parent task item." },
|
|
26
26
|
{ name: "switch-task --from <task> --to <task>", summary: "Switch the active task, carrying inherited context." },
|
|
27
27
|
{ name: "search [<query>] [options]", summary: "Recall project memory, truth, ADR, and declared docs." },
|
|
28
|
+
{ name: "knowledge <subcommand> [options]", summary: "Knowledge finalization lifecycle: wait, claim, done." },
|
|
28
29
|
{ name: "truth ingest [options]", summary: "Ingest a truth document under .claw/truth." },
|
|
29
30
|
{ name: "hook <event-name>", summary: "Emit host hook output (e.g. SessionStart)." },
|
|
30
31
|
];
|
|
@@ -311,6 +312,43 @@ const COMMAND_HELP = {
|
|
|
311
312
|
},
|
|
312
313
|
},
|
|
313
314
|
},
|
|
315
|
+
knowledge: {
|
|
316
|
+
usage: ["{script} knowledge <subcommand> [options]"],
|
|
317
|
+
description: "Session-bound lifecycle commands for a queued knowledge finalization job.",
|
|
318
|
+
subcommands: {
|
|
319
|
+
wait: {
|
|
320
|
+
usage: ["{script} knowledge wait --project-root <path> --finalize-id <id> [--timeout-ms <n>]"],
|
|
321
|
+
description: "Wait for Stop capture to create a knowledge finalization job. This command does not create or inspect session bindings.",
|
|
322
|
+
summary: "Wait until the finalization job exists.",
|
|
323
|
+
options: [
|
|
324
|
+
{ flag: "--project-root <path>", detail: "(required) Project that owns the pending finalization." },
|
|
325
|
+
{ flag: "--finalize-id <id>", detail: "(required) Stable finalization id returned by plan done." },
|
|
326
|
+
{ flag: "--timeout-ms <n>", detail: "Maximum wait in milliseconds (default 300000)." },
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
claim: {
|
|
330
|
+
usage: ["{script} knowledge claim --job <path>"],
|
|
331
|
+
description: "Claim a queued or retryable job after its executor session has been bound.",
|
|
332
|
+
summary: "Claim a session-bound finalization job.",
|
|
333
|
+
options: [{ flag: "--job <path>", detail: "(required) Finalization job JSON path." }],
|
|
334
|
+
},
|
|
335
|
+
done: {
|
|
336
|
+
usage: [
|
|
337
|
+
"{script} knowledge done --job <path> --claim-token <token> --status succeeded --result <text>",
|
|
338
|
+
"{script} knowledge done --job <path> --claim-token <token> --status failed --error <text>",
|
|
339
|
+
],
|
|
340
|
+
description: "Persist the terminal result for a claimed knowledge finalization job.",
|
|
341
|
+
summary: "Complete a claimed finalization job.",
|
|
342
|
+
options: [
|
|
343
|
+
{ flag: "--job <path>", detail: "(required) Finalization job JSON path." },
|
|
344
|
+
{ flag: "--claim-token <token>", detail: "(required) Token returned by knowledge claim." },
|
|
345
|
+
{ flag: "--status succeeded|failed", detail: "(required) Terminal execution status." },
|
|
346
|
+
{ flag: "--result <text>", detail: "Required when status is succeeded." },
|
|
347
|
+
{ flag: "--error <text>", detail: "Required when status is failed." },
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
314
352
|
truth: {
|
|
315
353
|
usage: ["{script} truth <subcommand> [options]"],
|
|
316
354
|
description: "Truth document ingestion under .claw/truth.",
|
|
@@ -356,6 +394,28 @@ const COMMAND_HELP = {
|
|
|
356
394
|
description: "Internal: warms the configured local persistent embedding session after context recovery without delaying SessionStart.",
|
|
357
395
|
options: [{ flag: "--cwd <dir>", detail: "(required) Project root." }],
|
|
358
396
|
},
|
|
397
|
+
"internal-daily-maintenance": {
|
|
398
|
+
usage: ["{script} internal-daily-maintenance --cwd <dir> [--session-key <key>] [--session-only]"],
|
|
399
|
+
description: "Internal: runs the lock-protected daily cleanup asynchronously after context recovery.",
|
|
400
|
+
options: [
|
|
401
|
+
{ flag: "--cwd <dir>", detail: "(required) Current workspace directory." },
|
|
402
|
+
{ flag: "--session-key <key>", detail: "Optional owner session key used for session workflow maintenance." },
|
|
403
|
+
{ flag: "--session-only", detail: "Only maintain the session workflow; do not touch project tasks." },
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
"internal-knowledge-sweep": {
|
|
407
|
+
usage: ["{script} internal-knowledge-sweep --cwd <dir>"],
|
|
408
|
+
description: "Internal: discovers retryable knowledge jobs and launches their detached finalizers.",
|
|
409
|
+
options: [{ flag: "--cwd <dir>", detail: "(required) Project root." }],
|
|
410
|
+
},
|
|
411
|
+
"internal-background-maintenance": {
|
|
412
|
+
usage: ["{script} internal-background-maintenance --cwd <dir> [--session-key <key>]"],
|
|
413
|
+
description: "Internal: runs non-blocking cleanup, embedding warmup, and knowledge job discovery in one worker.",
|
|
414
|
+
options: [
|
|
415
|
+
{ flag: "--cwd <dir>", detail: "(required) Current workspace directory." },
|
|
416
|
+
{ flag: "--session-key <key>", detail: "Optional owner session key used for session workflow maintenance." },
|
|
417
|
+
],
|
|
418
|
+
},
|
|
359
419
|
};
|
|
360
420
|
async function main() {
|
|
361
421
|
const args = process.argv.slice(2);
|
|
@@ -453,6 +513,9 @@ async function main() {
|
|
|
453
513
|
case "search":
|
|
454
514
|
await runSearch(args);
|
|
455
515
|
return;
|
|
516
|
+
case "knowledge":
|
|
517
|
+
runKnowledge(args);
|
|
518
|
+
return;
|
|
456
519
|
case "direct":
|
|
457
520
|
runDirect(args, effectiveHost);
|
|
458
521
|
return;
|
|
@@ -471,9 +534,30 @@ async function main() {
|
|
|
471
534
|
case "internal-knowledge-finalize":
|
|
472
535
|
await runInternalKnowledgeFinalize(args);
|
|
473
536
|
return;
|
|
537
|
+
case "internal-knowledge-capture":
|
|
538
|
+
await runInternalKnowledgeCapture(args, effectiveHost);
|
|
539
|
+
return;
|
|
540
|
+
case "internal-knowledge-complete":
|
|
541
|
+
runInternalKnowledgeComplete(args);
|
|
542
|
+
return;
|
|
543
|
+
case "internal-knowledge-claim":
|
|
544
|
+
runInternalKnowledgeClaim(args);
|
|
545
|
+
return;
|
|
546
|
+
case "internal-knowledge-fail":
|
|
547
|
+
runInternalKnowledgeFail(args);
|
|
548
|
+
return;
|
|
474
549
|
case "internal-embedding-warmup":
|
|
475
550
|
await runInternalEmbeddingWarmup(args);
|
|
476
551
|
return;
|
|
552
|
+
case "internal-daily-maintenance":
|
|
553
|
+
runInternalDailyMaintenance(args);
|
|
554
|
+
return;
|
|
555
|
+
case "internal-knowledge-sweep":
|
|
556
|
+
runInternalKnowledgeSweep(args);
|
|
557
|
+
return;
|
|
558
|
+
case "internal-background-maintenance":
|
|
559
|
+
await runInternalBackgroundMaintenance(args);
|
|
560
|
+
return;
|
|
477
561
|
default:
|
|
478
562
|
printTopLevelUsage();
|
|
479
563
|
process.exitCode = 1;
|
|
@@ -513,6 +597,87 @@ function runSession(args) {
|
|
|
513
597
|
removed: deleteSessionWorkflow(ownerSessionKey),
|
|
514
598
|
});
|
|
515
599
|
}
|
|
600
|
+
function runKnowledge(args) {
|
|
601
|
+
const subcommand = args.shift();
|
|
602
|
+
switch (subcommand) {
|
|
603
|
+
case "wait": {
|
|
604
|
+
const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
|
|
605
|
+
const finalizeId = readRequiredFlag(args, "--finalize-id");
|
|
606
|
+
const timeoutMs = readOptionalNumber(args, "--timeout-ms") ?? 300_000;
|
|
607
|
+
assertNoRemainingArgs(args, "knowledge wait");
|
|
608
|
+
const { jobPath, job } = waitForKnowledgeFinalizationJobReady({
|
|
609
|
+
project: resolveProjectContext(projectRoot),
|
|
610
|
+
finalizeId,
|
|
611
|
+
timeoutMs,
|
|
612
|
+
});
|
|
613
|
+
printJson({
|
|
614
|
+
ok: true,
|
|
615
|
+
command: "knowledge.wait",
|
|
616
|
+
finalizeId: job.finalizeId,
|
|
617
|
+
status: job.status,
|
|
618
|
+
jobPath,
|
|
619
|
+
});
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
case "claim": {
|
|
623
|
+
const jobPath = readRequiredFlag(args, "--job");
|
|
624
|
+
assertNoRemainingArgs(args, "knowledge claim");
|
|
625
|
+
const job = claimKnowledgeFinalizationJob(jobPath);
|
|
626
|
+
const assignments = job ? buildKnowledgeWriterAssignments(job) : [];
|
|
627
|
+
const templatePath = job
|
|
628
|
+
? path.join(path.dirname(jobPath), `${job.finalizeId}.assignments.json`)
|
|
629
|
+
: undefined;
|
|
630
|
+
if (job && templatePath) {
|
|
631
|
+
fs.writeFileSync(templatePath, `${JSON.stringify(buildKnowledgeAssignmentTemplate({
|
|
632
|
+
assignments,
|
|
633
|
+
finalizeId: job.finalizeId,
|
|
634
|
+
version: CLI_VERSION,
|
|
635
|
+
}), null, 2)}\n`, "utf-8");
|
|
636
|
+
}
|
|
637
|
+
printJson({
|
|
638
|
+
ok: true,
|
|
639
|
+
command: "knowledge.claim",
|
|
640
|
+
claimed: Boolean(job),
|
|
641
|
+
...(job ? {
|
|
642
|
+
finalizeId: job.finalizeId,
|
|
643
|
+
claimToken: job.claimToken,
|
|
644
|
+
projectRoot: job.projectRoot,
|
|
645
|
+
writer: job.writer ?? null,
|
|
646
|
+
planPath: job.planPath,
|
|
647
|
+
reportPath: job.reportPath,
|
|
648
|
+
assignments,
|
|
649
|
+
templatePath,
|
|
650
|
+
} : {}),
|
|
651
|
+
});
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
case "done": {
|
|
655
|
+
const jobPath = readRequiredFlag(args, "--job");
|
|
656
|
+
const claimToken = readRequiredFlag(args, "--claim-token");
|
|
657
|
+
const status = readRequiredFlag(args, "--status");
|
|
658
|
+
const result = readOptionalFlag(args, "--result");
|
|
659
|
+
const error = readOptionalFlag(args, "--error");
|
|
660
|
+
assertNoRemainingArgs(args, "knowledge done");
|
|
661
|
+
if (status === "succeeded") {
|
|
662
|
+
if (result === undefined) {
|
|
663
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge done --status succeeded requires --result.");
|
|
664
|
+
}
|
|
665
|
+
completeKnowledgeFinalizationJob(jobPath, result, claimToken);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (status === "failed") {
|
|
669
|
+
if (!error) {
|
|
670
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge done --status failed requires --error.");
|
|
671
|
+
}
|
|
672
|
+
failKnowledgeFinalizationJob(jobPath, error, claimToken);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unsupported knowledge done status "${status}".`);
|
|
676
|
+
}
|
|
677
|
+
default:
|
|
678
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown knowledge subcommand "${subcommand ?? ""}".`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
516
681
|
async function runPlan(args, effectiveHost) {
|
|
517
682
|
const subcommand = args.shift();
|
|
518
683
|
switch (subcommand) {
|
|
@@ -551,6 +716,19 @@ async function runPlan(args, effectiveHost) {
|
|
|
551
716
|
}
|
|
552
717
|
const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
|
|
553
718
|
const entersEndState = requestsPlanEndState(operations);
|
|
719
|
+
const current = entersEndState
|
|
720
|
+
? showPlan({ cwd: process.cwd(), ...target, ownerSessionKey })
|
|
721
|
+
: undefined;
|
|
722
|
+
const project = entersEndState ? tryResolveHookProject(process.cwd()) : null;
|
|
723
|
+
const effectiveWriter = current && project
|
|
724
|
+
? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
|
|
725
|
+
: undefined;
|
|
726
|
+
if (current
|
|
727
|
+
&& !current.plan.parentPlan
|
|
728
|
+
&& effectiveWriter?.executionPolicy === "subagent"
|
|
729
|
+
&& effectiveHost !== "codex") {
|
|
730
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex host.', { host: effectiveHost ?? null });
|
|
731
|
+
}
|
|
554
732
|
const queuePlanEndFinalization = entersEndState
|
|
555
733
|
? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
|
|
556
734
|
: undefined;
|
|
@@ -563,7 +741,18 @@ async function runPlan(args, effectiveHost) {
|
|
|
563
741
|
ownerSessionKey,
|
|
564
742
|
});
|
|
565
743
|
const completionRefresh = queuePlanEndFinalization?.(result.taskName);
|
|
566
|
-
|
|
744
|
+
const knowledgeDispatch = (current
|
|
745
|
+
&& project
|
|
746
|
+
&& effectiveHost === "codex"
|
|
747
|
+
&& !current.plan.parentPlan
|
|
748
|
+
&& effectiveWriter?.executionPolicy === "subagent"
|
|
749
|
+
&& result.knowledgeFinalizeId)
|
|
750
|
+
? buildKnowledgeDispatch({
|
|
751
|
+
finalizeId: result.knowledgeFinalizeId,
|
|
752
|
+
writer: effectiveWriter,
|
|
753
|
+
})
|
|
754
|
+
: undefined;
|
|
755
|
+
printJson(compactPlanCommandResult("plan.edit", result, effectiveHost, completionRefresh, false, knowledgeDispatch));
|
|
567
756
|
if (result.operationChain?.status === "partial")
|
|
568
757
|
process.exitCode = 1;
|
|
569
758
|
return;
|
|
@@ -631,6 +820,20 @@ async function runPlan(args, effectiveHost) {
|
|
|
631
820
|
const target = readPlanMutationTarget(args);
|
|
632
821
|
assertNoRemainingArgs(args, "plan done");
|
|
633
822
|
const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
|
|
823
|
+
const current = showPlan({
|
|
824
|
+
cwd: process.cwd(),
|
|
825
|
+
...target,
|
|
826
|
+
ownerSessionKey,
|
|
827
|
+
});
|
|
828
|
+
const project = tryResolveHookProject(process.cwd());
|
|
829
|
+
const effectiveWriter = project
|
|
830
|
+
? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
|
|
831
|
+
: undefined;
|
|
832
|
+
if (!current.plan.parentPlan
|
|
833
|
+
&& effectiveWriter?.executionPolicy === "subagent"
|
|
834
|
+
&& effectiveHost !== "codex") {
|
|
835
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex host.', { host: effectiveHost ?? null });
|
|
836
|
+
}
|
|
634
837
|
const queuePlanEndFinalization = preparePlanEndFinalization(process.cwd(), ownerSessionKey);
|
|
635
838
|
const result = await editPlan({
|
|
636
839
|
cwd: process.cwd(),
|
|
@@ -641,7 +844,16 @@ async function runPlan(args, effectiveHost) {
|
|
|
641
844
|
ownerSessionKey,
|
|
642
845
|
});
|
|
643
846
|
const completionRefresh = queuePlanEndFinalization?.(result.taskName);
|
|
644
|
-
|
|
847
|
+
const knowledgeDispatch = (effectiveHost === "codex"
|
|
848
|
+
&& !current.plan.parentPlan
|
|
849
|
+
&& effectiveWriter?.executionPolicy === "subagent"
|
|
850
|
+
&& result.knowledgeFinalizeId)
|
|
851
|
+
? buildKnowledgeDispatch({
|
|
852
|
+
finalizeId: result.knowledgeFinalizeId,
|
|
853
|
+
writer: effectiveWriter,
|
|
854
|
+
})
|
|
855
|
+
: undefined;
|
|
856
|
+
printJson(compactPlanCommandResult("plan.done", result, effectiveHost, completionRefresh, false, knowledgeDispatch));
|
|
645
857
|
return;
|
|
646
858
|
}
|
|
647
859
|
case "show": {
|
|
@@ -872,7 +1084,15 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
872
1084
|
let fixedPaths = [];
|
|
873
1085
|
const sessionProject = resolveSessionWorkflowContext(ownerSessionKey ?? undefined);
|
|
874
1086
|
if (sessionProject) {
|
|
875
|
-
const maintenance =
|
|
1087
|
+
const maintenance = effectiveHost
|
|
1088
|
+
? null
|
|
1089
|
+
: runDailyMaintenance(sessionProject, {
|
|
1090
|
+
excludeSessionKey: ownerSessionKey ?? undefined,
|
|
1091
|
+
includeProject: false,
|
|
1092
|
+
});
|
|
1093
|
+
if (effectiveHost) {
|
|
1094
|
+
launchDailyMaintenance(cwd, ownerSessionKey, true);
|
|
1095
|
+
}
|
|
876
1096
|
const activeWorkflow = !taskName && ownerSessionKey
|
|
877
1097
|
? await tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey, effectiveHost)
|
|
878
1098
|
: null;
|
|
@@ -883,7 +1103,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
883
1103
|
return {
|
|
884
1104
|
project: sessionProject,
|
|
885
1105
|
...(activeWorkflow ? { activeWorkflow } : {}),
|
|
886
|
-
...(maintenance
|
|
1106
|
+
...(maintenance?.ran ? { maintenance } : {}),
|
|
887
1107
|
...(codexRuntimeError ? { error: codexRuntimeError } : {}),
|
|
888
1108
|
};
|
|
889
1109
|
}
|
|
@@ -899,7 +1119,13 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
899
1119
|
initProject({ cwd, version: CLI_VERSION });
|
|
900
1120
|
initialized = true;
|
|
901
1121
|
}
|
|
902
|
-
const
|
|
1122
|
+
const project = resolveProjectContext(cwd);
|
|
1123
|
+
const maintenance = effectiveHost
|
|
1124
|
+
? null
|
|
1125
|
+
: runDailyMaintenance(project, { excludeSessionKey: ownerSessionKey ?? undefined });
|
|
1126
|
+
if (effectiveHost) {
|
|
1127
|
+
launchDailyMaintenance(cwd, ownerSessionKey, false);
|
|
1128
|
+
}
|
|
903
1129
|
let resolved = resolveContext(cwd, taskName);
|
|
904
1130
|
const versionSync = syncProjectVersionWithCli(cwd, resolved.project);
|
|
905
1131
|
if (versionSync.projectVersionUpdated) {
|
|
@@ -919,7 +1145,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
919
1145
|
launchProjectEmbeddingWarmup(resolved.project);
|
|
920
1146
|
return {
|
|
921
1147
|
...resolved,
|
|
922
|
-
...(maintenance
|
|
1148
|
+
...(maintenance?.ran ? { maintenance } : {}),
|
|
923
1149
|
...(activeWorkflow ? { activeWorkflow } : {}),
|
|
924
1150
|
...(codexRuntimeError ? { error: codexRuntimeError } : {}),
|
|
925
1151
|
protocolCheck: checkProjectProtocol(cwd),
|
|
@@ -967,6 +1193,28 @@ function launchProjectEmbeddingWarmup(project) {
|
|
|
967
1193
|
// Context recovery is authoritative; embedding warmup is fail-open latency work.
|
|
968
1194
|
}
|
|
969
1195
|
}
|
|
1196
|
+
function launchDailyMaintenance(cwd, ownerSessionKey, sessionOnly) {
|
|
1197
|
+
const args = [resolveCliEntryPath(), "internal-daily-maintenance", "--cwd", cwd];
|
|
1198
|
+
if (ownerSessionKey) {
|
|
1199
|
+
args.push("--session-key", ownerSessionKey);
|
|
1200
|
+
}
|
|
1201
|
+
if (sessionOnly) {
|
|
1202
|
+
args.push("--session-only");
|
|
1203
|
+
}
|
|
1204
|
+
try {
|
|
1205
|
+
const child = spawn(process.execPath, args, {
|
|
1206
|
+
cwd,
|
|
1207
|
+
detached: true,
|
|
1208
|
+
stdio: "ignore",
|
|
1209
|
+
windowsHide: true,
|
|
1210
|
+
env: withoutInvocationHost(),
|
|
1211
|
+
});
|
|
1212
|
+
child.unref();
|
|
1213
|
+
}
|
|
1214
|
+
catch {
|
|
1215
|
+
// Context recovery is authoritative; daily cleanup is fail-open latency work.
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
970
1218
|
function buildPublicContextOutput(context) {
|
|
971
1219
|
const project = asJsonRecord(context.project);
|
|
972
1220
|
const output = {};
|
|
@@ -1198,6 +1446,11 @@ async function runHook(args, effectiveHost) {
|
|
|
1198
1446
|
});
|
|
1199
1447
|
}
|
|
1200
1448
|
async function runStopHook(effectiveHost) {
|
|
1449
|
+
// Cindy owns task-completion closeout in its Host worker path. The CLI sidecar
|
|
1450
|
+
// must not enqueue a job that it cannot execute with a Cindy session runner.
|
|
1451
|
+
if (effectiveHost === "cindy") {
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1201
1454
|
if (process.env.CLAW_KNOWLEDGE_FINALIZER === "1") {
|
|
1202
1455
|
return;
|
|
1203
1456
|
}
|
|
@@ -1226,14 +1479,166 @@ async function runStopHook(effectiveHost) {
|
|
|
1226
1479
|
host: effectiveHost,
|
|
1227
1480
|
taskConclusions: transcriptPath ? extractTaskDoneConclusions(transcriptPath, turnId) : [],
|
|
1228
1481
|
});
|
|
1229
|
-
|
|
1482
|
+
// Current named hosts own their runner in their adapters. Keep the CLI
|
|
1483
|
+
// launcher only for jobs written by pre-adapter releases with no host.
|
|
1484
|
+
if (result.ok && result.jobPath && !effectiveHost && process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_LAUNCH !== "1") {
|
|
1230
1485
|
launchKnowledgeFinalizationWorker(result.jobPath, project.projectRoot);
|
|
1231
1486
|
}
|
|
1487
|
+
if (process.env.CLAW_KNOWLEDGE_CAPTURE_RESULT === "1") {
|
|
1488
|
+
printJson(result);
|
|
1489
|
+
}
|
|
1232
1490
|
}
|
|
1233
1491
|
catch {
|
|
1234
1492
|
// Knowledge capture is a fail-open sidecar and must never block Stop.
|
|
1235
1493
|
}
|
|
1236
1494
|
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Machine-facing report capture for hosts whose final-message hook is owned by
|
|
1497
|
+
* the adapter rather than by the CLI. Unlike `hook auto-doc`, this only
|
|
1498
|
+
* persists the report/job hand-off; it never chooses or launches a writer.
|
|
1499
|
+
*/
|
|
1500
|
+
async function runInternalKnowledgeCapture(args, effectiveHost) {
|
|
1501
|
+
assertNoRemainingArgs(args, "internal-knowledge-capture");
|
|
1502
|
+
const payload = await readStdinJson();
|
|
1503
|
+
const hookCwd = resolveHookCwd(payload);
|
|
1504
|
+
const sessionId = resolveOwnerSessionKey(payload);
|
|
1505
|
+
const turnId = readHookString(payload, "turn_id");
|
|
1506
|
+
const message = readHookString(payload, "message");
|
|
1507
|
+
const taskConclusions = readHookTaskConclusions(payload, turnId);
|
|
1508
|
+
if (!hookCwd || !sessionId || !turnId || !message || !containsClawDir(hookCwd)) {
|
|
1509
|
+
printJson({ ok: true, captured: false });
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
try {
|
|
1513
|
+
const project = resolveProjectContext(hookCwd);
|
|
1514
|
+
const result = tryCaptureKnowledgeStop({
|
|
1515
|
+
project,
|
|
1516
|
+
sessionId,
|
|
1517
|
+
turnId,
|
|
1518
|
+
message,
|
|
1519
|
+
host: effectiveHost === "cindy" ? "cindy" : effectiveHost,
|
|
1520
|
+
taskConclusions,
|
|
1521
|
+
});
|
|
1522
|
+
printJson(result);
|
|
1523
|
+
}
|
|
1524
|
+
catch (error) {
|
|
1525
|
+
// Capture is a non-blocking sidecar. Return structured failure so the
|
|
1526
|
+
// owning adapter can surface/retry it without affecting the assistant turn.
|
|
1527
|
+
printJson({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
/** Record a successful adapter-owned writer run without invoking a host SDK. */
|
|
1531
|
+
function runInternalKnowledgeComplete(args) {
|
|
1532
|
+
const jobPath = readRequiredFlag(args, "--job");
|
|
1533
|
+
const result = readRequiredFlag(args, "--result");
|
|
1534
|
+
assertNoRemainingArgs(args, "internal-knowledge-complete");
|
|
1535
|
+
const running = ensureLegacyKnowledgeClaim(jobPath);
|
|
1536
|
+
if (!running?.claimToken) {
|
|
1537
|
+
printJson({ ok: true, completed: false, reason: "job is not claimable" });
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
completeKnowledgeFinalizationJob(jobPath, result, running.claimToken);
|
|
1541
|
+
}
|
|
1542
|
+
function runInternalKnowledgeClaim(args) {
|
|
1543
|
+
const jobPath = readRequiredFlag(args, "--job");
|
|
1544
|
+
assertNoRemainingArgs(args, "internal-knowledge-claim");
|
|
1545
|
+
const job = ensureLegacyKnowledgeClaim(jobPath);
|
|
1546
|
+
printJson({
|
|
1547
|
+
ok: true,
|
|
1548
|
+
claimed: Boolean(job),
|
|
1549
|
+
...(job ? {
|
|
1550
|
+
finalizeId: job.finalizeId,
|
|
1551
|
+
claimToken: job.claimToken,
|
|
1552
|
+
} : {}),
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
function runInternalKnowledgeFail(args) {
|
|
1556
|
+
const jobPath = readRequiredFlag(args, "--job");
|
|
1557
|
+
const message = readRequiredFlag(args, "--message");
|
|
1558
|
+
assertNoRemainingArgs(args, "internal-knowledge-fail");
|
|
1559
|
+
const job = ensureLegacyKnowledgeClaim(jobPath);
|
|
1560
|
+
if (!job?.claimToken) {
|
|
1561
|
+
printJson({ ok: true, failed: false, reason: "job is not claimable" });
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
failKnowledgeFinalizationJob(jobPath, message, job.claimToken);
|
|
1565
|
+
}
|
|
1566
|
+
function ensureLegacyKnowledgeClaim(jobPath) {
|
|
1567
|
+
const job = readKnowledgeFinalizationJob(jobPath);
|
|
1568
|
+
if (job.status === "succeeded" || job.status === "failed" && job.attempts >= 3) {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
if (job.status === "running") {
|
|
1572
|
+
return job;
|
|
1573
|
+
}
|
|
1574
|
+
return claimKnowledgeFinalizationJob(jobPath);
|
|
1575
|
+
}
|
|
1576
|
+
function completeKnowledgeFinalizationJob(jobPath, result, claimToken) {
|
|
1577
|
+
const running = readKnowledgeFinalizationJob(jobPath);
|
|
1578
|
+
if (running.status === "succeeded") {
|
|
1579
|
+
const terminal = doneKnowledgeFinalizationJob({
|
|
1580
|
+
jobPath,
|
|
1581
|
+
claimToken,
|
|
1582
|
+
status: "succeeded",
|
|
1583
|
+
result,
|
|
1584
|
+
});
|
|
1585
|
+
removeKnowledgeAssignmentTemplate(jobPath, running.finalizeId);
|
|
1586
|
+
printJson({ ok: true, completed: true, alreadyDone: terminal.alreadyDone, finalizeId: running.finalizeId });
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (running.status !== "running") {
|
|
1590
|
+
throw new Error("Knowledge finalization job must be claimed before successful completion.");
|
|
1591
|
+
}
|
|
1592
|
+
if (running.claimToken !== claimToken) {
|
|
1593
|
+
throw new Error("Knowledge finalization completion does not match the active claim.");
|
|
1594
|
+
}
|
|
1595
|
+
const project = resolveProjectContext(running.projectRoot);
|
|
1596
|
+
const finishedAt = new Date().toISOString();
|
|
1597
|
+
const truthEncoding = normalizeTruthMarkdownEncoding(project);
|
|
1598
|
+
recordKnowledgeFinalizationResult(project, running.reportPath, {
|
|
1599
|
+
schemaVersion: 1,
|
|
1600
|
+
entryType: "knowledge_finalization",
|
|
1601
|
+
finalizeId: running.finalizeId,
|
|
1602
|
+
taskName: running.taskName,
|
|
1603
|
+
recordedAt: finishedAt,
|
|
1604
|
+
status: "succeeded",
|
|
1605
|
+
result,
|
|
1606
|
+
attempts: running.attempts,
|
|
1607
|
+
...(running.host !== undefined ? { host: running.host } : {}),
|
|
1608
|
+
truthEncoding,
|
|
1609
|
+
});
|
|
1610
|
+
const terminal = doneKnowledgeFinalizationJob({
|
|
1611
|
+
jobPath,
|
|
1612
|
+
claimToken,
|
|
1613
|
+
status: "succeeded",
|
|
1614
|
+
result,
|
|
1615
|
+
finishedAt,
|
|
1616
|
+
patch: { truthEncoding },
|
|
1617
|
+
});
|
|
1618
|
+
removeKnowledgeAssignmentTemplate(jobPath, running.finalizeId);
|
|
1619
|
+
queueCompletionRefresh({
|
|
1620
|
+
cwd: running.projectRoot,
|
|
1621
|
+
taskName: running.taskName,
|
|
1622
|
+
includeTaskRetention: false,
|
|
1623
|
+
includeTaskMemory: false,
|
|
1624
|
+
statusLabel: `knowledge-${running.finalizeId.slice(0, 12)}`,
|
|
1625
|
+
skipGitNexusRefresh: true,
|
|
1626
|
+
});
|
|
1627
|
+
printJson({ ok: true, completed: true, alreadyDone: terminal.alreadyDone, finalizeId: running.finalizeId });
|
|
1628
|
+
}
|
|
1629
|
+
function failKnowledgeFinalizationJob(jobPath, message, claimToken) {
|
|
1630
|
+
const terminal = doneKnowledgeFinalizationJob({
|
|
1631
|
+
jobPath,
|
|
1632
|
+
claimToken,
|
|
1633
|
+
status: "failed",
|
|
1634
|
+
error: message,
|
|
1635
|
+
});
|
|
1636
|
+
removeKnowledgeAssignmentTemplate(jobPath, terminal.job.finalizeId);
|
|
1637
|
+
printJson({ ok: true, failed: true, alreadyDone: terminal.alreadyDone, finalizeId: terminal.job.finalizeId });
|
|
1638
|
+
}
|
|
1639
|
+
function removeKnowledgeAssignmentTemplate(jobPath, finalizeId) {
|
|
1640
|
+
fs.rmSync(path.join(path.dirname(jobPath), `${finalizeId}.assignments.json`), { force: true });
|
|
1641
|
+
}
|
|
1237
1642
|
async function runInternalEmbeddingWarmup(args) {
|
|
1238
1643
|
const cwd = readRequiredFlag(args, "--cwd");
|
|
1239
1644
|
assertNoRemainingArgs(args, "internal-embedding-warmup");
|
|
@@ -1243,112 +1648,156 @@ async function runInternalEmbeddingWarmup(args) {
|
|
|
1243
1648
|
...await warmProjectMemoryEmbedding({ cwd }),
|
|
1244
1649
|
});
|
|
1245
1650
|
}
|
|
1651
|
+
function runInternalDailyMaintenance(args) {
|
|
1652
|
+
const cwd = readRequiredFlag(args, "--cwd");
|
|
1653
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
1654
|
+
const sessionOnly = readBooleanFlag(args, "--session-only");
|
|
1655
|
+
assertNoRemainingArgs(args, "internal-daily-maintenance");
|
|
1656
|
+
const project = sessionOnly
|
|
1657
|
+
? (sessionKey ? resolveSessionWorkflowContext(sessionKey) : null)
|
|
1658
|
+
: resolveProjectContext(cwd);
|
|
1659
|
+
if (!project) {
|
|
1660
|
+
printJson({ command: "internal-daily-maintenance", ok: true, ran: false, skipped: true });
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
printJson({
|
|
1664
|
+
command: "internal-daily-maintenance",
|
|
1665
|
+
ok: true,
|
|
1666
|
+
...runDailyMaintenance(project, {
|
|
1667
|
+
excludeSessionKey: sessionKey ?? undefined,
|
|
1668
|
+
includeProject: !sessionOnly,
|
|
1669
|
+
}),
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
function runInternalKnowledgeSweep(args) {
|
|
1673
|
+
const cwd = readRequiredFlag(args, "--cwd");
|
|
1674
|
+
assertNoRemainingArgs(args, "internal-knowledge-sweep");
|
|
1675
|
+
const project = tryResolveHookProject(cwd);
|
|
1676
|
+
if (!project) {
|
|
1677
|
+
printJson({ command: "internal-knowledge-sweep", ok: true, launched: 0, skipped: true });
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
const jobs = listRetryableKnowledgeFinalizationJobs(project);
|
|
1681
|
+
let launched = 0;
|
|
1682
|
+
for (const jobPath of jobs) {
|
|
1683
|
+
try {
|
|
1684
|
+
launchKnowledgeFinalizationWorker(jobPath, project.projectRoot);
|
|
1685
|
+
launched += 1;
|
|
1686
|
+
}
|
|
1687
|
+
catch {
|
|
1688
|
+
// A later background sweep can retry a job that could not be launched.
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
printJson({ command: "internal-knowledge-sweep", ok: true, discovered: jobs.length, launched });
|
|
1692
|
+
}
|
|
1693
|
+
async function runInternalBackgroundMaintenance(args) {
|
|
1694
|
+
const cwd = readRequiredFlag(args, "--cwd");
|
|
1695
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
1696
|
+
assertNoRemainingArgs(args, "internal-background-maintenance");
|
|
1697
|
+
const sessionProject = sessionKey ? resolveSessionWorkflowContext(sessionKey) : null;
|
|
1698
|
+
const project = sessionProject ? null : tryResolveHookProject(cwd);
|
|
1699
|
+
let maintenance = { ran: false, skipped: true };
|
|
1700
|
+
if (sessionProject) {
|
|
1701
|
+
maintenance = runDailyMaintenance(sessionProject, { excludeSessionKey: sessionKey ?? undefined, includeProject: false });
|
|
1702
|
+
}
|
|
1703
|
+
else if (project) {
|
|
1704
|
+
maintenance = runDailyMaintenance(project, { excludeSessionKey: sessionKey ?? undefined });
|
|
1705
|
+
}
|
|
1706
|
+
let embedding = { warmed: false, reason: "skipped" };
|
|
1707
|
+
try {
|
|
1708
|
+
embedding = await warmProjectMemoryEmbedding({ cwd });
|
|
1709
|
+
}
|
|
1710
|
+
catch (error) {
|
|
1711
|
+
embedding = { warmed: false, reason: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
1712
|
+
}
|
|
1713
|
+
let discovered = 0;
|
|
1714
|
+
let launched = 0;
|
|
1715
|
+
const knowledgeProject = project ?? sessionProject;
|
|
1716
|
+
if (knowledgeProject) {
|
|
1717
|
+
const jobs = listRetryableKnowledgeFinalizationJobs(knowledgeProject);
|
|
1718
|
+
discovered = jobs.length;
|
|
1719
|
+
for (const jobPath of jobs) {
|
|
1720
|
+
try {
|
|
1721
|
+
launchKnowledgeFinalizationWorker(jobPath, knowledgeProject.projectRoot);
|
|
1722
|
+
launched += 1;
|
|
1723
|
+
}
|
|
1724
|
+
catch {
|
|
1725
|
+
// A later background sweep can retry a job that could not be launched.
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
printJson({ command: "internal-background-maintenance", ok: true, maintenance, embedding, discovered, launched });
|
|
1730
|
+
}
|
|
1246
1731
|
async function runInternalKnowledgeFinalize(args) {
|
|
1247
1732
|
const jobPath = readRequiredFlag(args, "--job");
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1733
|
+
const queued = readKnowledgeFinalizationJob(jobPath);
|
|
1734
|
+
assertNoRemainingArgs(args, "internal-knowledge-finalize");
|
|
1735
|
+
if ((queued.writer?.executionPolicy ?? "background") !== "background") {
|
|
1250
1736
|
return;
|
|
1251
1737
|
}
|
|
1252
1738
|
try {
|
|
1253
|
-
const
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
:
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
queueCompletionRefresh({
|
|
1267
|
-
cwd: running.projectRoot,
|
|
1268
|
-
taskName: running.taskName,
|
|
1269
|
-
includeTaskRetention: false,
|
|
1270
|
-
includeTaskMemory: false,
|
|
1271
|
-
statusLabel: `knowledge-${running.finalizeId.slice(0, 12)}`,
|
|
1272
|
-
skipGitNexusRefresh: true,
|
|
1273
|
-
});
|
|
1274
|
-
const finishedAt = new Date().toISOString();
|
|
1275
|
-
recordKnowledgeFinalizationResult(project, running.reportPath, {
|
|
1276
|
-
schemaVersion: 1,
|
|
1277
|
-
entryType: "knowledge_finalization",
|
|
1278
|
-
finalizeId: running.finalizeId,
|
|
1279
|
-
taskName: running.taskName,
|
|
1280
|
-
recordedAt: finishedAt,
|
|
1281
|
-
status: "succeeded",
|
|
1282
|
-
result: writerRun.finalResponse,
|
|
1283
|
-
attempts: running.attempts,
|
|
1284
|
-
...(running.host !== undefined ? { host: running.host } : {}),
|
|
1739
|
+
const writerRun = await runKnowledgeDelegateForJob(queued);
|
|
1740
|
+
const terminal = readKnowledgeFinalizationJob(jobPath);
|
|
1741
|
+
if (terminal.status !== "succeeded" && terminal.status !== "failed") {
|
|
1742
|
+
throw new Error("Knowledge delegate returned without acknowledging a terminal result.");
|
|
1743
|
+
}
|
|
1744
|
+
if (writerRun.threadId) {
|
|
1745
|
+
assertCompletedKnowledgeWriterSession(writerRun.threadId);
|
|
1746
|
+
}
|
|
1747
|
+
printJson({
|
|
1748
|
+
ok: terminal.status === "succeeded",
|
|
1749
|
+
command: "internal-knowledge-finalize",
|
|
1750
|
+
finalizeId: terminal.finalizeId,
|
|
1751
|
+
status: terminal.status,
|
|
1285
1752
|
...(writerRun.threadId ? { threadId: writerRun.threadId } : {}),
|
|
1286
|
-
...(knowledgeGovernance ? { knowledgeGovernance } : {}),
|
|
1287
|
-
truthEncoding,
|
|
1288
|
-
});
|
|
1289
|
-
writeKnowledgeFinalizationJob(jobPath, {
|
|
1290
|
-
...running,
|
|
1291
|
-
status: "succeeded",
|
|
1292
|
-
finishedAt,
|
|
1293
|
-
...(writerRun.threadId ? { sdkThreadId: writerRun.threadId } : {}),
|
|
1294
|
-
...(writerRun.threadIds ? { sdkThreadIds: writerRun.threadIds } : {}),
|
|
1295
|
-
finalResponse: writerRun.finalResponse,
|
|
1296
|
-
...(knowledgeGovernance ? { knowledgeGovernance } : {}),
|
|
1297
|
-
truthEncoding,
|
|
1298
1753
|
});
|
|
1299
1754
|
}
|
|
1300
1755
|
catch (error) {
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1756
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1757
|
+
let failed = readKnowledgeFinalizationJob(jobPath);
|
|
1758
|
+
if (failed.status === "running" && failed.claimToken) {
|
|
1759
|
+
failed = doneKnowledgeFinalizationJob({
|
|
1760
|
+
jobPath,
|
|
1761
|
+
claimToken: failed.claimToken,
|
|
1762
|
+
status: "failed",
|
|
1763
|
+
error: message,
|
|
1764
|
+
}).job;
|
|
1765
|
+
}
|
|
1766
|
+
else if (failed.status === "queued") {
|
|
1767
|
+
const supervisorClaim = claimKnowledgeFinalizationJob(jobPath);
|
|
1768
|
+
if (supervisorClaim?.claimToken) {
|
|
1769
|
+
failed = doneKnowledgeFinalizationJob({
|
|
1770
|
+
jobPath,
|
|
1771
|
+
claimToken: supervisorClaim.claimToken,
|
|
1772
|
+
status: "failed",
|
|
1773
|
+
error: message,
|
|
1774
|
+
}).job;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
if (failed.status === "failed") {
|
|
1778
|
+
removeKnowledgeAssignmentTemplate(jobPath, failed.finalizeId);
|
|
1779
|
+
}
|
|
1308
1780
|
if (failed.attempts < 3 && process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_RETRY !== "1") {
|
|
1309
1781
|
launchKnowledgeFinalizationWorker(jobPath, failed.projectRoot);
|
|
1310
1782
|
}
|
|
1311
1783
|
}
|
|
1312
1784
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
});
|
|
1329
|
-
if (isBuiltInKnowledgeWriterSkill(skill)) {
|
|
1330
|
-
assertCompletedKnowledgeWriterSession(result.threadId ?? null);
|
|
1331
|
-
}
|
|
1332
|
-
results.push({
|
|
1333
|
-
finalResponse: result.finalResponse,
|
|
1334
|
-
...(result.threadId ? { threadId: result.threadId } : {}),
|
|
1335
|
-
});
|
|
1336
|
-
continue;
|
|
1337
|
-
}
|
|
1338
|
-
if (running.host !== undefined && running.host !== null && running.host !== "codex") {
|
|
1339
|
-
throw new Error(`Unsupported knowledge finalization job host "${String(running.host)}".`);
|
|
1340
|
-
}
|
|
1341
|
-
results.push(await runCodexSdkWriter(running, skill));
|
|
1785
|
+
async function runKnowledgeDelegateForJob(running) {
|
|
1786
|
+
const dispatch = buildKnowledgeDelegateDispatch({
|
|
1787
|
+
policy: "background",
|
|
1788
|
+
finalizeId: running.finalizeId,
|
|
1789
|
+
writer: running.writer,
|
|
1790
|
+
});
|
|
1791
|
+
if (running.host === "opencode") {
|
|
1792
|
+
return runOpencodeKnowledgeWriter({
|
|
1793
|
+
prompt: dispatch.prompt,
|
|
1794
|
+
projectRoot: running.projectRoot,
|
|
1795
|
+
writer: running.writer ?? null,
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
if (running.host !== undefined && running.host !== null && running.host !== "codex") {
|
|
1799
|
+
throw new Error(`Unsupported knowledge finalization job host "${String(running.host)}".`);
|
|
1342
1800
|
}
|
|
1343
|
-
const threadIds = results.flatMap((result) => result.threadId ? [result.threadId] : []);
|
|
1344
|
-
const last = results.at(-1);
|
|
1345
|
-
return {
|
|
1346
|
-
finalResponse: results.map((result) => result.finalResponse).join("\n\n"),
|
|
1347
|
-
...(last?.threadId ? { threadId: last.threadId } : {}),
|
|
1348
|
-
...(threadIds.length > 0 ? { threadIds } : {}),
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
async function runCodexSdkWriter(running, skill) {
|
|
1352
1801
|
const sdk = await import(pathToFileURL(resolveCodexSdkEntryPath()).href);
|
|
1353
1802
|
const Codex = sdk.Codex;
|
|
1354
1803
|
const codex = new Codex({
|
|
@@ -1368,10 +1817,7 @@ async function runCodexSdkWriter(running, skill) {
|
|
|
1368
1817
|
? { modelReasoningEffort: writer.reasoningEffort }
|
|
1369
1818
|
: {}),
|
|
1370
1819
|
});
|
|
1371
|
-
const turn = await thread.run(
|
|
1372
|
-
if (isBuiltInKnowledgeWriterSkill(skill)) {
|
|
1373
|
-
assertCompletedKnowledgeWriterSession(thread.id);
|
|
1374
|
-
}
|
|
1820
|
+
const turn = await thread.run(dispatch.prompt);
|
|
1375
1821
|
return {
|
|
1376
1822
|
finalResponse: turn.finalResponse,
|
|
1377
1823
|
...(thread.id ? { threadId: thread.id } : {}),
|
|
@@ -1416,28 +1862,11 @@ function knowledgeFinalizerEnvironment() {
|
|
|
1416
1862
|
}
|
|
1417
1863
|
}
|
|
1418
1864
|
env.CLAW_KNOWLEDGE_FINALIZER = "1";
|
|
1865
|
+
delete env.CLAW_SESSION_ID;
|
|
1866
|
+
delete env.CODEX_THREAD_ID;
|
|
1867
|
+
delete env.CODEX_SESSION_ID;
|
|
1419
1868
|
return env;
|
|
1420
1869
|
}
|
|
1421
|
-
function buildKnowledgeWriterPrompt(job, writerSkill) {
|
|
1422
|
-
return [
|
|
1423
|
-
`Apply the ${writerSkill} skill's documentation-governance rules to the supplied materials and update the governed project documentation. Work unattended; do not request review or confirmation. Use task status to distinguish completed work from pending or blocked intent, and never present requirements or intentions as results. Skip and report ambiguous or unsafe changes.`,
|
|
1424
|
-
"Materials:",
|
|
1425
|
-
`- ${job.planPath}`,
|
|
1426
|
-
`- ${job.reportPath}`,
|
|
1427
|
-
`Finalization id: ${job.finalizeId}`,
|
|
1428
|
-
"Interpret inputs by content, regardless of filename or schema. Do not reference or link to the supplied materials in governed documentation; they are transient and will be destroyed after finalization. Do not modify inputs, delegate, reimplement, or rerun tests.",
|
|
1429
|
-
].join("\n");
|
|
1430
|
-
}
|
|
1431
|
-
function resolveKnowledgeWriterSkills(job) {
|
|
1432
|
-
const configured = job.writer?.externalSkills?.map((skill) => skill.trim()).filter(Boolean);
|
|
1433
|
-
return configured && configured.length > 0 ? configured : [BUILT_IN_KNOWLEDGE_WRITER_SKILL];
|
|
1434
|
-
}
|
|
1435
|
-
function isBuiltInKnowledgeWriterSkill(skill) {
|
|
1436
|
-
return skill === BUILT_IN_KNOWLEDGE_WRITER_SKILL;
|
|
1437
|
-
}
|
|
1438
|
-
function usesBuiltInKnowledgeWriter(job) {
|
|
1439
|
-
return resolveKnowledgeWriterSkills(job).every(isBuiltInKnowledgeWriterSkill);
|
|
1440
|
-
}
|
|
1441
1870
|
function launchKnowledgeFinalizationWorker(jobPath, cwd) {
|
|
1442
1871
|
if (process.platform === "win32") {
|
|
1443
1872
|
const launcherScript = [
|
|
@@ -1484,11 +1913,15 @@ async function runSessionStartHook(effectiveHost) {
|
|
|
1484
1913
|
try {
|
|
1485
1914
|
const context = await runContextCommand([], hookCwd, ownerSessionKey, effectiveHost);
|
|
1486
1915
|
const contextProject = asJsonRecord(context.project);
|
|
1916
|
+
const retryableJobs = effectiveHost !== "cindy" && contextProject?.scope !== "session" && !context.error
|
|
1917
|
+
? listRetryableKnowledgeFinalizationJobs(resolveProjectContext(hookCwd))
|
|
1918
|
+
: [];
|
|
1487
1919
|
if (contextProject?.scope !== "session"
|
|
1488
1920
|
&& !context.error
|
|
1921
|
+
&& effectiveHost !== "cindy"
|
|
1489
1922
|
&& process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_LAUNCH !== "1") {
|
|
1490
1923
|
const project = resolveProjectContext(hookCwd);
|
|
1491
|
-
for (const jobPath of
|
|
1924
|
+
for (const jobPath of retryableJobs) {
|
|
1492
1925
|
try {
|
|
1493
1926
|
launchKnowledgeFinalizationWorker(jobPath, project.projectRoot);
|
|
1494
1927
|
}
|
|
@@ -1538,6 +1971,21 @@ function readHookString(payload, key) {
|
|
|
1538
1971
|
const value = payload[key];
|
|
1539
1972
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1540
1973
|
}
|
|
1974
|
+
function readHookTaskConclusions(payload, turnId) {
|
|
1975
|
+
if (!turnId || !payload || typeof payload !== "object")
|
|
1976
|
+
return [];
|
|
1977
|
+
const value = payload.task_conclusions;
|
|
1978
|
+
if (!Array.isArray(value))
|
|
1979
|
+
return [];
|
|
1980
|
+
return value.flatMap((item) => {
|
|
1981
|
+
if (!item || typeof item !== "object")
|
|
1982
|
+
return [];
|
|
1983
|
+
const message = item.message;
|
|
1984
|
+
return typeof message === "string" && message.trim()
|
|
1985
|
+
? [{ turnId, message: message.trim() }]
|
|
1986
|
+
: [];
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1541
1989
|
function containsClawDir(cwd) {
|
|
1542
1990
|
try {
|
|
1543
1991
|
const startDir = path.resolve(cwd);
|
|
@@ -1579,6 +2027,9 @@ function safeResolveTempDir() {
|
|
|
1579
2027
|
}
|
|
1580
2028
|
function buildSessionStartAdditionalContext(context, sessionCwd, effectiveHost) {
|
|
1581
2029
|
const versionSyncPrompt = buildVersionSyncPrompt(context);
|
|
2030
|
+
if (effectiveHost === "cindy") {
|
|
2031
|
+
return buildCindySessionStartContext(context, sessionCwd, versionSyncPrompt);
|
|
2032
|
+
}
|
|
1582
2033
|
const searchGuidance = buildContextSearchGuidance(context);
|
|
1583
2034
|
const runtimeErrorPrompt = buildCodexRuntimeErrorPrompt(context);
|
|
1584
2035
|
const activeWorkflow = context.activeWorkflow;
|
|
@@ -1613,6 +2064,45 @@ function buildSessionStartAdditionalContext(context, sessionCwd, effectiveHost)
|
|
|
1613
2064
|
const promptWithSearch = searchGuidance ? `${promptWithVersion}\n${searchGuidance}` : promptWithVersion;
|
|
1614
2065
|
return runtimeErrorPrompt ? `${runtimeErrorPrompt}\n\n${promptWithSearch}` : promptWithSearch;
|
|
1615
2066
|
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Cindy Agents use the Ghost Tool gateway, not shell commands or Host actions.
|
|
2069
|
+
* Match the Codex session-start wording and structure. Cindy's static entry
|
|
2070
|
+
* prompt supplies the shared skill and guidance lines; this dynamic context
|
|
2071
|
+
* supplies project identity or a recovered workflow snapshot. Goal Mode is
|
|
2072
|
+
* intentionally omitted because Cindy does not expose that Host surface.
|
|
2073
|
+
*/
|
|
2074
|
+
function buildCindySessionStartContext(context, sessionCwd, versionSyncPrompt) {
|
|
2075
|
+
const activeWorkflow = asJsonRecord(context.activeWorkflow);
|
|
2076
|
+
if (activeWorkflow) {
|
|
2077
|
+
return stripCindyGoalModeLines(buildRecoveredWorkflowAdditionalContext(activeWorkflow, versionSyncPrompt));
|
|
2078
|
+
}
|
|
2079
|
+
const project = asJsonRecord(context.project);
|
|
2080
|
+
if (!project)
|
|
2081
|
+
return null;
|
|
2082
|
+
const projectName = typeof project.projectName === "string" && project.projectName.trim()
|
|
2083
|
+
? project.projectName.trim()
|
|
2084
|
+
: typeof project.projectId === "string" && project.projectId.trim()
|
|
2085
|
+
? project.projectId.trim()
|
|
2086
|
+
: path.basename(String(project.projectRoot ?? sessionCwd ?? "project"));
|
|
2087
|
+
const projectRoot = typeof project.projectRoot === "string" ? project.projectRoot : sessionCwd;
|
|
2088
|
+
const projectId = typeof project.projectId === "string" ? project.projectId : projectName;
|
|
2089
|
+
const clawDir = typeof project.clawDir === "string" ? project.clawDir : path.join(projectRoot, ".claw");
|
|
2090
|
+
const prompt = [
|
|
2091
|
+
`This session started inside a .claw project: ${projectName} (${projectId}).`,
|
|
2092
|
+
`.claw directory: ${clawDir}`,
|
|
2093
|
+
].join("\n");
|
|
2094
|
+
if (!versionSyncPrompt)
|
|
2095
|
+
return prompt;
|
|
2096
|
+
return versionSyncPrompt.placement === "prefix"
|
|
2097
|
+
? `${versionSyncPrompt.lines.join("\n")}\n${prompt}`
|
|
2098
|
+
: `${prompt}\n${versionSyncPrompt.lines.join("\n")}`;
|
|
2099
|
+
}
|
|
2100
|
+
function stripCindyGoalModeLines(prompt) {
|
|
2101
|
+
return prompt
|
|
2102
|
+
.split("\n")
|
|
2103
|
+
.filter((line) => !/goal mode/i.test(line))
|
|
2104
|
+
.join("\n");
|
|
2105
|
+
}
|
|
1616
2106
|
function buildCodexRuntimeErrorPrompt(context) {
|
|
1617
2107
|
const error = asJsonRecord(context.error);
|
|
1618
2108
|
if (error?.code !== "CODEX_SDK_RUNTIME_MISSING") {
|
|
@@ -1674,8 +2164,9 @@ function buildVersionSyncPrompt(context) {
|
|
|
1674
2164
|
return {
|
|
1675
2165
|
placement: "suffix",
|
|
1676
2166
|
lines: [
|
|
1677
|
-
`
|
|
1678
|
-
|
|
2167
|
+
`A newer claw-kit version is available: installed CLI ${cliVersion}, published latest ${latestPublishedVersion}.`,
|
|
2168
|
+
"Tell the user in their language that the current claw-kit installation is out of date and must be updated before they can continue using claw-kit. Ask whether they want to update now, then wait for their answer.",
|
|
2169
|
+
`After the user confirms, use ${updateSkill} to update the claw-kit CLI and the current host plugin surface, then continue the original task.`,
|
|
1679
2170
|
],
|
|
1680
2171
|
};
|
|
1681
2172
|
}
|
|
@@ -1820,6 +2311,7 @@ async function runSubplan(args, effectiveHost) {
|
|
|
1820
2311
|
}
|
|
1821
2312
|
function resolveOwnerSessionKey(payload) {
|
|
1822
2313
|
const envCandidates = [
|
|
2314
|
+
process.env.CLAW_SESSION_ID,
|
|
1823
2315
|
process.env.CODEX_THREAD_ID,
|
|
1824
2316
|
process.env.CODEX_SESSION_ID,
|
|
1825
2317
|
];
|
|
@@ -1880,13 +2372,20 @@ function readJson(filePath) {
|
|
|
1880
2372
|
function stripBom(content) {
|
|
1881
2373
|
return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
|
|
1882
2374
|
}
|
|
1883
|
-
function
|
|
2375
|
+
function buildKnowledgeDispatch(input) {
|
|
2376
|
+
return buildKnowledgeDelegateDispatch({
|
|
2377
|
+
policy: "subagent",
|
|
2378
|
+
...input,
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
function compactPlanCommandResult(command, result, effectiveHost, completionRefresh, forceProjectionSync = false, knowledgeDispatch) {
|
|
1884
2382
|
const archivedPlanPath = completionRefresh?.taskRetention.archivedCurrentTask?.taskName === result.taskName &&
|
|
1885
2383
|
completionRefresh.taskRetention.archivedCurrentTask.archivedPlanPath
|
|
1886
2384
|
? completionRefresh.taskRetention.archivedCurrentTask.archivedPlanPath
|
|
1887
2385
|
: undefined;
|
|
1888
2386
|
const resolvedPlanPath = archivedPlanPath ?? result.planPath;
|
|
1889
2387
|
const codexResult = effectiveHost === "codex";
|
|
2388
|
+
const cindyResult = effectiveHost === "cindy";
|
|
1890
2389
|
const hostActions = codexResult ? buildHostActions(result, { forceProjectionSync, actionIdPrefix: command === "plan.sync" ? `plan.sync:${createHash("sha256").update(result.planPath).digest("hex").slice(0, 16)}` : undefined }) : [];
|
|
1891
2390
|
const nextsteps = codexResult
|
|
1892
2391
|
&& result.planStatus === "end.completed"
|
|
@@ -1917,16 +2416,17 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
|
|
|
1917
2416
|
...(archivedPlanPath ? { archivedPlanPath } : {}),
|
|
1918
2417
|
planStatus: result.planStatus,
|
|
1919
2418
|
...(achievement ? { achievement } : {}),
|
|
2419
|
+
...(knowledgeDispatch ? { knowledgeDispatch } : {}),
|
|
1920
2420
|
...(!codexResult && result.previousPlanStatus ? { previousPlanStatus: result.previousPlanStatus } : {}),
|
|
1921
2421
|
...(hostActions.length ? { hostActions } : {}),
|
|
1922
2422
|
...(!codexResult && result.changedTaskIds?.length ? { changedTaskIds: result.changedTaskIds } : {}),
|
|
1923
2423
|
...(!codexResult && result.appendedTaskIds?.length ? { appendedTaskIds: result.appendedTaskIds } : {}),
|
|
1924
2424
|
...(codexResult ? { stage: result.workflowGuidance.stage } : {}),
|
|
1925
|
-
...(!codexResult || result.planStatus === "end.completed"
|
|
2425
|
+
...((!codexResult && !cindyResult) || result.planStatus === "end.completed" && !cindyResult
|
|
1926
2426
|
? { nextsteps }
|
|
1927
2427
|
: {}),
|
|
1928
2428
|
...(result.workflowGuidance.nextTask ? { nextTask: result.workflowGuidance.nextTask } : {}),
|
|
1929
|
-
...(result.workflowGuidance.notes?.trim() && !codexResult
|
|
2429
|
+
...(result.workflowGuidance.notes?.trim() && !codexResult && !cindyResult
|
|
1930
2430
|
? { notes: result.workflowGuidance.notes }
|
|
1931
2431
|
: {}),
|
|
1932
2432
|
...(result.workflowGuidance.commandHints?.length
|
|
@@ -1941,9 +2441,13 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
|
|
|
1941
2441
|
failedOperation: result.operationChain.failedOperation,
|
|
1942
2442
|
}
|
|
1943
2443
|
: {}),
|
|
1944
|
-
...(!codexResult && result.workflowGuidance.goalMode ? { goalMode: result.workflowGuidance.goalMode } : {}),
|
|
1945
|
-
...(!codexResult && result.workflowGuidance.goalTool ? { goalTool: result.workflowGuidance.goalTool } : {}),
|
|
2444
|
+
...(!codexResult && !cindyResult && result.workflowGuidance.goalMode ? { goalMode: result.workflowGuidance.goalMode } : {}),
|
|
2445
|
+
...(!codexResult && !cindyResult && result.workflowGuidance.goalTool ? { goalTool: result.workflowGuidance.goalTool } : {}),
|
|
1946
2446
|
...(includePlan && result.plan ? { plan: result.plan } : {}),
|
|
2447
|
+
// Cindy's Ghost card is a Host-owned projection. It needs the
|
|
2448
|
+
// canonical task list to render its expandable Todo view, but that
|
|
2449
|
+
// view stays out of Agent guidance in the Cindy adapter.
|
|
2450
|
+
...(cindyResult ? { planView: result.planView } : {}),
|
|
1947
2451
|
...(result.planReview
|
|
1948
2452
|
? {
|
|
1949
2453
|
planReview: {
|
|
@@ -3305,7 +3809,7 @@ function printTopLevelUsage() {
|
|
|
3305
3809
|
"Global flags:",
|
|
3306
3810
|
" -h, --help Show help (use `claw help <command>` for command details).",
|
|
3307
3811
|
" -v, --version Print the CLI version.",
|
|
3308
|
-
" --host <host> Select host-specific output projection (codex or
|
|
3812
|
+
" --host <host> Select host-specific output projection (codex, opencode, or cindy).",
|
|
3309
3813
|
"",
|
|
3310
3814
|
"Run `claw help <command>` or `claw help <command> <subcommand>` for detailed help.",
|
|
3311
3815
|
];
|