@orchestrator-claude/definitions 3.37.0 → 3.37.1
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/agents/adversarial-reviewer.md +6 -1
- package/agents/api-extractor.md +6 -0
- package/agents/azure-devops-engineer.md +1 -2
- package/agents/business-rule-miner.md +6 -0
- package/agents/cicd-platform-engineer.md +1 -2
- package/agents/ciclo-engineer.md +15 -2
- package/agents/code-archaeologist.md +6 -0
- package/agents/debug-sidecar.md +6 -0
- package/agents/doc-sidecar.md +6 -0
- package/agents/docs-guardian.md +9 -3
- package/agents/frontend-engineer.md +1 -2
- package/agents/{ai-architect.md → harness-architect.md} +8 -3
- package/agents/implementer.md +6 -1
- package/agents/legacy-discoverer.md +6 -0
- package/agents/legacy-synthesizer.md +6 -0
- package/agents/methodology-researcher.md +6 -1
- package/agents/operational-artifact-builder.md +6 -1
- package/agents/orchestrator.md +27 -87
- package/agents/planner.md +6 -1
- package/agents/presentation-engineer.md +5 -4
- package/agents/python-data-engineer.md +0 -1
- package/agents/researcher.md +6 -0
- package/agents/reviewer.md +6 -1
- package/agents/schema-extractor.md +6 -0
- package/agents/software-architect.md +10 -5
- package/agents/specifier.md +6 -1
- package/agents/task-generator.md +6 -1
- package/agents/test-sidecar.md +6 -0
- package/dist/scripts/generate-hooks-matcher.d.ts +106 -0
- package/dist/scripts/generate-hooks-matcher.d.ts.map +1 -0
- package/dist/scripts/generate-hooks-matcher.js +153 -0
- package/dist/scripts/generate-hooks-matcher.js.map +1 -0
- package/dist/scripts/generate-manifest.d.ts +22 -0
- package/dist/scripts/generate-manifest.d.ts.map +1 -1
- package/dist/scripts/generate-manifest.js +67 -2
- package/dist/scripts/generate-manifest.js.map +1 -1
- package/hooks/agent-git-guard.mjs +63 -11
- package/hooks/dangling-guard.mjs +42 -12
- package/hooks/gate-guardian.mjs +38 -8
- package/hooks/hooks.json +3 -9
- package/hooks/lib/api-client.ts +128 -9
- package/hooks/lib/capability-scanner.ts +391 -55
- package/hooks/lib/dispatch-marker.ts +214 -0
- package/hooks/lib/git-checkpoint.ts +34 -37
- package/hooks/lib/observations-helper.ts +14 -1
- package/hooks/mailbox-listener.mjs +69 -17
- package/hooks/playwright-output-guard.mjs +40 -10
- package/hooks/post-implement.mjs +38 -8
- package/hooks/pre-agent-dispatch.mjs +137 -16
- package/hooks/pre-agent-dispatch.ts +82 -15
- package/hooks/session-start.mjs +274 -103
- package/hooks/session-start.ts +250 -69
- package/hooks/subagent-start.mjs +7665 -22
- package/hooks/subagent-start.ts +367 -16
- package/hooks/subagent-stop.mjs +102 -62
- package/hooks/subagent-stop.ts +124 -28
- package/hooks/user-prompt.mjs +43 -13
- package/hooks/verification-before-completion.mjs +63 -11
- package/hooks/workflow-guard.mjs +45 -11
- package/hooks-settings-template.json +1 -7
- package/manifest.json +62 -62
- package/package.json +4 -2
- package/skills/checkpoint-protocol/SKILL.md +19 -4
- package/skills/ciclo-lane-dispatch/SKILL.md +24 -0
- package/skills/fase-1b-projecao/SKILL.md +146 -0
- package/skills/orchestrator/SKILL.md +11 -3
- package/skills/workflow-coordination/SKILL.md +3 -3
- package/hooks/post-compact.mjs +0 -7931
- package/hooks/post-compact.ts +0 -170
package/hooks/subagent-start.ts
CHANGED
|
@@ -2,8 +2,113 @@
|
|
|
2
2
|
* subagent-start.ts — SubagentStart hook (RFC-022 Phase 2)
|
|
3
3
|
* Replaces track-agent-invocation.sh start
|
|
4
4
|
* Registers agent invocation via REST API.
|
|
5
|
+
*
|
|
6
|
+
* TD-214/TD-239 TASK-004 (RF-007 core, identity point #3 of the
|
|
7
|
+
* consolidator — see packages/definitions/tests/IdentityNormalizationConsolidator.test.ts):
|
|
8
|
+
* this is the PRIMARY critical path for invocation tracking — every
|
|
9
|
+
* RF-002/RF-004 telemetry signal downstream depends on this hook correctly
|
|
10
|
+
* identifying WHICH agent was dispatched. Two defects fixed here:
|
|
11
|
+
*
|
|
12
|
+
* 1. Precedence chain used to fall back to a literal `'unknown'` string that
|
|
13
|
+
* was then tracked via startInvocation() as if it were a genuine agent
|
|
14
|
+
* name — a silent fallback promoted to a real identity. The chain is now
|
|
15
|
+
* `agent_type -> tool_input.subagent_type -> subagent_type ->
|
|
16
|
+
* tool_input.type`, with NO trailing `|| 'unknown'`; an all-absent read
|
|
17
|
+
* resolves to `''`, which the defensive filter below treats as a read
|
|
18
|
+
* FAILURE (not a legitimate agent), not a would-be lookup key.
|
|
19
|
+
* 2. No namespace normalization: under plugin scope the harness sends
|
|
20
|
+
* `orchestrator:reviewer`; PHASE_MAP only has bare keys (`reviewer`), so
|
|
21
|
+
* a namespaced dispatch silently fell through to
|
|
22
|
+
* `PHASE_MAP[agentType] || agentType` and got the WRONG phase (the raw
|
|
23
|
+
* namespaced string itself). `normalizeAgentName` (moved to
|
|
24
|
+
* `lib/api-client.ts` in TASK-001) is applied immediately after the read.
|
|
25
|
+
*
|
|
26
|
+
* Defensive filter (classifyAgentType), applied to the normalized value:
|
|
27
|
+
* - `''` or literal `'unknown'` -> the read genuinely failed (no field in
|
|
28
|
+
* the 4-deep chain produced anything usable). Logged at [ERROR] — elevated
|
|
29
|
+
* from [WARN] per RNF-003, because this is a signal that the upstream
|
|
30
|
+
* payload shape changed, not an expected/everyday case. `startInvocation`
|
|
31
|
+
* is never called.
|
|
32
|
+
* - non-empty but NOT a member of the canonical agent set -> a real Task
|
|
33
|
+
* dispatch that simply isn't one of THIS project's workflow agents (the
|
|
34
|
+
* common case: `general-purpose`, or any other ad-hoc subagent_type).
|
|
35
|
+
* Logged at [DEBUG] — this is normal, not a failure. `startInvocation` is
|
|
36
|
+
* never called (there is nothing meaningful to track invocation-wise for
|
|
37
|
+
* an agent this hook doesn't recognize as a workflow participant).
|
|
38
|
+
* - a member of the canonical set -> proceeds exactly as before (phase
|
|
39
|
+
* lookup, startInvocation, writeInvocationState).
|
|
40
|
+
*
|
|
41
|
+
* These two branches are checked in THIS order (read-failure first) — '' and
|
|
42
|
+
* 'unknown' can never be members of the canonical set, so checking set
|
|
43
|
+
* membership first would make the [ERROR] branch permanently unreachable.
|
|
44
|
+
*
|
|
45
|
+
* Canonical agent set (ADR-023 plan ADR-P2, measured empirically S433):
|
|
46
|
+
* `packages/definitions/agents/` (28 `.md` files today) — DELIBERATELY NOT
|
|
47
|
+
* the `AGENTS_DIR = join(PROJECT_ROOT, '.claude', 'agents')` pattern used by
|
|
48
|
+
* session-start.ts. In THIS repo (ADR-023 dogfood — the
|
|
49
|
+
* canonical source lives here, nothing is "installed" into `.claude/agents/`)
|
|
50
|
+
* that path resolves to a directory with 0 `.md` files, so reusing it here
|
|
51
|
+
* would make `classifyAgentType` reject every real agent (fail-closed) or,
|
|
52
|
+
* if the check were inverted defensively, accept everything (fail-open) —
|
|
53
|
+
* both wrong. That gap is TD-243, filed and explicitly OUT OF SCOPE for this
|
|
54
|
+
* task; `AGENTS_DIR`'s call site in session-start.ts is
|
|
55
|
+
* NOT touched here. `resolveCanonicalAgentsDir` below resolves the path
|
|
56
|
+
* independently, local to this file.
|
|
57
|
+
*
|
|
58
|
+
* TD-214/TD-239 TASK-005 (RF-007 instrumentation, one layer above TASK-004):
|
|
59
|
+
* captures the FULL raw stdin payload — not just the 4 fields the precedence
|
|
60
|
+
* chain above extracts — into a DEDICATED log, `hook-raw-payloads.jsonl`,
|
|
61
|
+
* kept SEPARATE from `hook-debug.log` (retention-cost requirement, not
|
|
62
|
+
* style: this log is high-volume and `hook-debug.log` is read for other
|
|
63
|
+
* purposes). Every line also carries `correlatedWithDispatchIntent`, sourced
|
|
64
|
+
* from `hasFreshDispatchIntent(workflow.id)` (TD-205) — consumed VERBATIM
|
|
65
|
+
* from `lib/api-client.ts` (hard restriction: that file is CONSUMED, not
|
|
66
|
+
* modified, here; its signature/behavior are unchanged, since
|
|
67
|
+
* `dangling-guard.ts` rule 6 already depends on both).
|
|
68
|
+
*
|
|
69
|
+
* Capture fires on EVERY hook trigger — read-failure, unrecognized-agent,
|
|
70
|
+
* no-active-workflow, and proceed branches alike — deliberately, because the
|
|
71
|
+
* RF-011/TASK-012 consumer this log is written for needs the raw payload
|
|
72
|
+
* precisely for the cases where identity extraction FAILED or produced an
|
|
73
|
+
* unexpected value (the bug class this whole workflow exists to diagnose),
|
|
74
|
+
* not only for the successful path. To support this without a duplicate
|
|
75
|
+
* `getActiveWorkflow()` round-trip, the workflow lookup that previously
|
|
76
|
+
* happened only in the `proceed` branch now happens ONCE, up front, and is
|
|
77
|
+
* reused both for the correlation flag and for the pre-existing
|
|
78
|
+
* `startInvocation` gating further down.
|
|
79
|
+
*
|
|
80
|
+
* `hook-raw-payloads.jsonl`'s location is NOT obtained from
|
|
81
|
+
* `lib/api-client.ts` (its `STATE_DIR`/`DEBUG_LOG` constants are not
|
|
82
|
+
* exported, and this task must not touch that file) — `resolveStateDir`
|
|
83
|
+
* below duplicates the same override-then-sentinel derivation
|
|
84
|
+
* (`ORCHESTRATOR_STATE_DIR` env override, else `.orchestrator/.state` under
|
|
85
|
+
* the resolved project root) that `lib/api-client.ts` uses internally for
|
|
86
|
+
* `STATE_DIR`, mirroring the `resolveCanonicalAgentsDir` "resolve locally,
|
|
87
|
+
* don't touch the lib" precedent from TASK-004.
|
|
88
|
+
*
|
|
89
|
+
* TD-214/TD-239 TASK-010 (RF-010 Fase 5 Parte 3 — wiring): once
|
|
90
|
+
* `classifyAgentType` reaches `decision.kind === 'proceed'` (RF-007
|
|
91
|
+
* successfully resolved `resolvedAgentType`), reads
|
|
92
|
+
* `<resolveAgentMemoryBaseDir(cwd)>/<resolvedAgentType>/memory-index.yaml`
|
|
93
|
+
* (same `resolveAgentMemoryBaseDir` already injected by
|
|
94
|
+
* subagent-stop.ts/TASK-006 for the identical purpose), composes via
|
|
95
|
+
* `filterRelevantMemories` (TASK-008, HIGH-001 quota fix) and
|
|
96
|
+
* `formatMemoryContext` (TASK-009, per-entry cap + aggregate budget), and
|
|
97
|
+
* injects the result via `outputContext('SubagentStart', ...)` — but ONLY
|
|
98
|
+
* when the formatted string is non-empty (an index that reads but composes
|
|
99
|
+
* to nothing, e.g. every entry stale and none is `feedback`, must not emit a
|
|
100
|
+
* hollow `[MEMORY-CONTEXT]` block).
|
|
101
|
+
*
|
|
102
|
+
* Gated SOLELY on identity resolution (the RF-007->RF-010 hard dependency —
|
|
103
|
+
* TASK-011 proves this at the integration level: a payload RF-007 rejects
|
|
104
|
+
* must produce zero `outputContext` calls anywhere in the pipeline), NOT on
|
|
105
|
+
* an active workflow existing: memory belongs to the agent's own identity,
|
|
106
|
+
* not to workflow-tracking state, so this runs BEFORE the `if (!workflow)`
|
|
107
|
+
* early-return below and independently of it.
|
|
5
108
|
*/
|
|
6
109
|
|
|
110
|
+
import { join, dirname } from 'node:path';
|
|
111
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
7
112
|
import {
|
|
8
113
|
log,
|
|
9
114
|
readStdin,
|
|
@@ -11,7 +116,20 @@ import {
|
|
|
11
116
|
getActiveWorkflow,
|
|
12
117
|
startInvocation,
|
|
13
118
|
writeInvocationState,
|
|
119
|
+
normalizeAgentName,
|
|
120
|
+
resolveHookProjectRoot,
|
|
121
|
+
hasFreshDispatchIntent,
|
|
122
|
+
resolveAgentMemoryBaseDir,
|
|
123
|
+
outputContext,
|
|
14
124
|
} from './lib/api-client.js';
|
|
125
|
+
import {
|
|
126
|
+
scanAgentDefinitions,
|
|
127
|
+
readMemoryIndex,
|
|
128
|
+
filterRelevantMemories,
|
|
129
|
+
formatMemoryContext,
|
|
130
|
+
type MemoryEntry,
|
|
131
|
+
type MemoryIndexDiagnostic,
|
|
132
|
+
} from './lib/capability-scanner.js';
|
|
15
133
|
|
|
16
134
|
const HOOK = 'SUBAGENT-START';
|
|
17
135
|
|
|
@@ -24,39 +142,272 @@ const PHASE_MAP: Record<string, string> = {
|
|
|
24
142
|
reviewer: 'review',
|
|
25
143
|
};
|
|
26
144
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
145
|
+
// ─────────────────────────────────────────────────────────────
|
|
146
|
+
// Pure helpers (exported for testing)
|
|
147
|
+
// ─────────────────────────────────────────────────────────────
|
|
30
148
|
|
|
31
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Reads the dispatched agent's raw (un-normalized) identity from stdin via
|
|
151
|
+
* the 4-field precedence chain. Returns `''` when none of the fields are
|
|
152
|
+
* present — deliberately NOT `'unknown'`; a silent placeholder fallback is
|
|
153
|
+
* exactly the defect this task fixes (see module doc).
|
|
154
|
+
*/
|
|
155
|
+
export function resolveRawAgentType(stdin: Record<string, unknown>): string {
|
|
156
|
+
return (
|
|
157
|
+
getField(stdin, 'agent_type') ||
|
|
32
158
|
getField(stdin, 'tool_input.subagent_type') ||
|
|
33
159
|
getField(stdin, 'subagent_type') ||
|
|
34
|
-
getField(stdin, 'tool_input.type')
|
|
35
|
-
|
|
160
|
+
getField(stdin, 'tool_input.type')
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type AgentTypeDecision =
|
|
165
|
+
| { kind: 'skip-read-failed' }
|
|
166
|
+
| { kind: 'skip-unrecognized'; agentType: string }
|
|
167
|
+
| { kind: 'proceed'; agentType: string };
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Builds one JSONL line for `hook-raw-payloads.jsonl` (TASK-005, RF-007):
|
|
171
|
+
* the FULL raw stdin payload (verbatim, not filtered to the 4 identity
|
|
172
|
+
* fields) plus the dispatch-intent correlation flag. Pure — no IO — so it is
|
|
173
|
+
* independently testable and reusable by whoever writes the RF-011/TASK-012
|
|
174
|
+
* consumer (economia deliberada per the dispatch: parse this file instead of
|
|
175
|
+
* re-reading stdin / re-calling `hasFreshDispatchIntent`).
|
|
176
|
+
*/
|
|
177
|
+
export function buildRawPayloadLogLine(
|
|
178
|
+
payload: Record<string, unknown>,
|
|
179
|
+
correlatedWithDispatchIntent: boolean,
|
|
180
|
+
nowIso: string = new Date().toISOString()
|
|
181
|
+
): string {
|
|
182
|
+
return JSON.stringify({
|
|
183
|
+
timestamp: nowIso,
|
|
184
|
+
hook: HOOK,
|
|
185
|
+
payload,
|
|
186
|
+
correlatedWithDispatchIntent,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
36
189
|
|
|
37
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Defensive filter over an ALREADY-NORMALIZED agent type (see module doc for
|
|
192
|
+
* branch ordering rationale). `getCanonicalAgentNames` is only invoked when
|
|
193
|
+
* `agentType` is non-empty and not the literal `'unknown'` — no need to pay
|
|
194
|
+
* for a directory scan on a read that already failed.
|
|
195
|
+
*/
|
|
196
|
+
export function classifyAgentType(
|
|
197
|
+
agentType: string,
|
|
198
|
+
getCanonicalAgentNames: () => string[]
|
|
199
|
+
): AgentTypeDecision {
|
|
200
|
+
if (agentType === '' || agentType === 'unknown') {
|
|
201
|
+
return { kind: 'skip-read-failed' };
|
|
202
|
+
}
|
|
203
|
+
const canonicalAgents = getCanonicalAgentNames();
|
|
204
|
+
if (!canonicalAgents.includes(agentType)) {
|
|
205
|
+
return { kind: 'skip-unrecognized', agentType };
|
|
206
|
+
}
|
|
207
|
+
return { kind: 'proceed', agentType };
|
|
208
|
+
}
|
|
38
209
|
|
|
39
|
-
|
|
210
|
+
// ─────────────────────────────────────────────────────────────
|
|
211
|
+
// Dependency injection interface (enables testing without IO)
|
|
212
|
+
// ─────────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
export interface HookDeps {
|
|
215
|
+
readStdin: () => Promise<Record<string, unknown>>;
|
|
216
|
+
log: (prefix: string, msg: string) => void;
|
|
217
|
+
getActiveWorkflow: () => Promise<{ id: string } | null>;
|
|
218
|
+
startInvocation: (agentType: string, workflowId: string, phase: string) => Promise<string | null>;
|
|
219
|
+
writeInvocationState: (workflowId: string, agentType: string, invocationId: string) => boolean;
|
|
220
|
+
/** Returns the canonical agent-name roster (see module doc, ADR-P2). */
|
|
221
|
+
getCanonicalAgentNames: () => string[];
|
|
222
|
+
/**
|
|
223
|
+
* TASK-005 (RF-007, TD-205): dispatch-intent freshness check. Wired
|
|
224
|
+
* directly to `lib/api-client.ts`'s `hasFreshDispatchIntent` — same
|
|
225
|
+
* function, same signature, same behavior as the `dangling-guard.ts` rule
|
|
226
|
+
* 6 consumer; this hook must not (and does not) alter either.
|
|
227
|
+
*/
|
|
228
|
+
hasFreshDispatchIntent: (workflowId: string) => boolean;
|
|
229
|
+
/**
|
|
230
|
+
* TASK-005 (RF-007): appends one pre-built JSONL line to
|
|
231
|
+
* `hook-raw-payloads.jsonl` — a channel SEPARATE from `log`/
|
|
232
|
+
* `hook-debug.log` (see module doc for the retention-cost rationale).
|
|
233
|
+
*/
|
|
234
|
+
appendRawPayloadLog: (line: string) => void;
|
|
235
|
+
/**
|
|
236
|
+
* TASK-010 (RF-010 Parte 3): resolves the `.claude/agent-memory` base dir
|
|
237
|
+
* for the CURRENT hook invocation — same function/contract already
|
|
238
|
+
* injected by subagent-stop.ts (TASK-006).
|
|
239
|
+
*/
|
|
240
|
+
resolveAgentMemoryBaseDir: (cwd: string) => string;
|
|
241
|
+
/** TASK-010: reads `<baseDir>/<resolvedAgentType>/memory-index.yaml` (TASK-026 dual-shape parser, fail-open). */
|
|
242
|
+
readMemoryIndex: (indexPath: string, diag?: MemoryIndexDiagnostic) => MemoryEntry[] | null;
|
|
243
|
+
/** TASK-010: composition (TASK-008, HIGH-001 quota fix — reserves slots per non-feedback type). */
|
|
244
|
+
filterRelevantMemories: (entries: MemoryEntry[]) => MemoryEntry[];
|
|
245
|
+
/** TASK-010: formatting (TASK-009 — per-entry char cap + aggregate char budget). */
|
|
246
|
+
formatMemoryContext: (selected: MemoryEntry[], allEntries: MemoryEntry[]) => string;
|
|
247
|
+
/** TASK-010: injects the formatted memory context into the dispatched sub-agent's turn. */
|
|
248
|
+
outputContext: (hookEvent: string, context: string) => void;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** `packages/definitions/agents` under the resolved project root (module doc, ADR-P2). */
|
|
252
|
+
function resolveCanonicalAgentsDir(): string {
|
|
253
|
+
const root = resolveHookProjectRoot(process.cwd()) ?? process.cwd();
|
|
254
|
+
return join(root, 'packages', 'definitions', 'agents');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function getCanonicalAgentNamesDefault(): string[] {
|
|
258
|
+
return scanAgentDefinitions(resolveCanonicalAgentsDir());
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Duplicates `lib/api-client.ts`'s internal `STATE_DIR` derivation (TD-224
|
|
263
|
+
* Achado 1: `ORCHESTRATOR_STATE_DIR` override wins outright, else
|
|
264
|
+
* `.orchestrator/.state` under the resolved project root) WITHOUT importing
|
|
265
|
+
* it — `STATE_DIR` is not exported, and this task's hard restriction forbids
|
|
266
|
+
* touching `lib/api-client.ts`. Same "resolve locally, don't touch the lib"
|
|
267
|
+
* precedent as `resolveCanonicalAgentsDir` above (TASK-004).
|
|
268
|
+
*/
|
|
269
|
+
function resolveStateDir(): string {
|
|
270
|
+
const override = process.env.ORCHESTRATOR_STATE_DIR;
|
|
271
|
+
if (override !== undefined && override !== '') return override;
|
|
272
|
+
const root = resolveHookProjectRoot(process.cwd()) ?? process.cwd();
|
|
273
|
+
return join(root, '.orchestrator', '.state');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function resolveRawPayloadLogPath(): string {
|
|
277
|
+
return join(resolveStateDir(), 'hook-raw-payloads.jsonl');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Fail-open, mirrors `lib/api-client.ts`'s `log()` posture: never throws. */
|
|
281
|
+
function appendRawPayloadLogDefault(line: string): void {
|
|
282
|
+
try {
|
|
283
|
+
const path = resolveRawPayloadLogPath();
|
|
284
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
285
|
+
writeFileSync(path, `${line}\n`, { flag: 'a' });
|
|
286
|
+
} catch {
|
|
287
|
+
// fail silently
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const defaultDeps: HookDeps = {
|
|
292
|
+
readStdin,
|
|
293
|
+
log,
|
|
294
|
+
getActiveWorkflow,
|
|
295
|
+
startInvocation,
|
|
296
|
+
writeInvocationState,
|
|
297
|
+
getCanonicalAgentNames: getCanonicalAgentNamesDefault,
|
|
298
|
+
hasFreshDispatchIntent,
|
|
299
|
+
appendRawPayloadLog: appendRawPayloadLogDefault,
|
|
300
|
+
resolveAgentMemoryBaseDir,
|
|
301
|
+
readMemoryIndex,
|
|
302
|
+
filterRelevantMemories,
|
|
303
|
+
formatMemoryContext,
|
|
304
|
+
outputContext,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// ─────────────────────────────────────────────────────────────
|
|
308
|
+
// Core logic (exported for testing)
|
|
309
|
+
// ─────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
export async function mainWithDeps(deps: HookDeps): Promise<void> {
|
|
312
|
+
const stdin = await deps.readStdin();
|
|
313
|
+
deps.log(HOOK, `SubagentStart triggered`);
|
|
314
|
+
|
|
315
|
+
// TASK-005 (RF-007): resolve the active workflow ONCE, up front — reused
|
|
316
|
+
// both for the raw-payload correlation below and for the pre-existing
|
|
317
|
+
// startInvocation gating further down (no duplicate getActiveWorkflow call
|
|
318
|
+
// introduced by this instrumentation).
|
|
319
|
+
const workflow = await deps.getActiveWorkflow();
|
|
320
|
+
const correlatedWithDispatchIntent = workflow ? deps.hasFreshDispatchIntent(workflow.id) : false;
|
|
321
|
+
deps.appendRawPayloadLog(buildRawPayloadLogLine(stdin, correlatedWithDispatchIntent));
|
|
322
|
+
|
|
323
|
+
const rawAgentType = resolveRawAgentType(stdin);
|
|
324
|
+
const agentType = normalizeAgentName(rawAgentType);
|
|
325
|
+
|
|
326
|
+
const decision = classifyAgentType(agentType, deps.getCanonicalAgentNames);
|
|
327
|
+
|
|
328
|
+
if (decision.kind === 'skip-read-failed') {
|
|
329
|
+
deps.log(
|
|
330
|
+
HOOK,
|
|
331
|
+
`[ERROR] could not read agent identity from stdin (checked agent_type, ` +
|
|
332
|
+
`tool_input.subagent_type, subagent_type, tool_input.type — all empty or ` +
|
|
333
|
+
`literal 'unknown'; raw="${rawAgentType}") — skipping invocation tracking ` +
|
|
334
|
+
`(RNF-003: elevated from WARN)`
|
|
335
|
+
);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (decision.kind === 'skip-unrecognized') {
|
|
340
|
+
deps.log(
|
|
341
|
+
HOOK,
|
|
342
|
+
`[DEBUG] agent="${decision.agentType}" is not in the canonical agent set — ` +
|
|
343
|
+
`skipping invocation tracking (not a workflow agent, e.g. a general-purpose ` +
|
|
344
|
+
`Task dispatch)`
|
|
345
|
+
);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const resolvedAgentType = decision.agentType;
|
|
350
|
+
const phase = PHASE_MAP[resolvedAgentType] || resolvedAgentType;
|
|
351
|
+
|
|
352
|
+
deps.log(HOOK, `agent=${resolvedAgentType} phase=${phase}`);
|
|
353
|
+
|
|
354
|
+
// TASK-010 (RF-010 Parte 3): memory context injection. Gated ONLY on
|
|
355
|
+
// identity resolution (this point is only reached when decision.kind ===
|
|
356
|
+
// 'proceed', above) — deliberately BEFORE the `!workflow` early return,
|
|
357
|
+
// since memory belongs to the agent's identity, not to workflow-tracking
|
|
358
|
+
// state (see module doc).
|
|
359
|
+
const memoryIndexPath = join(
|
|
360
|
+
deps.resolveAgentMemoryBaseDir(process.cwd()),
|
|
361
|
+
resolvedAgentType,
|
|
362
|
+
'memory-index.yaml'
|
|
363
|
+
);
|
|
364
|
+
// S480: o sub-agente sofria a MESMA cegueira do session-start -- mesma funcao,
|
|
365
|
+
// mesmos tres pontos mudos. Consertar so um lado deixaria o outro calado.
|
|
366
|
+
const memoryDiag: MemoryIndexDiagnostic = {};
|
|
367
|
+
const memoryEntries = deps.readMemoryIndex(memoryIndexPath, memoryDiag);
|
|
368
|
+
if (memoryDiag.reason) {
|
|
369
|
+
deps.log(HOOK, `Memory index: ${memoryDiag.reason}`);
|
|
370
|
+
if (!memoryEntries || memoryEntries.length === 0) {
|
|
371
|
+
deps.outputContext(
|
|
372
|
+
'SubagentStart',
|
|
373
|
+
`[MEMORY-CONTEXT] INDISPONÍVEL — nenhuma memória injetada. ${memoryDiag.reason}`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (memoryEntries) {
|
|
378
|
+
const selectedMemories = deps.filterRelevantMemories(memoryEntries);
|
|
379
|
+
const memoryContext = deps.formatMemoryContext(selectedMemories, memoryEntries);
|
|
380
|
+
if (memoryContext) {
|
|
381
|
+
deps.outputContext('SubagentStart', memoryContext);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
40
384
|
|
|
41
|
-
const workflow = await getActiveWorkflow();
|
|
42
385
|
if (!workflow) {
|
|
43
|
-
log(HOOK, 'No active workflow, skipping');
|
|
386
|
+
deps.log(HOOK, 'No active workflow, skipping');
|
|
44
387
|
return;
|
|
45
388
|
}
|
|
46
389
|
|
|
47
|
-
const invocationId = await startInvocation(
|
|
390
|
+
const invocationId = await deps.startInvocation(resolvedAgentType, workflow.id, phase);
|
|
48
391
|
if (invocationId) {
|
|
49
|
-
const written = writeInvocationState(workflow.id,
|
|
50
|
-
log(
|
|
392
|
+
const written = deps.writeInvocationState(workflow.id, resolvedAgentType, invocationId);
|
|
393
|
+
deps.log(
|
|
51
394
|
HOOK,
|
|
52
|
-
`START: agent=${
|
|
395
|
+
`START: agent=${resolvedAgentType} phase=${phase} workflow=${workflow.id} invocationId=${invocationId} stateWritten=${written}`
|
|
53
396
|
);
|
|
54
397
|
} else {
|
|
55
|
-
log(
|
|
398
|
+
deps.log(
|
|
56
399
|
HOOK,
|
|
57
|
-
`START: agent=${
|
|
400
|
+
`START: agent=${resolvedAgentType} phase=${phase} workflow=${workflow.id} — startInvocation returned null (POST failed or no token)`
|
|
58
401
|
);
|
|
59
402
|
}
|
|
60
403
|
}
|
|
61
404
|
|
|
405
|
+
// ─────────────────────────────────────────────────────────────
|
|
406
|
+
// Entry point (production use)
|
|
407
|
+
// ─────────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
async function main(): Promise<void> {
|
|
410
|
+
await mainWithDeps(defaultDeps);
|
|
411
|
+
}
|
|
412
|
+
|
|
62
413
|
main().catch(() => process.exit(0));
|