@kodax-ai/kodax 0.7.39 → 0.7.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -0
- package/README.md +58 -0
- package/README_CN.md +31 -0
- package/dist/chunks/{chunk-SF7WD7E5.js → chunk-5TFLMGER.js} +1 -1
- package/dist/chunks/{chunk-HUAU4KB3.js → chunk-6OB4AJOM.js} +1 -1
- package/dist/chunks/chunk-6QO6HWGU.js +30 -0
- package/dist/chunks/{chunk-SONW6AC7.js → chunk-EQ5DGS2W.js} +1 -1
- package/dist/chunks/chunk-EVIDQWMF.js +5 -0
- package/dist/chunks/chunk-HYWVRTFA.js +1233 -0
- package/dist/chunks/chunk-SX2IS5JP.js +16 -0
- package/dist/chunks/chunk-V4WSBIXB.js +2 -0
- package/dist/chunks/chunk-ZPJPNLBK.js +462 -0
- package/dist/chunks/compaction-config-LT5PEXPT.js +2 -0
- package/dist/chunks/{construction-bootstrap-XSE7ZABG.js → construction-bootstrap-HBCWJFHC.js} +1 -1
- package/dist/chunks/{devtools-MOFU7YQF.js → devtools-EYGFOXEU.js} +1 -1
- package/dist/chunks/{dist-WKW4CBG6.js → dist-M57GIWR4.js} +1 -1
- package/dist/chunks/dist-V3BS2NKB.js +2 -0
- package/dist/chunks/paste-5DSTHQGK.js +2 -0
- package/dist/chunks/{utils-3HW4KOGE.js → utils-FAFUQJ2A.js} +1 -1
- package/dist/index.d.ts +232 -7
- package/dist/index.js +2 -2
- package/dist/kodax_cli.js +945 -923
- package/dist/sdk-agent.d.ts +1459 -10
- package/dist/sdk-agent.js +1 -1
- package/dist/sdk-coding.d.ts +4543 -14
- package/dist/sdk-coding.js +1 -1
- package/dist/sdk-llm.d.ts +209 -10
- package/dist/sdk-llm.js +1 -1
- package/dist/sdk-repl.d.ts +2694 -13
- package/dist/sdk-repl.js +1 -1
- package/dist/sdk-skills.d.ts +487 -11
- package/dist/sdk-skills.js +1 -1
- package/dist/types-chunks/bash-prefix-extractor.d-B2iliwdi.d.ts +2432 -0
- package/dist/types-chunks/capability.d-BxNgd1-c.d.ts +368 -0
- package/dist/types-chunks/cost-tracker.d-C4dMlQuV.d.ts +342 -0
- package/dist/types-chunks/history-cleanup.d-q1vAvCss.d.ts +1266 -0
- package/dist/types-chunks/instance-discovery.d-DZhp77vb.d.ts +1217 -0
- package/dist/types-chunks/resolver.d-BwD6TKz7.d.ts +262 -0
- package/dist/types-chunks/storage.d-Bv9T99Qu.d.ts +584 -0
- package/dist/types-chunks/types.d-C5mHR87z.d.ts +119 -0
- package/package.json +8 -2
- package/dist/acp_events.d.ts +0 -109
- package/dist/acp_logger.d.ts +0 -20
- package/dist/acp_server.d.ts +0 -92
- package/dist/chunks/chunk-4E76FLZ3.js +0 -2
- package/dist/chunks/chunk-7LQ2NCHF.js +0 -1221
- package/dist/chunks/chunk-N2VZ2MJF.js +0 -11
- package/dist/chunks/chunk-WEEQZYZS.js +0 -460
- package/dist/chunks/chunk-XI75LZIO.js +0 -30
- package/dist/chunks/compaction-config-YL4SWWII.js +0 -2
- package/dist/chunks/dist-AMUYI7R5.js +0 -2
- package/dist/cli_commands.d.ts +0 -17
- package/dist/cli_option_helpers.d.ts +0 -49
- package/dist/cli_option_helpers.test.d.ts +0 -1
- package/dist/constructed_cli.d.ts +0 -82
- package/dist/constructed_cli.test.d.ts +0 -1
- package/dist/kodax_cli.d.ts +0 -7
- package/dist/self_modify_cli.d.ts +0 -81
- package/dist/self_modify_cli.test.d.ts +0 -9
- package/dist/skill_cli.d.ts +0 -15
- package/dist/skill_cli.test.d.ts +0 -1
package/dist/sdk-agent.d.ts
CHANGED
|
@@ -1,15 +1,1464 @@
|
|
|
1
|
+
import { A as Agent, H as Handoff, $ as RunnerToolCall, a2 as RunnerToolResult, a6 as Span, a as AgentMessage, C as ChildTaskRegistry, D as DiscoveredInstance, a4 as SessionMeta, a5 as SessionStateSnapshot, aa as StateWriterFs, k as InstanceDiscoveryFs, a9 as StateWriter } from './types-chunks/instance-discovery.d-DZhp77vb.js';
|
|
2
|
+
export { b as AgentMiddlewareDeclaration, c as AgentReasoningProfile, d as AgentTool, e as CurrentTodoSummary, f as DiscoveryOptions, G as Guardrail, g as GuardrailBlockedError, h as GuardrailContext, i as GuardrailEscalateError, j as GuardrailVerdict, I as InputGuardrail, K as KodaXCompactMemoryProgress, l as KodaXCompactMemorySeed, m as KodaXExtensionSessionRecord, n as KodaXExtensionSessionState, o as KodaXExtensionStore, p as KodaXExtensionStoreEntry, q as KodaXJsonValue, r as KodaXSessionArchiveMarkerEntry, s as KodaXSessionArtifactLedgerEntry, t as KodaXSessionBranchSummaryEntry, u as KodaXSessionCompactionEntry, v as KodaXSessionData, w as KodaXSessionEntry, x as KodaXSessionEntryBase, y as KodaXSessionLabelEntry, z as KodaXSessionLineage, B as KodaXSessionMessageEntry, E as KodaXSessionMeta, F as KodaXSessionNavigationOptions, J as KodaXSessionRuntimeInfo, L as KodaXSessionScope, M as KodaXSessionStorage, N as KodaXSessionTreeNode, O as KodaXSessionUiHistoryItem, P as KodaXSessionUiHistoryItemType, Q as KodaXSessionWorkspaceKind, R as MAX_TOOL_LOOP_ITERATIONS, S as OutputGuardrail, T as PersistedSessionState, U as ReasoningDepth, V as RecentlyModifiedFile, W as RequestTaskStopOptions, X as RequestTaskStopResult, Y as RunnableTool, Z as RunnerLlmResult, _ as RunnerLlmReturn, a0 as RunnerToolContext, a1 as RunnerToolObserver, a3 as SessionErrorMetadata, ab as StateWriterOptions, ac as TaskAbortRegistry, ad as ToolBeforeOutcome, ae as ToolGuardrail, af as buildAssistantMessageFromLlmResult, ag as buildToolResultMessage, ah as collectGuardrails, ai as createAgent, aj as createHandoff, ak as createStateWriter, al as discoverInstances, am as executeRunnerToolCall, an as isRunnableTool, ao as isRunnerLlmResult, ap as registerChildTask, aq as requestTaskStop, ar as runInputGuardrails, as as runOutputGuardrails, at as runToolAfterGuardrails, au as runToolBeforeGuardrails } from './types-chunks/instance-discovery.d-DZhp77vb.js';
|
|
3
|
+
import { d as AgentManifest, M as ManifestPatch, k as InvariantId, Q as QualityInvariant } from './types-chunks/history-cleanup.d-q1vAvCss.js';
|
|
4
|
+
export { A as AdmissionAuditOptions, a as AdmissionCtx, b as AdmissionVerdict, c as AdmittedHandle, C as CompactionContext, e as CompactionEntry, f as CompactionEntryPayload, g as CompactionPolicy, D as DEFAULT_SYSTEM_CAP, h as DefaultSummaryCompaction, i as DefaultSummaryCompactionOptions, j as Deliverable, I as InMemorySessionOptions, l as InvariantResult, m as InvariantSession, K as KODAX_API_MIN_INTERVAL, n as KODAX_DEFAULT_TIMEOUT, o as KODAX_HARD_TIMEOUT, p as KODAX_MAX_INCOMPLETE_RETRIES, q as KODAX_MAX_MAXTOKENS_RETRIES, r as KODAX_MAX_RETRIES, s as KODAX_MAX_TOKENS, t as KODAX_RETRY_BASE_DELAY, u as KODAX_STAGGER_DELAY, v as MessageEntry, O as ObserveCtx, P as PROMISE_PATTERN, w as PolicyCompactionResult, x as PresetDispatcher, y as PresetTracingContext, R as ReadonlyMutationTracker, z as ReadonlyRecorder, B as RunEvent, E as RunOptions, F as RunResult, G as Runner, H as RunnerEvent, S as Session, J as SessionDispatchResult, L as SessionEntry, N as SessionExtension, T as SessionForkOptions, U as SystemCap, V as TerminalCtx, W as ToolCapability, X as ToolPermission, _ as _resetAdmittedAgentBindings, Y as _resetPresetDispatchers, Z as buildSystemPrompt, $ as cleanupIncompleteToolCalls, a0 as countTokens, a1 as createInMemorySession, a2 as createInvariantSessionForAgent, a3 as detectInstructionsInjection, a4 as estimateTokens, a5 as extractAssistantTextFromMessage, a6 as getAdmittedAgentBindings, a7 as registerPresetDispatcher, a8 as runAdmissionAudit, a9 as setAdmittedAgentBindings, aa as validateAndFixToolHistory } from './types-chunks/history-cleanup.d-q1vAvCss.js';
|
|
5
|
+
import { o as KodaXMessage } from './types-chunks/capability.d-BxNgd1-c.js';
|
|
6
|
+
export { C as CapabilityKind, a as CapabilityProvider, b as CapabilityResult, g as KodaXAssuranceIntent, i as KodaXContentBlock, k as KodaXExecutionMode, l as KodaXExecutionPattern, n as KodaXImageBlock, q as KodaXMutationSurface, t as KodaXProviderConfig, B as KodaXProviderStreamOptions, F as KodaXReasoningCapability, G as KodaXReasoningMode, I as KodaXReasoningRequest, J as KodaXRedactedThinkingBlock, M as KodaXRiskLevel, N as KodaXStreamResult, O as KodaXTaskActionability, P as KodaXTaskBudgetOverrides, Q as KodaXTaskComplexity, R as KodaXTaskFamily, S as KodaXTaskRoutingDecision, T as KodaXTaskType, U as KodaXTaskWorkIntent, V as KodaXTextBlock, W as KodaXThinkingBlock, X as KodaXThinkingBudgetMap, Y as KodaXThinkingDepth, Z as KodaXTokenUsage, _ as KodaXToolDefinition, $ as KodaXToolResultBlock, a0 as KodaXToolUseBlock } from './types-chunks/capability.d-BxNgd1-c.js';
|
|
7
|
+
import { Q as QueueEventListener, b as QueuedMessage, E as EnqueueInput, D as DequeueFilter, a as MessagePriority, M as MessageMode } from './types-chunks/types.d-C5mHR87z.js';
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
|
-
*
|
|
10
|
+
* Runner Handoff Helpers — FEATURE_084 Shard 4 (v0.7.26).
|
|
3
11
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
12
|
+
* When a tool result carries `metadata.handoffTarget`, the Runner should
|
|
13
|
+
* switch ownership to the target Agent declared in
|
|
14
|
+
* `currentAgent.handoffs`. This module provides pure helpers for that
|
|
15
|
+
* transition:
|
|
7
16
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
17
|
+
* - `detectHandoffSignal(currentAgent, toolResults)` — find the first
|
|
18
|
+
* tool result that carries a matching handoff and return the resolved
|
|
19
|
+
* Handoff + target.
|
|
20
|
+
* - `replaceSystemMessage(transcript, newAgent)` — swap the leading
|
|
21
|
+
* system message so the next LLM turn sees the new agent's
|
|
22
|
+
* instructions.
|
|
23
|
+
* - `emitHandoffSpan(parentSpan, from, to, ...)` — emit the
|
|
24
|
+
* `HandoffSpan` (FEATURE_083 span kind).
|
|
12
25
|
*
|
|
13
|
-
*
|
|
26
|
+
* Guardrail scoping for Shard 4: input / output guardrails are **run-scoped**
|
|
27
|
+
* — they use the starting agent's declarations and run once at start/end of
|
|
28
|
+
* the overall run. They do NOT re-run on handoff. Tool guardrails apply to
|
|
29
|
+
* every tool invocation regardless of which agent is calling. This keeps
|
|
30
|
+
* the mental model simple; later shards may refine.
|
|
14
31
|
*/
|
|
15
|
-
|
|
32
|
+
|
|
33
|
+
interface HandoffSignal {
|
|
34
|
+
readonly from: Agent;
|
|
35
|
+
readonly to: Agent;
|
|
36
|
+
readonly handoff: Handoff;
|
|
37
|
+
/** Index of the tool result that triggered the handoff. */
|
|
38
|
+
readonly triggerIndex: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Find the first tool result with a `handoffTarget` metadata field that
|
|
42
|
+
* resolves to a declared handoff on `currentAgent`. Returns `undefined` if
|
|
43
|
+
* no result carries a handoff target or if the target isn't declared.
|
|
44
|
+
*/
|
|
45
|
+
declare function detectHandoffSignal(currentAgent: Agent, _toolCalls: readonly RunnerToolCall[], toolResults: readonly RunnerToolResult[]): HandoffSignal | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Replace the leading system message with `newAgent`'s instructions so the
|
|
48
|
+
* next LLM turn runs under the new role. The rest of the transcript
|
|
49
|
+
* (user, assistant tool_use, tool_result blocks) is preserved verbatim so
|
|
50
|
+
* the new agent sees the full lead-up.
|
|
51
|
+
*/
|
|
52
|
+
declare function replaceSystemMessage(transcript: readonly AgentMessage[], newAgent: Agent): AgentMessage[];
|
|
53
|
+
/**
|
|
54
|
+
* Emit a `HandoffSpan` as a child of the agent span. Ends immediately
|
|
55
|
+
* because a handoff is a point-in-time event (unlike agent/tool spans
|
|
56
|
+
* which wrap a duration).
|
|
57
|
+
*/
|
|
58
|
+
declare function emitHandoffSpan(parentSpan: Span | null, from: Agent, to: Agent, handoffKind: 'continuation' | 'as-tool', description?: string): void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* FEATURE_101 (v0.7.31) admission runtime — patch reducer + invariant registry.
|
|
62
|
+
*
|
|
63
|
+
* Deterministic, side-effect-free helpers consumed by `Runner.admit()` (in
|
|
64
|
+
* `./runner.ts`, added in the next 1A.3 increment).
|
|
65
|
+
*
|
|
66
|
+
* - `applyManifestPatch(manifest, patch)`: produces a new manifest with the
|
|
67
|
+
* patch applied. Used when admission needs to clamp tools / budget /
|
|
68
|
+
* iterations to system caps.
|
|
69
|
+
* - `composePatches(patches)`: merges multiple patches (deterministic
|
|
70
|
+
* reducer; min wins for clamp values, union for collections).
|
|
71
|
+
* - `InvariantRegistry`: in-memory registry mapping `InvariantId` to
|
|
72
|
+
* `QualityInvariant` implementations. Open-ended — FEATURE_101 v1
|
|
73
|
+
* registers 7 closed-set invariants; FEATURE_106 registers 1 external
|
|
74
|
+
* invariant; future features may add more.
|
|
75
|
+
* - `resolveRequiredInvariants(role, toolScope, harnessTier)`: pure
|
|
76
|
+
* function returning the InvariantId set the admission layer enforces
|
|
77
|
+
* by default (caller's `manifest.declaredInvariants` is unioned on
|
|
78
|
+
* top — declared can only ADD, never remove from required).
|
|
79
|
+
*
|
|
80
|
+
* Pure data + pure functions. No I/O, no shared mutable state visible
|
|
81
|
+
* outside the registry's own opaque object. Tests live in
|
|
82
|
+
* `./admission-runtime.test.ts`.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Apply a patch to a manifest, returning a NEW manifest. Pure function;
|
|
87
|
+
* input is not mutated.
|
|
88
|
+
*
|
|
89
|
+
* Semantics per field:
|
|
90
|
+
*
|
|
91
|
+
* - `removeTools`: manifest.tools filtered by `(t) => !patch.removeTools.includes(t.name)`
|
|
92
|
+
* - `clampMaxBudget`: manifest.maxBudget = min(current, patch.clampMaxBudget) when current > clamp
|
|
93
|
+
* - `clampMaxIterations`: manifest.maxIterations = min(current, patch.clampMaxIterations) when current > clamp.
|
|
94
|
+
* v0.7.31.2 added `AgentManifest.maxIterations` as a first-class
|
|
95
|
+
* field (symmetric with maxBudget). Runner.run reads the post-
|
|
96
|
+
* clamp manifest through `getAdmittedAgentBindings` and takes
|
|
97
|
+
* min-wins against `RunOptions.maxToolLoopIterations`.
|
|
98
|
+
* - `addInvariants`: union into manifest.declaredInvariants
|
|
99
|
+
* - `notes`: ignored at the manifest level — surfaced
|
|
100
|
+
* through AdmissionVerdict.clampNotes
|
|
101
|
+
*
|
|
102
|
+
* The patch is monotone: tools can only be REMOVED (admission can shrink,
|
|
103
|
+
* never expand), budgets can only be CLAMPED DOWN, invariants can only be
|
|
104
|
+
* ADDED. This invariant is what makes "clamp severity is safe to apply
|
|
105
|
+
* automatically" — the manifest can't end up MORE permissive than what
|
|
106
|
+
* the LLM submitted.
|
|
107
|
+
*/
|
|
108
|
+
declare function applyManifestPatch(manifest: AgentManifest, patch: ManifestPatch): AgentManifest;
|
|
109
|
+
/**
|
|
110
|
+
* Compose multiple patches into one. Order is preserved for `notes`; numeric
|
|
111
|
+
* clamp fields use min-wins (most restrictive); collections union.
|
|
112
|
+
*
|
|
113
|
+
* Used when several invariants each return clamp at admission time — the
|
|
114
|
+
* Runner accumulates patches and applies them in one pass.
|
|
115
|
+
*/
|
|
116
|
+
declare function composePatches(patches: readonly ManifestPatch[]): ManifestPatch;
|
|
117
|
+
/**
|
|
118
|
+
* Register an invariant implementation. Throws if the id is already
|
|
119
|
+
* registered (overwrite would be a silent contract bug). Tests that
|
|
120
|
+
* need a fresh registry call `_resetInvariantRegistry()` first.
|
|
121
|
+
*/
|
|
122
|
+
declare function registerInvariant(invariant: QualityInvariant): void;
|
|
123
|
+
/**
|
|
124
|
+
* Look up a registered invariant by id. Returns undefined when not
|
|
125
|
+
* registered — Runner.admit treats this as "skip this id silently"
|
|
126
|
+
* because the closed-set guarantee belongs to the registry caller, not
|
|
127
|
+
* to the runtime.
|
|
128
|
+
*/
|
|
129
|
+
declare function getInvariant(id: InvariantId): QualityInvariant | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Snapshot of the currently-registered invariant ids. Read-only —
|
|
132
|
+
* mutations on the returned array do NOT affect the registry.
|
|
133
|
+
*/
|
|
134
|
+
declare function listRegisteredInvariants(): readonly InvariantId[];
|
|
135
|
+
/**
|
|
136
|
+
* Reset the registry to empty. **Tests only** — production code should
|
|
137
|
+
* never invoke this. Module export name is prefixed with `_` to make
|
|
138
|
+
* accidental imports stand out.
|
|
139
|
+
*/
|
|
140
|
+
declare function _resetInvariantRegistry(): void;
|
|
141
|
+
/**
|
|
142
|
+
* Per-role / per-tool-scope / per-harness-tier required invariant set.
|
|
143
|
+
*
|
|
144
|
+
* Pure function — same inputs always return the same set. The admission
|
|
145
|
+
* layer unions this with `manifest.declaredInvariants` to produce the
|
|
146
|
+
* effective set. Declared can only ADD (it's an LLM voluntary commitment
|
|
147
|
+
* on top of what the system requires).
|
|
148
|
+
*
|
|
149
|
+
* v1 default policy: every manifest gets the 7 admission-v1 closed-set
|
|
150
|
+
* invariants. role / toolScope / harnessTier are accepted but currently
|
|
151
|
+
* not used to differentiate — they're plumbed through so future
|
|
152
|
+
* refinements can specialize without breaking the API.
|
|
153
|
+
*/
|
|
154
|
+
declare function resolveRequiredInvariants(_role: string, _toolScope: readonly string[], _harnessTier: string): readonly InvariantId[];
|
|
155
|
+
/**
|
|
156
|
+
* Effective invariant set for a manifest = union of required (system)
|
|
157
|
+
* and declared (LLM voluntary). Returned in stable order (required
|
|
158
|
+
* first, then declared additions in insertion order).
|
|
159
|
+
*/
|
|
160
|
+
declare function resolveEffectiveInvariants(required: readonly InvariantId[], declared: readonly InvariantId[] | undefined): readonly InvariantId[];
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* FEATURE_101 v0.7.31.1 — admission metrics counters.
|
|
164
|
+
*
|
|
165
|
+
* Closes the "admission decoration" risk called out in
|
|
166
|
+
* docs/features/v0.7.31.md §dispatch eval 新增指标. v0.7.31 declared
|
|
167
|
+
* three metrics in the design (`admission_reject_after_retry_rate`,
|
|
168
|
+
* `admission_clamp_rate`, `invariant_violation_rate`) but never emitted
|
|
169
|
+
* them at runtime — leaving operators with no signal on whether
|
|
170
|
+
* admission was actually catching things or had become a no-op.
|
|
171
|
+
*
|
|
172
|
+
* The module exports an in-process counter table plus pure helpers to
|
|
173
|
+
* read / reset / compute rates. Counters increment from:
|
|
174
|
+
*
|
|
175
|
+
* - `runAdmissionAudit` on every verdict (ok / ok+clamp / reject /
|
|
176
|
+
* reject-final).
|
|
177
|
+
* - `InvariantSession.recordX` and `assertTerminal` on every
|
|
178
|
+
* observed violation.
|
|
179
|
+
*
|
|
180
|
+
* Reading the rates:
|
|
181
|
+
*
|
|
182
|
+
* - `admission_clamp_rate` = admitOkClamped / admitTotal
|
|
183
|
+
* - `admission_reject_after_retry_rate` = admitRejectFinal / admitTotal
|
|
184
|
+
* - `invariant_violation_rate` = invariantViolations / admitTotal
|
|
185
|
+
*
|
|
186
|
+
* Counters are process-local — exporters (Prometheus, OpenTelemetry,
|
|
187
|
+
* etc.) are expected to scrape `getAdmissionMetricsSnapshot()` on a
|
|
188
|
+
* cadence. `_resetAdmissionMetrics` is for tests; production never
|
|
189
|
+
* calls it.
|
|
190
|
+
*/
|
|
191
|
+
interface AdmissionMetricsSnapshot {
|
|
192
|
+
readonly admitTotal: number;
|
|
193
|
+
readonly admitOk: number;
|
|
194
|
+
readonly admitOkClamped: number;
|
|
195
|
+
readonly admitReject: number;
|
|
196
|
+
readonly admitRejectFinal: number;
|
|
197
|
+
readonly invariantViolationsObserved: number;
|
|
198
|
+
readonly invariantViolationsTerminal: number;
|
|
199
|
+
readonly admissionClampRate: number;
|
|
200
|
+
readonly admissionRejectAfterRetryRate: number;
|
|
201
|
+
readonly invariantViolationRate: number;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Snapshot of current counters + computed rates. Returned object is a
|
|
205
|
+
* fresh copy — mutating it does NOT affect the live counters.
|
|
206
|
+
*/
|
|
207
|
+
declare function getAdmissionMetricsSnapshot(): AdmissionMetricsSnapshot;
|
|
208
|
+
/**
|
|
209
|
+
* Test-only reset. Production code MUST NOT call this — the counters
|
|
210
|
+
* are designed to accumulate across the process lifetime.
|
|
211
|
+
*/
|
|
212
|
+
declare function _resetAdmissionMetrics(): void;
|
|
213
|
+
/**
|
|
214
|
+
* Returns true when the admission debug flag is set in the environment.
|
|
215
|
+
* Recognises `'1'`, `'true'`, `'yes'`, `'on'` (case-insensitive).
|
|
216
|
+
* Falls through to false on unset / empty / any other value, so the
|
|
217
|
+
* default is silent.
|
|
218
|
+
*/
|
|
219
|
+
declare function isAdmissionDebugEnabled(): boolean;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* FEATURE_101 invariant: `evidenceTrail`.
|
|
223
|
+
*
|
|
224
|
+
* Mutations must leave evidence; the terminal deliverable verifies the
|
|
225
|
+
* evidence trail is complete.
|
|
226
|
+
*
|
|
227
|
+
* Hooks:
|
|
228
|
+
* - observe(mutation_recorded): no-op in v1 — the runtime mutation
|
|
229
|
+
* tracker already records the file. Reserved for future "did the
|
|
230
|
+
* evidence_added event arrive within N tool calls of the mutation"
|
|
231
|
+
* timing checks.
|
|
232
|
+
* - assertTerminal(deliverable): if the run produced any mutations
|
|
233
|
+
* (deliverable.mutationCount > 0), at least one evidence artifact
|
|
234
|
+
* must accompany them. Empty `evidenceArtifacts` for a mutating run
|
|
235
|
+
* is a reject — the deliverable is unauditable.
|
|
236
|
+
*
|
|
237
|
+
* The threshold is intentionally coarse (any > 0 works): per-file
|
|
238
|
+
* evidence is the @kodax-ai/coding mutation tracker's job, not the Layer A
|
|
239
|
+
* primitive's. We can tighten the rule once FEATURE_089 starts emitting
|
|
240
|
+
* structured mutation→evidence pairings.
|
|
241
|
+
*/
|
|
242
|
+
|
|
243
|
+
declare const evidenceTrail: QualityInvariant;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* FEATURE_101 invariant: `finalOwner`.
|
|
247
|
+
*
|
|
248
|
+
* Admit-only check: the manifest must designate a final owner. v1 heuristic:
|
|
249
|
+
*
|
|
250
|
+
* - manifest.name is non-empty (schema validation already enforces, but
|
|
251
|
+
* re-checking here keeps the invariant self-contained), and
|
|
252
|
+
* - if the manifest declares handoffs, the handoff graph (manifest +
|
|
253
|
+
* ctx.activatedAgents) must contain at least one terminal node — an
|
|
254
|
+
* agent with no outgoing handoffs. If every node has an outgoing
|
|
255
|
+
* handoff, the deliverable has no resting place; admit rejects.
|
|
256
|
+
*
|
|
257
|
+
* The check is intentionally lightweight: deeper graph properties (single
|
|
258
|
+
* sink, dominator analysis) are noise for v1 and would burn invariant
|
|
259
|
+
* budget on hypothetical multi-role topologies that don't ship in
|
|
260
|
+
* v0.7.31. We can sharpen the rule when FEATURE_089 starts emitting
|
|
261
|
+
* richer manifests.
|
|
262
|
+
*
|
|
263
|
+
* Pure function — no I/O, no shared mutable state.
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
declare const finalOwner: QualityInvariant;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* FEATURE_101 invariant: `handoffLegality`.
|
|
270
|
+
*
|
|
271
|
+
* Admit-time DAG check: the handoff graph (manifest.handoffs + the
|
|
272
|
+
* transitive closure of ctx.activatedAgents.handoffs) must be acyclic.
|
|
273
|
+
* A cycle means the system can transfer ownership in a loop without
|
|
274
|
+
* ever terminating — admission rejects.
|
|
275
|
+
*
|
|
276
|
+
* v1 algorithm: iterative DFS with explicit white/gray/black colouring.
|
|
277
|
+
* Iterative (not recursive) because manifests with deep handoff chains
|
|
278
|
+
* could blow the JS stack on certain runtimes — handing the search a
|
|
279
|
+
* stack of our own keeps the bound predictable.
|
|
280
|
+
*
|
|
281
|
+
* Observe-time: no-op in v1. Cycle detection at admit time covers the
|
|
282
|
+
* static graph; runtime handoff traversal is a separate concern handled
|
|
283
|
+
* by the Runner's loop bound. We keep an `observe` stub registered so
|
|
284
|
+
* future versions can add per-event bookkeeping (e.g. detect "agent X
|
|
285
|
+
* already handed off this run, refusing second handoff") without a
|
|
286
|
+
* registry migration.
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
declare const handoffLegality: QualityInvariant;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Pure-new invariant implementations bundled with @kodax-ai/agent.
|
|
293
|
+
*
|
|
294
|
+
* The admission contract types live in `../admission.ts`; the registry
|
|
295
|
+
* runtime in `../admission-runtime.ts`. This module exports the three
|
|
296
|
+
* invariant declarations plus a `registerCoreInvariants()` helper that
|
|
297
|
+
* registers them in one call.
|
|
298
|
+
*
|
|
299
|
+
* Why this split:
|
|
300
|
+
* - These three (finalOwner, handoffLegality, evidenceTrail) are pure
|
|
301
|
+
* functions of the admission types — they have NO @kodax-ai/coding
|
|
302
|
+
* dependencies. Living in @kodax-ai/agent keeps the dependency direction
|
|
303
|
+
* clean and lets `Runner.admit` unit-test against real invariants
|
|
304
|
+
* without pulling the coding runtime into the test harness.
|
|
305
|
+
* - The four coupled invariants (budgetCeiling, toolPermission,
|
|
306
|
+
* boundedRevise, independentReview) wrap @kodax-ai/coding capabilities
|
|
307
|
+
* (mutation tracker, budget controller, ToolGuardrail tier resolver)
|
|
308
|
+
* and live in `@kodax-ai/coding/src/agent-runtime/invariants/`.
|
|
309
|
+
* - `harnessSelectionTiming` (FEATURE_106 external) was here in
|
|
310
|
+
* v0.7.31; v0.7.35.1 FEATURE_142 (A-R2) moved it back to
|
|
311
|
+
* `@kodax-ai/coding/src/agent-runtime/invariants/` because its body
|
|
312
|
+
* hardcoded `ctx.recorder.scout.payload.scout.confirmedHarness` —
|
|
313
|
+
* a coding-AMA Scout-role field reference (ADR-021).
|
|
314
|
+
*
|
|
315
|
+
* Registration is NOT side-effecting on import — consumers call
|
|
316
|
+
* `registerCoreInvariants()` explicitly so test isolation
|
|
317
|
+
* (`_resetInvariantRegistry()` followed by registering only the subset
|
|
318
|
+
* a test needs) stays predictable.
|
|
319
|
+
*/
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The three pure invariants @kodax-ai/agent ships, in registration order.
|
|
323
|
+
* Exposed as a constant so consumers can introspect the set without
|
|
324
|
+
* registering (e.g. dispatch-eval metric setup that wants id labels).
|
|
325
|
+
*/
|
|
326
|
+
declare const CORE_INVARIANTS: readonly QualityInvariant[];
|
|
327
|
+
/**
|
|
328
|
+
* Register the three pure-new invariants on the shared runtime registry.
|
|
329
|
+
* Idempotent only when paired with `_resetInvariantRegistry()` first —
|
|
330
|
+
* `registerInvariant` itself throws on duplicate registration, which is
|
|
331
|
+
* the desired contract (silent overwrite would mask refactors).
|
|
332
|
+
*/
|
|
333
|
+
declare function registerCoreInvariants(): void;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* v0.7.35.1 FEATURE_145 — Agent config home, 3-tier resolution.
|
|
337
|
+
*
|
|
338
|
+
* Centralizes the user-config directory used to be hardcoded across
|
|
339
|
+
* ~30 sites in 6 packages as `path.join(os.homedir(), '.kodax', ...)`.
|
|
340
|
+
* That pattern had two problems:
|
|
341
|
+
*
|
|
342
|
+
* 1. **Drift**: each new caller in a future feature was a fresh
|
|
343
|
+
* hardcode site; nothing stopped a caller from using the wrong
|
|
344
|
+
* string (`'kodax'` instead of `'.kodax'`, etc.).
|
|
345
|
+
* 2. **Substrate consumer coupling**: when `@kodax-ai/agent` is reused
|
|
346
|
+
* by a downstream agent (e.g. `@kodax-ai/ops-agent`,
|
|
347
|
+
* `@kodax-ai/data-analysis-agent`), there was no way to redirect the
|
|
348
|
+
* runtime config dir — every derivative agent was forced to
|
|
349
|
+
* share the `~/.kodax/` namespace.
|
|
350
|
+
*
|
|
351
|
+
* The helper exposes a 3-tier priority chain:
|
|
352
|
+
*
|
|
353
|
+
* 1. **Programmatic override** via {@link setAgentConfigHome} —
|
|
354
|
+
* highest priority. Substrate consumers call this once at boot,
|
|
355
|
+
* before any subsystem reads the path.
|
|
356
|
+
* 2. **`KODAX_HOME` env var** — middle priority. Used by shell / CI /
|
|
357
|
+
* test isolation / multi-tenant shared machines. (Already honored
|
|
358
|
+
* historically by `@kodax-ai/llm/src/reasoning-overrides.ts`; this
|
|
359
|
+
* helper makes it the canonical path for all packages.)
|
|
360
|
+
* 3. **`~/.kodax/`** — lowest priority. Default for the standalone
|
|
361
|
+
* kodax CLI. With DI not set + env not set, the resolver returns
|
|
362
|
+
* the same byte sequence as the prior hardcoded
|
|
363
|
+
* `path.join(os.homedir(), '.kodax')` calls — so the migration
|
|
364
|
+
* from hardcoded sites to this helper is byte-equivalent for the
|
|
365
|
+
* existing user base.
|
|
366
|
+
*
|
|
367
|
+
* Why a process-level singleton (and not per-call DI):
|
|
368
|
+
* the ~30 fs callsites are buried in library helpers (construction /
|
|
369
|
+
* mcp catalog / oauth tokens / paste-cache etc.). Threading a
|
|
370
|
+
* `configHome` parameter through every helper would change ~50
|
|
371
|
+
* function signatures, and every caller would have to remember to
|
|
372
|
+
* thread it — a single forgotten thread silently falls back to
|
|
373
|
+
* default. Singleton matches the `process.env.NODE_ENV` pattern: a
|
|
374
|
+
* process really has a single config home (no legitimate use case
|
|
375
|
+
* for a process to interleave reads/writes against `~/.kodax/` AND
|
|
376
|
+
* `~/.opsagent/` simultaneously).
|
|
377
|
+
*
|
|
378
|
+
* NOT migrated:
|
|
379
|
+
* - `@kodax-ai/llm/src/reasoning-overrides.ts:49` keeps its inline
|
|
380
|
+
* `process.env.KODAX_HOME ?? path.join(os.homedir(), '.kodax')`
|
|
381
|
+
* fallback because moving it to this helper would create an
|
|
382
|
+
* `@kodax-ai/llm → @kodax-ai/agent` dependency cycle (agent already
|
|
383
|
+
* imports ai). The two implementations have identical observable
|
|
384
|
+
* behavior at the env / default tiers; the programmatic override
|
|
385
|
+
* tier doesn't apply to ai-layer code.
|
|
386
|
+
* - **Project-relative** `.kodax/` paths (e.g. `path.join(projectRoot,
|
|
387
|
+
* '.kodax', 'AGENTS.md')`) are NOT migrated — those name a
|
|
388
|
+
* different concept (per-project config) and use a different root.
|
|
389
|
+
* - **CWD-relative** subpath constants like `path.join('.kodax',
|
|
390
|
+
* 'constructed', '_audit.jsonl')` (joined with a project root by
|
|
391
|
+
* the caller) are likewise project-scoped and stay as-is.
|
|
392
|
+
*/
|
|
393
|
+
/**
|
|
394
|
+
* Set the agent config home programmatically. Highest priority in
|
|
395
|
+
* {@link getAgentConfigHome}'s 3-tier chain.
|
|
396
|
+
*
|
|
397
|
+
* Substrate consumers (e.g. an agent built on top of `@kodax-ai/agent`)
|
|
398
|
+
* should call this once at process boot, before any subsystem reads
|
|
399
|
+
* the path. Pass `undefined` to reset (used in tests).
|
|
400
|
+
*/
|
|
401
|
+
declare function setAgentConfigHome(path: string | undefined): void;
|
|
402
|
+
/**
|
|
403
|
+
* Resolve the agent runtime config home directory.
|
|
404
|
+
*
|
|
405
|
+
* Priority (high → low):
|
|
406
|
+
* 1. Programmatic override via {@link setAgentConfigHome}
|
|
407
|
+
* 2. `KODAX_HOME` env var
|
|
408
|
+
* 3. `~/.kodax` (hardcoded default)
|
|
409
|
+
*/
|
|
410
|
+
declare function getAgentConfigHome(): string;
|
|
411
|
+
/**
|
|
412
|
+
* Resolve a sub-path under the agent config home.
|
|
413
|
+
*
|
|
414
|
+
* Equivalent to `path.join(getAgentConfigHome(), ...segments)` but
|
|
415
|
+
* shorter at every callsite (which is the entire point of the helper —
|
|
416
|
+
* 30 callsites of `path.join(os.homedir(), '.kodax', x, y)` collapse to
|
|
417
|
+
* 30 callsites of `getAgentConfigPath(x, y)`).
|
|
418
|
+
*/
|
|
419
|
+
declare function getAgentConfigPath(...segments: string[]): string;
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @kodax-ai/agent/messaging/queue — 2-tier agentId-scoped FIFO message queue.
|
|
423
|
+
*
|
|
424
|
+
* FEATURE_115 (v0.7.36).
|
|
425
|
+
*
|
|
426
|
+
* Invariants:
|
|
427
|
+
* - In-priority FIFO: messages of the same priority drain in enqueue order.
|
|
428
|
+
* - Cross-priority precedence: 'user' drains before 'background', regardless
|
|
429
|
+
* of enqueue order.
|
|
430
|
+
* - agentId routing: messages addressed to agentId X are only visible to
|
|
431
|
+
* consumers filtering for that exact agentId. undefined ≠ "any agent" —
|
|
432
|
+
* it specifically matches main-thread messages.
|
|
433
|
+
* - Not persistent: process restart loses queue state, by design (matches
|
|
434
|
+
* TodoStore semantics).
|
|
435
|
+
*
|
|
436
|
+
* The queue is process-global by default (`getMessageQueue()`); the class is
|
|
437
|
+
* also exported for tests / isolated downstream use.
|
|
438
|
+
*/
|
|
439
|
+
|
|
440
|
+
declare class MessageQueue {
|
|
441
|
+
private messages;
|
|
442
|
+
private nextSeq;
|
|
443
|
+
/**
|
|
444
|
+
* FEATURE_159 (v0.7.40) — observable subscription set. Same pattern as
|
|
445
|
+
* Claude Code's `messageQueueManager.ts` `createSignal()` substrate,
|
|
446
|
+
* but the listener carries a structured `QueueEvent` so SDK
|
|
447
|
+
* observability consumers (logger, tracer, metrics) can react per-
|
|
448
|
+
* event without re-diffing snapshots. `useSyncExternalStore` consumers
|
|
449
|
+
* still work — they ignore the event argument.
|
|
450
|
+
*
|
|
451
|
+
* Cached `snapshotRef` keeps reference identity stable across reads
|
|
452
|
+
* when nothing changed — required by React 18's `useSyncExternalStore`
|
|
453
|
+
* to avoid render loops.
|
|
454
|
+
*/
|
|
455
|
+
private listeners;
|
|
456
|
+
private snapshotRef;
|
|
457
|
+
private notify;
|
|
458
|
+
/**
|
|
459
|
+
* Subscribe to queue mutations. Compatible with React 18's
|
|
460
|
+
* `useSyncExternalStore(subscribe, getSnapshot)` — the hook passes a
|
|
461
|
+
* `() => void` callback, which is structurally assignable to the
|
|
462
|
+
* `QueueEventListener` parameter because TypeScript treats
|
|
463
|
+
* callback parameter discards as compatible. SDK consumers that
|
|
464
|
+
* declare a typed `(event: QueueEvent) => void` listener see the
|
|
465
|
+
* structured event.
|
|
466
|
+
*
|
|
467
|
+
* Listener is called synchronously after every mutation; returns an
|
|
468
|
+
* unsubscribe function.
|
|
469
|
+
*/
|
|
470
|
+
subscribe: (listener: QueueEventListener) => (() => void);
|
|
471
|
+
/**
|
|
472
|
+
* Returns the current frozen queue snapshot. Reference identity is
|
|
473
|
+
* stable across reads when the queue has not mutated, which is the
|
|
474
|
+
* contract React's `useSyncExternalStore` relies on.
|
|
475
|
+
*/
|
|
476
|
+
getSnapshot: () => readonly QueuedMessage[];
|
|
477
|
+
/** Returns the assigned id of the enqueued message. */
|
|
478
|
+
enqueue(input: EnqueueInput): string;
|
|
479
|
+
/**
|
|
480
|
+
* Drain matching messages, ordered by priority (user > background) then
|
|
481
|
+
* FIFO within each priority. Removes drained messages from the queue.
|
|
482
|
+
*/
|
|
483
|
+
dequeue(filter: DequeueFilter): QueuedMessage[];
|
|
484
|
+
/**
|
|
485
|
+
* Peek at matching messages without removing them. Returns messages in
|
|
486
|
+
* the same priority + FIFO order that `dequeue(filter)` would return,
|
|
487
|
+
* so callers can inspect what the next drain would yield.
|
|
488
|
+
*/
|
|
489
|
+
peek(filter: DequeueFilter): QueuedMessage[];
|
|
490
|
+
/** Total queue size across all priorities / agents. */
|
|
491
|
+
size(): number;
|
|
492
|
+
/** Count of messages matching the filter. */
|
|
493
|
+
count(filter: DequeueFilter): number;
|
|
494
|
+
/** True iff at least one message matches the filter. */
|
|
495
|
+
has(filter: DequeueFilter): boolean;
|
|
496
|
+
/** Remove all queued messages — used in tests / process abort scenarios. */
|
|
497
|
+
clear(): void;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Returns the process-global MessageQueue singleton, creating it lazily on
|
|
501
|
+
* first call. Use this for production wiring; instantiate `MessageQueue`
|
|
502
|
+
* directly in tests / isolated subsystems where a shared instance would
|
|
503
|
+
* cause cross-test pollution.
|
|
504
|
+
*/
|
|
505
|
+
declare function getMessageQueue(): MessageQueue;
|
|
506
|
+
/**
|
|
507
|
+
* Test-only reset hook. Production code MUST NOT call this. Underscored
|
|
508
|
+
* prefix follows the same convention as `_resetInvariantRegistry` /
|
|
509
|
+
* `_resetAdmittedAgentBindings` / `_resetAdmissionMetrics`.
|
|
510
|
+
*/
|
|
511
|
+
declare function _resetMessageQueueForTests(): void;
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* @kodax-ai/agent/messaging/drain — mid-turn drain decision (FEATURE_115).
|
|
515
|
+
*
|
|
516
|
+
* Mid-turn drain pulls queued messages between tool execution and the
|
|
517
|
+
* next LLM call. Default ceiling is `user` priority — user input
|
|
518
|
+
* interrupts the agent loop; background-priority messages
|
|
519
|
+
* (subagent task-notifications) wait for the next safe boundary.
|
|
520
|
+
*
|
|
521
|
+
* **FEATURE_155 v0.7.39 Slice C2 — yield-tool gate retired.** Before
|
|
522
|
+
* v0.7.39 the drain widened to background priority when the agent had
|
|
523
|
+
* just called `await_child_task` (FEATURE_119 Pattern B). With idle-
|
|
524
|
+
* yield (the default since Slice B1.D), `await_child_task` is gone and
|
|
525
|
+
* the runner's outer loop in
|
|
526
|
+
* `coding/src/task-engine/_internal/managed-task/idle-yield.ts` owns
|
|
527
|
+
* background-priority dequeue at the no-tool-calls exit. The mid-turn
|
|
528
|
+
* drain therefore stays at `user` priority — that's the only path
|
|
529
|
+
* where it makes sense to interrupt the agent without violating the
|
|
530
|
+
* tool_use/tool_result pairing contract. `YIELD_TOOL_NAMES` and the
|
|
531
|
+
* `lastTurnToolNames` parameter remain on the public surface as a
|
|
532
|
+
* placeholder for any FUTURE yield tool (e.g. an explicit `sleep`),
|
|
533
|
+
* but the set is empty so the gate is currently a no-op.
|
|
534
|
+
*/
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Tools that gate background-priority drain. Empty under FEATURE_155
|
|
538
|
+
* idle-yield (v0.7.39+) — see file header. Adding an entry should
|
|
539
|
+
* remain a deliberate decision: only tools that semantically
|
|
540
|
+
* represent the agent yielding control should unlock background
|
|
541
|
+
* priority, because background drain can interleave subagent
|
|
542
|
+
* results into the main conversation in unexpected places.
|
|
543
|
+
*/
|
|
544
|
+
declare const YIELD_TOOL_NAMES: ReadonlySet<string>;
|
|
545
|
+
interface MaybeDrainMidTurnInput {
|
|
546
|
+
/** Tool names invoked during the most recent iteration's tool_use blocks. */
|
|
547
|
+
readonly lastTurnToolNames: readonly string[];
|
|
548
|
+
/** Current agent's id (`undefined` for the main thread). */
|
|
549
|
+
readonly agentId?: string;
|
|
550
|
+
/** Optional cap on the number of drained messages. */
|
|
551
|
+
readonly limit?: number;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Returns the priority ceiling that mid-turn drain should use given the
|
|
555
|
+
* tools the agent invoked in the most recent iteration.
|
|
556
|
+
*
|
|
557
|
+
* Under FEATURE_155 idle-yield (`YIELD_TOOL_NAMES` empty by default),
|
|
558
|
+
* this always returns `'user'` — background-priority messages are
|
|
559
|
+
* picked up by the runner-driven outer loop's `waitForWakeEvent` at
|
|
560
|
+
* the no-tool-calls exit, not by the mid-turn drain.
|
|
561
|
+
*/
|
|
562
|
+
declare function midTurnDrainPriority(lastTurnToolNames: readonly string[]): MessagePriority;
|
|
563
|
+
/**
|
|
564
|
+
* Drain queued messages destined for `agentId` (main-thread by default)
|
|
565
|
+
* up to the priority ceiling decided by Sleep gating. Returns the drained
|
|
566
|
+
* messages in `dequeue` order (priority-first FIFO).
|
|
567
|
+
*/
|
|
568
|
+
declare function maybeDrainMidTurn(input: MaybeDrainMidTurnInput): QueuedMessage[];
|
|
569
|
+
interface EnqueueChildTaskNotificationInput {
|
|
570
|
+
/**
|
|
571
|
+
* agentId of the parent / coordinator that should receive the
|
|
572
|
+
* notification. `undefined` targets the main thread.
|
|
573
|
+
*/
|
|
574
|
+
readonly parentAgentId?: string;
|
|
575
|
+
/** Stable identifier of the completed child task (e.g. `child-...`). */
|
|
576
|
+
readonly taskId: string;
|
|
577
|
+
/** Human-readable summary appended after the task id banner. */
|
|
578
|
+
readonly summary: string;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Enqueue a `task-notification` message destined for the parent / main
|
|
582
|
+
* thread when a backgrounded child task finishes (FEATURE_119 Pattern B).
|
|
583
|
+
*
|
|
584
|
+
* Goes in at `priority: 'background'`. Under FEATURE_155 idle-yield
|
|
585
|
+
* (v0.7.39+), the runner's outer loop drains background priority at
|
|
586
|
+
* the no-tool-calls exit (`waitForWakeEvent` →
|
|
587
|
+
* `composeIdleYieldUserMessage`), so the notification surfaces in
|
|
588
|
+
* the agent's next-turn user message as a `<task-completed
|
|
589
|
+
* task_id="…">…</task-completed>` block. Pre-v0.7.39 a `yield tool`
|
|
590
|
+
* (`await_child_task`) gated mid-turn drain to background priority —
|
|
591
|
+
* that path is gone with the tool.
|
|
592
|
+
*
|
|
593
|
+
* Returns the enqueued message id (matches `MessageQueue.enqueue` contract).
|
|
594
|
+
*/
|
|
595
|
+
declare function enqueueChildTaskNotification(input: EnqueueChildTaskNotificationInput): string;
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Idle-yield primitives — async chat-while-waiting orchestration core.
|
|
599
|
+
*
|
|
600
|
+
* Originally shipped as `packages/coding/src/task-engine/_internal/
|
|
601
|
+
* managed-task/idle-yield.ts` (v0.7.39 Slices A1-C3, FEATURE_155). The
|
|
602
|
+
* v0.7.38 hotfix follow-up chain (Bug A-G) landed inside the same file.
|
|
603
|
+
* v0.7.39 FEATURE_120 Step 0 (this slice) lifts the module to
|
|
604
|
+
* `@kodax-ai/agent`'s `orchestration/` so any agent-flavor consumer
|
|
605
|
+
* outside KodaX's coding stack can reuse the same async fan-out
|
|
606
|
+
* wait-and-resume mechanic (ADR-021).
|
|
607
|
+
*
|
|
608
|
+
* The lifted module is **generic over the child-task result type**
|
|
609
|
+
* (`TChildResult`). Coding's `KodaXChildExecutionResult` shape stays in
|
|
610
|
+
* `@kodax-ai/coding`; only `taskId` + `error` are read here. The
|
|
611
|
+
* `result` field is opaque — `composeIdleYieldUserMessage` never
|
|
612
|
+
* inspects it (the fallback banner uses only `taskId` / error message).
|
|
613
|
+
*
|
|
614
|
+
* Replaces the blocking `await_child_task` semantics with a Claude-Code-
|
|
615
|
+
* style "agent turn ends idle, runner waits for the next external event"
|
|
616
|
+
* mechanism. When the agent has dispatched ≥1 children and has nothing
|
|
617
|
+
* else to do, it outputs a brief status line (no tool calls), and
|
|
618
|
+
* Runner.run returns. This module gives the runner layer the utilities
|
|
619
|
+
* it needs to interpret that exit and resume:
|
|
620
|
+
*
|
|
621
|
+
* 1. `detectIdleYield(...)` — synchronous predicate over the run's exit
|
|
622
|
+
* state. Returns true when the agent turn ended without an
|
|
623
|
+
* `emit_handoff` AND there are still child tasks the agent is
|
|
624
|
+
* expected to wait on. False on every other path so legacy
|
|
625
|
+
* semantics stay untouched.
|
|
626
|
+
*
|
|
627
|
+
* 2. `waitForWakeEvent(...)` — async race between child-task
|
|
628
|
+
* completions and the MessageQueue. Returns the first event so the
|
|
629
|
+
* runner layer knows what to splice into the next-turn context.
|
|
630
|
+
* Cooperative with `AbortSignal` so REPL Esc tears it down promptly.
|
|
631
|
+
*
|
|
632
|
+
* 3. `composeIdleYieldUserMessage(...)` — given a resolved
|
|
633
|
+
* `WakeEvent`, builds the synthetic user message that the runner
|
|
634
|
+
* should splice into the next `Runner.run` input.
|
|
635
|
+
*
|
|
636
|
+
* Bug A-G hotfix invariants preserved verbatim from the v0.7.38
|
|
637
|
+
* release:
|
|
638
|
+
*
|
|
639
|
+
* - Bug B / D / `hasEmittedTerminalVerdict` field: outer loop gates
|
|
640
|
+
* on terminal Evaluator verdict, NOT on legacy
|
|
641
|
+
* `managedProtocolPayloadRef.verdict`. The agent layer carries
|
|
642
|
+
* the boolean as a snapshot field; callers compute it.
|
|
643
|
+
* - Bug E / `hasPendingBackgroundMessages` field: fast-child race
|
|
644
|
+
* recovery — keep the loop alive when either the registry OR the
|
|
645
|
+
* queue still has undelivered work.
|
|
646
|
+
* - Bug F / abort listener cleanup: explicit
|
|
647
|
+
* `removeEventListener` in `settle()` even on non-abort wakes.
|
|
648
|
+
* - Bug A registry cleanup: NOT this module's responsibility — owned
|
|
649
|
+
* by `registerChildTask` in `task-registry.ts`.
|
|
650
|
+
*/
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Env-flag gate for the runner outer-loop wiring.
|
|
654
|
+
*
|
|
655
|
+
* **Slice C3 (v0.7.39) — flag retired as a runtime gate.** With
|
|
656
|
+
* `await_child_task` removed (Slice C1) there is no working "v0.7.38
|
|
657
|
+
* emulation" path: the prompt + banner always teach idle-yield, so
|
|
658
|
+
* gating only the outer loop would leave a flag-OFF deployment with
|
|
659
|
+
* agents that exit text-only but no resumer to wake them. The
|
|
660
|
+
* function is therefore hard-coded to `true` and exists only for
|
|
661
|
+
* import compatibility with the Slice A1/A2 callers and
|
|
662
|
+
* historical-test references; the env var has no effect.
|
|
663
|
+
*/
|
|
664
|
+
declare function isIdleYieldEnabled(): boolean;
|
|
665
|
+
/**
|
|
666
|
+
* Count the `tool_use` blocks on the last assistant message of a Runner
|
|
667
|
+
* transcript. Used to populate `IdleYieldSnapshot.lastAssistantToolCallCount`
|
|
668
|
+
* — a 0 count is the marker for the no-tool-calls exit branch
|
|
669
|
+
* `Runner.run` uses to terminate its tool loop.
|
|
670
|
+
*/
|
|
671
|
+
declare function countLastAssistantToolCalls(messages: readonly KodaXMessage[]): number;
|
|
672
|
+
/** Snapshot of the agent run's exit state, computed by the runner layer. */
|
|
673
|
+
interface IdleYieldSnapshot {
|
|
674
|
+
/**
|
|
675
|
+
* The last assistant message's tool-call count from the Runner.run
|
|
676
|
+
* transcript. Idle-yield is signalled when this is 0 (Runner
|
|
677
|
+
* exited via the no-tool-calls branch, not via a tool-driven
|
|
678
|
+
* handoff).
|
|
679
|
+
*/
|
|
680
|
+
readonly lastAssistantToolCallCount: number;
|
|
681
|
+
/**
|
|
682
|
+
* Number of child tasks still in the registry when Runner.run
|
|
683
|
+
* returned. Reads `registry.size` at the boundary. Idle-yield only
|
|
684
|
+
* fires when this is > 0 OR `hasPendingBackgroundMessages` is true
|
|
685
|
+
* — otherwise there's nothing to wait for and the stop is a real
|
|
686
|
+
* terminal event.
|
|
687
|
+
*/
|
|
688
|
+
readonly pendingChildTaskCount: number;
|
|
689
|
+
/**
|
|
690
|
+
* True if the run's managed-protocol payload has been populated
|
|
691
|
+
* with a handoff (typically `emit_handoff` for the worker→evaluator
|
|
692
|
+
* boundary). False = the run ended without a handoff. Idle-yield
|
|
693
|
+
* REQUIRES this to be false; otherwise the handoff target already
|
|
694
|
+
* owns the next step.
|
|
695
|
+
*/
|
|
696
|
+
readonly hasEmittedHandoff: boolean;
|
|
697
|
+
/**
|
|
698
|
+
* v0.7.38 FEATURE_155 Bug B+D hotfix — true if the run's managed-
|
|
699
|
+
* protocol payload has been populated with a terminal verdict
|
|
700
|
+
* (`accept` / `blocked`; `revise` triggers a chain re-run, not
|
|
701
|
+
* idle-yield continuation). Without this gate the outer loop would
|
|
702
|
+
* keep re-entering `Runner.run` after a terminal verdict — wasting
|
|
703
|
+
* LLM turns on post-verdict child notifications. Idle-yield
|
|
704
|
+
* REQUIRES this to be false; same reasoning as `hasEmittedHandoff`
|
|
705
|
+
* but for the verdict side.
|
|
706
|
+
*/
|
|
707
|
+
readonly hasEmittedTerminalVerdict: boolean;
|
|
708
|
+
/**
|
|
709
|
+
* v0.7.38 FEATURE_155 Bug E hotfix — true if the background-priority
|
|
710
|
+
* message queue still has undelivered banners destined for the
|
|
711
|
+
* caller agent. Set this alongside `pendingChildTaskCount` because
|
|
712
|
+
* of the **fast-child race**: the dispatch IIFE may enqueue a
|
|
713
|
+
* notification BEFORE its promise resolves, and the registry's
|
|
714
|
+
* `.finally(delete)` cleanup runs in the same microtask burst.
|
|
715
|
+
* When a child completes faster than the surrounding Runner.run
|
|
716
|
+
* iteration (e.g. a sub-second probe vs a multi-second LLM call),
|
|
717
|
+
* the registry entry is removed BEFORE the outer loop reads
|
|
718
|
+
* `pendingChildTaskCount` — making the snapshot see `0`, breaking
|
|
719
|
+
* the loop, and orphaning the banner in the background queue.
|
|
720
|
+
* With this field, the loop stays in the wait state whenever
|
|
721
|
+
* there's still something to deliver, regardless of which arm
|
|
722
|
+
* (registry or queue) carries it.
|
|
723
|
+
*
|
|
724
|
+
* Drained only by the outer loop's `composeIdleYieldUserMessage`
|
|
725
|
+
* call AFTER `waitForWakeEvent` returns. The mid-turn drain caps
|
|
726
|
+
* at `user` priority post-FEATURE_155, so this is the **only**
|
|
727
|
+
* consumer of background-priority messages — losing it strands
|
|
728
|
+
* the banner.
|
|
729
|
+
*/
|
|
730
|
+
readonly hasPendingBackgroundMessages: boolean;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Pure predicate. True when the agent turn ended via the
|
|
734
|
+
* "no tool calls + still has pending children" path that idle-yield
|
|
735
|
+
* is designed to handle.
|
|
736
|
+
*
|
|
737
|
+
* The conjunction terms are deliberately independent — caller can mix
|
|
738
|
+
* in additional gating (e.g. a feature flag) without rewriting this.
|
|
739
|
+
* Returning false here means "treat the run as terminal / delegate to
|
|
740
|
+
* legacy semantics" and is the safe default. The current term set is:
|
|
741
|
+
* `lastAssistantToolCallCount`, `hasEmittedHandoff`,
|
|
742
|
+
* `hasEmittedTerminalVerdict`, and (`pendingChildTaskCount` OR
|
|
743
|
+
* `hasPendingBackgroundMessages`) — the last pair forms the wait-or-
|
|
744
|
+
* resume gate (fast-child race recovery; see field docs).
|
|
745
|
+
*/
|
|
746
|
+
declare function detectIdleYield(snapshot: IdleYieldSnapshot): boolean;
|
|
747
|
+
/**
|
|
748
|
+
* FEATURE_167 (v0.7.41) — terminal-verdict-missing predicate.
|
|
749
|
+
*
|
|
750
|
+
* Companion to `detectIdleYield`, gating the OPPOSITE half of the
|
|
751
|
+
* outer-loop exit decision: when the inner Runner.run loop exits
|
|
752
|
+
* with no tool calls AFTER a handoff has fired but BEFORE the
|
|
753
|
+
* Evaluator has emitted a terminal verdict, the run is structurally
|
|
754
|
+
* INCOMPLETE — the audit step is missing. `detectIdleYield` correctly
|
|
755
|
+
* returns false in this state (the gate at line 174 short-circuits on
|
|
756
|
+
* `hasEmittedHandoff`), so the outer loop currently terminates with
|
|
757
|
+
* `recorder.verdict === undefined`. Downstream `deriveFinalStatus`
|
|
758
|
+
* then falls back to `signal:'COMPLETE'`, falsely reporting success
|
|
759
|
+
* for a failed audit (production session 20260515_185354).
|
|
760
|
+
*
|
|
761
|
+
* This predicate identifies that exact missing-verdict state so the
|
|
762
|
+
* outer loop can branch into a retry path (inject a "call emit_verdict"
|
|
763
|
+
* prompt and re-invoke the Evaluator) before falling through to a
|
|
764
|
+
* synthesized fallback verdict.
|
|
765
|
+
*
|
|
766
|
+
* **Disjoint from `detectIdleYield`** by construction:
|
|
767
|
+
*
|
|
768
|
+
* - `detectIdleYield` requires `!hasEmittedHandoff`
|
|
769
|
+
* - `detectMissingTerminalVerdict` requires `hasEmittedHandoff`
|
|
770
|
+
*
|
|
771
|
+
* Both also require `lastAssistantToolCallCount === 0`. Both gate on
|
|
772
|
+
* `!hasEmittedTerminalVerdict`. The two predicates partition the
|
|
773
|
+
* "Evaluator turn just exited text-only" space cleanly — no run can
|
|
774
|
+
* satisfy both.
|
|
775
|
+
*
|
|
776
|
+
* **Pending children take precedence over verdict retry.** Predicate
|
|
777
|
+
* is intentionally `pendingChildTaskCount <= 0 && !hasPendingBackgroundMessages`
|
|
778
|
+
* so that if Evaluator dispatched audit children and is correctly
|
|
779
|
+
* idle-yielding for them (FEATURE_155 Bug C discipline), the
|
|
780
|
+
* verdict-missing retry does NOT fire — `detectIdleYield` handles
|
|
781
|
+
* that path instead. Only when there's nothing left to wait on does
|
|
782
|
+
* the verdict-missing branch take over.
|
|
783
|
+
*/
|
|
784
|
+
declare function detectMissingTerminalVerdict(snapshot: IdleYieldSnapshot): boolean;
|
|
785
|
+
/**
|
|
786
|
+
* Discriminated union surfacing the reason a wake completed.
|
|
787
|
+
*
|
|
788
|
+
* Generic over `TChildResult` so coding-flavor consumers can carry
|
|
789
|
+
* their `KodaXChildExecutionResult` shape through `child-completed`
|
|
790
|
+
* wakes without the agent layer naming the type. This module only
|
|
791
|
+
* reads `taskId` (for the fallback banner) and `error` — `result` is
|
|
792
|
+
* opaque pass-through.
|
|
793
|
+
*/
|
|
794
|
+
type WakeEvent<TChildResult = unknown> = {
|
|
795
|
+
readonly kind: 'child-completed';
|
|
796
|
+
readonly taskId: string;
|
|
797
|
+
readonly result: TChildResult;
|
|
798
|
+
} | {
|
|
799
|
+
readonly kind: 'child-failed';
|
|
800
|
+
readonly taskId: string;
|
|
801
|
+
readonly error: Error;
|
|
802
|
+
} | {
|
|
803
|
+
readonly kind: 'messages-arrived';
|
|
804
|
+
readonly messages: readonly QueuedMessage[];
|
|
805
|
+
} | {
|
|
806
|
+
readonly kind: 'aborted';
|
|
807
|
+
};
|
|
808
|
+
interface WaitForWakeEventOptions<TChildResult = unknown> {
|
|
809
|
+
/**
|
|
810
|
+
* Live ChildTaskRegistry snapshot. The waiter wraps each entry's
|
|
811
|
+
* promise so the FIRST settling child wins the race.
|
|
812
|
+
*
|
|
813
|
+
* **NOTE**: the waiter does NOT delete entries on settlement — the
|
|
814
|
+
* registry's normal cleanup path (`registerChildTask`'s built-in
|
|
815
|
+
* `.finally` chain) owns deletion. Wrapping doesn't double-consume.
|
|
816
|
+
*/
|
|
817
|
+
readonly registry: ChildTaskRegistry<TChildResult>;
|
|
818
|
+
/** Process-global message queue surface (FEATURE_115 substrate). */
|
|
819
|
+
readonly messageQueue: MessageQueue;
|
|
820
|
+
/**
|
|
821
|
+
* AgentId filter for queue dequeues. Use `undefined` to match
|
|
822
|
+
* main-thread messages (the standard queue scope).
|
|
823
|
+
*/
|
|
824
|
+
readonly agentId: string | undefined;
|
|
825
|
+
/**
|
|
826
|
+
* Optional cancellation. When fired, the waiter resolves with
|
|
827
|
+
* `{ kind: 'aborted' }` and tears down its poll timer.
|
|
828
|
+
*/
|
|
829
|
+
readonly abortSignal?: AbortSignal;
|
|
830
|
+
/**
|
|
831
|
+
* Queue poll interval. The MessageQueue is poll-based; this is the
|
|
832
|
+
* granularity at which user input becomes visible to the waiter.
|
|
833
|
+
* 100 ms keeps perceived REPL responsiveness < 1 frame at 60 fps
|
|
834
|
+
* for the human eye and stays well below typical LLM-call latency.
|
|
835
|
+
* Tests can pass smaller values (e.g. 10 ms) to keep them fast.
|
|
836
|
+
*/
|
|
837
|
+
readonly pollIntervalMs?: number;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Race child completions against MessageQueue arrivals. Returns the
|
|
841
|
+
* first wake event. Guarantees:
|
|
842
|
+
*
|
|
843
|
+
* - Cleanup: the poll timer is cleared on resolution regardless of
|
|
844
|
+
* which arm wins.
|
|
845
|
+
* - At-most-once dequeue: when the queue arm wins, the messages it
|
|
846
|
+
* drained are returned to the caller AND removed from the queue
|
|
847
|
+
* (the caller is now responsible for splicing them into the
|
|
848
|
+
* agent's next-turn context).
|
|
849
|
+
* - Abort-safe: if `abortSignal` fires before any other event, the
|
|
850
|
+
* waiter resolves with `{ kind: 'aborted' }`. Already-settled
|
|
851
|
+
* child promises are NOT cancelled — the registry's owner handles
|
|
852
|
+
* them on the next turn.
|
|
853
|
+
*
|
|
854
|
+
* Caller responsibilities:
|
|
855
|
+
* - Pass the EXACT registry snapshot (not a copy) so subsequent
|
|
856
|
+
* dispatches the agent performs after wake are visible to the
|
|
857
|
+
* next `waitForWakeEvent` call.
|
|
858
|
+
* - Splice the returned messages / child result into the agent's
|
|
859
|
+
* next Runner.run input. The waiter does not itself construct
|
|
860
|
+
* synthetic user-message bytes — that's the runner-layer's job.
|
|
861
|
+
*/
|
|
862
|
+
declare function waitForWakeEvent<TChildResult = unknown>(options: WaitForWakeEventOptions<TChildResult>): Promise<WakeEvent<TChildResult>>;
|
|
863
|
+
/**
|
|
864
|
+
* Compose the synthetic user message spliced after an agent idle-yield
|
|
865
|
+
* resume. The runner outer loop calls this with the resolved
|
|
866
|
+
* `WakeEvent` plus a function that drains any pending background
|
|
867
|
+
* messages (typically `() => getMessageQueue().dequeue(...)`).
|
|
868
|
+
*
|
|
869
|
+
* - `messages-arrived` wake: the queue arm already drained the
|
|
870
|
+
* QueuedMessage(s); pass their content through verbatim. Then
|
|
871
|
+
* drain anything that arrived between settle and this call (a
|
|
872
|
+
* tight race with the dispatch handler's enqueue is possible) and
|
|
873
|
+
* concatenate.
|
|
874
|
+
*
|
|
875
|
+
* - `child-completed` / `child-failed` wake: the dispatch handler's
|
|
876
|
+
* in-IIFE `enqueueChildTaskNotification` is a precondition of the
|
|
877
|
+
* promise settling, so the queue holds the canonical
|
|
878
|
+
* `<task-completed>` banner. Drain to capture it. If for any
|
|
879
|
+
* reason the banner is missing (defensive — a misbehaving
|
|
880
|
+
* dispatch path that resolved without enqueuing), synthesize a
|
|
881
|
+
* minimal one from the wake event so the agent still observes
|
|
882
|
+
* the resolution rather than silently looping again.
|
|
883
|
+
*
|
|
884
|
+
* - `aborted`: caller is expected to break out before reaching this
|
|
885
|
+
* helper. If reached anyway, returns `undefined` so the outer
|
|
886
|
+
* loop treats it as a terminal exit.
|
|
887
|
+
*
|
|
888
|
+
* Returns `undefined` when the wake yields no spliceable content —
|
|
889
|
+
* the outer loop treats that as a real terminal exit.
|
|
890
|
+
*/
|
|
891
|
+
/**
|
|
892
|
+
* FEATURE_121 (v0.7.40) — Envelope aggregate budget enforcer.
|
|
893
|
+
*
|
|
894
|
+
* Pure `string[] → string[]` transform applied to drained envelope
|
|
895
|
+
* fragments before they're joined into a single synthetic user
|
|
896
|
+
* message. The coding layer provides the implementation that knows
|
|
897
|
+
* how to spill oversized fragments to disk and replace them with
|
|
898
|
+
* preview + path markers (see `@kodax-ai/coding`
|
|
899
|
+
* `createEnvelopeAggregateBudgetEnforcer`). The agent layer only
|
|
900
|
+
* sees this opaque function type — **no `@kodax-ai/coding` symbols
|
|
901
|
+
* may leak into this signature**, otherwise ADR-021 layer
|
|
902
|
+
* independence breaks and `@kodax/agent` cannot build standalone.
|
|
903
|
+
*/
|
|
904
|
+
type EnvelopeAggregateEnforcer = (fragments: readonly string[]) => readonly string[] | Promise<readonly string[]>;
|
|
905
|
+
/**
|
|
906
|
+
* FEATURE_159 (v0.7.40) — mode-split synthetic.
|
|
907
|
+
*
|
|
908
|
+
* Pre-FEATURE_159: every wake-drained message was wrapped in a single
|
|
909
|
+
* `_synthetic: true` user message. This was correct for child-task
|
|
910
|
+
* notifications (the agent needs to see them; the human doesn't), but
|
|
911
|
+
* WRONG for user-typed prompts that arrived via chat-while-waiting —
|
|
912
|
+
* those got hidden from the transcript so users couldn't see their own
|
|
913
|
+
* messages echoed in the conversation history.
|
|
914
|
+
*
|
|
915
|
+
* Post-FEATURE_159: fragments are partitioned by `msg.mode`. Two
|
|
916
|
+
* separate messages may be emitted:
|
|
917
|
+
* 1. Synthetic banner (`_synthetic: true`) — concatenates
|
|
918
|
+
* `task-notification` + `system-reminder` content. Hidden from
|
|
919
|
+
* REPL display; the agent sees it as context. Spliced FIRST so it
|
|
920
|
+
* reads as the "tail of the prior turn" before the new prompt.
|
|
921
|
+
* 2. Real user message (no `_synthetic`) — concatenates `prompt`
|
|
922
|
+
* content from chat-while-waiting. Renders as a normal user
|
|
923
|
+
* bubble in transcript. Spliced AFTER the banner so the chain
|
|
924
|
+
* reads naturally as "previous turn outputs → user follow-up".
|
|
925
|
+
*
|
|
926
|
+
* Aggregate budget enforcer applies ONLY to the synthetic banner
|
|
927
|
+
* fragments (the side that can carry child-task envelopes of arbitrary
|
|
928
|
+
* size). User prompts pass through unchanged — the user's intent must
|
|
929
|
+
* never be silently truncated.
|
|
930
|
+
*
|
|
931
|
+
* Return type changed from `KodaXMessage | undefined` to
|
|
932
|
+
* `readonly KodaXMessage[]` (possibly empty). Callers must spread the
|
|
933
|
+
* result into their next-iteration input.
|
|
934
|
+
*/
|
|
935
|
+
declare function composeIdleYieldUserMessage<TChildResult = unknown>(wakeEvent: WakeEvent<TChildResult>, drainBackgroundQueue: () => readonly QueuedMessage[], enforceAggregate?: EnvelopeAggregateEnforcer): Promise<readonly KodaXMessage[]>;
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Generic outer loop for async fan-out + idle-yield resume.
|
|
939
|
+
*
|
|
940
|
+
* FEATURE_120 v0.7.39 Step 0c. Wraps the `while (true) { Runner.run; if
|
|
941
|
+
* (detectIdleYield) waitForWakeEvent + compose + resume; else break }`
|
|
942
|
+
* control flow so that any agent flavor consuming `@kodax-ai/agent` as
|
|
943
|
+
* a standalone framework can adopt the FEATURE_155 chat-while-waiting
|
|
944
|
+
* pattern without re-implementing the loop and re-discovering the
|
|
945
|
+
* v0.7.38 Bug A-G hotfix invariants.
|
|
946
|
+
*
|
|
947
|
+
* The wrapper does NOT take ownership of any agent-flavor-specific
|
|
948
|
+
* state — recorder access, role mapping, status-bar emission,
|
|
949
|
+
* checkpoint cleanup, chain-reset-on-resume — those flow in through
|
|
950
|
+
* callbacks. The wrapper owns only the universal pieces:
|
|
951
|
+
*
|
|
952
|
+
* 1. Iteration cap (default 64) — defensive against prompt bugs.
|
|
953
|
+
* 2. Per-iteration `runOnce` invocation (callee owns Runner.run
|
|
954
|
+
* options closure + error-path cleanup chain).
|
|
955
|
+
* 3. Snapshot computation via callback, then `detectIdleYield` gate.
|
|
956
|
+
* 4. Optional `onIdleWaiting` hook fired AFTER the gate passes but
|
|
957
|
+
* BEFORE the wake wait.
|
|
958
|
+
* 5. `waitForWakeEvent` with the registry / queue / abort plumbing.
|
|
959
|
+
* 6. `composeIdleYieldUserMessage` to splice synthetic input.
|
|
960
|
+
* 7. `resumeAgent` callback to pick the agent for the next iteration.
|
|
961
|
+
*
|
|
962
|
+
* Order is significant — see Risk R4 in v0.7.39 Phase 1c design notes.
|
|
963
|
+
* Tests in `runner-with-idle-yield.test.ts` pin every boundary.
|
|
964
|
+
*
|
|
965
|
+
* The wrapper has zero inbound dependency on coding-flavor types; all
|
|
966
|
+
* agent-flavor specifics flow through `TRunResult` / `TChildResult` /
|
|
967
|
+
* the callbacks (ADR-021).
|
|
968
|
+
*/
|
|
969
|
+
|
|
970
|
+
/** Default iteration cap matches the legacy `IDLE_YIELD_MAX_ITERATIONS` constant. */
|
|
971
|
+
declare const DEFAULT_IDLE_YIELD_MAX_ITERATIONS = 64;
|
|
972
|
+
/**
|
|
973
|
+
* Minimal shape requirement on the run result the caller's `runOnce`
|
|
974
|
+
* returns. The wrapper reads `messages` to replay the transcript into
|
|
975
|
+
* the next iteration after a wake; everything else on the result is
|
|
976
|
+
* opaque pass-through.
|
|
977
|
+
*/
|
|
978
|
+
interface RunWithIdleYieldRunResult {
|
|
979
|
+
readonly messages: readonly AgentMessage[];
|
|
980
|
+
}
|
|
981
|
+
interface RunWithIdleYieldOptions<TRunResult extends RunWithIdleYieldRunResult, TChildResult> {
|
|
982
|
+
/** Agent that executes the first iteration of the loop. */
|
|
983
|
+
readonly initialAgent: Agent;
|
|
984
|
+
/** Initial input messages for the first `Runner.run`. */
|
|
985
|
+
readonly initialInput: readonly AgentMessage[];
|
|
986
|
+
/**
|
|
987
|
+
* Per-iteration Runner invocation. Caller closes over its Runner
|
|
988
|
+
* options (llm, guardrails, toolObserver, maxToolLoopIterations,
|
|
989
|
+
* abortSignal, compactionHook, error-path cleanup). The wrapper
|
|
990
|
+
* passes the current `agent` and `input` and awaits the result.
|
|
991
|
+
*
|
|
992
|
+
* **Error-path note**: if `runOnce` rejects, the rejection
|
|
993
|
+
* propagates out of `runWithIdleYield` verbatim — wrapper does not
|
|
994
|
+
* swallow. Caller's `.catch` (if any) must run inside the
|
|
995
|
+
* `runOnce` closure to keep cleanup ordering observable.
|
|
996
|
+
*/
|
|
997
|
+
readonly runOnce: (agent: Agent, input: readonly AgentMessage[]) => Promise<TRunResult>;
|
|
998
|
+
/**
|
|
999
|
+
* Compute the `IdleYieldSnapshot` after each `runOnce` returns.
|
|
1000
|
+
* Caller has access to the full run result + any external state
|
|
1001
|
+
* via closure (recorder, registry size, queue inspection).
|
|
1002
|
+
*/
|
|
1003
|
+
readonly computeSnapshot: (runResult: TRunResult) => IdleYieldSnapshot;
|
|
1004
|
+
/**
|
|
1005
|
+
* Live child-task registry. Passed verbatim to `waitForWakeEvent`'s
|
|
1006
|
+
* `registry` arm. Mutations to this map between iterations are
|
|
1007
|
+
* visible to the next wake.
|
|
1008
|
+
*/
|
|
1009
|
+
readonly registry: ChildTaskRegistry<TChildResult>;
|
|
1010
|
+
/** Process-global message queue. Passed verbatim to `waitForWakeEvent`. */
|
|
1011
|
+
readonly messageQueue: MessageQueue;
|
|
1012
|
+
/**
|
|
1013
|
+
* AgentId scope for the queue arm. `undefined` matches main-thread
|
|
1014
|
+
* messages (the default scope a parent agent's dispatch handler
|
|
1015
|
+
* targets).
|
|
1016
|
+
*/
|
|
1017
|
+
readonly agentId: string | undefined;
|
|
1018
|
+
/** Optional cancellation. Tears the loop down on the next wait boundary. */
|
|
1019
|
+
readonly abortSignal?: AbortSignal;
|
|
1020
|
+
/**
|
|
1021
|
+
* Choose the agent for the next iteration after a wake. Caller has
|
|
1022
|
+
* access to the just-finished run result; coding-flavor consumers
|
|
1023
|
+
* typically return their `chain.worker` regardless of the result.
|
|
1024
|
+
*/
|
|
1025
|
+
readonly resumeAgent: (runResult: TRunResult) => Agent;
|
|
1026
|
+
/**
|
|
1027
|
+
* Optional hook fired AFTER `detectIdleYield` returns true but BEFORE
|
|
1028
|
+
* `waitForWakeEvent` parks. Used by coding for FEATURE_156 status-bar
|
|
1029
|
+
* emission. The `currentAgent` arg is the agent that just ran (so
|
|
1030
|
+
* role lookups read the right name on every iteration).
|
|
1031
|
+
*/
|
|
1032
|
+
readonly onIdleWaiting?: (currentAgent: Agent, runResult: TRunResult) => void;
|
|
1033
|
+
/**
|
|
1034
|
+
* Optional hook fired when the iteration cap is hit. Coding does not
|
|
1035
|
+
* currently log here (matches v0.7.38 behavior — silent break) but
|
|
1036
|
+
* the hook exists so SDK consumers can record the prompt-bug signal.
|
|
1037
|
+
*/
|
|
1038
|
+
readonly onIterationCap?: () => void;
|
|
1039
|
+
/**
|
|
1040
|
+
* Defensive ceiling on outer-loop iterations. Default 64 matches the
|
|
1041
|
+
* legacy `IDLE_YIELD_MAX_ITERATIONS` constant. Set lower in tests to
|
|
1042
|
+
* exercise the cap branch quickly.
|
|
1043
|
+
*/
|
|
1044
|
+
readonly maxIterations?: number;
|
|
1045
|
+
/**
|
|
1046
|
+
* FEATURE_121 (v0.7.40): optional aggregate budget enforcer for the
|
|
1047
|
+
* synthetic user message built from drained background banners. When
|
|
1048
|
+
* provided, it transforms the fragment array before they're joined
|
|
1049
|
+
* (per-banner per-`<task-completed>` summary chunks remain unchanged
|
|
1050
|
+
* by this wrapper — that's the caller's responsibility at enqueue
|
|
1051
|
+
* time via `applyToolResultGuardrail`). The aggregate hook only kicks
|
|
1052
|
+
* in when N banners' total exceeds the limit set by the coding-layer
|
|
1053
|
+
* implementation (default 200KB per claudecode parity).
|
|
1054
|
+
*
|
|
1055
|
+
* Type is `string[] → string[] | Promise<string[]>` so the agent
|
|
1056
|
+
* layer carries no `@kodax-ai/coding` symbols. See
|
|
1057
|
+
* `EnvelopeAggregateEnforcer` in idle-yield.ts.
|
|
1058
|
+
*/
|
|
1059
|
+
readonly envelopeAggregateEnforcer?: EnvelopeAggregateEnforcer;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Run the agent chain with idle-yield resume.
|
|
1063
|
+
*
|
|
1064
|
+
* On each iteration:
|
|
1065
|
+
* 1. Invoke `runOnce(currentAgent, currentInput)`.
|
|
1066
|
+
* 2. Increment the iteration counter. If it exceeds
|
|
1067
|
+
* `maxIterations`, call `onIterationCap?.()` and break.
|
|
1068
|
+
* 3. Compute the snapshot via `computeSnapshot(runResult)`.
|
|
1069
|
+
* 4. If `detectIdleYield(snapshot)` is false (run terminal or
|
|
1070
|
+
* handoff/verdict already emitted), break.
|
|
1071
|
+
* 5. Fire `onIdleWaiting?.(currentAgent, runResult)`.
|
|
1072
|
+
* 6. Park in `waitForWakeEvent({registry, messageQueue, agentId, abortSignal})`.
|
|
1073
|
+
* 7. If wake is `aborted`, break.
|
|
1074
|
+
* 8. Build synthetic user message via `composeIdleYieldUserMessage`.
|
|
1075
|
+
* If undefined (truly empty wake), break.
|
|
1076
|
+
* 9. Replay: `currentInput = [...runResult.messages, syntheticUserMessage]`,
|
|
1077
|
+
* `currentAgent = resumeAgent(runResult)`, loop.
|
|
1078
|
+
*
|
|
1079
|
+
* Returns the last `runResult` (the one that broke the loop).
|
|
1080
|
+
*
|
|
1081
|
+
* Bug A-G hotfix invariants preserved:
|
|
1082
|
+
* - Bug A (registry cleanup): owned by `registerChildTask` —
|
|
1083
|
+
* unaffected by this wrapper.
|
|
1084
|
+
* - Bug B/D (terminal-verdict + handoff gates): caller's
|
|
1085
|
+
* `computeSnapshot` must populate `hasEmittedTerminalVerdict`
|
|
1086
|
+
* and `hasEmittedHandoff` from the canonical recorder source.
|
|
1087
|
+
* - Bug E (fast-child race): caller's `computeSnapshot` must read
|
|
1088
|
+
* `hasPendingBackgroundMessages` alongside the registry size.
|
|
1089
|
+
* - Bug F (abort listener leak): owned by `waitForWakeEvent` —
|
|
1090
|
+
* unaffected by this wrapper.
|
|
1091
|
+
*/
|
|
1092
|
+
declare function runWithIdleYield<TRunResult extends RunWithIdleYieldRunResult, TChildResult>(opts: RunWithIdleYieldOptions<TRunResult, TChildResult>): Promise<TRunResult>;
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Generic concurrency-bounded fan-out — `runFanOut`.
|
|
1096
|
+
*
|
|
1097
|
+
* FEATURE_120 v0.7.39 Step 0d (Option D scope). Lifts the truly
|
|
1098
|
+
* agent-flavor-agnostic part of `@kodax-ai/coding`'s `executeChildAgents`
|
|
1099
|
+
* orchestrator: bounded-concurrency `Promise.allSettled` + abort-pre-
|
|
1100
|
+
* check + structured progress events + cancelled-bundles tracking.
|
|
1101
|
+
*
|
|
1102
|
+
* What stays at `@kodax-ai/coding`'s `executeChildAgents`:
|
|
1103
|
+
* - `KodaXChildContextBundle` / `KodaXChildExecutionResult` shapes
|
|
1104
|
+
* - read vs write child differentiation
|
|
1105
|
+
* - `validateWriteBundles` role policy
|
|
1106
|
+
* - worktree isolation + cleanup
|
|
1107
|
+
* - briefing + `CHILD_AGENT_SYSTEM_PROMPT` injection
|
|
1108
|
+
*
|
|
1109
|
+
* What lives here:
|
|
1110
|
+
* - Bounded concurrency via private semaphore
|
|
1111
|
+
* - Pre-execution abort check (cancelled bundles collected)
|
|
1112
|
+
* - Promise.allSettled-style rejection capture
|
|
1113
|
+
* - Per-bundle structured progress events (`start` / `item-done` /
|
|
1114
|
+
* `item-failed`) with stable bundle reference + completed/total
|
|
1115
|
+
* counter via a second context argument
|
|
1116
|
+
*
|
|
1117
|
+
* The wrapper has zero inbound dependency on coding-flavor types —
|
|
1118
|
+
* `TBundle` and `TResult` are fully opaque to the module (ADR-021).
|
|
1119
|
+
*/
|
|
1120
|
+
/** Discriminated union for `onProgress` event payloads. */
|
|
1121
|
+
type FanOutProgressEvent<TBundle, TResult> = {
|
|
1122
|
+
readonly kind: 'start';
|
|
1123
|
+
readonly bundle: TBundle;
|
|
1124
|
+
readonly bundleIndex: number;
|
|
1125
|
+
} | {
|
|
1126
|
+
readonly kind: 'item-done';
|
|
1127
|
+
readonly bundle: TBundle;
|
|
1128
|
+
readonly bundleIndex: number;
|
|
1129
|
+
readonly result: TResult;
|
|
1130
|
+
} | {
|
|
1131
|
+
readonly kind: 'item-failed';
|
|
1132
|
+
readonly bundle: TBundle;
|
|
1133
|
+
readonly bundleIndex: number;
|
|
1134
|
+
readonly error: Error;
|
|
1135
|
+
};
|
|
1136
|
+
interface RunFanOutOptions<TBundle, TResult> {
|
|
1137
|
+
/**
|
|
1138
|
+
* The bundles to execute. Order is preserved as the canonical
|
|
1139
|
+
* "bundle index" for progress events; result order is **completion
|
|
1140
|
+
* order**, not bundle order (mirror callers use the bundle reference
|
|
1141
|
+
* carried in each event or each result to attribute outcomes).
|
|
1142
|
+
*/
|
|
1143
|
+
readonly bundles: readonly TBundle[];
|
|
1144
|
+
/**
|
|
1145
|
+
* Per-bundle executor. The wrapper invokes this after acquiring a
|
|
1146
|
+
* concurrency slot AND after passing the abort pre-check. If the
|
|
1147
|
+
* promise rejects, the rejection is captured in the result
|
|
1148
|
+
* `{status: 'rejected', bundle, reason}` and onProgress's
|
|
1149
|
+
* `item-failed` event fires; otherwise `{status: 'fulfilled', bundle,
|
|
1150
|
+
* value}` and `item-done` fires.
|
|
1151
|
+
*/
|
|
1152
|
+
readonly runOne: (bundle: TBundle) => Promise<TResult>;
|
|
1153
|
+
/**
|
|
1154
|
+
* Maximum concurrent in-flight `runOne` invocations. Must be ≥ 1;
|
|
1155
|
+
* a value of 1 collapses the fan-out to strict serial execution.
|
|
1156
|
+
*/
|
|
1157
|
+
readonly maxParallel: number;
|
|
1158
|
+
/**
|
|
1159
|
+
* Optional cancellation signal. Checked AFTER each semaphore acquire
|
|
1160
|
+
* but BEFORE invoking `runOne`. Bundles whose abort check fires are
|
|
1161
|
+
* added to `cancelled` and never see their `runOne` call. In-flight
|
|
1162
|
+
* `runOne` invocations are NOT interrupted — abort acts as a "stop
|
|
1163
|
+
* scheduling new work" signal.
|
|
1164
|
+
*/
|
|
1165
|
+
readonly abortSignal?: AbortSignal;
|
|
1166
|
+
/**
|
|
1167
|
+
* Optional progress hook. The first argument is the event; the
|
|
1168
|
+
* second is a shared snapshot of `{completedCount, totalCount}`
|
|
1169
|
+
* captured at the moment the event fires (so callers don't need
|
|
1170
|
+
* to maintain their own counter).
|
|
1171
|
+
*/
|
|
1172
|
+
readonly onProgress?: (event: FanOutProgressEvent<TBundle, TResult>, ctx: {
|
|
1173
|
+
readonly completedCount: number;
|
|
1174
|
+
readonly totalCount: number;
|
|
1175
|
+
}) => void;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Per-bundle outcome carried in the final result. Mirrors
|
|
1179
|
+
* `PromiseSettledResult` but adds the originating `bundle` for easy
|
|
1180
|
+
* mapping back to caller-owned state.
|
|
1181
|
+
*/
|
|
1182
|
+
type FanOutOutcome<TBundle, TResult> = {
|
|
1183
|
+
readonly status: 'fulfilled';
|
|
1184
|
+
readonly bundle: TBundle;
|
|
1185
|
+
readonly value: TResult;
|
|
1186
|
+
} | {
|
|
1187
|
+
readonly status: 'rejected';
|
|
1188
|
+
readonly bundle: TBundle;
|
|
1189
|
+
readonly reason: Error;
|
|
1190
|
+
};
|
|
1191
|
+
interface RunFanOutResult<TBundle, TResult> {
|
|
1192
|
+
/**
|
|
1193
|
+
* Per-bundle outcomes in **completion order** (NOT input bundle
|
|
1194
|
+
* order). Use the embedded `bundle` reference to map outcomes back
|
|
1195
|
+
* to caller state — index-based access is order-fragile.
|
|
1196
|
+
*/
|
|
1197
|
+
readonly results: ReadonlyArray<FanOutOutcome<TBundle, TResult>>;
|
|
1198
|
+
/**
|
|
1199
|
+
* Bundles that were skipped due to abort firing before their
|
|
1200
|
+
* `runOne` call. Preserves input bundle reference so callers can
|
|
1201
|
+
* map back to their own per-bundle state.
|
|
1202
|
+
*/
|
|
1203
|
+
readonly cancelled: readonly TBundle[];
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Run `runOne` over each bundle with bounded concurrency. Returns
|
|
1207
|
+
* after every bundle has either settled (`results`) or been skipped
|
|
1208
|
+
* due to abort (`cancelled`).
|
|
1209
|
+
*
|
|
1210
|
+
* Behavior:
|
|
1211
|
+
* - Empty `bundles` → returns `{results: [], cancelled: []}`
|
|
1212
|
+
* immediately without firing any events.
|
|
1213
|
+
* - `maxParallel ≥ bundles.length` collapses to native
|
|
1214
|
+
* `Promise.allSettled` semantics (no semaphore contention).
|
|
1215
|
+
* - `maxParallel === 1` collapses to strict serial execution.
|
|
1216
|
+
* - Each successful `runOne` fires `onProgress({kind: 'item-done',
|
|
1217
|
+
* ..., result})` AFTER `completedCount` is incremented (so the
|
|
1218
|
+
* `ctx.completedCount` in the event reflects "including this
|
|
1219
|
+
* one").
|
|
1220
|
+
* - Each failed `runOne` fires `onProgress({kind: 'item-failed',
|
|
1221
|
+
* ..., error})` with `completedCount` updated the same way —
|
|
1222
|
+
* the counter counts settled items regardless of success.
|
|
1223
|
+
* - Abort firing AFTER a bundle has entered `runOne` does NOT
|
|
1224
|
+
* cancel that bundle's promise — it completes normally and
|
|
1225
|
+
* joins `results`. Only bundles still waiting for a semaphore
|
|
1226
|
+
* slot (or those whose semaphore-acquired callback runs after
|
|
1227
|
+
* abort) get marked cancelled.
|
|
1228
|
+
*/
|
|
1229
|
+
declare function runFanOut<TBundle, TResult>(opts: RunFanOutOptions<TBundle, TResult>): Promise<RunFanOutResult<TBundle, TResult>>;
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Generic cross-agent send-message router primitive.
|
|
1233
|
+
*
|
|
1234
|
+
* FEATURE_120 v0.7.39 Phase 2a (ADR-021). Coordinator-style agents need
|
|
1235
|
+
* to dispatch instructions to running peer/child agents whose execution
|
|
1236
|
+
* is gated by an agentId-scoped `MessageQueue`. This module owns the
|
|
1237
|
+
* minimal "validate target exists, enqueue addressed message, report
|
|
1238
|
+
* outcome" trio so the @kodax-ai/agent package can ship the substrate
|
|
1239
|
+
* without inheriting any coding-flavor framing.
|
|
1240
|
+
*
|
|
1241
|
+
* Caller responsibilities (NOT done here):
|
|
1242
|
+
* - Content framing (e.g., `<coordinator-instruction>` tag wrapping)
|
|
1243
|
+
* — this is a tool-layer / flavor concern.
|
|
1244
|
+
* - Priority choice — the caller decides whether a route is
|
|
1245
|
+
* `'user'` (interrupts) or `'background'` (queued for next yield).
|
|
1246
|
+
* - Mode choice — `'prompt'` for new instructions,
|
|
1247
|
+
* `'system-reminder'` for advisory metadata, etc.
|
|
1248
|
+
* - Sentinel target handling (e.g., broadcast `'*'`) — the router
|
|
1249
|
+
* rejects them as `unknown-target` because they aren't registry
|
|
1250
|
+
* keys; callers can intercept upstream if they need fan-out
|
|
1251
|
+
* semantics.
|
|
1252
|
+
*
|
|
1253
|
+
* What this primitive owns:
|
|
1254
|
+
* - Existence check against a `ReadonlyMap<string, unknown>`
|
|
1255
|
+
* registry — the value type is opaque; we only care about key
|
|
1256
|
+
* membership.
|
|
1257
|
+
* - Enqueue via `MessageQueue.enqueue` with `agentId` set to `to`.
|
|
1258
|
+
* - Structured result so callers can render success / error UX
|
|
1259
|
+
* without re-parsing strings.
|
|
1260
|
+
*/
|
|
1261
|
+
|
|
1262
|
+
interface RouteMessageOptions {
|
|
1263
|
+
/** Target agentId. Must exist as a key in `registry`. */
|
|
1264
|
+
readonly to: string;
|
|
1265
|
+
/** Priority class — `'user'` interrupts, `'background'` waits. */
|
|
1266
|
+
readonly priority: MessagePriority;
|
|
1267
|
+
/** Message mode — caller selects based on intent. */
|
|
1268
|
+
readonly mode: MessageMode;
|
|
1269
|
+
/**
|
|
1270
|
+
* Pre-formatted message body. The router does NOT wrap this — any
|
|
1271
|
+
* framing (e.g. tags) must be applied by the caller before invoking.
|
|
1272
|
+
*/
|
|
1273
|
+
readonly content: string;
|
|
1274
|
+
/**
|
|
1275
|
+
* Registry the target must appear in. The value type is opaque; the
|
|
1276
|
+
* router only calls `.has(to)`. Pass a `ChildTaskRegistry<T>` for the
|
|
1277
|
+
* standard child-task case, or any other `ReadonlyMap<string, ?>`.
|
|
1278
|
+
*/
|
|
1279
|
+
readonly registry: ReadonlyMap<string, unknown>;
|
|
1280
|
+
/** Queue to enqueue into when the target is known. */
|
|
1281
|
+
readonly queue: MessageQueue;
|
|
1282
|
+
}
|
|
1283
|
+
type RouteMessageResult = {
|
|
1284
|
+
readonly ok: true;
|
|
1285
|
+
readonly messageId: string;
|
|
1286
|
+
} | {
|
|
1287
|
+
readonly ok: false;
|
|
1288
|
+
readonly reason: 'unknown-target';
|
|
1289
|
+
readonly to: string;
|
|
1290
|
+
};
|
|
1291
|
+
/**
|
|
1292
|
+
* Validate that `to` is registered, then enqueue an addressed message.
|
|
1293
|
+
*
|
|
1294
|
+
* Returns `{ok: true, messageId}` on success; `{ok: false, reason:
|
|
1295
|
+
* 'unknown-target', to}` when the target is missing from `registry`.
|
|
1296
|
+
* On failure the queue is not mutated.
|
|
1297
|
+
*
|
|
1298
|
+
* Synchronous because `MessageQueue.enqueue` is synchronous — the
|
|
1299
|
+
* routing decision is a pure function of registry membership.
|
|
1300
|
+
*/
|
|
1301
|
+
declare function routeMessage(opts: RouteMessageOptions): RouteMessageResult;
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* FEATURE_125 (v0.7.41) — Team Mode Layer 2b: system-prompt injection.
|
|
1305
|
+
*
|
|
1306
|
+
* Renders the `=== Other active KodaX sessions ===` block that the
|
|
1307
|
+
* coding-side system-prompt builder splices into the worker system
|
|
1308
|
+
* prompt when sibling instances are alive. Pure formatter — no fs,
|
|
1309
|
+
* no clock, no globals.
|
|
1310
|
+
*
|
|
1311
|
+
* Contract:
|
|
1312
|
+
* - Input: a list of `DiscoveredInstance` (from S2) + `nowMs`.
|
|
1313
|
+
* - Output: a string block (or empty string when input is empty).
|
|
1314
|
+
* - When the list is empty, returns `''` so the caller can splice
|
|
1315
|
+
* unconditionally without an `if (block)` branch.
|
|
1316
|
+
* - When the list is non-empty, the rendered block is bookended by
|
|
1317
|
+
* a single header line and a single coordination-guidance paragraph
|
|
1318
|
+
* so the LLM has clear context for the data it just received.
|
|
1319
|
+
*
|
|
1320
|
+
* The block deliberately does NOT prescribe a specific action (no
|
|
1321
|
+
* "you MUST avoid editing X"); it surfaces context and lets the LLM
|
|
1322
|
+
* self-decide per the FEATURE_125 LLM-First philosophy. The prompt
|
|
1323
|
+
* eval at S7 verifies LLM behavior under this block.
|
|
1324
|
+
*/
|
|
1325
|
+
|
|
1326
|
+
interface RenderOptions {
|
|
1327
|
+
/**
|
|
1328
|
+
* Defaults to `Date.now()`. Tests pass a controllable clock.
|
|
1329
|
+
* Affects only the "(started N min ago)" / "(M min ago)" relative
|
|
1330
|
+
* timestamps; absolute fields are passed through verbatim.
|
|
1331
|
+
*/
|
|
1332
|
+
readonly nowMs?: number;
|
|
1333
|
+
/**
|
|
1334
|
+
* Maximum number of sibling instances to render. Defaults to 5 —
|
|
1335
|
+
* if a user has >5 sessions open we summarize the rest with a
|
|
1336
|
+
* "+N more" hint rather than blowing up the system prompt. Per
|
|
1337
|
+
* EVAL_GUIDELINES, prompt context budget is finite; cap before
|
|
1338
|
+
* the LLM-call site (not after).
|
|
1339
|
+
*/
|
|
1340
|
+
readonly maxRendered?: number;
|
|
1341
|
+
/**
|
|
1342
|
+
* Cap on `recentlyModifiedFiles` per peer. Defaults to 3 — keeps
|
|
1343
|
+
* the block scannable when one peer has touched many files.
|
|
1344
|
+
*/
|
|
1345
|
+
readonly maxRecentFilesPerPeer?: number;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Build the system-prompt block describing currently-active sibling
|
|
1349
|
+
* KodaX sessions. Pure function — same inputs always produce the same
|
|
1350
|
+
* output.
|
|
1351
|
+
*
|
|
1352
|
+
* Returns `''` (empty string) when `instances.length === 0`. Callers
|
|
1353
|
+
* splice with template-literal concatenation; the empty-string default
|
|
1354
|
+
* means no conditional is needed.
|
|
1355
|
+
*/
|
|
1356
|
+
declare function buildOtherInstancesPromptBlock(instances: readonly DiscoveredInstance[], options?: RenderOptions): string;
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* FEATURE_125 (v0.7.41) — Team Mode bootstrap helper.
|
|
1360
|
+
*
|
|
1361
|
+
* One-call entry point invoked from the REPL bootstrap (`kodax_cli.ts`
|
|
1362
|
+
* / `repl.ts`) once per process. Wires the four moving parts:
|
|
1363
|
+
*
|
|
1364
|
+
* 1. Reaps stale instance directories left by crashed peers
|
|
1365
|
+
* (acceptance criterion 7 in v0.7.41.md).
|
|
1366
|
+
* 2. Constructs the `StateWriter` (S1) using sensible defaults.
|
|
1367
|
+
* 3. Registers the writer in the process-level singleton (S3
|
|
1368
|
+
* consumers + tool-result paths use `getActiveTeamModeWriter`).
|
|
1369
|
+
* 4. Returns a small handle exposing the writer, a sibling-snapshot
|
|
1370
|
+
* function (used by the runner-driven adapter once per LLM
|
|
1371
|
+
* round), and an idempotent `shutdown()`.
|
|
1372
|
+
*
|
|
1373
|
+
* Disabled via `KODAX_DISABLE_MULTI_INSTANCE=1` — the helper returns
|
|
1374
|
+
* `null` and the singleton stays empty so every caller silently falls
|
|
1375
|
+
* back to solo-mode behavior. The env var is the project-spec escape
|
|
1376
|
+
* hatch for failure diagnosis; do NOT use it as a daily-driver opt-out.
|
|
1377
|
+
*
|
|
1378
|
+
* DI-clean: optional `fs`, `clock`, `instancesRoot` overrides so tests
|
|
1379
|
+
* can exercise the full lifecycle without touching `~/.kodax`.
|
|
1380
|
+
*/
|
|
1381
|
+
|
|
1382
|
+
interface TeamModeBootstrapOptions {
|
|
1383
|
+
/** Static session meta. Required because cwd / startedAt are essential. */
|
|
1384
|
+
readonly meta: SessionMeta;
|
|
1385
|
+
/**
|
|
1386
|
+
* Initial published state. Defaults to `{ agentPhase: 'idle' }` —
|
|
1387
|
+
* callers typically update this immediately when work begins.
|
|
1388
|
+
*/
|
|
1389
|
+
readonly initialState?: SessionStateSnapshot;
|
|
1390
|
+
/**
|
|
1391
|
+
* pid override; defaults to `process.pid`. Tests inject a stable
|
|
1392
|
+
* pid; production never passes this.
|
|
1393
|
+
*/
|
|
1394
|
+
readonly pid?: number;
|
|
1395
|
+
/**
|
|
1396
|
+
* Override the instances root directory. Defaults to
|
|
1397
|
+
* `getAgentConfigPath('instances')` via the underlying
|
|
1398
|
+
* `createStateWriter` / `discoverInstances`. Tests pass a temp dir.
|
|
1399
|
+
*/
|
|
1400
|
+
readonly instancesRoot?: string;
|
|
1401
|
+
/** Inject an in-memory fs for both the writer and the discovery scan. Tests only. */
|
|
1402
|
+
readonly fs?: StateWriterFs & InstanceDiscoveryFs;
|
|
1403
|
+
readonly clock?: () => number;
|
|
1404
|
+
readonly heartbeatIntervalMs?: number;
|
|
1405
|
+
/**
|
|
1406
|
+
* Pass `false` to skip the initial reap of stale peer directories.
|
|
1407
|
+
* Defaults to `true` — every session that bootstraps Team Mode is
|
|
1408
|
+
* also a candidate cleaner for crashed peers.
|
|
1409
|
+
*/
|
|
1410
|
+
readonly reapStaleOnStart?: boolean;
|
|
1411
|
+
/** Forwarded to discoverInstances for per-instance failure logs. */
|
|
1412
|
+
readonly logger?: (message: string) => void;
|
|
1413
|
+
}
|
|
1414
|
+
interface TeamModeHandle {
|
|
1415
|
+
readonly writer: StateWriter;
|
|
1416
|
+
/**
|
|
1417
|
+
* Get the current sibling snapshot. Cheap (a single readdir + N
|
|
1418
|
+
* stat). Caller wires this into the runner-driven LLM-call prefix
|
|
1419
|
+
* so each round sees a fresh sibling list — Team Mode block is
|
|
1420
|
+
* intentionally NOT cached in the stable system-prompt prefix.
|
|
1421
|
+
*/
|
|
1422
|
+
discoverSiblings(): DiscoveredInstance[];
|
|
1423
|
+
/**
|
|
1424
|
+
* Tear down the writer + clear the singleton. Idempotent. Safe to
|
|
1425
|
+
* call from a process-exit handler, an Ink unmount, or an explicit
|
|
1426
|
+
* `/exit` slash command.
|
|
1427
|
+
*/
|
|
1428
|
+
shutdown(): Promise<void>;
|
|
1429
|
+
}
|
|
1430
|
+
declare function bootstrapTeamMode(options: TeamModeBootstrapOptions): TeamModeHandle | null;
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* FEATURE_125 (v0.7.41) — Process-level singleton for Team Mode.
|
|
1434
|
+
*
|
|
1435
|
+
* One KodaX process owns at most one `StateWriter` instance. Consumers
|
|
1436
|
+
* scattered across the codebase (REPL bootstrap, tool execution
|
|
1437
|
+
* contexts, runner-driven adapter, todo store) need a shared handle
|
|
1438
|
+
* without threading the writer through every signature. This module
|
|
1439
|
+
* is the canonical accessor:
|
|
1440
|
+
*
|
|
1441
|
+
* - `setActiveTeamModeWriter(writer)` — REPL bootstrap calls this
|
|
1442
|
+
* once after `createStateWriter`.
|
|
1443
|
+
* - `getActiveTeamModeWriter()` — any module asks the singleton for
|
|
1444
|
+
* the live writer (or null when Team Mode is disabled / hasn't
|
|
1445
|
+
* bootstrapped yet).
|
|
1446
|
+
* - `updateActiveTeamMode(patch)` — convenience for "I want to
|
|
1447
|
+
* bump current_intent / active_files; do nothing if no writer".
|
|
1448
|
+
*
|
|
1449
|
+
* Mirrors the `activeExtensionRuntime` singleton pattern in
|
|
1450
|
+
* `packages/coding/src/extensions/runtime.ts` so familiarity is
|
|
1451
|
+
* preserved.
|
|
1452
|
+
*/
|
|
1453
|
+
|
|
1454
|
+
declare function setActiveTeamModeWriter(writer: StateWriter | null): void;
|
|
1455
|
+
declare function getActiveTeamModeWriter(): StateWriter | null;
|
|
1456
|
+
/**
|
|
1457
|
+
* Convenience: if a writer is active, merge `patch` into its state +
|
|
1458
|
+
* flush. No-op when Team Mode is disabled (no writer was bootstrapped,
|
|
1459
|
+
* or `KODAX_DISABLE_MULTI_INSTANCE=1` short-circuited the bootstrap).
|
|
1460
|
+
*/
|
|
1461
|
+
declare function updateActiveTeamMode(patch: Partial<SessionStateSnapshot>): void;
|
|
1462
|
+
|
|
1463
|
+
export { Agent, AgentManifest, AgentMessage, CORE_INVARIANTS, ChildTaskRegistry, DEFAULT_IDLE_YIELD_MAX_ITERATIONS, DequeueFilter, DiscoveredInstance, EnqueueInput, Handoff, InstanceDiscoveryFs, InvariantId, KodaXMessage, ManifestPatch, MessageMode, MessagePriority, MessageQueue, QualityInvariant, QueuedMessage, RunnerToolCall, RunnerToolResult, SessionMeta, SessionStateSnapshot, StateWriter, StateWriterFs, YIELD_TOOL_NAMES, _resetAdmissionMetrics, _resetInvariantRegistry, _resetMessageQueueForTests, applyManifestPatch, bootstrapTeamMode, buildOtherInstancesPromptBlock, composeIdleYieldUserMessage, composePatches, countLastAssistantToolCalls, detectHandoffSignal, detectIdleYield, detectMissingTerminalVerdict, emitHandoffSpan, enqueueChildTaskNotification, evidenceTrail, finalOwner, getActiveTeamModeWriter, getAdmissionMetricsSnapshot, getAgentConfigHome, getAgentConfigPath, getInvariant, getMessageQueue, handoffLegality, isAdmissionDebugEnabled, isIdleYieldEnabled, listRegisteredInvariants, maybeDrainMidTurn, midTurnDrainPriority, registerCoreInvariants, registerInvariant, replaceSystemMessage, resolveEffectiveInvariants, resolveRequiredInvariants, routeMessage, runFanOut, runWithIdleYield, setActiveTeamModeWriter, setAgentConfigHome, updateActiveTeamMode, waitForWakeEvent };
|
|
1464
|
+
export type { AdmissionMetricsSnapshot, EnqueueChildTaskNotificationInput, EnvelopeAggregateEnforcer, FanOutOutcome, FanOutProgressEvent, HandoffSignal, IdleYieldSnapshot, MaybeDrainMidTurnInput, RenderOptions, RouteMessageOptions, RouteMessageResult, RunFanOutOptions, RunFanOutResult, RunWithIdleYieldOptions, RunWithIdleYieldRunResult, TeamModeBootstrapOptions, TeamModeHandle, WaitForWakeEventOptions, WakeEvent };
|