agentfootprint 7.17.0 → 7.18.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 (136) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +2 -2
  3. package/ai-instructions/claude-code/SKILL.md +1 -1
  4. package/dist/core/Agent.js +51 -2
  5. package/dist/core/Agent.js.map +1 -1
  6. package/dist/core/RunnerBase.js +16 -1
  7. package/dist/core/RunnerBase.js.map +1 -1
  8. package/dist/core/agent/AgentBuilder.js +117 -1
  9. package/dist/core/agent/AgentBuilder.js.map +1 -1
  10. package/dist/core/agent/middleware/errors.js +54 -0
  11. package/dist/core/agent/middleware/errors.js.map +1 -0
  12. package/dist/core/agent/middleware/index.js +23 -0
  13. package/dist/core/agent/middleware/index.js.map +1 -0
  14. package/dist/core/agent/middleware/ledger.js +61 -0
  15. package/dist/core/agent/middleware/ledger.js.map +1 -0
  16. package/dist/core/agent/middleware/outcomes.js +66 -0
  17. package/dist/core/agent/middleware/outcomes.js.map +1 -0
  18. package/dist/core/agent/middleware/runChain.js +144 -0
  19. package/dist/core/agent/middleware/runChain.js.map +1 -0
  20. package/dist/core/agent/middleware/types.js +41 -0
  21. package/dist/core/agent/middleware/types.js.map +1 -0
  22. package/dist/core/agent/stages/prepareFinal.js +10 -0
  23. package/dist/core/agent/stages/prepareFinal.js.map +1 -1
  24. package/dist/core/agent/stages/route.js +62 -1
  25. package/dist/core/agent/stages/route.js.map +1 -1
  26. package/dist/core/agent/stages/seed.js +130 -85
  27. package/dist/core/agent/stages/seed.js.map +1 -1
  28. package/dist/core/agent/stages/toolCalls.js +195 -8
  29. package/dist/core/agent/stages/toolCalls.js.map +1 -1
  30. package/dist/core/pause.js +19 -1
  31. package/dist/core/pause.js.map +1 -1
  32. package/dist/esm/core/Agent.d.ts +9 -1
  33. package/dist/esm/core/Agent.js +52 -3
  34. package/dist/esm/core/Agent.js.map +1 -1
  35. package/dist/esm/core/RunnerBase.js +16 -1
  36. package/dist/esm/core/RunnerBase.js.map +1 -1
  37. package/dist/esm/core/agent/AgentBuilder.d.ts +97 -0
  38. package/dist/esm/core/agent/AgentBuilder.js +117 -1
  39. package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
  40. package/dist/esm/core/agent/buildAgentChart.d.ts +2 -2
  41. package/dist/esm/core/agent/middleware/errors.d.ts +51 -0
  42. package/dist/esm/core/agent/middleware/errors.js +50 -0
  43. package/dist/esm/core/agent/middleware/errors.js.map +1 -0
  44. package/dist/esm/core/agent/middleware/index.d.ts +16 -0
  45. package/dist/esm/core/agent/middleware/index.js +16 -0
  46. package/dist/esm/core/agent/middleware/index.js.map +1 -0
  47. package/dist/esm/core/agent/middleware/ledger.d.ts +40 -0
  48. package/dist/esm/core/agent/middleware/ledger.js +57 -0
  49. package/dist/esm/core/agent/middleware/ledger.js.map +1 -0
  50. package/dist/esm/core/agent/middleware/outcomes.d.ts +45 -0
  51. package/dist/esm/core/agent/middleware/outcomes.js +60 -0
  52. package/dist/esm/core/agent/middleware/outcomes.js.map +1 -0
  53. package/dist/esm/core/agent/middleware/runChain.d.ts +104 -0
  54. package/dist/esm/core/agent/middleware/runChain.js +139 -0
  55. package/dist/esm/core/agent/middleware/runChain.js.map +1 -0
  56. package/dist/esm/core/agent/middleware/types.d.ts +191 -0
  57. package/dist/esm/core/agent/middleware/types.js +40 -0
  58. package/dist/esm/core/agent/middleware/types.js.map +1 -0
  59. package/dist/esm/core/agent/stages/prepareFinal.d.ts +10 -0
  60. package/dist/esm/core/agent/stages/prepareFinal.js +10 -0
  61. package/dist/esm/core/agent/stages/prepareFinal.js.map +1 -1
  62. package/dist/esm/core/agent/stages/route.d.ts +29 -0
  63. package/dist/esm/core/agent/stages/route.js +60 -0
  64. package/dist/esm/core/agent/stages/route.js.map +1 -1
  65. package/dist/esm/core/agent/stages/seed.d.ts +16 -1
  66. package/dist/esm/core/agent/stages/seed.js +130 -85
  67. package/dist/esm/core/agent/stages/seed.js.map +1 -1
  68. package/dist/esm/core/agent/stages/toolCalls.d.ts +23 -0
  69. package/dist/esm/core/agent/stages/toolCalls.js +195 -8
  70. package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
  71. package/dist/esm/core/agent/types.d.ts +18 -0
  72. package/dist/esm/core/pause.d.ts +42 -0
  73. package/dist/esm/core/pause.js +17 -0
  74. package/dist/esm/core/pause.js.map +1 -1
  75. package/dist/esm/events/dispatcher.d.ts +1 -1
  76. package/dist/esm/events/dispatcher.js.map +1 -1
  77. package/dist/esm/events/payloads.d.ts +26 -0
  78. package/dist/esm/events/registry.d.ts +5 -1
  79. package/dist/esm/events/registry.js +4 -0
  80. package/dist/esm/events/registry.js.map +1 -1
  81. package/dist/esm/index.d.ts +2 -1
  82. package/dist/esm/index.js +13 -1
  83. package/dist/esm/index.js.map +1 -1
  84. package/dist/esm/lib/mcp/mcpServe.js +39 -6
  85. package/dist/esm/lib/mcp/mcpServe.js.map +1 -1
  86. package/dist/esm/lib/mcp/types.d.ts +16 -0
  87. package/dist/events/dispatcher.js.map +1 -1
  88. package/dist/events/registry.js +4 -0
  89. package/dist/events/registry.js.map +1 -1
  90. package/dist/index.js +31 -14
  91. package/dist/index.js.map +1 -1
  92. package/dist/lib/mcp/mcpServe.js +39 -6
  93. package/dist/lib/mcp/mcpServe.js.map +1 -1
  94. package/dist/types/core/Agent.d.ts +9 -1
  95. package/dist/types/core/Agent.d.ts.map +1 -1
  96. package/dist/types/core/RunnerBase.d.ts.map +1 -1
  97. package/dist/types/core/agent/AgentBuilder.d.ts +97 -0
  98. package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
  99. package/dist/types/core/agent/buildAgentChart.d.ts +2 -2
  100. package/dist/types/core/agent/buildAgentChart.d.ts.map +1 -1
  101. package/dist/types/core/agent/middleware/errors.d.ts +52 -0
  102. package/dist/types/core/agent/middleware/errors.d.ts.map +1 -0
  103. package/dist/types/core/agent/middleware/index.d.ts +17 -0
  104. package/dist/types/core/agent/middleware/index.d.ts.map +1 -0
  105. package/dist/types/core/agent/middleware/ledger.d.ts +41 -0
  106. package/dist/types/core/agent/middleware/ledger.d.ts.map +1 -0
  107. package/dist/types/core/agent/middleware/outcomes.d.ts +46 -0
  108. package/dist/types/core/agent/middleware/outcomes.d.ts.map +1 -0
  109. package/dist/types/core/agent/middleware/runChain.d.ts +105 -0
  110. package/dist/types/core/agent/middleware/runChain.d.ts.map +1 -0
  111. package/dist/types/core/agent/middleware/types.d.ts +192 -0
  112. package/dist/types/core/agent/middleware/types.d.ts.map +1 -0
  113. package/dist/types/core/agent/stages/prepareFinal.d.ts +10 -0
  114. package/dist/types/core/agent/stages/prepareFinal.d.ts.map +1 -1
  115. package/dist/types/core/agent/stages/route.d.ts +29 -0
  116. package/dist/types/core/agent/stages/route.d.ts.map +1 -1
  117. package/dist/types/core/agent/stages/seed.d.ts +16 -1
  118. package/dist/types/core/agent/stages/seed.d.ts.map +1 -1
  119. package/dist/types/core/agent/stages/toolCalls.d.ts +23 -0
  120. package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
  121. package/dist/types/core/agent/types.d.ts +18 -0
  122. package/dist/types/core/agent/types.d.ts.map +1 -1
  123. package/dist/types/core/pause.d.ts +42 -0
  124. package/dist/types/core/pause.d.ts.map +1 -1
  125. package/dist/types/events/dispatcher.d.ts +1 -1
  126. package/dist/types/events/dispatcher.d.ts.map +1 -1
  127. package/dist/types/events/payloads.d.ts +26 -0
  128. package/dist/types/events/payloads.d.ts.map +1 -1
  129. package/dist/types/events/registry.d.ts +5 -1
  130. package/dist/types/events/registry.d.ts.map +1 -1
  131. package/dist/types/index.d.ts +2 -1
  132. package/dist/types/index.d.ts.map +1 -1
  133. package/dist/types/lib/mcp/mcpServe.d.ts.map +1 -1
  134. package/dist/types/lib/mcp/types.d.ts +16 -0
  135. package/dist/types/lib/mcp/types.d.ts.map +1 -1
  136. package/package.json +1 -1
