@inixiative/foundry-core 0.2.0 → 0.3.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.
@@ -1,85 +1,236 @@
1
1
  /** Portable SHA-256 for public evidence, shared by Bun and the historical browser.
2
2
  * This is an integrity join, not a signature or native delivery acknowledgment. */
3
3
  export function evidenceDigest(text: string): string {
4
- const input = new TextEncoder().encode(text), bytes = new Uint8Array(Math.ceil((input.length + 9) / 64) * 64);
5
- bytes.set(input); bytes[input.length] = 128;
6
- const view = new DataView(bytes.buffer), bits = input.length * 8;
7
- view.setUint32(bytes.length - 8, Math.floor(bits / 4294967296)); view.setUint32(bytes.length - 4, bits >>> 0);
8
- const h = [0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19];
9
- const k = [0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2];
10
- const rr = (v: number, n: number) => (v >>> n) | (v << (32 - n)), w = new Uint32Array(64);
4
+ const input = new TextEncoder().encode(text),
5
+ bytes = new Uint8Array(Math.ceil((input.length + 9) / 64) * 64);
6
+ bytes.set(input);
7
+ bytes[input.length] = 128;
8
+ const view = new DataView(bytes.buffer),
9
+ bits = input.length * 8;
10
+ view.setUint32(bytes.length - 8, Math.floor(bits / 4294967296));
11
+ view.setUint32(bytes.length - 4, bits >>> 0);
12
+ const h = [
13
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
14
+ ];
15
+ const k = [
16
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
17
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
18
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
19
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
20
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
21
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
22
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
23
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
24
+ ];
25
+ const rr = (v: number, n: number) => (v >>> n) | (v << (32 - n)),
26
+ w = new Uint32Array(64);
11
27
  for (let offset = 0; offset < bytes.length; offset += 64) {
12
28
  for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + 4 * i);
13
- for (let i = 16; i < 64; i++) { const x=w[i-15]!, y=w[i-2]!; w[i]=w[i-16]!+(rr(x,7)^rr(x,18)^(x>>>3))+w[i-7]!+(rr(y,17)^rr(y,19)^(y>>>10)); }
14
- let [a,b,c,d,e,f,g,j] = h as [number,number,number,number,number,number,number,number];
15
- for (let i=0;i<64;i++) { const t=(j+(rr(e,6)^rr(e,11)^rr(e,25))+((e&f)^(~e&g))+k[i]!+w[i]!)|0;
16
- const u=((rr(a,2)^rr(a,13)^rr(a,22))+((a&b)^(a&c)^(b&c)))|0; j=g;g=f;f=e;e=(d+t)|0;d=c;c=b;b=a;a=(t+u)|0; }
17
- [a,b,c,d,e,f,g,j].forEach((v,i)=>{h[i]=(h[i]!+v)>>>0;});
29
+ for (let i = 16; i < 64; i++) {
30
+ const x = w[i - 15]!,
31
+ y = w[i - 2]!;
32
+ w[i] =
33
+ w[i - 16]! +
34
+ (rr(x, 7) ^ rr(x, 18) ^ (x >>> 3)) +
35
+ w[i - 7]! +
36
+ (rr(y, 17) ^ rr(y, 19) ^ (y >>> 10));
37
+ }
38
+ let [a, b, c, d, e, f, g, j] = h as [
39
+ number,
40
+ number,
41
+ number,
42
+ number,
43
+ number,
44
+ number,
45
+ number,
46
+ number,
47
+ ];
48
+ for (let i = 0; i < 64; i++) {
49
+ const t = (j + (rr(e, 6) ^ rr(e, 11) ^ rr(e, 25)) + ((e & f) ^ (~e & g)) + k[i]! + w[i]!) | 0;
50
+ const u = ((rr(a, 2) ^ rr(a, 13) ^ rr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) | 0;
51
+ j = g;
52
+ g = f;
53
+ f = e;
54
+ e = (d + t) | 0;
55
+ d = c;
56
+ c = b;
57
+ b = a;
58
+ a = (t + u) | 0;
59
+ }
60
+ [a, b, c, d, e, f, g, j].forEach((v, i) => {
61
+ h[i] = (h[i]! + v) >>> 0;
62
+ });
18
63
  }
19
- return h.map(v=>v.toString(16).padStart(8,"0")).join("");
64
+ return h.map((v) => v.toString(16).padStart(8, '0')).join('');
20
65
  }
