opencode-swarm 7.121.4 → 7.122.0

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 (37) hide show
  1. package/dist/cli/{curation-policy-10wnhpwn.js → curation-policy-cj1tpzr5.js} +2 -2
  2. package/dist/cli/{curator-3azybqrw.js → curator-3rze3pd4.js} +8 -8
  3. package/dist/cli/{curator-llm-factory-6rafny5e.js → curator-llm-factory-8rg0f77t.js} +8 -8
  4. package/dist/cli/{guardrail-explain-d1m747g5.js → guardrail-explain-hdsxa2c5.js} +9 -9
  5. package/dist/cli/{hive-promoter-4xnj3fdk.js → hive-promoter-dh38qd9m.js} +8 -8
  6. package/dist/cli/{index-5a3xsnzg.js → index-3r1w7xky.js} +1 -1
  7. package/dist/cli/{index-35xyj7gn.js → index-3sx27bxt.js} +26 -3
  8. package/dist/cli/{index-982hhpcm.js → index-68aggz0v.js} +1 -1
  9. package/dist/cli/{index-q0fv0xec.js → index-ahxrne50.js} +143 -84
  10. package/dist/cli/{index-x4fwtd59.js → index-gr7ssnj9.js} +3 -3
  11. package/dist/cli/{index-db82jxt7.js → index-j33zb3cx.js} +2 -2
  12. package/dist/cli/{index-2k6z335c.js → index-jzpv74mq.js} +9 -9
  13. package/dist/cli/{index-0f800qg7.js → index-ny13nr0x.js} +2 -2
  14. package/dist/cli/{index-j8349ac0.js → index-rkd6qwjh.js} +1 -1
  15. package/dist/cli/{index-ft6jw816.js → index-xdsj4qtd.js} +3 -3
  16. package/dist/cli/index.js +8 -8
  17. package/dist/cli/{knowledge-escalator-8ajq1fhb.js → knowledge-escalator-sya06p12.js} +3 -3
  18. package/dist/cli/{knowledge-events-2sjy3aa7.js → knowledge-events-xzps61a9.js} +3 -1
  19. package/dist/cli/{knowledge-store-27cesthc.js → knowledge-store-qg66a93n.js} +1 -1
  20. package/dist/cli/{knowledge-validator-9keh3p8j.js → knowledge-validator-gar6wv4f.js} +4 -4
  21. package/dist/cli/{scan-cursor-qmsmmg1b.js → scan-cursor-nx5y5k7r.js} +2 -2
  22. package/dist/cli/{skill-generator-hkyxdc3j.js → skill-generator-nt5qxafq.js} +5 -5
  23. package/dist/hooks/cohort-cache.d.ts +44 -0
  24. package/dist/hooks/hive-promoter.d.ts +4 -3
  25. package/dist/hooks/host-boundary.d.ts +147 -0
  26. package/dist/hooks/incremental-verify.d.ts +3 -0
  27. package/dist/hooks/knowledge-events.d.ts +25 -2
  28. package/dist/hooks/knowledge-injector.d.ts +9 -1
  29. package/dist/hooks/knowledge-receipt-validator.d.ts +98 -0
  30. package/dist/hooks/micro-reflector.d.ts +3 -0
  31. package/dist/hooks/pr-auto-subscribe.d.ts +3 -0
  32. package/dist/hooks/promotion-evidence-store.d.ts +33 -0
  33. package/dist/index.js +237 -230
  34. package/dist/services/knowledge-diagnostics.d.ts +12 -0
  35. package/dist/session/snapshot-writer.d.ts +5 -0
  36. package/dist/state.d.ts +11 -0
  37. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  authorizeCuration
4
- } from "./index-5a3xsnzg.js";
4
+ } from "./index-3r1w7xky.js";
5
5
  import {
6
6
  findNearDuplicate,
7
7
  inferTags,
@@ -9,7 +9,7 @@ import {
9
9
  resolveSwarmKnowledgePath,
10
10
  resolveSwarmRejectedPath,
11
11
  transactKnowledge
12
- } from "./index-0f800qg7.js";
12
+ } from "./index-ny13nr0x.js";
13
13
  import {
14
14
  resolveKnowledgeStoreDir
15
15
  } from "./index-3jvqw5hy.js";
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-982hhpcm.js";
4
+ } from "./index-68aggz0v.js";
5
5
  import {
6
6
  handleGuardrailLog
7
7
  } from "./index-zvae4m8g.js";
@@ -83,24 +83,24 @@ import {
83
83
  handleWriteRetroCommand,
84
84
  normalizeSwarmCommandInput,
85
85
  resolveCommand
86
- } from "./index-q0fv0xec.js";
86
+ } from "./index-ahxrne50.js";
87
87
  import"./index-kyvg2cmp.js";
