intentdna 1.9.0 → 1.9.3

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.
Files changed (131) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +227 -87
  4. package/dist/cli/commands/run-lifecycle.d.ts +61 -11
  5. package/dist/cli/commands/run-lifecycle.js +184 -50
  6. package/dist/cli/commands/run-observer.d.ts +15 -0
  7. package/dist/cli/commands/run-observer.js +210 -0
  8. package/dist/cli/commands/run.d.ts +16 -20
  9. package/dist/cli/commands/run.js +461 -659
  10. package/dist/cli/commands/sync.js +16 -12
  11. package/dist/cli/index.js +14 -5
  12. package/dist/compiler/compile.d.ts +1 -0
  13. package/dist/compiler/compile.js +1 -1
  14. package/dist/compiler/controller.d.ts +16 -0
  15. package/dist/compiler/controller.js +693 -0
  16. package/dist/compiler/input-resolver.d.ts +2 -0
  17. package/dist/compiler/input-resolver.js +22 -6
  18. package/dist/hooks/cli.d.ts +45 -0
  19. package/dist/hooks/cli.js +263 -31
  20. package/dist/hooks/host-event.d.ts +57 -0
  21. package/dist/hooks/host-event.js +187 -0
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.js +1 -0
  24. package/dist/hooks/protocol.d.ts +7 -0
  25. package/dist/hooks/schema.d.ts +1 -0
  26. package/dist/hooks/schema.js +89 -1
  27. package/dist/hooks/state.d.ts +23 -1
  28. package/dist/hooks/state.js +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-run.d.ts +4 -0
  33. package/dist/mcp/tools-run.js +419 -0
  34. package/dist/mcp/tools-state.d.ts +1 -1
  35. package/dist/mcp/tools-state.js +13 -241
  36. package/dist/runtime/artifact-store.d.ts +146 -0
  37. package/dist/runtime/artifact-store.js +1436 -0
  38. package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
  39. package/dist/runtime/canonical-attempt-outcome.js +195 -0
  40. package/dist/runtime/canonical-json.d.ts +10 -0
  41. package/dist/runtime/canonical-json.js +105 -0
  42. package/dist/runtime/canonical-run-application.d.ts +93 -0
  43. package/dist/runtime/canonical-run-application.js +229 -0
  44. package/dist/runtime/canonical-run-service.d.ts +454 -0
  45. package/dist/runtime/canonical-run-service.js +4760 -0
  46. package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
  47. package/dist/runtime/canonical-runtime-composition.js +34 -0
  48. package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
  49. package/dist/runtime/canonical-runtime-projection.js +4 -0
  50. package/dist/runtime/canonical-target-compiler.d.ts +46 -0
  51. package/dist/runtime/canonical-target-compiler.js +514 -0
  52. package/dist/runtime/claude-agent-identity.d.ts +2 -0
  53. package/dist/runtime/claude-agent-identity.js +21 -0
  54. package/dist/runtime/executable-run-plan.d.ts +328 -0
  55. package/dist/runtime/executable-run-plan.js +1485 -0
  56. package/dist/runtime/execution-authority.d.ts +83 -0
  57. package/dist/runtime/execution-authority.js +98 -0
  58. package/dist/runtime/handoff-resolver.js +4 -1
  59. package/dist/runtime/harness-pull-adapter.d.ts +146 -0
  60. package/dist/runtime/harness-pull-adapter.js +315 -0
  61. package/dist/runtime/index.d.ts +39 -14
  62. package/dist/runtime/index.js +29 -7
  63. package/dist/runtime/local-execution-authority.d.ts +194 -0
  64. package/dist/runtime/local-execution-authority.js +2897 -0
  65. package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
  66. package/dist/runtime/local-execution-reconciliation.js +107 -0
  67. package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
  68. package/dist/runtime/local-execution-supervisor-script.js +793 -0
  69. package/dist/runtime/local-provider-sandbox.d.ts +42 -0
  70. package/dist/runtime/local-provider-sandbox.js +154 -0
  71. package/dist/runtime/local-verifier-execution.d.ts +24 -0
  72. package/dist/runtime/local-verifier-execution.js +290 -0
  73. package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
  74. package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
  75. package/dist/runtime/plan-store.d.ts +30 -0
  76. package/dist/runtime/plan-store.js +231 -0
  77. package/dist/runtime/process-tree.d.ts +9 -0
  78. package/dist/runtime/process-tree.js +97 -24
  79. package/dist/runtime/providers/codex.js +2 -2
  80. package/dist/runtime/push-driver.d.ts +184 -0
  81. package/dist/runtime/push-driver.js +1002 -0
  82. package/dist/runtime/result-store.d.ts +34 -3
  83. package/dist/runtime/result-store.js +1073 -2
  84. package/dist/runtime/run-binding.d.ts +68 -0
  85. package/dist/runtime/run-binding.js +278 -0
  86. package/dist/runtime/run-contracts.d.ts +575 -0
  87. package/dist/runtime/run-contracts.js +58 -7
  88. package/dist/runtime/run-controller.d.ts +1 -6
  89. package/dist/runtime/run-controller.js +0 -41
  90. package/dist/runtime/run-store.d.ts +85 -7
  91. package/dist/runtime/run-store.js +2528 -186
  92. package/dist/runtime/skill-adapter.d.ts +18 -7
  93. package/dist/runtime/skill-adapter.js +100 -742
  94. package/dist/runtime/structured-output-validator.d.ts +44 -0
  95. package/dist/runtime/structured-output-validator.js +171 -0
  96. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  97. package/dist/runtime/verifier-command-binding.js +162 -0
  98. package/dist/runtime/verifier.d.ts +46 -6
  99. package/dist/runtime/verifier.js +355 -53
  100. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  101. package/dist/runtime/windows-job-keeper.js +231 -0
  102. package/dist/runtime/worker-executor.d.ts +17 -1
  103. package/dist/runtime/worker-executor.js +26 -5
  104. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  105. package/dist/runtime/workflow-plan-adapter.js +502 -6
  106. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  107. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  108. package/dist/runtime/workspace-isolation.d.ts +29 -2
  109. package/dist/runtime/workspace-isolation.js +488 -11
  110. package/dist/runtime/workspace-observation.d.ts +44 -0
  111. package/dist/runtime/workspace-observation.js +216 -0
  112. package/dist/schema/controller-registry.d.ts +0 -7
  113. package/dist/schema/controller-registry.js +0 -8
  114. package/dist/schema/types.d.ts +84 -10
  115. package/dist/schema/types.js +2 -0
  116. package/dist/schema/validate.js +151 -1
  117. package/dist/schema/validators/controllers.d.ts +1 -1
  118. package/dist/schema/validators/controllers.js +126 -41
  119. package/dist/schema/workflow-authoring-contract.js +2 -0
  120. package/dist/schema/yaml-parser.js +1 -1
  121. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  122. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  123. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  124. package/native/windows-job-keeper/README.md +80 -0
  125. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  127. package/native/windows-job-keeper/keeper.c +979 -0
  128. package/native/windows-job-keeper/manifest.json +33 -0
  129. package/package.json +6 -2
  130. package/scripts/build-windows-job-keeper.mjs +172 -0
  131. package/scripts/verify-windows-job-keeper.mjs +184 -0
