@veewo/claw 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,35 @@ 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
+ return { sessionId, turnId, taskConclusions };
1015
+ }
980
1016
  function runKnowledge(args) {
981
1017
  const subcommand = args.shift();
982
1018
  switch (subcommand) {
@@ -1039,9 +1075,68 @@ function runKnowledge(args) {
1039
1075
  return;
1040
1076
  }
1041
1077
  case "claim": {
1042
- const jobPath = readRequiredFlag(args, "--job");
1078
+ const explicitJobPath = readOptionalFlag(args, "--job");
1079
+ const projectRoot = readOptionalFlag(args, "--project-root");
1080
+ const finalizeId = readOptionalFlag(args, "--finalize-id");
1081
+ const captureCindyReport = readBooleanFlag(args, "--cindy-report-stdin");
1082
+ const cindyCapture = captureCindyReport ? readCindyKnowledgeClaimCaptureInput() : undefined;
1083
+ if (explicitJobPath && (projectRoot || finalizeId)) {
1084
+ throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge claim accepts either --job or --project-root with --finalize-id.");
1085
+ }
1086
+ if (!explicitJobPath && (!projectRoot || !finalizeId)) {
1087
+ throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge claim requires --job or both --project-root and --finalize-id.");
1088
+ }
1043
1089
  assertNoRemainingArgs(args, "knowledge claim");
1044
- const job = claimKnowledgeFinalizationJob(jobPath);
1090
+ const jobPath = explicitJobPath ?? findKnowledgeFinalizationJobPath(resolveProjectContext(path.resolve(projectRoot)), finalizeId);
1091
+ if (!jobPath) {
1092
+ throw new Error(`Knowledge finalization ${finalizeId} is unavailable.`);
1093
+ }
1094
+ const job = claimKnowledgeFinalizationJob(jobPath, {
1095
+ prepare: (queued) => {
1096
+ if (queued.writer?.executionPolicy !== "subagent"
1097
+ || queued.reportCapture?.mode !== "claim"
1098
+ || queued.reportCapture.status === "captured") {
1099
+ return;
1100
+ }
1101
+ if (queued.host === "cindy") {
1102
+ if (!cindyCapture) {
1103
+ throw new Error(`Cindy report capture is unavailable for knowledge session ${queued.sessionId}.`);
1104
+ }
1105
+ if (cindyCapture.sessionId !== queued.sessionId) {
1106
+ throw new Error("Cindy report capture does not match the originating knowledge session.");
1107
+ }
1108
+ const capturedAt = new Date().toISOString();
1109
+ appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, cindyCapture.taskConclusions, capturedAt);
1110
+ return {
1111
+ reportCapture: {
1112
+ ...queued.reportCapture,
1113
+ status: "captured",
1114
+ capturedAt,
1115
+ messageCount: cindyCapture.taskConclusions.length,
1116
+ },
1117
+ };
1118
+ }
1119
+ if (queued.host !== "codex") {
1120
+ throw new Error(`Claim-time report capture is unavailable for host ${queued.host ?? "unknown"}.`);
1121
+ }
1122
+ const transcriptPath = findCodexTranscriptPath(queued.sessionId);
1123
+ if (!transcriptPath) {
1124
+ throw new Error(`Codex transcript is unavailable for knowledge session ${queued.sessionId}.`);
1125
+ }
1126
+ const conclusions = extractTaskDoneConclusions(transcriptPath, undefined, queued.reportCapture.startedAt);
1127
+ const capturedAt = new Date().toISOString();
1128
+ appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, conclusions, capturedAt);
1129
+ return {
1130
+ reportCapture: {
1131
+ ...queued.reportCapture,
1132
+ status: "captured",
1133
+ capturedAt,
1134
+ transcriptPath,
1135
+ messageCount: conclusions.length,
1136
+ },
1137
+ };
1138
+ },
1139
+ });
1045
1140
  const assignments = job ? buildKnowledgeWriterAssignments(job) : [];
1046
1141
  const templatePath = job
1047
1142
  ? path.join(path.dirname(jobPath), `${job.finalizeId}.assignments.json`)
@@ -1059,6 +1154,7 @@ function runKnowledge(args) {
1059
1154
  claimed: Boolean(job),
1060
1155
  ...(job ? {
1061
1156
  finalizeId: job.finalizeId,
1157
+ jobPath,
1062
1158
  claimToken: job.claimToken,
1063
1159
  projectRoot: job.projectRoot,
1064
1160
  writer: job.writer ?? null,
@@ -1139,14 +1235,15 @@ async function runPlan(args, effectiveHost) {
1139
1235
  ? showPlan({ cwd: process.cwd(), ...target, ownerSessionKey })
1140
1236
  : undefined;
1141
1237
  const project = entersEndTerminal ? tryResolveHookProject(process.cwd()) : null;
1142
- const effectiveWriter = current && project
1238
+ const effectiveWriter = resolveKnowledgeWriterForHost(current && project
1143
1239
  ? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
1144
- : undefined;
1240
+ : undefined, effectiveHost);
1145
1241
  if (current
1146
1242
  && !current.plan.parentPlan
1147
1243
  && 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 });
1244
+ && effectiveHost !== "codex"
1245
+ && effectiveHost !== "cindy") {
1246
+ throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
1150
1247
  }
1151
1248
  const queuePlanEndFinalization = entersEndTerminal
1152
1249
  ? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
@@ -1162,11 +1259,12 @@ async function runPlan(args, effectiveHost) {
1162
1259
  const completionRefresh = queuePlanEndFinalization?.(result.taskName);
1163
1260
  const knowledgeDispatch = (current
1164
1261
  && project
1165
- && effectiveHost === "codex"
1262
+ && (effectiveHost === "codex" || effectiveHost === "cindy")
1166
1263
  && !current.plan.parentPlan
1167
1264
  && effectiveWriter?.executionPolicy === "subagent"
1168
1265
  && result.knowledgeFinalizeId)
1169
1266
  ? buildKnowledgeDispatch({
1267
+ host: effectiveHost,
1170
1268
  finalizeId: result.knowledgeFinalizeId,
1171
1269
  writer: effectiveWriter,
1172
1270
  })
@@ -1245,13 +1343,14 @@ async function runPlan(args, effectiveHost) {
1245
1343
  ownerSessionKey,
1246
1344
  });
1247
1345
  const project = tryResolveHookProject(process.cwd());
1248
- const effectiveWriter = project
1346
+ const effectiveWriter = resolveKnowledgeWriterForHost(project
1249
1347
  ? resolvePlanEffectiveConfig(project.projectConfig, current.plan)?.knowledgeWriter
1250
- : undefined;
1348
+ : undefined, effectiveHost);
1251
1349
  if (!current.plan.parentPlan
1252
1350
  && 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 });
1351
+ && effectiveHost !== "codex"
1352
+ && effectiveHost !== "cindy") {
1353
+ throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
1255
1354
  }
1256
1355
  const queuePlanEndFinalization = preparePlanEndFinalization(process.cwd(), ownerSessionKey);
1257
1356
  const result = await editPlan({
@@ -1263,11 +1362,12 @@ async function runPlan(args, effectiveHost) {
1263
1362
  ownerSessionKey,
1264
1363
  });
1265
1364
  const completionRefresh = queuePlanEndFinalization?.(result.taskName);
1266
- const knowledgeDispatch = (effectiveHost === "codex"
1365
+ const knowledgeDispatch = ((effectiveHost === "codex" || effectiveHost === "cindy")
1267
1366
  && !current.plan.parentPlan
1268
1367
  && effectiveWriter?.executionPolicy === "subagent"
1269
1368
  && result.knowledgeFinalizeId)
1270
1369
  ? buildKnowledgeDispatch({
1370
+ host: effectiveHost,
1271
1371
  finalizeId: result.knowledgeFinalizeId,
1272
1372
  writer: effectiveWriter,
1273
1373
  })
@@ -1394,7 +1494,7 @@ async function runTask(args, effectiveHost) {
1394
1494
  cwd: process.cwd(),
1395
1495
  ...target,
1396
1496
  operations,
1397
- commandSource: "plan.edit",
1497
+ commandSource: "task.add",
1398
1498
  host: effectiveHost,
1399
1499
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1400
1500
  });
@@ -1410,7 +1510,7 @@ async function runTask(args, effectiveHost) {
1410
1510
  cwd: process.cwd(),
1411
1511
  ...target,
1412
1512
  operations,
1413
- commandSource: "plan.edit",
1513
+ commandSource: "task.edit",
1414
1514
  host: effectiveHost,
1415
1515
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1416
1516
  });
@@ -1426,7 +1526,7 @@ async function runTask(args, effectiveHost) {
1426
1526
  cwd: process.cwd(),
1427
1527
  ...target,
1428
1528
  operations,
1429
- commandSource: "plan.edit",
1529
+ commandSource: "task.remove",
1430
1530
  host: effectiveHost,
1431
1531
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1432
1532
  });
@@ -1442,6 +1542,7 @@ async function runTask(args, effectiveHost) {
1442
1542
  cwd: process.cwd(),
1443
1543
  ...target,
1444
1544
  operations,
1545
+ commandSource: "task.done",
1445
1546
  host: effectiveHost,
1446
1547
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1447
1548
  });
@@ -2805,9 +2906,13 @@ function stripBom(content) {
2805
2906
  return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
2806
2907
  }
2807
2908
  function buildKnowledgeDispatch(input) {
2909
+ if (input.host === "cindy") {
2910
+ return buildKnowledgeAtomicDispatch(input);
2911
+ }
2808
2912
  return buildKnowledgeDelegateDispatch({
2809
2913
  policy: "subagent",
2810
- ...input,
2914
+ finalizeId: input.finalizeId,
2915
+ writer: input.writer,
2811
2916
  });
2812
2917
  }
2813
2918
  function compactPlanCommandResult(command, result, effectiveHost, completionRefresh, forceProjectionSync = false, knowledgeDispatch) {