88
88
  import"./index-vqcgmy4y.js";
89
89
  import"./index-emkb0bbe.js";
90
90
  import"./index-4905hd2m.js";
91
91
  import"./index-nprawcc0.js";
92
- import"./index-x4fwtd59.js";
93
- import"./index-ft6jw816.js";
94
- import"./index-j8349ac0.js";
92
+ import"./index-gr7ssnj9.js";
93
+ import"./index-xdsj4qtd.js";
94
+ import"./index-rkd6qwjh.js";
95
95
  import"./index-vxv732ex.js";
96
96
  import"./index-c8s9a3zh.js";
97
97
  import"./index-816rkp4y.js";
98
98
  import"./index-22e127jw.js";
99
- import"./index-db82jxt7.js";
100
- import"./index-5a3xsnzg.js";
99
+ import"./index-j33zb3cx.js";
100
+ import"./index-3r1w7xky.js";
101
101
  import"./index-rtry5xyf.js";
102
- import"./index-0f800qg7.js";
103
- import"./index-35xyj7gn.js";
102
+ import"./index-ny13nr0x.js";
103
+ import"./index-3sx27bxt.js";
104
104
  import"./index-ae75rja9.js";
105
105
  import"./index-3jvqw5hy.js";
106
106
  import"./index-9xpgytj9.js";
@@ -647,7 +647,7 @@ async function applyConfidenceFloorAction(directory, touched, options) {
647
647
  const recovered = touched.filter((e) => e.confidence > CONFIDENCE_FLOOR + FLOOR_EPSILON && e.confidence_floor_demoted);
648
648
  if (atFloor.length === 0 && recovered.length === 0)
649
649
  return;
650
- const { readKnowledgeCounterRollups, effectiveRetrievalOutcomes } = await import("./knowledge-events-2sjy3aa7.js");
650
+ const { readKnowledgeCounterRollups, effectiveRetrievalOutcomes } = await import("./knowledge-events-xzps61a9.js");
651
651
  const rollups = await readKnowledgeCounterRollups(directory);
652
652
  const flagIds = new Set;
653
653
  for (const e of atFloor) {
@@ -714,7 +714,7 @@ async function applyConfidenceFloorAction(directory, touched, options) {
714
714
  });
715
715
  }
716
716
  if (action === "quarantine" && toQuarantine.length > 0) {
717
- const { quarantineEntry } = await import("./knowledge-validator-9keh3p8j.js");
717
+ const { quarantineEntry } = await import("./knowledge-validator-gar6wv4f.js");
718
718
  const { KnowledgeConfigSchema } = await import("./schema-vwxpsk6j.js");
719
719
  let config;
720
720
  try {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readKnowledge,
4
4
  transactKnowledge
5
- } from "./index-0f800qg7.js";
5
+ } from "./index-ny13nr0x.js";
6
6
  import {
7
7
  resolveKnowledgeStoreDir
8
8
  } from "./index-3jvqw5hy.js";
@@ -6,13 +6,13 @@ import {
6
6
  resolveSwarmKnowledgePath,
7
7
  transactKnowledge,
8
8
  wordBigrams
9
- } from "./index-0f800qg7.js";
9
+ } from "./index-ny13nr0x.js";
10
10
  import {
11
11
  countEntryContradictionsInWindow,
12
12
  countEntryViolationsInWindow,
13
13
  readKnowledgeEvents,
14
14
  recordKnowledgeEvent
15
- } from "./index-35xyj7gn.js";
15
+ } from "./index-3sx27bxt.js";
16
16
  import {
17
17
  isActiveStatus
18
18
  } from "./index-4rhcth7a.js";
@@ -188,7 +188,7 @@ async function maybeQuarantineOnContradiction(directory, entryId, threshold, win
188
188
  alreadyInactive: true
189
189
  };
190
190
  }
191
- const { quarantineEntry } = await import("./knowledge-validator-9keh3p8j.js");
191
+ const { quarantineEntry } = await import("./knowledge-validator-gar6wv4f.js");
192
192
  let policyConfig;
193
193
  try {
194
194
  const { KnowledgeConfigSchema } = await import("./schema-vwxpsk6j.js");
package/dist/cli/index.js CHANGED
@@ -7,24 +7,24 @@ import {
7
7
  getPluginLockFilePaths,
8
8
  package_default,
9
9
  resolveCommand
10
- } from "./index-q0fv0xec.js";
10
+ } from "./index-ahxrne50.js";
11
11
  import"./index-kyvg2cmp.js";