@@ -0,0 +1,187 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalizeJson } from "../runtime/canonical-json.js";
3
+ import { isCurrentClaudeSubagentStopInput } from "./schema.js";
4
+ export const CLAUDE_HOST_EVENT_SCHEMA_VERSION = "intentdna.claude_host_event.v1";
5
+ export const CLAUDE_HOST_EVENT_STDERR_PREFIX = "INTENTDNA_CLAUDE_HOST_EVENT_V1 ";
6
+ export class ClaudeHostEventError extends Error {
7
+ code;
8
+ constructor(code, message) {
9
+ super(message);
10
+ this.name = "ClaudeHostEventError";
11
+ this.code = code;
12
+ }
13
+ }
14
+ function nonEmpty(value) {
15
+ return typeof value === "string" && value.trim().length > 0;
16
+ }
17
+ function digest(value) {
18
+ return `sha256:${createHash("sha256").update(value, "utf8").digest("hex")}`;
19
+ }
20
+ function hostSessionId(input) {
21
+ if (nonEmpty(input.session_id))
22
+ return input.session_id;
23
+ if (nonEmpty(input.sessionId))
24
+ return input.sessionId;
25
+ return undefined;
26
+ }
27
+ export function buildClaudeSubagentStopHostEvent(input, state) {
28
+ if (!state?.active
29
+ || !nonEmpty(state.run_id)
30
+ || !nonEmpty(state.step_id)
31
+ || !nonEmpty(state.attempt_id)
32
+ || !nonEmpty(state.worker_session_id)
33
+ || !isCurrentClaudeSubagentStopInput(input)
34
+ || input.agent_id !== state.worker_session_id) {
35
+ return null;
36
+ }
37
+ return {
38
+ schema_version: CLAUDE_HOST_EVENT_SCHEMA_VERSION,
39
+ hook_event_name: "SubagentStop",
40
+ session_id: hostSessionId(input),
41
+ transcript_path: input.transcript_path,
42
+ agent_id: input.agent_id,
43
+ agent_type: input.agent_type,
44
+ agent_transcript_path: input.agent_transcript_path,
45
+ last_assistant_message_digest: digest(input.last_assistant_message),
46
+ run_id: state.run_id,
47
+ step_id: state.step_id,
48
+ attempt_id: state.attempt_id,
49
+ worker_session_id: state.worker_session_id,
50
+ };
51
+ }
52
+ export function encodeClaudeHostEventForStderr(event) {
53
+ return `${CLAUDE_HOST_EVENT_STDERR_PREFIX}${Buffer.from(canonicalizeJson(event), "utf8").toString("base64url")}\n`;
54
+ }
55
+ function parseEnvelope(value) {
56
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
57
+ throw new ClaudeHostEventError("malformed_envelope", "Claude host event envelope must be an object");
58
+ }
59
+ const event = value;
60
+ const expectedKeys = [
61
+ "agent_id",
62
+ "agent_transcript_path",
63
+ "agent_type",
64
+ "attempt_id",
65
+ "hook_event_name",
66
+ "last_assistant_message_digest",
67
+ "run_id",
68
+ "schema_version",
69
+ "session_id",
70
+ "step_id",
71
+ "transcript_path",
72
+ "worker_session_id",
73
+ ];
74
+ if (Object.keys(event).sort().join("\0") !== expectedKeys.join("\0")) {
75
+ throw new ClaudeHostEventError("malformed_envelope", "Claude host event envelope fields are not the fixed v1 projection");
76
+ }
77
+ if (event.schema_version !== CLAUDE_HOST_EVENT_SCHEMA_VERSION
78
+ || event.hook_event_name !== "SubagentStop"
79
+ || !nonEmpty(event.session_id)
80
+ || !nonEmpty(event.transcript_path)
81
+ || !nonEmpty(event.agent_id)
82
+ || !nonEmpty(event.agent_type)
83
+ || !nonEmpty(event.agent_transcript_path)
84
+ || !nonEmpty(event.last_assistant_message_digest)
85
+ || !/^sha256:[0-9a-f]{64}$/.test(event.last_assistant_message_digest)
86
+ || !nonEmpty(event.run_id)
87
+ || !nonEmpty(event.step_id)
88
+ || !nonEmpty(event.attempt_id)
89
+ || !nonEmpty(event.worker_session_id)) {
90
+ throw new ClaudeHostEventError("malformed_envelope", "Claude host event envelope is invalid");
91
+ }
92
+ if (event.agent_id !== event.worker_session_id) {
93
+ throw new ClaudeHostEventError("identity_mismatch", "Claude agent_id does not match the Controller worker_session_id");
94
+ }
95
+ return {
96
+ schema_version: CLAUDE_HOST_EVENT_SCHEMA_VERSION,
97
+ hook_event_name: "SubagentStop",
98
+ session_id: event.session_id,
99
+ transcript_path: event.transcript_path,
100
+ agent_id: event.agent_id,
101
+ agent_type: event.agent_type,
102
+ agent_transcript_path: event.agent_transcript_path,
103
+ last_assistant_message_digest: event.last_assistant_message_digest,
104
+ run_id: event.run_id,
105
+ step_id: event.step_id,
106
+ attempt_id: event.attempt_id,
107
+ worker_session_id: event.worker_session_id,
108
+ };
109
+ }
110
+ function assertExpectedContext(value, event, expected) {
111
+ const mismatches = [
112
+ ["hook_id", value.hook_id, expected.hook_id],
113
+ ["session_id", event.session_id, expected.session_id],
114
+ ["run_id", event.run_id, expected.run_id],
115
+ ["step_id", event.step_id, expected.step_id],
116
+ ["attempt_id", event.attempt_id, expected.attempt_id],
117
+ ["worker_session_id", event.worker_session_id, expected.worker_session_id],
118
+ ].filter(([, actual, wanted]) => actual !== wanted);
119
+ if (mismatches.length > 0) {
120
+ throw new ClaudeHostEventError("context_mismatch", `Claude hook response does not match the Controller expectation: ${mismatches
121
+ .map(([field]) => field)
122
+ .join(", ")}`);
123
+ }
124
+ }
125
+ export async function parseClaudeSubagentStopHookResponse(frame, expected, consumptionStore) {
126
+ if (frame === null || typeof frame !== "object" || Array.isArray(frame)) {
127
+ throw new ClaudeHostEventError("invalid_frame", "Claude hook response frame must be an object");
128
+ }
129
+ const value = frame;
130
+ if (value.type !== "system"
131
+ || value.subtype !== "hook_response"
132
+ || value.hook_event !== "SubagentStop"
133
+ || value.outcome !== "success"
134
+ || value.exit_code !== 0
135
+ || !nonEmpty(value.hook_id)
136
+ || !nonEmpty(value.session_id)
137
+ || !nonEmpty(value.uuid)
138
+ || typeof value.stderr !== "string") {
139
+ throw new ClaudeHostEventError("invalid_frame", "Claude hook response frame is not an accepted SubagentStop success");
140
+ }
141
+ const encoded = value.stderr
142
+ .split(/\r?\n/u)
143
+ .filter((line) => line.startsWith(CLAUDE_HOST_EVENT_STDERR_PREFIX))
144
+ .map((line) => line.slice(CLAUDE_HOST_EVENT_STDERR_PREFIX.length));
145
+ if (encoded.length === 0) {
146
+ throw new ClaudeHostEventError("missing_envelope", "Claude hook response has no IntentDNA host event envelope");
147
+ }
148
+ if (encoded.length !== 1) {
149
+ throw new ClaudeHostEventError("duplicate_envelope", "Claude hook response has multiple IntentDNA host event envelopes");
150
+ }
151
+ const payload = encoded[0];
152
+ if (!/^[A-Za-z0-9_-]+$/u.test(payload)) {
153
+ throw new ClaudeHostEventError("malformed_envelope", "Claude host event envelope encoding is invalid");
154
+ }
155
+ let parsed;
156
+ try {
157
+ const decoded = Buffer.from(payload, "base64url").toString("utf8");
158
+ if (Buffer.from(decoded, "utf8").toString("base64url") !== payload) {
159
+ throw new Error("non-canonical base64url");
160
+ }
161
+ parsed = JSON.parse(decoded);
162
+ if (canonicalizeJson(parsed) !== decoded) {
163
+ throw new Error("non-canonical JSON");
164
+ }
165
+ }
166
+ catch (error) {
167
+ throw new ClaudeHostEventError("malformed_envelope", `Claude host event envelope cannot be decoded: ${error instanceof Error ? error.message : String(error)}`);
168
+ }
169
+ const event = parseEnvelope(parsed);
170
+ if (event.session_id !== value.session_id) {
171
+ throw new ClaudeHostEventError("identity_mismatch", "Claude hook frame session_id does not match its host event envelope");
172
+ }
173
+ assertExpectedContext(value, event, expected);
174
+ const consumptionKey = {
175
+ session_id: event.session_id,
176
+ frame_uuid: value.uuid,
177
+ hook_id: value.hook_id,
178
+ };
179
+ if (!await consumptionStore.consume(consumptionKey)) {
180
+ throw new ClaudeHostEventError("replayed_frame", "Claude hook response frame was already consumed");
181
+ }
182
+ return {
183
+ hook_id: value.hook_id,
184
+ frame_uuid: value.uuid,
185
+ event,
186
+ };
187
+ }
@@ -6,6 +6,7 @@
6
6
  */
