@veewo/claw 0.2.25 → 0.2.26

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,12 +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, appendKnowledgeTaskConclusions, buildKnowledgeAtomicDispatch, buildKnowledgeDelegateDispatch, buildKnowledgeAssignmentTemplate, buildDirectKnowledgeAssignments, buildKnowledgeWriterAssignments, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, assertRootPlanCreateAllowedForPlan, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findKnowledgeFinalizationJobPath, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolvePlanEffectiveConfig, resolveThreadGoalPlan, resolveKnowledgeWriterForHost, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, createPlanRef, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, doneKnowledgeFinalizationJob, readKnowledgeFinalizationJob, waitForKnowledgeFinalizationJobReady, listKnowledgeFinalizationJobs, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, governKnowledgeMarkdownPaths, resolveKnowledgeDocUpdateSnapshot, recordKnowledgeFinalizationResult, unbindSession, writePlan, } from "@veewo/claw-core";
9
+ import { buildDirectWorkflowGuidance, buildKnowledgeAtomicDispatch, buildKnowledgeDelegateDispatch, buildKnowledgeAssignmentTemplate, buildDirectKnowledgeAssignments, buildKnowledgeWriterAssignments, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, assertRootPlanCreateAllowedForPlan, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findKnowledgeFinalizationJobPath, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolvePlanEffectiveConfig, resolveThreadGoalPlan, resolveKnowledgeWriterForHost, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, createPlanRef, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, doneKnowledgeFinalizationJob, readKnowledgeFinalizationJob, waitForKnowledgeFinalizationJobReady, listKnowledgeFinalizationJobs, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, governKnowledgeMarkdownPaths, resolveKnowledgeDocUpdateSnapshot, 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, findCodexTranscriptPath, } from "./codex-transcript.js";
14
- import { readDshKnowledgeCapture } from "./dsh-capture.js";
13
+ import { collectReport, registerReportCollector } from "./report-collector-registry.js";
15
14
  import { consumeBufferedHookInput } from "./knowledge-hook-preflight.js";
16
15
  import { isSubagentPolicyHost, resolveInvocationHost, withoutInvocationHost } from "./invocation-host.js";
17
16
  import { runOpencodeKnowledgeWriter } from "./opencode-runner.js";
@@ -564,6 +563,9 @@ async function main() {
564
563
  case "knowledge":
565
564
  await runKnowledge(args);
566
565
  return;
566
+ case "internal-report-collector-register":
567
+ runInternalReportCollectorRegister(args);
568
+ return;
567
569
  case "direct":
568
570
  runDirect(args, effectiveHost);
569
571
  return;
@@ -1013,34 +1015,18 @@ function serializeSessionError(error) {
1013
1015
  }
1014
1016
  return { code: "SESSION_COMMAND_FAILED", message: error instanceof Error ? error.message : String(error) };
1015
1017
  }