12
12
  import"./index-vqcgmy4y.js";
13
13
  import"./index-emkb0bbe.js";
14
14
  import"./index-4905hd2m.js";
15
15
  import"./index-nprawcc0.js";
16
- import"./index-x4fwtd59.js";
17
- import"./index-ft6jw816.js";
18
- import"./index-j8349ac0.js";
16
+ import"./index-gr7ssnj9.js";
17
+ import"./index-xdsj4qtd.js";
18
+ import"./index-rkd6qwjh.js";
19
19
  import"./index-vxv732ex.js";
20
20
  import"./index-c8s9a3zh.js";
21
21
  import"./index-816rkp4y.js";
22
22
  import"./index-22e127jw.js";
23
- import"./index-db82jxt7.js";
24
- import"./index-5a3xsnzg.js";
23
+ import"./index-j33zb3cx.js";
24
+ import"./index-3r1w7xky.js";
25
25
  import"./index-rtry5xyf.js";
26
- import"./index-0f800qg7.js";
27
- import"./index-35xyj7gn.js";
26
+ import"./index-ny13nr0x.js";
27
+ import"./index-3sx27bxt.js";
28
28
  import"./index-ae75rja9.js";
29
29
  import"./index-3jvqw5hy.js";
30
30
  import"./index-9xpgytj9.js";
@@ -8,9 +8,9 @@ import {
8
8
  maybeEscalateOnViolation,
9
9
  maybeQuarantineOnContradiction,
10
10
  readRecentEscalations
11
- } from "./index-ft6jw816.js";
12
- import"./index-0f800qg7.js";
13
- import"./index-35xyj7gn.js";
11
+ } from "./index-xdsj4qtd.js";
12
+ import"./index-ny13nr0x.js";
13
+ import"./index-3sx27bxt.js";
14
14
  import"./index-ae75rja9.js";
15
15
  import"./index-3jvqw5hy.js";
16
16
  import"./index-4rhcth7a.js";
@@ -8,6 +8,7 @@ import {
8
8
  appendHiveKnowledgeEvent,
9
9
  appendKnowledgeEvent,
10
10
  applyKnowledgeVerdictFeedback,
11
+ countEmptyTraceTerminals,
11
12
  countEntryContradictionsInWindow,
12
13
  countEntryViolationsInWindow,
13
14
  countViolationsInWindow,
@@ -25,7 +26,7 @@ import {
25
26
  resolveKnowledgeCounterBaselinePath,
26
27
  resolveKnowledgeEventsPath,
27
28
  resolveLegacyApplicationLogPath
28
- } from "./index-35xyj7gn.js";
29
+ } from "./index-3sx27bxt.js";
29
30
  import"./index-ae75rja9.js";
30
31
  import"./index-3jvqw5hy.js";
31
32
  import"./index-zgwm4ryv.js";
@@ -54,6 +55,7 @@ export {
54
55
  countViolationsInWindow,
55
56
  countEntryViolationsInWindow,
56
57
  countEntryContradictionsInWindow,
58
+ countEmptyTraceTerminals,
57
59
  applyKnowledgeVerdictFeedback,
58
60
  appendKnowledgeEvent,
59
61
  appendHiveKnowledgeEvent,
@@ -40,7 +40,7 @@ import {
40
40
  transactKnowledge,
41
41
  transactKnowledgeWithCas,
42
42
  wordBigrams
43
- } from "./index-0f800qg7.js";
43
+ } from "./index-ny13nr0x.js";
44
44
  import {
45
45
  resolveHiveDataDir
46
46
  } from "./index-ae75rja9.js";
@@ -23,11 +23,11 @@ import {
23
23
  validateLesson,
24
24
  validateSkillCandidatePath,
25
25
  validateSkillPath
26
- } from "./index-db82jxt7.js";
27
- import"./index-5a3xsnzg.js";
26
+ } from "./index-j33zb3cx.js";
27
+ import"./index-3r1w7xky.js";
28
28
  import"./index-rtry5xyf.js";
29
- import"./index-0f800qg7.js";
30
- import"./index-35xyj7gn.js";
29
+ import"./index-ny13nr0x.js";
30
+ import"./index-3sx27bxt.js";
31
31
  import"./index-ae75rja9.js";
32
32
  import"./index-3jvqw5hy.js";
33
33
  import"./index-4rhcth7a.js";
@@ -4,8 +4,8 @@ import {
4
4
  alreadyCuratedThisGeneration,
5
5
  claimNextScanBatch,
6
6
  getScanStatus
7
- } from "./index-j8349ac0.js";
8
- import"./index-0f800qg7.js";
7
+ } from "./index-rkd6qwjh.js";
8
+ import"./index-ny13nr0x.js";
9
9
  import"./index-ae75rja9.js";