7
7
  export { type HookEvent, type HookInput, type HookOutput, type HookInputBase, type PreToolUseInput, type PostToolUseInput, type UserPromptSubmitInput, type SubagentStopInput, type PreCompactInput, type NotificationInput, type StopInput, type SessionStartInput as SessionStartHookInput, type StdinReadResult, readStdin, readStdinResult, writeOutput, allowOutput, blockOutput, escalateOutput, silentOutput, } from "./protocol.js";
8
8
  export { type HookFailureDisposition, type HookFailureKind, hookFailureDisposition, hookFailureOutput, } from "./enforcement-boundary.js";
9
+ export { type ClaudeSubagentStopHostEvent, type ClaudeSubagentStopHostEventExpectation, type ClaudeHookResponseFrame, type ClaudeHostEventConsumptionKey, type ClaudeHostEventConsumptionStore, type VerifiedClaudeSubagentStopHostEvent, type ClaudeHostEventErrorCode, ClaudeHostEventError, CLAUDE_HOST_EVENT_SCHEMA_VERSION, CLAUDE_HOST_EVENT_STDERR_PREFIX, buildClaudeSubagentStopHostEvent, encodeClaudeHostEventForStderr, parseClaudeSubagentStopHookResponse, } from "./host-event.js";
9
10
  export { type EnforceState, type SessionStartInput, type StopEnforceInput, type StopWorkflowContext, enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, checkWriteAllowed, globToPrefix, evaluateGateCondition, resolveToolTarget, } from "./enforce.js";
