indusagi-coding-agent 0.2.8 → 0.2.10

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 (222) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/entry.js +4 -4
  3. package/dist/index.js +4 -4
  4. package/package.json +3 -2
  5. package/src/_decls/entry.ts +18 -0
  6. package/src/_decls/guardrails.ts +35 -0
  7. package/src/_decls/index.ts +26 -0
  8. package/src/addons/contract.ts +236 -0
  9. package/src/addons/dispatch/event-dispatcher.ts +164 -0
  10. package/src/addons/dispatch/index.ts +25 -0
  11. package/src/addons/dispatch/tool-interceptor.ts +208 -0
  12. package/src/addons/host.ts +225 -0
  13. package/src/addons/index.ts +112 -0
  14. package/src/addons/manifest.ts +158 -0
  15. package/src/addons/sandbox.ts +170 -0
  16. package/src/addons/surface.ts +78 -0
  17. package/src/boot/auth-vault.ts +195 -0
  18. package/src/boot/boot.ts +138 -0
  19. package/src/boot/contract.ts +238 -0
  20. package/src/boot/heap.ts +59 -0
  21. package/src/boot/index.ts +28 -0
  22. package/src/boot/invocation.ts +93 -0
  23. package/src/boot/runners/addon-wiring.ts +153 -0
  24. package/src/boot/runners/checkpoint.ts +169 -0
  25. package/src/boot/runners/delegate-runner.ts +294 -0
  26. package/src/boot/runners/index.ts +13 -0
  27. package/src/boot/runners/link-runner.ts +45 -0
  28. package/src/boot/runners/memdir.ts +168 -0
  29. package/src/boot/runners/oneshot-runner.ts +58 -0
  30. package/src/boot/runners/read-state.ts +90 -0
  31. package/src/boot/runners/registry.ts +42 -0
  32. package/src/boot/runners/repl-runner.ts +143 -0
  33. package/src/boot/runners/server-mode.ts +121 -0
  34. package/src/boot/runners/session.ts +641 -0
  35. package/src/boot/server-token.ts +148 -0
  36. package/src/boot/stages.ts +167 -0
  37. package/src/boot/upgrade/apply.ts +94 -0
  38. package/src/boot/upgrade/index.ts +13 -0
  39. package/src/boot/upgrade/upgrades.ts +289 -0
  40. package/src/briefing/compose.ts +150 -0
  41. package/src/briefing/context-docs.ts +19 -0
  42. package/src/briefing/contract.ts +717 -0
  43. package/src/briefing/index.ts +31 -0
  44. package/src/briefing/macros.ts +97 -0
  45. package/src/briefing/skills.ts +47 -0
  46. package/src/capability-deck/bridge-ledger/index.ts +27 -0
  47. package/src/capability-deck/bridge-ledger/key.ts +67 -0
  48. package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
  49. package/src/capability-deck/bridge-ledger/network.ts +117 -0
  50. package/src/capability-deck/builtin-bridge.ts +312 -0
  51. package/src/capability-deck/cards/bg-process-card.ts +335 -0
  52. package/src/capability-deck/cards/index.ts +115 -0
  53. package/src/capability-deck/cards/memory-card.ts +146 -0
  54. package/src/capability-deck/cards/plan-file.ts +97 -0
  55. package/src/capability-deck/cards/plan-tools.ts +185 -0
  56. package/src/capability-deck/cards/saas-card.ts +183 -0
  57. package/src/capability-deck/cards/task-card.ts +207 -0
  58. package/src/capability-deck/cards/todo-card.ts +168 -0
  59. package/src/capability-deck/cards/workflow-card.ts +247 -0
  60. package/src/capability-deck/contract.ts +388 -0
  61. package/src/capability-deck/index.ts +48 -0
  62. package/src/capability-deck/manifest.ts +109 -0
  63. package/src/capability-deck/provision.ts +169 -0
  64. package/src/channels/contract.ts +191 -0
  65. package/src/channels/framer.ts +50 -0
  66. package/src/channels/index.ts +101 -0
  67. package/src/channels/link/dialog.ts +129 -0
  68. package/src/channels/link/driver.ts +190 -0
  69. package/src/channels/link/index.ts +34 -0
  70. package/src/channels/link/server.ts +134 -0
  71. package/src/channels/oneshot.ts +90 -0
  72. package/src/channels/ops.ts +65 -0
  73. package/src/channels/session-ops.ts +81 -0
  74. package/src/conductor/bash-guard.ts +599 -0
  75. package/src/conductor/catalog/catalog.ts +116 -0
  76. package/src/conductor/catalog/index.ts +8 -0
  77. package/src/conductor/catalog/matcher.ts +134 -0
  78. package/src/conductor/conductor.ts +234 -0
  79. package/src/conductor/contract.ts +842 -0
  80. package/src/conductor/diagnostics.ts +227 -0
  81. package/src/conductor/index.ts +33 -0
  82. package/src/conductor/permissions.ts +588 -0
  83. package/src/conductor/quota-error.ts +49 -0
  84. package/src/conductor/signal-hub/hub.ts +46 -0
  85. package/src/conductor/signal-hub/index.ts +2 -0
  86. package/src/conductor/signal-hub/translate.test.ts +81 -0
  87. package/src/conductor/signal-hub/translate.ts +74 -0
  88. package/src/conductor/skill-parse/index.ts +2 -0
  89. package/src/conductor/skill-parse/parse.ts +108 -0
  90. package/src/conductor/transcript-store/index.ts +22 -0
  91. package/src/conductor/transcript-store/serialize.ts +116 -0
  92. package/src/conductor/transcript-store/store.ts +205 -0
  93. package/src/console/auth-status.ts +56 -0
  94. package/src/console/components/AgentsView.ts +165 -0
  95. package/src/console/components/BackgroundAgents.ts +155 -0
  96. package/src/console/components/Banner.ts +334 -0
  97. package/src/console/components/Composer.ts +94 -0
  98. package/src/console/components/StatusBar.ts +49 -0
  99. package/src/console/components/TerminalConsole.ts +1090 -0
  100. package/src/console/components/WorkingIndicator.ts +98 -0
  101. package/src/console/components/banner-sweep.ts +24 -0
  102. package/src/console/components/welcome.ts +74 -0
  103. package/src/console/contract.ts +630 -0
  104. package/src/console/index.ts +34 -0
  105. package/src/console/input/complete.ts +127 -0
  106. package/src/console/input/dir-reader.ts +34 -0
  107. package/src/console/input/index.ts +23 -0
  108. package/src/console/input/keymap.ts +159 -0
  109. package/src/console/input/paste.ts +104 -0
  110. package/src/console/mount.ts +56 -0
  111. package/src/console/overlays/approval-queue.ts +57 -0
  112. package/src/console/overlays/approval.ts +130 -0
  113. package/src/console/overlays/auth.ts +342 -0
  114. package/src/console/overlays/boards.ts +308 -0
  115. package/src/console/overlays/host.ts +36 -0
  116. package/src/console/overlays/index.ts +26 -0
  117. package/src/console/overlays/pickers.ts +258 -0
  118. package/src/console/overlays/sessions.ts +190 -0
  119. package/src/console/reducer.ts +182 -0
  120. package/src/console/slash/builtins.ts +81 -0
  121. package/src/console/slash/commands/dynamic.ts +83 -0
  122. package/src/console/slash/commands/integrations.ts +695 -0
  123. package/src/console/slash/commands/shared.ts +75 -0
  124. package/src/console/slash/commands/transcript.ts +263 -0
  125. package/src/console/slash/commands/workbench.ts +246 -0
  126. package/src/console/slash/index.ts +15 -0
  127. package/src/console/slash/registry.ts +70 -0
  128. package/src/console/slash/resolve.ts +63 -0
  129. package/src/console/startup.ts +209 -0
  130. package/src/console/theme/adapter.ts +45 -0
  131. package/src/console/theme/index.ts +7 -0
  132. package/src/console/theme/palette.ts +68 -0
  133. package/src/console/theme/resolve.ts +39 -0
  134. package/src/console/theme/tokens.ts +71 -0
  135. package/src/entry.ts +55 -0
  136. package/src/guardrails.ts +37 -0
  137. package/src/index.ts +18 -0
  138. package/src/insight/channel.ts +88 -0
  139. package/src/insight/contract.ts +185 -0
  140. package/src/insight/index.ts +110 -0
  141. package/src/insight/recorder.ts +213 -0
  142. package/src/insight/redaction.ts +157 -0
  143. package/src/insight/replay.ts +158 -0
  144. package/src/insight/sampling.ts +70 -0
  145. package/src/insight/serialize.ts +50 -0
  146. package/src/insight/sinks/console.ts +64 -0
  147. package/src/insight/sinks/file.ts +40 -0
  148. package/src/insight/sinks/index.ts +24 -0
  149. package/src/insight/sinks/stream.ts +54 -0
  150. package/src/integrations/sarvam/attach.ts +239 -0
  151. package/src/integrations/sarvam/config.ts +156 -0
  152. package/src/integrations/sarvam/index.ts +25 -0
  153. package/src/integrations/sarvam/sarvam.test.ts +60 -0
  154. package/src/integrations/sarvam/types.ts +27 -0
  155. package/src/integrations/zoho/attach.ts +342 -0
  156. package/src/integrations/zoho/config.ts +125 -0
  157. package/src/integrations/zoho/index.ts +27 -0
  158. package/src/integrations/zoho/types.ts +21 -0
  159. package/src/integrations/zoho/zoho.test.ts +50 -0
  160. package/src/kit/clipboard-image.ts +107 -0
  161. package/src/kit/external-editor.ts +48 -0
  162. package/src/kit/image.ts +59 -0
  163. package/src/kit/index.ts +51 -0
  164. package/src/kit/shell.ts +19 -0
  165. package/src/kit/tool-fetch.ts +85 -0
  166. package/src/launch/catalog.ts +148 -0
  167. package/src/launch/contract.ts +187 -0
  168. package/src/launch/credentials.ts +625 -0
  169. package/src/launch/index.ts +98 -0
  170. package/src/launch/invocation/attachments.ts +179 -0
  171. package/src/launch/invocation/flags.ts +196 -0
  172. package/src/launch/invocation/index.ts +25 -0
  173. package/src/launch/invocation/read.ts +260 -0
  174. package/src/launch/invocation/usage.ts +67 -0
  175. package/src/launch/login.ts +324 -0
  176. package/src/launch/oauth.test.ts +18 -0
  177. package/src/launch/oauth.ts +203 -0
  178. package/src/launch/packages.ts +194 -0
  179. package/src/launch/pickers.ts +189 -0
  180. package/src/runtime-bridge/bridges/_drive.ts +96 -0
  181. package/src/runtime-bridge/bridges/builtins.ts +68 -0
  182. package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
  183. package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
  184. package/src/runtime-bridge/bridges/index.ts +33 -0
  185. package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
  186. package/src/runtime-bridge/broker.ts +227 -0
  187. package/src/runtime-bridge/contract.ts +122 -0
  188. package/src/runtime-bridge/index.ts +79 -0
  189. package/src/runtime-bridge/sink.ts +180 -0
  190. package/src/sessions/contract.ts +81 -0
  191. package/src/sessions/index.ts +13 -0
  192. package/src/sessions/library.ts +229 -0
  193. package/src/settings/contract.ts +114 -0
  194. package/src/settings/index.ts +32 -0
  195. package/src/settings/manager.ts +117 -0
  196. package/src/transcript-export/index.ts +45 -0
  197. package/src/transcript-export/publish.ts +260 -0
  198. package/src/transcript-export/sgr.ts +315 -0
  199. package/src/transcript-export/template.ts +272 -0
  200. package/src/transcript-export/theme-bridge.ts +150 -0
  201. package/src/window-budget/budget/estimate.ts +135 -0
  202. package/src/window-budget/budget/gate.ts +33 -0
  203. package/src/window-budget/budget/index.ts +16 -0
  204. package/src/window-budget/budget/slice.ts +56 -0
  205. package/src/window-budget/condenser.ts +58 -0
  206. package/src/window-budget/contract.ts +184 -0
  207. package/src/window-budget/index.ts +19 -0
  208. package/src/window-budget/microcompact.ts +95 -0
  209. package/src/window-budget/rehydrate.ts +136 -0
  210. package/src/window-budget/summarize/condense.ts +103 -0
  211. package/src/window-budget/summarize/index.ts +14 -0
  212. package/src/window-budget/summarize/prompt.ts +149 -0
  213. package/src/workflow-engine/agent-runner.ts +181 -0
  214. package/src/workflow-engine/display.ts +224 -0
  215. package/src/workflow-engine/engine.ts +294 -0
  216. package/src/workflow-engine/index.ts +23 -0
  217. package/src/workflow-engine/parse.ts +172 -0
  218. package/src/workflow-engine/structured-output.ts +35 -0
  219. package/src/workspace/brand.ts +29 -0
  220. package/src/workspace/index.ts +18 -0
  221. package/src/workspace/locator.ts +103 -0
  222. package/src/workspace/runtime-detect.ts +64 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Window-budget — the condenser factory (the conductor-consumable seam).
