@veewo/claw 0.1.78 → 0.1.80

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
@@ -5,15 +5,18 @@ import path from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { createHash } from "node:crypto";
7
7
  import { spawn, spawnSync } from "node:child_process";
8
- import { buildDirectWorkflowGuidance, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolveProjectContext, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemory, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, tryCleanupKnowledgeFinalizationReport, writeKnowledgeFinalizationJob, unbindSession, writePlan, } from "@veewo/claw-core";
8
+ import { buildDirectWorkflowGuidance, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemory, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, tryCleanupKnowledgeFinalizationReport, writeKnowledgeFinalizationJob, unbindSession, writePlan, } from "@veewo/claw-core";
9
9
  import { buildCodexDriverEnvelope } from "./codex-driver.js";
10
10
  import { checkCodexRuntime, resolveCodexSdkEntryPath } from "./codex-runtime.js";
11
11
  import { extractLatestFinalAssistantMessage } from "./codex-transcript.js";
12
+ import { consumeBufferedHookInput } from "./knowledge-hook-preflight.js";
13
+ import { resolveInvocationHost, withoutInvocationHost } from "./invocation-host.js";
12
14
  import { runOpencodeKnowledgeWriter } from "./opencode-runner.js";
13
15
  const CLI_VERSION = readCliVersion();
