@evo-dev/core 0.0.1-alpha → 0.0.1-alpha.10
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/assets/agents/review/code-reviewer/examples.md +1 -1
- package/assets/agents/review/code-reviewer/prompt.md +1 -1
- package/assets/agents/review/code-reviewer/verification.md +1 -1
- package/assets/skills/coding/knowledge-distillation/SKILL.md +251 -0
- package/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
- package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +126 -0
- package/assets/team/agents/code-reviewer.md +48 -0
- package/assets/team/agents/docs-maintainer.md +51 -0
- package/assets/team/agents/implementation-engineer.md +51 -0
- package/assets/team/agents/product-scope-analyst.md +58 -0
- package/assets/team/agents/release-engineer.md +55 -0
- package/assets/team/agents/security-boundary-reviewer.md +50 -0
- package/assets/team/agents/solution-architect.md +51 -0
- package/assets/team/agents/verification-engineer.md +51 -0
- package/assets/team/team.md +102 -0
- package/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
- package/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
- package/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
- package/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
- package/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
- package/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
- package/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
- package/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
- package/dist/config/index.js +1115 -81
- package/dist/index.js +13796 -2196
- package/dist/plugins/index.js +32 -32
- package/package.json +5 -1
- package/src/agents/index.ts +63 -292
- package/src/code-agent-traces/index.ts +520 -0
- package/src/config/index.ts +7 -0
- package/src/config/paths.ts +30 -0
- package/src/config/settings.ts +201 -0
- package/src/config/store.ts +152 -0
- package/src/daemon/index.ts +462 -40
- package/src/evolution/candidates/index.ts +564 -0
- package/src/evolution/control/index.ts +20 -0
- package/src/evolution/evidence/analysis.ts +533 -0
- package/src/evolution/evidence/index.ts +3 -0
- package/src/evolution/evidence/session-memory/analysis.ts +281 -0
- package/src/evolution/evidence/session-memory/constants.ts +9 -0
- package/src/evolution/evidence/session-memory/index.ts +7 -0
- package/src/evolution/evidence/session-memory/paths.ts +29 -0
- package/src/evolution/evidence/session-memory/policy.ts +39 -0
- package/src/evolution/evidence/session-memory/segment.ts +202 -0
- package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
- package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
- package/src/evolution/evidence/session-memory/storage.ts +379 -0
- package/src/evolution/evidence/session-memory/types.ts +221 -0
- package/src/evolution/evidence/session-memory/updater.ts +191 -0
- package/src/evolution/formatters.ts +169 -0
- package/src/evolution/index.ts +16 -0
- package/src/evolution/knowledge/index.ts +5427 -0
- package/src/evolution/paths.ts +44 -0
- package/src/evolution/processor/distillation.ts +518 -0
- package/src/evolution/processor/index.ts +3 -0
- package/src/evolution/processor/process.ts +528 -0
- package/src/{learning → evolution/review}/index.ts +10 -14
- package/src/evolution/schema.ts +568 -0
- package/src/evolution/shared.ts +758 -0
- package/src/evolution/triggers/classification.ts +102 -0
- package/src/evolution/triggers/index.ts +295 -0
- package/src/hooks/index.ts +652 -376
- package/src/index.ts +16 -3
- package/src/pack/index.ts +13 -13
- package/src/plugins/capabilities.ts +40 -42
- package/src/plugins/index.ts +0 -1
- package/src/plugins/types.ts +4 -0
- package/src/projects/index.ts +453 -0
- package/src/protected-zones/index.ts +29 -11
- package/src/runtime-logs/index.ts +790 -0
- package/src/sync/orchestrator.ts +6 -0
- package/src/team/index.ts +3642 -0
- package/src/team/mcp.ts +405 -0
- package/src/team/prompts.ts +141 -0
- package/src/utils/errors.ts +13 -0
- package/src/utils/fs.ts +40 -0
- package/src/utils/hash.ts +9 -0
- package/src/utils/ids.ts +12 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/parsing.ts +11 -0
- package/src/utils/text.ts +18 -0
- package/src/utils/time.ts +5 -0
- package/src/workflow/index.ts +6 -24
- package/src/project/index.ts +0 -507
- package/src/task/index.ts +0 -840
package/src/hooks/index.ts
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
2
|
import { dirname, join } from "node:path";
|
|
4
3
|
import {
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
type SessionMemoryPolicySnapshot,
|
|
5
|
+
updateSessionMemoryFromHook,
|
|
6
|
+
} from "../evolution/evidence/session-memory/index.ts";
|
|
7
|
+
import {
|
|
8
|
+
createScopedKnowledgeContextPack,
|
|
9
|
+
formatScopedKnowledgePromptBlock,
|
|
10
|
+
hasContextInjectionReceipt,
|
|
11
|
+
writeContextInjectionReceipt,
|
|
12
|
+
} from "../evolution/knowledge/index.ts";
|
|
13
|
+
import {
|
|
14
|
+
enqueueEvolutionTrigger,
|
|
15
|
+
resolveEvolutionTriggerDecision,
|
|
16
|
+
} from "../evolution/triggers/index.ts";
|
|
17
|
+
import { recordDiscoveredProject } from "../projects/index.ts";
|
|
18
|
+
import { resolveTraceSessionKey, resolveTraceTeamContext } from "../runtime-logs/index.ts";
|
|
19
|
+
import {
|
|
20
|
+
type TeamMessageRecord,
|
|
21
|
+
createTeamRoleRuntimeContext,
|
|
22
|
+
markTeamMessagesDelivered,
|
|
23
|
+
readPendingTeamMessagesForRole,
|
|
24
|
+
recordTeamAgentNativeSession,
|
|
25
|
+
resolveTeamRunPaths,
|
|
26
|
+
updateTeamAgentHookState,
|
|
27
|
+
} from "../team/index.ts";
|
|
28
|
+
import { sha256Short } from "../utils/index.ts";
|
|
10
29
|
|
|
11
30
|
export const CANONICAL_HOOK_EVENT_TYPES = [
|
|
12
31
|
"SessionStart",
|
|
@@ -49,15 +68,15 @@ export type CommandRiskClass =
|
|
|
49
68
|
| "credential-sensitive"
|
|
50
69
|
| "path-escaping"
|
|
51
70
|
| "unknown";
|
|
52
|
-
export type HookDecisionAction = "allow" | "warn"
|
|
71
|
+
export type HookDecisionAction = "allow" | "warn";
|
|
53
72
|
|
|
54
73
|
export interface HookSettings {
|
|
55
74
|
enabled: boolean;
|
|
56
75
|
targets: Record<CodeAgentHookTarget, HookTargetSettings>;
|
|
57
76
|
observability: {
|
|
58
|
-
metadataOnly:
|
|
59
|
-
rawPayloadStorage:
|
|
60
|
-
appendEvents:
|
|
77
|
+
metadataOnly: boolean;
|
|
78
|
+
rawPayloadStorage: boolean;
|
|
79
|
+
appendEvents: boolean;
|
|
61
80
|
};
|
|
62
81
|
learning: {
|
|
63
82
|
emitCandidates: false;
|
|
@@ -78,7 +97,7 @@ export interface HookEventV1 {
|
|
|
78
97
|
pluginId: string;
|
|
79
98
|
agent: string;
|
|
80
99
|
sessionIdHash: string | null;
|
|
81
|
-
rawPayloadStored:
|
|
100
|
+
rawPayloadStored: boolean;
|
|
82
101
|
};
|
|
83
102
|
time: {
|
|
84
103
|
occurredAt: string | null;
|
|
@@ -87,7 +106,7 @@ export interface HookEventV1 {
|
|
|
87
106
|
scope: {
|
|
88
107
|
taskId: string | null;
|
|
89
108
|
projectId: string | null;
|
|
90
|
-
cwdPolicy: "metadata-only";
|
|
109
|
+
cwdPolicy: "metadata-only" | "raw-local";
|
|
91
110
|
projectContextOptedIn: false;
|
|
92
111
|
};
|
|
93
112
|
payload: {
|
|
@@ -95,11 +114,11 @@ export interface HookEventV1 {
|
|
|
95
114
|
metadata: Record<string, string | number | boolean | string[]>;
|
|
96
115
|
redactions: string[];
|
|
97
116
|
redactionCount: number;
|
|
98
|
-
rawContentIncluded:
|
|
117
|
+
rawContentIncluded: boolean;
|
|
99
118
|
};
|
|
100
119
|
policy: {
|
|
101
120
|
classification: "local-private";
|
|
102
|
-
allowedUses: Array<"observability" | "
|
|
121
|
+
allowedUses: Array<"observability" | "workflow-suggestion">;
|
|
103
122
|
learningAllowed: false;
|
|
104
123
|
externalUploadAllowed: false;
|
|
105
124
|
};
|
|
@@ -128,17 +147,15 @@ export interface HookInstallDryRunPlan {
|
|
|
128
147
|
reason: string;
|
|
129
148
|
}>;
|
|
130
149
|
warnings: string[];
|
|
131
|
-
|
|
150
|
+
advisories: string[];
|
|
132
151
|
}
|
|
133
152
|
|
|
134
153
|
export interface HookRuntimeSessionBinding {
|
|
135
154
|
version: 1;
|
|
136
155
|
target: CodeAgentHookTarget;
|
|
137
156
|
sessionKey: string;
|
|
138
|
-
taskId: string | null;
|
|
139
|
-
contractPath: string | null;
|
|
140
157
|
cwd: string | null;
|
|
141
|
-
|
|
158
|
+
teamRuntimeContextDeliveredAt?: string | null;
|
|
142
159
|
updatedAt: string;
|
|
143
160
|
}
|
|
144
161
|
|
|
@@ -148,6 +165,7 @@ export interface HookRuntimeResult {
|
|
|
148
165
|
enabled: boolean;
|
|
149
166
|
output: Record<string, unknown> | null;
|
|
150
167
|
stateWrites: string[];
|
|
168
|
+
warnings: string[];
|
|
151
169
|
summary: string;
|
|
152
170
|
}
|
|
153
171
|
|
|
@@ -155,52 +173,76 @@ export interface HandleHookRuntimeInput {
|
|
|
155
173
|
target: CodeAgentHookTarget;
|
|
156
174
|
homeDir: string;
|
|
157
175
|
settings: HookSettings;
|
|
176
|
+
teamRuntimeDisplayMode?: "normal" | "development";
|
|
158
177
|
event: HookEventV1;
|
|
159
178
|
rawPayload: Record<string, unknown>;
|
|
160
179
|
receivedAt?: string;
|
|
180
|
+
environment?: Record<string, string | undefined>;
|
|
181
|
+
runtimeInjectionEnabled?: boolean;
|
|
182
|
+
sessionMemoryPolicy?: Partial<SessionMemoryPolicySnapshot>;
|
|
161
183
|
}
|
|
162
184
|
|
|
163
185
|
const DEFAULT_EVENT_SETTINGS: Record<CanonicalHookEventType, boolean> = {
|
|
164
|
-
SessionStart:
|
|
165
|
-
UserPromptSubmit:
|
|
166
|
-
UserPromptExpansion:
|
|
167
|
-
PreToolUse:
|
|
168
|
-
PermissionRequest:
|
|
169
|
-
PostToolUse:
|
|
170
|
-
PostToolUseFailure:
|
|
171
|
-
PostToolBatch:
|
|
172
|
-
PermissionDenied:
|
|
173
|
-
SubagentStart:
|
|
174
|
-
Stop:
|
|
175
|
-
StopFailure:
|
|
176
|
-
TeammateIdle:
|
|
177
|
-
SubagentStop:
|
|
178
|
-
TaskCreated:
|
|
179
|
-
TaskCompleted:
|
|
180
|
-
PreCompact:
|
|
181
|
-
PostCompact:
|
|
182
|
-
SessionEnd:
|
|
183
|
-
ConfigChange:
|
|
184
|
-
CwdChanged:
|
|
185
|
-
FileChanged:
|
|
186
|
-
WorktreeCreate:
|
|
187
|
-
WorktreeRemove:
|
|
186
|
+
SessionStart: true,
|
|
187
|
+
UserPromptSubmit: true,
|
|
188
|
+
UserPromptExpansion: true,
|
|
189
|
+
PreToolUse: true,
|
|
190
|
+
PermissionRequest: true,
|
|
191
|
+
PostToolUse: true,
|
|
192
|
+
PostToolUseFailure: true,
|
|
193
|
+
PostToolBatch: true,
|
|
194
|
+
PermissionDenied: true,
|
|
195
|
+
SubagentStart: true,
|
|
196
|
+
Stop: true,
|
|
197
|
+
StopFailure: true,
|
|
198
|
+
TeammateIdle: true,
|
|
199
|
+
SubagentStop: true,
|
|
200
|
+
TaskCreated: true,
|
|
201
|
+
TaskCompleted: true,
|
|
202
|
+
PreCompact: true,
|
|
203
|
+
PostCompact: true,
|
|
204
|
+
SessionEnd: true,
|
|
205
|
+
ConfigChange: true,
|
|
206
|
+
CwdChanged: true,
|
|
207
|
+
FileChanged: true,
|
|
208
|
+
WorktreeCreate: true,
|
|
209
|
+
WorktreeRemove: true,
|
|
188
210
|
};
|
|
189
211
|
|
|
190
212
|
const SENSITIVE_TEXT_PATTERN =
|
|
191
213
|
/https?:\/\/\S+|\b(secret|token|password|passwd|private|internal|api[_-]?key|apikey|credential|credentials|\.env)\b/i;
|
|
192
214
|
const SOURCE_LIKE_PATTERN = /\b(function|class|import|export|const|let|var)\b.*[{};]/s;
|
|
215
|
+
const TEAM_MESSAGE_DELIVERY_EVENTS = new Set<CanonicalHookEventType>([
|
|
216
|
+
"SessionStart",
|
|
217
|
+
"UserPromptSubmit",
|
|
218
|
+
"PostToolUse",
|
|
219
|
+
"PostToolUseFailure",
|
|
220
|
+
"Stop",
|
|
221
|
+
"TeammateIdle",
|
|
222
|
+
"SubagentStop",
|
|
223
|
+
"TaskCompleted",
|
|
224
|
+
]);
|
|
225
|
+
const CODEX_STOP_EVENTS_WITHOUT_ADDITIONAL_CONTEXT = new Set<CanonicalHookEventType>([
|
|
226
|
+
"Stop",
|
|
227
|
+
"SubagentStop",
|
|
228
|
+
]);
|
|
229
|
+
const COMPLETION_EVENTS_WITHOUT_DEVELOPMENT_DIAGNOSTICS = new Set<CanonicalHookEventType>([
|
|
230
|
+
"Stop",
|
|
231
|
+
"SubagentStop",
|
|
232
|
+
"TaskCompleted",
|
|
233
|
+
"TeammateIdle",
|
|
234
|
+
]);
|
|
193
235
|
|
|
194
236
|
export function createDefaultHookSettings(): HookSettings {
|
|
195
237
|
return {
|
|
196
|
-
enabled:
|
|
238
|
+
enabled: true,
|
|
197
239
|
targets: {
|
|
198
240
|
claude: {
|
|
199
|
-
enabled:
|
|
241
|
+
enabled: true,
|
|
200
242
|
events: { ...DEFAULT_EVENT_SETTINGS },
|
|
201
243
|
},
|
|
202
244
|
codex: {
|
|
203
|
-
enabled:
|
|
245
|
+
enabled: true,
|
|
204
246
|
events: { ...DEFAULT_EVENT_SETTINGS },
|
|
205
247
|
},
|
|
206
248
|
},
|
|
@@ -220,6 +262,17 @@ export function parseHookSettings(value: unknown): HookSettings {
|
|
|
220
262
|
const defaults = createDefaultHookSettings();
|
|
221
263
|
if (value === undefined || value === null) return defaults;
|
|
222
264
|
if (!isRecord(value)) throw new Error("Invalid hooks settings; expected object.");
|
|
265
|
+
const observability = isRecord(value.observability) ? value.observability : undefined;
|
|
266
|
+
optionalBoolean(
|
|
267
|
+
observability?.metadataOnly,
|
|
268
|
+
defaults.observability.metadataOnly,
|
|
269
|
+
"hooks.observability.metadataOnly",
|
|
270
|
+
);
|
|
271
|
+
optionalBoolean(
|
|
272
|
+
observability?.rawPayloadStorage,
|
|
273
|
+
defaults.observability.rawPayloadStorage,
|
|
274
|
+
"hooks.observability.rawPayloadStorage",
|
|
275
|
+
);
|
|
223
276
|
|
|
224
277
|
return {
|
|
225
278
|
enabled: optionalBoolean(value.enabled, defaults.enabled, "hooks.enabled"),
|
|
@@ -230,7 +283,11 @@ export function parseHookSettings(value: unknown): HookSettings {
|
|
|
230
283
|
observability: {
|
|
231
284
|
metadataOnly: true,
|
|
232
285
|
rawPayloadStorage: false,
|
|
233
|
-
appendEvents:
|
|
286
|
+
appendEvents: optionalBoolean(
|
|
287
|
+
observability?.appendEvents,
|
|
288
|
+
defaults.observability.appendEvents,
|
|
289
|
+
"hooks.observability.appendEvents",
|
|
290
|
+
),
|
|
234
291
|
},
|
|
235
292
|
learning: {
|
|
236
293
|
emitCandidates: false,
|
|
@@ -281,7 +338,7 @@ export function normalizeHookEvent(input: NormalizeHookEventInput): HookEventV1
|
|
|
281
338
|
},
|
|
282
339
|
policy: {
|
|
283
340
|
classification: "local-private",
|
|
284
|
-
allowedUses: ["observability", "
|
|
341
|
+
allowedUses: ["observability", "workflow-suggestion"],
|
|
285
342
|
learningAllowed: false,
|
|
286
343
|
externalUploadAllowed: false,
|
|
287
344
|
},
|
|
@@ -318,7 +375,8 @@ export function formatHookInstallDryRun(plan: HookInstallDryRunPlan): string {
|
|
|
318
375
|
(eventType) => ` - ${eventType}: ${plan.settings.targets[plan.target].events[eventType]}`,
|
|
319
376
|
),
|
|
320
377
|
"Boundaries:",
|
|
321
|
-
" -
|
|
378
|
+
" - execution events: metadata-only; raw payload/prompt/output/source storage=false",
|
|
379
|
+
" - historical raw trace files may remain for read-only migration compatibility",
|
|
322
380
|
" - learning: emitCandidates=false, writeMemory=false",
|
|
323
381
|
" - external upload: false",
|
|
324
382
|
" - protected project writes: .claude/.codex/CLAUDE.md/AGENTS.md not targeted",
|
|
@@ -331,10 +389,10 @@ export function formatHookInstallDryRun(plan: HookInstallDryRunPlan): string {
|
|
|
331
389
|
...(plan.warnings.length === 0
|
|
332
390
|
? [" - none"]
|
|
333
391
|
: plan.warnings.map((warning) => ` - ${warning}`)),
|
|
334
|
-
"
|
|
335
|
-
...(plan.
|
|
392
|
+
"Advisories:",
|
|
393
|
+
...(plan.advisories.length === 0
|
|
336
394
|
? [" - none"]
|
|
337
|
-
: plan.
|
|
395
|
+
: plan.advisories.map((advisory) => ` - ${advisory}`)),
|
|
338
396
|
].join("\n");
|
|
339
397
|
}
|
|
340
398
|
|
|
@@ -364,12 +422,11 @@ export function formatHookEventDryRun(event: HookEventV1): string {
|
|
|
364
422
|
export function resolveHookRuntimeSessionPaths(input: {
|
|
365
423
|
homeDir: string;
|
|
366
424
|
sessionKey: string;
|
|
367
|
-
}): { sessionDir: string; bindingPath: string
|
|
425
|
+
}): { sessionDir: string; bindingPath: string } {
|
|
368
426
|
const sessionDir = join(input.homeDir, ".evodev", "STATE", "hooks", "sessions", input.sessionKey);
|
|
369
427
|
return {
|
|
370
428
|
sessionDir,
|
|
371
429
|
bindingPath: join(sessionDir, "binding.json"),
|
|
372
|
-
contractPath: join(sessionDir, "contract.json"),
|
|
373
430
|
};
|
|
374
431
|
}
|
|
375
432
|
|
|
@@ -382,35 +439,76 @@ export async function handleHookRuntime(input: HandleHookRuntimeInput): Promise<
|
|
|
382
439
|
enabled,
|
|
383
440
|
output: null,
|
|
384
441
|
stateWrites: [],
|
|
442
|
+
warnings: [],
|
|
385
443
|
summary: `Hook ${input.event.type} ignored because EvoDev hooks are disabled.`,
|
|
386
444
|
};
|
|
387
445
|
}
|
|
388
446
|
|
|
389
|
-
if (input
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return handlePostToolUse(input);
|
|
447
|
+
if (isActiveClaudeStopHook(input)) {
|
|
448
|
+
return createRuntimeResult(input, null, {
|
|
449
|
+
summary: `${input.event.type} re-entry allowed to complete without hook output.`,
|
|
450
|
+
});
|
|
394
451
|
}
|
|
395
|
-
|
|
396
|
-
|
|
452
|
+
|
|
453
|
+
const diagnostics = await recordTeamNativeSessionFromHook(input);
|
|
454
|
+
const projectDiagnostics = await recordDiscoveredProjectFromHook(input);
|
|
455
|
+
let result: HookRuntimeResult | undefined;
|
|
456
|
+
if (input.event.type === "SessionStart") result = await handleSessionStart(input);
|
|
457
|
+
else if (input.event.type === "UserPromptSubmit") result = await handleUserPromptSubmit(input);
|
|
458
|
+
else if (input.event.type === "PreToolUse") result = await handlePreToolUse(input);
|
|
459
|
+
if (input.event.type === "PostToolUse" || input.event.type === "PostToolUseFailure") {
|
|
460
|
+
result = await handlePostToolUse(input);
|
|
461
|
+
} else if (input.event.type === "PostToolBatch") {
|
|
462
|
+
result = await handleAdditionalContext(input, "PostToolBatch");
|
|
463
|
+
} else if (
|
|
397
464
|
input.event.type === "SubagentStart" ||
|
|
398
465
|
input.event.type === "TaskCreated" ||
|
|
399
466
|
input.event.type === "PermissionRequest"
|
|
400
467
|
) {
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
if (
|
|
468
|
+
result = await handlePreToolUse(input);
|
|
469
|
+
} else if (
|
|
404
470
|
input.event.type === "Stop" ||
|
|
405
471
|
input.event.type === "SubagentStop" ||
|
|
406
472
|
input.event.type === "TaskCompleted" ||
|
|
407
473
|
input.event.type === "TeammateIdle"
|
|
408
474
|
) {
|
|
409
|
-
|
|
475
|
+
result = await handleCompletionObservation(input);
|
|
476
|
+
} else if (input.event.type === "PreCompact") {
|
|
477
|
+
result = await handlePreCompact(input);
|
|
478
|
+
} else if (input.event.type === "SessionEnd") {
|
|
479
|
+
result = await handleSessionEnd(input);
|
|
480
|
+
} else if (result === undefined) {
|
|
481
|
+
result = await handleAdditionalContext(input, input.event.type);
|
|
410
482
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
483
|
+
const sessionMemoryDiagnostics = await recordSessionMemoryFromHook(input);
|
|
484
|
+
const evolutionDiagnostics =
|
|
485
|
+
sessionMemoryDiagnostics.queuedSegmentTriggerId !== null ||
|
|
486
|
+
shouldSuppressLegacyEvolutionTrigger(input.event.type)
|
|
487
|
+
? { stateWrites: [], warnings: [] }
|
|
488
|
+
: await recordEvolutionTriggerFromHook(input);
|
|
489
|
+
const messageDiagnostics = await deliverPendingTeamMessagesFromHook(input, result);
|
|
490
|
+
const scopedContextDiagnostics = await injectScopedKnowledgeContextFromHook(input, result);
|
|
491
|
+
const teamStateDiagnostics = await recordTeamAgentHookStateFromHook(input);
|
|
492
|
+
const completed = appendRuntimeDiagnostics(
|
|
493
|
+
appendRuntimeDiagnostics(
|
|
494
|
+
appendRuntimeDiagnostics(
|
|
495
|
+
appendRuntimeDiagnostics(
|
|
496
|
+
appendRuntimeDiagnostics(
|
|
497
|
+
appendRuntimeDiagnostics(
|
|
498
|
+
appendRuntimeDiagnostics(result, diagnostics),
|
|
499
|
+
projectDiagnostics,
|
|
500
|
+
),
|
|
501
|
+
sessionMemoryDiagnostics,
|
|
502
|
+
),
|
|
503
|
+
evolutionDiagnostics,
|
|
504
|
+
),
|
|
505
|
+
messageDiagnostics,
|
|
506
|
+
),
|
|
507
|
+
scopedContextDiagnostics,
|
|
508
|
+
),
|
|
509
|
+
teamStateDiagnostics,
|
|
510
|
+
);
|
|
511
|
+
return appendDevelopmentHookDiagnostics(input, completed);
|
|
414
512
|
}
|
|
415
513
|
|
|
416
514
|
export function formatHookRuntimeOutput(result: HookRuntimeResult): string {
|
|
@@ -429,207 +527,427 @@ function isHookEventEnabled(
|
|
|
429
527
|
);
|
|
430
528
|
}
|
|
431
529
|
|
|
530
|
+
interface HookRuntimeDiagnostics {
|
|
531
|
+
stateWrites: string[];
|
|
532
|
+
warnings: string[];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async function recordDiscoveredProjectFromHook(
|
|
536
|
+
input: HandleHookRuntimeInput,
|
|
537
|
+
): Promise<HookRuntimeDiagnostics> {
|
|
538
|
+
if (
|
|
539
|
+
input.event.type !== "SessionStart" &&
|
|
540
|
+
input.event.type !== "UserPromptSubmit" &&
|
|
541
|
+
input.event.type !== "CwdChanged"
|
|
542
|
+
) {
|
|
543
|
+
return { stateWrites: [], warnings: [] };
|
|
544
|
+
}
|
|
545
|
+
const cwd = optionalPayloadString(input.rawPayload.cwd);
|
|
546
|
+
if (cwd === null) return { stateWrites: [], warnings: [] };
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const result = await recordDiscoveredProject({
|
|
550
|
+
homeDir: input.homeDir,
|
|
551
|
+
cwd,
|
|
552
|
+
target: input.target,
|
|
553
|
+
sessionKey: resolveTraceSessionKey(input.rawPayload),
|
|
554
|
+
now:
|
|
555
|
+
input.receivedAt === undefined || input.receivedAt === "dry-run"
|
|
556
|
+
? undefined
|
|
557
|
+
: input.receivedAt,
|
|
558
|
+
});
|
|
559
|
+
return result === null
|
|
560
|
+
? { stateWrites: [], warnings: [] }
|
|
561
|
+
: { stateWrites: [result.path], warnings: [] };
|
|
562
|
+
} catch {
|
|
563
|
+
return {
|
|
564
|
+
stateWrites: [],
|
|
565
|
+
warnings: ["Local project discovery could not be updated at this hook safe point."],
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function recordTeamNativeSessionFromHook(
|
|
571
|
+
input: HandleHookRuntimeInput,
|
|
572
|
+
): Promise<HookRuntimeDiagnostics> {
|
|
573
|
+
const environment = input.environment ?? {};
|
|
574
|
+
const runId = optionalPayloadString(environment.EVODEV_TEAM_RUN_ID);
|
|
575
|
+
const roleId = optionalPayloadString(environment.EVODEV_TEAM_ROLE_ID);
|
|
576
|
+
const sessionId = optionalPayloadString(
|
|
577
|
+
input.rawPayload.session_id ?? input.rawPayload.sessionId,
|
|
578
|
+
);
|
|
579
|
+
if (runId === null || roleId === null || sessionId === null) {
|
|
580
|
+
return { stateWrites: [], warnings: [] };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
try {
|
|
584
|
+
await recordTeamAgentNativeSession({
|
|
585
|
+
homeDir: input.homeDir,
|
|
586
|
+
runId,
|
|
587
|
+
roleId,
|
|
588
|
+
sessionId,
|
|
589
|
+
now:
|
|
590
|
+
input.receivedAt === undefined || input.receivedAt === "dry-run"
|
|
591
|
+
? undefined
|
|
592
|
+
: new Date(input.receivedAt),
|
|
593
|
+
});
|
|
594
|
+
return {
|
|
595
|
+
stateWrites: [resolveTeamRunPaths(input.homeDir).agentPath(runId, roleId)],
|
|
596
|
+
warnings: [],
|
|
597
|
+
};
|
|
598
|
+
} catch {
|
|
599
|
+
return {
|
|
600
|
+
stateWrites: [],
|
|
601
|
+
warnings: [
|
|
602
|
+
`Team native session metadata could not be recorded for run ${safeDiagnosticId(runId)} role ${safeDiagnosticId(roleId)}.`,
|
|
603
|
+
],
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function recordEvolutionTriggerFromHook(
|
|
609
|
+
input: HandleHookRuntimeInput,
|
|
610
|
+
): Promise<HookRuntimeDiagnostics> {
|
|
611
|
+
const decision = resolveEvolutionTriggerDecision({
|
|
612
|
+
eventType: input.event.type,
|
|
613
|
+
summary: input.event.payload.summary,
|
|
614
|
+
});
|
|
615
|
+
if (!decision.shouldQueue) return { stateWrites: [], warnings: [] };
|
|
616
|
+
|
|
617
|
+
const team = resolveTraceTeamContext({
|
|
618
|
+
homeDir: input.homeDir,
|
|
619
|
+
environment: input.environment,
|
|
620
|
+
payload: input.rawPayload,
|
|
621
|
+
});
|
|
622
|
+
if (team === null) {
|
|
623
|
+
return {
|
|
624
|
+
stateWrites: [],
|
|
625
|
+
warnings:
|
|
626
|
+
decision.strength === "strong"
|
|
627
|
+
? [
|
|
628
|
+
`Evolution trigger ${input.event.type} was not queued because team context is missing.`,
|
|
629
|
+
]
|
|
630
|
+
: [],
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
const trigger = await enqueueEvolutionTrigger({
|
|
636
|
+
homeDir: input.homeDir,
|
|
637
|
+
projectKey: team.projectKey,
|
|
638
|
+
runId: team.runId,
|
|
639
|
+
roleId: team.roleId,
|
|
640
|
+
taskId: input.event.scope.taskId,
|
|
641
|
+
eventType: input.event.type,
|
|
642
|
+
eventId: input.event.eventId,
|
|
643
|
+
summary: input.event.payload.summary,
|
|
644
|
+
now:
|
|
645
|
+
input.receivedAt === undefined || input.receivedAt === "dry-run"
|
|
646
|
+
? undefined
|
|
647
|
+
: input.receivedAt,
|
|
648
|
+
});
|
|
649
|
+
if (trigger === null) return { stateWrites: [], warnings: [] };
|
|
650
|
+
return {
|
|
651
|
+
stateWrites: [
|
|
652
|
+
join(
|
|
653
|
+
input.homeDir,
|
|
654
|
+
".evodev",
|
|
655
|
+
"state",
|
|
656
|
+
"evolution",
|
|
657
|
+
trigger.projectKey,
|
|
658
|
+
trigger.runId,
|
|
659
|
+
"triggers",
|
|
660
|
+
`${trigger.id}.json`,
|
|
661
|
+
),
|
|
662
|
+
],
|
|
663
|
+
warnings: [],
|
|
664
|
+
};
|
|
665
|
+
} catch {
|
|
666
|
+
return {
|
|
667
|
+
stateWrites: [],
|
|
668
|
+
warnings: [`Evolution trigger ${input.event.type} could not be queued.`],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function recordSessionMemoryFromHook(
|
|
674
|
+
input: HandleHookRuntimeInput,
|
|
675
|
+
): Promise<HookRuntimeDiagnostics & { queuedSegmentTriggerId: string | null }> {
|
|
676
|
+
try {
|
|
677
|
+
const result = await updateSessionMemoryFromHook({
|
|
678
|
+
homeDir: input.homeDir,
|
|
679
|
+
target: input.target,
|
|
680
|
+
event: input.event,
|
|
681
|
+
rawPayload: input.rawPayload,
|
|
682
|
+
environment: input.environment,
|
|
683
|
+
receivedAt: input.receivedAt,
|
|
684
|
+
policy: input.sessionMemoryPolicy,
|
|
685
|
+
});
|
|
686
|
+
return {
|
|
687
|
+
stateWrites: result.stateWrites,
|
|
688
|
+
warnings: result.warnings,
|
|
689
|
+
queuedSegmentTriggerId: result.queuedSegmentTriggerId,
|
|
690
|
+
};
|
|
691
|
+
} catch {
|
|
692
|
+
return {
|
|
693
|
+
stateWrites: [],
|
|
694
|
+
warnings: ["Session Memory state could not be updated at this hook safe point."],
|
|
695
|
+
queuedSegmentTriggerId: null,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function shouldSuppressLegacyEvolutionTrigger(eventType: CanonicalHookEventType): boolean {
|
|
701
|
+
return eventType === "Stop" || eventType === "SubagentStop" || eventType === "SessionEnd";
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async function deliverPendingTeamMessagesFromHook(
|
|
705
|
+
input: HandleHookRuntimeInput,
|
|
706
|
+
result: HookRuntimeResult | undefined,
|
|
707
|
+
): Promise<HookRuntimeDiagnostics> {
|
|
708
|
+
if (result === undefined || !canDeliverTeamMessagesFromHook(input.target, input.event.type)) {
|
|
709
|
+
return { stateWrites: [], warnings: [] };
|
|
710
|
+
}
|
|
711
|
+
const team = resolveTraceTeamContext({
|
|
712
|
+
homeDir: input.homeDir,
|
|
713
|
+
environment: input.environment,
|
|
714
|
+
payload: input.rawPayload,
|
|
715
|
+
});
|
|
716
|
+
if (team === null) return { stateWrites: [], warnings: [] };
|
|
717
|
+
|
|
718
|
+
try {
|
|
719
|
+
const messages = await readPendingTeamMessagesForRole({
|
|
720
|
+
homeDir: input.homeDir,
|
|
721
|
+
runId: team.runId,
|
|
722
|
+
roleId: team.roleId,
|
|
723
|
+
limit: 5,
|
|
724
|
+
});
|
|
725
|
+
if (messages.length === 0) return { stateWrites: [], warnings: [] };
|
|
726
|
+
result.output = appendAdditionalContext(
|
|
727
|
+
result.output,
|
|
728
|
+
input.event.type,
|
|
729
|
+
formatTeamInboxContext(messages),
|
|
730
|
+
);
|
|
731
|
+
await markTeamMessagesDelivered({
|
|
732
|
+
homeDir: input.homeDir,
|
|
733
|
+
runId: team.runId,
|
|
734
|
+
roleId: team.roleId,
|
|
735
|
+
messageIds: messages.map((message) => message.messageId),
|
|
736
|
+
now:
|
|
737
|
+
input.receivedAt === undefined || input.receivedAt === "dry-run"
|
|
738
|
+
? undefined
|
|
739
|
+
: new Date(input.receivedAt),
|
|
740
|
+
});
|
|
741
|
+
const teamPaths = resolveTeamRunPaths(input.homeDir);
|
|
742
|
+
return {
|
|
743
|
+
stateWrites: [teamPaths.eventsPath(team.runId), teamPaths.messageListPath(team.runId)],
|
|
744
|
+
warnings: [],
|
|
745
|
+
};
|
|
746
|
+
} catch {
|
|
747
|
+
return {
|
|
748
|
+
stateWrites: [],
|
|
749
|
+
warnings: [
|
|
750
|
+
`Team inbox messages could not be delivered for role ${safeDiagnosticId(team.roleId)}.`,
|
|
751
|
+
],
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
async function injectScopedKnowledgeContextFromHook(
|
|
757
|
+
input: HandleHookRuntimeInput,
|
|
758
|
+
result: HookRuntimeResult | undefined,
|
|
759
|
+
): Promise<HookRuntimeDiagnostics> {
|
|
760
|
+
if (result === undefined || !canDeliverTeamMessagesFromHook(input.target, input.event.type)) {
|
|
761
|
+
return { stateWrites: [], warnings: [] };
|
|
762
|
+
}
|
|
763
|
+
const team = resolveTraceTeamContext({
|
|
764
|
+
homeDir: input.homeDir,
|
|
765
|
+
environment: input.environment,
|
|
766
|
+
payload: input.rawPayload,
|
|
767
|
+
});
|
|
768
|
+
if (team === null || team.roleId !== "main") return { stateWrites: [], warnings: [] };
|
|
769
|
+
|
|
770
|
+
try {
|
|
771
|
+
if (input.runtimeInjectionEnabled === false) return { stateWrites: [], warnings: [] };
|
|
772
|
+
const pack = await createScopedKnowledgeContextPack({
|
|
773
|
+
homeDir: input.homeDir,
|
|
774
|
+
projectKey: team.projectKey,
|
|
775
|
+
roleId: team.roleId,
|
|
776
|
+
});
|
|
777
|
+
if (pack === null) return { stateWrites: [], warnings: [] };
|
|
778
|
+
const sessionKey = resolveHookSessionKey(input.rawPayload);
|
|
779
|
+
if (
|
|
780
|
+
await hasContextInjectionReceipt({
|
|
781
|
+
homeDir: input.homeDir,
|
|
782
|
+
sessionKey,
|
|
783
|
+
contextPackId: pack.id,
|
|
784
|
+
})
|
|
785
|
+
) {
|
|
786
|
+
return { stateWrites: [], warnings: [] };
|
|
787
|
+
}
|
|
788
|
+
const receipt = await writeContextInjectionReceipt({
|
|
789
|
+
homeDir: input.homeDir,
|
|
790
|
+
sessionKey,
|
|
791
|
+
pack,
|
|
792
|
+
trigger: "hook-safe-point",
|
|
793
|
+
hookEventId: input.event.eventId,
|
|
794
|
+
injectedAt: input.receivedAt ?? new Date().toISOString(),
|
|
795
|
+
});
|
|
796
|
+
result.output = appendAdditionalContext(
|
|
797
|
+
result.output,
|
|
798
|
+
input.event.type,
|
|
799
|
+
formatScopedKnowledgePromptBlock(pack),
|
|
800
|
+
);
|
|
801
|
+
return { stateWrites: [receipt.path], warnings: [] };
|
|
802
|
+
} catch {
|
|
803
|
+
return {
|
|
804
|
+
stateWrites: [],
|
|
805
|
+
warnings: ["Scoped knowledge context could not be injected at this hook safe point."],
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function canDeliverTeamMessagesFromHook(
|
|
811
|
+
target: CodeAgentHookTarget,
|
|
812
|
+
eventType: CanonicalHookEventType,
|
|
813
|
+
): boolean {
|
|
814
|
+
if (!TEAM_MESSAGE_DELIVERY_EVENTS.has(eventType)) return false;
|
|
815
|
+
return target !== "codex" || !CODEX_STOP_EVENTS_WITHOUT_ADDITIONAL_CONTEXT.has(eventType);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async function recordTeamAgentHookStateFromHook(
|
|
819
|
+
input: HandleHookRuntimeInput,
|
|
820
|
+
): Promise<HookRuntimeDiagnostics> {
|
|
821
|
+
const team = resolveTraceTeamContext({
|
|
822
|
+
homeDir: input.homeDir,
|
|
823
|
+
environment: input.environment,
|
|
824
|
+
payload: input.rawPayload,
|
|
825
|
+
});
|
|
826
|
+
if (team === null) return { stateWrites: [], warnings: [] };
|
|
827
|
+
|
|
828
|
+
try {
|
|
829
|
+
const result = await updateTeamAgentHookState({
|
|
830
|
+
homeDir: input.homeDir,
|
|
831
|
+
runId: team.runId,
|
|
832
|
+
roleId: team.roleId,
|
|
833
|
+
hookEvent: input.event.type,
|
|
834
|
+
now:
|
|
835
|
+
input.receivedAt === undefined || input.receivedAt === "dry-run"
|
|
836
|
+
? undefined
|
|
837
|
+
: new Date(input.receivedAt),
|
|
838
|
+
});
|
|
839
|
+
return { stateWrites: [result.agentPath, result.statusPath], warnings: [] };
|
|
840
|
+
} catch {
|
|
841
|
+
return {
|
|
842
|
+
stateWrites: [],
|
|
843
|
+
warnings: [
|
|
844
|
+
`Team agent state could not be updated for hook role ${safeDiagnosticId(team.roleId)}.`,
|
|
845
|
+
],
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function appendRuntimeDiagnostics(
|
|
851
|
+
result: HookRuntimeResult | undefined,
|
|
852
|
+
diagnostics: HookRuntimeDiagnostics,
|
|
853
|
+
): HookRuntimeResult {
|
|
854
|
+
if (result === undefined) {
|
|
855
|
+
throw new Error("Hook runtime did not produce a result.");
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
...result,
|
|
859
|
+
stateWrites: [...new Set([...diagnostics.stateWrites, ...result.stateWrites])],
|
|
860
|
+
warnings: [...new Set([...diagnostics.warnings, ...result.warnings])],
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
432
864
|
async function handleSessionStart(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
|
|
433
|
-
|
|
434
|
-
const context = [
|
|
435
|
-
"EvoDev session initialized.",
|
|
436
|
-
binding?.contractPath
|
|
437
|
-
? `Active Task Contract: ${binding.contractPath}`
|
|
438
|
-
: "No active Task Contract yet.",
|
|
439
|
-
"User prompts will be routed through EvoDev before execution.",
|
|
440
|
-
].join(" ");
|
|
441
|
-
return createRuntimeResult(input, hookOutput(input.event.type, { additionalContext: context }), {
|
|
865
|
+
return createRuntimeResult(input, null, {
|
|
442
866
|
summary: "Session context prepared.",
|
|
443
867
|
});
|
|
444
868
|
}
|
|
445
869
|
|
|
446
870
|
async function handleUserPromptSubmit(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
|
|
447
|
-
const classification = classifyUserPrompt(input.rawPayload.prompt ?? input.rawPayload.userPrompt);
|
|
448
871
|
const sessionKey = resolveHookSessionKey(input.rawPayload);
|
|
449
872
|
const paths = resolveHookRuntimeSessionPaths({ homeDir: input.homeDir, sessionKey });
|
|
450
|
-
const
|
|
873
|
+
const previousBinding = await readSessionBinding(input.homeDir, input.rawPayload);
|
|
874
|
+
const teamRuntimeContext =
|
|
875
|
+
previousBinding?.teamRuntimeContextDeliveredAt === undefined ||
|
|
876
|
+
previousBinding.teamRuntimeContextDeliveredAt === null
|
|
877
|
+
? await createTeamRuntimeContextForUserPrompt(input)
|
|
878
|
+
: null;
|
|
879
|
+
const teamRuntimeContextDeliveredAt =
|
|
880
|
+
teamRuntimeContext !== null
|
|
881
|
+
? (input.receivedAt ?? new Date().toISOString())
|
|
882
|
+
: (previousBinding?.teamRuntimeContextDeliveredAt ?? null);
|
|
451
883
|
const binding: HookRuntimeSessionBinding = {
|
|
452
884
|
version: 1,
|
|
453
885
|
target: input.target,
|
|
454
886
|
sessionKey,
|
|
455
|
-
taskId: contract.taskId,
|
|
456
|
-
contractPath: paths.contractPath,
|
|
457
887
|
cwd: optionalPayloadString(input.rawPayload.cwd),
|
|
458
|
-
|
|
888
|
+
teamRuntimeContextDeliveredAt,
|
|
459
889
|
updatedAt: input.receivedAt ?? new Date().toISOString(),
|
|
460
890
|
};
|
|
461
891
|
|
|
462
|
-
await writeTaskContract(paths.contractPath, contract, { overwrite: true });
|
|
463
892
|
await writeJsonFile(paths.bindingPath, binding);
|
|
464
893
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
"
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
return createRuntimeResult(input, hookOutput(input.event.type, { additionalContext: context }), {
|
|
475
|
-
summary: "User prompt routed through Task Contract.",
|
|
476
|
-
stateWrites: [paths.contractPath, paths.bindingPath],
|
|
894
|
+
let output: Record<string, unknown> | null = null;
|
|
895
|
+
if (teamRuntimeContext !== null) {
|
|
896
|
+
output = appendAdditionalContext(output, input.event.type, teamRuntimeContext);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
return createRuntimeResult(input, output, {
|
|
900
|
+
summary: "User prompt observed; session binding updated.",
|
|
901
|
+
stateWrites: [paths.bindingPath],
|
|
477
902
|
});
|
|
478
903
|
}
|
|
479
904
|
|
|
905
|
+
async function createTeamRuntimeContextForUserPrompt(
|
|
906
|
+
input: HandleHookRuntimeInput,
|
|
907
|
+
): Promise<string | null> {
|
|
908
|
+
const team = resolveTraceTeamContext({
|
|
909
|
+
homeDir: input.homeDir,
|
|
910
|
+
environment: input.environment,
|
|
911
|
+
payload: input.rawPayload,
|
|
912
|
+
});
|
|
913
|
+
if (team === null || team.roleId !== "main") return null;
|
|
914
|
+
|
|
915
|
+
try {
|
|
916
|
+
return await createTeamRoleRuntimeContext({
|
|
917
|
+
homeDir: input.homeDir,
|
|
918
|
+
runId: team.runId,
|
|
919
|
+
roleId: team.roleId,
|
|
920
|
+
});
|
|
921
|
+
} catch {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
480
926
|
async function handlePreToolUse(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
|
|
481
|
-
const contract = await readActiveContract(input.homeDir, input.rawPayload);
|
|
482
927
|
const commandClass =
|
|
483
928
|
typeof input.event.payload.metadata.commandClass === "string"
|
|
484
929
|
? input.event.payload.metadata.commandClass
|
|
485
930
|
: undefined;
|
|
486
|
-
const protectedPath = findProtectedPath(input.rawPayload);
|
|
487
|
-
|
|
488
|
-
if (contract === null) {
|
|
489
|
-
return createRuntimeResult(
|
|
490
|
-
input,
|
|
491
|
-
hookOutput(input.event.type, {
|
|
492
|
-
permissionDecision: "ask",
|
|
493
|
-
permissionDecisionReason: "EvoDev requires an active Task Contract before tool use.",
|
|
494
|
-
additionalContext:
|
|
495
|
-
"Submit the user prompt through UserPromptSubmit to create the Task Contract first.",
|
|
496
|
-
}),
|
|
497
|
-
{ summary: "Tool use requires user confirmation because no active contract exists." },
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
931
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
hookOutput(input.event.type, {
|
|
505
|
-
permissionDecision: "deny",
|
|
506
|
-
permissionDecisionReason: `EvoDev blocked access to protected project asset: ${protectedPath}`,
|
|
507
|
-
}),
|
|
508
|
-
{ summary: "Protected path access denied." },
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (
|
|
513
|
-
commandClass === "delete" ||
|
|
514
|
-
commandClass === "publish" ||
|
|
515
|
-
commandClass === "credential-sensitive" ||
|
|
516
|
-
commandClass === "path-escaping"
|
|
517
|
-
) {
|
|
518
|
-
return createRuntimeResult(
|
|
519
|
-
input,
|
|
520
|
-
hookOutput(input.event.type, {
|
|
521
|
-
permissionDecision: "deny",
|
|
522
|
-
permissionDecisionReason: `EvoDev blocked high-risk tool action: ${commandClass}`,
|
|
523
|
-
}),
|
|
524
|
-
{ summary: "High-risk tool action denied." },
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (commandClass === "network" || commandClass === "unknown" || commandClass === "write") {
|
|
529
|
-
return createRuntimeResult(
|
|
530
|
-
input,
|
|
531
|
-
hookOutput(input.event.type, {
|
|
532
|
-
permissionDecision: "ask",
|
|
533
|
-
permissionDecisionReason: `EvoDev requires confirmation for ${commandClass} action under ${contract.route.mode ?? "unrouted"} mode.`,
|
|
534
|
-
additionalContext: `Active Task Contract: ${contract.taskId}. Confirm scope before continuing.`,
|
|
535
|
-
}),
|
|
536
|
-
{ summary: "Tool use requires user confirmation." },
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
return createRuntimeResult(
|
|
541
|
-
input,
|
|
542
|
-
hookOutput(input.event.type, {
|
|
543
|
-
permissionDecision: "allow",
|
|
544
|
-
permissionDecisionReason: `EvoDev allowed ${commandClass ?? "metadata-only"} action under Task Contract ${contract.taskId}.`,
|
|
545
|
-
}),
|
|
546
|
-
{ summary: "Tool use allowed by EvoDev policy." },
|
|
547
|
-
);
|
|
932
|
+
return createRuntimeResult(input, null, {
|
|
933
|
+
summary: `${input.event.type} observed (${commandClass ?? "metadata-only"}); no permission decision emitted.`,
|
|
934
|
+
});
|
|
548
935
|
}
|
|
549
936
|
|
|
550
937
|
async function handlePostToolUse(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
|
|
551
|
-
|
|
552
|
-
const binding = await readSessionBinding(input.homeDir, input.rawPayload);
|
|
553
|
-
if (contract === null || binding?.contractPath === null || binding?.contractPath === undefined) {
|
|
554
|
-
return handleAdditionalContext(input, input.event.type);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const status = optionalPayloadNumber(input.rawPayload.exit_code ?? input.rawPayload.exitCode);
|
|
558
|
-
const nextContract: TaskContract = {
|
|
559
|
-
...contract,
|
|
560
|
-
evidence: {
|
|
561
|
-
metadataOnly: true,
|
|
562
|
-
items: [
|
|
563
|
-
...contract.evidence.items,
|
|
564
|
-
{
|
|
565
|
-
type: "command-result",
|
|
566
|
-
id: input.event.eventId,
|
|
567
|
-
status: status === 0 ? "pass" : status === null ? "unknown" : "fail",
|
|
568
|
-
summary: input.event.payload.summary,
|
|
569
|
-
rawOutputStored: false,
|
|
570
|
-
sourceContentStored: false,
|
|
571
|
-
},
|
|
572
|
-
],
|
|
573
|
-
},
|
|
574
|
-
};
|
|
575
|
-
await writeTaskContract(binding.contractPath, nextContract, { overwrite: true });
|
|
576
|
-
|
|
577
|
-
return createRuntimeResult(
|
|
578
|
-
input,
|
|
579
|
-
hookOutput(input.event.type, {
|
|
580
|
-
additionalContext: `EvoDev recorded metadata-only evidence for Task Contract ${contract.taskId}. Raw output was not stored.`,
|
|
581
|
-
}),
|
|
582
|
-
{
|
|
583
|
-
summary: "Post-tool metadata evidence recorded.",
|
|
584
|
-
stateWrites: [binding.contractPath],
|
|
585
|
-
},
|
|
586
|
-
);
|
|
938
|
+
return handleAdditionalContext(input, input.event.type);
|
|
587
939
|
}
|
|
588
940
|
|
|
589
|
-
async function
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
!stopHookActive &&
|
|
596
|
-
contract.route.mode === "rigorous" &&
|
|
597
|
-
contract.evidence.items.length === 0
|
|
598
|
-
) {
|
|
599
|
-
return createRuntimeResult(
|
|
600
|
-
input,
|
|
601
|
-
{
|
|
602
|
-
decision: "block",
|
|
603
|
-
reason: "EvoDev rigorous mode requires metadata-only evidence before stopping.",
|
|
604
|
-
...hookOutput(input.event.type, {
|
|
605
|
-
additionalContext:
|
|
606
|
-
"Run the required verification or record metadata-only evidence before finishing.",
|
|
607
|
-
}),
|
|
608
|
-
},
|
|
609
|
-
{ summary: "Completion blocked until evidence exists." },
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
return createRuntimeResult(
|
|
614
|
-
input,
|
|
615
|
-
hookOutput(input.event.type, {
|
|
616
|
-
additionalContext: `EvoDev completion gate checked Task Contract ${contract.taskId}. Evidence items: ${contract.evidence.items.length}.`,
|
|
617
|
-
}),
|
|
618
|
-
{ summary: "Completion gate checked." },
|
|
619
|
-
);
|
|
941
|
+
async function handleCompletionObservation(
|
|
942
|
+
input: HandleHookRuntimeInput,
|
|
943
|
+
): Promise<HookRuntimeResult> {
|
|
944
|
+
return createRuntimeResult(input, null, {
|
|
945
|
+
summary: `${input.event.type} observed as metadata-only hook context.`,
|
|
946
|
+
});
|
|
620
947
|
}
|
|
621
948
|
|
|
622
949
|
async function handlePreCompact(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
|
|
623
|
-
|
|
624
|
-
return createRuntimeResult(
|
|
625
|
-
input,
|
|
626
|
-
hookOutput(input.event.type, {
|
|
627
|
-
additionalContext: binding?.contractPath
|
|
628
|
-
? `Preserve EvoDev Task Contract reference across compaction: ${binding.contractPath}`
|
|
629
|
-
: "No EvoDev Task Contract is active for this session.",
|
|
630
|
-
}),
|
|
631
|
-
{ summary: "PreCompact context prepared." },
|
|
632
|
-
);
|
|
950
|
+
return createRuntimeResult(input, null, { summary: "PreCompact observed." });
|
|
633
951
|
}
|
|
634
952
|
|
|
635
953
|
async function handleSessionEnd(input: HandleHookRuntimeInput): Promise<HookRuntimeResult> {
|
|
@@ -656,20 +974,16 @@ function handleAdditionalContext(
|
|
|
656
974
|
eventName: CanonicalHookEventType,
|
|
657
975
|
): Promise<HookRuntimeResult> {
|
|
658
976
|
return Promise.resolve(
|
|
659
|
-
createRuntimeResult(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
additionalContext: `EvoDev processed ${eventName} as metadata-only hook context.`,
|
|
663
|
-
}),
|
|
664
|
-
{ summary: `${eventName} processed as metadata-only context.` },
|
|
665
|
-
),
|
|
977
|
+
createRuntimeResult(input, null, {
|
|
978
|
+
summary: `${eventName} observed as metadata-only hook context.`,
|
|
979
|
+
}),
|
|
666
980
|
);
|
|
667
981
|
}
|
|
668
982
|
|
|
669
983
|
function createRuntimeResult(
|
|
670
984
|
input: HandleHookRuntimeInput,
|
|
671
985
|
output: Record<string, unknown> | null,
|
|
672
|
-
options: { summary: string; stateWrites?: string[] },
|
|
986
|
+
options: { summary: string; stateWrites?: string[]; warnings?: string[] },
|
|
673
987
|
): HookRuntimeResult {
|
|
674
988
|
return {
|
|
675
989
|
target: input.target,
|
|
@@ -677,6 +991,7 @@ function createRuntimeResult(
|
|
|
677
991
|
enabled: true,
|
|
678
992
|
output,
|
|
679
993
|
stateWrites: options.stateWrites ?? [],
|
|
994
|
+
warnings: options.warnings ?? [],
|
|
680
995
|
summary: options.summary,
|
|
681
996
|
};
|
|
682
997
|
}
|
|
@@ -693,96 +1008,101 @@ function hookOutput(
|
|
|
693
1008
|
};
|
|
694
1009
|
}
|
|
695
1010
|
|
|
696
|
-
function
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1011
|
+
function appendAdditionalContext(
|
|
1012
|
+
output: Record<string, unknown> | null,
|
|
1013
|
+
eventName: CanonicalHookEventType,
|
|
1014
|
+
context: string,
|
|
1015
|
+
): Record<string, unknown> {
|
|
1016
|
+
if (output === null) return hookOutput(eventName, { additionalContext: context });
|
|
1017
|
+
const hookSpecificOutput = isRecord(output.hookSpecificOutput) ? output.hookSpecificOutput : {};
|
|
1018
|
+
const previous =
|
|
1019
|
+
typeof hookSpecificOutput.additionalContext === "string"
|
|
1020
|
+
? hookSpecificOutput.additionalContext
|
|
1021
|
+
: "";
|
|
708
1022
|
return {
|
|
709
|
-
...
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
constraints: [
|
|
718
|
-
`prompt-kind:${classification.kind}`,
|
|
719
|
-
...classification.riskTerms.map((term) => `risk:${term}`),
|
|
720
|
-
],
|
|
721
|
-
},
|
|
722
|
-
scope: {
|
|
723
|
-
...contract.scope,
|
|
724
|
-
requiresUserConfirmation: classification.riskTerms,
|
|
725
|
-
},
|
|
726
|
-
context: {
|
|
727
|
-
...contract.context,
|
|
728
|
-
assumptions: [
|
|
729
|
-
`hook-session:${sessionKey}`,
|
|
730
|
-
`cwd:${optionalPayloadString(input.rawPayload.cwd) ?? "unknown"}`,
|
|
731
|
-
],
|
|
732
|
-
openQuestions: classification.needsClarification
|
|
733
|
-
? ["User request may need clarification before broad changes."]
|
|
734
|
-
: [],
|
|
1023
|
+
...output,
|
|
1024
|
+
hookSpecificOutput: {
|
|
1025
|
+
...hookSpecificOutput,
|
|
1026
|
+
hookEventName:
|
|
1027
|
+
typeof hookSpecificOutput.hookEventName === "string"
|
|
1028
|
+
? hookSpecificOutput.hookEventName
|
|
1029
|
+
: eventName,
|
|
1030
|
+
additionalContext: previous === "" ? context : `${previous}\n\n${context}`,
|
|
735
1031
|
},
|
|
736
1032
|
};
|
|
737
1033
|
}
|
|
738
1034
|
|
|
739
|
-
function
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
"security",
|
|
751
|
-
"release",
|
|
752
|
-
"publish",
|
|
753
|
-
"hook",
|
|
754
|
-
"memory",
|
|
755
|
-
"learning",
|
|
756
|
-
"secret",
|
|
757
|
-
"privacy",
|
|
758
|
-
].filter((term) => text.includes(term));
|
|
759
|
-
let kind = "feature";
|
|
760
|
-
if (/\bbug|fix|error|failed|failure\b/.test(text)) kind = "bugfix";
|
|
761
|
-
if (/\brefactor|migration|migrate\b/.test(text)) kind = "refactor";
|
|
762
|
-
if (/\breview|audit\b/.test(text)) kind = "review";
|
|
763
|
-
if (/\btest|coverage\b/.test(text)) kind = "test";
|
|
764
|
-
if (/\bdoc|readme|guide\b/.test(text)) kind = "docs";
|
|
765
|
-
if (riskTerms.includes("security") || riskTerms.includes("privacy")) kind = "security";
|
|
766
|
-
if (riskTerms.includes("release") || riskTerms.includes("publish")) kind = "release";
|
|
1035
|
+
function appendDevelopmentHookDiagnostics(
|
|
1036
|
+
input: HandleHookRuntimeInput,
|
|
1037
|
+
result: HookRuntimeResult,
|
|
1038
|
+
): HookRuntimeResult {
|
|
1039
|
+
if (
|
|
1040
|
+
input.teamRuntimeDisplayMode !== "development" ||
|
|
1041
|
+
COMPLETION_EVENTS_WITHOUT_DEVELOPMENT_DIAGNOSTICS.has(input.event.type) ||
|
|
1042
|
+
!canDeliverTeamMessagesFromHook(input.target, input.event.type)
|
|
1043
|
+
) {
|
|
1044
|
+
return result;
|
|
1045
|
+
}
|
|
767
1046
|
return {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1047
|
+
...result,
|
|
1048
|
+
output: appendAdditionalContext(
|
|
1049
|
+
result.output,
|
|
1050
|
+
input.event.type,
|
|
1051
|
+
formatDevelopmentHookDiagnostics(input),
|
|
1052
|
+
),
|
|
771
1053
|
};
|
|
772
1054
|
}
|
|
773
1055
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1056
|
+
function isActiveClaudeStopHook(input: HandleHookRuntimeInput): boolean {
|
|
1057
|
+
return (
|
|
1058
|
+
input.target === "claude" &&
|
|
1059
|
+
(input.event.type === "Stop" || input.event.type === "SubagentStop") &&
|
|
1060
|
+
input.rawPayload.stop_hook_active === true
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function formatDevelopmentHookDiagnostics(input: HandleHookRuntimeInput): string {
|
|
1065
|
+
const metadata = Object.entries(input.event.payload.metadata);
|
|
1066
|
+
const inputFields = Object.keys(input.rawPayload)
|
|
1067
|
+
.map((field) => field.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 80))
|
|
1068
|
+
.filter((field) => field.length > 0)
|
|
1069
|
+
.slice(0, 30);
|
|
1070
|
+
return [
|
|
1071
|
+
"EvoDev hook development diagnostics",
|
|
1072
|
+
`Target: ${input.target}`,
|
|
1073
|
+
`Event: ${input.event.type}`,
|
|
1074
|
+
`Summary: ${input.event.payload.summary}`,
|
|
1075
|
+
`Input fields: ${inputFields.join(", ") || "none"}`,
|
|
1076
|
+
"Normalized metadata:",
|
|
1077
|
+
...(metadata.length === 0
|
|
1078
|
+
? ["- none"]
|
|
1079
|
+
: metadata.map(([key, value]) => `- ${key}: ${formatMetadataValue(value)}`)),
|
|
1080
|
+
`Redactions: ${input.event.payload.redactions.join(", ") || "none"}`,
|
|
1081
|
+
"Raw payload included: false",
|
|
1082
|
+
].join("\n");
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function formatTeamInboxContext(messages: TeamMessageRecord[]): string {
|
|
1086
|
+
const blocks = messages.map((message) =>
|
|
1087
|
+
[
|
|
1088
|
+
"[EvoDev team message]",
|
|
1089
|
+
"Instruction: read this queued team message at this safe point; reply through Teams MCP only when a response is needed.",
|
|
1090
|
+
`from: ${message.fromRoleId}`,
|
|
1091
|
+
`to: ${message.toRoleId}`,
|
|
1092
|
+
`type: ${message.type}`,
|
|
1093
|
+
`messageId: ${message.messageId}`,
|
|
1094
|
+
`cc: ${message.ccRoleIds.join(",") || "none"}`,
|
|
1095
|
+
"",
|
|
1096
|
+
truncateTeamMessageBody(message.body),
|
|
1097
|
+
"[/EvoDev team message]",
|
|
1098
|
+
].join("\n"),
|
|
1099
|
+
);
|
|
1100
|
+
return ["EvoDev team inbox:", ...blocks].join("\n\n");
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function truncateTeamMessageBody(value: string): string {
|
|
1104
|
+
if (value.length <= 4000) return value;
|
|
1105
|
+
return `${value.slice(0, 4000)}...[truncated:${value.length - 4000}]`;
|
|
786
1106
|
}
|
|
787
1107
|
|
|
788
1108
|
async function readSessionBinding(
|
|
@@ -803,7 +1123,7 @@ function resolveHookSessionKey(payload: Record<string, unknown>): string {
|
|
|
803
1123
|
const sessionId = optionalPayloadString(payload.session_id ?? payload.sessionId);
|
|
804
1124
|
const cwd = optionalPayloadString(payload.cwd);
|
|
805
1125
|
const source = sessionId ?? cwd ?? "local";
|
|
806
|
-
return `session-${
|
|
1126
|
+
return `session-${sha256Short(source)}`;
|
|
807
1127
|
}
|
|
808
1128
|
|
|
809
1129
|
async function writeJsonFile(path: string, value: unknown): Promise<void> {
|
|
@@ -811,36 +1131,12 @@ async function writeJsonFile(path: string, value: unknown): Promise<void> {
|
|
|
811
1131
|
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
812
1132
|
}
|
|
813
1133
|
|
|
814
|
-
function findProtectedPath(payload: Record<string, unknown>): string | null {
|
|
815
|
-
const candidates = collectStringValues(payload).filter((value) => value.length < 500);
|
|
816
|
-
for (const candidate of candidates) {
|
|
817
|
-
if (
|
|
818
|
-
candidate === "CLAUDE.md" ||
|
|
819
|
-
candidate === "AGENTS.md" ||
|
|
820
|
-
candidate.includes("/CLAUDE.md") ||
|
|
821
|
-
candidate.includes("/AGENTS.md") ||
|
|
822
|
-
candidate.includes(".claude/") ||
|
|
823
|
-
candidate.includes(".codex/")
|
|
824
|
-
) {
|
|
825
|
-
return candidate;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
function collectStringValues(value: unknown): string[] {
|
|
832
|
-
if (typeof value === "string") return [value];
|
|
833
|
-
if (Array.isArray(value)) return value.flatMap((item) => collectStringValues(item));
|
|
834
|
-
if (isRecord(value)) return Object.values(value).flatMap((item) => collectStringValues(item));
|
|
835
|
-
return [];
|
|
836
|
-
}
|
|
837
|
-
|
|
838
1134
|
function optionalPayloadString(value: unknown): string | null {
|
|
839
1135
|
return typeof value === "string" && value.length > 0 ? value.slice(0, 300) : null;
|
|
840
1136
|
}
|
|
841
1137
|
|
|
842
|
-
function
|
|
843
|
-
return
|
|
1138
|
+
function safeDiagnosticId(value: string): string {
|
|
1139
|
+
return value.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 120) || "unknown";
|
|
844
1140
|
}
|
|
845
1141
|
|
|
846
1142
|
function isNotFoundError(error: unknown): boolean {
|
|
@@ -892,9 +1188,7 @@ function extractMetadata(
|
|
|
892
1188
|
payload: Record<string, unknown>,
|
|
893
1189
|
redactions: string[],
|
|
894
1190
|
): Record<string, string | number | boolean | string[]> {
|
|
895
|
-
const metadata: Record<string, string | number | boolean | string[]> = {
|
|
896
|
-
rawContentIncluded: false,
|
|
897
|
-
};
|
|
1191
|
+
const metadata: Record<string, string | number | boolean | string[]> = {};
|
|
898
1192
|
const toolInput = isRecord(payload.tool_input)
|
|
899
1193
|
? payload.tool_input
|
|
900
1194
|
: isRecord(payload.toolInput)
|
|
@@ -932,30 +1226,12 @@ function extractMetadata(
|
|
|
932
1226
|
}
|
|
933
1227
|
|
|
934
1228
|
function decideHookPolicy(commandClass: string | undefined): HookEventV1["decision"] {
|
|
935
|
-
if (
|
|
936
|
-
commandClass === "delete" ||
|
|
937
|
-
commandClass === "network" ||
|
|
938
|
-
commandClass === "publish" ||
|
|
939
|
-
commandClass === "credential-sensitive" ||
|
|
940
|
-
commandClass === "path-escaping" ||
|
|
941
|
-
commandClass === "unknown"
|
|
942
|
-
) {
|
|
943
|
-
return {
|
|
944
|
-
action: "ask-user",
|
|
945
|
-
reason: `Command risk requires explicit confirmation: ${commandClass}`,
|
|
946
|
-
requiresUserConfirmation: true,
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
if (commandClass === "write") {
|
|
950
|
-
return {
|
|
951
|
-
action: "warn",
|
|
952
|
-
reason: "Write-like command requires scope review.",
|
|
953
|
-
requiresUserConfirmation: true,
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
1229
|
return {
|
|
957
1230
|
action: "allow",
|
|
958
|
-
reason:
|
|
1231
|
+
reason:
|
|
1232
|
+
commandClass === undefined
|
|
1233
|
+
? "Observed metadata only; EvoDev does not stop hook execution."
|
|
1234
|
+
: `Observed ${commandClass} tool action; EvoDev does not stop hook execution.`,
|
|
959
1235
|
requiresUserConfirmation: false,
|
|
960
1236
|
};
|
|
961
1237
|
}
|
|
@@ -966,7 +1242,7 @@ function summarizeEvent(
|
|
|
966
1242
|
): string {
|
|
967
1243
|
if (type === "PreToolUse") return `Tool use requested (${metadata.commandClass ?? "unknown"}).`;
|
|
968
1244
|
if (type === "PostToolUse") return "Tool use completed with metadata-only result.";
|
|
969
|
-
if (type === "UserPromptSubmit") return "User prompt submitted
|
|
1245
|
+
if (type === "UserPromptSubmit") return "User prompt submitted.";
|
|
970
1246
|
if (type === "SubagentStop") return "Subagent stopped; transcript omitted.";
|
|
971
1247
|
return `${type} received; metadata-only dry-run.`;
|
|
972
1248
|
}
|
|
@@ -1001,7 +1277,7 @@ function stableEventId(
|
|
|
1001
1277
|
|
|
1002
1278
|
function hashOptionalIdentifier(value: unknown): string | null {
|
|
1003
1279
|
if (typeof value !== "string" || value.length === 0) return null;
|
|
1004
|
-
return `sha256-${
|
|
1280
|
+
return `sha256-${sha256Short(value)}`;
|
|
1005
1281
|
}
|
|
1006
1282
|
|
|
1007
1283
|
function optionalBoolean(value: unknown, fallback: boolean, path: string): boolean {
|