3
+ *
4
+ * {@link createCondenser} ties the three pure pieces together into a single
5
+ * function the conductor can drop into its auto-compaction hook.
6
+ */
7
+ import type {
8
+ AgentMessage,
9
+ BudgetPolicy,
10
+ Condenser,
11
+ CondenserDeps,
12
+ Summary,
13
+ } from "./contract.js";
14
+ import { isOverBudget } from "./budget/gate.js";
15
+ import { planSlice } from "./budget/slice.js";
16
+ import { condenseScope, summarize, type SummarizeDeps } from "./summarize/index.js";
17
+
18
+ /**
19
+ * The fallback {@link BudgetPolicy} when the caller supplies none.
20
+ */
21
+ export const AUTO_CONDENSE_POLICY: BudgetPolicy = {
22
+ triggerRatio: 0.75,
23
+ keepRecent: 6e3,
24
+ reserveTokens: 2048,
25
+ };
26
+
27
+ /**
28
+ * Build a {@link Condenser} from a {@link CondenserDeps} bundle. The result plugs
29
+ * straight into the conductor's `CondenseFn` seam.
30
+ */
31
+ export function createCondenser(deps: CondenserDeps = {}): Condenser {
32
+ const policy = deps.policy ?? AUTO_CONDENSE_POLICY;
33
+ const model = deps.model;
34
+ const complete = deps.complete;
35
+ return async (messages: AgentMessage[]): Promise<AgentMessage[]> => {
36
+ if (!model) return messages;
37
+ if (!isOverBudget(messages, model, policy)) return messages;
38
+ const plan = planSlice(messages, policy);
39
+ if (plan.cut === 0 || plan.dropped.length === 0) {
40
+ return messages;
41
+ }
42
+ const summary = await summarize(plan.dropped, { complete, model });
43
+ return [summary.message, ...plan.kept];
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Session-scope summarization: fold a slice of dropped messages into one digest
49
+ * for the active session.
50
+ */
51
+ export function condense(
52
+ messages: AgentMessage[],
53
+ deps: Omit<SummarizeDeps, "scope"> = {},
54
+ ): Promise<Summary> {
55
+ return summarize(messages, { ...deps, scope: "session" });
56
+ }
57
+
58
+ export { condenseScope };
@@ -0,0 +1,184 @@
1
+ // @ts-nocheck
2
+ // Type declarations recovered from dist/types (no runtime body)
3
+ /**
4
+ * Window-budget contract — the FROZEN type surface of the context-window
5
+ * budgeting subsystem (Phase 3, subsystem 05).
6
+ *
7
+ * A long-running coding session accrues messages — user turns, assistant turns,
8
+ * tool calls and their results — until the transcript approaches the model's
9
+ * context window. This subsystem keeps the session usable by *condensing* older
10
+ * history into a single structured summary message while keeping a verbatim
11
+ * tail of the most-recent turns. It answers three questions:
12
+ *
13
+ * 1. **Budget** — *when* are we over budget? A {@link BudgetPolicy} carries
14
+ * config-sourced thresholds (no magic literals); {@link isOverBudget}
15
+ * decides from a real {@link TokenEstimate}.
16
+ * 2. **Slice** — *where* do we cut? {@link planSlice} computes a
17
+ * {@link CondensePlan} via a forward prefix-sum + binary search over a
18
+ * cumulative-token array, never splitting a tool_call from its tool_result.
19
+ * 3. **Condense** — *how* do we compress the dropped portion? A
20
+ * {@link Condenser} flattens the dropped messages, asks an injectable model
21
+ * completer for a structured {@link Summary}, and returns the rebuilt
22
+ * transcript (summary + kept tail).
23
+ *
24
+ * This file declares *only* shapes plus the function-type seams — no behavior,
25
+ * no I/O, no prompt strings. Every later window-budget module (the meter, the
26
+ * slice planner, the condenser, the branch archivist, the prompt templates, and
27
+ * the public barrel) is written against the names declared here.
28
+ *
29
+ * Design stance:
30
+ * - Thresholds are **configuration, not constants**. {@link BudgetPolicy}
31
+ * fields are supplied by the caller; this module bakes in no fixed token
32
+ * fingerprints. Defaults, if any, live in the implementation and are
33
+ * computed relative to the model's own context window.
34
+ * - Branch-summarization is **not a separate engine**. It collapses into the
35
+ * condenser behind a {@link CondenseScope} flag (`"session" | "branch"`),
36
+ * so there is one prompt-assembly path, one token estimator, one slicer.
37
+ * - The model completer is **injectable** ({@link CompleteFn}, default the
38
+ * framework's `completeSimple`) so the condenser runs in tests with no
39
+ * network.
40
+ * - The produced condenser is **conductor-consumable**: a {@link Condenser}
41
+ * is structurally a conductor `CondenseFn`
42
+ * (`(messages: AgentMessage[]) => AgentMessage[] | Promise<AgentMessage[]>`),
43
+ * so a {@link CondenserDeps}-built condenser plugs straight into the
44
+ * conductor's auto-compaction seam.
45
+ *
46
+ * Framework anchors (all from the `indusagi` package — the sibling rebuilt
47
+ * framework this app targets):
48
+ * - `AgentMessage` ← `indusagi/agent`
49
+ * - `Model`, `Usage`, `completeSimple` (and its `Api`/`AssistantMessage`/
50
+ * `Context`/`SimpleStreamOptions`/`StreamLogger` shapes) ← `indusagi/ai`
51
+ *
52
+ * This module never re-declares these; it composes them.
53
+ */
54
+ import type { AgentMessage } from "indusagi/agent";
55
+ import type { Api, AssistantMessage, Context, Model, SimpleStreamOptions, StreamLogger, Usage } from "indusagi/ai";
56
+ /** Re-exported framework vocabulary window-budget consumers routinely need. */
57
+ export type { AgentMessage, Model, Usage };
58
+ /**
59
+ * The config-sourced thresholds that govern when and how the transcript is
60
+ * condensed. Every field is a *number the caller supplies* — there are no magic
61
+ * token literals baked into this contract. An implementation may
62
+ * provide defaults, but they are configuration values, not protected constants,
63
+ * and deliberately differ from the legacy fingerprints.
64
+ */
65
+ export interface BudgetPolicy {
66
+ /**
67
+ * Fraction of the model's `contextWindow` at which condensing is triggered,
68
+ * in `(0, 1]`. The meter is "over budget" once the estimated context tokens
69
+ * cross `contextWindow * triggerRatio`. Expressing the trigger as a ratio
70
+ * (rather than an absolute reserve) keeps it window-relative and free of
71
+ * baked-in token counts.
72
+ */
73
+ readonly triggerRatio: number;
74
+ /**
75
+ * How much of the recent transcript to keep verbatim, measured in estimated
76
+ * tokens. The slice planner preserves a tail of at least this many tokens
77
+ * (snapped to a legal boundary) and condenses everything before it.
78
+ */
79
+ readonly keepRecent: number;
80
+ /**
81
+ * Optional token headroom to subtract from the window before the trigger is
82
+ * evaluated — a safety margin so a condense fires *before* the very next turn
83
+ * would overflow. When omitted, only {@link triggerRatio} governs the trigger.
84
+ */
85
+ readonly reserveTokens?: number;
86
+ }
87
+ /**
88
+ * The outcome of measuring a transcript against a model's window. Anchors on the
89
+ * provider's authoritative `usage` count when available (`anchored: true`) and
90
+ * falls back to a rough char/role estimate otherwise; either way `contextTokens`
91
+ * is the figure {@link isOverBudget} compares to `contextWindow`.
92
+ */
93
+ export interface TokenEstimate {
94
+ /** Best estimate of tokens currently occupying the context window. */
95
+ readonly contextTokens: number;
96
+ /** The model's total context window, in tokens (`Model.contextWindow`). */
97
+ readonly contextWindow: number;
98
+ /**
99
+ * `true` when {@link contextTokens} is anchored on a real provider `usage`
100
+ * figure (only trailing, post-usage messages are estimated); `false` when the
101
+ * whole figure is a heuristic estimate.
102
+ */
103
+ readonly anchored: boolean;
104
+ }
105
+ /**
106
+ * The pre-computed result of {@link planSlice}: the chosen cut index and the
107
+ * resulting partition of the transcript.
108
+ *
109
+ * The cut is found by a *forward* prefix-sum + binary search: build a cumulative
110
+ * estimated-token array over the messages, binary-search for the boundary index
111
+ * nearest `total - keepRecent`, then nudge to the closest *legal* boundary —
112
+ * never landing between a `toolCall` and its `toolResult`.
113
+ *
114
+ * Invariants:
115
+ * - `kept.length + dropped.length === messages.length`
116
+ * - `dropped` is the prefix `messages[0 .. cut)` (the older slice to condense)
117
+ * - `kept` is the suffix `messages[cut ..]` (the verbatim recent tail)
118
+ * - `cut` is `0` when nothing is condensable (everything kept).
119
+ */
120
+ export interface CondensePlan {
121
+ /** Index of the legal cut boundary: `dropped = messages.slice(0, cut)`. */
122
+ readonly cut: number;
123
+ /** The verbatim recent tail preserved as-is (`messages.slice(cut)`). */
124
+ readonly kept: AgentMessage[];
125
+ /** The older slice to be folded into a {@link Summary} (`messages.slice(0, cut)`). */
126
+ readonly dropped: AgentMessage[];
127
+ }
128
+ /**
129
+ * Which kind of condensing run this is — the flag that collapses
130
+ * branch-summarization into the single condenser path:
131
+ * - `"session"` — condense the *active* transcript in place (the trigger /
132
+ * overflow path); keeps a recent tail, summarizes the head.
133
+ * - `"branch"` — archive an *abandoned* branch into one summary message so its
134
+ * context isn't lost on tree navigation; no verbatim tail is retained.
135
+ */
136
+ export type CondenseScope = "session" | "branch";
137
+ /**
138
+ * The structured result of summarizing a slice of dropped messages: a single
139
+ * synthetic {@link AgentMessage} that stands in for the messages it covers.
140
+ */
141
+ export interface Summary {
142
+ /** The synthetic summary message to splice into the rebuilt transcript. */
143
+ readonly message: AgentMessage;
144
+ /** How many source messages this summary replaces. */
145
+ readonly coveredCount: number;
146
+ }
147
+ /**
148
+ * The injectable model-completion function. Structurally identical to the
149
+ * framework's `completeSimple`, so the real `completeSimple` is the drop-in
150
+ * default and a test can pass a no-network stub of the same shape. The
151
+ * condenser uses this single call to turn flattened transcript text into a
152
+ * {@link Summary}.
153
+ */
154
+ export type CompleteFn = <TApi extends Api>(model: Model<TApi>, context: Context, options?: SimpleStreamOptions, logger?: StreamLogger) => Promise<AssistantMessage>;
155
+ /**
156
+ * A condenser: takes the active branch's messages and returns the rebuilt
157
+ * transcript (condensed head + verbatim tail, or the input unchanged when there
158
+ * is nothing to do).
159
+ *
160
+ * This type is **structurally compatible with the conductor's `CondenseFn`**
161
+ * (`(messages: AgentMessage[]) => AgentMessage[] | Promise<AgentMessage[]>`), so
162
+ * the result of the condenser factory plugs directly into the conductor's
163
+ * auto-compaction seam with no adapter.
164
+ */
165
+ export type Condenser = (messages: AgentMessage[]) => AgentMessage[] | Promise<AgentMessage[]>;
166
+ /**
167
+ * Dependencies for building a {@link Condenser}. All fields are optional so a
168
+ * bare `createCondenser()` yields a working, network-free-testable condenser:
169
+ * - `complete` defaults to the framework's `completeSimple`.
170
+ * - `policy` defaults to the implementation's config-sourced thresholds.
171
+ * - `model` is the summarization model; when omitted the condenser may fall
172
+ * back to a no-op (return input unchanged) rather than guess a model.
173
+ */
174
+ export interface CondenserDeps {
175
+ /**
176
+ * The injectable model completer (default: framework `completeSimple`). Inject
177
+ * a stub of {@link CompleteFn} shape to run the condenser with no network.
178
+ */
179
+ readonly complete?: CompleteFn;
180
+ /** The model used to write the summary. When omitted, condensing is a no-op. */
181
+ readonly model?: Model<Api>;
182
+ /** The config-sourced budget thresholds (default: implementation defaults). */
183
+ readonly policy?: BudgetPolicy;
184
+ }
@@ -0,0 +1,19 @@
1
+ // @ts-nocheck
2
+ // Type declarations recovered from dist/types (no runtime body)
3
+ /**
4
+ * Window-budget — public barrel.
5
+ *
6
+ * The context-window budgeting subsystem: token measurement, slice planning,
7
+ * and transcript condensing (with branch-archival collapsed in behind a scope
8
+ * flag). Re-exports the frozen contract types plus the live functions: the
9
+ * budget math (estimateTokens / isOverBudget / planSlice), the summarization core
10
+ * (summarize / condenseScope), and the conductor-consumable condenser factory
11
+ * (createCondenser / condense).
12
+ */
13
+ export type { AgentMessage, BudgetPolicy, CompleteFn, CondensePlan, Condenser, CondenserDeps, CondenseScope, Model, Summary, TokenEstimate, Usage, } from "./contract.js";
14
+ export { budgetLimit, estimateMessageTokens, estimateTokens, isOverBudget, planSlice, prefixTokens, } from "./budget/index.js";
15
+ export { buildSummaryPrompt, CONDENSER_BRIEF, flattenTranscript, summarize, } from "./summarize/index.js";
16
+ export type { SummarizeDeps } from "./summarize/index.js";
17
+ export { AUTO_CONDENSE_POLICY, condense, condenseScope, createCondenser } from "./condenser.js";
18
+ export { CLEARED_TOOL_RESULT, clearStaleToolResults, COMPACTABLE_TOOL_NAMES, } from "./microcompact.js";
19
+ export { rehydrateRecentReads, RESTORED_FILE_PREFIX } from "./rehydrate.js";
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Window-budget / microcompact — clear stale tool-result bodies in place.
3
+ */
4
+ import type { AgentMessage } from "./contract.js";
5
+
6
+ /**
7
+ * The placeholder spliced in where a stale tool-result body used to be.
8
+ */
9
+ export const CLEARED_TOOL_RESULT = "[Old tool result content cleared]";
10
+
11
+ /**
12
+ * The wire names of tools whose results are safe to clear once stale.
13
+ */
14
+ export const COMPACTABLE_TOOL_NAMES: ReadonlySet<string> = new Set([
15
+ "read",
16
+ "grep",
17
+ "find",
18
+ "ls",
19
+ "glob",
20
+ "bash",
21
+ "websearch",
22
+ "webfetch",
23
+ "edit",
24
+ "write",
25
+ ]);
26
+
27
+ const DEFAULT_KEEP_RECENT = 6;
28
+
29
+ function isRecord3(value: unknown): value is Record<string, unknown> {
30
+ return typeof value === "object" && value !== null;
31
+ }
32
+
33
+ function collectCompactableToolIds(messages: AgentMessage[]): Set<string> {
34
+ const ids = new Set<string>();
35
+ for (const message of messages) {
36
+ if (!isRecord3(message) || message.role !== "assistant") continue;
37
+ const content = Array.isArray(message.content) ? message.content : [];
38
+ for (const block of content) {
39
+ if (!isRecord3(block) || block.type !== "toolCall") continue;
40
+ const name = typeof block.name === "string" ? block.name : "";
41
+ const id = typeof block.id === "string" ? block.id : "";
42
+ if (id.length > 0 && COMPACTABLE_TOOL_NAMES.has(name)) ids.add(id);
43
+ }
44
+ }
45
+ return ids;
46
+ }
47
+
48
+ function isAlreadyCleared(message: AgentMessage): boolean {
49
+ if (!isRecord3(message)) return false;
50
+ const content = (message as { content?: unknown }).content;
51
+ if (!Array.isArray(content) || content.length !== 1) return false;
52
+ const block = content[0];
53
+ return (
54
+ isRecord3(block) &&
55
+ block.type === "text" &&
56
+ block.text === CLEARED_TOOL_RESULT
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Clear the bodies of stale compactable tool results, keeping the most recent
62
+ * `keepRecent` of them verbatim.
63
+ */
64
+ export function clearStaleToolResults(
65
+ messages: AgentMessage[],
66
+ opts: { keepRecent?: number } = {},
67
+ ): AgentMessage[] {
68
+ const keepRecent = Math.max(1, opts.keepRecent ?? DEFAULT_KEEP_RECENT);
69
+ const compactableIds = collectCompactableToolIds(messages);
70
+ if (compactableIds.size === 0) return messages;
71
+ const clearAt = new Set<number>();
72
+ let kept = 0;
73
+ for (let i = messages.length - 1; i >= 0; i--) {
74
+ const message = messages[i];
75
+ if (!isRecord3(message) || message.role !== "toolResult") continue;
76
+ const id = typeof (message as { toolCallId?: unknown }).toolCallId === "string"
77
+ ? (message as { toolCallId: string }).toolCallId
78
+ : "";
79
+ if (!compactableIds.has(id)) continue;
80
+ if (kept < keepRecent) {
81
+ kept++;
82
+ continue;
83
+ }
84
+ if (isAlreadyCleared(message as AgentMessage)) continue;
85
+ clearAt.add(i);
86
+ }
87
+ if (clearAt.size === 0) return messages;
88
+ return messages.map((message, i) => {
89
+ if (!clearAt.has(i)) return message;
90
+ return {
91
+ ...message,
92
+ content: [{ type: "text", text: CLEARED_TOOL_RESULT }],
93
+ };
94
+ });
95
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Window-budget / rehydrate — re-attach recently-read files after a condense.
3
+ */
4
+ import type { AgentMessage } from "./contract.js";
5
+ import { estimateMessageTokens } from "./budget/estimate.js";
6
+
7
+ /** Prefix marking a synthetic message that restores a file body post-compaction. */
8
+ export const RESTORED_FILE_PREFIX = "[Restored file after compaction]";
9
+
10
+ const DEFAULT_MAX_FILES = 5;
11
+ const DEFAULT_TOKEN_BUDGET = 8e3;
12
+ const DEFAULT_READ_TOOL_NAME = "read";
13
+
14
+ function isRecord4(value: unknown): value is Record<string, unknown> {
15
+ return typeof value === "object" && value !== null;
16
+ }
17
+
18
+ function resultBodyText(message: AgentMessage): string {
19
+ if (!isRecord4(message)) return "";
20
+ const content = (message as { content?: unknown }).content;
21
+ if (!Array.isArray(content)) return "";
22
+ const parts: string[] = [];
23
+ for (const block of content) {
24
+ if (
25
+ isRecord4(block) &&
26
+ block.type === "text" &&
27
+ typeof block.text === "string"
28
+ ) {
29
+ parts.push(block.text);
30
+ }
31
+ }
32
+ return parts.join("\n");
33
+ }
34
+
35
+ function collectVisibleReadPaths(kept: AgentMessage[], readToolName: string): Set<string> {
36
+ const paths = new Set<string>();
37
+ for (const message of kept) {
38
+ if (!isRecord4(message) || message.role !== "assistant") continue;
39
+ const content = Array.isArray(message.content) ? message.content : [];
40
+ for (const block of content) {
41
+ if (!isRecord4(block) || block.type !== "toolCall") continue;
42
+ if (block.name !== readToolName) continue;
43
+ const args = isRecord4(block.arguments) ? block.arguments : undefined;
44
+ const path = args && typeof args.path === "string" ? args.path : "";
45
+ if (path.length > 0) paths.add(path);
46
+ }
47
+ }
48
+ return paths;
49
+ }
50
+
51
+ interface DroppedRead {
52
+ order: number;
53
+ path: string;
54
+ body: string;
55
+ }
56
+
57
+ function collectDroppedReads(
58
+ dropped: AgentMessage[],
59
+ readToolName: string,
60
+ ): DroppedRead[] {
61
+ const calls = new Map<string, { path: string; order: number }>();
62
+ for (let i = 0; i < dropped.length; i++) {
63
+ const message = dropped[i];
64
+ if (!isRecord4(message) || message.role !== "assistant") continue;
65
+ const content = Array.isArray(message.content) ? message.content : [];
66
+ for (const block of content) {
67
+ if (!isRecord4(block) || block.type !== "toolCall") continue;
68
+ if (block.name !== readToolName) continue;
69
+ const id = typeof block.id === "string" ? block.id : "";
70
+ const args = isRecord4(block.arguments) ? block.arguments : undefined;
71
+ const path = args && typeof args.path === "string" ? args.path : "";
72
+ if (id.length > 0 && path.length > 0) calls.set(id, { path, order: i });
73
+ }
74
+ }
75
+ if (calls.size === 0) return [];
76
+ const byPath = new Map<string, DroppedRead>();
77
+ for (let i = 0; i < dropped.length; i++) {
78
+ const message = dropped[i];
79
+ if (!isRecord4(message) || message.role !== "toolResult") continue;
80
+ if ((message as { isError?: unknown }).isError === true) continue;
81
+ const id = typeof (message as { toolCallId?: unknown }).toolCallId === "string"
82
+ ? (message as { toolCallId: string }).toolCallId
83
+ : "";
84
+ const call = calls.get(id);
85
+ if (call === undefined) continue;
86
+ const body = resultBodyText(message as AgentMessage);
87
+ if (body.length === 0) continue;
88
+ byPath.set(call.path, { order: i, path: call.path, body });
89
+ }
90
+ return [...byPath.values()];
91
+ }
92
+
93
+ /**
94
+ * Build the synthetic user messages that re-attach the most-recently-read files
95
+ * dropped by a condense.
96
+ */
97
+ export function rehydrateRecentReads(
98
+ dropped: AgentMessage[],
99
+ kept: AgentMessage[],
100
+ opts: {
101
+ maxFiles?: number;
102
+ tokenBudget?: number;
103
+ readToolName?: string;
104
+ } = {},
105
+ ): AgentMessage[] {
106
+ const maxFiles = Math.max(0, opts.maxFiles ?? DEFAULT_MAX_FILES);
107
+ const tokenBudget = Math.max(0, opts.tokenBudget ?? DEFAULT_TOKEN_BUDGET);
108
+ const readToolName = opts.readToolName ?? DEFAULT_READ_TOOL_NAME;
109
+ if (maxFiles === 0 || tokenBudget === 0) return [];
110
+ const reads = collectDroppedReads(dropped, readToolName);
111
+ if (reads.length === 0) return [];
112
+ const visible = collectVisibleReadPaths(kept, readToolName);
113
+ const candidates = reads
114
+ .filter((r) => !visible.has(r.path))
115
+ .sort((a, b) => b.order - a.order)
116
+ .slice(0, maxFiles);
117
+ if (candidates.length === 0) return [];
118
+ const restored: AgentMessage[] = [];
119
+ let spent = 0;
120
+ for (const read of candidates) {
121
+ const message: AgentMessage = {
122
+ role: "user",
123
+ content: `${RESTORED_FILE_PREFIX} ${read.path}\n${read.body}`,
124
+ timestamp: Date.now(),
125
+ };
126
+ const cost = estimateMessageTokens(message);
127
+ if (spent + cost > tokenBudget && restored.length > 0) break;
128
+ if (spent + cost > tokenBudget && restored.length === 0) {
129
+ restored.push(message);
130
+ break;
131
+ }
132
+ restored.push(message);
133
+ spent += cost;
134
+ }
135
+ return restored.reverse();
136
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Window-budget — model-driven summarization.
3
+ *
4
+ * {@link summarize} is the single condensing primitive: given the dropped prefix
5
+ * of a transcript and a {@link SummarizeDeps} bundle, it flattens those messages,
6
+ * asks the injectable model completer to write a structured digest, and returns a
7
+ * synthetic {@link Summary} message.
8
+ */
9
+ import type { AgentMessage, CompleteFn, CondenseScope, Model, Summary } from "../contract.js";
10
+ import { completeSimple } from "indusagi/ai";
11
+ import { CONDENSER_BRIEF, buildSummaryPrompt, flattenTranscript } from "./prompt.js";
12
+
13
+ export interface SummarizeDeps {
14
+ /** Injectable completer (default: framework `completeSimple`). */
15
+ readonly complete?: CompleteFn;
16
+ /** The summarization model. When omitted, a deterministic local digest is used. */
17
+ readonly model?: Model<Model<string>["api"]>;
18
+ /** Which kind of condense this is; defaults to `"session"`. */
19
+ readonly scope?: CondenseScope;
20
+ /** An earlier digest to refresh/extend (iterative-refresh / branch carry-in). */
21
+ readonly priorDigest?: string;
22
+ /** Cancellation signal forwarded to the completer. */
23
+ readonly signal?: AbortSignal;
24
+ /** Credential for the summarization model's provider, forwarded to the completer. */
25
+ readonly apiKey?: string;
26
+ /** Optional cap on the digest size, in tokens. */
27
+ readonly maxTokens?: number;
28
+ }
29
+
30
+ const DIGEST_HEADER = "[session digest \u2014 older turns condensed]";
31
+ const BRANCH_HEADER = "[branch digest \u2014 archived from a path not taken]";
32
+
33
+ function toSummaryMessage(digest: string, scope: CondenseScope): AgentMessage {
34
+ const header = scope === "branch" ? BRANCH_HEADER : DIGEST_HEADER;
35
+ return {
36
+ role: "user",
37
+ content: `${header}\n\n${digest}`,
38
+ timestamp: Date.now(),
39
+ };
40
+ }
41
+
42
+ function textOf(message: { content: Array<{ type: string; text?: string }> }): string {
43
+ const parts: string[] = [];
44
+ for (const block of message.content) {
45
+ if (block.type === "text" && block.text && block.text.trim()) parts.push(block.text);
46
+ }
47
+ return parts.join("\n").trim();
48
+ }
49
+
50
+ function localFallbackDigest(messages: AgentMessage[], priorDigest?: string): string {
51
+ const lines: string[] = [
52
+ `# Carryover`,
53
+ `${messages.length} earlier message(s) were elided without a summarization model bound.`,
54
+ ];
55
+ if (priorDigest && priorDigest.trim()) {
56
+ lines.push("", priorDigest.trim());
57
+ }
58
+ return lines.join("\n");
59
+ }
60
+
61
+ /**
62
+ * Summarize a slice of dropped messages into a single synthetic {@link Summary}.
63
+ */
64
+ export async function summarize(
65
+ messages: AgentMessage[],
66
+ deps: SummarizeDeps = {},
67
+ ): Promise<Summary> {
68
+ const scope = deps.scope ?? "session";
69
+ const coveredCount = messages.length;
70
+ if (!deps.model) {
71
+ const digest = localFallbackDigest(messages, deps.priorDigest);
72
+ return { message: toSummaryMessage(digest, scope), coveredCount };
73
+ }
74
+ const prompt = buildSummaryPrompt(messages, scope, deps.priorDigest);
75
+ const context = {
76
+ systemPrompt: CONDENSER_BRIEF,
77
+ messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
78
+ };
79
+ const options = {
80
+ reasoning: "high",
81
+ ...(deps.maxTokens !== undefined ? { maxTokens: deps.maxTokens } : {}),
82
+ ...(deps.signal ? { signal: deps.signal } : {}),
83
+ ...(deps.apiKey !== undefined ? { apiKey: deps.apiKey } : {}),
84
+ };
85
+ const complete = deps.complete ?? completeSimple;
86
+ const reply = await complete(deps.model, context, options);
87
+ const digest = textOf(reply as { content: Array<{ type: string; text?: string }> });
88
+ const finalDigest = digest || localFallbackDigest(messages, deps.priorDigest);
89
+ return { message: toSummaryMessage(finalDigest, scope), coveredCount };
90
+ }
91
+
92
+ /**
93
+ * Branch-archival entrypoint: condense an abandoned branch into one summary
94
+ * message.
95
+ */
96
+ export function condenseScope(
97
+ messages: AgentMessage[],
98
+ deps: Omit<SummarizeDeps, "scope"> = {},
99
+ ): Promise<Summary> {
100
+ return summarize(messages, { ...deps, scope: "branch" });
101
+ }
102
+
103
+ export { flattenTranscript };
@@ -0,0 +1,14 @@
1
+ // @ts-nocheck
2
+ // Type declarations recovered from dist/types (no runtime body)
3
+ /**
4
+ * Window-budget — summarization subsystem barrel.
5
+ *
6
+ * Model-driven condensing of the dropped transcript prefix into one synthetic
7
+ * summary message, with re-authored prompts (new section names + a new tag
8
+ * vocabulary). Branch-archival folds in behind the scope flag — `condenseScope`
9
+ * is the branch entrypoint over the same `summarize` core.
10
+ */
11
+ export { buildSummaryPrompt, CONDENSER_BRIEF, flattenTranscript, } from "./prompt.js";
12
+ export { condenseScope, summarize } from "./condense.js";
13
+ export type { SummarizeDeps } from "./condense.js";
14
+ export type { AgentMessage, CompleteFn, CondenseScope, Model, Summary, } from "../contract.js";