14
16
  const TOP_LEVEL_COMMANDS = [
15
17
  { name: "init [options]", summary: "Initialize and normalize the .claw project surface." },
16
18
  { name: "context [--task <name>]", summary: "Resolve project context, auto-initializing or correcting .claw state." },
19
+ { name: "session clean [--expired]", summary: "Remove current or expired session workflow state." },
17
20
  { name: "check", summary: "Check and auto-correct .claw project protocol fields." },
18
21
  { name: "plan <subcommand> [options]", summary: "Plan lifecycle: create, start, edit, remove, wait, resume, show, done." },
19
22
  { name: "codex driver", summary: "Return the versioned code-mode driver used by the Codex adapter." },
@@ -34,7 +37,7 @@ const COMMAND_HELP = {
34
37
  { flag: "--name <project-name>", detail: "Human-readable project name." },
35
38
  { flag: "--context-path <file>", detail: "Extra context path to track (repeatable)." },
36
39
  { flag: "--ext-path <path>", detail: "External doc path to index (repeatable)." },
37
- { flag: "--external-writer-skill <skill>", detail: "Skill id for the combined knowledge writer." },
40
+ { flag: "--external-writer-skill <skill>", detail: "Skill id override for the combined knowledge-writer pass." },
38
41
  { flag: "--planning true|false", detail: "Enable planning-aware default template behavior (default true)." },
39
42
  { flag: "--external-planning-skill <skill>", detail: "Skill id for an external planning skill." },
40
43
  { flag: "--gitnexus true|false", detail: "Enable GitNexus integration (default false)." },
@@ -49,6 +52,10 @@ const COMMAND_HELP = {
49
52
  { flag: "--task <name>", detail: "Resolve context scoped to a specific task." },
50
53
  ],
51
54
  },
55
+ session: {
56
+ usage: ["{script} session clean", "{script} session clean --expired"],
57
+ description: "Clean ephemeral session-scoped workflow state without touching a project .claw directory.",
58
+ },
52
59
  check: {
53
60
  usage: ["{script} check"],
54
61
  description: "Check the .claw project protocol and auto-correct any missing or malformed fields in project.json. Returns issues found and the paths that were fixed.",
@@ -59,14 +66,15 @@ const COMMAND_HELP = {
59
66
  subcommands: {
60
67
  create: {
61
68
  usage: [
62
- "{script} plan create \"<title>\" [--goal <text>]",
63
- "{script} plan create --title <text> [--goal <text>] [--template <name>]",
69
+ "{script} plan create \"<title>\" [--goal <text>] [--scope session]",
70
+ "{script} plan create --title <text> [--goal <text>] [--template <name>] [--scope session]",
64
71
  ],
65
72
  description: "Create the task scope and initial plan from a template. Uses explicit `--template` first, otherwise the project's configured `defaultPlanTemplate`, and finally falls back to the built-in `default`; planning-enabled projects start in process.discussing with the default planning bridge tasks, while planning-disabled projects start directly in process.active with one executable task.",
66
73
  summary: "Create the task scope and initial plan.",
67
74
  options: [
68
75
  { flag: "--title <text>", detail: "Task title (required unless a positional title is given)." },
69
76
  { flag: "--goal <text>", detail: "Optional goal text." },
77
+ { flag: "--scope session", detail: "Use ephemeral per-session storage and disable project knowledge side effects." },
70
78
  { flag: "--template <name>", detail: "Optional plan template name. Overrides the project's configured default template." },
71
79
  ],
72
80
  },
@@ -336,12 +344,13 @@ const COMMAND_HELP = {
336
344
  async function main() {
337
345
  const args = process.argv.slice(2);
338
346
  const explicitHost = readOptionalFlag(args, "--host");
339
- if (explicitHost) {
340
- if (!new Set(["codex", "opencode"]).has(explicitHost)) {
341
- handleError(new ClawError("PROJECT_CONFIG_INVALID", `Unsupported host "${explicitHost}".`));
342
- return;
343
- }
344
- process.env.CLAW_HOST = explicitHost;
347
+ let effectiveHost;
348
+ try {
349
+ effectiveHost = resolveInvocationHost(explicitHost, process.env.CLAW_HOST);
350
+ }
351
+ catch (error) {
352
+ handleError(error);
353
+ return;
345
354
  }
346
355
  const command = args.shift();
347
356
  if (command === "--help" || command === "-h") {
@@ -383,7 +392,10 @@ async function main() {
383
392
  printJson(initProject(initInput));
384
393
  return;
385
394
  case "context":
386
- printJson(buildPublicContextOutput(await runContextCommand(args)));
395
+ printJson(buildPublicContextOutput(await runContextCommand(args, process.cwd(), resolveOwnerSessionKey(), effectiveHost)));
396
+ return;
397
+ case "session":
398
+ runSession(args);
387
399
  return;
388
400
  case "check":
389
401
  const checkResult = ensureProjectProtocol(process.cwd());
@@ -398,7 +410,7 @@ async function main() {
398
410
  });
399
411
  return;
400
412
  case "plan":
401
- await runPlan(args);
413
+ await runPlan(args, effectiveHost);
402
414
  return;
403
415
  case "codex":
404
416
  runCodex(args);
@@ -407,10 +419,10 @@ async function main() {
407
419
  await runTemplate(args);
408
420
  return;
409
421
  case "task":
410
- await runTask(args);
422
+ await runTask(args, effectiveHost);
411
423
  return;
412
424
  case "subplan":
413
- await runSubplan(args);
425
+ await runSubplan(args, effectiveHost);
414
426
  return;
415
427
  case "switch-task":
416
428
  printJson(switchTask({
@@ -426,13 +438,13 @@ async function main() {
426
438
  runSearch(args);
427
439
  return;
428
440
  case "direct":
429
- runDirect(args);
441
+ runDirect(args, effectiveHost);
430
442
  return;
431
443
  case "truth":
432
444
  runTruth(args);
433
445
  return;
434
446
  case "hook":
435
- await runHook(args);
447
+ await runHook(args, effectiveHost);
436
448
  return;
437
449
  case "help":
438
450
  printHelp(args);
@@ -460,13 +472,36 @@ function runCodex(args) {
460
472
  assertNoRemainingArgs(args, "codex driver");
461
473
  printJson(buildCodexDriverEnvelope(CLI_VERSION));
462
474
  }
463
- async function runPlan(args) {
475
+ function runSession(args) {
476
+ const subcommand = args.shift();
477
+ if (subcommand !== "clean") {
478
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown session subcommand "${subcommand ?? ""}".`);
479
+ }
480
+ const expired = readBooleanFlag(args, "--expired");
481
+ assertNoRemainingArgs(args, "session clean");
482
+ if (expired) {
483
+ const removed = sweepExpiredSessionWorkflows();
484
+ printJson({ ok: true, command: "session.clean", expired: true, removedCount: removed.length, removed });
485
+ return;
486
+ }
487
+ const ownerSessionKey = resolveOwnerSessionKey();
488
+ if (!ownerSessionKey) {
489
+ throw new ClawError("PROJECT_CONFIG_INVALID", "session clean requires a platform session id.");
490
+ }
491
+ printJson({
492
+ ok: true,
493
+ command: "session.clean",
494
+ removed: deleteSessionWorkflow(ownerSessionKey),
495
+ });
496
+ }
497
+ async function runPlan(args, effectiveHost) {
464
498
  const subcommand = args.shift();
465
499
  switch (subcommand) {
466
500
  case "create":
467
501
  rejectFlags(args, ["--task", "--plan", "--content", "--status", "--parent-task-id", "--description"]);
468
502
  const explicitTitle = readOptionalFlag(args, "--title");
469
503
  const explicitTemplate = readOptionalFlag(args, "--template");
504
+ const scope = readWorkflowScope(args);
470
505
  const title = explicitTitle ?? readOptionalPositionalArg(args);
471
506
  const templateName = explicitTemplate;
472
507
  if (!title) {
@@ -474,14 +509,15 @@ async function runPlan(args) {
474
509
  }
475
510
  const result = await writePlan({
476
511
  cwd: process.cwd(),
512
+ scope,
477
513
  templateName,
478
514
  title,
479
515
  goalText: readOptionalFlag(args, "--goal"),
480
516
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
481
- host: process.env.CLAW_HOST ?? undefined,
517
+ host: effectiveHost,
482
518
  });
483
519
  assertNoRemainingArgs(args, "plan create");
484
- printJson(compactPlanCommandResult("plan.create", result));
520
+ printJson(compactPlanCommandResult("plan.create", result, effectiveHost));
485
521
  return;
486
522
  case "edit": {
487
523
  const target = readPlanMutationTarget(args);
@@ -494,10 +530,10 @@ async function runPlan(args) {
494
530
  ...target,
495
531
  operations,
496
532
  commandSource: "plan.edit",
497
- host: process.env.CLAW_HOST ?? undefined,
533
+ host: effectiveHost,
498
534
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
499
535
  });
500
- printJson(compactPlanCommandResult("plan.edit", result));
536
+ printJson(compactPlanCommandResult("plan.edit", result, effectiveHost));
501
537
  if (result.operationChain?.status === "partial")
502
538
  process.exitCode = 1;
503
539
  return;
@@ -514,17 +550,17 @@ async function runPlan(args) {
514
550
  ...target,
515
551
  updates,
516
552
  commandSource: "plan.edit",
517
- host: process.env.CLAW_HOST ?? undefined,
553
+ host: effectiveHost,
518
554
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
519
555
  });
520
- printJson(compactPlanCommandResult("plan.remove", result));
556
+ printJson(compactPlanCommandResult("plan.remove", result, effectiveHost));
521
557
  return;
522
558
  }
523
559
  case "wait":
524
- await runPlanStatusAlias(args, "process.wait", "plan.wait");
560
+ await runPlanStatusAlias(args, "process.wait", "plan.wait", effectiveHost);
525
561
  return;
526
562
  case "resume":
527
- await runPlanStatusAlias(args, "process.active", "plan.resume");
563
+ await runPlanStatusAlias(args, "process.active", "plan.resume", effectiveHost);
528
564
  return;
529
565
  case "start": {
530
566
  const updates = readPlanFieldUpdates(args);
@@ -542,10 +578,10 @@ async function runPlan(args) {
542
578
  planStatus: "process.active",
543
579
  completeLifecycleBridge: true,
544
580
  commandSource: "plan.start",
545
- host: process.env.CLAW_HOST ?? undefined,
581
+ host: effectiveHost,
546
582
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
547
583
  });
548
- printJson(compactPlanCommandResult("plan.start", result));
584
+ printJson(compactPlanCommandResult("plan.start", result, effectiveHost));
549
585
  return;
550
586
  }
551
587
  case "done": {
@@ -562,22 +598,28 @@ async function runPlan(args) {
562
598
  };
563
599
  const target = readPlanMutationTarget(args);
564
600
  assertNoRemainingArgs(args, "plan done");
565
- const gitNexusPreflightAnalyzed = ensureGitNexusReadyForPlanDone(process.cwd());
601
+ const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
602
+ const workflowProject = resolveWorkflowProjectContext(process.cwd(), ownerSessionKey);
603
+ const gitNexusPreflightAnalyzed = workflowProject.scope === "project"
604
+ ? ensureGitNexusReadyForPlanDone(process.cwd())
605
+ : false;
566
606
  const result = await editPlan({
567
607
  cwd: process.cwd(),
568
608
  ...target,
569
609
  updates,
570
610
  planStatus: "end.completed",
571
611
  commandSource: "plan.done",
572
- host: process.env.CLAW_HOST ?? undefined,
573
- ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
574
- });
575
- const completionRefresh = queueCompletionRefresh({
576
- cwd: process.cwd(),
577
- taskName: result.taskName,
578
- skipGitNexusRefresh: gitNexusPreflightAnalyzed,
612
+ host: effectiveHost,
613
+ ownerSessionKey,
579
614
  });
580
- printJson(compactPlanCommandResult("plan.done", result, completionRefresh));
615
+ const completionRefresh = workflowProject.scope === "project"
616
+ ? queueCompletionRefresh({
617
+ cwd: process.cwd(),
618
+ taskName: result.taskName,
619
+ skipGitNexusRefresh: gitNexusPreflightAnalyzed,
620
+ })
621
+ : undefined;
622
+ printJson(compactPlanCommandResult("plan.done", result, effectiveHost, completionRefresh));
581
623
  return;
582
624
  }
583
625
  case "show": {
@@ -586,6 +628,7 @@ async function runPlan(args) {
586
628
  const result = showPlan({
587
629
  cwd: process.cwd(),
588
630
  ...target,
631
+ ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
589
632
  });
590
633
  printJson({
591
634
  ok: true,
@@ -603,17 +646,17 @@ async function runPlan(args) {
603
646
  throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown plan subcommand "${subcommand ?? ""}".`);
604
647
  }
605
648
  }
606
- async function runPlanStatusAlias(args, planStatus, command) {
649
+ async function runPlanStatusAlias(args, planStatus, command, effectiveHost) {
607
650
  const result = await editPlan({
608
651
  cwd: process.cwd(),
609
652
  ...readPlanMutationTarget(args),
610
653
  planStatus,
611
654
  commandSource: "plan.edit",
612
- host: process.env.CLAW_HOST ?? undefined,
655
+ host: effectiveHost,
613
656
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
614
657
  });
615
658
  assertNoRemainingArgs(args, command);
616
- printJson(compactPlanCommandResult(command, result));
659
+ printJson(compactPlanCommandResult(command, result, effectiveHost));
617
660
  }
618
661
  async function runTemplate(args) {
619
662
  const subcommand = args.shift();
@@ -643,6 +686,7 @@ async function runTemplate(args) {
643
686
  command: "template.validate",
644
687
  ok: true,
645
688
  templateId: template.id,
689
+ ...(template.scope ? { scope: template.scope } : {}),
646
690
  source: template.source,
647
691
  ...(template.templatePath ? { templatePath: template.templatePath } : {}),
648
692
  status: template.status,
@@ -660,7 +704,7 @@ async function runTemplate(args) {
660
704
  });
661
705
  }
662
706
  }
663
- async function runTask(args) {
707
+ async function runTask(args, effectiveHost) {
664
708
  const subcommand = args.shift();
665
709
  switch (subcommand) {
666
710
  case "add": {
@@ -671,10 +715,10 @@ async function runTask(args) {
671
715
  ...target,
672
716
  operations,
673
717
  commandSource: "plan.edit",
674
- host: process.env.CLAW_HOST ?? undefined,
718
+ host: effectiveHost,
675
719
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
676
720
  });
677
- printJson(compactPlanCommandResult("task.add", result));
721
+ printJson(compactPlanCommandResult("task.add", result, effectiveHost));
678
722
  if (result.operationChain?.status === "partial")
679
723
  process.exitCode = 1;
680
724
  return;
@@ -687,10 +731,10 @@ async function runTask(args) {
687
731
  ...target,
688
732
  operations,
689
733
  commandSource: "plan.edit",
690
- host: process.env.CLAW_HOST ?? undefined,
734
+ host: effectiveHost,
691
735
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
692
736
  });
693
- printJson(compactPlanCommandResult("task.edit", result));
737
+ printJson(compactPlanCommandResult("task.edit", result, effectiveHost));
694
738
  if (result.operationChain?.status === "partial")
695
739
  process.exitCode = 1;
696
740
  return;
@@ -703,10 +747,10 @@ async function runTask(args) {
703
747
  ...target,
704
748
  operations,
705
749
  commandSource: "plan.edit",
706
- host: process.env.CLAW_HOST ?? undefined,
750
+ host: effectiveHost,
707
751
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
708
752
  });
709
- printJson(compactPlanCommandResult("task.remove", result));
753
+ printJson(compactPlanCommandResult("task.remove", result, effectiveHost));
710
754
  if (result.operationChain?.status === "partial")
711
755
  process.exitCode = 1;
712
756
  return;
@@ -718,10 +762,10 @@ async function runTask(args) {
718
762
  cwd: process.cwd(),
719
763
  ...target,
720
764
  operations,
721
- host: process.env.CLAW_HOST ?? undefined,
765
+ host: effectiveHost,
722
766
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
723
767
  });
724
- printJson(compactPlanCommandResult("task.done", result));
768
+ printJson(compactPlanCommandResult("task.done", result, effectiveHost));
725
769
  if (result.operationChain?.status === "partial")
726
770
  process.exitCode = 1;
727
771
  return;
@@ -763,7 +807,7 @@ function runSearch(args) {
763
807
  }),
764
808
  });
765
809
  }
766
- function runDirect(args) {
810
+ function runDirect(args, effectiveHost) {
767
811
  assertNoRemainingArgs(args, "direct");
768
812
  const completionRefresh = queueCompletionRefresh({
769
813
  cwd: process.cwd(),
@@ -774,14 +818,29 @@ function runDirect(args) {
774
818
  });
775
819
  printJson(compactDirectCommandResult("direct", buildDirectWorkflowGuidance({
776
820
  projectConfig: resolveProjectContext(process.cwd()).projectConfig,
777
- host: process.env.CLAW_HOST ?? undefined,
821
+ host: effectiveHost,
778
822
  }), completionRefresh));
779
823
  }
780
- async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = resolveOwnerSessionKey()) {
824
+ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = resolveOwnerSessionKey(), effectiveHost) {
781
825
  const taskName = readOptionalFlag(args, "--task");
782
826
  let initialized = false;
783
827
  let corrected = false;
784
828
  let fixedPaths = [];
829
+ const sessionProject = resolveSessionWorkflowContext(ownerSessionKey ?? undefined);
830
+ if (sessionProject) {
831
+ const activeWorkflow = !taskName && ownerSessionKey
832
+ ? await tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey, effectiveHost)
833
+ : null;
834
+ const codexRuntime = effectiveHost === "codex" ? checkCodexRuntime() : null;
835
+ const codexRuntimeError = codexRuntime && !codexRuntime.ok
836
+ ? buildCodexRuntimeError(codexRuntime.detail)
837
+ : null;
838
+ return {
839
+ project: sessionProject,
840
+ ...(activeWorkflow ? { activeWorkflow } : {}),
841
+ ...(codexRuntimeError ? { error: codexRuntimeError } : {}),
842
+ };
843
+ }
785
844
  try {
786
845
  const ensureResult = ensureProjectProtocol(cwd);
787
846
  corrected = ensureResult.changed;
@@ -804,9 +863,9 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
804
863
  resolved = resolveContext(cwd, taskName);
805
864
  }
806
865
  const activeWorkflow = !taskName && ownerSessionKey
807
- ? await tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey)
866
+ ? await tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey, effectiveHost)
808
867
  : null;
809
- const codexRuntime = process.env.CLAW_HOST === "codex" ? checkCodexRuntime() : null;
868
+ const codexRuntime = effectiveHost === "codex" ? checkCodexRuntime() : null;
810
869
  const codexRuntimeError = codexRuntime && !codexRuntime.ok
811
870
  ? buildCodexRuntimeError(codexRuntime.detail)
812
871
  : null;
@@ -840,6 +899,7 @@ function buildPublicContextOutput(context) {
840
899
  const output = {};
841
900
  if (project) {
842
901
  output.project = {
902
+ ...(project.scope === "session" ? { scope: "session" } : {}),
843
903
  projectRoot: project.projectRoot,
844
904
  clawDir: project.clawDir,
845
905
  projectId: project.projectId,
@@ -854,6 +914,12 @@ function buildPublicContextOutput(context) {
854
914
  if (context.activeWorkflow !== undefined) {
855
915
  output.activeWorkflow = context.activeWorkflow;
856
916
  }
917
+ else {
918
+ output.session = {
919
+ boundPlan: false,
920
+ note: "No plan is bound to this session yet. Ask the user for the task scope, or run `claw plan create` when ready to start one.",
921
+ };
922
+ }
857
923
  if (context.error !== undefined) {
858
924
  output.error = context.error;
859
925
  }
@@ -1011,17 +1077,17 @@ function runTruth(args) {
1011
1077
  throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown truth subcommand "${subcommand ?? ""}".`);
1012
1078
  }
1013
1079
  }
1014
- async function runHook(args) {
1080
+ async function runHook(args, effectiveHost) {
1015
1081
  const eventName = args.shift();
1016
1082
  if (!eventName) {
1017
1083
  throw new ClawError("PROJECT_CONFIG_INVALID", "claw hook requires an event name.");
1018
1084
  }
1019
1085
  if (eventName === "SessionStart" || eventName === "auto-claw") {
1020
- await runSessionStartHook();
1086
+ await runSessionStartHook(effectiveHost);
1021
1087
  return;
1022
1088
  }
1023
1089
  if (eventName === "Stop" || eventName === "auto-doc") {
1024
- await runStopHook();
1090
+ await runStopHook(effectiveHost);
1025
1091
  return;
1026
1092
  }
1027
1093
  const project = tryResolveHookProject(process.cwd());
@@ -1058,7 +1124,7 @@ async function runHook(args) {
1058
1124
  logPath,
1059
1125
  });
1060
1126
  }
1061
- async function runStopHook() {
1127
+ async function runStopHook(effectiveHost) {
1062
1128
  if (process.env.CLAW_KNOWLEDGE_FINALIZER === "1") {
1063
1129
  return;
1064
1130
  }
@@ -1084,7 +1150,7 @@ async function runStopHook() {
1084
1150
  sessionId,
1085
1151
  turnId,
1086
1152
  message,
1087
- host: process.env.CLAW_HOST,
1153
+ host: effectiveHost,
1088
1154
  });
1089
1155
  if (result.ok && result.jobPath && process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_LAUNCH !== "1") {
1090
1156
  launchKnowledgeFinalizationWorker(result.jobPath, project.projectRoot);
@@ -1101,8 +1167,8 @@ async function runInternalKnowledgeFinalize(args) {
1101
1167
  return;
1102
1168
  }
1103
1169
  try {
1104
- const run = await runKnowledgeWriterForJob(running);
1105
1170
  const project = resolveProjectContext(running.projectRoot);
1171
+ const writerRun = await runKnowledgeWriterForJob(running);
1106
1172
  const truthEncoding = normalizeTruthMarkdownEncoding(project);
1107
1173
  queueCompletionRefresh({
1108
1174
  cwd: running.projectRoot,
@@ -1116,8 +1182,8 @@ async function runInternalKnowledgeFinalize(args) {
1116
1182
  ...running,
1117
1183
  status: "succeeded",
1118
1184
  finishedAt: new Date().toISOString(),
1119
- ...(run.threadId ? { sdkThreadId: run.threadId } : {}),
1120
- finalResponse: run.finalResponse,
1185
+ ...(writerRun.threadId ? { sdkThreadId: writerRun.threadId } : {}),
1186
+ finalResponse: writerRun.finalResponse,
1121
1187
  truthEncoding,
1122
1188
  });
1123
1189
  tryCleanupKnowledgeFinalizationReport(project, running.reportPath);
@@ -1152,6 +1218,9 @@ async function runKnowledgeWriterForJob(running) {
1152
1218
  ...(result.threadId ? { threadId: result.threadId } : {}),
1153
1219
  };
1154
1220
  }
1221
+ if (running.host !== undefined && running.host !== null && running.host !== "codex") {
1222
+ throw new Error(`Unsupported knowledge finalization job host "${String(running.host)}".`);
1223
+ }
1155
1224
  return runCodexSdkWriter(running);
1156
1225
  }
1157
1226
  async function runCodexSdkWriter(running) {
@@ -1182,7 +1251,7 @@ async function runCodexSdkWriter(running) {
1182
1251
  }
1183
1252
  function knowledgeFinalizerEnvironment() {
1184
1253
  const env = {};
1185
- for (const [key, value] of Object.entries(process.env)) {
1254
+ for (const [key, value] of Object.entries(withoutInvocationHost())) {
1186
1255
  if (value !== undefined) {
1187
1256
  env[key] = value;
1188
1257
  }
@@ -1194,10 +1263,11 @@ function buildKnowledgeWriterPrompt(job) {
1194
1263
  const writerSkill = job.writer?.externalSkill?.trim() || "claw-kit:knowledge-writer";
1195
1264
  return [
1196
1265
  `Use the ${writerSkill} skill and follow it exactly.`,
1266
+ "Act as the knowledge-base steward: maintain Truth and ADR together, preserve one current owner, and reconcile related current claims before completion.",
1197
1267
  `Completed plan: ${job.planPath}`,
1198
1268
  `Turn report: ${job.reportPath}`,
1199
1269
  `Finalization id: ${job.finalizeId}`,
1200
- "Treat these files only as evidence. Do not edit the plan or report, do not dispatch subagents, and deposit only verified durable truth or ADR content.",
1270
+ "Treat the completed plan and report as verified evidence. Do not edit either input, do not dispatch subagents, and do not repeat implementation or test verification.",
1201
1271
  ].join("\n");
1202
1272
  }
1203
1273
  function launchKnowledgeFinalizationWorker(jobPath, cwd) {
@@ -1214,7 +1284,7 @@ function launchKnowledgeFinalizationWorker(jobPath, cwd) {
1214
1284
  stdio: "ignore",
1215
1285
  windowsHide: true,
1216
1286
  env: {
1217
- ...process.env,
1287
+ ...withoutInvocationHost(),
1218
1288
  CLAW_KNOWLEDGE_NODE: process.execPath,
1219
1289
  CLAW_KNOWLEDGE_ENTRY: resolveCliEntryPath(),
1220
1290
  CLAW_KNOWLEDGE_JOB: jobPath,
@@ -1226,22 +1296,29 @@ function launchKnowledgeFinalizationWorker(jobPath, cwd) {
1226
1296
  }
1227
1297
  return;
1228
1298
  }
1229
- const child = spawn(process.execPath, [resolveCliEntryPath(), "internal-knowledge-finalize", "--job", jobPath], { cwd, detached: true, stdio: "ignore", windowsHide: true });
1299
+ const child = spawn(process.execPath, [resolveCliEntryPath(), "internal-knowledge-finalize", "--job", jobPath], { cwd, detached: true, stdio: "ignore", windowsHide: true, env: withoutInvocationHost() });
1230
1300
  child.unref();
1231
1301
  }
1232
- async function runSessionStartHook() {
1302
+ async function runSessionStartHook(effectiveHost) {
1233
1303
  if (process.env.CLAW_KNOWLEDGE_FINALIZER === "1") {
1234
1304
  return;
1235
1305
  }
1236
1306
  const payload = await readStdinJson();
1237
1307
  const hookCwd = resolveHookCwd(payload);
1238
1308
  const ownerSessionKey = resolveOwnerSessionKey(payload);
1239
- if (!hookCwd || !containsClawDir(hookCwd)) {
1309
+ if (!hookCwd) {
1310
+ return;
1311
+ }
1312
+ const sessionProject = resolveSessionWorkflowContext(ownerSessionKey ?? undefined);
1313
+ if (!containsClawDir(hookCwd) && !sessionProject) {
1240
1314
  return;
1241
1315
  }
1242
1316
  try {
1243
- const context = await runContextCommand([], hookCwd, ownerSessionKey);
1244
- if (!context.error && process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_LAUNCH !== "1") {
1317
+ const context = await runContextCommand([], hookCwd, ownerSessionKey, effectiveHost);
1318
+ const contextProject = asJsonRecord(context.project);
1319
+ if (contextProject?.scope !== "session"
1320
+ && !context.error
1321
+ && process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_LAUNCH !== "1") {
1245
1322
  const project = resolveProjectContext(hookCwd);
1246
1323
  for (const jobPath of listRetryableKnowledgeFinalizationJobs(project)) {
1247
1324
  try {
@@ -1499,8 +1576,8 @@ function summarizeRecoveredPlanContent(planContent) {
1499
1576
  }
1500
1577
  return lines.length > 0 ? lines : ["- plan content present in activeWorkflow.planContent JSON."];
1501
1578
  }
1502
- async function tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey) {
1503
- const project = resolveProjectContext(cwd);
1579
+ async function tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey, effectiveHost) {
1580
+ const project = resolveWorkflowProjectContext(cwd, ownerSessionKey);
1504
1581
  const planPath = resolveSessionBoundPlan(project, ownerSessionKey);
1505
1582
  if (!planPath) {
1506
1583
  return null;
@@ -1518,6 +1595,7 @@ async function tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey) {
1518
1595
  cwd,
1519
1596
  taskName,
1520
1597
  planFile,
1598
+ ownerSessionKey,
1521
1599
  });
1522
1600
  if (result.plan.status.startsWith("end.")) {
1523
1601
  unbindSession(project, ownerSessionKey);
@@ -1536,6 +1614,7 @@ async function tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey) {
1536
1614
  plan: result.plan,
1537
1615
  projectRoot: project.projectRoot,
1538
1616
  projectConfig: project.projectConfig,
1617
+ host: effectiveHost,
1539
1618
  }),
1540
1619
  };
1541
1620
  }
@@ -1544,7 +1623,7 @@ async function tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey) {
1544
1623
  return null;
1545
1624
  }
1546
1625
  }
1547
- async function runSubplan(args) {
1626
+ async function runSubplan(args, effectiveHost) {
1548
1627
  const subcommand = args.shift();
1549
1628
  switch (subcommand) {
1550
1629
  case "create": {
@@ -1554,9 +1633,10 @@ async function runSubplan(args) {
1554
1633
  parentTaskId: readOptionalNumber(args, "--task-id") ?? failMissingNumericFlag("--task-id"),
1555
1634
  templateName: readOptionalFlag(args, "--template") ?? undefined,
1556
1635
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1636
+ host: effectiveHost,
1557
1637
  });
1558
1638
  assertNoRemainingArgs(args, "subplan create");
1559
- printJson(compactPlanCommandResult("subplan.create", result));
1639
+ printJson(compactPlanCommandResult("subplan.create", result, effectiveHost));
1560
1640
  return;
1561
1641
  }
1562
1642
  default:
@@ -1601,11 +1681,14 @@ function summarizeGoalMode(value) {
1601
1681
  return value.recommendedObjective.trim();
1602
1682
  }
1603
1683
  async function readStdinJson() {
1684
+ const bufferedInput = consumeBufferedHookInput();
1604
1685
  const chunks = [];
1605
- for await (const chunk of process.stdin) {
1606
- chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
1686
+ if (bufferedInput === null) {
1687
+ for await (const chunk of process.stdin) {
1688
+ chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
1689
+ }
1607
1690
  }
1608
- const raw = chunks.join("").trim();
1691
+ const raw = (bufferedInput ?? chunks.join("")).trim();
1609
1692
  if (!raw) {
1610
1693
  return null;
1611
1694
  }
@@ -1622,14 +1705,14 @@ function readJson(filePath) {
1622
1705
  function stripBom(content) {
1623
1706
  return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
1624
1707
  }
1625
- function compactPlanCommandResult(command, result, completionRefresh) {
1708
+ function compactPlanCommandResult(command, result, effectiveHost, completionRefresh) {
1626
1709
  const archivedPlanPath = completionRefresh?.taskRetention.archivedCurrentTask?.taskName === result.taskName &&
1627
1710
  completionRefresh.taskRetention.archivedCurrentTask.archivedPlanPath
1628
1711
  ? completionRefresh.taskRetention.archivedCurrentTask.archivedPlanPath
1629
1712
  : undefined;
1630
1713
  const resolvedPlanPath = archivedPlanPath ?? result.planPath;
1631
- const hostActions = buildHostActions(result);
1632
- const codexResult = process.env.CLAW_HOST === "codex";
1714
+ const codexResult = effectiveHost === "codex";
1715
+ const hostActions = codexResult ? buildHostActions(result) : [];
1633
1716
  return {
1634
1717
  ok: true,
1635
1718
  command,
@@ -1637,8 +1720,6 @@ function compactPlanCommandResult(command, result, completionRefresh) {
1637
1720
  ...(archivedPlanPath ? { archivedPlanPath } : {}),
1638
1721
  planStatus: result.planStatus,
1639
1722
  ...(!codexResult && result.previousPlanStatus ? { previousPlanStatus: result.previousPlanStatus } : {}),
1640
- ...(!codexResult && result.emittedEvents?.length ? { emittedEvents: result.emittedEvents } : {}),
1641
- ...(!codexResult && result.events?.length ? { events: result.events } : {}),
1642
1723
  ...(hostActions.length ? { hostActions } : {}),
1643
1724
  ...(!codexResult && result.changedTaskIds?.length ? { changedTaskIds: result.changedTaskIds } : {}),
1644
1725
  ...(!codexResult && result.appendedTaskIds?.length ? { appendedTaskIds: result.appendedTaskIds } : {}),
@@ -1944,7 +2025,7 @@ function readPlanMutationTarget(args) {
1944
2025
  ...(explicitPlanFile ? { planFile: explicitPlanFile } : {}),
1945
2026
  };
1946
2027
  }
1947
- const project = resolveProjectContext(process.cwd());
2028
+ const project = resolveWorkflowProjectContext(process.cwd(), resolveOwnerSessionKey() ?? undefined);
1948
2029
  const boundPlanPath = resolveSessionBoundPlan(project, resolveOwnerSessionKey() ?? undefined);
1949
2030
  if (!boundPlanPath) {
1950
2031
  throw new ClawError("PROJECT_CONFIG_INVALID", "No plan is bound to the current session. Create or recover a plan first, or use --task-name and optional --plan-file as an advanced override.");
@@ -2098,7 +2179,7 @@ function launchCompletionRefreshWorker(input) {
2098
2179
  stdio: "ignore",
2099
2180
  windowsHide: true,
2100
2181
  env: {
2101
- ...process.env,
2182
+ ...withoutInvocationHost(),
2102
2183
  CLAW_COMPLETION_NODE: process.execPath,
2103
2184
  CLAW_COMPLETION_ENTRY: resolveCliEntryPath(),
2104
2185
  CLAW_COMPLETION_CWD: input.cwd,
@@ -2134,6 +2215,7 @@ function launchCompletionRefreshWorker(input) {
2134
2215
  detached: true,
2135
2216
  stdio: "ignore",
2136
2217
  windowsHide: true,
2218
+ env: withoutInvocationHost(),
2137
2219
  });
2138
2220
  child.unref();
2139
2221
  }
@@ -2776,6 +2858,18 @@ function normalizeVersionString(value) {
2776
2858
  const trimmed = value.trim();
2777
2859
  return trimmed ? trimmed : null;
2778
2860
  }
2861
+ function readWorkflowScope(args) {
2862
+ const value = readOptionalFlag(args, "--scope");
2863
+ if (value === undefined) {
2864
+ return undefined;
2865
+ }
2866
+ if (value === "session") {
2867
+ return "session";
2868
+ }
2869
+ throw new ClawError("PROJECT_CONFIG_INVALID", "--scope currently accepts only session; omit it for project scope.", {
2870
+ scope: value,
2871
+ });
2872
+ }
2779
2873
  function readOptionalFlag(args, flag) {
2780
2874
  const index = args.indexOf(flag);
2781
2875
  if (index === -1) {