@veewo/claw 0.2.3 → 0.2.5
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 +44 -0
- package/dist/cli.js +619 -163
- package/dist/cli.js.map +1 -1
- package/dist/codex-driver.d.ts +2 -2
- package/dist/codex-driver.js +80 -37
- package/dist/codex-driver.js.map +1 -1
- package/dist/codex-host-actions.d.ts +15 -0
- package/dist/codex-host-actions.js +75 -0
- package/dist/codex-host-actions.js.map +1 -0
- package/dist/codex-transcript.d.ts +2 -1
- package/dist/codex-transcript.js +52 -2
- package/dist/codex-transcript.js.map +1 -1
- package/dist/command-service.d.ts +102 -0
- package/dist/command-service.js +461 -0
- package/dist/command-service.js.map +1 -0
- package/dist/knowledge-hook-preflight.js +43 -7
- package/dist/knowledge-hook-preflight.js.map +1 -1
- package/dist/search-entry.js +3 -1
- package/dist/search-entry.js.map +1 -1
- package/dist/session-daemon-entry.d.ts +1 -0
- package/dist/session-daemon-entry.js +8 -0
- package/dist/session-daemon-entry.js.map +1 -0
- package/dist/session-daemon.d.ts +9 -0
- package/dist/session-daemon.js +363 -0
- package/dist/session-daemon.js.map +1 -0
- package/dist/session-registry-v2.d.ts +66 -0
- package/dist/session-registry-v2.js +292 -0
- package/dist/session-registry-v2.js.map +1 -0
- package/package.json +6 -2
package/dist/cli.js
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
5
6
|
import { pathToFileURL } from "node:url";
|
|
6
7
|
import { createHash } from "node:crypto";
|
|
7
8
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
-
import { buildDirectWorkflowGuidance, buildKnowledgeDelegateDispatch, buildKnowledgeAssignmentTemplate, buildKnowledgeWriterAssignments, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance,
|
|
9
|
+
import { buildDirectWorkflowGuidance, appendKnowledgeTaskConclusions, buildKnowledgeAtomicDispatch, buildKnowledgeDelegateDispatch, buildKnowledgeAssignmentTemplate, buildKnowledgeWriterAssignments, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findKnowledgeFinalizationJobPath, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolvePlanEffectiveConfig, resolveKnowledgeWriterForHost, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, doneKnowledgeFinalizationJob, readKnowledgeFinalizationJob, waitForKnowledgeFinalizationJobReady, listKnowledgeFinalizationJobs, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, recordKnowledgeFinalizationResult, unbindSession, writePlan, } from "@veewo/claw-core";
|
|
9
10
|
import { buildCodexDriverEnvelope } from "./codex-driver.js";
|
|
11
|
+
import { buildCodexHostActions } from "./codex-host-actions.js";
|
|
10
12
|
import { checkCodexRuntime, resolveCodexSdkEntryPath } from "./codex-runtime.js";
|
|
11
|
-
import { extractLatestFinalAssistantMessage, extractTaskDoneConclusions, } from "./codex-transcript.js";
|
|
13
|
+
import { extractLatestFinalAssistantMessage, extractTaskDoneConclusions, findCodexTranscriptPath, } from "./codex-transcript.js";
|
|
12
14
|
import { consumeBufferedHookInput } from "./knowledge-hook-preflight.js";
|
|
13
15
|
import { resolveInvocationHost, withoutInvocationHost } from "./invocation-host.js";
|
|
14
16
|
import { runOpencodeKnowledgeWriter } from "./opencode-runner.js";
|
|
17
|
+
import { ClawClient, ClawSessionError, } from "@veewo/claw-client";
|
|
15
18
|
const CLI_VERSION = readCliVersion();
|
|
16
19
|
const TOP_LEVEL_COMMANDS = [
|
|
17
20
|
{ name: "init [options]", summary: "Initialize and normalize the .claw project surface." },
|
|
@@ -54,8 +57,12 @@ const COMMAND_HELP = {
|
|
|
54
57
|
],
|
|
55
58
|
},
|
|
56
59
|
session: {
|
|
57
|
-
usage: [
|
|
58
|
-
|
|
60
|
+
usage: [
|
|
61
|
+
"{script} session open <dir> <session-id>",
|
|
62
|
+
"{script} session clean",
|
|
63
|
+
"{script} session clean --expired",
|
|
64
|
+
],
|
|
65
|
+
description: "Open a persistent claw command session or clean legacy ephemeral session workflow state.",
|
|
59
66
|
},
|
|
60
67
|
check: {
|
|
61
68
|
usage: ["{script} check"],
|
|
@@ -160,10 +167,11 @@ const COMMAND_HELP = {
|
|
|
160
167
|
],
|
|
161
168
|
},
|
|
162
169
|
show: {
|
|
163
|
-
usage: ["{script} plan show"],
|
|
170
|
+
usage: ["{script} plan show", "{script} plan show --simple"],
|
|
164
171
|
description: "Show the session-bound current plan, including archived plans through an explicit override.",
|
|
165
172
|
summary: "Show the current plan for a task.",
|
|
166
173
|
options: [
|
|
174
|
+
{ flag: "--simple", detail: "Return only status, goal.text, tasks[].title, and rules." },
|
|
167
175
|
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
168
176
|
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
169
177
|
],
|
|
@@ -275,7 +283,7 @@ const COMMAND_HELP = {
|
|
|
275
283
|
description: "Create a flat subplan file under the task directory. Uses explicit `--template` first, otherwise the project's configured `defaultPlanTemplate`, and finally falls back to the built-in `default`. The current session binding switches to the subplan and returns to its parent when the subplan ends.",
|
|
276
284
|
summary: "Create a subplan under a parent task's task item.",
|
|
277
285
|
options: [
|
|
278
|
-
{ flag: "--parent <task-name>", detail: "(required) Parent task name." },
|
|
286
|
+
{ flag: "--parent <task-name>", detail: "(required) Parent task directory name (`taskName`), not its plan title." },
|
|
279
287
|
{ flag: "--task-id <number>", detail: "(required) Parent task item id to split into a subplan." },
|
|
280
288
|
{ flag: "--template <name>", detail: "Optional plan template name. Overrides the project's configured default template." },
|
|
281
289
|
{ flag: "--template-file <path>", detail: "Exact plan template file. Mutually exclusive with --template." },
|
|
@@ -295,10 +303,11 @@ const COMMAND_HELP = {
|
|
|
295
303
|
],
|
|
296
304
|
},
|
|
297
305
|
search: {
|
|
298
|
-
usage: ["{script} search [<query>] [--limit <n>]", "{script} search index --refresh"],
|
|
306
|
+
usage: ["{script} search [<query>] [--dir <dir>] [--limit <n>]", "{script} search index --refresh"],
|
|
299
307
|
description: "Project-scoped recall over .claw memory, truth, ADR, and declared markdown docs. Use a positional query or --query. Task-local scope (--task/--scope) is rejected; put task materials in plan.references instead.",
|
|
300
308
|
options: [
|
|
301
309
|
{ flag: "--query <text>", detail: "Search query (or pass the query positionally)." },
|
|
310
|
+
{ flag: "--dir <dir>", detail: "Override the project directory for this search only." },
|
|
302
311
|
{ flag: "--limit <n>", detail: "Max number of results." },
|
|
303
312
|
],
|
|
304
313
|
subcommands: {
|
|
@@ -327,10 +336,17 @@ const COMMAND_HELP = {
|
|
|
327
336
|
],
|
|
328
337
|
},
|
|
329
338
|
claim: {
|
|
330
|
-
usage: [
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
339
|
+
usage: [
|
|
340
|
+
"{script} knowledge claim --job <path>",
|
|
341
|
+
"{script} knowledge claim --project-root <path> --finalize-id <id>",
|
|
342
|
+
],
|
|
343
|
+
description: "Claim a queued or retryable job. Codex subagent claims capture the existing task conclusions from the parent transcript before ownership is granted.",
|
|
344
|
+
summary: "Claim a ready finalization job and prepare its report.",
|
|
345
|
+
options: [
|
|
346
|
+
{ flag: "--job <path>", detail: "Exact finalization job JSON path." },
|
|
347
|
+
{ flag: "--project-root <path>", detail: "Project that owns a ready finalization job." },
|
|
348
|
+
{ flag: "--finalize-id <id>", detail: "Finalization id used with --project-root." },
|
|
349
|
+
],
|
|
334
350
|
},
|
|
335
351
|
done: {
|
|
336
352
|
usage: [
|
|
@@ -471,7 +487,7 @@ async function main() {
|
|
|
471
487
|
printJson(buildPublicContextOutput(await runContextCommand(args, process.cwd(), resolveOwnerSessionKey(), effectiveHost)));
|
|
472
488
|
return;
|
|
473
489
|
case "session":
|
|
474
|
-
runSession(args);
|
|
490
|
+
await runSession(args, effectiveHost);
|
|
475
491
|
return;
|
|
476
492
|
case "check":
|
|
477
493
|
const checkResult = ensureProjectProtocol(process.cwd());
|
|
@@ -489,7 +505,7 @@ async function main() {
|
|
|
489
505
|
await runPlan(args, effectiveHost);
|
|
490
506
|
return;
|
|
491
507
|
case "codex":
|
|
492
|
-
runCodex(args);
|
|
508
|
+
await runCodex(args);
|
|
493
509
|
return;
|
|
494
510
|
case "template":
|
|
495
511
|
await runTemplate(args);
|
|
@@ -567,16 +583,55 @@ async function main() {
|
|
|
567
583
|
handleError(error);
|
|
568
584
|
}
|
|
569
585
|
}
|
|
570
|
-
function runCodex(args) {
|
|
586
|
+
async function runCodex(args) {
|
|
571
587
|
const subcommand = args.shift();
|
|
572
|
-
if (subcommand
|
|
573
|
-
|
|
588
|
+
if (subcommand === "driver") {
|
|
589
|
+
assertNoRemainingArgs(args, "codex driver");
|
|
590
|
+
printJson(buildCodexDriverEnvelope(CLI_VERSION));
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (subcommand === "invoke") {
|
|
594
|
+
const encoded = args.shift();
|
|
595
|
+
assertNoRemainingArgs(args, "codex invoke");
|
|
596
|
+
if (!encoded || !/^(?:[a-f0-9]{4})+$/i.test(encoded)) {
|
|
597
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "codex invoke requires one UTF-16 hex argv payload.");
|
|
598
|
+
}
|
|
599
|
+
let json = "";
|
|
600
|
+
for (let index = 0; index < encoded.length; index += 4) {
|
|
601
|
+
json += String.fromCharCode(Number.parseInt(encoded.slice(index, index + 4), 16));
|
|
602
|
+
}
|
|
603
|
+
const invocation = JSON.parse(json);
|
|
604
|
+
if (!Array.isArray(invocation)
|
|
605
|
+
|| invocation.length === 0
|
|
606
|
+
|| invocation.some((value) => typeof value !== "string")
|
|
607
|
+
|| !["plan", "task", "subplan"].includes(invocation[0])
|
|
608
|
+
|| invocation.some((value) => value === "--host")) {
|
|
609
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "codex invoke accepts only a structured plan, task, or subplan argv without --host.");
|
|
610
|
+
}
|
|
611
|
+
const originalArgv = process.argv;
|
|
612
|
+
try {
|
|
613
|
+
process.argv = [originalArgv[0], originalArgv[1], ...invocation, "--host", "codex"];
|
|
614
|
+
await main();
|
|
615
|
+
}
|
|
616
|
+
finally {
|
|
617
|
+
process.argv = originalArgv;
|
|
618
|
+
}
|
|
619
|
+
return;
|
|
574
620
|
}
|
|
575
|
-
|
|
576
|
-
printJson(buildCodexDriverEnvelope(CLI_VERSION));
|
|
621
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown codex subcommand "${subcommand ?? ""}".`);
|
|
577
622
|
}
|
|
578
|
-
function runSession(args) {
|
|
623
|
+
async function runSession(args, effectiveHost) {
|
|
579
624
|
const subcommand = args.shift();
|
|
625
|
+
if (subcommand === "open") {
|
|
626
|
+
const workdir = args.shift();
|
|
627
|
+
const agentSessionId = args.shift();
|
|
628
|
+
assertNoRemainingArgs(args, "session open");
|
|
629
|
+
if (!workdir || !agentSessionId) {
|
|
630
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "session open requires <dir> <session-id> in that order.");
|
|
631
|
+
}
|
|
632
|
+
await runPersistentSession(workdir, agentSessionId, effectiveHost);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
580
635
|
if (subcommand !== "clean") {
|
|
581
636
|
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown session subcommand "${subcommand ?? ""}".`);
|
|
582
637
|
}
|
|
@@ -597,19 +652,419 @@ function runSession(args) {
|
|
|
597
652
|
removed: deleteSessionWorkflow(ownerSessionKey),
|
|
598
653
|
});
|
|
599
654
|
}
|
|
655
|
+
async function runPersistentSession(workdir, agentSessionId, effectiveHost) {
|
|
656
|
+
const opened = await new ClawClient({
|
|
657
|
+
clientKind: "terminal",
|
|
658
|
+
...(effectiveHost ? { host: effectiveHost } : {}),
|
|
659
|
+
}).open(agentSessionId, path.resolve(workdir));
|
|
660
|
+
writeSessionOutput({
|
|
661
|
+
ok: true,
|
|
662
|
+
command: "session.open",
|
|
663
|
+
...opened.openResult,
|
|
664
|
+
});
|
|
665
|
+
const readline = createInterface({
|
|
666
|
+
input: process.stdin,
|
|
667
|
+
output: process.stdout,
|
|
668
|
+
terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
669
|
+
prompt: "claw> ",
|
|
670
|
+
});
|
|
671
|
+
if (process.stdin.isTTY && process.stdout.isTTY)
|
|
672
|
+
readline.prompt();
|
|
673
|
+
try {
|
|
674
|
+
for await (const line of readline) {
|
|
675
|
+
const trimmed = line.trim();
|
|
676
|
+
if (!trimmed) {
|
|
677
|
+
if (process.stdin.isTTY && process.stdout.isTTY)
|
|
678
|
+
readline.prompt();
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const command = parsePersistentSessionCommand(trimmed);
|
|
683
|
+
if (command.kind === "close") {
|
|
684
|
+
await opened.close();
|
|
685
|
+
writeSessionOutput({ ok: true, command: "session.close" });
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (command.kind === "status") {
|
|
689
|
+
writeSessionOutput({ ok: true, command: "session.status", ...await opened.status() });
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
const envelope = await opened.commandEnvelope(command.request);
|
|
693
|
+
writeSessionOutput({
|
|
694
|
+
ok: true,
|
|
695
|
+
command: command.request.operation,
|
|
696
|
+
...envelope,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
writeSessionOutput({ ok: false, error: serializeSessionError(error) });
|
|
702
|
+
if (error instanceof ClawSessionError && error.code === "SESSION_CONNECTION_LOST")
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (process.stdin.isTTY && process.stdout.isTTY)
|
|
706
|
+
readline.prompt();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
finally {
|
|
710
|
+
readline.close();
|
|
711
|
+
try {
|
|
712
|
+
await opened.close();
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
// EOF is a soft close; retained state survives a lost daemon connection.
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function parsePersistentSessionCommand(line) {
|
|
720
|
+
if (line.startsWith("{")) {
|
|
721
|
+
const request = JSON.parse(line);
|
|
722
|
+
if (typeof request.operation !== "string") {
|
|
723
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "JSON session commands require operation.");
|
|
724
|
+
}
|
|
725
|
+
return {
|
|
726
|
+
kind: "command",
|
|
727
|
+
request: { operation: request.operation, input: request.input ?? {} },
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const tokens = tokenizeSessionLine(line);
|
|
731
|
+
const group = tokens.shift();
|
|
732
|
+
const action = tokens.shift();
|
|
733
|
+
if ((group === "session" && action === "close") || group === "exit" || group === "quit") {
|
|
734
|
+
return { kind: "close" };
|
|
735
|
+
}
|
|
736
|
+
if ((group === "session" && action === "status") || group === "status") {
|
|
737
|
+
return { kind: "status" };
|
|
738
|
+
}
|
|
739
|
+
if (group === "plan" && action === "show") {
|
|
740
|
+
const simple = consumeSessionBoolean(tokens, "--simple");
|
|
741
|
+
assertSessionTokensConsumed(tokens, line);
|
|
742
|
+
return {
|
|
743
|
+
kind: "command",
|
|
744
|
+
request: { operation: "plan.show", input: { simple } },
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
if (group === "plan" && action === "leave") {
|
|
748
|
+
assertSessionTokensConsumed(tokens, line);
|
|
749
|
+
return { kind: "command", request: { operation: "plan.leave", input: {} } };
|
|
750
|
+
}
|
|
751
|
+
if (group === "plan" && action === "resume") {
|
|
752
|
+
const planId = tokens.shift();
|
|
753
|
+
assertSessionTokensConsumed(tokens, line);
|
|
754
|
+
return {
|
|
755
|
+
kind: "command",
|
|
756
|
+
request: { operation: "plan.resume", input: planId ? { planId } : {} },
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
if (group === "plan" && action === "create") {
|
|
760
|
+
const title = consumeSessionFlag(tokens, "--title") ?? tokens.shift();
|
|
761
|
+
if (!title)
|
|
762
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "plan create requires a title.");
|
|
763
|
+
const goalText = consumeSessionFlag(tokens, "--goal");
|
|
764
|
+
assertSessionTokensConsumed(tokens, line);
|
|
765
|
+
return {
|
|
766
|
+
kind: "command",
|
|
767
|
+
request: {
|
|
768
|
+
operation: "plan.create",
|
|
769
|
+
input: { title, ...(goalText ? { goalText } : {}) },
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (group === "plan" && action === "wait") {
|
|
774
|
+
assertSessionTokensConsumed(tokens, line);
|
|
775
|
+
return { kind: "command", request: { operation: "plan.wait", input: {} } };
|
|
776
|
+
}
|
|
777
|
+
if (group === "plan" && action === "edit") {
|
|
778
|
+
const operations = parseSessionPlanEditOperations(tokens, line);
|
|
779
|
+
return {
|
|
780
|
+
kind: "command",
|
|
781
|
+
request: { operation: "plan.edit", input: { operations } },
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
if (group === "plan" && action === "done") {
|
|
785
|
+
const retrospectiveSummary = consumeSessionFlag(tokens, "--retrospective");
|
|
786
|
+
if (!retrospectiveSummary) {
|
|
787
|
+
throw new ClawError("RETROSPECTIVE_REQUIRED", "plan done requires --retrospective.");
|
|
788
|
+
}
|
|
789
|
+
const keyDecisions = consumeAllSessionFlags(tokens, "--key-decision");
|
|
790
|
+
const whatWorked = consumeAllSessionFlags(tokens, "--what-worked");
|
|
791
|
+
const issues = consumeAllSessionFlags(tokens, "--issue");
|
|
792
|
+
const followUps = consumeAllSessionFlags(tokens, "--follow-up");
|
|
793
|
+
assertSessionTokensConsumed(tokens, line);
|
|
794
|
+
return {
|
|
795
|
+
kind: "command",
|
|
796
|
+
request: {
|
|
797
|
+
operation: "plan.done",
|
|
798
|
+
input: {
|
|
799
|
+
retrospectiveSummary,
|
|
800
|
+
...(keyDecisions.length ? { keyDecisions } : {}),
|
|
801
|
+
...(whatWorked.length ? { whatWorked } : {}),
|
|
802
|
+
...(issues.length ? { issues } : {}),
|
|
803
|
+
...(followUps.length ? { followUps } : {}),
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (group === "task" && action === "add") {
|
|
809
|
+
const tasks = [];
|
|
810
|
+
while (tokens.length > 0) {
|
|
811
|
+
if (tokens.shift() !== "--title") {
|
|
812
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add expects --title to start each task.");
|
|
813
|
+
}
|
|
814
|
+
const title = tokens.shift();
|
|
815
|
+
if (!title)
|
|
816
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add requires a title.");
|
|
817
|
+
let detail;
|
|
818
|
+
if (tokens[0] === "--detail") {
|
|
819
|
+
tokens.shift();
|
|
820
|
+
detail = tokens.shift();
|
|
821
|
+
if (!detail)
|
|
822
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add --detail requires a value.");
|
|
823
|
+
}
|
|
824
|
+
tasks.push({ title, ...(detail ? { detail } : {}) });
|
|
825
|
+
}
|
|
826
|
+
if (!tasks.length)
|
|
827
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add requires at least one task.");
|
|
828
|
+
return { kind: "command", request: { operation: "task.add", input: { tasks } } };
|
|
829
|
+
}
|
|
830
|
+
if (group === "task" && action === "edit") {
|
|
831
|
+
const id = Number(consumeSessionFlag(tokens, "--id"));
|
|
832
|
+
if (!Number.isInteger(id))
|
|
833
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task edit requires integer --id.");
|
|
834
|
+
const taskTitle = consumeSessionFlag(tokens, "--title");
|
|
835
|
+
const taskDetail = consumeSessionFlag(tokens, "--detail");
|
|
836
|
+
const taskStatus = consumeSessionFlag(tokens, "--status");
|
|
837
|
+
const taskChoiceId = consumeSessionFlag(tokens, "--choice");
|
|
838
|
+
if (!taskTitle && !taskDetail && !taskStatus && !taskChoiceId) {
|
|
839
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task edit requires a field to change.");
|
|
840
|
+
}
|
|
841
|
+
assertSessionTokensConsumed(tokens, line);
|
|
842
|
+
return {
|
|
843
|
+
kind: "command",
|
|
844
|
+
request: {
|
|
845
|
+
operation: "task.edit",
|
|
846
|
+
input: {
|
|
847
|
+
taskId: id,
|
|
848
|
+
...(taskTitle ? { taskTitle } : {}),
|
|
849
|
+
...(taskDetail ? { taskDetail } : {}),
|
|
850
|
+
...(taskStatus ? { taskStatus } : {}),
|
|
851
|
+
...(taskChoiceId ? { taskChoiceId } : {}),
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
if (group === "task" && action === "done") {
|
|
857
|
+
const tasks = [];
|
|
858
|
+
while (tokens.length > 0) {
|
|
859
|
+
if (tokens.shift() !== "--id") {
|
|
860
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done expects --id to start each task.");
|
|
861
|
+
}
|
|
862
|
+
const id = Number(tokens.shift());
|
|
863
|
+
if (!Number.isInteger(id))
|
|
864
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done requires integer ids.");
|
|
865
|
+
let choiceId;
|
|
866
|
+
if (tokens[0] === "--choice") {
|
|
867
|
+
tokens.shift();
|
|
868
|
+
choiceId = tokens.shift();
|
|
869
|
+
if (!choiceId)
|
|
870
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done --choice requires a value.");
|
|
871
|
+
}
|
|
872
|
+
tasks.push({ id, ...(choiceId ? { choiceId } : {}) });
|
|
873
|
+
}
|
|
874
|
+
if (!tasks.length)
|
|
875
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done requires at least one task.");
|
|
876
|
+
return { kind: "command", request: { operation: "task.done", input: { tasks } } };
|
|
877
|
+
}
|
|
878
|
+
if (group === "search") {
|
|
879
|
+
const query = consumeSessionFlag(tokens, "--query") ?? action;
|
|
880
|
+
if (!query)
|
|
881
|
+
throw new ClawError("MEMORY_QUERY_REQUIRED", "search requires a query.");
|
|
882
|
+
const dir = consumeSessionFlag(tokens, "--dir");
|
|
883
|
+
const limitRaw = consumeSessionFlag(tokens, "--limit");
|
|
884
|
+
assertSessionTokensConsumed(tokens, line);
|
|
885
|
+
return {
|
|
886
|
+
kind: "command",
|
|
887
|
+
request: {
|
|
888
|
+
operation: "search",
|
|
889
|
+
input: {
|
|
890
|
+
query,
|
|
891
|
+
...(dir ? { dir } : {}),
|
|
892
|
+
...(limitRaw ? { limit: Number(limitRaw) } : {}),
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
throw new ClawError("SESSION_OPERATION_UNSUPPORTED", `Unsupported persistent session command: ${line}`);
|
|
898
|
+
}
|
|
899
|
+
function tokenizeSessionLine(line) {
|
|
900
|
+
const tokens = [];
|
|
901
|
+
const pattern = /"((?:\\.|[^"])*)"|'((?:\\.|[^'])*)'|(\S+)/g;
|
|
902
|
+
for (const match of line.matchAll(pattern)) {
|
|
903
|
+
tokens.push((match[1] ?? match[2] ?? match[3] ?? "").replace(/\\(["'\\])/g, "$1"));
|
|
904
|
+
}
|
|
905
|
+
return tokens;
|
|
906
|
+
}
|
|
907
|
+
function consumeSessionFlag(tokens, flag) {
|
|
908
|
+
const index = tokens.indexOf(flag);
|
|
909
|
+
if (index < 0)
|
|
910
|
+
return undefined;
|
|
911
|
+
const value = tokens[index + 1];
|
|
912
|
+
if (!value)
|
|
913
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Missing value for ${flag}.`);
|
|
914
|
+
tokens.splice(index, 2);
|
|
915
|
+
return value;
|
|
916
|
+
}
|
|
917
|
+
function consumeAllSessionFlags(tokens, flag) {
|
|
918
|
+
const values = [];
|
|
919
|
+
while (tokens.includes(flag)) {
|
|
920
|
+
values.push(consumeSessionFlag(tokens, flag));
|
|
921
|
+
}
|
|
922
|
+
return values;
|
|
923
|
+
}
|
|
924
|
+
function parseSessionPlanEditOperations(tokens, line) {
|
|
925
|
+
const operations = [];
|
|
926
|
+
const updateFlags = {
|
|
927
|
+
"--goal": "goalText",
|
|
928
|
+
"--requirements": "requirementsSummary",
|
|
929
|
+
"--summary": "planSummary",
|
|
930
|
+
"--rule": "rules",
|
|
931
|
+
"--key-decision": "keyDecisions",
|
|
932
|
+
};
|
|
933
|
+
while (tokens.length > 0) {
|
|
934
|
+
const flag = tokens.shift();
|
|
935
|
+
const value = tokens.shift();
|
|
936
|
+
if (!value)
|
|
937
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Missing value for ${flag}.`);
|
|
938
|
+
if (flag === "--status") {
|
|
939
|
+
operations.push({ type: "plan.status", status: value });
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
const field = updateFlags[flag];
|
|
943
|
+
if (!field) {
|
|
944
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown plan edit argument in "${line}": ${flag}`);
|
|
945
|
+
}
|
|
946
|
+
const listField = field === "rules" || field === "keyDecisions";
|
|
947
|
+
operations.push({
|
|
948
|
+
type: "plan.update",
|
|
949
|
+
updates: { [field]: listField ? [value] : value },
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
if (!operations.length)
|
|
953
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "plan edit requires a field to change.");
|
|
954
|
+
return operations;
|
|
955
|
+
}
|
|
956
|
+
function consumeSessionBoolean(tokens, flag) {
|
|
957
|
+
const index = tokens.indexOf(flag);
|
|
958
|
+
if (index < 0)
|
|
959
|
+
return false;
|
|
960
|
+
tokens.splice(index, 1);
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
963
|
+
function assertSessionTokensConsumed(tokens, line) {
|
|
964
|
+
if (tokens.length > 0) {
|
|
965
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown session command arguments in "${line}": ${tokens.join(" ")}`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function writeSessionOutput(value) {
|
|
969
|
+
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
970
|
+
}
|
|
971
|
+
function serializeSessionError(error) {
|
|
972
|
+
if (error instanceof ClawSessionError) {
|
|
973
|
+
return {
|
|
974
|
+
code: error.code,
|
|
975
|
+
message: error.message,
|
|
976
|
+
retryable: error.retryable,
|
|
977
|
+
outcome: error.outcome,
|
|
978
|
+
...(error.recoveryCommand ? { recoveryCommand: error.recoveryCommand } : {}),
|
|
979
|
+
...(error.details ? { details: error.details } : {}),
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
if (error instanceof ClawError) {
|
|
983
|
+
return { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) };
|
|
984
|
+
}
|
|
985
|
+
return { code: "SESSION_COMMAND_FAILED", message: error instanceof Error ? error.message : String(error) };
|
|
986
|
+
}
|
|
987
|
+
function readCindyKnowledgeClaimCaptureInput() {
|
|
988
|
+
const raw = fs.readFileSync(0, "utf8").trim();
|
|
989
|
+
if (!raw) {
|
|
990
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input is empty.");
|
|
991
|
+
}
|
|
992
|
+
let parsed;
|
|
993
|
+
try {
|
|
994
|
+
parsed = JSON.parse(raw);
|
|
995
|
+
}
|
|
996
|
+
catch {
|
|
997
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input is invalid JSON.");
|
|
998
|
+
}
|
|
999
|
+
const sessionId = typeof parsed.session_id === "string" ? parsed.session_id.trim() : "";
|
|
1000
|
+
const turnId = typeof parsed.turn_id === "string" ? parsed.turn_id.trim() : "";
|
|
1001
|
+
const taskConclusions = Array.isArray(parsed.task_conclusions)
|
|
1002
|
+
? parsed.task_conclusions.flatMap((entry) => {
|
|
1003
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
1004
|
+
return [];
|
|
1005
|
+
const item = entry;
|
|
1006
|
+
const itemTurnId = typeof item.turnId === "string" ? item.turnId.trim() : "";
|
|
1007
|
+
const message = typeof item.message === "string" ? item.message.trim() : "";
|
|
1008
|
+
return itemTurnId && message ? [{ turnId: itemTurnId, message }] : [];
|
|
1009
|
+
})
|
|
1010
|
+
: [];
|
|
1011
|
+
if (!sessionId || !turnId) {
|
|
1012
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input requires session_id and turn_id.");
|
|
1013
|
+
}
|
|
1014
|
+
return { sessionId, turnId, taskConclusions };
|
|
1015
|
+
}
|
|
600
1016
|
function runKnowledge(args) {
|
|
601
1017
|
const subcommand = args.shift();
|
|
602
1018
|
switch (subcommand) {
|
|
1019
|
+
case "list": {
|
|
1020
|
+
const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
|
|
1021
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
1022
|
+
const host = readOptionalFlag(args, "--job-host");
|
|
1023
|
+
assertNoRemainingArgs(args, "knowledge list");
|
|
1024
|
+
const sessionProject = sessionKey ? resolveSessionWorkflowContext(sessionKey) : null;
|
|
1025
|
+
const project = resolveProjectContext(projectRoot);
|
|
1026
|
+
const candidates = sessionProject && sessionProject.clawDir !== project.clawDir
|
|
1027
|
+
? [sessionProject, project]
|
|
1028
|
+
: [sessionProject ?? project];
|
|
1029
|
+
const jobs = candidates.flatMap((candidate) => listKnowledgeFinalizationJobs(candidate))
|
|
1030
|
+
.map((jobPath) => ({ jobPath, job: readKnowledgeFinalizationJob(jobPath) }))
|
|
1031
|
+
.filter(({ job }) => (job.status === "running"
|
|
1032
|
+
|| ((job.status === "queued" || job.status === "failed") && job.attempts < 3)) && (!host || job.host === host))
|
|
1033
|
+
.map(({ jobPath, job }) => ({
|
|
1034
|
+
jobPath,
|
|
1035
|
+
finalizeId: job.finalizeId,
|
|
1036
|
+
status: job.status,
|
|
1037
|
+
attempts: job.attempts,
|
|
1038
|
+
}));
|
|
1039
|
+
printJson({ ok: true, command: "knowledge.list", jobs });
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
603
1042
|
case "wait": {
|
|
604
1043
|
const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
|
|
605
1044
|
const finalizeId = readRequiredFlag(args, "--finalize-id");
|
|
1045
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
606
1046
|
const timeoutMs = readOptionalNumber(args, "--timeout-ms") ?? 300_000;
|
|
607
1047
|
assertNoRemainingArgs(args, "knowledge wait");
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
1048
|
+
const sessionProject = sessionKey ? resolveSessionWorkflowContext(sessionKey) : null;
|
|
1049
|
+
const project = resolveProjectContext(projectRoot);
|
|
1050
|
+
const candidates = sessionProject && sessionProject.clawDir !== project.clawDir
|
|
1051
|
+
? [sessionProject, project]
|
|
1052
|
+
: [sessionProject ?? project];
|
|
1053
|
+
let located = candidates
|
|
1054
|
+
.map((candidate) => findKnowledgeFinalizationJobPath(candidate, finalizeId))
|
|
1055
|
+
.find((candidate) => Boolean(candidate));
|
|
1056
|
+
if (!located && timeoutMs > 0) {
|
|
1057
|
+
located = waitForKnowledgeFinalizationJobReady({
|
|
1058
|
+
project: candidates[0],
|
|
1059
|
+
finalizeId,
|
|
1060
|
+
timeoutMs,
|
|
1061
|
+
}).jobPath;
|
|
1062
|
+
}
|
|
1063
|
+
if (!located) {
|
|
1064
|
+
throw new Error(`Knowledge finalization ${finalizeId} is unavailable.`);
|
|
1065
|
+
}
|
|
1066
|
+
const jobPath = located;
|
|
1067
|
+
const job = readKnowledgeFinalizationJob(jobPath);
|
|
613
1068
|
printJson({
|
|
614
1069
|
ok: true,
|
|
615
1070
|
command: "knowledge.wait",
|
|
@@ -620,9 +1075,68 @@ function runKnowledge(args) {
|
|
|
620
1075
|
return;
|
|
621
1076
|
}
|
|
622
1077
|
case "claim": {
|
|
623
|
-
const
|
|
1078
|
+
const explicitJobPath = readOptionalFlag(args, "--job");
|
|
1079
|
+
const projectRoot = readOptionalFlag(args, "--project-root");
|
|
1080
|
+
const finalizeId = readOptionalFlag(args, "--finalize-id");
|
|
1081
|
+
const captureCindyReport = readBooleanFlag(args, "--cindy-report-stdin");
|
|
1082
|
+
const cindyCapture = captureCindyReport ? readCindyKnowledgeClaimCaptureInput() : undefined;
|
|
1083
|
+
if (explicitJobPath && (projectRoot || finalizeId)) {
|
|
1084
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge claim accepts either --job or --project-root with --finalize-id.");
|
|
1085
|
+
}
|
|
1086
|
+
if (!explicitJobPath && (!projectRoot || !finalizeId)) {
|
|
1087
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge claim requires --job or both --project-root and --finalize-id.");
|
|
1088
|
+
}
|
|
624
1089
|
assertNoRemainingArgs(args, "knowledge claim");
|
|
625
|
-
const
|
|
1090
|
+
const jobPath = explicitJobPath ?? findKnowledgeFinalizationJobPath(resolveProjectContext(path.resolve(projectRoot)), finalizeId);
|
|
1091
|
+
if (!jobPath) {
|
|
1092
|
+
throw new Error(`Knowledge finalization ${finalizeId} is unavailable.`);
|
|
1093
|
+
}
|
|
1094
|
+
const job = claimKnowledgeFinalizationJob(jobPath, {
|
|
1095
|
+
prepare: (queued) => {
|
|
1096
|
+
if (queued.writer?.executionPolicy !== "subagent"
|
|
1097
|
+
|| queued.reportCapture?.mode !== "claim"
|
|
1098
|
+
|| queued.reportCapture.status === "captured") {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if (queued.host === "cindy") {
|
|
1102
|
+
if (!cindyCapture) {
|
|
1103
|
+
throw new Error(`Cindy report capture is unavailable for knowledge session ${queued.sessionId}.`);
|
|
1104
|
+
}
|
|
1105
|
+
if (cindyCapture.sessionId !== queued.sessionId) {
|
|
1106
|
+
throw new Error("Cindy report capture does not match the originating knowledge session.");
|
|
1107
|
+
}
|
|
1108
|
+
const capturedAt = new Date().toISOString();
|
|
1109
|
+
appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, cindyCapture.taskConclusions, capturedAt);
|
|
1110
|
+
return {
|
|
1111
|
+
reportCapture: {
|
|
1112
|
+
...queued.reportCapture,
|
|
1113
|
+
status: "captured",
|
|
1114
|
+
capturedAt,
|
|
1115
|
+
messageCount: cindyCapture.taskConclusions.length,
|
|
1116
|
+
},
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
if (queued.host !== "codex") {
|
|
1120
|
+
throw new Error(`Claim-time report capture is unavailable for host ${queued.host ?? "unknown"}.`);
|
|
1121
|
+
}
|
|
1122
|
+
const transcriptPath = findCodexTranscriptPath(queued.sessionId);
|
|
1123
|
+
if (!transcriptPath) {
|
|
1124
|
+
throw new Error(`Codex transcript is unavailable for knowledge session ${queued.sessionId}.`);
|
|
1125
|
+
}
|
|
1126
|
+
const conclusions = extractTaskDoneConclusions(transcriptPath, undefined, queued.reportCapture.startedAt);
|
|
1127
|
+
const capturedAt = new Date().toISOString();
|
|
1128
|
+
appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, conclusions, capturedAt);
|
|
1129
|
+
return {
|
|
1130
|
+
reportCapture: {
|
|
1131
|
+
...queued.reportCapture,
|
|
1132
|
+
status: "captured",
|
|
1133
|
+
capturedAt,
|
|
1134
|
+
transcriptPath,
|
|
1135
|
+
messageCount: conclusions.length,
|
|
1136
|
+
},
|
|
1137
|
+
};
|
|
1138
|
+
},
|
|
1139
|
+
});
|
|
626
1140
|
const assignments = job ? buildKnowledgeWriterAssignments(job) : [];
|
|
627
1141
|
const templatePath = job
|
|
628
1142
|
? path.join(path.dirname(jobPath), `${job.finalizeId}.assignments.json`)
|
|
@@ -640,6 +1154,7 @@ function runKnowledge(args) {
|
|
|
640
1154
|
claimed: Boolean(job),
|
|
641
1155
|
...(job ? {
|
|
642
1156
|
finalizeId: job.finalizeId,
|
|
1157
|
+
jobPath,
|
|
643
1158
|
claimToken: job.claimToken,
|
|
644
1159
|
projectRoot: job.projectRoot,
|
|
645
1160
|
writer: job.writer ?? null,
|
|
@@ -715,21 +1230,22 @@ async function runPlan(args, effectiveHost) {
|
|
|
715
1230
|
throw new ClawError("PROJECT_CONFIG_INVALID", "plan edit requires at least one plan field or --status.");
|
|
716
1231
|
}
|
|
717
1232
|
const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
|
|
718
|
-
const
|
|
719
|
-
const current =
|
|
1233
|
+
const entersEndTerminal = requestsPlanEndTerminal(operations);
|
|
1234
|
+
const current = entersEndTerminal
|
|
720
1235
|
? showPlan({ cwd: process.cwd(), ...target, ownerSessionKey })
|
|
721
1236
|
: undefined;
|
|
722
|
-
const project =
|
|
723
|
-
const effectiveWriter = current && project
|
|
1237
|
+
const project = entersEndTerminal ? tryResolveHookProject(process.cwd()) : null;
|
|
1238
|
+
const effectiveWriter = resolveKnowledgeWriterForHost(current && project
|
|
724
1239
|
? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
|
|
725
|
-
: undefined;
|
|
1240
|
+
: undefined, effectiveHost);
|
|
726
1241
|
if (current
|
|
727
1242
|
&& !current.plan.parentPlan
|
|
728
1243
|
&& effectiveWriter?.executionPolicy === "subagent"
|
|
729
|
-
&& effectiveHost !== "codex"
|
|
730
|
-
|
|
1244
|
+
&& effectiveHost !== "codex"
|
|
1245
|
+
&& effectiveHost !== "cindy") {
|
|
1246
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
|
|
731
1247
|
}
|
|
732
|
-
const queuePlanEndFinalization =
|
|
1248
|
+
const queuePlanEndFinalization = entersEndTerminal
|
|
733
1249
|
? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
|
|
734
1250
|
: undefined;
|
|
735
1251
|
const result = await editPlan({
|
|
@@ -743,11 +1259,12 @@ async function runPlan(args, effectiveHost) {
|
|
|
743
1259
|
const completionRefresh = queuePlanEndFinalization?.(result.taskName);
|
|
744
1260
|
const knowledgeDispatch = (current
|
|
745
1261
|
&& project
|
|
746
|
-
&& effectiveHost === "codex"
|
|
1262
|
+
&& (effectiveHost === "codex" || effectiveHost === "cindy")
|
|
747
1263
|
&& !current.plan.parentPlan
|
|
748
1264
|
&& effectiveWriter?.executionPolicy === "subagent"
|
|
749
1265
|
&& result.knowledgeFinalizeId)
|
|
750
1266
|
? buildKnowledgeDispatch({
|
|
1267
|
+
host: effectiveHost,
|
|
751
1268
|
finalizeId: result.knowledgeFinalizeId,
|
|
752
1269
|
writer: effectiveWriter,
|
|
753
1270
|
})
|
|
@@ -826,13 +1343,14 @@ async function runPlan(args, effectiveHost) {
|
|
|
826
1343
|
ownerSessionKey,
|
|
827
1344
|
});
|
|
828
1345
|
const project = tryResolveHookProject(process.cwd());
|
|
829
|
-
const effectiveWriter = project
|
|
1346
|
+
const effectiveWriter = resolveKnowledgeWriterForHost(project
|
|
830
1347
|
? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
|
|
831
|
-
: undefined;
|
|
1348
|
+
: undefined, effectiveHost);
|
|
832
1349
|
if (!current.plan.parentPlan
|
|
833
1350
|
&& effectiveWriter?.executionPolicy === "subagent"
|
|
834
|
-
&& effectiveHost !== "codex"
|
|
835
|
-
|
|
1351
|
+
&& effectiveHost !== "codex"
|
|
1352
|
+
&& effectiveHost !== "cindy") {
|
|
1353
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
|
|
836
1354
|
}
|
|
837
1355
|
const queuePlanEndFinalization = preparePlanEndFinalization(process.cwd(), ownerSessionKey);
|
|
838
1356
|
const result = await editPlan({
|
|
@@ -844,11 +1362,12 @@ async function runPlan(args, effectiveHost) {
|
|
|
844
1362
|
ownerSessionKey,
|
|
845
1363
|
});
|
|
846
1364
|
const completionRefresh = queuePlanEndFinalization?.(result.taskName);
|
|
847
|
-
const knowledgeDispatch = (effectiveHost === "codex"
|
|
1365
|
+
const knowledgeDispatch = ((effectiveHost === "codex" || effectiveHost === "cindy")
|
|
848
1366
|
&& !current.plan.parentPlan
|
|
849
1367
|
&& effectiveWriter?.executionPolicy === "subagent"
|
|
850
1368
|
&& result.knowledgeFinalizeId)
|
|
851
1369
|
? buildKnowledgeDispatch({
|
|
1370
|
+
host: effectiveHost,
|
|
852
1371
|
finalizeId: result.knowledgeFinalizeId,
|
|
853
1372
|
writer: effectiveWriter,
|
|
854
1373
|
})
|
|
@@ -857,6 +1376,7 @@ async function runPlan(args, effectiveHost) {
|
|
|
857
1376
|
return;
|
|
858
1377
|
}
|
|
859
1378
|
case "show": {
|
|
1379
|
+
const simple = readBooleanFlag(args, "--simple");
|
|
860
1380
|
const target = readPlanMutationTarget(args);
|
|
861
1381
|
assertNoRemainingArgs(args, "plan show");
|
|
862
1382
|
const result = showPlan({
|
|
@@ -864,6 +1384,10 @@ async function runPlan(args, effectiveHost) {
|
|
|
864
1384
|
...target,
|
|
865
1385
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
866
1386
|
});
|
|
1387
|
+
if (simple) {
|
|
1388
|
+
printJson(result.simplePlanView);
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
867
1391
|
printJson({
|
|
868
1392
|
ok: true,
|
|
869
1393
|
command: "plan.show",
|
|
@@ -970,7 +1494,7 @@ async function runTask(args, effectiveHost) {
|
|
|
970
1494
|
cwd: process.cwd(),
|
|
971
1495
|
...target,
|
|
972
1496
|
operations,
|
|
973
|
-
commandSource: "
|
|
1497
|
+
commandSource: "task.add",
|
|
974
1498
|
host: effectiveHost,
|
|
975
1499
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
976
1500
|
});
|
|
@@ -986,7 +1510,7 @@ async function runTask(args, effectiveHost) {
|
|
|
986
1510
|
cwd: process.cwd(),
|
|
987
1511
|
...target,
|
|
988
1512
|
operations,
|
|
989
|
-
commandSource: "
|
|
1513
|
+
commandSource: "task.edit",
|
|
990
1514
|
host: effectiveHost,
|
|
991
1515
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
992
1516
|
});
|
|
@@ -1002,7 +1526,7 @@ async function runTask(args, effectiveHost) {
|
|
|
1002
1526
|
cwd: process.cwd(),
|
|
1003
1527
|
...target,
|
|
1004
1528
|
operations,
|
|
1005
|
-
commandSource: "
|
|
1529
|
+
commandSource: "task.remove",
|
|
1006
1530
|
host: effectiveHost,
|
|
1007
1531
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
1008
1532
|
});
|
|
@@ -1018,6 +1542,7 @@ async function runTask(args, effectiveHost) {
|
|
|
1018
1542
|
cwd: process.cwd(),
|
|
1019
1543
|
...target,
|
|
1020
1544
|
operations,
|
|
1545
|
+
commandSource: "task.done",
|
|
1021
1546
|
host: effectiveHost,
|
|
1022
1547
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
1023
1548
|
});
|
|
@@ -1052,11 +1577,12 @@ async function runSearch(args) {
|
|
|
1052
1577
|
if (args.includes("--scope") || args.includes("--task")) {
|
|
1053
1578
|
throw new ClawError("PROJECT_CONFIG_INVALID", "claw search is project-scoped only. Put task-specific materials in plan.references instead of using task-local search.");
|
|
1054
1579
|
}
|
|
1580
|
+
const dir = readOptionalFlag(args, "--dir");
|
|
1055
1581
|
printJson({
|
|
1056
1582
|
ok: true,
|
|
1057
1583
|
command: "search",
|
|
1058
1584
|
...await searchMemoryAsync({
|
|
1059
|
-
cwd: process.cwd(),
|
|
1585
|
+
cwd: dir ? path.resolve(process.cwd(), dir) : process.cwd(),
|
|
1060
1586
|
limit: readOptionalNumber(args, "--limit"),
|
|
1061
1587
|
query: readRequiredSearchQuery(args),
|
|
1062
1588
|
scope: "project",
|
|
@@ -1269,7 +1795,7 @@ function buildPublicContextOutput(context) {
|
|
|
1269
1795
|
if (Object.keys(compactRecovery).length > 0) {
|
|
1270
1796
|
output.startupRecovery = compactRecovery;
|
|
1271
1797
|
}
|
|
1272
|
-
const searchGuidance = buildContextSearchGuidance(context);
|
|
1798
|
+
const searchGuidance = buildContextSearchGuidance(context, "rg");
|
|
1273
1799
|
if (searchGuidance) {
|
|
1274
1800
|
output.searchGuidance = searchGuidance;
|
|
1275
1801
|
}
|
|
@@ -1281,20 +1807,26 @@ function shouldExposeVersionSync(versionSync) {
|
|
|
1281
1807
|
|| versionSync.updateAvailable === true
|
|
1282
1808
|
|| versionSync.projectVersion !== versionSync.cliVersion;
|
|
1283
1809
|
}
|
|
1284
|
-
function buildContextSearchGuidance(context) {
|
|
1810
|
+
function buildContextSearchGuidance(context, style = "default") {
|
|
1285
1811
|
const project = asJsonRecord(context.project);
|
|
1286
1812
|
const projectConfig = asJsonRecord(project?.projectConfig);
|
|
1287
1813
|
const memory = asJsonRecord(projectConfig?.memory);
|
|
1288
1814
|
const embeddingEnabled = memory?.enabled === true && asJsonRecord(memory.embedding) !== null;
|
|
1289
1815
|
const gitnexusEnabled = projectConfig?.gitnexus === true;
|
|
1290
1816
|
if (embeddingEnabled && gitnexusEnabled) {
|
|
1291
|
-
return
|
|
1817
|
+
return style === "rg"
|
|
1818
|
+
? "Before using `rg`, use `claw search --query` to narrow the document search scope and GitNexus to narrow the code search scope, then use `rg` to locate exact files or symbols."
|
|
1819
|
+
: "When useful, use `claw search` to narrow the document search scope and GitNexus to narrow the code search scope, then use the default search to locate exact files or symbols.";
|
|
1292
1820
|
}
|
|
1293
1821
|
if (embeddingEnabled) {
|
|
1294
|
-
return
|
|
1822
|
+
return style === "rg"
|
|
1823
|
+
? "Before using `rg`, use `claw search --query` to narrow the document search scope, then use `rg` to locate exact files or symbols."
|
|
1824
|
+
: "When useful, use `claw search` to narrow the document search scope, then use the default search to locate exact files or symbols.";
|
|
1295
1825
|
}
|
|
1296
1826
|
if (gitnexusEnabled) {
|
|
1297
|
-
return
|
|
1827
|
+
return style === "rg"
|
|
1828
|
+
? "Before using `rg`, use GitNexus to narrow the code search scope, then use `rg` to locate exact files or symbols."
|
|
1829
|
+
: "When useful, use GitNexus to narrow the code search scope, then use the default search to locate exact files or symbols.";
|
|
1298
1830
|
}
|
|
1299
1831
|
return null;
|
|
1300
1832
|
}
|
|
@@ -1505,12 +2037,12 @@ async function runInternalKnowledgeCapture(args, effectiveHost) {
|
|
|
1505
2037
|
const turnId = readHookString(payload, "turn_id");
|
|
1506
2038
|
const message = readHookString(payload, "message");
|
|
1507
2039
|
const taskConclusions = readHookTaskConclusions(payload, turnId);
|
|
1508
|
-
if (!hookCwd || !sessionId || !turnId || !message
|
|
2040
|
+
if (!hookCwd || !sessionId || !turnId || !message) {
|
|
1509
2041
|
printJson({ ok: true, captured: false });
|
|
1510
2042
|
return;
|
|
1511
2043
|
}
|
|
1512
2044
|
try {
|
|
1513
|
-
const project =
|
|
2045
|
+
const project = resolveWorkflowProjectContext(hookCwd, sessionId);
|
|
1514
2046
|
const result = tryCaptureKnowledgeStop({
|
|
1515
2047
|
project,
|
|
1516
2048
|
sessionId,
|
|
@@ -1592,7 +2124,7 @@ function completeKnowledgeFinalizationJob(jobPath, result, claimToken) {
|
|
|
1592
2124
|
if (running.claimToken !== claimToken) {
|
|
1593
2125
|
throw new Error("Knowledge finalization completion does not match the active claim.");
|
|
1594
2126
|
}
|
|
1595
|
-
const project =
|
|
2127
|
+
const project = resolveKnowledgeJobProject(jobPath, running);
|
|
1596
2128
|
const finishedAt = new Date().toISOString();
|
|
1597
2129
|
const truthEncoding = normalizeTruthMarkdownEncoding(project);
|
|
1598
2130
|
recordKnowledgeFinalizationResult(project, running.reportPath, {
|
|
@@ -1626,6 +2158,17 @@ function completeKnowledgeFinalizationJob(jobPath, result, claimToken) {
|
|
|
1626
2158
|
});
|
|
1627
2159
|
printJson({ ok: true, completed: true, alreadyDone: terminal.alreadyDone, finalizeId: running.finalizeId });
|
|
1628
2160
|
}
|
|
2161
|
+
function resolveKnowledgeJobProject(jobPath, job) {
|
|
2162
|
+
const project = resolveProjectContext(job.projectRoot);
|
|
2163
|
+
const sessionProject = resolveSessionWorkflowContext(job.sessionId);
|
|
2164
|
+
for (const candidate of sessionProject ? [sessionProject, project] : [project]) {
|
|
2165
|
+
const relative = path.relative(candidate.clawDir, path.resolve(jobPath));
|
|
2166
|
+
if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
2167
|
+
return candidate;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
throw new Error("Knowledge finalization job is outside its project or session workflow.");
|
|
2171
|
+
}
|
|
1629
2172
|
function failKnowledgeFinalizationJob(jobPath, message, claimToken) {
|
|
1630
2173
|
const terminal = doneKnowledgeFinalizationJob({
|
|
1631
2174
|
jobPath,
|
|
@@ -1677,7 +2220,7 @@ function runInternalKnowledgeSweep(args) {
|
|
|
1677
2220
|
printJson({ command: "internal-knowledge-sweep", ok: true, launched: 0, skipped: true });
|
|
1678
2221
|
return;
|
|
1679
2222
|
}
|
|
1680
|
-
const jobs = listRetryableKnowledgeFinalizationJobs(project);
|
|
2223
|
+
const jobs = listRetryableKnowledgeFinalizationJobs(project, { excludeHosts: ["cindy"] });
|
|
1681
2224
|
let launched = 0;
|
|
1682
2225
|
for (const jobPath of jobs) {
|
|
1683
2226
|
try {
|
|
@@ -1714,7 +2257,7 @@ async function runInternalBackgroundMaintenance(args) {
|
|
|
1714
2257
|
let launched = 0;
|
|
1715
2258
|
const knowledgeProject = project ?? sessionProject;
|
|
1716
2259
|
if (knowledgeProject) {
|
|
1717
|
-
const jobs = listRetryableKnowledgeFinalizationJobs(knowledgeProject);
|
|
2260
|
+
const jobs = listRetryableKnowledgeFinalizationJobs(knowledgeProject, { excludeHosts: ["cindy"] });
|
|
1718
2261
|
discovered = jobs.length;
|
|
1719
2262
|
for (const jobPath of jobs) {
|
|
1720
2263
|
try {
|
|
@@ -1914,7 +2457,7 @@ async function runSessionStartHook(effectiveHost) {
|
|
|
1914
2457
|
const context = await runContextCommand([], hookCwd, ownerSessionKey, effectiveHost);
|
|
1915
2458
|
const contextProject = asJsonRecord(context.project);
|
|
1916
2459
|
const retryableJobs = effectiveHost !== "cindy" && contextProject?.scope !== "session" && !context.error
|
|
1917
|
-
? listRetryableKnowledgeFinalizationJobs(resolveProjectContext(hookCwd))
|
|
2460
|
+
? listRetryableKnowledgeFinalizationJobs(resolveProjectContext(hookCwd), { excludeHosts: ["cindy"] })
|
|
1918
2461
|
: [];
|
|
1919
2462
|
if (contextProject?.scope !== "session"
|
|
1920
2463
|
&& !context.error
|
|
@@ -2071,31 +2614,21 @@ function buildSessionStartAdditionalContext(context, sessionCwd, effectiveHost)
|
|
|
2071
2614
|
* supplies project identity or a recovered workflow snapshot. Goal Mode is
|
|
2072
2615
|
* intentionally omitted because Cindy does not expose that Host surface.
|
|
2073
2616
|
*/
|
|
2074
|
-
function buildCindySessionStartContext(context,
|
|
2075
|
-
const
|
|
2076
|
-
|
|
2077
|
-
|
|
2617
|
+
function buildCindySessionStartContext(context, _sessionCwd, versionSyncPrompt) {
|
|
2618
|
+
const lines = [];
|
|
2619
|
+
const startupRecovery = asJsonRecord(context.startupRecovery);
|
|
2620
|
+
const fixedPaths = Array.isArray(startupRecovery?.fixedPaths)
|
|
2621
|
+
? startupRecovery.fixedPaths.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
2622
|
+
: [];
|
|
2623
|
+
if (startupRecovery?.corrected === true) {
|
|
2624
|
+
lines.push(`claw-kit repaired the project configuration${fixedPaths.length > 0 ? `: ${fixedPaths.join(", ")}` : "."}`);
|
|
2078
2625
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
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")}`;
|
|
2626
|
+
if (versionSyncPrompt?.placement === "prefix")
|
|
2627
|
+
lines.push(...versionSyncPrompt.lines);
|
|
2628
|
+
lines.push("Before using `rg`, use `claw search --query` to narrow the document search scope and GitNexus to narrow the code search scope, then use `rg` to locate exact files or symbols.");
|
|
2629
|
+
if (versionSyncPrompt?.placement === "suffix")
|
|
2630
|
+
lines.push(...versionSyncPrompt.lines);
|
|
2631
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
2099
2632
|
}
|
|
2100
2633
|
function stripCindyGoalModeLines(prompt) {
|
|
2101
2634
|
return prompt
|
|
@@ -2373,9 +2906,13 @@ function stripBom(content) {
|
|
|
2373
2906
|
return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
|
|
2374
2907
|
}
|
|
2375
2908
|
function buildKnowledgeDispatch(input) {
|
|
2909
|
+
if (input.host === "cindy") {
|
|
2910
|
+
return buildKnowledgeAtomicDispatch(input);
|
|
2911
|
+
}
|
|
2376
2912
|
return buildKnowledgeDelegateDispatch({
|
|
2377
2913
|
policy: "subagent",
|
|
2378
|
-
|
|
2914
|
+
finalizeId: input.finalizeId,
|
|
2915
|
+
writer: input.writer,
|
|
2379
2916
|
});
|
|
2380
2917
|
}
|
|
2381
2918
|
function compactPlanCommandResult(command, result, effectiveHost, completionRefresh, forceProjectionSync = false, knowledgeDispatch) {
|
|
@@ -2386,7 +2923,7 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
|
|
|
2386
2923
|
const resolvedPlanPath = archivedPlanPath ?? result.planPath;
|
|
2387
2924
|
const codexResult = effectiveHost === "codex";
|
|
2388
2925
|
const cindyResult = effectiveHost === "cindy";
|
|
2389
|
-
const hostActions = codexResult ?
|
|
2926
|
+
const hostActions = codexResult ? buildCodexHostActions(result, { forceProjectionSync, actionIdPrefix: command === "plan.sync" ? `plan.sync:${createHash("sha256").update(result.planPath).digest("hex").slice(0, 16)}` : undefined }) : [];
|
|
2390
2927
|
const nextsteps = codexResult
|
|
2391
2928
|
&& result.planStatus === "end.completed"
|
|
2392
2929
|
&& result.workflowGuidance.goalTool?.tool === "update_goal"
|
|
@@ -2461,87 +2998,6 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
|
|
|
2461
2998
|
...(!codexResult || !includePlan ? { planSummary } : {}),
|
|
2462
2999
|
};
|
|
2463
3000
|
}
|
|
2464
|
-
function buildHostActions(result, options = {}) {
|
|
2465
|
-
const latestEvent = result.events?.at(-1);
|
|
2466
|
-
const actionIdPrefix = options.actionIdPrefix ?? latestEvent?.mutationId;
|
|
2467
|
-
if (!actionIdPrefix) {
|
|
2468
|
-
return [];
|
|
2469
|
-
}
|
|
2470
|
-
const actions = [];
|
|
2471
|
-
const goalTool = result.workflowGuidance.goalTool;
|
|
2472
|
-
const isSubplanGoalHandoff = Boolean(result.plan?.parentPlan
|
|
2473
|
-
&& goalTool?.tool === "update_goal"
|
|
2474
|
-
&& goalTool.status === "complete");
|
|
2475
|
-
if (isSubplanGoalHandoff && goalTool?.tool === "update_goal") {
|
|
2476
|
-
actions.push({
|
|
2477
|
-
schemaVersion: 1,
|
|
2478
|
-
id: `${actionIdPrefix}:update_goal`,
|
|
2479
|
-
tool: "update_goal",
|
|
2480
|
-
input: {
|
|
2481
|
-
status: "complete",
|
|
2482
|
-
},
|
|
2483
|
-
});
|
|
2484
|
-
}
|
|
2485
|
-
if (result.plan
|
|
2486
|
-
&& shouldUsePlanHostIntegration(result.plan)
|
|
2487
|
-
&& result.plan.tasks.length > 0
|
|
2488
|
-
&& (options.forceProjectionSync || codexPlanProjectionChanged(result.previousPlan, result.plan, result.planStatus))) {
|
|
2489
|
-
const plan = buildCodexPlanProjection(result.plan, result.planStatus);
|
|
2490
|
-
actions.push({
|
|
2491
|
-
schemaVersion: 1,
|
|
2492
|
-
id: `${actionIdPrefix}:update_plan`,
|
|
2493
|
-
tool: "update_plan",
|
|
2494
|
-
input: {
|
|
2495
|
-
explanation: result.workflowGuidance.summary,
|
|
2496
|
-
plan,
|
|
2497
|
-
},
|
|
2498
|
-
});
|
|
2499
|
-
}
|
|
2500
|
-
if (goalTool && !isSubplanGoalHandoff) {
|
|
2501
|
-
if (goalTool.tool === "create_goal") {
|
|
2502
|
-
actions.push({
|
|
2503
|
-
schemaVersion: 1,
|
|
2504
|
-
id: `${actionIdPrefix}:create_goal`,
|
|
2505
|
-
tool: "create_goal",
|
|
2506
|
-
input: {
|
|
2507
|
-
objective: goalTool.objective,
|
|
2508
|
-
},
|
|
2509
|
-
});
|
|
2510
|
-
}
|
|
2511
|
-
else {
|
|
2512
|
-
const codexStatus = result.planStatus === "process.wait" || result.planStatus === "process.discussing"
|
|
2513
|
-
? "complete"
|
|
2514
|
-
: goalTool.status;
|
|
2515
|
-
actions.push({
|
|
2516
|
-
schemaVersion: 1,
|
|
2517
|
-
id: `${actionIdPrefix}:update_goal`,
|
|
2518
|
-
tool: "update_goal",
|
|
2519
|
-
input: {
|
|
2520
|
-
status: codexStatus,
|
|
2521
|
-
},
|
|
2522
|
-
});
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
return actions;
|
|
2526
|
-
}
|
|
2527
|
-
function codexPlanProjectionChanged(previousPlan, plan, planStatus) {
|
|
2528
|
-
if (!previousPlan)
|
|
2529
|
-
return true;
|
|
2530
|
-
return JSON.stringify(buildCodexPlanProjection(previousPlan, previousPlan.status))
|
|
2531
|
-
!== JSON.stringify(buildCodexPlanProjection(plan, planStatus));
|
|
2532
|
-
}
|
|
2533
|
-
function buildCodexPlanProjection(plan, planStatus) {
|
|
2534
|
-
const activeTask = plan.tasks.find((task) => task.status === "in_progress" || task.status === "subagent_running") ?? (planStatus === "process.active"
|
|
2535
|
-
? plan.tasks.find((task) => task.status !== "done")
|
|
2536
|
-
: undefined);
|
|
2537
|
-
return plan.tasks.map((task) => {
|
|
2538
|
-
let status = task.status === "done" ? "completed" : "pending";
|
|
2539
|
-
if (task.id === activeTask?.id) {
|
|
2540
|
-
status = "in_progress";
|
|
2541
|
-
}
|
|
2542
|
-
return { step: task.title, status };
|
|
2543
|
-
});
|
|
2544
|
-
}
|
|
2545
3001
|
function compactDirectCommandResult(command, workflowGuidance, completionRefresh) {
|
|
2546
3002
|
return {
|
|
2547
3003
|
ok: true,
|
|
@@ -2629,7 +3085,7 @@ function readOrderedPlanEditOperations(args) {
|
|
|
2629
3085
|
}
|
|
2630
3086
|
return operations;
|
|
2631
3087
|
}
|
|
2632
|
-
function
|
|
3088
|
+
function requestsPlanEndTerminal(operations) {
|
|
2633
3089
|
return operations.some((operation) => operation.type === "plan.status" && operation.status.startsWith("end."));
|
|
2634
3090
|
}
|
|
2635
3091
|
function planCompletionOperations(updates) {
|