10
11
  export { type DNAWorkflowState, type AuditEntry, resolveStateDir, readWorkflowState, writeWorkflowState, clearWorkflowState, cleanupWorkerHookState, appendAudit, } from "./state.js";
11
12
  export { type SessionSummary, computeSummary, formatSummary, } from "./cli.js";
@@ -7,6 +7,7 @@
7
7
  // Protocol types and I/O
8
8
  export { readStdin, readStdinResult, writeOutput, allowOutput, blockOutput, escalateOutput, silentOutput, } from "./protocol.js";
9
9
  export { hookFailureDisposition, hookFailureOutput, } from "./enforcement-boundary.js";
10
+ export { ClaudeHostEventError, CLAUDE_HOST_EVENT_SCHEMA_VERSION, CLAUDE_HOST_EVENT_STDERR_PREFIX, buildClaudeSubagentStopHostEvent, encodeClaudeHostEventForStderr, parseClaudeSubagentStopHookResponse, } from "./host-event.js";
10
11
  // Enforcement engine
11
12
  export { enforcePreToolUse, enforcePostToolUse, enforceUserPromptSubmit, enforceSubagentStop, enforcePreCompact, enforceNotification, enforceSessionStart, enforceStop, checkWriteAllowed, globToPrefix, evaluateGateCondition, resolveToolTarget, } from "./enforce.js";