21
66
 
22
- export interface DeliveryOwner { threadId: string; messageId: string; projectId?: string }
67
+ export interface DeliveryOwner {
68
+ threadId: string;
69
+ messageId: string;
70
+ projectId?: string;
71
+ }
23
72
  export interface ProviderBoundaryReceipt {
24
- schema: 1; owner: DeliveryOwner; assemblyHash: string; messagesHash: string;
73
+ schema: 1;
74
+ owner: DeliveryOwner;
75
+ assemblyHash: string;
76
+ messagesHash: string;
25
77
  /** Exactly one initial system message starts with the assembled context. */
26
78
  systemMessageIndex: number;
27
79
  }
28
80
  // Fixed top-level field order over the preserved serialization, never the current cache.
29
81
  export function assemblyDigest(i: any): string {
30
- return evidenceDigest(JSON.stringify([i.threadId,i.projectId ?? null,i.messageId,i.executorContext,i.userMessage,
31
- i.layers,i.blocks,i.decoration]));
82
+ return evidenceDigest(
83
+ JSON.stringify([
84
+ i.threadId,
85
+ i.projectId ?? null,
86
+ i.messageId,
87
+ i.executorContext,
88
+ i.userMessage,
89
+ i.layers,
90
+ i.blocks,
91
+ i.decoration,
92
+ ]),
93
+ );
32
94
  }
33
95
  export function boundaryReceipt(i: any, owner: DeliveryOwner): ProviderBoundaryReceipt {
34
- const matches = i.providerMessages.flatMap((m: any, n: number) => m.role === "system" && typeof m.content === "string"
35
- && typeof i.executorContext === "string" && i.executorContext.length > 0
36
- && (m.content === i.executorContext || m.content.startsWith(i.executorContext + "\n\n")) ? [n] : []);
37
- return { schema: 1, owner: { ...owner }, assemblyHash: assemblyDigest(i),
38
- messagesHash: evidenceDigest(JSON.stringify(i.providerMessages)), systemMessageIndex: matches.length === 1 ? matches[0] : -1 };
96
+ const matches = i.providerMessages.flatMap((m: any, n: number) =>
97
+ m.role === 'system' &&
98
+ typeof m.content === 'string' &&
99
+ typeof i.executorContext === 'string' &&
100
+ i.executorContext.length > 0 &&
101
+ (m.content === i.executorContext || m.content.startsWith(i.executorContext + '\n\n'))
102
+ ? [n]
103
+ : [],
104
+ );
105
+ return {
106
+ schema: 1,
107
+ owner: { ...owner },
108
+ assemblyHash: assemblyDigest(i),
109
+ messagesHash: evidenceDigest(JSON.stringify(i.providerMessages)),
110
+ systemMessageIndex: matches.length === 1 ? matches[0] : -1,
111
+ };
39
112
  }
40
113
  export interface ExpertDeliveryProof {
41
- domain: string; layerId: string; assessedRevision: number; deliveredRevision: number;
42
- deliveredHash: string; relation: "assessed" | "advanced"; content: string;
114
+ domain: string;
115
+ layerId: string;
116
+ assessedRevision: number;
117
+ deliveredRevision: number;
118
+ deliveredHash: string;
119
+ relation: 'assessed' | 'advanced';
120
+ content: string;
43
121
  }
44
122
  /** One validation contract used by inspection, the runner and the copy-only audit. */