@@ -0,0 +1,104 @@
1
+ /**
2
+ * middleware/runChain — INTERNAL. The one place a chain is walked.
3
+ *
4
+ * Pattern: Chain of Responsibility driver.
5
+ * Role: core/ layer. Both stages (tool dispatch, message boundary) and
6
+ * the MCP server call in here, so the ordering law, the
7
+ * throw-is-a-denial law and the ledger row shape are written once
8
+ * and cannot drift between the three callers.
9
+ * Emits: N/A — it RETURNS rows; the caller commits and emits them, so a
10
+ * middleware still never touches scope.
11
+ *
12
+ * ## The rules this file implements
13
+ *
14
+ * **Order is declaration order.** The chain is walked front to back, and
15
+ * each link is handed the previous link's output — so a scrubber placed
16
+ * first is a scrubber every later rule sees the effect of.
17
+ *
18
+ * **The first non-allow answer wins.** A deny or an ask stops the walk;
19
+ * the links behind it do not run. There is no "second opinion" pass,
20
+ * because a refusal that a later middleware could overturn is not a
21
+ * refusal.
22
+ *
23
+ * **A middleware that throws is a denial, never a pass.** The thrown
24
+ * message becomes the reason. This is the same deny-by-default the
25
+ * permission gate has always applied to a checker that throws: a
26
+ * governance layer whose failure mode is "allow" is not a governance
27
+ * layer.
28
+ */
29
+ import type { LLMMessage } from '../../../adapters/types.js';
30
+ import type { MemoryIdentity } from '../../../memory/identity/types.js';
31
+ import type { AskPayload, MessageMiddleware, MiddlewareDecision, ToolMiddleware } from './types.js';
32
+ /** Tool args, as they travel the chain. */
33
+ export type ToolArgs = Readonly<Record<string, unknown>>;
34
+ /**
35
+ * What to do when a middleware answers `ask` somewhere that cannot pause.
36
+ *
37
+ * `'pause'` — the caller will checkpoint (the normal dispatch path).
38
+ * `'refuse'` — the ask is converted into a deny naming the middleware. Used
39
+ * on the resume path (footprintjs's `PausableHandler.resume` returns void,
40
+ * so a resumed dispatch cannot suspend a second time) and by `mcpServe`
41
+ * (MCP is request/response — there is no pause to carry the ask).
42
+ */
43
+ export type AskPolicy = 'pause' | 'refuse';
44
+ export type ToolChainResult = {
45
+ readonly kind: 'allow';
46
+ readonly args: ToolArgs;
47
+ readonly decisions: MiddlewareDecision[];
48
+ } | {
49
+ readonly kind: 'deny';
50
+ readonly reason: string;
51
+ readonly middleware: string;
52
+ readonly args: ToolArgs;
53
+ readonly decisions: MiddlewareDecision[];
54
+ } | {
55
+ readonly kind: 'ask';
56
+ readonly payload: AskPayload;
57
+ readonly middleware: string;
58
+ /** Index of the link that asked — resume continues from `index + 1`. */
59
+ readonly index: number;
60
+ /** Args as transformed up to the ask. These ride the checkpoint. */
61
+ readonly args: ToolArgs;
62
+ readonly decisions: MiddlewareDecision[];
63
+ };
64
+ export interface ToolChainInput {
65
+ readonly toolName: string;
66
+ readonly toolCallId: string;
67
+ readonly iteration: number;
68
+ readonly args: ToolArgs;
69
+ readonly history: readonly LLMMessage[];
70
+ readonly identity?: MemoryIdentity;
71
+ readonly signal?: AbortSignal;
72
+ /**
73
+ * Where to start walking. `0` for a fresh dispatch; `n` on resume, to
74
+ * continue the chain AFTER the link that asked without re-running the
75
+ * links whose decisions the checkpoint already carries.
76
+ */
77
+ readonly startIndex?: number;
78
+ readonly askPolicy?: AskPolicy;
79
+ }
80
+ /** Walk the tool chain. Never throws — a throwing link becomes a denial. */
81
+ export declare function runToolChain(chain: readonly ToolMiddleware[], input: ToolChainInput): Promise<ToolChainResult>;
82
+ export type MessageChainResult = {
83
+ readonly kind: 'allow';
84
+ readonly content: string;
85
+ readonly decisions: MiddlewareDecision[];
86
+ } | {
87
+ readonly kind: 'deny';
88
+ readonly reason: string;
89
+ readonly middleware: string;
90
+ /** Content as transformed by the links BEFORE the refusal. */
91
+ readonly content: string;
92
+ readonly decisions: MiddlewareDecision[];
93
+ };
94
+ export interface MessageChainInput {
95
+ readonly phase: 'input' | 'output';
96
+ readonly content: string;
97
+ readonly history: readonly LLMMessage[];
98
+ /** ReAct iteration for the ledger row. `0` at `'input'` (before iter 1). */
99
+ readonly iteration: number;
100
+ readonly identity?: MemoryIdentity;
101
+ readonly signal?: AbortSignal;
102
+ }
103
+ /** Walk the message chain. Never throws — a throwing link becomes a denial. */
104
+ export declare function runMessageChain(chain: readonly MessageMiddleware[], input: MessageChainInput): Promise<MessageChainResult>;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * middleware/runChain — INTERNAL. The one place a chain is walked.
3
+ *
4
+ * Pattern: Chain of Responsibility driver.
5
+ * Role: core/ layer. Both stages (tool dispatch, message boundary) and
6
+ * the MCP server call in here, so the ordering law, the
7
+ * throw-is-a-denial law and the ledger row shape are written once
8
+ * and cannot drift between the three callers.
9
+ * Emits: N/A — it RETURNS rows; the caller commits and emits them, so a
10
+ * middleware still never touches scope.
11
+ *
12
+ * ## The rules this file implements
13
+ *
14
+ * **Order is declaration order.** The chain is walked front to back, and
15
+ * each link is handed the previous link's output — so a scrubber placed
16
+ * first is a scrubber every later rule sees the effect of.
17
+ *
18
+ * **The first non-allow answer wins.** A deny or an ask stops the walk;
19
+ * the links behind it do not run. There is no "second opinion" pass,
20
+ * because a refusal that a later middleware could overturn is not a
21
+ * refusal.
22
+ *
23
+ * **A middleware that throws is a denial, never a pass.** The thrown
24
+ * message becomes the reason. This is the same deny-by-default the
25
+ * permission gate has always applied to a checker that throws: a
26
+ * governance layer whose failure mode is "allow" is not a governance
27
+ * layer.
28
+ */
29
+ /** Walk the tool chain. Never throws — a throwing link becomes a denial. */
30
+ export async function runToolChain(chain, input) {
31
+ const decisions = [];
32
+ const askPolicy = input.askPolicy ?? 'pause';
33
+ let args = input.args;
34
+ for (let i = input.startIndex ?? 0; i < chain.length; i++) {
35
+ const mw = chain[i];
36
+ const row = (extra) => ({
37
+ middleware: mw.name,
38
+ at: 'tool',
39
+ toolName: input.toolName,
40
+ toolCallId: input.toolCallId,
41
+ iteration: input.iteration,
42
+ outcome: 'allow',
43
+ changed: false,
44
+ ...extra,
45
+ });
46
+ let outcome;
47
+ try {
48
+ outcome = await mw.onToolCall({
49
+ toolName: input.toolName,
50
+ toolCallId: input.toolCallId,
51
+ iteration: input.iteration,
52
+ args,
53
+ history: input.history,
54
+ ...(input.identity && { identity: input.identity }),
55
+ ...(input.signal && { signal: input.signal }),
56
+ });
57
+ }
58
+ catch (err) {
59
+ const reason = `middleware '${mw.name}' threw: ${err instanceof Error ? err.message : String(err)}`;
60
+ decisions.push(row({ outcome: 'deny', why: reason }));
61
+ return { kind: 'deny', reason, middleware: mw.name, args, decisions };
62
+ }
63
+ if (outcome.kind === 'deny') {
64
+ decisions.push(row({ outcome: 'deny', why: outcome.reason }));
65
+ return { kind: 'deny', reason: outcome.reason, middleware: mw.name, args, decisions };
66
+ }
67
+ if (outcome.kind === 'ask') {
68
+ if (askPolicy === 'refuse') {
69
+ const reason = `middleware '${mw.name}' asked a person to decide, and there is no pause here to ` +
70
+ `carry that ask: ${outcome.payload.question}`;
71
+ decisions.push(row({ outcome: 'deny', why: reason }));
72
+ return { kind: 'deny', reason, middleware: mw.name, args, decisions };
73
+ }
74
+ decisions.push(row({ outcome: 'ask', why: outcome.payload.question }));
75
+ return {
76
+ kind: 'ask',
77
+ payload: outcome.payload,
78
+ middleware: mw.name,
79
+ index: i,
80
+ args,
81
+ decisions,
82
+ };
83
+ }
84
+ if (outcome.value !== undefined) {
85
+ const before = args;
86
+ args = outcome.value;
87
+ decisions.push(row({ changed: true, ...(outcome.why && { why: outcome.why }), before, after: args }));
88
+ }
89
+ else {
90
+ decisions.push(row({}));
91
+ }
92
+ }
93
+ return { kind: 'allow', args, decisions };
94
+ }
95
+ /** Walk the message chain. Never throws — a throwing link becomes a denial. */
96
+ export async function runMessageChain(chain, input) {
97
+ const decisions = [];
98
+ let content = input.content;
99
+ for (const mw of chain) {
100
+ const row = (extra) => ({
101
+ middleware: mw.name,
102
+ at: 'message',
103
+ phase: input.phase,
104
+ iteration: input.iteration,
105
+ outcome: 'allow',
106
+ changed: false,
107
+ ...extra,
108
+ });
109
+ let outcome;
110
+ try {
111
+ outcome = await mw.onMessage({
112
+ phase: input.phase,
113
+ content,
114
+ history: input.history,
115
+ ...(input.identity && { identity: input.identity }),
116
+ ...(input.signal && { signal: input.signal }),
117
+ });
118
+ }
119
+ catch (err) {
120
+ const reason = `middleware '${mw.name}' threw: ${err instanceof Error ? err.message : String(err)}`;
121
+ decisions.push(row({ outcome: 'deny', why: reason }));
122
+ return { kind: 'deny', reason, middleware: mw.name, content, decisions };
123
+ }
124
+ if (outcome.kind === 'deny') {
125
+ decisions.push(row({ outcome: 'deny', why: outcome.reason }));
126
+ return { kind: 'deny', reason: outcome.reason, middleware: mw.name, content, decisions };
127
+ }
128
+ if (outcome.value !== undefined) {
129
+ const before = content;
130
+ content = outcome.value;
131
+ decisions.push(row({ changed: true, ...(outcome.why && { why: outcome.why }), before, after: content }));
132
+ }
133
+ else {
134
+ decisions.push(row({}));
135
+ }
136
+ }
137
+ return { kind: 'allow', content, decisions };
138
+ }
139
+ //# sourceMappingURL=runChain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runChain.js","sourceRoot":"","sources":["../../../../../src/core/agent/middleware/runChain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * middleware/types — PUBLIC. The shape of a governance chain.
3
+ *
4
+ * Pattern: Chain of Responsibility (GoF), with the one dangerous answer
5
+ * removed at the type level.
6
+ * Role: core/ layer. Two chains, one vocabulary:
7
+ *
8
+ * `toolMiddleware` wraps every tool dispatch
9
+ * `messageMiddleware` wraps the message boundary — the input
10
+ * before the model sees it, the output
11
+ * before the caller receives it
12
+ *
13
+ * Emits: N/A. A middleware is a pure decision. It never touches scope,
14
+ * never emits and never writes — the stage does all of that, so
15
+ * "record everything that happened" lives in ONE place rather
16
+ * than being re-implemented per middleware.
17
+ *
18
+ * ## The law that shapes these types
19
+ *
20
+ * **A middleware cannot answer for the tool.** The outcome union has three
21
+ * arms — allow, deny, ask — and no `result` arm. There is no spelling of
22
+ * "here is what the tool would have returned". Whatever the chain decides,
23
+ * the answer the model finally reads is the real tool's output or a
24
+ * refusal. That is not a convention a reviewer has to enforce; it is the
25
+ * absence of a field.
26
+ *
27
+ * **A transform declares itself.** `allow(value, why)` requires a reason
28
+ * whenever it changes something, and the stage commits BOTH the original
29
+ * and the transformed value into the run's ledger. A prompt scrubbed by a
30
+ * middleware that hid its own scrubbing would poison every slice taken
31
+ * afterwards: the trace would show text nobody ever sent.
32
+ *
33
+ * **`ask` exists only where a pause exists.** `ToolOutcome` has it because
34
+ * tool dispatch runs inside a footprintjs pausable stage — the same door
35
+ * `checkIn` and `askHuman` already go through. `MessageOutcome` does not,
36
+ * because the message boundary is a plain stage, and inventing a second
37
+ * pause to give it one would be a worse answer than not offering it.
38
+ */
39
+ import type { LLMMessage } from '../../../adapters/types.js';
40
+ import type { MemoryIdentity } from '../../../memory/identity/types.js';
41
+ /**
42
+ * Let the call through — optionally with a replacement for what the chain
43
+ * carries forward.
44
+ *
45
+ * `allow()` passes the value along untouched. `allow(value, why)` replaces
46
+ * it and says why; the `why` is not decoration, it is the row the ledger
47
+ * shows a person asking "who changed this, and what did it look like
48
+ * before?".
49
+ */
50
+ export interface AllowOutcome<T> {
51
+ readonly kind: 'allow';
52
+ /** The replacement value. Absent = pass through unchanged. */
53
+ readonly value?: T;
54
+ /** Why the value changed. Present whenever `value` is. */
55
+ readonly why?: string;
56
+ }
57
+ /**
58
+ * Refuse the call. For a tool, `reason` reaches the model verbatim as the
59
+ * tool result and the run continues — a denial is data the agent can adapt
60
+ * to, not a crash. For a message, `reason` surfaces as a
61
+ * `MessageDeniedError` at the API boundary.
62
+ */
63
+ export interface DenyOutcome {
64
+ readonly kind: 'deny';
65
+ readonly reason: string;
66
+ }
67
+ /** Suspend the run and put the question to a person. Tool dispatch only. */
68
+ export interface AskOutcome {
69
+ readonly kind: 'ask';
70
+ readonly payload: AskPayload;
71
+ }
72
+ /** What a person is being asked. Carried verbatim to the checkpoint. */
73
+ export interface AskPayload {
74
+ /** The question, in your own words. Shown to whoever answers. */
75
+ readonly question: string;
76
+ /** Anything else the answering UI should render. Never interpreted here. */
77
+ readonly detail?: unknown;
78
+ }
79
+ /**
80
+ * Everything a tool middleware may answer. Closed, and every arm has a
81
+ * home in this codebase: allow rides the normal dispatch, deny rides the
82
+ * synthetic tool result every other gate already uses, ask rides the
83
+ * pausable-stage checkpoint that `checkIn` rides.
84
+ */
85
+ export type ToolOutcome = AllowOutcome<Readonly<Record<string, unknown>>> | DenyOutcome | AskOutcome;
86
+ /** Everything a message middleware may answer. No `ask` — see the header. */
87
+ export type MessageOutcome = AllowOutcome<string> | DenyOutcome;
88
+ /** The call a tool middleware is deciding about. */
89
+ export interface ToolMiddlewareContext {
90
+ readonly toolName: string;
91
+ /** Matches `stream.tool_start.toolCallId` for this dispatch. */
92
+ readonly toolCallId: string;
93
+ /** ReAct iteration this call belongs to. */
94
+ readonly iteration: number;
95
+ /**
96
+ * The args as THIS middleware sees them — every earlier transform in the
97
+ * chain already applied. The first middleware sees what the model asked
98
+ * for; the last sees what the tool is about to receive.
99
+ */
100
+ readonly args: Readonly<Record<string, unknown>>;
101
+ /** Conversation so far, including the assistant turn that made this call. */
102
+ readonly history: readonly LLMMessage[];
103
+ /** Multi-tenant run identity, when the run carried one. */
104
+ readonly identity?: MemoryIdentity;
105
+ /** Abort signal from `run({ env: { signal } })`. */
106
+ readonly signal?: AbortSignal;
107
+ }
108
+ /** The message a message middleware is deciding about. */
109
+ export interface MessageMiddlewareContext {
110
+ /**
111
+ * `'input'` runs at the very top of the run, BEFORE the user's message is
112
+ * committed — so the window strategies, the injections, the slots, the
113
+ * request bytes and every later slice all see the transformed text and
114
+ * agree with each other. `'output'` runs where the final answer is
115
+ * captured, so the record and the caller receive the same string.
116
+ */
117
+ readonly phase: 'input' | 'output';
118
+ /** The content as THIS middleware sees it — earlier transforms applied. */
119
+ readonly content: string;
120
+ /** Conversation so far. Empty at `'input'`. */
121
+ readonly history: readonly LLMMessage[];
122
+ readonly identity?: MemoryIdentity;
123
+ readonly signal?: AbortSignal;
124
+ }
125
+ /**
126
+ * One link in the tool-dispatch chain.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * const noProdWrites: ToolMiddleware = {
131
+ * name: 'no-prod-writes',
132
+ * onToolCall: (call) =>
133
+ * call.args.env === 'prod' ? deny('writes to prod need a change ticket') : allow(),
134
+ * };
135
+ * ```
136
+ */
137
+ export interface ToolMiddleware {
138
+ /** Identifies this middleware in every ledger row and event it produces. */
139
+ readonly name: string;
140
+ onToolCall(call: ToolMiddlewareContext): ToolOutcome | Promise<ToolOutcome>;
141
+ }
142
+ /**
143
+ * One link in the message chain. Runs at both phases unless it decides
144
+ * otherwise by reading `msg.phase`.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * const scrubSSNs: MessageMiddleware = {
149
+ * name: 'scrub-ssns',
150
+ * onMessage: (msg) => {
151
+ * const clean = msg.content.replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[ssn]');
152
+ * return clean === msg.content ? allow() : allow(clean, 'masked a US SSN');
153
+ * },
154
+ * };
155
+ * ```
156
+ */
157
+ export interface MessageMiddleware {
158
+ readonly name: string;
159
+ onMessage(msg: MessageMiddlewareContext): MessageOutcome | Promise<MessageOutcome>;
160
+ }
161
+ /**
162
+ * One row per middleware decision, committed to `scope.middlewareDecisions`.
163
+ *
164
+ * Every decision files a row, including the pass-throughs. A chain that
165
+ * only recorded its refusals would leave you unable to tell "the middleware
166
+ * looked and was fine with it" apart from "the middleware never ran" — and
167
+ * those are different facts about a run.
168
+ */
169
+ export interface MiddlewareDecision {
170
+ /** The middleware's `name`. */
171
+ readonly middleware: string;
172
+ /** Which chain this row came from. */
173
+ readonly at: 'tool' | 'message';
174
+ /** Message chain only. */
175
+ readonly phase?: 'input' | 'output';
176
+ /** Tool chain only. */
177
+ readonly toolName?: string;
178
+ /** Tool chain only. */
179
+ readonly toolCallId?: string;
180
+ /** ReAct iteration. `0` for the `'input'` phase, which runs before iter 1. */
181
+ readonly iteration: number;
182
+ readonly outcome: 'allow' | 'deny' | 'ask';
183
+ /** True when this row changed the value the chain carries forward. */
184
+ readonly changed: boolean;
185
+ /** The transform's `why`, the denial's `reason`, or the ask's `question`. */
186
+ readonly why?: string;
187
+ /** The value before this middleware. Present only when `changed`. */
188
+ readonly before?: unknown;
189
+ /** The value after this middleware. Present only when `changed`. */
190
+ readonly after?: unknown;
191
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * middleware/types — PUBLIC. The shape of a governance chain.
3
+ *
4
+ * Pattern: Chain of Responsibility (GoF), with the one dangerous answer
5
+ * removed at the type level.
6
+ * Role: core/ layer. Two chains, one vocabulary:
7
+ *
8
+ * `toolMiddleware` wraps every tool dispatch
9
+ * `messageMiddleware` wraps the message boundary — the input
10
+ * before the model sees it, the output
11
+ * before the caller receives it
12
+ *
13
+ * Emits: N/A. A middleware is a pure decision. It never touches scope,
14
+ * never emits and never writes — the stage does all of that, so
15
+ * "record everything that happened" lives in ONE place rather
16
+ * than being re-implemented per middleware.
17
+ *
18
+ * ## The law that shapes these types
19
+ *
20
+ * **A middleware cannot answer for the tool.** The outcome union has three
21
+ * arms — allow, deny, ask — and no `result` arm. There is no spelling of
22
+ * "here is what the tool would have returned". Whatever the chain decides,
23
+ * the answer the model finally reads is the real tool's output or a
24
+ * refusal. That is not a convention a reviewer has to enforce; it is the
25
+ * absence of a field.
26
+ *
27
+ * **A transform declares itself.** `allow(value, why)` requires a reason
28
+ * whenever it changes something, and the stage commits BOTH the original
29
+ * and the transformed value into the run's ledger. A prompt scrubbed by a
30
+ * middleware that hid its own scrubbing would poison every slice taken
31
+ * afterwards: the trace would show text nobody ever sent.
32
+ *
33
+ * **`ask` exists only where a pause exists.** `ToolOutcome` has it because
34
+ * tool dispatch runs inside a footprintjs pausable stage — the same door
35
+ * `checkIn` and `askHuman` already go through. `MessageOutcome` does not,
36
+ * because the message boundary is a plain stage, and inventing a second
37
+ * pause to give it one would be a worse answer than not offering it.
38
+ */
39
+ export {};
40
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/core/agent/middleware/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG"}
@@ -13,6 +13,16 @@
13
13
  *
14
14
  * Pure function — no closure over Agent class state. Imported and
15
15
  * passed directly to `flowChart(...)` in buildAgentChart.
16
+ *
17
+ * NOT the seam for `messageMiddleware`'s `'output'` half, deliberately.
18
+ * This stage runs inside the Final BRANCH subflow, whose state does not
19
+ * merge back into the run (a branch mount hands its outputMapper the
20
+ * branch's RESULT, not its scope). Rows filed here would land in an
21
+ * isolated commit log and never reach `snapshot.sharedState`, splitting
22
+ * one ledger across two places. The chain runs one stage earlier instead
23
+ * — in the Route decider, in the main chart — and rewrites
24
+ * `llmLatestContent`, which is the value this stage copies. See
25
+ * `stages/route.ts`.
16
26
  */
17
27
  import type { TypedScope } from 'footprintjs';
18
28
  import type { AgentState } from '../types.js';
@@ -13,6 +13,16 @@
13
13
  *
14
14
  * Pure function — no closure over Agent class state. Imported and
15
15
  * passed directly to `flowChart(...)` in buildAgentChart.
16
+ *
17
+ * NOT the seam for `messageMiddleware`'s `'output'` half, deliberately.
18
+ * This stage runs inside the Final BRANCH subflow, whose state does not
19
+ * merge back into the run (a branch mount hands its outputMapper the
20
+ * branch's RESULT, not its scope). Rows filed here would land in an
21
+ * isolated commit log and never reach `snapshot.sharedState`, splitting
22
+ * one ledger across two places. The chain runs one stage earlier instead
23
+ * — in the Route decider, in the main chart — and rewrites
24
+ * `llmLatestContent`, which is the value this stage copies. See
25
+ * `stages/route.ts`.
16
26
  */
17
27
  import { typedEmit } from '../../../recorders/core/typedEmit.js';
18
28
  export const prepareFinalStage = (scope) => {
@@ -1 +1 @@
1
- {"version":3,"file":"prepareFinal.js","sourceRoot":"","sources":["../../../../../src/core/agent/stages/prepareFinal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAGjE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAA6B,EAAQ,EAAE;IACvE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IAClC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,gBAAgB,CAAC;IAC5C,gEAAgE;IAChE,mEAAmE;IACnE,oEAAoE;IACpE,iEAAiE;IACjE,+DAA+D;IAC/D,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;IAC5C,MAAM,WAAW,GAAG,cAAc,KAAK,SAAS,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9E,6DAA6D;IAC7D,wCAAwC;IACxC,KAAK,CAAC,WAAW,GAAG;QAClB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,EAAE;QAC5C;YACE,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,KAAK,CAAC,YAAY;YAC3B,GAAG,CAAC,WAAW,IAAI,EAAE,cAAc,EAAE,CAAC;SACvC;KACF,CAAC;IAEF,SAAS,CAAC,KAAK,EAAE,oCAAoC,EAAE;QACrD,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,SAAS;QACpB,aAAa,EAAE,CAAC;KACjB,CAAC,CAAC;IACH,SAAS,CAAC,KAAK,EAAE,+BAA+B,EAAE;QAChD,SAAS,EAAE,CAAC;QACZ,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,cAAc,EAAE,SAAS;QACzB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,WAAW;KAC3C,CAAC,CAAC;AACL,CAAC,CAAC"}
1
+ {"version":3,"file":"prepareFinal.js","sourceRoot":"","sources":["../../../../../src/core/agent/stages/prepareFinal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAGjE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAA6B,EAAQ,EAAE;IACvE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IAClC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,gBAAgB,CAAC;IAC5C,gEAAgE;IAChE,mEAAmE;IACnE,oEAAoE;IACpE,iEAAiE;IACjE,+DAA+D;IAC/D,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;IAC5C,MAAM,WAAW,GAAG,cAAc,KAAK,SAAS,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9E,6DAA6D;IAC7D,wCAAwC;IACxC,KAAK,CAAC,WAAW,GAAG;QAClB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,EAAE;QAC5C;YACE,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,KAAK,CAAC,YAAY;YAC3B,GAAG,CAAC,WAAW,IAAI,EAAE,cAAc,EAAE,CAAC;SACvC;KACF,CAAC;IAEF,SAAS,CAAC,KAAK,EAAE,oCAAoC,EAAE;QACrD,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,SAAS;QACpB,aAAa,EAAE,CAAC;KACjB,CAAC,CAAC;IACH,SAAS,CAAC,KAAK,EAAE,+BAA+B,EAAE;QAChD,SAAS,EAAE,CAAC;QACZ,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,cAAc,EAAE,SAAS;QACzB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,WAAW;KAC3C,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -14,5 +14,34 @@
14
14
  */
15
15
  import type { TypedScope } from 'footprintjs';
16
16
  import type { AgentState } from '../types.js';
17
+ import type { MessageMiddleware } from '../middleware/types.js';
17
18
  export type RouteBranch = 'tool-calls' | 'final';
18
19
  export declare const routeDeciderStage: (scope: TypedScope<AgentState>) => RouteBranch;
20
+ /**
21
+ * Build the Route decider, optionally carrying the `'output'` half of the
22
+ * message chain.
23
+ *
24
+ * ## Why the chain runs HERE and not in PrepareFinal
25
+ *
26
+ * PrepareFinal is where `finalContent` is set, which makes it look like the
27
+ * output seam. It is not, for one structural reason: it lives inside the Final
28
+ * BRANCH subflow, and a branch mount hands its `outputMapper` the branch's
29
+ * RESULT, not its scope — so nothing that stage writes reaches the run's
30
+ * shared state. Ledger rows filed there would sit in an isolated commit log,
31
+ * and one ledger would be split across two places a reader has to know about.
32
+ *
33
+ * The decider is one stage earlier, in the main chart, and it is the first
34
+ * moment the run KNOWS this turn is the final answer. Its writes commit before
35
+ * the branch resolves (footprintjs commits a decider stage ahead of matching
36
+ * the branch), and the value it rewrites — `llmLatestContent` — is exactly
37
+ * what PrepareFinal copies into `finalContent`. So the caller, the committed
38
+ * record, the `newMessages` the memory writes persist, and the ledger row all
39
+ * carry the same string. One seam, one answer.
40
+ *
41
+ * The `'tool-calls'` path is untouched: a turn that is still calling tools has
42
+ * not produced an answer, and running an output rule over a half-finished turn
43
+ * would be running it over something that is not the output.
44
+ *
45
+ * Empty chain → the exact synchronous decider this file has always exported.
46
+ */
47
+ export declare function buildRouteDeciderStage(messageMiddleware?: readonly MessageMiddleware[]): (scope: TypedScope<AgentState>) => RouteBranch | Promise<RouteBranch>;
@@ -13,6 +13,8 @@
13
13
  * passed directly to `addDeciderFunction(...)` in buildAgentChart.
14
14
  */
15
15
  import { typedEmit } from '../../../recorders/core/typedEmit.js';
16
+ import { runMessageChain } from '../middleware/runChain.js';
17
+ import { recordDecisions } from '../middleware/ledger.js';
16
18
  export const routeDeciderStage = (scope) => {
17
19
  const toolCalls = scope.llmLatestToolCalls;
18
20
  const iteration = scope.iteration;
@@ -29,4 +31,62 @@ export const routeDeciderStage = (scope) => {
29
31
  });
30
32
  return chosen;
31
33
  };
34
+ /**
35
+ * Build the Route decider, optionally carrying the `'output'` half of the
36
+ * message chain.
37
+ *
38
+ * ## Why the chain runs HERE and not in PrepareFinal
39
+ *
40
+ * PrepareFinal is where `finalContent` is set, which makes it look like the
41
+ * output seam. It is not, for one structural reason: it lives inside the Final
42
+ * BRANCH subflow, and a branch mount hands its `outputMapper` the branch's
43
+ * RESULT, not its scope — so nothing that stage writes reaches the run's
44
+ * shared state. Ledger rows filed there would sit in an isolated commit log,
45
+ * and one ledger would be split across two places a reader has to know about.
46
+ *
47
+ * The decider is one stage earlier, in the main chart, and it is the first
48
+ * moment the run KNOWS this turn is the final answer. Its writes commit before
49
+ * the branch resolves (footprintjs commits a decider stage ahead of matching
50
+ * the branch), and the value it rewrites — `llmLatestContent` — is exactly
51
+ * what PrepareFinal copies into `finalContent`. So the caller, the committed
52
+ * record, the `newMessages` the memory writes persist, and the ledger row all
53
+ * carry the same string. One seam, one answer.
54
+ *
55
+ * The `'tool-calls'` path is untouched: a turn that is still calling tools has
56
+ * not produced an answer, and running an output rule over a half-finished turn
57
+ * would be running it over something that is not the output.
58
+ *
59
+ * Empty chain → the exact synchronous decider this file has always exported.
60
+ */
61
+ export function buildRouteDeciderStage(messageMiddleware) {
62
+ const chain = messageMiddleware ?? [];
63
+ if (chain.length === 0)
64
+ return routeDeciderStage;
65
+ return async (scope) => {
66
+ const chosen = routeDeciderStage(scope);
67
+ if (chosen !== 'final')
68
+ return chosen;
69
+ const verdict = await runMessageChain(chain, {
70
+ phase: 'output',
71
+ content: scope.llmLatestContent,
72
+ history: [...scope.history],
73
+ iteration: scope.iteration,
74
+ ...(scope.runIdentity && { identity: scope.runIdentity }),
75
+ });
76
+ recordDecisions(scope, verdict.decisions);
77
+ if (verdict.kind === 'deny') {
78
+ // The answer is NOT released. It stays in the commit log where the run
79
+ // put it, under whatever redaction the run configured, and the boundary
80
+ // raises instead of returning — handing the caller a refusal in place of
81
+ // an answer is the one substitution they must never make unknowingly.
82
+ scope.messageDeniedReason = verdict.reason;
83
+ scope.messageDeniedPhase = 'output';
84
+ scope.messageDeniedBy = verdict.middleware;
85
+ }
86
+ // Committed either way: on a refusal this is what was withheld, and the
87
+ // ledger row beside it says who withheld it and why.
88
+ scope.llmLatestContent = verdict.content;
89
+ return chosen;
90
+ };
91
+ }
32
92
  //# sourceMappingURL=route.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../../src/core/agent/stages/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAKjE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAA6B,EAAe,EAAE;IAC9E,MAAM,SAAS,GAAG,KAAK,CAAC,kBAAiD,CAAC;IAC1E,MAAM,SAAS,GAAG,KAAK,CAAC,SAAmB,CAAC;IAC5C,MAAM,MAAM,GACV,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;IAEnF,SAAS,CAAC,KAAK,EAAE,oCAAoC,EAAE;QACrD,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,SAAS;QACpB,MAAM;QACN,SAAS,EACP,MAAM,KAAK,YAAY;YACrB,CAAC,CAAC,iBAAiB,SAAS,CAAC,MAAM,eAAe;YAClD,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,aAAa;gBAClC,CAAC,CAAC,uCAAuC;gBACzC,CAAC,CAAC,2CAA2C;KAClD,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../../src/core/agent/stages/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AAIjE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAA6B,EAAe,EAAE;IAC9E,MAAM,SAAS,GAAG,KAAK,CAAC,kBAAiD,CAAC;IAC1E,MAAM,SAAS,GAAG,KAAK,CAAC,SAAmB,CAAC;IAC5C,MAAM,MAAM,GACV,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;IAEnF,SAAS,CAAC,KAAK,EAAE,oCAAoC,EAAE;QACrD,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,SAAS;QACpB,MAAM;QACN,SAAS,EACP,MAAM,KAAK,YAAY;YACrB,CAAC,CAAC,iBAAiB,SAAS,CAAC,MAAM,eAAe;YAClD,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,aAAa;gBAClC,CAAC,CAAC,uCAAuC;gBACzC,CAAC,CAAC,2CAA2C;KAClD,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,sBAAsB,CACpC,iBAAgD;IAEhD,MAAM,KAAK,GAAG,iBAAiB,IAAI,EAAE,CAAC;IACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,iBAAiB,CAAC;IACjD,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,OAAO;YAAE,OAAO,MAAM,CAAC;QAEtC,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,KAAK,EAAE;YAC3C,KAAK,EAAE,QAAQ;YACf,OAAO,EAAE,KAAK,CAAC,gBAAgB;YAC/B,OAAO,EAAE,CAAC,GAAI,KAAK,CAAC,OAAiC,CAAC;YACtD,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;SAC1D,CAAC,CAAC;QACH,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC5B,uEAAuE;YACvE,wEAAwE;YACxE,yEAAyE;YACzE,sEAAsE;YACtE,KAAK,CAAC,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;YAC3C,KAAK,CAAC,kBAAkB,GAAG,QAAQ,CAAC;YACpC,KAAK,CAAC,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;QAC7C,CAAC;QACD,wEAAwE;QACxE,qDAAqD;QACrD,KAAK,CAAC,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;QACzC,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}