@veewo/claw 0.2.3 → 0.2.4
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 +492 -141
- 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/command-service.d.ts +102 -0
- package/dist/command-service.js +455 -0
- package/dist/command-service.js.map +1 -0
- package/dist/knowledge-hook-preflight.js +37 -5
- 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, 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, 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
13
|
import { extractLatestFinalAssistantMessage, extractTaskDoneConclusions, } 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
|
],
|
|
@@ -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: {
|
|
@@ -471,7 +480,7 @@ async function main() {
|
|
|
471
480
|
printJson(buildPublicContextOutput(await runContextCommand(args, process.cwd(), resolveOwnerSessionKey(), effectiveHost)));
|
|
472
481
|
return;
|
|
473
482
|
case "session":
|
|
474
|
-
runSession(args);
|
|
483
|
+
await runSession(args, effectiveHost);
|
|
475
484
|
return;
|
|
476
485
|
case "check":
|
|
477
486
|
const checkResult = ensureProjectProtocol(process.cwd());
|
|
@@ -489,7 +498,7 @@ async function main() {
|
|
|
489
498
|
await runPlan(args, effectiveHost);
|
|
490
499
|
return;
|
|
491
500
|
case "codex":
|
|
492
|
-
runCodex(args);
|
|
501
|
+
await runCodex(args);
|
|
493
502
|
return;
|
|
494
503
|
case "template":
|
|
495
504
|
await runTemplate(args);
|
|
@@ -567,16 +576,55 @@ async function main() {
|
|
|
567
576
|
handleError(error);
|
|
568
577
|
}
|
|
569
578
|
}
|
|
570
|
-
function runCodex(args) {
|
|
579
|
+
async function runCodex(args) {
|
|
571
580
|
const subcommand = args.shift();
|
|
572
|
-
if (subcommand
|
|
573
|
-
|
|
581
|
+
if (subcommand === "driver") {
|
|
582
|
+
assertNoRemainingArgs(args, "codex driver");
|
|
583
|
+
printJson(buildCodexDriverEnvelope(CLI_VERSION));
|
|
584
|
+
return;
|
|
574
585
|
}
|
|
575
|
-
|
|
576
|
-
|
|
586
|
+
if (subcommand === "invoke") {
|
|
587
|
+
const encoded = args.shift();
|
|
588
|
+
assertNoRemainingArgs(args, "codex invoke");
|
|
589
|
+
if (!encoded || !/^(?:[a-f0-9]{4})+$/i.test(encoded)) {
|
|
590
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "codex invoke requires one UTF-16 hex argv payload.");
|
|
591
|
+
}
|
|
592
|
+
let json = "";
|
|
593
|
+
for (let index = 0; index < encoded.length; index += 4) {
|
|
594
|
+
json += String.fromCharCode(Number.parseInt(encoded.slice(index, index + 4), 16));
|
|
595
|
+
}
|
|
596
|
+
const invocation = JSON.parse(json);
|
|
597
|
+
if (!Array.isArray(invocation)
|
|
598
|
+
|| invocation.length === 0
|
|
599
|
+
|| invocation.some((value) => typeof value !== "string")
|
|
600
|
+
|| !["plan", "task", "subplan"].includes(invocation[0])
|
|
601
|
+
|| invocation.some((value) => value === "--host")) {
|
|
602
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "codex invoke accepts only a structured plan, task, or subplan argv without --host.");
|
|
603
|
+
}
|
|
604
|
+
const originalArgv = process.argv;
|
|
605
|
+
try {
|
|
606
|
+
process.argv = [originalArgv[0], originalArgv[1], ...invocation, "--host", "codex"];
|
|
607
|
+
await main();
|
|
608
|
+
}
|
|
609
|
+
finally {
|
|
610
|
+
process.argv = originalArgv;
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown codex subcommand "${subcommand ?? ""}".`);
|
|
577
615
|
}
|
|
578
|
-
function runSession(args) {
|
|
616
|
+
async function runSession(args, effectiveHost) {
|
|
579
617
|
const subcommand = args.shift();
|
|
618
|
+
if (subcommand === "open") {
|
|
619
|
+
const workdir = args.shift();
|
|
620
|
+
const agentSessionId = args.shift();
|
|
621
|
+
assertNoRemainingArgs(args, "session open");
|
|
622
|
+
if (!workdir || !agentSessionId) {
|
|
623
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "session open requires <dir> <session-id> in that order.");
|
|
624
|
+
}
|
|
625
|
+
await runPersistentSession(workdir, agentSessionId, effectiveHost);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
580
628
|
if (subcommand !== "clean") {
|
|
581
629
|
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown session subcommand "${subcommand ?? ""}".`);
|
|
582
630
|
}
|
|
@@ -597,19 +645,390 @@ function runSession(args) {
|
|
|
597
645
|
removed: deleteSessionWorkflow(ownerSessionKey),
|
|
598
646
|
});
|
|
599
647
|
}
|
|
648
|
+
async function runPersistentSession(workdir, agentSessionId, effectiveHost) {
|
|
649
|
+
const opened = await new ClawClient({
|
|
650
|
+
clientKind: "terminal",
|
|
651
|
+
...(effectiveHost ? { host: effectiveHost } : {}),
|
|
652
|
+
}).open(agentSessionId, path.resolve(workdir));
|
|
653
|
+
writeSessionOutput({
|
|
654
|
+
ok: true,
|
|
655
|
+
command: "session.open",
|
|
656
|
+
...opened.openResult,
|
|
657
|
+
});
|
|
658
|
+
const readline = createInterface({
|
|
659
|
+
input: process.stdin,
|
|
660
|
+
output: process.stdout,
|
|
661
|
+
terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
662
|
+
prompt: "claw> ",
|
|
663
|
+
});
|
|
664
|
+
if (process.stdin.isTTY && process.stdout.isTTY)
|
|
665
|
+
readline.prompt();
|
|
666
|
+
try {
|
|
667
|
+
for await (const line of readline) {
|
|
668
|
+
const trimmed = line.trim();
|
|
669
|
+
if (!trimmed) {
|
|
670
|
+
if (process.stdin.isTTY && process.stdout.isTTY)
|
|
671
|
+
readline.prompt();
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
try {
|
|
675
|
+
const command = parsePersistentSessionCommand(trimmed);
|
|
676
|
+
if (command.kind === "close") {
|
|
677
|
+
await opened.close();
|
|
678
|
+
writeSessionOutput({ ok: true, command: "session.close" });
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (command.kind === "status") {
|
|
682
|
+
writeSessionOutput({ ok: true, command: "session.status", ...await opened.status() });
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
const envelope = await opened.commandEnvelope(command.request);
|
|
686
|
+
writeSessionOutput({
|
|
687
|
+
ok: true,
|
|
688
|
+
command: command.request.operation,
|
|
689
|
+
...envelope,
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch (error) {
|
|
694
|
+
writeSessionOutput({ ok: false, error: serializeSessionError(error) });
|
|
695
|
+
if (error instanceof ClawSessionError && error.code === "SESSION_CONNECTION_LOST")
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (process.stdin.isTTY && process.stdout.isTTY)
|
|
699
|
+
readline.prompt();
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
finally {
|
|
703
|
+
readline.close();
|
|
704
|
+
try {
|
|
705
|
+
await opened.close();
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
// EOF is a soft close; retained state survives a lost daemon connection.
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function parsePersistentSessionCommand(line) {
|
|
713
|
+
if (line.startsWith("{")) {
|
|
714
|
+
const request = JSON.parse(line);
|
|
715
|
+
if (typeof request.operation !== "string") {
|
|
716
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "JSON session commands require operation.");
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
kind: "command",
|
|
720
|
+
request: { operation: request.operation, input: request.input ?? {} },
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
const tokens = tokenizeSessionLine(line);
|
|
724
|
+
const group = tokens.shift();
|
|
725
|
+
const action = tokens.shift();
|
|
726
|
+
if ((group === "session" && action === "close") || group === "exit" || group === "quit") {
|
|
727
|
+
return { kind: "close" };
|
|
728
|
+
}
|
|
729
|
+
if ((group === "session" && action === "status") || group === "status") {
|
|
730
|
+
return { kind: "status" };
|
|
731
|
+
}
|
|
732
|
+
if (group === "plan" && action === "show") {
|
|
733
|
+
const simple = consumeSessionBoolean(tokens, "--simple");
|
|
734
|
+
assertSessionTokensConsumed(tokens, line);
|
|
735
|
+
return {
|
|
736
|
+
kind: "command",
|
|
737
|
+
request: { operation: "plan.show", input: { simple } },
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
if (group === "plan" && action === "leave") {
|
|
741
|
+
assertSessionTokensConsumed(tokens, line);
|
|
742
|
+
return { kind: "command", request: { operation: "plan.leave", input: {} } };
|
|
743
|
+
}
|
|
744
|
+
if (group === "plan" && action === "resume") {
|
|
745
|
+
const planId = tokens.shift();
|
|
746
|
+
assertSessionTokensConsumed(tokens, line);
|
|
747
|
+
return {
|
|
748
|
+
kind: "command",
|
|
749
|
+
request: { operation: "plan.resume", input: planId ? { planId } : {} },
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
if (group === "plan" && action === "create") {
|
|
753
|
+
const title = consumeSessionFlag(tokens, "--title") ?? tokens.shift();
|
|
754
|
+
if (!title)
|
|
755
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "plan create requires a title.");
|
|
756
|
+
const goalText = consumeSessionFlag(tokens, "--goal");
|
|
757
|
+
assertSessionTokensConsumed(tokens, line);
|
|
758
|
+
return {
|
|
759
|
+
kind: "command",
|
|
760
|
+
request: {
|
|
761
|
+
operation: "plan.create",
|
|
762
|
+
input: { title, ...(goalText ? { goalText } : {}) },
|
|
763
|
+
},
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
if (group === "plan" && action === "wait") {
|
|
767
|
+
assertSessionTokensConsumed(tokens, line);
|
|
768
|
+
return { kind: "command", request: { operation: "plan.wait", input: {} } };
|
|
769
|
+
}
|
|
770
|
+
if (group === "plan" && action === "edit") {
|
|
771
|
+
const operations = parseSessionPlanEditOperations(tokens, line);
|
|
772
|
+
return {
|
|
773
|
+
kind: "command",
|
|
774
|
+
request: { operation: "plan.edit", input: { operations } },
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
if (group === "plan" && action === "done") {
|
|
778
|
+
const retrospectiveSummary = consumeSessionFlag(tokens, "--retrospective");
|
|
779
|
+
if (!retrospectiveSummary) {
|
|
780
|
+
throw new ClawError("RETROSPECTIVE_REQUIRED", "plan done requires --retrospective.");
|
|
781
|
+
}
|
|
782
|
+
const keyDecisions = consumeAllSessionFlags(tokens, "--key-decision");
|
|
783
|
+
const whatWorked = consumeAllSessionFlags(tokens, "--what-worked");
|
|
784
|
+
const issues = consumeAllSessionFlags(tokens, "--issue");
|
|
785
|
+
const followUps = consumeAllSessionFlags(tokens, "--follow-up");
|
|
786
|
+
assertSessionTokensConsumed(tokens, line);
|
|
787
|
+
return {
|
|
788
|
+
kind: "command",
|
|
789
|
+
request: {
|
|
790
|
+
operation: "plan.done",
|
|
791
|
+
input: {
|
|
792
|
+
retrospectiveSummary,
|
|
793
|
+
...(keyDecisions.length ? { keyDecisions } : {}),
|
|
794
|
+
...(whatWorked.length ? { whatWorked } : {}),
|
|
795
|
+
...(issues.length ? { issues } : {}),
|
|
796
|
+
...(followUps.length ? { followUps } : {}),
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
if (group === "task" && action === "add") {
|
|
802
|
+
const tasks = [];
|
|
803
|
+
while (tokens.length > 0) {
|
|
804
|
+
if (tokens.shift() !== "--title") {
|
|
805
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add expects --title to start each task.");
|
|
806
|
+
}
|
|
807
|
+
const title = tokens.shift();
|
|
808
|
+
if (!title)
|
|
809
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add requires a title.");
|
|
810
|
+
let detail;
|
|
811
|
+
if (tokens[0] === "--detail") {
|
|
812
|
+
tokens.shift();
|
|
813
|
+
detail = tokens.shift();
|
|
814
|
+
if (!detail)
|
|
815
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add --detail requires a value.");
|
|
816
|
+
}
|
|
817
|
+
tasks.push({ title, ...(detail ? { detail } : {}) });
|
|
818
|
+
}
|
|
819
|
+
if (!tasks.length)
|
|
820
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add requires at least one task.");
|
|
821
|
+
return { kind: "command", request: { operation: "task.add", input: { tasks } } };
|
|
822
|
+
}
|
|
823
|
+
if (group === "task" && action === "edit") {
|
|
824
|
+
const id = Number(consumeSessionFlag(tokens, "--id"));
|
|
825
|
+
if (!Number.isInteger(id))
|
|
826
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task edit requires integer --id.");
|
|
827
|
+
const taskTitle = consumeSessionFlag(tokens, "--title");
|
|
828
|
+
const taskDetail = consumeSessionFlag(tokens, "--detail");
|
|
829
|
+
const taskStatus = consumeSessionFlag(tokens, "--status");
|
|
830
|
+
const taskChoiceId = consumeSessionFlag(tokens, "--choice");
|
|
831
|
+
if (!taskTitle && !taskDetail && !taskStatus && !taskChoiceId) {
|
|
832
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task edit requires a field to change.");
|
|
833
|
+
}
|
|
834
|
+
assertSessionTokensConsumed(tokens, line);
|
|
835
|
+
return {
|
|
836
|
+
kind: "command",
|
|
837
|
+
request: {
|
|
838
|
+
operation: "task.edit",
|
|
839
|
+
input: {
|
|
840
|
+
taskId: id,
|
|
841
|
+
...(taskTitle ? { taskTitle } : {}),
|
|
842
|
+
...(taskDetail ? { taskDetail } : {}),
|
|
843
|
+
...(taskStatus ? { taskStatus } : {}),
|
|
844
|
+
...(taskChoiceId ? { taskChoiceId } : {}),
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
if (group === "task" && action === "done") {
|
|
850
|
+
const tasks = [];
|
|
851
|
+
while (tokens.length > 0) {
|
|
852
|
+
if (tokens.shift() !== "--id") {
|
|
853
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done expects --id to start each task.");
|
|
854
|
+
}
|
|
855
|
+
const id = Number(tokens.shift());
|
|
856
|
+
if (!Number.isInteger(id))
|
|
857
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done requires integer ids.");
|
|
858
|
+
let choiceId;
|
|
859
|
+
if (tokens[0] === "--choice") {
|
|
860
|
+
tokens.shift();
|
|
861
|
+
choiceId = tokens.shift();
|
|
862
|
+
if (!choiceId)
|
|
863
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done --choice requires a value.");
|
|
864
|
+
}
|
|
865
|
+
tasks.push({ id, ...(choiceId ? { choiceId } : {}) });
|
|
866
|
+
}
|
|
867
|
+
if (!tasks.length)
|
|
868
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done requires at least one task.");
|
|
869
|
+
return { kind: "command", request: { operation: "task.done", input: { tasks } } };
|
|
870
|
+
}
|
|
871
|
+
if (group === "search") {
|
|
872
|
+
const query = consumeSessionFlag(tokens, "--query") ?? action;
|
|
873
|
+
if (!query)
|
|
874
|
+
throw new ClawError("MEMORY_QUERY_REQUIRED", "search requires a query.");
|
|
875
|
+
const dir = consumeSessionFlag(tokens, "--dir");
|
|
876
|
+
const limitRaw = consumeSessionFlag(tokens, "--limit");
|
|
877
|
+
assertSessionTokensConsumed(tokens, line);
|
|
878
|
+
return {
|
|
879
|
+
kind: "command",
|
|
880
|
+
request: {
|
|
881
|
+
operation: "search",
|
|
882
|
+
input: {
|
|
883
|
+
query,
|
|
884
|
+
...(dir ? { dir } : {}),
|
|
885
|
+
...(limitRaw ? { limit: Number(limitRaw) } : {}),
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
throw new ClawError("SESSION_OPERATION_UNSUPPORTED", `Unsupported persistent session command: ${line}`);
|
|
891
|
+
}
|
|
892
|
+
function tokenizeSessionLine(line) {
|
|
893
|
+
const tokens = [];
|
|
894
|
+
const pattern = /"((?:\\.|[^"])*)"|'((?:\\.|[^'])*)'|(\S+)/g;
|
|
895
|
+
for (const match of line.matchAll(pattern)) {
|
|
896
|
+
tokens.push((match[1] ?? match[2] ?? match[3] ?? "").replace(/\\(["'\\])/g, "$1"));
|
|
897
|
+
}
|
|
898
|
+
return tokens;
|
|
899
|
+
}
|
|
900
|
+
function consumeSessionFlag(tokens, flag) {
|
|
901
|
+
const index = tokens.indexOf(flag);
|
|
902
|
+
if (index < 0)
|
|
903
|
+
return undefined;
|
|
904
|
+
const value = tokens[index + 1];
|
|
905
|
+
if (!value)
|
|
906
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Missing value for ${flag}.`);
|
|
907
|
+
tokens.splice(index, 2);
|
|
908
|
+
return value;
|
|
909
|
+
}
|
|
910
|
+
function consumeAllSessionFlags(tokens, flag) {
|
|
911
|
+
const values = [];
|
|
912
|
+
while (tokens.includes(flag)) {
|
|
913
|
+
values.push(consumeSessionFlag(tokens, flag));
|
|
914
|
+
}
|
|
915
|
+
return values;
|
|
916
|
+
}
|
|
917
|
+
function parseSessionPlanEditOperations(tokens, line) {
|
|
918
|
+
const operations = [];
|
|
919
|
+
const updateFlags = {
|
|
920
|
+
"--goal": "goalText",
|
|
921
|
+
"--requirements": "requirementsSummary",
|
|
922
|
+
"--summary": "planSummary",
|
|
923
|
+
"--rule": "rules",
|
|
924
|
+
"--key-decision": "keyDecisions",
|
|
925
|
+
};
|
|
926
|
+
while (tokens.length > 0) {
|
|
927
|
+
const flag = tokens.shift();
|
|
928
|
+
const value = tokens.shift();
|
|
929
|
+
if (!value)
|
|
930
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Missing value for ${flag}.`);
|
|
931
|
+
if (flag === "--status") {
|
|
932
|
+
operations.push({ type: "plan.status", status: value });
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
const field = updateFlags[flag];
|
|
936
|
+
if (!field) {
|
|
937
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown plan edit argument in "${line}": ${flag}`);
|
|
938
|
+
}
|
|
939
|
+
const listField = field === "rules" || field === "keyDecisions";
|
|
940
|
+
operations.push({
|
|
941
|
+
type: "plan.update",
|
|
942
|
+
updates: { [field]: listField ? [value] : value },
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
if (!operations.length)
|
|
946
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "plan edit requires a field to change.");
|
|
947
|
+
return operations;
|
|
948
|
+
}
|
|
949
|
+
function consumeSessionBoolean(tokens, flag) {
|
|
950
|
+
const index = tokens.indexOf(flag);
|
|
951
|
+
if (index < 0)
|
|
952
|
+
return false;
|
|
953
|
+
tokens.splice(index, 1);
|
|
954
|
+
return true;
|
|
955
|
+
}
|
|
956
|
+
function assertSessionTokensConsumed(tokens, line) {
|
|
957
|
+
if (tokens.length > 0) {
|
|
958
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown session command arguments in "${line}": ${tokens.join(" ")}`);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
function writeSessionOutput(value) {
|
|
962
|
+
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
963
|
+
}
|
|
964
|
+
function serializeSessionError(error) {
|
|
965
|
+
if (error instanceof ClawSessionError) {
|
|
966
|
+
return {
|
|
967
|
+
code: error.code,
|
|
968
|
+
message: error.message,
|
|
969
|
+
retryable: error.retryable,
|
|
970
|
+
outcome: error.outcome,
|
|
971
|
+
...(error.recoveryCommand ? { recoveryCommand: error.recoveryCommand } : {}),
|
|
972
|
+
...(error.details ? { details: error.details } : {}),
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
if (error instanceof ClawError) {
|
|
976
|
+
return { code: error.code, message: error.message, ...(error.details ? { details: error.details } : {}) };
|
|
977
|
+
}
|
|
978
|
+
return { code: "SESSION_COMMAND_FAILED", message: error instanceof Error ? error.message : String(error) };
|
|
979
|
+
}
|
|
600
980
|
function runKnowledge(args) {
|
|
601
981
|
const subcommand = args.shift();
|
|
602
982
|
switch (subcommand) {
|
|
983
|
+
case "list": {
|
|
984
|
+
const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
|
|
985
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
986
|
+
const host = readOptionalFlag(args, "--job-host");
|
|
987
|
+
assertNoRemainingArgs(args, "knowledge list");
|
|
988
|
+
const sessionProject = sessionKey ? resolveSessionWorkflowContext(sessionKey) : null;
|
|
989
|
+
const project = resolveProjectContext(projectRoot);
|
|
990
|
+
const candidates = sessionProject && sessionProject.clawDir !== project.clawDir
|
|
991
|
+
? [sessionProject, project]
|
|
992
|
+
: [sessionProject ?? project];
|
|
993
|
+
const jobs = candidates.flatMap((candidate) => listKnowledgeFinalizationJobs(candidate))
|
|
994
|
+
.map((jobPath) => ({ jobPath, job: readKnowledgeFinalizationJob(jobPath) }))
|
|
995
|
+
.filter(({ job }) => (job.status === "running"
|
|
996
|
+
|| ((job.status === "queued" || job.status === "failed") && job.attempts < 3)) && (!host || job.host === host))
|
|
997
|
+
.map(({ jobPath, job }) => ({
|
|
998
|
+
jobPath,
|
|
999
|
+
finalizeId: job.finalizeId,
|
|
1000
|
+
status: job.status,
|
|
1001
|
+
attempts: job.attempts,
|
|
1002
|
+
}));
|
|
1003
|
+
printJson({ ok: true, command: "knowledge.list", jobs });
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
603
1006
|
case "wait": {
|
|
604
1007
|
const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
|
|
605
1008
|
const finalizeId = readRequiredFlag(args, "--finalize-id");
|
|
1009
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
606
1010
|
const timeoutMs = readOptionalNumber(args, "--timeout-ms") ?? 300_000;
|
|
607
1011
|
assertNoRemainingArgs(args, "knowledge wait");
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
1012
|
+
const sessionProject = sessionKey ? resolveSessionWorkflowContext(sessionKey) : null;
|
|
1013
|
+
const project = resolveProjectContext(projectRoot);
|
|
1014
|
+
const candidates = sessionProject && sessionProject.clawDir !== project.clawDir
|
|
1015
|
+
? [sessionProject, project]
|
|
1016
|
+
: [sessionProject ?? project];
|
|
1017
|
+
let located = candidates
|
|
1018
|
+
.map((candidate) => findKnowledgeFinalizationJobPath(candidate, finalizeId))
|
|
1019
|
+
.find((candidate) => Boolean(candidate));
|
|
1020
|
+
if (!located && timeoutMs > 0) {
|
|
1021
|
+
located = waitForKnowledgeFinalizationJobReady({
|
|
1022
|
+
project: candidates[0],
|
|
1023
|
+
finalizeId,
|
|
1024
|
+
timeoutMs,
|
|
1025
|
+
}).jobPath;
|
|
1026
|
+
}
|
|
1027
|
+
if (!located) {
|
|
1028
|
+
throw new Error(`Knowledge finalization ${finalizeId} is unavailable.`);
|
|
1029
|
+
}
|
|
1030
|
+
const jobPath = located;
|
|
1031
|
+
const job = readKnowledgeFinalizationJob(jobPath);
|
|
613
1032
|
printJson({
|
|
614
1033
|
ok: true,
|
|
615
1034
|
command: "knowledge.wait",
|
|
@@ -715,11 +1134,11 @@ async function runPlan(args, effectiveHost) {
|
|
|
715
1134
|
throw new ClawError("PROJECT_CONFIG_INVALID", "plan edit requires at least one plan field or --status.");
|
|
716
1135
|
}
|
|
717
1136
|
const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
|
|
718
|
-
const
|
|
719
|
-
const current =
|
|
1137
|
+
const entersEndTerminal = requestsPlanEndTerminal(operations);
|
|
1138
|
+
const current = entersEndTerminal
|
|
720
1139
|
? showPlan({ cwd: process.cwd(), ...target, ownerSessionKey })
|
|
721
1140
|
: undefined;
|
|
722
|
-
const project =
|
|
1141
|
+
const project = entersEndTerminal ? tryResolveHookProject(process.cwd()) : null;
|
|
723
1142
|
const effectiveWriter = current && project
|
|
724
1143
|
? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
|
|
725
1144
|
: undefined;
|
|
@@ -729,7 +1148,7 @@ async function runPlan(args, effectiveHost) {
|
|
|
729
1148
|
&& effectiveHost !== "codex") {
|
|
730
1149
|
throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex host.', { host: effectiveHost ?? null });
|
|
731
1150
|
}
|
|
732
|
-
const queuePlanEndFinalization =
|
|
1151
|
+
const queuePlanEndFinalization = entersEndTerminal
|
|
733
1152
|
? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
|
|
734
1153
|
: undefined;
|
|
735
1154
|
const result = await editPlan({
|
|
@@ -857,6 +1276,7 @@ async function runPlan(args, effectiveHost) {
|
|
|
857
1276
|
return;
|
|
858
1277
|
}
|
|
859
1278
|
case "show": {
|
|
1279
|
+
const simple = readBooleanFlag(args, "--simple");
|
|
860
1280
|
const target = readPlanMutationTarget(args);
|
|
861
1281
|
assertNoRemainingArgs(args, "plan show");
|
|
862
1282
|
const result = showPlan({
|
|
@@ -864,6 +1284,10 @@ async function runPlan(args, effectiveHost) {
|
|
|
864
1284
|
...target,
|
|
865
1285
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
866
1286
|
});
|
|
1287
|
+
if (simple) {
|
|
1288
|
+
printJson(result.simplePlanView);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
867
1291
|
printJson({
|
|
868
1292
|
ok: true,
|
|
869
1293
|
command: "plan.show",
|
|
@@ -1052,11 +1476,12 @@ async function runSearch(args) {
|
|
|
1052
1476
|
if (args.includes("--scope") || args.includes("--task")) {
|
|
1053
1477
|
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
1478
|
}
|
|
1479
|
+
const dir = readOptionalFlag(args, "--dir");
|
|
1055
1480
|
printJson({
|
|
1056
1481
|
ok: true,
|
|
1057
1482
|
command: "search",
|
|
1058
1483
|
...await searchMemoryAsync({
|
|
1059
|
-
cwd: process.cwd(),
|
|
1484
|
+
cwd: dir ? path.resolve(process.cwd(), dir) : process.cwd(),
|
|
1060
1485
|
limit: readOptionalNumber(args, "--limit"),
|
|
1061
1486
|
query: readRequiredSearchQuery(args),
|
|
1062
1487
|
scope: "project",
|
|
@@ -1269,7 +1694,7 @@ function buildPublicContextOutput(context) {
|
|
|
1269
1694
|
if (Object.keys(compactRecovery).length > 0) {
|
|
1270
1695
|
output.startupRecovery = compactRecovery;
|
|
1271
1696
|
}
|
|
1272
|
-
const searchGuidance = buildContextSearchGuidance(context);
|
|
1697
|
+
const searchGuidance = buildContextSearchGuidance(context, "rg");
|
|
1273
1698
|
if (searchGuidance) {
|
|
1274
1699
|
output.searchGuidance = searchGuidance;
|
|
1275
1700
|
}
|
|
@@ -1281,20 +1706,26 @@ function shouldExposeVersionSync(versionSync) {
|
|
|
1281
1706
|
|| versionSync.updateAvailable === true
|
|
1282
1707
|
|| versionSync.projectVersion !== versionSync.cliVersion;
|
|
1283
1708
|
}
|
|
1284
|
-
function buildContextSearchGuidance(context) {
|
|
1709
|
+
function buildContextSearchGuidance(context, style = "default") {
|
|
1285
1710
|
const project = asJsonRecord(context.project);
|
|
1286
1711
|
const projectConfig = asJsonRecord(project?.projectConfig);
|
|
1287
1712
|
const memory = asJsonRecord(projectConfig?.memory);
|
|
1288
1713
|
const embeddingEnabled = memory?.enabled === true && asJsonRecord(memory.embedding) !== null;
|
|
1289
1714
|
const gitnexusEnabled = projectConfig?.gitnexus === true;
|
|
1290
1715
|
if (embeddingEnabled && gitnexusEnabled) {
|
|
1291
|
-
return
|
|
1716
|
+
return style === "rg"
|
|
1717
|
+
? "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."
|
|
1718
|
+
: "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
1719
|
}
|
|
1293
1720
|
if (embeddingEnabled) {
|
|
1294
|
-
return
|
|
1721
|
+
return style === "rg"
|
|
1722
|
+
? "Before using `rg`, use `claw search --query` to narrow the document search scope, then use `rg` to locate exact files or symbols."
|
|
1723
|
+
: "When useful, use `claw search` to narrow the document search scope, then use the default search to locate exact files or symbols.";
|
|
1295
1724
|
}
|
|
1296
1725
|
if (gitnexusEnabled) {
|
|
1297
|
-
return
|
|
1726
|
+
return style === "rg"
|
|
1727
|
+
? "Before using `rg`, use GitNexus to narrow the code search scope, then use `rg` to locate exact files or symbols."
|
|
1728
|
+
: "When useful, use GitNexus to narrow the code search scope, then use the default search to locate exact files or symbols.";
|
|
1298
1729
|
}
|
|
1299
1730
|
return null;
|
|
1300
1731
|
}
|
|
@@ -1505,12 +1936,12 @@ async function runInternalKnowledgeCapture(args, effectiveHost) {
|
|
|
1505
1936
|
const turnId = readHookString(payload, "turn_id");
|
|
1506
1937
|
const message = readHookString(payload, "message");
|
|
1507
1938
|
const taskConclusions = readHookTaskConclusions(payload, turnId);
|
|
1508
|
-
if (!hookCwd || !sessionId || !turnId || !message
|
|
1939
|
+
if (!hookCwd || !sessionId || !turnId || !message) {
|
|
1509
1940
|
printJson({ ok: true, captured: false });
|
|
1510
1941
|
return;
|
|
1511
1942
|
}
|
|
1512
1943
|
try {
|
|
1513
|
-
const project =
|
|
1944
|
+
const project = resolveWorkflowProjectContext(hookCwd, sessionId);
|
|
1514
1945
|
const result = tryCaptureKnowledgeStop({
|
|
1515
1946
|
project,
|
|
1516
1947
|
sessionId,
|
|
@@ -1592,7 +2023,7 @@ function completeKnowledgeFinalizationJob(jobPath, result, claimToken) {
|
|
|
1592
2023
|
if (running.claimToken !== claimToken) {
|
|
1593
2024
|
throw new Error("Knowledge finalization completion does not match the active claim.");
|
|
1594
2025
|
}
|
|
1595
|
-
const project =
|
|
2026
|
+
const project = resolveKnowledgeJobProject(jobPath, running);
|
|
1596
2027
|
const finishedAt = new Date().toISOString();
|
|
1597
2028
|
const truthEncoding = normalizeTruthMarkdownEncoding(project);
|
|
1598
2029
|
recordKnowledgeFinalizationResult(project, running.reportPath, {
|
|
@@ -1626,6 +2057,17 @@ function completeKnowledgeFinalizationJob(jobPath, result, claimToken) {
|
|
|
1626
2057
|
});
|
|
1627
2058
|
printJson({ ok: true, completed: true, alreadyDone: terminal.alreadyDone, finalizeId: running.finalizeId });
|
|
1628
2059
|
}
|
|
2060
|
+
function resolveKnowledgeJobProject(jobPath, job) {
|
|
2061
|
+
const project = resolveProjectContext(job.projectRoot);
|
|
2062
|
+
const sessionProject = resolveSessionWorkflowContext(job.sessionId);
|
|
2063
|
+
for (const candidate of sessionProject ? [sessionProject, project] : [project]) {
|
|
2064
|
+
const relative = path.relative(candidate.clawDir, path.resolve(jobPath));
|
|
2065
|
+
if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
2066
|
+
return candidate;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
throw new Error("Knowledge finalization job is outside its project or session workflow.");
|
|
2070
|
+
}
|
|
1629
2071
|
function failKnowledgeFinalizationJob(jobPath, message, claimToken) {
|
|
1630
2072
|
const terminal = doneKnowledgeFinalizationJob({
|
|
1631
2073
|
jobPath,
|
|
@@ -1677,7 +2119,7 @@ function runInternalKnowledgeSweep(args) {
|
|
|
1677
2119
|
printJson({ command: "internal-knowledge-sweep", ok: true, launched: 0, skipped: true });
|
|
1678
2120
|
return;
|
|
1679
2121
|
}
|
|
1680
|
-
const jobs = listRetryableKnowledgeFinalizationJobs(project);
|
|
2122
|
+
const jobs = listRetryableKnowledgeFinalizationJobs(project, { excludeHosts: ["cindy"] });
|
|
1681
2123
|
let launched = 0;
|
|
1682
2124
|
for (const jobPath of jobs) {
|
|
1683
2125
|
try {
|
|
@@ -1714,7 +2156,7 @@ async function runInternalBackgroundMaintenance(args) {
|
|
|
1714
2156
|
let launched = 0;
|
|
1715
2157
|
const knowledgeProject = project ?? sessionProject;
|
|
1716
2158
|
if (knowledgeProject) {
|
|
1717
|
-
const jobs = listRetryableKnowledgeFinalizationJobs(knowledgeProject);
|
|
2159
|
+
const jobs = listRetryableKnowledgeFinalizationJobs(knowledgeProject, { excludeHosts: ["cindy"] });
|
|
1718
2160
|
discovered = jobs.length;
|
|
1719
2161
|
for (const jobPath of jobs) {
|
|
1720
2162
|
try {
|
|
@@ -1914,7 +2356,7 @@ async function runSessionStartHook(effectiveHost) {
|
|
|
1914
2356
|
const context = await runContextCommand([], hookCwd, ownerSessionKey, effectiveHost);
|
|
1915
2357
|
const contextProject = asJsonRecord(context.project);
|
|
1916
2358
|
const retryableJobs = effectiveHost !== "cindy" && contextProject?.scope !== "session" && !context.error
|
|
1917
|
-
? listRetryableKnowledgeFinalizationJobs(resolveProjectContext(hookCwd))
|
|
2359
|
+
? listRetryableKnowledgeFinalizationJobs(resolveProjectContext(hookCwd), { excludeHosts: ["cindy"] })
|
|
1918
2360
|
: [];
|
|
1919
2361
|
if (contextProject?.scope !== "session"
|
|
1920
2362
|
&& !context.error
|
|
@@ -2071,31 +2513,21 @@ function buildSessionStartAdditionalContext(context, sessionCwd, effectiveHost)
|
|
|
2071
2513
|
* supplies project identity or a recovered workflow snapshot. Goal Mode is
|
|
2072
2514
|
* intentionally omitted because Cindy does not expose that Host surface.
|
|
2073
2515
|
*/
|
|
2074
|
-
function buildCindySessionStartContext(context,
|
|
2075
|
-
const
|
|
2076
|
-
|
|
2077
|
-
|
|
2516
|
+
function buildCindySessionStartContext(context, _sessionCwd, versionSyncPrompt) {
|
|
2517
|
+
const lines = [];
|
|
2518
|
+
const startupRecovery = asJsonRecord(context.startupRecovery);
|
|
2519
|
+
const fixedPaths = Array.isArray(startupRecovery?.fixedPaths)
|
|
2520
|
+
? startupRecovery.fixedPaths.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
|
2521
|
+
: [];
|
|
2522
|
+
if (startupRecovery?.corrected === true) {
|
|
2523
|
+
lines.push(`claw-kit repaired the project configuration${fixedPaths.length > 0 ? `: ${fixedPaths.join(", ")}` : "."}`);
|
|
2078
2524
|
}
|
|
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")}`;
|
|
2525
|
+
if (versionSyncPrompt?.placement === "prefix")
|
|
2526
|
+
lines.push(...versionSyncPrompt.lines);
|
|
2527
|
+
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.");
|
|
2528
|
+
if (versionSyncPrompt?.placement === "suffix")
|
|
2529
|
+
lines.push(...versionSyncPrompt.lines);
|
|
2530
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
2099
2531
|
}
|
|
2100
2532
|
function stripCindyGoalModeLines(prompt) {
|
|
2101
2533
|
return prompt
|
|
@@ -2386,7 +2818,7 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
|
|
|
2386
2818
|
const resolvedPlanPath = archivedPlanPath ?? result.planPath;
|
|
2387
2819
|
const codexResult = effectiveHost === "codex";
|
|
2388
2820
|
const cindyResult = effectiveHost === "cindy";
|
|
2389
|
-
const hostActions = codexResult ?
|
|
2821
|
+
const hostActions = codexResult ? buildCodexHostActions(result, { forceProjectionSync, actionIdPrefix: command === "plan.sync" ? `plan.sync:${createHash("sha256").update(result.planPath).digest("hex").slice(0, 16)}` : undefined }) : [];
|
|
2390
2822
|
const nextsteps = codexResult
|
|
2391
2823
|
&& result.planStatus === "end.completed"
|
|
2392
2824
|
&& result.workflowGuidance.goalTool?.tool === "update_goal"
|
|
@@ -2461,87 +2893,6 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
|
|
|
2461
2893
|
...(!codexResult || !includePlan ? { planSummary } : {}),
|
|
2462
2894
|
};
|
|
2463
2895
|
}
|
|
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
2896
|
function compactDirectCommandResult(command, workflowGuidance, completionRefresh) {
|
|
2546
2897
|
return {
|
|
2547
2898
|
ok: true,
|
|
@@ -2629,7 +2980,7 @@ function readOrderedPlanEditOperations(args) {
|
|
|
2629
2980
|
}
|
|
2630
2981
|
return operations;
|
|
2631
2982
|
}
|
|
2632
|
-
function
|
|
2983
|
+
function requestsPlanEndTerminal(operations) {
|
|
2633
2984
|
return operations.some((operation) => operation.type === "plan.status" && operation.status.startsWith("end."));
|
|
2634
2985
|
}
|
|
2635
2986
|
function planCompletionOperations(updates) {
|