12
13
  // State management
@@ -11,6 +11,10 @@ export type { HookEvent } from "./event-registry.js";
11
11
  export interface HookInputBase {
12
12
  cwd?: string;
13
13
  sessionId?: string;
14
+ transcript_path?: string;
15
+ permission_mode?: string;
16
+ hook_event_name?: string;
17
+ agent_id?: string;
14
18
  worker_session_id?: string;
15
19
  run_id?: string;
16
20
  step_id?: string;
@@ -33,6 +37,9 @@ export interface UserPromptSubmitInput extends HookInputBase {
33
37
  export interface SubagentStopInput extends HookInputBase {
34
38
  agent_name?: string;
35
39
  agent_type?: string;
40
+ agent_transcript_path?: string;
41
+ last_assistant_message?: string;
42
+ stop_hook_active?: boolean;
36
43
  }
37
44
  export interface PreCompactInput extends HookInputBase {
38
45
  }
@@ -8,6 +8,7 @@ export interface ValidationResult {
8
8
  errors: string[];
9
9
  normalized?: Record<string, unknown>;
10
10
  }
11
+ export declare function isCurrentClaudeSubagentStopInput(input: Record<string, unknown>): boolean;
11
12
  /**
12
13
  * Validate hook input against expected schema for each event type.
13
14
  * Returns normalized input (camelCase) on success.
@@ -4,6 +4,30 @@
4
4
  * Classification of invalid input belongs to the event boundary.
5
5
  */
6
6
  import { isHookEventSupportedBySurface } from "./event-registry.js";
7
+ function nonEmptyString(value) {
8
+ return typeof value === "string" && value.trim().length > 0;
9
+ }
10
+ export function isCurrentClaudeSubagentStopInput(input) {
11
+ const parentSessionId = nonEmptyString(input.session_id)
12
+ ? input.session_id
13
+ : nonEmptyString(input.sessionId)
14
+ ? input.sessionId
15
+ : undefined;
16
+ return parentSessionId !== undefined
17
+ && !(nonEmptyString(input.session_id)
18
+ && nonEmptyString(input.sessionId)
19
+ && input.session_id !== input.sessionId)
20
+ && input.hook_event_name === "SubagentStop"
21
+ && nonEmptyString(input.transcript_path)
22
+ && nonEmptyString(input.permission_mode)
23
+ && typeof input.stop_hook_active === "boolean"
24
+ && nonEmptyString(input.agent_id)
25
+ && nonEmptyString(input.agent_type)
26
+ && nonEmptyString(input.agent_transcript_path)
27
+ && typeof input.last_assistant_message === "string"
28
+ && (input.worker_session_id === undefined
29
+ || input.worker_session_id === input.agent_id);
30
+ }
7
31
  /**
8
32
  * Validate hook input against expected schema for each event type.
9
33
  * Returns normalized input (camelCase) on success.
@@ -15,6 +39,26 @@ export function validateHookInput(event, raw) {
15
39
  const input = raw;
16
40
  const errors = [];
17
41
  const normalized = { ...input };
42
+ for (const field of [
43
+ "session_id",
44
+ "sessionId",
45
+ "worker_session_id",
46
+ "transcript_path",
47
+ "permission_mode",
48
+ "hook_event_name",
49
+ ]) {
50
+ if (input[field] !== undefined && typeof input[field] !== "string") {
51
+ errors.push(`${field} must be a string if present`);
52
+ }
53
+ }
54
+ if (typeof input.session_id === "string"
55
+ && typeof input.sessionId === "string"
56
+ && input.session_id !== input.sessionId) {
57
+ errors.push("session_id and sessionId must match when both are present");
58
+ }
59
+ if (typeof input.hook_event_name === "string" && input.hook_event_name !== event) {
60
+ errors.push(`hook_event_name must match ${event}`);
61
+ }
18
62
  // The Controller's disposable worker session is the Hook context key.
19
63
  const sessionId = normalizeSessionId(input);
20
64
  if (sessionId !== undefined) {
@@ -46,7 +90,51 @@ export function validateHookInput(event, raw) {
46
90
  // session_id is optional but should be normalized if present
47
91
  break;
48
92
  case "SubagentStop":
49
- // agent_type or agent_name should be present but not strictly required
93
+ for (const field of [
94
+ "agent_id",
95
+ "agent_name",
96
+ "agent_type",
97
+ "agent_transcript_path",
98
+ "last_assistant_message",
99
+ ]) {
100
+ if (input[field] !== undefined && typeof input[field] !== "string") {
101
+ errors.push(`SubagentStop: ${field} must be a string if present`);
102
+ }
103
+ }
104
+ if (typeof input.agent_id === "string" && input.agent_id.trim().length === 0) {
105
+ errors.push("SubagentStop: agent_id must be non-empty if present");
106
+ }
107
+ if (typeof input.agent_id === "string" && input.agent_id.trim().length > 0) {
108
+ for (const field of [
109
+ "transcript_path",
110
+ "permission_mode",
111
+ "agent_type",
112
+ "agent_transcript_path",
113
+ ]) {
114
+ if (typeof input[field] !== "string" || input[field].trim().length === 0) {
115
+ errors.push(`SubagentStop: ${field} must be a non-empty string when agent_id is present`);
116
+ }
117
+ }
118
+ if (!nonEmptyString(input.session_id) && !nonEmptyString(input.sessionId)) {
119
+ errors.push("SubagentStop: a non-empty parent session_id is required when agent_id is present");
120
+ }
121
+ if (input.hook_event_name !== "SubagentStop") {
122
+ errors.push("SubagentStop: hook_event_name must be SubagentStop when agent_id is present");
123
+ }
124
+ if (typeof input.last_assistant_message !== "string") {
125
+ errors.push("SubagentStop: last_assistant_message must be a string when agent_id is present");
126
+ }
127
+ if (typeof input.stop_hook_active !== "boolean") {
128
+ errors.push("SubagentStop: stop_hook_active must be a boolean when agent_id is present");
129
+ }
130
+ if (typeof input.worker_session_id === "string"
131
+ && input.worker_session_id !== input.agent_id) {
132
+ errors.push("SubagentStop: worker_session_id must match agent_id when both are present");
133
+ }
134
+ }
135
+ if (input.stop_hook_active !== undefined && typeof input.stop_hook_active !== "boolean") {
136
+ errors.push("SubagentStop: stop_hook_active must be a boolean if present");
137
+ }
50
138
  break;
51
139
  case "Notification":
52
140
  if (typeof input.title !== "string" && input.title !== undefined) {
@@ -10,6 +10,8 @@
10
10
  * All write operations use atomic temp+rename pattern.
11
11
  */
12
12
  import type { ArtifactFact, ArtifactKey, ArtifactManifest, CompletedArtifactEntry, HandoffArtifact, HandoffType, VerifierKind, VerifierSeverity, VerifierWhen } from "../schema/types.js";
13
+ import type { CompiledIRFile } from "../runtime/plugin-adapter.js";
14
+ import type { ClaimToken } from "../runtime/run-contracts.js";
13
15
  import { type RuntimeDecisionEvent } from "../governance/index.js";
14
16
  export declare const EVIDENCE_CAPTURE_SCHEMA_VERSION = "intentdna.evidence_capture.v1";
15
17
  /** Workflow state written by runtime and read by hooks */
@@ -108,6 +110,25 @@ export declare function safePathComponent(value: string, label: string): string;
108
110
  * Without: `.dna/state/`
109
111
  */
110
112
  export declare function resolveStateDir(projectDir: string, sessionId?: string): string;
113
+ interface CanonicalHookRuntimeProjectionBase {
114
+ readonly run_id: string;
115
+ readonly step_id: string;
116
+ readonly attempt_id: string;
117
+ readonly worker_session_id: string;
118
+ readonly compiled_ir_hash: string;
119
+ readonly compiled_ir: CompiledIRFile;
120
+ }
121
+ export interface CanonicalHookRuntimeProjectionV1 extends CanonicalHookRuntimeProjectionBase {
122
+ readonly schema_version: "intentdna.canonical_hook_projection.v1";
123
+ }
124
+ export interface CanonicalHookRuntimeProjectionV2 extends CanonicalHookRuntimeProjectionBase {
125
+ readonly schema_version: "intentdna.canonical_hook_projection.v2";
126
+ readonly lease_token: ClaimToken;
127
+ readonly owner_id: string;
128
+ }
129
+ export type CanonicalHookRuntimeProjection = CanonicalHookRuntimeProjectionV1 | CanonicalHookRuntimeProjectionV2;
130
+ export declare function resolveSessionCompiledIRPath(projectDir: string, workerSessionId: string): string;
131
+ export declare function writeCanonicalHookRuntimeProjection(projectDir: string, projection: CanonicalHookRuntimeProjection): Promise<void>;
111
132
  export declare const ARTIFACT_RESOLVER_VERSION = 1;
112
133
  export declare function isRequiredArtifact(artifact: HandoffArtifact): boolean;
113
134
  export declare function artifactIdentity(artifact: HandoffArtifact, resolvedPath?: string): string | null;
@@ -237,7 +258,7 @@ export interface TraceEntry {
237
258
  * Fail-open: never throws.
238
259
  */
239
260
  export declare function appendTrace(projectDir: string, entry: TraceEntry, sessionId?: string): Promise<void>;
240
- export declare function appendEvidenceCaptureEvent(projectDir: string, event: EvidenceCaptureEvent): Promise<void>;
261
+ export declare function appendEvidenceCaptureEvent(projectDir: string, event: EvidenceCaptureEvent): Promise<boolean>;
241
262
  export declare function appendRuntimeDecisionEvent(projectDir: string, event: RuntimeDecisionEvent, sessionId?: string): Promise<void>;
242
263
  export declare function readRuntimeDecisionEvents(projectDir: string, days?: number, sessionId?: string): Promise<RuntimeDecisionEvent[]>;
243
264
  export declare function readTraces(projectDir: string, days?: number, sessionId?: string): Promise<TraceEntry[]>;
@@ -265,3 +286,4 @@ export declare function cleanStaleState(projectDir: string): Promise<number>;
265
286
  * or removed by this operation.
266
287
  */
267
288
  export declare function cleanupWorkerHookState(projectDir: string, workerSessionId: string): Promise<void>;
289
+ export {};
@@ -17,6 +17,7 @@ import { CASCADE_LAYER_NAMES, RUNTIME_DECISION_EVENT_SCHEMA_VERSION } from "../g
17
17
  export const EVIDENCE_CAPTURE_SCHEMA_VERSION = "intentdna.evidence_capture.v1";
18
18
  // ── Constants ──────────────────────────────────────────────
19
19
  const WORKFLOW_FILE = "workflow.json";
20
+ const SESSION_COMPILED_IR_FILE = "compiled-ir.json";
20
21
  const DEFAULT_STALENESS_MS = 2 * 60 * 60 * 1000; // 2 hours
21
22
  const SAFE_PATH_COMPONENT = /^[A-Za-z0-9._-]{1,128}$/;
22
23
  const USER_ARTIFACT_PATH_ARGUMENTS = new Set(["ARGUMENTS", "arguments", "task_id"]);
@@ -47,6 +48,12 @@ export function resolveStateDir(projectDir, sessionId) {
47
48
  }
48
49
  return base;
49
50
  }
51
+ export function resolveSessionCompiledIRPath(projectDir, workerSessionId) {
52
+ return join(resolveStateDir(projectDir, safePathComponent(workerSessionId, "worker_session_id")), SESSION_COMPILED_IR_FILE);
53
+ }
54
+ export async function writeCanonicalHookRuntimeProjection(projectDir, projection) {
55
+ await atomicWrite(resolveSessionCompiledIRPath(projectDir, projection.worker_session_id), `${JSON.stringify(projection, null, 2)}\n`);
56
+ }
50
57
  export const ARTIFACT_RESOLVER_VERSION = 1;
51
58
  export function isRequiredArtifact(artifact) {
52
59
  return artifact.required !== false;
@@ -847,9 +854,11 @@ export async function appendEvidenceCaptureEvent(projectDir, event) {
847
854
  const captureDir = join(projectDir, ".dna", EVIDENCE_CAPTURE_DIR);
848
855
  await mkdir(captureDir, { recursive: true });
849
856
  await atomicAppend(join(captureDir, evidenceCaptureFileName(date)), line);
857
+ return true;
850
858
  }
851
859
  catch {
852
860
  // Fail-open: evidence capture write failure never affects hook execution
861
+ return false;
853
862
  }
854
863
  }
855
864
  export async function appendRuntimeDecisionEvent(projectDir, event, sessionId) {
package/dist/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export * from "./governance/index.js";
5
5
  export * from "./assets/index.js";
6
6
  export * from "./workflow/index.js";
7
7
  export * from "./organization/index.js";
8
+ export * from "./hooks/host-event.js";
package/dist/index.js CHANGED
@@ -5,3 +5,4 @@ export * from "./governance/index.js";
5
5
  export * from "./assets/index.js";
6
6
  export * from "./workflow/index.js";
7
7
  export * from "./organization/index.js";
8
+ export * from "./hooks/host-event.js";
package/dist/mcp/index.js CHANGED
@@ -17,6 +17,7 @@ import { createEnforceTools } from "./tools-enforce.js";
17
17
  import { createObservabilityTools } from "./tools-observability.js";
18
18
  import { createContextTools } from "./tools-context.js";
19
19
  import { createArtifactTools } from "./tools-artifacts.js";
20
+ import { createRunTools } from "./tools-run.js";
20
21
  // Parse args
21
22
  const args = process.argv.slice(2);
22
23
  let projectDir = process.cwd();
@@ -42,5 +43,6 @@ const tools = [
42
43
  ...createArtifactTools(projectDir),
43
44
  ...createEnforceTools(projectDir),
44
45
  ...createObservabilityTools(projectDir),
46
+ ...createRunTools(projectDir),
45
47
  ];
46
48
  createMCPServer({ name: "intentdna", version }, tools);
@@ -0,0 +1,4 @@
1
+ import { HarnessPullAdapter } from "../runtime/harness-pull-adapter.js";
2
+ import type { ToolDef } from "./server.js";
3
+ export declare function createMCPHarnessAdapter(projectDirectory: string): HarnessPullAdapter;
4
+ export declare function createRunTools(projectDir: string): ToolDef[];