@veewo/claw 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6,11 +6,11 @@ import { createInterface } from "node:readline";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import { createHash } from "node:crypto";
8
8
  import { spawn, spawnSync } from "node:child_process";
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
+ import { buildDirectWorkflowGuidance, appendKnowledgeTaskConclusions, buildKnowledgeAtomicDispatch, buildKnowledgeDelegateDispatch, buildKnowledgeAssignmentTemplate, buildKnowledgeWriterAssignments, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findKnowledgeFinalizationJobPath, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolvePlanEffectiveConfig, resolveKnowledgeWriterForHost, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, doneKnowledgeFinalizationJob, readKnowledgeFinalizationJob, waitForKnowledgeFinalizationJobReady, listKnowledgeFinalizationJobs, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, recordKnowledgeFinalizationResult, unbindSession, writePlan, } from "@veewo/claw-core";
10
10
  import { buildCodexDriverEnvelope } from "./codex-driver.js";
11
11
  import { buildCodexHostActions } from "./codex-host-actions.js";
12
12
  import { checkCodexRuntime, resolveCodexSdkEntryPath } from "./codex-runtime.js";
13
- import { extractLatestFinalAssistantMessage, extractTaskDoneConclusions, } from "./codex-transcript.js";
13
+ import { extractLatestFinalAssistantMessage, extractTaskDoneConclusions, findCodexTranscriptPath, } from "./codex-transcript.js";
14
14
  import { consumeBufferedHookInput } from "./knowledge-hook-preflight.js";
15
15
  import { resolveInvocationHost, withoutInvocationHost } from "./invocation-host.js";
16
16
  import { runOpencodeKnowledgeWriter } from "./opencode-runner.js";