45
- export function verifyExpertDelivery(i: any, delivery: any, domain: string, expected: DeliveryOwner): ExpertDeliveryProof {
46
- const fail = () => { throw Error("Recorded expert delivery invalid"); };
123
+ export function verifyExpertDelivery(
124
+ i: any,
125
+ delivery: any,
126
+ domain: string,
127
+ expected: DeliveryOwner,
128
+ ): ExpertDeliveryProof {
129
+ const fail = () => {
130
+ throw Error('Recorded expert delivery invalid');
131
+ };
47
132
  const one = (rows: any, predicate: (row: any) => boolean): any => {
48
- if (!Array.isArray(rows)) return fail(); const found = rows.filter(predicate); return found.length === 1 ? found[0] : fail();
133
+ if (!Array.isArray(rows)) return fail();
134
+ const found = rows.filter(predicate);
135
+ return found.length === 1 ? found[0] : fail();
49
136
  };
50
- const owner = (x: any) => x?.threadId === expected.threadId && x?.projectId === expected.projectId;
137
+ const owner = (x: any) =>
138
+ x?.threadId === expected.threadId && x?.projectId === expected.projectId;
51
139
  const receipt = i?.providerBoundary;
52
- if (!expected.threadId || !expected.messageId || !owner(i) || i.messageId !== expected.messageId
53
- || !owner(i.decoration?.input?.currentMessage) || i.decoration.input.currentMessage.messageId !== expected.messageId
54
- || receipt?.schema !== 1 || !owner(receipt.owner) || receipt.owner.messageId !== expected.messageId
55
- || receipt.assemblyHash !== assemblyDigest(i) || !Array.isArray(i.providerMessages)
56
- || receipt.messagesHash !== evidenceDigest(JSON.stringify(i.providerMessages))) fail();
140
+ if (
141
+ !expected.threadId ||
142
+ !expected.messageId ||
143
+ !owner(i) ||
144
+ i.messageId !== expected.messageId ||
145
+ !owner(i.decoration?.input?.currentMessage) ||
146
+ i.decoration.input.currentMessage.messageId !== expected.messageId ||
147
+ receipt?.schema !== 1 ||
148
+ !owner(receipt.owner) ||
149
+ receipt.owner.messageId !== expected.messageId ||
150
+ receipt.assemblyHash !== assemblyDigest(i) ||
151
+ !Array.isArray(i.providerMessages) ||
152
+ receipt.messagesHash !== evidenceDigest(JSON.stringify(i.providerMessages))
153
+ )
154
+ fail();
57
155
  const system = i.providerMessages[receipt.systemMessageIndex];
58
- if (!Number.isSafeInteger(receipt.systemMessageIndex) || system?.role !== "system" || typeof system.content !== "string"
59
- || !i.executorContext || !(system.content === i.executorContext || system.content.startsWith(i.executorContext + "\n\n"))) fail();
60
- const p = one(i.decoration?.participants, p => p?.id === domain), id = `thread-knowledge:${domain}`;
61
- const e = one(delivery?.layers, e => e?.id === id), l = one(i.layers, l => l?.id === id);
156
+ if (
157
+ !Number.isSafeInteger(receipt.systemMessageIndex) ||
158
+ system?.role !== 'system' ||
159
+ typeof system.content !== 'string' ||
160
+ !i.executorContext ||
161
+ !(system.content === i.executorContext || system.content.startsWith(i.executorContext + '\n\n'))
162
+ )
163
+ fail();
164
+ const p = one(i.decoration?.participants, (p) => p?.id === domain),
165
+ id = `thread-knowledge:${domain}`;
166
+ const e = one(delivery?.layers, (e) => e?.id === id),
167
+ l = one(i.layers, (l) => l?.id === id);
62
168
  if (new Set(delivery.layers.map((row: any) => row?.id)).size !== delivery.layers.length) fail();
63
169
  for (const row of delivery.layers.filter((x: any) => x?.domain === domain && x.id !== id)) {
64
- const cache = one(i.layers, l => l?.id === row.id);
65
- if (!owner(cache) || !owner(row) || cache.included !== true || row.assessedRevision !== undefined
66
- || row.relation !== undefined || row.assessedHash !== p.provenance?.cacheHash
67
- || row.deliveredHash !== cache.hash || row.drift !== (row.assessedHash !== row.deliveredHash)) fail();
170
+ const cache = one(i.layers, (l) => l?.id === row.id);
171
+ if (
172
+ !owner(cache) ||
173
+ !owner(row) ||
174
+ cache.included !== true ||
175
+ row.assessedRevision !== undefined ||
176
+ row.relation !== undefined ||
177
+ row.assessedHash !== p.provenance?.cacheHash ||
178
+ row.deliveredHash !== cache.hash ||
179
+ row.drift !== (row.assessedHash !== row.deliveredHash)
180
+ )
181
+ fail();
68
182
  }
69
- if (delivery.layers.filter((e: any) => e?.domain === domain && e?.assessedRevision !== undefined).length !== 1) fail();
70
- const v=l.version, ar=p.provenance?.threadKnowledgeRevision, ah=p.provenance?.threadKnowledgeHash, r=e.deliveredRevision;
71
- if (!owner(l) || l.included !== true || l.segment !== "thread-knowledge" || l.definitionId !== id
72
- || !owner(v) || v?.domain !== domain || e.domain !== domain || !owner(e)
73
- || !Number.isSafeInteger(ar) || ar < 0 || !Number.isSafeInteger(r) || r < 0
74
- || e.assessedRevision !== ar || e.assessedHash !== ah || typeof ah !== "string"
75
- || typeof p.segments?.threadKnowledge !== "string" || typeof l.content !== "string"
76
- || e.deliveredHash !== l.hash || v.hash !== l.hash || v.revision !== r
77
- || e.drift !== (e.assessedHash !== e.deliveredHash)
78
- || (e.relation === "assessed" ? r !== ar || ah !== l.hash : e.relation === "advanced" ? r <= ar : true)) fail();
79
- const b = one(i.blocks, b => b?.source === id && b.kind === "thread-knowledge");
80
- if (b.text !== l.content || b.hash !== l.hash || b.id !== `thread-knowledge:${id}:${l.hash}`) fail();
183
+ if (
184
+ delivery.layers.filter((e: any) => e?.domain === domain && e?.assessedRevision !== undefined)
185
+ .length !== 1
186
+ )
187
+ fail();
188
+ const v = l.version,
189
+ ar = p.provenance?.threadKnowledgeRevision,
190
+ ah = p.provenance?.threadKnowledgeHash,
191
+ r = e.deliveredRevision;
192
+ if (
193
+ !owner(l) ||
194
+ l.included !== true ||
195
+ l.segment !== 'thread-knowledge' ||
196
+ l.definitionId !== id ||
197
+ !owner(v) ||
198
+ v?.domain !== domain ||
199
+ e.domain !== domain ||
200
+ !owner(e) ||
201
+ !Number.isSafeInteger(ar) ||
202
+ ar < 0 ||
203
+ !Number.isSafeInteger(r) ||
204
+ r < 0 ||
205
+ e.assessedRevision !== ar ||
206
+ e.assessedHash !== ah ||
207
+ typeof ah !== 'string' ||
208
+ typeof p.segments?.threadKnowledge !== 'string' ||
209
+ typeof l.content !== 'string' ||
210
+ e.deliveredHash !== l.hash ||
211
+ v.hash !== l.hash ||
212
+ v.revision !== r ||
213
+ e.drift !== (e.assessedHash !== e.deliveredHash) ||
214
+ (e.relation === 'assessed'
215
+ ? r !== ar || ah !== l.hash
216
+ : e.relation === 'advanced'
217
+ ? r <= ar
218
+ : true)
219
+ )
220
+ fail();
221
+ const b = one(i.blocks, (b) => b?.source === id && b.kind === 'thread-knowledge');
222
+ if (b.text !== l.content || b.hash !== l.hash || b.id !== `thread-knowledge:${id}:${l.hash}`)
223
+ fail();
81
224
  // Intended assembled block, not a matching quotation in the user message.
82
- const body = i.blocks.map((b: any) => b.text).join("\n\n");
225
+ const body = i.blocks.map((b: any) => b.text).join('\n\n');
83
226
  if (body !== i.executorContext) fail();
84
- return { domain, layerId:id, assessedRevision:ar, deliveredRevision:r, deliveredHash:l.hash, relation:e.relation, content:l.content };
227
+ return {
228
+ domain,
229
+ layerId: id,
230
+ assessedRevision: ar,
231
+ deliveredRevision: r,
232
+ deliveredHash: l.hash,
233
+ relation: e.relation,
234
+ content: l.content,
235
+ };
85
236
  }
@@ -1,12 +1,12 @@
1
- import type { LifecycleEvent } from "./cache-lifecycle";
2
- import type { Dispatch } from "./thread";
3
- import type { Signal } from "./signal";
4
- import type { DispatchContext } from "./middleware";
5
- import type { ActionPrompt } from "./action-prompt";
1
+ import type { ActionPrompt } from './action-prompt';
2
+ import type { LifecycleEvent } from './cache-lifecycle';
3
+ import type { DispatchContext } from './middleware';
4
+ import type { Signal } from './signal';
5
+ import type { Dispatch } from './thread';
6
6
 
7
7
  /** Session lifecycle events (minimal type for event stream). */
8
8
  export interface SessionEvent {
9
- readonly type: "thread:added" | "thread:removed" | "thread:spawned";
9
+ readonly type: 'thread:added' | 'thread:removed' | 'thread:spawned';
10
10
  readonly threadId: string;
11
11
  readonly parentId?: string;
12
12
  readonly timestamp: number;
@@ -16,14 +16,28 @@ export interface SessionEvent {
16
16
  * Every observable event in the system, tagged by origin.
17
17
  */
18
18
  export type StreamEvent =
19
- | { kind: "journal"; threadId: string; turnId: string | null; projectId?: string; timestamp: number; scope?: "phase" | "learning"; outcome?: "durable" | "failed" }
20
- | { kind: "layer"; threadId: string; event: LifecycleEvent }
21
- | { kind: "dispatch"; threadId: string; dispatch: Dispatch }
22
- | { kind: "signal"; threadId: string; signal: Signal }
23
- | { kind: "prompt"; threadId: string; prompt: ActionPrompt }
24
- | { kind: "session"; event: SessionEvent }
25
- | { kind: "middleware"; threadId: string; phase: "before" | "after"; context: DispatchContext }
26
- | { kind: "error"; source: string; message: string; severity: "error" | "warn"; timestamp: number };
19
+ | {
20
+ kind: 'journal';
21
+ threadId: string;
22
+ turnId: string | null;
23
+ projectId?: string;
24
+ timestamp: number;
25
+ scope?: 'phase' | 'learning';
26
+ outcome?: 'durable' | 'failed';
27
+ }
28
+ | { kind: 'layer'; threadId: string; event: LifecycleEvent }
29
+ | { kind: 'dispatch'; threadId: string; dispatch: Dispatch }
30
+ | { kind: 'signal'; threadId: string; signal: Signal }
31
+ | { kind: 'prompt'; threadId: string; prompt: ActionPrompt }
32
+ | { kind: 'session'; event: SessionEvent }
33
+ | { kind: 'middleware'; threadId: string; phase: 'before' | 'after'; context: DispatchContext }
34
+ | {
35
+ kind: 'error';
36
+ source: string;
37
+ message: string;
38
+ severity: 'error' | 'warn';
39
+ timestamp: number;
40
+ };
27
41
 
28
42
  /**
29
43
  * Unified event stream across all threads and sessions.
@@ -56,7 +70,7 @@ export class EventStream {
56
70
  try {
57
71
  listener(event);
58
72
  } catch (err) {
59
- console.warn("[EventStream] listener error:", (err as Error).message ?? err);
73
+ console.warn('[EventStream] listener error:', (err as Error).message ?? err);
60
74
  }
61
75
  }
62
76
  }
@@ -72,7 +86,7 @@ export class EventStream {
72
86
 
73
87
  /** Get recent events, optionally filtered by kind and/or thread. */
74
88
  recent(opts?: {
75
- kind?: StreamEvent["kind"];
89
+ kind?: StreamEvent['kind'];
76
90
  threadId?: string;
77
91
  limit?: number;
78
92
  }): ReadonlyArray<StreamEvent> {
@@ -84,8 +98,8 @@ export class EventStream {
84
98
 
85
99
  if (opts?.threadId) {
86
100
  events = events.filter((e) => {
87
- if (e.kind === "session") return true; // session events are global
88
- return "threadId" in e && e.threadId === opts.threadId;
101
+ if (e.kind === 'session') return true; // session events are global
102
+ return 'threadId' in e && e.threadId === opts.threadId;
89
103
  });
90
104
  }
91
105
 
@@ -94,8 +108,8 @@ export class EventStream {
94
108
  }
95
109
 
96
110
  /** Push an error event — surfaced as a toast in the viewer UI. */
97
- pushError(source: string, message: string, severity: "error" | "warn" = "error"): void {
98
- this.push({ kind: "error", source, message, severity, timestamp: Date.now() });
111
+ pushError(source: string, message: string, severity: 'error' | 'warn' = 'error'): void {
112
+ this.push({ kind: 'error', source, message, severity, timestamp: Date.now() });
99
113
  }
100
114
 
101
115
  /** Clear history. */
package/src/executor.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { computeHash, REQUIRED_CONTEXT_BLOCKED } from "./context-layer";
2
- import { BaseAgent, type AgentConfig, type ExecutionResult } from "./base-agent";
3
- import type { AssembledContext, ContextStack, LayerFilter } from "./context-stack";
4
- import { buildInjectionArtifact, type InjectionArtifact, type MessageDecoration } from "./messages";
5
- import type { LLMMessage } from "./types";
6
- import type { ToolCallObservation } from "./tools";
7
- import type { NativeEvidence, NativeObservation } from "./native-evidence";
1
+ import { type AgentConfig, BaseAgent, type ExecutionResult } from './base-agent';
2
+ import { computeHash, REQUIRED_CONTEXT_BLOCKED } from './context-layer';
3
+ import type { AssembledContext, ContextStack, LayerFilter } from './context-stack';
4
+ import { buildInjectionArtifact, type InjectionArtifact, type MessageDecoration } from './messages';
5
+ import type { NativeEvidence, NativeObservation } from './native-evidence';
6
+ import type { ToolCallObservation } from './tools';
7
+ import type { LLMMessage } from './types';
8
8
 
9
9
  /** Runtime metadata passed to executor handlers at dispatch time. */
10
10
  export interface ExecuteMeta {
@@ -44,21 +44,17 @@ export interface ExecuteMeta {
44
44
  export type ExecuteHandler<TPayload, TResult> = (
45
45
  context: string,
46
46
  payload: TPayload,
47
- meta?: ExecuteMeta
47
+ meta?: ExecuteMeta,
48
48
  ) => Promise<TResult>;
49
49
 
50
- export interface ExecutorConfig<TPayload = unknown, TResult = unknown>
51
- extends AgentConfig {
50
+ export interface ExecutorConfig<TPayload = unknown, TResult = unknown> extends AgentConfig {
52
51
  handler: ExecuteHandler<TPayload, TResult>;
53
52
  }
54
53
 
55
54
  /**
56
55
  * An Executor takes context + payload, goes and does work, returns full results.
57
56
  */
58
- export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<
59
- TPayload,
60
- TResult
61
- > {
57
+ export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<TPayload, TResult> {
62
58
  private _handler: ExecuteHandler<TPayload, TResult>;
63
59
 
64
60
  constructor(config: ExecutorConfig<TPayload, TResult>) {
@@ -73,7 +69,7 @@ export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<
73
69
  async run(
74
70
  payload: TPayload,
75
71
  filterOverride?: LayerFilter,
76
- meta?: ExecuteMeta
72
+ meta?: ExecuteMeta,
77
73
  ): Promise<ExecutionResult<TResult>> {
78
74
  // Decoration composed by pre-message middleware (parallel domain advice)
79
75
  // rides in on annotations and is appended after the assembled layers, so
@@ -83,37 +79,54 @@ export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<
83
79
  const base = this.assembleContext(filterOverride);
84
80
  const blocks = decoration?.blocks.length ? [...base.blocks, ...decoration.blocks] : base.blocks;
85
81
  const assembled: AssembledContext = decoration?.blocks.length
86
- ? { blocks, text: blocks.map((block) => block.text).join("\n\n") }
82
+ ? { blocks, text: blocks.map((block) => block.text).join('\n\n') }
87
83
  : base;
88
84
  const context = assembled.text;
89
85
  const contextHash = computeHash(context);
90
86
  const capturedAt = Date.now();
91
- const included = new Set(assembled.blocks.map(block => block.id));
92
- const layers = this._stack.layers.map(layer => ({
87
+ const included = new Set(assembled.blocks.map((block) => block.id));
88
+ const layers = this._stack.layers.map((layer) => ({
93
89
  ...layer.snapshotInstance(meta?.threadId),
94
90
  id: layer.id,
95
91
  prompt: layer.prompt,
96
- sourceIds: layer.sources.map(source => source.id),
92
+ sourceIds: layer.sources.map((source) => source.id),
97
93
  included: included.has(layer.id),
98
94
  }));
99
- const userMessage = typeof payload === "string" ? payload : JSON.stringify(payload);
95
+ const userMessage = typeof payload === 'string' ? payload : JSON.stringify(payload);
100
96
  const injection = buildInjectionArtifact({
101
97
  userMessage,
102
98
  assembled: base,
103
99
  decoration,
104
- layerFreshness: Object.fromEntries(layers.map(layer => [layer.id, {
105
- state: layer.state, lastWarmed: layer.lastWarmed,
106
- }])),
100
+ layerFreshness: Object.fromEntries(
101
+ layers.map((layer) => [
102
+ layer.id,
103
+ {
104
+ state: layer.state,
105
+ lastWarmed: layer.lastWarmed,
106
+ },
107
+ ]),
108
+ ),
107
109
  });
108
110
  let providerMessages: LLMMessage[] | undefined;
109
111
  let providerBoundary: ProviderBoundaryReceipt | undefined;
110
- const capture = (): InjectionArtifact => ({ ...injection, capturedAt, threadId: meta?.threadId,
111
- projectId: meta?.projectId, messageId: meta?.messageId, providerBoundary,
112
- executorContext: context, providerMessages, layers,
113
- ...(plan !== undefined ? { plan } : {}) });
112
+ const capture = (): InjectionArtifact => ({
113
+ ...injection,
114
+ capturedAt,
115
+ threadId: meta?.threadId,
116
+ projectId: meta?.projectId,
117
+ messageId: meta?.messageId,
118
+ providerBoundary,
119
+ executorContext: context,
120
+ providerMessages,
121
+ layers,
122
+ ...(plan !== undefined ? { plan } : {}),
123
+ });
114
124
  const observe = (callback: () => void) => {
115
- try { callback(); }
116
- catch (error) { console.warn("[Executor] input evidence observer failed:", error); }
125
+ try {
126
+ callback();
127
+ } catch (error) {
128
+ console.warn('[Executor] input evidence observer failed:', error);
129
+ }
117
130
  };
118
131
  const record = () => observe(() => meta?.recordInjection?.(structuredClone(capture())));
119
132
  // These are in-memory preparation observations, not durable writes or
@@ -122,27 +135,43 @@ export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<
122
135
  // Shared layers may finish a coalesced load for a later request. Preserve
123
136
  // the actual prepared evidence above, but never send it as this request's
124
137
  // selection. No retry/replay is implied by refusing this provider call.
125
- const mismatched = layers.filter(layer => {
138
+ const mismatched = layers.filter((layer) => {
126
139
  if (!layer.included || !layer.selection) return false;
127
140
  const selectedFor = layer.selection.currentMessage;
128
141
  // Standalone legacy sources have no request-identity contract. A flow
129
142
  // plan does: even its unidentified loads must not replace identified work.
130
143
  if (!selectedFor && plan === undefined) return false;
131
144
  const expectedId = meta?.messageId && meta.threadId ? meta.messageId : undefined;
132
- return selectedFor?.messageId !== expectedId || (selectedFor !== undefined &&
133
- (selectedFor.threadId !== meta?.threadId || selectedFor.projectId !== meta?.projectId));
145
+ return (
146
+ selectedFor?.messageId !== expectedId ||
147
+ (selectedFor !== undefined &&
148
+ (selectedFor.threadId !== meta?.threadId || selectedFor.projectId !== meta?.projectId))
149
+ );
134
150
  });
135
151
  if (mismatched.length) {
136
- throw new Error(`Memory selection changed before provider execution: logical message ownership differs for layer(s) ${mismatched.map(l => l.id).join(", ")}`);
152
+ throw new Error(
153
+ `Memory selection changed before provider execution: logical message ownership differs for layer(s) ${mismatched.map((l) => l.id).join(', ')}`,
154
+ );
137
155
  }
138
156
  // A selecting source that could not carry required context is a reason to
139
157
  // stop, not a note for the model. Refuse before any provider call; the
140
158
  // recorded artifact above keeps the conflict and record ids inspectable.
141
- const blocked = layers.filter(layer => layer.included).flatMap(layer => (layer.selection?.sources ?? []).flatMap(source =>
142
- source.report.conflicts.filter(conflict => conflict.kind === REQUIRED_CONTEXT_BLOCKED)
143
- .map(conflict => `layer ${layer.id}, source ${source.sourceId}: ${conflict.ids.join(", ")}`)));
159
+ const blocked = layers
160
+ .filter((layer) => layer.included)
161
+ .flatMap((layer) =>
162
+ (layer.selection?.sources ?? []).flatMap((source) =>
163
+ source.report.conflicts
164
+ .filter((conflict) => conflict.kind === REQUIRED_CONTEXT_BLOCKED)
165
+ .map(
166
+ (conflict) =>
167
+ `layer ${layer.id}, source ${source.sourceId}: ${conflict.ids.join(', ')}`,
168
+ ),
169
+ ),
170
+ );
144
171
  if (blocked.length) {
145
- throw new Error(`Required memory context blocked before provider execution: pinned records exceed the selection cap and no scoped retrieval is available (${blocked.join("; ")})`);
172
+ throw new Error(
173
+ `Required memory context blocked before provider execution: pinned records exceed the selection cap and no scoped retrieval is available (${blocked.join('; ')})`,
174
+ );
146
175
  }
147
176
  let output: TResult;
148
177
  let native: NativeEvidence | undefined;
@@ -150,14 +179,22 @@ export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<
150
179
  try {
151
180
  output = await this._handler(context, payload, {
152
181
  ...meta,
153
- recordNative: evidence => { if (recording) { native = evidence; observe(() => meta?.recordNative?.(evidence)); } },
154
- recordProviderInput: messages => {
182
+ recordNative: (evidence) => {
183
+ if (recording) {
184
+ native = evidence;
185
+ observe(() => meta?.recordNative?.(evidence));
186
+ }
187
+ },
188
+ recordProviderInput: (messages) => {
155
189
  // The initial boundary input must not be replaced by tool followups.
156
190
  if (providerMessages === undefined) {
157
191
  providerMessages = structuredClone(messages);
158
- if (meta?.threadId && meta.messageId) providerBoundary = boundaryReceipt(capture(), {
159
- threadId: meta.threadId, messageId: meta.messageId, ...(meta.projectId ? { projectId: meta.projectId } : {}),
160
- });
192
+ if (meta?.threadId && meta.messageId)
193
+ providerBoundary = boundaryReceipt(capture(), {
194
+ threadId: meta.threadId,
195
+ messageId: meta.messageId,
196
+ ...(meta.projectId ? { projectId: meta.projectId } : {}),
197
+ });
161
198
  }
162
199
  record();
163
200
  observe(() => meta?.recordProviderInput?.(structuredClone(messages)));
@@ -182,4 +219,5 @@ export class Executor<TPayload = unknown, TResult = unknown> extends BaseAgent<
182
219
  };
183
220
  }
184
221
  }
185
- import { boundaryReceipt, type ProviderBoundaryReceipt } from "./delivery-evidence";
222
+
223
+ import { boundaryReceipt, type ProviderBoundaryReceipt } from './delivery-evidence';