1016
- function readCindyKnowledgeClaimCaptureInput() {
1017
- const raw = fs.readFileSync(0, "utf8").trim();
1018
- if (!raw) {
1019
- throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input is empty.");
1020
- }
1021
- let parsed;
1022
- try {
1023
- parsed = JSON.parse(raw);
1024
- }
1025
- catch {
1026
- throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input is invalid JSON.");
1027
- }
1028
- const sessionId = typeof parsed.session_id === "string" ? parsed.session_id.trim() : "";
1029
- const turnId = typeof parsed.turn_id === "string" ? parsed.turn_id.trim() : "";
1030
- const taskConclusions = Array.isArray(parsed.task_conclusions)
1031
- ? parsed.task_conclusions.flatMap((entry) => {
1032
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
1033
- return [];
1034
- const item = entry;
1035
- const itemTurnId = typeof item.turnId === "string" ? item.turnId.trim() : "";
1036
- const message = typeof item.message === "string" ? item.message.trim() : "";
1037
- return itemTurnId && message ? [{ turnId: itemTurnId, message }] : [];
1038
- })
1039
- : [];
1040
- if (!sessionId || !turnId) {
1041
- throw new ClawError("PROJECT_CONFIG_INVALID", "Cindy knowledge claim report input requires session_id and turn_id.");
1018
+ /** Adapter-only registration. The descriptor contains no host history schema. */
1019
+ function runInternalReportCollectorRegister(args) {
1020
+ const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
1021
+ const host = readRequiredFlag(args, "--collector-host");
1022
+ const executable = readRequiredFlag(args, "--executable");
1023
+ const collectorArgs = readRepeatedFlag(args, "--arg");
1024
+ assertNoRemainingArgs(args, "internal-report-collector-register");
1025
+ if (host !== "codex" && host !== "dsh" && host !== "cindy") {
1026
+ throw new ClawError("PROJECT_CONFIG_INVALID", "report collector host must be codex, dsh, or cindy.");
1042
1027
  }
1043
- return { sessionId, turnId, taskConclusions };
1028
+ registerReportCollector(projectRoot, { host, executable, args: collectorArgs });
1029
+ printJson({ ok: true, command: "internal-report-collector-register", host });
1044
1030
  }
1045
1031
  async function runKnowledge(args) {
1046
1032
  const subcommand = args.shift();
@@ -1125,8 +1111,6 @@ async function runKnowledge(args) {
1125
1111
  const explicitJobPath = readOptionalFlag(args, "--job");
1126
1112
  const projectRoot = readOptionalFlag(args, "--project-root");
1127
1113
  const finalizeId = readOptionalFlag(args, "--finalize-id");
1128
- const captureCindyReport = readBooleanFlag(args, "--cindy-report-stdin");
1129
- const cindyCapture = captureCindyReport ? readCindyKnowledgeClaimCaptureInput() : undefined;
1130
1114
  if (explicitJobPath && (projectRoot || finalizeId)) {
1131
1115
  throw new ClawError("PROJECT_CONFIG_INVALID", "knowledge claim accepts either --job or --project-root with --finalize-id.");
1132
1116
  }
@@ -1139,9 +1123,6 @@ async function runKnowledge(args) {
1139
1123
  throw new Error(`Knowledge finalization ${finalizeId} is unavailable.`);
1140
1124
  }
1141
1125
  const queued = readKnowledgeFinalizationJob(jobPath);
1142
- if (queued.host === "codex" && queued.writer?.executionPolicy === "subagent" && queued.reportCapture?.mode === "claim" && queued.reportCapture.status !== "captured") {
1143
- await new Promise((resolve) => setTimeout(resolve, 10_000));
1144
- }
1145
1126
  const job = claimKnowledgeFinalizationJob(jobPath, {
1146
1127
  prepare: (queued) => {
1147
1128
  if (queued.writer?.executionPolicy !== "subagent"
@@ -1149,103 +1130,22 @@ async function runKnowledge(args) {
1149
1130
  || queued.reportCapture.status === "captured") {
1150
1131
  return;
1151
1132
  }
1152
- if (queued.host === "cindy") {
1153
- if (!cindyCapture) {
1154
- throw new Error(`Cindy report capture is unavailable for knowledge session ${queued.sessionId}.`);
1155
- }
1156
- if (cindyCapture.sessionId !== queued.sessionId) {
1157
- throw new Error("Cindy report capture does not match the originating knowledge session.");
1158
- }
1159
- const capturedAt = new Date().toISOString();
1160
- appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, cindyCapture.taskConclusions, capturedAt);
1161
- return {
1162
- reportCapture: {
1163
- ...queued.reportCapture,
1164
- status: "captured",
1165
- capturedAt,
1166
- messageCount: cindyCapture.taskConclusions.length,
1167
- },
1168
- };
1169
- }
1170
- if (queued.host === "dsh") {
1171
- // The dsh-adapter plugin reads the DSH session log at the terminal
1172
- // plan mutation and writes the extracted final message + task.done
1173
- // conclusions to a capture file (mirroring the Codex transcript
1174
- // hand-off). Only conclusions from THIS plan's window are used:
1175
- // the job's reportCapture.startedAt (registry activeStartedAt) is
1176
- // the authoritative window start, and each conclusion carries its
1177
- // event time.
1178
- const capture = readDshKnowledgeCapture(queued.sessionId);
1179
- if (!capture || capture.sessionId !== queued.sessionId) {
1180
- throw new Error(`DSH report capture is unavailable for knowledge session ${queued.sessionId}.`);
1181
- }
1182
- const startedAtMs = queued.reportCapture?.startedAt
1183
- ? Date.parse(queued.reportCapture.startedAt)
1184
- : Number.NaN;
1185
- const conclusions = (capture.taskConclusions ?? []).filter((conclusion) => {
1186
- const time = conclusion.time;
1187
- if (Number.isFinite(startedAtMs) && typeof time === "number") {
1188
- return time >= startedAtMs;
1189
- }
1190
- return true;
1191
- });
1192
- const capturedAt = new Date().toISOString();
1193
- appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, conclusions, capturedAt);
1194
- return {
1195
- reportCapture: {
1196
- ...queued.reportCapture,
1197
- status: "captured",
1198
- capturedAt,
1199
- messageCount: conclusions.length,
1200
- },
1201
- };
1202
- }
1203
- if (queued.host == null) {
1204
- // A job created by a pre-dsh-host build has host null. The
1205
- // dsh-adapter plugin is the only writer of the dsh-capture file,
1206
- // so its presence proves this is a DSH-originated session; route it
1207
- // through the dsh branch instead of failing as "host unknown".
1208
- const capture = readDshKnowledgeCapture(queued.sessionId);
1209
- if (capture && capture.sessionId === queued.sessionId) {
1210
- const startedAtMs = queued.reportCapture?.startedAt
1211
- ? Date.parse(queued.reportCapture.startedAt)
1212
- : Number.NaN;
1213
- const conclusions = (capture.taskConclusions ?? []).filter((conclusion) => {
1214
- const time = conclusion.time;
1215
- if (Number.isFinite(startedAtMs) && typeof time === "number") {
1216
- return time >= startedAtMs;
1217
- }
1218
- return true;
1219
- });
1220
- const capturedAt = new Date().toISOString();
1221
- appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, conclusions, capturedAt);
1222
- return {
1223
- reportCapture: {
1224
- ...queued.reportCapture,
1225
- status: "captured",
1226
- capturedAt,
1227
- messageCount: conclusions.length,
1228
- },
1229
- };
1230
- }
1231
- }
1232
- if (queued.host !== "codex") {
1133
+ if (queued.host !== "codex" && queued.host !== "dsh" && queued.host !== "cindy") {
1233
1134
  throw new Error(`Claim-time report capture is unavailable for host ${queued.host ?? "unknown"}.`);
1234
1135
  }
1235
- const transcriptPath = findCodexTranscriptPath(queued.sessionId);
1236
- if (!transcriptPath) {
1237
- throw new Error(`Codex transcript is unavailable for knowledge session ${queued.sessionId}.`);
1238
- }
1239
- const conclusions = extractTaskDoneConclusions(transcriptPath, undefined, queued.reportCapture.startedAt, queued.planPath);
1240
- const capturedAt = new Date().toISOString();
1241
- appendKnowledgeTaskConclusions(queued.reportPath, queued.sessionId, conclusions, capturedAt);
1136
+ collectReport({
1137
+ host: queued.host,
1138
+ sessionId: queued.sessionId,
1139
+ projectRoot: queued.projectRoot,
1140
+ planPath: queued.planPath,
1141
+ canonicalReportPath: queued.reportPath,
1142
+ startedAt: queued.reportCapture.startedAt,
1143
+ });
1242
1144
  return {
1243
1145
  reportCapture: {
1244
1146
  ...queued.reportCapture,
1245
1147
  status: "captured",
1246
- capturedAt,
1247
- transcriptPath,
1248
- messageCount: conclusions.length,
1148
+ capturedAt: new Date().toISOString(),
1249
1149
  },
1250
1150
  };
1251
1151
  },
@@ -2237,16 +2137,14 @@ async function runStopHook(effectiveHost) {
2237
2137
  const hookCwd = resolveHookCwd(payload);
2238
2138
  const sessionId = resolveOwnerSessionKey(payload);
2239
2139
  const turnId = readHookString(payload, "turn_id");
2240
- const transcriptPath = readHookString(payload, "transcript_path");
2241
2140
  const payloadMessage = readHookString(payload, "message");
2242
2141
  if (!hookCwd || !sessionId || !turnId || !containsClawDir(hookCwd)) {
2243
2142
  return;
2244
2143
  }
2245
2144
  try {
2246
2145
  const project = resolveProjectContext(hookCwd);
2247
- // Codex passes a transcript_path; opencode and other hosts without a file
2248
- // transcript pass the final assistant message inline. Either source is valid.
2249
- const message = payloadMessage ?? (transcriptPath ? extractLatestFinalAssistantMessage(transcriptPath, turnId) : null);
2146
+ // Host adapters own history parsing and pass their current final inline.
2147
+ const message = payloadMessage;
2250
2148
  if (!message) {
2251
2149
  return;
2252
2150
  }
@@ -2256,7 +2154,7 @@ async function runStopHook(effectiveHost) {
2256
2154
  turnId,
2257
2155
  message,
2258
2156
  host: effectiveHost,
2259
- taskConclusions: transcriptPath ? extractTaskDoneConclusions(transcriptPath, turnId) : [],
2157
+ taskConclusions: [],
2260
2158
  });
2261
2159
  // Current named hosts own their runner in their adapters. Keep the CLI
2262
2160
  // launcher only for jobs written by pre-adapter releases with no host.
@@ -3177,7 +3075,11 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
3177
3075
  const hostActions = hostActionsResult ? buildCodexHostActions(result, { forceProjectionSync, actionIdPrefix: command === "plan.sync" ? `plan.sync:${createHash("sha256").update(result.planPath).digest("hex").slice(0, 16)}` : undefined }) : [];
3178
3076
  const nextsteps = [
3179
3077
  ...result.workflowGuidance.nextsteps,
3180
- ...(knowledgeDispatch
3078
+ // Manual-dispatch instruction for hosts WITHOUT automatic writer
3079
+ // dispatch (codex/cindy/opencode). DSH's adapter auto-dispatches the
3080
+ // writer subagent inside claw_run, so the model must NOT re-dispatch;
3081
+ // injecting this line there would ask it to duplicate the dispatch.
3082
+ ...(knowledgeDispatch && effectiveHost !== "dsh"
3181
3083
  ? ["A knowledgeDispatch is present: dispatch its unchanged prompt through the current Host's designated knowledge-finalizer now, then immediately end the Lead turn; do not wait for or poll that worker."]
3182
3084
  : []),
3183
3085
  ];