@@ -283,7 +283,7 @@ const COMMAND_HELP = {
283
283
  description: "Create a flat subplan file under the task directory. Uses explicit `--template` first, otherwise the project's configured `defaultPlanTemplate`, and finally falls back to the built-in `default`. The current session binding switches to the subplan and returns to its parent when the subplan ends.",
284
284
  summary: "Create a subplan under a parent task's task item.",
285
285
  options: [
286
- { flag: "--parent <task-name>", detail: "(required) Parent task name." },
286
+ { flag: "--parent <task-name>", detail: "(required) Parent task directory name (`taskName`), not its plan title." },
287
287
  { flag: "--task-id <number>", detail: "(required) Parent task item id to split into a subplan." },
288
288
  { flag: "--template <name>", detail: "Optional plan template name. Overrides the project's configured default template." },
289
289
  { flag: "--template-file <path>", detail: "Exact plan template file. Mutually exclusive with --template." },
@@ -336,10 +336,17 @@ const COMMAND_HELP = {
336
336
  ],
337
337
  },
338
338
  claim: {
339
- usage: ["{script} knowledge claim --job <path>"],
340
- description: "Claim a queued or retryable job after its executor session has been bound.",
341
- summary: "Claim a session-bound finalization job.",
342
- options: [{ flag: "--job <path>", detail: "(required) Finalization job JSON path." }],
339
+ usage: [
340
+ "{script} knowledge claim --job <path>",
341
+ "{script} knowledge claim --project-root <path> --finalize-id <id>",
342
+ ],
343
+ description: "Claim a queued or retryable job. Codex subagent claims capture the existing task conclusions from the parent transcript before ownership is granted.",
344
+ summary: "Claim a ready finalization job and prepare its report.",
345
+ options: [
346
+ { flag: "--job <path>", detail: "Exact finalization job JSON path." },
347
+ { flag: "--project-root <path>", detail: "Project that owns a ready finalization job." },
348
+ { flag: "--finalize-id <id>", detail: "Finalization id used with --project-root." },
349
+ ],
343
350
  },
344
351
  done: {
345
352
  usage: [
@@ -977,6 +984,38 @@ function serializeSessionError(error) {
977
984
  }
978
985
  return { code: "SESSION_COMMAND_FAILED", message: error instanceof Error ? error.message : String(error) };
979
986
  }
987
+ function readCindyKnowledgeClaimCaptureInput() {
988
+ const raw = fs.readFileSync(0, "utf8").trim();
989
+ if (!raw) {
990
+ throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input is empty.");
991
+ }
992
+ let parsed;
993
+ try {
994
+ parsed = JSON.parse(raw);
995
+ }
996
+ catch {
997
+ throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input is invalid JSON.");
998
+ }
999
+ const sessionId = typeof parsed.session_id === "string" ? parsed.session_id.trim() : "";
1000
+ const turnId = typeof parsed.turn_id === "string" ? parsed.turn_id.trim() : "";
1001
+ const taskConclusions = Array.isArray(parsed.task_conclusions)
1002
+ ? parsed.task_conclusions.flatMap((entry) => {
1003
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
1004
+ return [];
1005
+ const item = entry;
1006
+ const itemTurnId = typeof item.turnId === "string" ? item.turnId.trim() : "";
1007
+ const message = typeof item.message === "string" ? item.message.trim() : "";
1008
+ return itemTurnId && message ? [{ turnId: itemTurnId, message }] : [];
1009
+ })
1010
+ : [];
1011
+ if (!sessionId || !turnId) {
1012
+ throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input requires session_id and turn_id.");
1013
+ }
1014
+ if (taskConclusions.length === 0) {
1015
+ throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input requires at least one task conclusion.");
1016
+ }
1017
+ return { sessionId, turnId, taskConclusions };
1018
+ }
980
1019
  function runKnowledge(args) {
981
1020
  const subcommand = args.shift();
982
1021
  switch (subcommand) {
@@ -1039,9 +1078,68 @@ function runKnowledge(args) {
1039
1078
  return;
1040
1079
  }
1041
1080
  case "claim": {
1042
- const jobPath = readRequiredFlag(args, "--job");
1081
+ const explicitJobPath = readOptionalFlag(args, "--job");
1082
+ const projectRoot = readOptionalFlag(args, "--project-root");
1083
+ const finalizeId = readOptionalFlag(args, "--finalize-id");
1084
+ const captureCindyReport = readBooleanFlag(args, "--cindy-report-stdin");
1085
+ const cindyCapture = captureCindyReport ? readCindyKnowledgeClaimCaptureInput() : undefined;
1086
+ if (explicitJobPath && (projectRoot || finalizeId)) {
1087
+ throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge claim accepts either --job or --project-root with --finalize-id.");
1088
+ }
1089
+ if (!explicitJobPath && (!projectRoot || !finalizeId)) {
1090
+ throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge claim requires --job or both --project-root and --finalize-id.");
1091
+ }
1043
1092
  assertNoRemainingArgs(args, "knowledge claim");
1044
- const job = claimKnowledgeFinalizationJob(jobPath);
1093
+ const jobPath = explicitJobPath ?? findKnowledgeFinalizationJobPath(resolveProjectContext(path.resolve(projectRoot)), finalizeId);
1094
+ if (!jobPath) {
1095
+ throw new Error(`Knowledge finalization ${finalizeId} is unavailable.`);
1096
+ }
1097
+ const job = claimKnowledgeFinalizationJob(jobPath, {
1098
+ prepare: (queued) => {
1099
+ if (queued.writer?.executionPolicy !== "subagent"
1100
+ || queued.reportCapture?.mode !== "claim"
1101
+ || queued.reportCapture.status === "captured") {
1102
+ return;
1103
+ }
1104
+ if (queued.host === "cindy") {
1105
+ if (!cindyCapture) {
1106
+ throw new Error(`Cindy report capture is unavailable for knowledge session ${queued.sessionId}.`);
1107
+ }
1108
+ if (cindyCapture.sessionId !== queued.sessionId) {
1109
+ throw new Error("Cindy report capture does not match the originating knowledge session.");
1110
+ }
1111
+ const capturedAt = new Date().toISOString();
1112
+ appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, cindyCapture.taskConclusions, capturedAt);
1113
+ return {
1114
+ reportCapture: {
1115
+ ...queued.reportCapture,
1116
+ status: "captured",
1117
+ capturedAt,
1118
+ messageCount: cindyCapture.taskConclusions.length,
1119
+ },
1120
+ };
1121
+ }
1122
+ if (queued.host !== "codex") {
1123
+ throw new Error(`Claim-time report capture is unavailable for host ${queued.host ?? "unknown"}.`);
1124
+ }
1125
+ const transcriptPath = findCodexTranscriptPath(queued.sessionId);
1126
+ if (!transcriptPath) {
1127
+ throw new Error(`Codex transcript is unavailable for knowledge session ${queued.sessionId}.`);
1128
+ }
1129
+ const conclusions = extractTaskDoneConclusions(transcriptPath, undefined, queued.reportCapture.startedAt);
1130
+ const capturedAt = new Date().toISOString();
1131
+ appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, conclusions, capturedAt);
1132
+ return {
1133
+ reportCapture: {
1134
+ ...queued.reportCapture,
1135
+ status: "captured",
1136
+ capturedAt,
1137
+ transcriptPath,
1138
+ messageCount: conclusions.length,
1139
+ },
1140
+ };
1141
+ },
1142
+ });
1045
1143
  const assignments = job ? buildKnowledgeWriterAssignments(job) : [];
1046
1144
  const templatePath = job
1047
1145
  ? path.join(path.dirname(jobPath), `${job.finalizeId}.assignments.json`)
@@ -1059,6 +1157,7 @@ function runKnowledge(args) {
1059
1157
  claimed: Boolean(job),
1060
1158
  ...(job ? {
1061
1159
  finalizeId: job.finalizeId,
1160
+ jobPath,
1062
1161
  claimToken: job.claimToken,
1063
1162
  projectRoot: job.projectRoot,
1064
1163
  writer: job.writer ?? null,
@@ -1139,14 +1238,15 @@ async function runPlan(args, effectiveHost) {
1139
1238
  ? showPlan({ cwd: process.cwd(), ...target, ownerSessionKey })
1140
1239
  : undefined;
1141
1240
  const project = entersEndTerminal ? tryResolveHookProject(process.cwd()) : null;
1142
- const effectiveWriter = current && project
1241
+ const effectiveWriter = resolveKnowledgeWriterForHost(current && project
1143
1242
  ? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
1144
- : undefined;
1243
+ : undefined, effectiveHost);
1145
1244
  if (current
1146
1245
  && !current.plan.parentPlan
1147
1246
  && effectiveWriter?.executionPolicy === "subagent"
1148
- && effectiveHost !== "codex") {
1149
- throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex host.', { host: effectiveHost ?? null });
1247
+ && effectiveHost !== "codex"
1248
+ && effectiveHost !== "cindy") {
1249
+ throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
1150
1250
  }
1151
1251
  const queuePlanEndFinalization = entersEndTerminal
1152
1252
  ? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
@@ -1162,11 +1262,12 @@ async function runPlan(args, effectiveHost) {
1162
1262
  const completionRefresh = queuePlanEndFinalization?.(result.taskName);
1163
1263
  const knowledgeDispatch = (current
1164
1264
  && project
1165
- && effectiveHost === "codex"
1265
+ && (effectiveHost === "codex" || effectiveHost === "cindy")
1166
1266
  && !current.plan.parentPlan
1167
1267
  && effectiveWriter?.executionPolicy === "subagent"
1168
1268
  && result.knowledgeFinalizeId)
1169
1269
  ? buildKnowledgeDispatch({
1270
+ host: effectiveHost,
1170
1271
  finalizeId: result.knowledgeFinalizeId,
1171
1272
  writer: effectiveWriter,
1172
1273
  })
@@ -1245,13 +1346,14 @@ async function runPlan(args, effectiveHost) {
1245
1346
  ownerSessionKey,
1246
1347
  });
1247
1348
  const project = tryResolveHookProject(process.cwd());
1248
- const effectiveWriter = project
1349
+ const effectiveWriter = resolveKnowledgeWriterForHost(project
1249
1350
  ? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
1250
- : undefined;
1351
+ : undefined, effectiveHost);
1251
1352
  if (!current.plan.parentPlan
1252
1353
  && effectiveWriter?.executionPolicy === "subagent"
1253
- && effectiveHost !== "codex") {
1254
- throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex host.', { host: effectiveHost ?? null });
1354
+ && effectiveHost !== "codex"
1355
+ && effectiveHost !== "cindy") {
1356
+ throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
1255
1357
  }
1256
1358
  const queuePlanEndFinalization = preparePlanEndFinalization(process.cwd(), ownerSessionKey);
1257
1359
  const result = await editPlan({
@@ -1263,11 +1365,12 @@ async function runPlan(args, effectiveHost) {
1263
1365
  ownerSessionKey,
1264
1366
  });
1265
1367
  const completionRefresh = queuePlanEndFinalization?.(result.taskName);
1266
- const knowledgeDispatch = (effectiveHost === "codex"
1368
+ const knowledgeDispatch = ((effectiveHost === "codex" || effectiveHost === "cindy")
1267
1369
  && !current.plan.parentPlan
1268
1370
  && effectiveWriter?.executionPolicy === "subagent"
1269
1371
  && result.knowledgeFinalizeId)
1270
1372
  ? buildKnowledgeDispatch({
1373
+ host: effectiveHost,
1271
1374
  finalizeId: result.knowledgeFinalizeId,
1272
1375
  writer: effectiveWriter,
1273
1376
  })
@@ -1394,7 +1497,7 @@ async function runTask(args, effectiveHost) {
1394
1497
  cwd: process.cwd(),
1395
1498
  ...target,
1396
1499
  operations,
1397
- commandSource: "plan.edit",
1500
+ commandSource: "task.add",
1398
1501
  host: effectiveHost,
1399
1502
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1400
1503
  });
@@ -1410,7 +1513,7 @@ async function runTask(args, effectiveHost) {
1410
1513
  cwd: process.cwd(),
1411
1514
  ...target,
1412
1515
  operations,
1413
- commandSource: "plan.edit",
1516
+ commandSource: "task.edit",
1414
1517
  host: effectiveHost,
1415
1518
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1416
1519
  });
@@ -1426,7 +1529,7 @@ async function runTask(args, effectiveHost) {
1426
1529
  cwd: process.cwd(),
1427
1530
  ...target,
1428
1531
  operations,
1429
- commandSource: "plan.edit",
1532
+ commandSource: "task.remove",
1430
1533
  host: effectiveHost,
1431
1534
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1432
1535
  });
@@ -1442,6 +1545,7 @@ async function runTask(args, effectiveHost) {
1442
1545
  cwd: process.cwd(),
1443
1546
  ...target,
1444
1547
  operations,
1548
+ commandSource: "task.done",
1445
1549
  host: effectiveHost,
1446
1550
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1447
1551
  });
@@ -2805,9 +2909,13 @@ function stripBom(content) {
2805
2909
  return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
2806
2910
  }
2807
2911
  function buildKnowledgeDispatch(input) {
2912
+ if (input.host === "cindy") {
2913
+ return buildKnowledgeAtomicDispatch(input);
2914
+ }
2808
2915
  return buildKnowledgeDelegateDispatch({
2809
2916
  policy: "subagent",
2810
- ...input,
2917
+ finalizeId: input.finalizeId,
2918
+ writer: input.writer,
2811
2919
  });
2812
2920
  }
2813
2921
  function compactPlanCommandResult(command, result, effectiveHost, completionRefresh, forceProjectionSync = false, knowledgeDispatch) {