@veewo/claw 0.2.2 → 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 +127 -83
- package/dist/cli.js +671 -158
- 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 +45 -41
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: {
|
|
@@ -394,6 +403,28 @@ const COMMAND_HELP = {
|
|
|
394
403
|
description: "Internal: warms the configured local persistent embedding session after context recovery without delaying SessionStart.",
|
|
395
404
|
options: [{ flag: "--cwd <dir>", detail: "(required) Project root." }],
|
|
396
405
|
},
|
|
406
|
+
"internal-daily-maintenance": {
|
|
407
|
+
usage: ["{script} internal-daily-maintenance --cwd <dir> [--session-key <key>] [--session-only]"],
|
|
408
|
+
description: "Internal: runs the lock-protected daily cleanup asynchronously after context recovery.",
|
|
409
|
+
options: [
|
|
410
|
+
{ flag: "--cwd <dir>", detail: "(required) Current workspace directory." },
|
|
411
|
+
{ flag: "--session-key <key>", detail: "Optional owner session key used for session workflow maintenance." },
|
|
412
|
+
{ flag: "--session-only", detail: "Only maintain the session workflow; do not touch project tasks." },
|
|
413
|
+
],
|
|
414
|
+
},
|
|
415
|
+
"internal-knowledge-sweep": {
|
|
416
|
+
usage: ["{script} internal-knowledge-sweep --cwd <dir>"],
|
|
417
|
+
description: "Internal: discovers retryable knowledge jobs and launches their detached finalizers.",
|
|
418
|
+
options: [{ flag: "--cwd <dir>", detail: "(required) Project root." }],
|
|
419
|
+
},
|
|
420
|
+
"internal-background-maintenance": {
|
|
421
|
+
usage: ["{script} internal-background-maintenance --cwd <dir> [--session-key <key>]"],
|
|
422
|
+
description: "Internal: runs non-blocking cleanup, embedding warmup, and knowledge job discovery in one worker.",
|
|
423
|
+
options: [
|
|
424
|
+
{ flag: "--cwd <dir>", detail: "(required) Current workspace directory." },
|
|
425
|
+
{ flag: "--session-key <key>", detail: "Optional owner session key used for session workflow maintenance." },
|
|
426
|
+
],
|
|
427
|
+
},
|
|
397
428
|
};
|
|
398
429
|
async function main() {
|
|
399
430
|
const args = process.argv.slice(2);
|
|
@@ -449,7 +480,7 @@ async function main() {
|
|
|
449
480
|
printJson(buildPublicContextOutput(await runContextCommand(args, process.cwd(), resolveOwnerSessionKey(), effectiveHost)));
|
|
450
481
|
return;
|
|
451
482
|
case "session":
|
|
452
|
-
runSession(args);
|
|
483
|
+
await runSession(args, effectiveHost);
|
|
453
484
|
return;
|
|
454
485
|
case "check":
|
|
455
486
|
const checkResult = ensureProjectProtocol(process.cwd());
|
|
@@ -467,7 +498,7 @@ async function main() {
|
|
|
467
498
|
await runPlan(args, effectiveHost);
|
|
468
499
|
return;
|
|
469
500
|
case "codex":
|
|
470
|
-
runCodex(args);
|
|
501
|
+
await runCodex(args);
|
|
471
502
|
return;
|
|
472
503
|
case "template":
|
|
473
504
|
await runTemplate(args);
|
|
@@ -527,6 +558,15 @@ async function main() {
|
|
|
527
558
|
case "internal-embedding-warmup":
|
|
528
559
|
await runInternalEmbeddingWarmup(args);
|
|
529
560
|
return;
|
|
561
|
+
case "internal-daily-maintenance":
|
|
562
|
+
runInternalDailyMaintenance(args);
|
|
563
|
+
return;
|
|
564
|
+
case "internal-knowledge-sweep":
|
|
565
|
+
runInternalKnowledgeSweep(args);
|
|
566
|
+
return;
|
|
567
|
+
case "internal-background-maintenance":
|
|
568
|
+
await runInternalBackgroundMaintenance(args);
|
|
569
|
+
return;
|
|
530
570
|
default:
|
|
531
571
|
printTopLevelUsage();
|
|
532
572
|
process.exitCode = 1;
|
|
@@ -536,16 +576,55 @@ async function main() {
|
|
|
536
576
|
handleError(error);
|
|
537
577
|
}
|
|
538
578
|
}
|
|
539
|
-
function runCodex(args) {
|
|
579
|
+
async function runCodex(args) {
|
|
540
580
|
const subcommand = args.shift();
|
|
541
|
-
if (subcommand
|
|
542
|
-
|
|
581
|
+
if (subcommand === "driver") {
|
|
582
|
+
assertNoRemainingArgs(args, "codex driver");
|
|
583
|
+
printJson(buildCodexDriverEnvelope(CLI_VERSION));
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
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;
|
|
543
613
|
}
|
|
544
|
-
|
|
545
|
-
printJson(buildCodexDriverEnvelope(CLI_VERSION));
|
|
614
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown codex subcommand "${subcommand ?? ""}".`);
|
|
546
615
|
}
|
|
547
|
-
function runSession(args) {
|
|
616
|
+
async function runSession(args, effectiveHost) {
|
|
548
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
|
+
}
|
|
549
628
|
if (subcommand !== "clean") {
|
|
550
629
|
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown session subcommand "${subcommand ?? ""}".`);
|
|
551
630
|
}
|
|
@@ -566,19 +645,390 @@ function runSession(args) {
|
|
|
566
645
|
removed: deleteSessionWorkflow(ownerSessionKey),
|
|
567
646
|
});
|
|
568
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
|
+
}
|
|
569
980
|
function runKnowledge(args) {
|
|
570
981
|
const subcommand = args.shift();
|
|
571
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
|
+
}
|
|
572
1006
|
case "wait": {
|
|
573
1007
|
const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
|
|
574
1008
|
const finalizeId = readRequiredFlag(args, "--finalize-id");
|
|
1009
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
575
1010
|
const timeoutMs = readOptionalNumber(args, "--timeout-ms") ?? 300_000;
|
|
576
1011
|
assertNoRemainingArgs(args, "knowledge wait");
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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);
|
|
582
1032
|
printJson({
|
|
583
1033
|
ok: true,
|
|
584
1034
|
command: "knowledge.wait",
|
|
@@ -684,11 +1134,11 @@ async function runPlan(args, effectiveHost) {
|
|
|
684
1134
|
throw new ClawError("PROJECT_CONFIG_INVALID", "plan edit requires at least one plan field or --status.");
|
|
685
1135
|
}
|
|
686
1136
|
const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
|
|
687
|
-
const
|
|
688
|
-
const current =
|
|
1137
|
+
const entersEndTerminal = requestsPlanEndTerminal(operations);
|
|
1138
|
+
const current = entersEndTerminal
|
|
689
1139
|
? showPlan({ cwd: process.cwd(), ...target, ownerSessionKey })
|
|
690
1140
|
: undefined;
|
|
691
|
-
const project =
|
|
1141
|
+
const project = entersEndTerminal ? tryResolveHookProject(process.cwd()) : null;
|
|
692
1142
|
const effectiveWriter = current && project
|
|
693
1143
|
? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
|
|
694
1144
|
: undefined;
|
|
@@ -698,7 +1148,7 @@ async function runPlan(args, effectiveHost) {
|
|
|
698
1148
|
&& effectiveHost !== "codex") {
|
|
699
1149
|
throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex host.', { host: effectiveHost ?? null });
|
|
700
1150
|
}
|
|
701
|
-
const queuePlanEndFinalization =
|
|
1151
|
+
const queuePlanEndFinalization = entersEndTerminal
|
|
702
1152
|
? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
|
|
703
1153
|
: undefined;
|
|
704
1154
|
const result = await editPlan({
|
|
@@ -717,8 +1167,6 @@ async function runPlan(args, effectiveHost) {
|
|
|
717
1167
|
&& effectiveWriter?.executionPolicy === "subagent"
|
|
718
1168
|
&& result.knowledgeFinalizeId)
|
|
719
1169
|
? buildKnowledgeDispatch({
|
|
720
|
-
projectRoot: project.projectRoot,
|
|
721
|
-
taskName: result.taskName,
|
|
722
1170
|
finalizeId: result.knowledgeFinalizeId,
|
|
723
1171
|
writer: effectiveWriter,
|
|
724
1172
|
})
|
|
@@ -820,8 +1268,6 @@ async function runPlan(args, effectiveHost) {
|
|
|
820
1268
|
&& effectiveWriter?.executionPolicy === "subagent"
|
|
821
1269
|
&& result.knowledgeFinalizeId)
|
|
822
1270
|
? buildKnowledgeDispatch({
|
|
823
|
-
projectRoot: project.projectRoot,
|
|
824
|
-
taskName: result.taskName,
|
|
825
1271
|
finalizeId: result.knowledgeFinalizeId,
|
|
826
1272
|
writer: effectiveWriter,
|
|
827
1273
|
})
|
|
@@ -830,6 +1276,7 @@ async function runPlan(args, effectiveHost) {
|
|
|
830
1276
|
return;
|
|
831
1277
|
}
|
|
832
1278
|
case "show": {
|
|
1279
|
+
const simple = readBooleanFlag(args, "--simple");
|
|
833
1280
|
const target = readPlanMutationTarget(args);
|
|
834
1281
|
assertNoRemainingArgs(args, "plan show");
|
|
835
1282
|
const result = showPlan({
|
|
@@ -837,6 +1284,10 @@ async function runPlan(args, effectiveHost) {
|
|
|
837
1284
|
...target,
|
|
838
1285
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
839
1286
|
});
|
|
1287
|
+
if (simple) {
|
|
1288
|
+
printJson(result.simplePlanView);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
840
1291
|
printJson({
|
|
841
1292
|
ok: true,
|
|
842
1293
|
command: "plan.show",
|
|
@@ -1025,11 +1476,12 @@ async function runSearch(args) {
|
|
|
1025
1476
|
if (args.includes("--scope") || args.includes("--task")) {
|
|
1026
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.");
|
|
1027
1478
|
}
|
|
1479
|
+
const dir = readOptionalFlag(args, "--dir");
|
|
1028
1480
|
printJson({
|
|
1029
1481
|
ok: true,
|
|
1030
1482
|
command: "search",
|
|
1031
1483
|
...await searchMemoryAsync({
|
|
1032
|
-
cwd: process.cwd(),
|
|
1484
|
+
cwd: dir ? path.resolve(process.cwd(), dir) : process.cwd(),
|
|
1033
1485
|
limit: readOptionalNumber(args, "--limit"),
|
|
1034
1486
|
query: readRequiredSearchQuery(args),
|
|
1035
1487
|
scope: "project",
|
|
@@ -1057,7 +1509,15 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
1057
1509
|
let fixedPaths = [];
|
|
1058
1510
|
const sessionProject = resolveSessionWorkflowContext(ownerSessionKey ?? undefined);
|
|
1059
1511
|
if (sessionProject) {
|
|
1060
|
-
const maintenance =
|
|
1512
|
+
const maintenance = effectiveHost
|
|
1513
|
+
? null
|
|
1514
|
+
: runDailyMaintenance(sessionProject, {
|
|
1515
|
+
excludeSessionKey: ownerSessionKey ?? undefined,
|
|
1516
|
+
includeProject: false,
|
|
1517
|
+
});
|
|
1518
|
+
if (effectiveHost) {
|
|
1519
|
+
launchDailyMaintenance(cwd, ownerSessionKey, true);
|
|
1520
|
+
}
|
|
1061
1521
|
const activeWorkflow = !taskName && ownerSessionKey
|
|
1062
1522
|
? await tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey, effectiveHost)
|
|
1063
1523
|
: null;
|
|
@@ -1068,7 +1528,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
1068
1528
|
return {
|
|
1069
1529
|
project: sessionProject,
|
|
1070
1530
|
...(activeWorkflow ? { activeWorkflow } : {}),
|
|
1071
|
-
...(maintenance
|
|
1531
|
+
...(maintenance?.ran ? { maintenance } : {}),
|
|
1072
1532
|
...(codexRuntimeError ? { error: codexRuntimeError } : {}),
|
|
1073
1533
|
};
|
|
1074
1534
|
}
|
|
@@ -1084,7 +1544,13 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
1084
1544
|
initProject({ cwd, version: CLI_VERSION });
|
|
1085
1545
|
initialized = true;
|
|
1086
1546
|
}
|
|
1087
|
-
const
|
|
1547
|
+
const project = resolveProjectContext(cwd);
|
|
1548
|
+
const maintenance = effectiveHost
|
|
1549
|
+
? null
|
|
1550
|
+
: runDailyMaintenance(project, { excludeSessionKey: ownerSessionKey ?? undefined });
|
|
1551
|
+
if (effectiveHost) {
|
|
1552
|
+
launchDailyMaintenance(cwd, ownerSessionKey, false);
|
|
1553
|
+
}
|
|
1088
1554
|
let resolved = resolveContext(cwd, taskName);
|
|
1089
1555
|
const versionSync = syncProjectVersionWithCli(cwd, resolved.project);
|
|
1090
1556
|
if (versionSync.projectVersionUpdated) {
|
|
@@ -1104,7 +1570,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
1104
1570
|
launchProjectEmbeddingWarmup(resolved.project);
|
|
1105
1571
|
return {
|
|
1106
1572
|
...resolved,
|
|
1107
|
-
...(maintenance
|
|
1573
|
+
...(maintenance?.ran ? { maintenance } : {}),
|
|
1108
1574
|
...(activeWorkflow ? { activeWorkflow } : {}),
|
|
1109
1575
|
...(codexRuntimeError ? { error: codexRuntimeError } : {}),
|
|
1110
1576
|
protocolCheck: checkProjectProtocol(cwd),
|
|
@@ -1152,6 +1618,28 @@ function launchProjectEmbeddingWarmup(project) {
|
|
|
1152
1618
|
// Context recovery is authoritative; embedding warmup is fail-open latency work.
|
|
1153
1619
|
}
|
|
1154
1620
|
}
|
|
1621
|
+
function launchDailyMaintenance(cwd, ownerSessionKey, sessionOnly) {
|
|
1622
|
+
const args = [resolveCliEntryPath(), "internal-daily-maintenance", "--cwd", cwd];
|
|
1623
|
+
if (ownerSessionKey) {
|
|
1624
|
+
args.push("--session-key", ownerSessionKey);
|
|
1625
|
+
}
|
|
1626
|
+
if (sessionOnly) {
|
|
1627
|
+
args.push("--session-only");
|
|
1628
|
+
}
|
|
1629
|
+
try {
|
|
1630
|
+
const child = spawn(process.execPath, args, {
|
|
1631
|
+
cwd,
|
|
1632
|
+
detached: true,
|
|
1633
|
+
stdio: "ignore",
|
|
1634
|
+
windowsHide: true,
|
|
1635
|
+
env: withoutInvocationHost(),
|
|
1636
|
+
});
|
|
1637
|
+
child.unref();
|
|
1638
|
+
}
|
|
1639
|
+
catch {
|
|
1640
|
+
// Context recovery is authoritative; daily cleanup is fail-open latency work.
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1155
1643
|
function buildPublicContextOutput(context) {
|
|
1156
1644
|
const project = asJsonRecord(context.project);
|
|
1157
1645
|
const output = {};
|
|
@@ -1206,7 +1694,7 @@ function buildPublicContextOutput(context) {
|
|
|
1206
1694
|
if (Object.keys(compactRecovery).length > 0) {
|
|
1207
1695
|
output.startupRecovery = compactRecovery;
|
|
1208
1696
|
}
|
|
1209
|
-
const searchGuidance = buildContextSearchGuidance(context);
|
|
1697
|
+
const searchGuidance = buildContextSearchGuidance(context, "rg");
|
|
1210
1698
|
if (searchGuidance) {
|
|
1211
1699
|
output.searchGuidance = searchGuidance;
|
|
1212
1700
|
}
|
|
@@ -1218,20 +1706,26 @@ function shouldExposeVersionSync(versionSync) {
|
|
|
1218
1706
|
|| versionSync.updateAvailable === true
|
|
1219
1707
|
|| versionSync.projectVersion !== versionSync.cliVersion;
|
|
1220
1708
|
}
|
|
1221
|
-
function buildContextSearchGuidance(context) {
|
|
1709
|
+
function buildContextSearchGuidance(context, style = "default") {
|
|
1222
1710
|
const project = asJsonRecord(context.project);
|
|
1223
1711
|
const projectConfig = asJsonRecord(project?.projectConfig);
|
|
1224
1712
|
const memory = asJsonRecord(projectConfig?.memory);
|
|
1225
1713
|
const embeddingEnabled = memory?.enabled === true && asJsonRecord(memory.embedding) !== null;
|
|
1226
1714
|
const gitnexusEnabled = projectConfig?.gitnexus === true;
|
|
1227
1715
|
if (embeddingEnabled && gitnexusEnabled) {
|
|
1228
|
-
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.";
|
|
1229
1719
|
}
|
|
1230
1720
|
if (embeddingEnabled) {
|
|
1231
|
-
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.";
|
|
1232
1724
|
}
|
|
1233
1725
|
if (gitnexusEnabled) {
|
|
1234
|
-
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.";
|
|
1235
1729
|
}
|
|
1236
1730
|
return null;
|
|
1237
1731
|
}
|
|
@@ -1441,18 +1935,20 @@ async function runInternalKnowledgeCapture(args, effectiveHost) {
|
|
|
1441
1935
|
const sessionId = resolveOwnerSessionKey(payload);
|
|
1442
1936
|
const turnId = readHookString(payload, "turn_id");
|
|
1443
1937
|
const message = readHookString(payload, "message");
|
|
1444
|
-
|
|
1938
|
+
const taskConclusions = readHookTaskConclusions(payload, turnId);
|
|
1939
|
+
if (!hookCwd || !sessionId || !turnId || !message) {
|
|
1445
1940
|
printJson({ ok: true, captured: false });
|
|
1446
1941
|
return;
|
|
1447
1942
|
}
|
|
1448
1943
|
try {
|
|
1449
|
-
const project =
|
|
1944
|
+
const project = resolveWorkflowProjectContext(hookCwd, sessionId);
|
|
1450
1945
|
const result = tryCaptureKnowledgeStop({
|
|
1451
1946
|
project,
|
|
1452
1947
|
sessionId,
|
|
1453
1948
|
turnId,
|
|
1454
1949
|
message,
|
|
1455
1950
|
host: effectiveHost === "cindy" ? "cindy" : effectiveHost,
|
|
1951
|
+
taskConclusions,
|
|
1456
1952
|
});
|
|
1457
1953
|
printJson(result);
|
|
1458
1954
|
}
|
|
@@ -1527,7 +2023,7 @@ function completeKnowledgeFinalizationJob(jobPath, result, claimToken) {
|
|
|
1527
2023
|
if (running.claimToken !== claimToken) {
|
|
1528
2024
|
throw new Error("Knowledge finalization completion does not match the active claim.");
|
|
1529
2025
|
}
|
|
1530
|
-
const project =
|
|
2026
|
+
const project = resolveKnowledgeJobProject(jobPath, running);
|
|
1531
2027
|
const finishedAt = new Date().toISOString();
|
|
1532
2028
|
const truthEncoding = normalizeTruthMarkdownEncoding(project);
|
|
1533
2029
|
recordKnowledgeFinalizationResult(project, running.reportPath, {
|
|
@@ -1561,6 +2057,17 @@ function completeKnowledgeFinalizationJob(jobPath, result, claimToken) {
|
|
|
1561
2057
|
});
|
|
1562
2058
|
printJson({ ok: true, completed: true, alreadyDone: terminal.alreadyDone, finalizeId: running.finalizeId });
|
|
1563
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
|
+
}
|
|
1564
2071
|
function failKnowledgeFinalizationJob(jobPath, message, claimToken) {
|
|
1565
2072
|
const terminal = doneKnowledgeFinalizationJob({
|
|
1566
2073
|
jobPath,
|
|
@@ -1583,6 +2090,86 @@ async function runInternalEmbeddingWarmup(args) {
|
|
|
1583
2090
|
...await warmProjectMemoryEmbedding({ cwd }),
|
|
1584
2091
|
});
|
|
1585
2092
|
}
|
|
2093
|
+
function runInternalDailyMaintenance(args) {
|
|
2094
|
+
const cwd = readRequiredFlag(args, "--cwd");
|
|
2095
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
2096
|
+
const sessionOnly = readBooleanFlag(args, "--session-only");
|
|
2097
|
+
assertNoRemainingArgs(args, "internal-daily-maintenance");
|
|
2098
|
+
const project = sessionOnly
|
|
2099
|
+
? (sessionKey ? resolveSessionWorkflowContext(sessionKey) : null)
|
|
2100
|
+
: resolveProjectContext(cwd);
|
|
2101
|
+
if (!project) {
|
|
2102
|
+
printJson({ command: "internal-daily-maintenance", ok: true, ran: false, skipped: true });
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
printJson({
|
|
2106
|
+
command: "internal-daily-maintenance",
|
|
2107
|
+
ok: true,
|
|
2108
|
+
...runDailyMaintenance(project, {
|
|
2109
|
+
excludeSessionKey: sessionKey ?? undefined,
|
|
2110
|
+
includeProject: !sessionOnly,
|
|
2111
|
+
}),
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
function runInternalKnowledgeSweep(args) {
|
|
2115
|
+
const cwd = readRequiredFlag(args, "--cwd");
|
|
2116
|
+
assertNoRemainingArgs(args, "internal-knowledge-sweep");
|
|
2117
|
+
const project = tryResolveHookProject(cwd);
|
|
2118
|
+
if (!project) {
|
|
2119
|
+
printJson({ command: "internal-knowledge-sweep", ok: true, launched: 0, skipped: true });
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
const jobs = listRetryableKnowledgeFinalizationJobs(project, { excludeHosts: ["cindy"] });
|
|
2123
|
+
let launched = 0;
|
|
2124
|
+
for (const jobPath of jobs) {
|
|
2125
|
+
try {
|
|
2126
|
+
launchKnowledgeFinalizationWorker(jobPath, project.projectRoot);
|
|
2127
|
+
launched += 1;
|
|
2128
|
+
}
|
|
2129
|
+
catch {
|
|
2130
|
+
// A later background sweep can retry a job that could not be launched.
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
printJson({ command: "internal-knowledge-sweep", ok: true, discovered: jobs.length, launched });
|
|
2134
|
+
}
|
|
2135
|
+
async function runInternalBackgroundMaintenance(args) {
|
|
2136
|
+
const cwd = readRequiredFlag(args, "--cwd");
|
|
2137
|
+
const sessionKey = readOptionalFlag(args, "--session-key");
|
|
2138
|
+
assertNoRemainingArgs(args, "internal-background-maintenance");
|
|
2139
|
+
const sessionProject = sessionKey ? resolveSessionWorkflowContext(sessionKey) : null;
|
|
2140
|
+
const project = sessionProject ? null : tryResolveHookProject(cwd);
|
|
2141
|
+
let maintenance = { ran: false, skipped: true };
|
|
2142
|
+
if (sessionProject) {
|
|
2143
|
+
maintenance = runDailyMaintenance(sessionProject, { excludeSessionKey: sessionKey ?? undefined, includeProject: false });
|
|
2144
|
+
}
|
|
2145
|
+
else if (project) {
|
|
2146
|
+
maintenance = runDailyMaintenance(project, { excludeSessionKey: sessionKey ?? undefined });
|
|
2147
|
+
}
|
|
2148
|
+
let embedding = { warmed: false, reason: "skipped" };
|
|
2149
|
+
try {
|
|
2150
|
+
embedding = await warmProjectMemoryEmbedding({ cwd });
|
|
2151
|
+
}
|
|
2152
|
+
catch (error) {
|
|
2153
|
+
embedding = { warmed: false, reason: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
2154
|
+
}
|
|
2155
|
+
let discovered = 0;
|
|
2156
|
+
let launched = 0;
|
|
2157
|
+
const knowledgeProject = project ?? sessionProject;
|
|
2158
|
+
if (knowledgeProject) {
|
|
2159
|
+
const jobs = listRetryableKnowledgeFinalizationJobs(knowledgeProject, { excludeHosts: ["cindy"] });
|
|
2160
|
+
discovered = jobs.length;
|
|
2161
|
+
for (const jobPath of jobs) {
|
|
2162
|
+
try {
|
|
2163
|
+
launchKnowledgeFinalizationWorker(jobPath, knowledgeProject.projectRoot);
|
|
2164
|
+
launched += 1;
|
|
2165
|
+
}
|
|
2166
|
+
catch {
|
|
2167
|
+
// A later background sweep can retry a job that could not be launched.
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
printJson({ command: "internal-background-maintenance", ok: true, maintenance, embedding, discovered, launched });
|
|
2172
|
+
}
|
|
1586
2173
|
async function runInternalKnowledgeFinalize(args) {
|
|
1587
2174
|
const jobPath = readRequiredFlag(args, "--job");
|
|
1588
2175
|
const queued = readKnowledgeFinalizationJob(jobPath);
|
|
@@ -1640,8 +2227,6 @@ async function runInternalKnowledgeFinalize(args) {
|
|
|
1640
2227
|
async function runKnowledgeDelegateForJob(running) {
|
|
1641
2228
|
const dispatch = buildKnowledgeDelegateDispatch({
|
|
1642
2229
|
policy: "background",
|
|
1643
|
-
projectRoot: running.projectRoot,
|
|
1644
|
-
taskName: running.taskName,
|
|
1645
2230
|
finalizeId: running.finalizeId,
|
|
1646
2231
|
writer: running.writer,
|
|
1647
2232
|
});
|
|
@@ -1770,8 +2355,8 @@ async function runSessionStartHook(effectiveHost) {
|
|
|
1770
2355
|
try {
|
|
1771
2356
|
const context = await runContextCommand([], hookCwd, ownerSessionKey, effectiveHost);
|
|
1772
2357
|
const contextProject = asJsonRecord(context.project);
|
|
1773
|
-
const retryableJobs = contextProject?.scope !== "session" && !context.error
|
|
1774
|
-
? listRetryableKnowledgeFinalizationJobs(resolveProjectContext(hookCwd))
|
|
2358
|
+
const retryableJobs = effectiveHost !== "cindy" && contextProject?.scope !== "session" && !context.error
|
|
2359
|
+
? listRetryableKnowledgeFinalizationJobs(resolveProjectContext(hookCwd), { excludeHosts: ["cindy"] })
|
|
1775
2360
|
: [];
|
|
1776
2361
|
if (contextProject?.scope !== "session"
|
|
1777
2362
|
&& !context.error
|
|
@@ -1795,7 +2380,6 @@ async function runSessionStartHook(effectiveHost) {
|
|
|
1795
2380
|
hookSpecificOutput: {
|
|
1796
2381
|
hookEventName: "SessionStart",
|
|
1797
2382
|
additionalContext,
|
|
1798
|
-
...(effectiveHost === "cindy" ? { knowledgeJobs: retryableJobs } : {}),
|
|
1799
2383
|
},
|
|
1800
2384
|
})}\n`);
|
|
1801
2385
|
}
|
|
@@ -1829,6 +2413,21 @@ function readHookString(payload, key) {
|
|
|
1829
2413
|
const value = payload[key];
|
|
1830
2414
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1831
2415
|
}
|
|
2416
|
+
function readHookTaskConclusions(payload, turnId) {
|
|
2417
|
+
if (!turnId || !payload || typeof payload !== "object")
|
|
2418
|
+
return [];
|
|
2419
|
+
const value = payload.task_conclusions;
|
|
2420
|
+
if (!Array.isArray(value))
|
|
2421
|
+
return [];
|
|
2422
|
+
return value.flatMap((item) => {
|
|
2423
|
+
if (!item || typeof item !== "object")
|
|
2424
|
+
return [];
|
|
2425
|
+
const message = item.message;
|
|
2426
|
+
return typeof message === "string" && message.trim()
|
|
2427
|
+
? [{ turnId, message: message.trim() }]
|
|
2428
|
+
: [];
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
1832
2431
|
function containsClawDir(cwd) {
|
|
1833
2432
|
try {
|
|
1834
2433
|
const startDir = path.resolve(cwd);
|
|
@@ -1869,10 +2468,10 @@ function safeResolveTempDir() {
|
|
|
1869
2468
|
}
|
|
1870
2469
|
}
|
|
1871
2470
|
function buildSessionStartAdditionalContext(context, sessionCwd, effectiveHost) {
|
|
2471
|
+
const versionSyncPrompt = buildVersionSyncPrompt(context);
|
|
1872
2472
|
if (effectiveHost === "cindy") {
|
|
1873
|
-
return buildCindySessionStartContext(context, sessionCwd);
|
|
2473
|
+
return buildCindySessionStartContext(context, sessionCwd, versionSyncPrompt);
|
|
1874
2474
|
}
|
|
1875
|
-
const versionSyncPrompt = buildVersionSyncPrompt(context);
|
|
1876
2475
|
const searchGuidance = buildContextSearchGuidance(context);
|
|
1877
2476
|
const runtimeErrorPrompt = buildCodexRuntimeErrorPrompt(context);
|
|
1878
2477
|
const activeWorkflow = context.activeWorkflow;
|
|
@@ -1909,37 +2508,32 @@ function buildSessionStartAdditionalContext(context, sessionCwd, effectiveHost)
|
|
|
1909
2508
|
}
|
|
1910
2509
|
/**
|
|
1911
2510
|
* Cindy Agents use the Ghost Tool gateway, not shell commands or Host actions.
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
2511
|
+
* Match the Codex session-start wording and structure. Cindy's static entry
|
|
2512
|
+
* prompt supplies the shared skill and guidance lines; this dynamic context
|
|
2513
|
+
* supplies project identity or a recovered workflow snapshot. Goal Mode is
|
|
2514
|
+
* intentionally omitted because Cindy does not expose that Host surface.
|
|
1914
2515
|
*/
|
|
1915
|
-
function buildCindySessionStartContext(context,
|
|
1916
|
-
const
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
const lines = [
|
|
1924
|
-
"claw-kit recovered a Cindy workflow.",
|
|
1925
|
-
`- task: ${taskName}`,
|
|
1926
|
-
`- plan status: ${planStatus}`,
|
|
1927
|
-
...(planSummary ? [`- plan summary: ${planSummary}`] : []),
|
|
1928
|
-
...(typeof nextTaskTitle === "string" && nextTaskTitle.trim() ? [`- next task: ${nextTaskTitle.trim()}`] : []),
|
|
1929
|
-
"Use the claw-kit Ghost tools to inspect or advance this workflow. Do not run claw shell commands or manage host/session/Goal state yourself.",
|
|
1930
|
-
];
|
|
1931
|
-
return lines.join("\n");
|
|
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(", ")}` : "."}`);
|
|
1932
2524
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
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;
|
|
2531
|
+
}
|
|
2532
|
+
function stripCindyGoalModeLines(prompt) {
|
|
2533
|
+
return prompt
|
|
2534
|
+
.split("\n")
|
|
2535
|
+
.filter((line) => !/goal mode/i.test(line))
|
|
2536
|
+
.join("\n");
|
|
1943
2537
|
}
|
|
1944
2538
|
function buildCodexRuntimeErrorPrompt(context) {
|
|
1945
2539
|
const error = asJsonRecord(context.error);
|
|
@@ -2224,7 +2818,7 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
|
|
|
2224
2818
|
const resolvedPlanPath = archivedPlanPath ?? result.planPath;
|
|
2225
2819
|
const codexResult = effectiveHost === "codex";
|
|
2226
2820
|
const cindyResult = effectiveHost === "cindy";
|
|
2227
|
-
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 }) : [];
|
|
2228
2822
|
const nextsteps = codexResult
|
|
2229
2823
|
&& result.planStatus === "end.completed"
|
|
2230
2824
|
&& result.workflowGuidance.goalTool?.tool === "update_goal"
|
|
@@ -2299,87 +2893,6 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
|
|
|
2299
2893
|
...(!codexResult || !includePlan ? { planSummary } : {}),
|
|
2300
2894
|
};
|
|
2301
2895
|
}
|
|
2302
|
-
function buildHostActions(result, options = {}) {
|
|
2303
|
-
const latestEvent = result.events?.at(-1);
|
|
2304
|
-
const actionIdPrefix = options.actionIdPrefix ?? latestEvent?.mutationId;
|
|
2305
|
-
if (!actionIdPrefix) {
|
|
2306
|
-
return [];
|
|
2307
|
-
}
|
|
2308
|
-
const actions = [];
|
|
2309
|
-
const goalTool = result.workflowGuidance.goalTool;
|
|
2310
|
-
const isSubplanGoalHandoff = Boolean(result.plan?.parentPlan
|
|
2311
|
-
&& goalTool?.tool === "update_goal"
|
|
2312
|
-
&& goalTool.status === "complete");
|
|
2313
|
-
if (isSubplanGoalHandoff && goalTool?.tool === "update_goal") {
|
|
2314
|
-
actions.push({
|
|
2315
|
-
schemaVersion: 1,
|
|
2316
|
-
id: `${actionIdPrefix}:update_goal`,
|
|
2317
|
-
tool: "update_goal",
|
|
2318
|
-
input: {
|
|
2319
|
-
status: "complete",
|
|
2320
|
-
},
|
|
2321
|
-
});
|
|
2322
|
-
}
|
|
2323
|
-
if (result.plan
|
|
2324
|
-
&& shouldUsePlanHostIntegration(result.plan)
|
|
2325
|
-
&& result.plan.tasks.length > 0
|
|
2326
|
-
&& (options.forceProjectionSync || codexPlanProjectionChanged(result.previousPlan, result.plan, result.planStatus))) {
|
|
2327
|
-
const plan = buildCodexPlanProjection(result.plan, result.planStatus);
|
|
2328
|
-
actions.push({
|
|
2329
|
-
schemaVersion: 1,
|
|
2330
|
-
id: `${actionIdPrefix}:update_plan`,
|
|
2331
|
-
tool: "update_plan",
|
|
2332
|
-
input: {
|
|
2333
|
-
explanation: result.workflowGuidance.summary,
|
|
2334
|
-
plan,
|
|
2335
|
-
},
|
|
2336
|
-
});
|
|
2337
|
-
}
|
|
2338
|
-
if (goalTool && !isSubplanGoalHandoff) {
|
|
2339
|
-
if (goalTool.tool === "create_goal") {
|
|
2340
|
-
actions.push({
|
|
2341
|
-
schemaVersion: 1,
|
|
2342
|
-
id: `${actionIdPrefix}:create_goal`,
|
|
2343
|
-
tool: "create_goal",
|
|
2344
|
-
input: {
|
|
2345
|
-
objective: goalTool.objective,
|
|
2346
|
-
},
|
|
2347
|
-
});
|
|
2348
|
-
}
|
|
2349
|
-
else {
|
|
2350
|
-
const codexStatus = result.planStatus === "process.wait" || result.planStatus === "process.discussing"
|
|
2351
|
-
? "complete"
|
|
2352
|
-
: goalTool.status;
|
|
2353
|
-
actions.push({
|
|
2354
|
-
schemaVersion: 1,
|
|
2355
|
-
id: `${actionIdPrefix}:update_goal`,
|
|
2356
|
-
tool: "update_goal",
|
|
2357
|
-
input: {
|
|
2358
|
-
status: codexStatus,
|
|
2359
|
-
},
|
|
2360
|
-
});
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
return actions;
|
|
2364
|
-
}
|
|
2365
|
-
function codexPlanProjectionChanged(previousPlan, plan, planStatus) {
|
|
2366
|
-
if (!previousPlan)
|
|
2367
|
-
return true;
|
|
2368
|
-
return JSON.stringify(buildCodexPlanProjection(previousPlan, previousPlan.status))
|
|
2369
|
-
!== JSON.stringify(buildCodexPlanProjection(plan, planStatus));
|
|
2370
|
-
}
|
|
2371
|
-
function buildCodexPlanProjection(plan, planStatus) {
|
|
2372
|
-
const activeTask = plan.tasks.find((task) => task.status === "in_progress" || task.status === "subagent_running") ?? (planStatus === "process.active"
|
|
2373
|
-
? plan.tasks.find((task) => task.status !== "done")
|
|
2374
|
-
: undefined);
|
|
2375
|
-
return plan.tasks.map((task) => {
|
|
2376
|
-
let status = task.status === "done" ? "completed" : "pending";
|
|
2377
|
-
if (task.id === activeTask?.id) {
|
|
2378
|
-
status = "in_progress";
|
|
2379
|
-
}
|
|
2380
|
-
return { step: task.title, status };
|
|
2381
|
-
});
|
|
2382
|
-
}
|
|
2383
2896
|
function compactDirectCommandResult(command, workflowGuidance, completionRefresh) {
|
|
2384
2897
|
return {
|
|
2385
2898
|
ok: true,
|
|
@@ -2467,7 +2980,7 @@ function readOrderedPlanEditOperations(args) {
|
|
|
2467
2980
|
}
|
|
2468
2981
|
return operations;
|
|
2469
2982
|
}
|
|
2470
|
-
function
|
|
2983
|
+
function requestsPlanEndTerminal(operations) {
|
|
2471
2984
|
return operations.some((operation) => operation.type === "plan.status" && operation.status.startsWith("end."));
|
|
2472
2985
|
}
|
|
2473
2986
|
function planCompletionOperations(updates) {
|