10
10
  import"./index-3jvqw5hy.js";
11
11
  import"./index-4rhcth7a.js";
@@ -33,12 +33,12 @@ import {
33
33
  sanitizeSlug,
34
34
  selectCandidateEntries,
35
35
  writeEvalStub
36
- } from "./index-x4fwtd59.js";
37
- import"./index-db82jxt7.js";
38
- import"./index-5a3xsnzg.js";
36
+ } from "./index-gr7ssnj9.js";
37
+ import"./index-j33zb3cx.js";
38
+ import"./index-3r1w7xky.js";
39
39
  import"./index-rtry5xyf.js";
40
- import"./index-0f800qg7.js";
41
- import"./index-35xyj7gn.js";
40
+ import"./index-ny13nr0x.js";
41
+ import"./index-3sx27bxt.js";
42
42
  import"./index-ae75rja9.js";
43
43
  import"./index-3jvqw5hy.js";
44
44
  import"./index-4rhcth7a.js";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Cohort-id cache helpers (issue #1849).
3
+ *
4
+ * `resolveCohortId` (#1846) canonicalizes repo identity but spawns git (up to
5
+ * 3 subprocesses). Calling it per-turn from `experimental.chat.system.transform`
6
+ * or per-receipt from the PromotionEvidenceRecord writer would violate the
7
+ * bounded-init / no-hot-path-subprocess invariants. These helpers resolve the
8
+ * cohort id ONCE, cache it on the per-session state, and let every hot-path
9
+ * caller read the cached value.
10
+ *
11
+ * Primary population path: `chat.message` (fires turn 1 with sessionID + agent).
12
+ * Fallback: callers that observe a cache miss (pre-existing session, restored
13
+ * old snapshot, first-turn race) call {@link ensureCohortIdCached}, which
14
+ * resolves once-bounded and caches — never per-turn.
15
+ */
16
+ /**
17
+ * Resolve the cohort id for `directory` and cache it on the session at
18
+ * `sessionID`. Fail-open + bounded: `resolveCohortId` never throws, and the
19
+ * call is wrapped so any unexpected error logs and returns undefined without
20
+ * caching a bad value. Idempotent — if the cache is already populated, returns
21
+ * immediately without re-running git.
22
+ *
23
+ * Intended call sites (NOT per-turn):
24
+ * - `chat.message` (primary; fires turn 1).
25
+ * - `ensureCohortIdCached` fallback (only on cache miss).
26
+ */
27
+ export declare function cacheCohortIdAtMessage(directory: string, sessionID: string): Promise<string | undefined>;
28
+ /**
29
+ * Resolve the cohort id for `directory` once-bounded and cache it on the
30
+ * session at `sessionID`. Returns the cached value if present; otherwise
31
+ * resolves, caches, and returns. Returns `undefined` when resolution fails or
32
+ * the identity is degraded in a way that yields no usable cohort id.
33
+ *
34
+ * This is the fallback for hot-path callers (system-enhancer,
35
+ * PromotionEvidenceRecord writer) that observe a cache miss. It MUST stay
36
+ * off the per-turn critical path: callers should read the cached value first
37
+ * and only invoke this on miss.
38
+ */
39
+ export declare function ensureCohortIdCached(directory: string, sessionID: string | undefined): Promise<string | undefined>;
40
+ /**
41
+ * Read the cached cohort id for `sessionID` WITHOUT resolving. Returns
42
+ * `undefined` when not cached. This is the hot-path read (no git, no I/O).
43
+ */
44
+ export declare function readCachedCohortId(sessionID: string | undefined): string | undefined;
@@ -81,9 +81,10 @@ export declare const _internals: {
81
81
  transactHiveStore: typeof transactHiveStore;
82
82
  validateLesson: typeof validateLesson;
83
83
  authorizeCuration: typeof authorizeCuration;
84
- /** Loads validated terminal-application evidence per swarm entry id. Empty
85
- * until #1849 produces real receipts (conservative: no synthetic credit). */
86
- loadPromotionEvidence: (_swarmEntries: SwarmKnowledgeEntry[]) => Promise<Record<string, PromotionEvidenceRecord[]>>;
84
+ /** (#1849) Loads validated terminal-application evidence per swarm entry id
85
+ * from .swarm/knowledge-promotion-evidence.jsonl. Empty when no validated
86
+ * receipts exist yet (conservative: no synthetic credit). */
87
+ loadPromotionEvidence: (directory: string) => Promise<Record<string, PromotionEvidenceRecord[]>>;
87
88
  /** Loads the default KnowledgeConfig (schema defaults) for manual promotion
88
89
  * paths when the command did not load one. */
89
90
  loadDefaultKnowledgeConfig: () => {
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Host-boundary adapter (issue #1849).
3
+ *
4
+ * The single place that translates real OpenCode SDK callback payloads into a
5
+ * stable, typed internal context. Every downstream knowledge/delegation hook
6
+ * reads from this adapter — never directly from the raw `input`/`output`
7
+ * callback arguments. This eliminates the class of defect where hooks guessed
8
+ * SDK payload shapes (`input.agent`, `input.args`, synthetic `role:'system'`
9
+ * messages) that the host never provides.
10
+ *
11
+ * Authoritative SDK contract (`@opencode-ai/plugin@1.x`, `Hooks` interface):
12
+ *
13
+ * tool.execute.before: input { tool, sessionID, callID }
14
+ * output { args } ← mutable tool args
15
+ * tool.execute.after: input { tool, sessionID, callID }
16
+ * output { title, output, metadata }
17
+ * experimental.chat.messages.transform:
18
+ * input {} ← no sessionID/agent
19
+ * output { messages: { info: Message, parts }[] }
20
+ * experimental.chat.system.transform:
21
+ * input { sessionID?, model }
22
+ * output { system: string[] }
23
+ * chat.message: input { sessionID, agent?, ... } ← agent IS provided here
24
+ *
25
+ * `Message = UserMessage | AssistantMessage` — there is NO `role:'system'`
26
+ * message variant. `UserMessage` carries `agent` + `sessionID`; `AssistantMessage`
27
+ * carries `sessionID` only. System content is delivered via the separate
28
+ * `experimental.chat.system.transform` hook.
29
+ *
30
+ * Identity recovery strategy (verified against the SDK contract):
31
+ * - For `tool.execute.*` and `experimental.chat.messages.transform`, the
32
+ * reliable identity source is `swarmState.activeAgent.get(sessionID)`, which
33
+ * is populated by the `chat.message` hook (where `agent` IS provided).
34
+ * - For `messages.transform`, the last user message's `info.agent` is a
35
+ * first-turn fallback when `swarmState.activeAgent` has no entry yet.
36
+ * - Multi-swarm prefixed names (`cohort_architect`) are canonicalized via
37
+ * `stripKnownSwarmPrefix` (suffix-based; handles any user-defined prefix).
38
+ */
39
+ /** Resolved context for `tool.execute.before` / `tool.execute.after`. */
40
+ export interface ToolBoundaryContext {
41
+ tool: string;
42
+ sessionID: string;
43
+ callID: string;
44
+ /** Active agent for this session (from swarmState.activeAgent; architect fallback). */
45
+ agent: string;
46
+ /** Canonicalized role, e.g. `architect` for `cohort_architect`. */
47
+ callerRole: string;
48
+ /**
49
+ * Mutable tool arguments.
50
+ * - toolBefore: `output.args` (the SDK mutation target).
51
+ * - toolAfter: `getStoredInputArgs(callID)` (the snapshot taken in toolBefore
52
+ * by `guardrails/tool-before.ts`).
53
+ * `null` when absent (the host did not supply args, or the snapshot was
54
+ * already cleaned up).
55
+ */
56
+ args: Record<string, unknown> | null;
57
+ /** True when the caller canonicalizes to the orchestrator/architect role. */
58
+ isArchitect: boolean;
59
+ }
60
+ /** Resolved context for `experimental.chat.messages.transform`. */
61
+ export interface MessageTransformContext {
62
+ sessionID: string | undefined;
63
+ /** Active agent (swarmState.activeAgent primary; last user message fallback). */
64
+ agent: string | undefined;
65
+ callerRole: string | undefined;
66
+ isArchitect: boolean;
67
+ isDelegate: boolean;
68
+ }
69
+ /**
70
+ * Minimal shape of `output.messages[].info` we read. The real SDK `Message`
71
+ * union carries many more fields; we only depend on `role`, `agent`
72
+ * (UserMessage only), and `sessionID`.
73
+ */
74
+ export interface MessageInfoLike {
75
+ role?: string;
76
+ agent?: string;
77
+ sessionID?: string;
78
+ }
79
+ export interface MessageArrayLike {
80
+ messages?: Array<{
81
+ info?: MessageInfoLike;
82
+ }>;
83
+ }
84
+ /**
85
+ * Resolve the active agent for a sessionID.
86
+ *
87
+ * PRIMARY: `swarmState.activeAgent.get(sessionID)` — set reliably by the
88
+ * `chat.message` hook. FALLBACK: `ORCHESTRATOR_NAME` ('architect'), mirroring
89
+ * the existing pre-#1849 behavior at `src/index.ts:2039-2041` (when no active
90
+ * agent is mapped, the session is treated as the primary/architect).
91
+ *
92
+ * Never throws. Emits a bounded, debug-gated diagnostic only when the session
93
+ * has no mapped agent (informational — the fallback is intentional, not an
94
+ * error).
95
+ */
96
+ declare function resolveAgent(sessionID: string | undefined): string;
97
+ declare function isPlainObject(v: unknown): v is Record<string, unknown>;
98
+ declare function toArgs(v: unknown): Record<string, unknown> | null;
99
+ /**
100
+ * Build the `tool.execute.before` context.
101
+ *
102
+ * Reads `agent` from `swarmState.activeAgent` (NOT `input.agent`, which the
103
+ * host does not provide) and `args` from `output.args` (NOT `input.args`).
104
+ */
105
+ export declare function resolveToolBeforeContext(input: {
106
+ tool: string;
107
+ sessionID: string;
108
+ callID: string;
109
+ }, output: {
110
+ args?: unknown;
111
+ }): ToolBoundaryContext;
112
+ /**
113
+ * Build the `tool.execute.after` context.
114
+ *
115
+ * `args` is recovered from the snapshot taken in toolBefore via
116
+ * `setStoredInputArgs(callID, output.args)` (`guardrails/tool-before.ts`). The
117
+ * SDK `tool.execute.after` input has no `args` field and the output carries only
118
+ * `title`/`output`/`metadata`, so the snapshot is the only correct source.
119
+ */
120
+ export declare function resolveToolAfterContext(input: {
121
+ tool: string;
122
+ sessionID: string;
123
+ callID: string;
124
+ }): ToolBoundaryContext;
125
+ /**
126
+ * Build the `experimental.chat.messages.transform` context.
127
+ *
128
+ * The SDK `input` is `{}`, so session/agent identity is recovered from
129
+ * `output.messages[].info`:
130
+ * - `sessionID` from any message's `info.sessionID` (every `Message` carries it).
131
+ * - `agent` PRIMARY from `swarmState.activeAgent.get(sessionID)`; FALLBACK from
132
+ * the LAST user message's `info.agent` (`UserMessage` carries `agent`).
133
+ *
134
+ * Critically, this NEVER searches for a `role:'system'` message — the SDK
135
+ * `Message` union has no system-role variant, and looking for one was the root
136
+ * cause of the dark-in-production architect injection (#1768/#1849).
137
+ */
138
+ export declare function resolveMessageTransformContext(output: MessageArrayLike): MessageTransformContext;
139
+ /** Like resolveAgent but returns undefined instead of the orchestrator fallback. */
140
+ declare function resolveAgentOptional(sessionID: string | undefined): string | undefined;
141
+ export declare const _internals: {
142
+ resolveAgent: typeof resolveAgent;
143
+ resolveAgentOptional: typeof resolveAgentOptional;
144
+ toArgs: typeof toArgs;
145
+ isPlainObject: typeof isPlainObject;
146
+ };
147
+ export {};
@@ -11,6 +11,9 @@ export interface IncrementalVerifyHook {
11
11
  tool: string;
12
12
  sessionID: string;
13
13
  args?: unknown;
14
+ /** (#1849) SDK tool.execute.after input carries callID (not args); used
15
+ * to recover the snapshot args via resolveToolAfterContext. */
16
+ callID?: string;
14
17
  }, output: {
15
18
  output?: unknown;
16
19
  args?: unknown;
@@ -64,11 +64,26 @@ export interface ReceiptEvent {
64
64
  | 'n_a'
65
65
  /** Architect explicitly accepted an unresolved critical violation at
66
66
  * phase_complete (Change 2, Task 2.4). Audit-only; never affects rollups. */
67
- | 'override';
67
+ | 'override'
68
+ /**
69
+ * Terminal accounting event for an EMPTY retrieval (issue #1849): an agent
70
+ * considered a trace that surfaced no relevant knowledge and explicitly filed
71
+ * `no_relevant_knowledge`. This closes the "every retrieval attempt has one
72
+ * durable terminal accounting path, including empty result" contract. Audit
73
+ * only — `recomputeCounters` intentionally does NOT mutate any per-entry
74
+ * counter (there is no `knowledge_id` to credit; this is a trace-level
75
+ * tombstone, not application credit).
76
+ */
77
+ | 'no_relevant';
68
78
  schema_version?: number;
69
79
  event_id: string;
70
80
  trace_id: string;
71
- knowledge_id: string;
81
+ /**
82
+ * The considered knowledge entry. Optional for the `no_relevant` terminal
83
+ * (issue #1849), which is a trace-level tombstone for an empty retrieval and
84
+ * references no specific entry.
85
+ */
86
+ knowledge_id?: string;
72
87
  timestamp: string;
73
88
  session_id: string;
74
89
  phase?: string;
@@ -300,6 +315,14 @@ export declare function recomputeCounters(events: KnowledgeEvent[], legacyRecord
300
315
  * timestamps are ignored.
301
316
  */
302
317
  export declare function countViolationsInWindow(timestamps: string[], windowDays: number, now?: Date): number;
318
+ /**
319
+ * Count the durable `no_relevant` terminal events (issue #1849) in the given
320
+ * event list — the trace-level tombstones recording that a retrieval surfaced
321
+ * nothing relevant and was explicitly accounted for. Pure helper for
322
+ * diagnostics (`/swarm status`, `knowledge_recall debug`). NOT a per-entry
323
+ * counter; there is no `knowledge_id` to credit.
324
+ */
325
+ export declare function countEmptyTraceTerminals(events: KnowledgeEvent[]): number;
303
326
  /**
304
327
  * Async convenience: count an entry's violations within a day-window. Counts
305
328
  * directly from the event log + legacy application records so the result is
@@ -21,7 +21,7 @@ export declare const DELEGATE_DIRECTIVE_BLOCK_TAG = "<delegate_knowledge_directi
21
21
  * (critical first) then ID so the block is stable across runs and prompt caches
22
22
  * remain warm. Returns null when there are no entries (no empty wrapper).
23
23
  */
24
- export declare function buildDelegateDirectiveBlock(entries: RankedEntry[], cfg: KnowledgeConfig): string | null;
24
+ export declare function buildDelegateDirectiveBlock(entries: RankedEntry[], cfg: KnowledgeConfig, traceId?: string): string | null;
25
25
  /** A directive that was rendered into a delegate block, recovered by parsing. */
26
26
  export interface ShownDelegateDirective {
27
27
  id: string;
@@ -35,6 +35,14 @@ export interface ShownDelegateDirective {
35
35
  * ack for a directive it never received. Returns [] when no block is present.
36
36
  */
37
37
  export declare function parseDelegateDirectiveBlock(text: string): ShownDelegateDirective[];
38
+ /**
39
+ * Recover the `trace_id` rendered into a `<delegate_knowledge_directives>` block
40
+ * (issue #1849 RC-4). Returns undefined when the block is absent or when it
41
+ * predates the trace_id header (backward-compatible with older prompts). The
42
+ * delegate-ack-collector uses this to attribute acks to the ORIGINAL retrieval
43
+ * trace instead of minting an untied one.
44
+ */
45
+ export declare function parseDelegateDirectiveTraceId(text: string): string | undefined;
38
46
  export interface InjectForDelegateParams {
39
47
  directory: string;
40
48
  agent: string;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Shared receipt validator (issue #1849).
3
+ *
4
+ * One validator that enforces the terminal-receipt contract for BOTH the
5
+ * `knowledge_receipt` tool AND the `delegate-ack-collector`. Reads the
6
+ * authoritative event log and rejects forged / expired / conflicting /
7
+ * non-trace receipts while keeping idempotent retries free of double-counting.
8
+ *
9
+ * Authority contract (per issue #1849 §3):
10
+ * - trace/result delivery proves shown/retrieved only;
11
+ * - a validated terminal receipt is the durable audit outcome;
12
+ * - applied counters derive ONLY from validated `applied` receipts;
13
+ * - ignored/contradicted/no_relevant remain visible but are not application credit.
14
+ *
15
+ * Uniqueness grain: ONE terminal per (trace_id, knowledge_id). A trace surfaces a
16
+ * set of entries; for each entry at most one terminal outcome is accepted.
17
+ * Idempotent retry of the SAME outcome is accepted (not re-emitted, not
18
+ * double-counted); a DIFFERENT outcome for the same (trace_id, knowledge_id)
19
+ * is a conflicting-terminal rejection. The delegate ack path legitimately
20
+ * emits multiple terminals per trace (one per shown directive) — that is fine
21
+ * because each directive has a distinct knowledge_id.
22
+ */
23
+ import { type KnowledgeEvent, type RetrievedEvent } from './knowledge-events.js';
24
+ /** Terminal outcomes the validator accepts. */
25
+ export type ReceiptOutcome = 'applied' | 'ignored' | 'contradicted' | 'violated' | 'n_a' | 'no_relevant';
26
+ /** A single item being filed in a receipt. */
27
+ export interface ReceiptItem {
28
+ id: string;
29
+ outcome: Exclude<ReceiptOutcome, 'no_relevant'>;
30
+ reason?: string;
31
+ }
32
+ export interface ReceiptValidationContext {
33
+ directory: string;
34
+ /** The retrieval trace this receipt accounts for, or `'none'`. */
35
+ trace_id: string;
36
+ session_id: string;
37
+ task_id?: string;
38
+ phase?: string;
39
+ agent: string;
40
+ items: ReceiptItem[];
41
+ /** True when the receipt asserts nothing relevant was surfaced. */
42
+ no_relevant_knowledge: boolean;
43
+ }
44
+ export type ReceiptRejectReason = 'trace_not_found' | 'id_not_in_trace' | 'wrong_session' | 'expired' | 'duplicate_conflicting_terminal' | 'invalid_outcome' | 'empty_receipt';
45
+ export type ReceiptValidationResult = {
46
+ ok: true;
47
+ /** Items that should be freshly emitted (excludes idempotent skips). */
48
+ accepted: ReceiptItem[];
49
+ /** Items already recorded with the same outcome — skip emitting. */
50
+ idempotent_skips: ReceiptItem[];
51
+ /** The matched retrieval trace, or null for the real-empty (`'none'`) path. */
52
+ trace: RetrievedEvent | null;
53
+ /** True when this receipt closes the loop with a `no_relevant` terminal. */
54
+ closes_no_relevant: boolean;
55
+ /**
56
+ * Per-item rejections when SOME items were accepted but others were not
57
+ * (id_not_in_trace / conflicting). Present only when non-empty.
58
+ */
59
+ rejected_items?: Array<{
60
+ item: ReceiptItem;
61
+ reason: ReceiptRejectReason;
62
+ }>;
63
+ } | {
64
+ ok: false;
65
+ rejected: true;
66
+ reason: ReceiptRejectReason;
67
+ detail: string;
68
+ /** Items rejected by per-item checks (id_not_in_trace / conflicting). */
69
+ rejected_items?: Array<{
70
+ item: ReceiptItem;
71
+ reason: ReceiptRejectReason;
72
+ }>;
73
+ };
74
+ /**
75
+ * Receipt validity window. A receipt must be filed within this many
76
+ * milliseconds of the retrieval trace's timestamp. Generous constant (30 min)
77
+ * covers slow agents. NOTE: this is a compile-time constant, NOT configurable
78
+ * via the config schema — raise an issue if runtime configurability is needed.
79
+ */
80
+ export declare const RECEIPT_VALIDITY_MS: number;
81
+ /** The `trace_id` sentinel meaning "no retrieval occurred". */
82
+ export declare const NO_TRACE_SENTINEL = "none";
83
+ /**
84
+ * Validate a receipt against the authoritative event log. Never throws — any
85
+ * internal error fails open by accepting the items (the agent's work must not
86
+ * be blocked by a validator crash). Rejections are AUDITED by the caller via
87
+ * the returned reason; the validator itself does not write audit events to
88
+ * keep it side-effect-free and testable.
89
+ */
90
+ export declare function validateReceipt(ctx: ReceiptValidationContext): Promise<ReceiptValidationResult>;
91
+ declare function findTrace(events: KnowledgeEvent[], traceId: string): RetrievedEvent | null;
92
+ declare function isTerminalReceipt(e: KnowledgeEvent): boolean;
93
+ export declare const _internals: {
94
+ findTrace: typeof findTrace;
95
+ isTerminalReceipt: typeof isTerminalReceipt;
96
+ VALID_OUTCOMES: ReadonlySet<string>;
97
+ };
98
+ export {};
@@ -80,6 +80,9 @@ export interface MicroReflectorInput {
80
80
  tool: unknown;
81
81
  args?: unknown;
82
82
  sessionID?: unknown;
83
+ /** (#1849) callID from the SDK tool.execute.after input — used to recover the
84
+ * snapshot args (the SDK toolAfter input has NO args field). */
85
+ callID?: unknown;
83
86
  }
84
87
  export interface MicroReflectorOutput {
85
88
  output?: unknown;
@@ -23,6 +23,9 @@ export interface PrAutoSubscribeHook {
23
23
  tool: string;
24
24
  sessionID?: string;
25
25
  args?: unknown;
26
+ /** (#1849) SDK tool.execute.after input carries callID (not args); used
27
+ * to recover the snapshot args via resolveToolAfterContext. */
28
+ callID?: string;
26
29
  }, output: {
27
30
  output?: unknown;
28
31
  args?: unknown;