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,129 @@
1
+ /**
2
+ * Dialog bridge — the ask / tell seam over the link transport.
3
+ */
4
+ import type {
5
+ Ask,
6
+ AskAnswer,
7
+ ChannelTimings,
8
+ DialogBridge,
9
+ NdjsonFramer,
10
+ WritableLine,
11
+ } from "../contract.js";
12
+
13
+ export interface DialogBridgeDeps {
14
+ readonly out: WritableLine;
15
+ readonly framer: NdjsonFramer;
16
+ readonly timings: ChannelTimings;
17
+ }
18
+
19
+ export interface LinkedDialogBridge extends DialogBridge {
20
+ deliver(answer: AskAnswer): boolean;
21
+ drain(): void;
22
+ }
23
+
24
+ interface PendingEntry {
25
+ settle: (value: unknown) => void;
26
+ timer: ReturnType<typeof setTimeout>;
27
+ }
28
+
29
+ function nextAskId(seq: { n: number }): string {
30
+ seq.n += 1;
31
+ return `ask-${Date.now().toString(36)}-${seq.n.toString(36)}`;
32
+ }
33
+
34
+ /**
35
+ * Construct a {@link LinkedDialogBridge} over an injected sink + framer.
36
+ */
37
+ export function createDialogBridge(deps: DialogBridgeDeps): LinkedDialogBridge {
38
+ const pending = new Map<string, PendingEntry>();
39
+ const seq = { n: 0 };
40
+
41
+ function emit(frame: Ask | { type: "tell"; kind: string; payload?: unknown }): void {
42
+ deps.out.write(deps.framer.encodeLine(frame));
43
+ }
44
+
45
+ function resolvePending(id: string, value: unknown): boolean {
46
+ const entry = pending.get(id);
47
+ if (entry === undefined) {
48
+ return false;
49
+ }
50
+ pending.delete(id);
51
+ clearTimeout(entry.timer);
52
+ entry.settle(value);
53
+ return true;
54
+ }
55
+
56
+ return {
57
+ ask<T>(kind: string, payload: unknown, fallback: T): Promise<T> {
58
+ const id = nextAskId(seq);
59
+ const frame: Ask = { type: "ask", id, kind, payload };
60
+ return new Promise<T>((resolve) => {
61
+ const timer = setTimeout(() => {
62
+ pending.delete(id);
63
+ resolve(fallback);
64
+ }, deps.timings.dialogMs);
65
+ if (typeof timer.unref === "function") {
66
+ timer.unref();
67
+ }
68
+ pending.set(id, {
69
+ settle: (value: unknown) => resolve(value as T),
70
+ timer,
71
+ });
72
+ emit(frame);
73
+ });
74
+ },
75
+ tell(kind: string, payload?: unknown): void {
76
+ emit({ type: "tell", kind, payload });
77
+ },
78
+ deliver(answer: AskAnswer): boolean {
79
+ return resolvePending(answer.id, answer.value);
80
+ },
81
+ drain(): void {
82
+ for (const [, entry] of pending) {
83
+ clearTimeout(entry.timer);
84
+ entry.settle(undefined);
85
+ }
86
+ pending.clear();
87
+ },
88
+ };
89
+ }
90
+
91
+ export interface DialogSpec {
92
+ readonly kind: string;
93
+ readonly mode: "ask" | "tell";
94
+ readonly fallback?: unknown;
95
+ }
96
+
97
+ export const DIALOG_TABLE = Object.freeze({
98
+ select: { kind: "select", mode: "ask", fallback: null },
99
+ confirm: { kind: "confirm", mode: "ask", fallback: false },
100
+ input: { kind: "input", mode: "ask", fallback: null },
101
+ editor: { kind: "editor", mode: "ask", fallback: null },
102
+ notify: { kind: "notify", mode: "tell" },
103
+ status: { kind: "status", mode: "tell" },
104
+ title: { kind: "title", mode: "tell" },
105
+ });
106
+
107
+ export type DialogMethods = {
108
+ [K in keyof typeof DIALOG_TABLE]: (typeof DIALOG_TABLE)[K]["mode"] extends "ask"
109
+ ? (payload?: unknown) => Promise<unknown>
110
+ : (payload?: unknown) => void;
111
+ };
112
+
113
+ /**
114
+ * Close the {@link DIALOG_TABLE} over one {@link DialogBridge} into typed,
115
+ * named convenience methods.
116
+ */
117
+ export function makeDialogMethods(bridge: DialogBridge): DialogMethods {
118
+ const methods = {} as Record<string, (payload?: unknown) => unknown>;
119
+ for (const name of Object.keys(DIALOG_TABLE)) {
120
+ const spec = DIALOG_TABLE[name as keyof typeof DIALOG_TABLE];
121
+ if (spec.mode === "ask") {
122
+ methods[name] = (payload?: unknown): Promise<unknown> =>
123
+ bridge.ask(spec.kind, payload, spec.fallback);
124
+ } else {
125
+ methods[name] = (payload?: unknown): void => bridge.tell(spec.kind, payload);
126
+ }
127
+ }
128
+ return methods as DialogMethods;
129
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Link driver — the generated client over the JSON-RPC link.
3
+ */
4
+ import {
5
+ DEFAULT_CHANNEL_TIMINGS,
6
+ PROTOCOL_VERSION,
7
+ REQUEST_ID_PREFIX,
8
+ isReplyOk,
9
+ type Ask,
10
+ type ChannelTimings,
11
+ type LinkDriver,
12
+ type NdjsonFramer,
13
+ type OpError,
14
+ type OpRegistry,
15
+ type ReadableChunks,
16
+ type Reply,
17
+ type Signal,
18
+ type WritableLine,
19
+ } from "../contract.js";
20
+ import { ndjsonFramer } from "../framer.js";
21
+
22
+ export interface LinkDriverIo {
23
+ readonly out: WritableLine;
24
+ readonly in: ReadableChunks;
25
+ }
26
+
27
+ export interface LinkDriverConfig {
28
+ readonly framer?: NdjsonFramer;
29
+ readonly timings?: Partial<ChannelTimings>;
30
+ readonly onSignal?: (signal: Signal) => void;
31
+ readonly onAsk?: (ask: Ask) => unknown | Promise<unknown>;
32
+ }
33
+
34
+ export class LinkRequestError extends Error {
35
+ readonly code: number;
36
+ readonly data: unknown;
37
+
38
+ constructor(error: OpError) {
39
+ super(error.message);
40
+ this.name = "LinkRequestError";
41
+ this.code = error.code;
42
+ this.data = error.data;
43
+ }
44
+ }
45
+
46
+ export interface LinkDriverHandle<Ops extends OpRegistry> {
47
+ readonly client: LinkDriver<Ops>;
48
+ readonly done: Promise<void>;
49
+ close(reason?: string): void;
50
+ }
51
+
52
+ function isReplyFrame(frame: unknown): frame is Reply {
53
+ if (typeof frame !== "object" || frame === null) return false;
54
+ const f = frame as Record<string, unknown>;
55
+ return f.id !== undefined && ("result" in f || "error" in f);
56
+ }
57
+
58
+ function isSignalFrame(frame: unknown): frame is Signal {
59
+ return (
60
+ typeof frame === "object" &&
61
+ frame !== null &&
62
+ (frame as { type?: unknown }).type === "signal"
63
+ );
64
+ }
65
+
66
+ function isAskFrame(frame: unknown): frame is Ask {
67
+ return (
68
+ typeof frame === "object" &&
69
+ frame !== null &&
70
+ (frame as { type?: unknown }).type === "ask" &&
71
+ typeof (frame as { id?: unknown }).id === "string"
72
+ );
73
+ }
74
+
75
+ function resolveTimings(
76
+ overrides: Partial<ChannelTimings> | undefined,
77
+ ): ChannelTimings {
78
+ return { ...DEFAULT_CHANNEL_TIMINGS, ...overrides };
79
+ }
80
+
81
+ interface PendingEntry {
82
+ resolve: (value: unknown) => void;
83
+ reject: (reason: unknown) => void;
84
+ timer: ReturnType<typeof setTimeout>;
85
+ }
86
+
87
+ /**
88
+ * Build a {@link LinkDriverHandle} over an injected transport.
89
+ */
90
+ export function createLinkDriver<Ops extends OpRegistry = OpRegistry>(
91
+ io: LinkDriverIo,
92
+ config: LinkDriverConfig = {},
93
+ ): LinkDriverHandle<Ops> {
94
+ const framer = config.framer ?? ndjsonFramer;
95
+ const timings = resolveTimings(config.timings);
96
+ const pending = new Map<string, PendingEntry>();
97
+ let seq = 0;
98
+ let closed = false;
99
+
100
+ function nextId(): string {
101
+ seq += 1;
102
+ return `${REQUEST_ID_PREFIX}${Date.now().toString(36)}-${seq.toString(36)}`;
103
+ }
104
+
105
+ function request(method: string, params?: unknown): Promise<unknown> {
106
+ if (closed) {
107
+ return Promise.reject(new Error("link driver is closed"));
108
+ }
109
+ const id = nextId();
110
+ const frame =
111
+ params === undefined
112
+ ? { jsonrpc: PROTOCOL_VERSION, id, method }
113
+ : { jsonrpc: PROTOCOL_VERSION, id, method, params };
114
+ return new Promise<unknown>((resolve, reject) => {
115
+ const timer = setTimeout(() => {
116
+ pending.delete(id);
117
+ reject(new Error(`link request timed out: ${method}`));
118
+ }, timings.requestMs);
119
+ if (typeof timer.unref === "function") {
120
+ timer.unref();
121
+ }
122
+ pending.set(id, { resolve, reject, timer });
123
+ io.out.write(framer.encodeLine(frame));
124
+ });
125
+ }
126
+
127
+ function settleReply(reply: Reply): void {
128
+ const entry = pending.get(String(reply.id));
129
+ if (entry === undefined) return;
130
+ pending.delete(String(reply.id));
131
+ clearTimeout(entry.timer);
132
+ if (isReplyOk(reply)) {
133
+ entry.resolve(reply.result);
134
+ } else {
135
+ entry.reject(new LinkRequestError(reply.error));
136
+ }
137
+ }
138
+
139
+ async function answerAsk(ask: Ask): Promise<void> {
140
+ const value = config.onAsk ? await config.onAsk(ask) : null;
141
+ const answer = { type: "answer", id: ask.id, value };
142
+ io.out.write(framer.encodeLine(answer));
143
+ }
144
+
145
+ function rejectAll(reason: string): void {
146
+ for (const [, entry] of pending) {
147
+ clearTimeout(entry.timer);
148
+ entry.reject(new Error(reason));
149
+ }
150
+ pending.clear();
151
+ }
152
+
153
+ async function pump(): Promise<void> {
154
+ try {
155
+ for await (const frame of framer.decodeLines(io.in)) {
156
+ if (isReplyFrame(frame)) {
157
+ settleReply(frame);
158
+ continue;
159
+ }
160
+ if (isSignalFrame(frame)) {
161
+ config.onSignal?.(frame);
162
+ continue;
163
+ }
164
+ if (isAskFrame(frame)) {
165
+ await answerAsk(frame);
166
+ continue;
167
+ }
168
+ }
169
+ } finally {
170
+ closed = true;
171
+ rejectAll("link closed before reply");
172
+ }
173
+ }
174
+
175
+ const client = new Proxy({} as LinkDriver<Ops>, {
176
+ get(_target, prop) {
177
+ if (typeof prop !== "string") return undefined;
178
+ return (params?: unknown) => request(prop, params);
179
+ },
180
+ });
181
+
182
+ return {
183
+ client,
184
+ done: pump(),
185
+ close(reason: string = "link driver closed by caller"): void {
186
+ closed = true;
187
+ rejectAll(reason);
188
+ },
189
+ };
190
+ }
@@ -0,0 +1,34 @@
1
+ // @ts-nocheck
2
+ // Type declarations recovered from dist/types (no runtime body)
3
+ /**
4
+ * Link channel — public barrel.
5
+ *
6
+ * The link channel is the long-lived, bidirectional half of the channels
7
+ * subsystem: a JSON-RPC 2.0 server that dispatches framed requests through an
8
+ * {@link OpRegistry} (`server.ts`), a generated {@link LinkDriver} client built
9
+ * from a {@link Proxy} rather than hand-written methods (`driver.ts`), and the
10
+ * ask / tell dialog bridge that both ends share (`dialog.ts`). Consumers import
11
+ * the link surface from `src/channels/link` rather than the individual modules.
12
+ */
13
+ export {
14
+ createLinkServer,
15
+ type LinkServer,
16
+ type LinkServerIo,
17
+ type LinkServerConfig,
18
+ } from "./server.js";
19
+ export {
20
+ createLinkDriver,
21
+ LinkRequestError,
22
+ type LinkDriverHandle,
23
+ type LinkDriverIo,
24
+ type LinkDriverConfig,
25
+ } from "./driver.js";
26
+ export {
27
+ createDialogBridge,
28
+ makeDialogMethods,
29
+ DIALOG_TABLE,
30
+ type LinkedDialogBridge,
31
+ type DialogBridgeDeps,
32
+ type DialogSpec,
33
+ type DialogMethods,
34
+ } from "./dialog.js";
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Link server — the data-driven JSON-RPC 2.0 dispatch loop.
3
+ */
4
+ import {
5
+ DEFAULT_CHANNEL_TIMINGS,
6
+ OP_ERROR,
7
+ PROTOCOL_VERSION,
8
+ type AskAnswer,
9
+ type ChannelContext,
10
+ type NdjsonFramer,
11
+ type OpError,
12
+ type OpRegistry,
13
+ type ReadableChunks,
14
+ type Reply,
15
+ type SessionConductor,
16
+ type WritableLine,
17
+ } from "../contract.js";
18
+ import { ndjsonFramer } from "../framer.js";
19
+ import { dispatch, UnknownOpError } from "../ops.js";
20
+ import { createDialogBridge, type LinkedDialogBridge } from "./dialog.js";
21
+
22
+ export interface LinkServerIo {
23
+ readonly in: ReadableChunks;
24
+ readonly out: WritableLine;
25
+ }
26
+
27
+ export interface LinkServerConfig {
28
+ readonly framer?: NdjsonFramer;
29
+ readonly timings?: Partial<typeof DEFAULT_CHANNEL_TIMINGS>;
30
+ }
31
+
32
+ export interface LinkServer {
33
+ readonly done: Promise<void>;
34
+ readonly dialog: LinkedDialogBridge;
35
+ }
36
+
37
+ function isRequestFrame(frame: unknown): frame is { method: string; id?: unknown; params?: unknown } {
38
+ return (
39
+ typeof frame === "object" && frame !== null && typeof (frame as { method?: unknown }).method === "string"
40
+ );
41
+ }
42
+
43
+ function isAnswerFrame(frame: unknown): frame is AskAnswer {
44
+ return (
45
+ typeof frame === "object" &&
46
+ frame !== null &&
47
+ (frame as { type?: unknown }).type === "answer" &&
48
+ typeof (frame as { id?: unknown }).id === "string"
49
+ );
50
+ }
51
+
52
+ function toOpError(error: unknown): OpError {
53
+ if (error instanceof UnknownOpError) {
54
+ return { code: error.code, message: error.message, data: { method: error.method } };
55
+ }
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ return { code: OP_ERROR.handlerFailed, message };
58
+ }
59
+
60
+ function resolveTimings(
61
+ overrides: Partial<typeof DEFAULT_CHANNEL_TIMINGS> | undefined,
62
+ ): typeof DEFAULT_CHANNEL_TIMINGS {
63
+ return { ...DEFAULT_CHANNEL_TIMINGS, ...overrides };
64
+ }
65
+
66
+ /**
67
+ * Start a link server over an op registry, a conductor, and an injected
68
+ * transport.
69
+ */
70
+ export function createLinkServer(
71
+ registry: OpRegistry,
72
+ conductor: SessionConductor,
73
+ io: LinkServerIo,
74
+ config: LinkServerConfig = {},
75
+ ): LinkServer {
76
+ const framer = config.framer ?? ndjsonFramer;
77
+ const timings = resolveTimings(config.timings);
78
+ const dialog = createDialogBridge({ out: io.out, framer, timings });
79
+ const ctx: ChannelContext = {
80
+ conductor,
81
+ out: io.out,
82
+ framer,
83
+ dialog,
84
+ };
85
+
86
+ function writeReply(reply: Reply): void {
87
+ io.out.write(framer.encodeLine(reply));
88
+ }
89
+
90
+ function replyOk(id: unknown, result: unknown): void {
91
+ writeReply({ jsonrpc: PROTOCOL_VERSION, id: id as Reply["id"], result });
92
+ }
93
+
94
+ function replyErr(id: unknown, error: OpError): void {
95
+ writeReply({ jsonrpc: PROTOCOL_VERSION, id: id as Reply["id"], error });
96
+ }
97
+
98
+ async function handleRequest(frame: { method: string; id?: unknown; params?: unknown }): Promise<void> {
99
+ const wantsReply = frame.id !== undefined;
100
+ try {
101
+ const result = await dispatch(registry, frame.method, frame.params, ctx);
102
+ if (wantsReply) {
103
+ replyOk(frame.id, result);
104
+ }
105
+ } catch (error) {
106
+ if (wantsReply) {
107
+ replyErr(frame.id, toOpError(error));
108
+ }
109
+ }
110
+ }
111
+
112
+ async function pump(): Promise<void> {
113
+ const inflight = new Set<Promise<void>>();
114
+ try {
115
+ for await (const frame of framer.decodeLines(io.in)) {
116
+ if (isAnswerFrame(frame)) {
117
+ dialog.deliver({ type: "answer", id: frame.id, value: frame.value });
118
+ continue;
119
+ }
120
+ if (isRequestFrame(frame)) {
121
+ const task = handleRequest(frame);
122
+ inflight.add(task);
123
+ void task.finally(() => inflight.delete(task));
124
+ continue;
125
+ }
126
+ }
127
+ await Promise.all(inflight);
128
+ } finally {
129
+ dialog.drain();
130
+ }
131
+ }
132
+
133
+ return { done: pump(), dialog };
134
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Oneshot channel — a single non-interactive run to settlement.
3
+ */
4
+ import type {
5
+ ChannelContext,
6
+ ConductorState,
7
+ DialogBridge,
8
+ OneshotRequest,
9
+ OneshotShape,
10
+ OneshotStrategy,
11
+ } from "./contract.js";
12
+
13
+ export const inertDialog: DialogBridge = Object.freeze({
14
+ ask<T>(_kind: string, _payload: unknown, fallback: T): Promise<T> {
15
+ return Promise.resolve(fallback);
16
+ },
17
+ tell(_kind: string, _payload?: unknown): void {
18
+ /* no-op */
19
+ },
20
+ });
21
+
22
+ const EXIT_OK = 0;
23
+ const EXIT_FAULT = 1;
24
+
25
+ function exitCodeFor(state: ConductorState): number {
26
+ return state.phase === "faulted" ? EXIT_FAULT : EXIT_OK;
27
+ }
28
+
29
+ function textStrategy(): OneshotStrategy {
30
+ let answer = "";
31
+ return {
32
+ onSignal(signal: unknown): void {
33
+ const s = signal as { kind?: string; delta?: string };
34
+ if (s.kind === "text") {
35
+ answer += s.delta ?? "";
36
+ }
37
+ },
38
+ finish(state: ConductorState, ctx: ChannelContext): number {
39
+ ctx.out.write(answer.endsWith("\n") ? answer : `${answer}\n`);
40
+ return exitCodeFor(state);
41
+ },
42
+ };
43
+ }
44
+
45
+ function ndjsonStrategy(): OneshotStrategy {
46
+ return {
47
+ onStart(ctx: ChannelContext): void {
48
+ ctx.out.write(ctx.framer.encodeLine({ type: "signal", name: "start", body: {} }));
49
+ },
50
+ onSignal(signal: unknown, ctx: ChannelContext): void {
51
+ const s = signal as { kind: string };
52
+ ctx.out.write(ctx.framer.encodeLine({ type: "signal", name: s.kind, body: s }));
53
+ },
54
+ finish(state: ConductorState, ctx: ChannelContext): number {
55
+ ctx.out.write(
56
+ ctx.framer.encodeLine({
57
+ type: "signal",
58
+ name: "end",
59
+ body: { phase: state.phase, usage: state.usage, fault: state.fault },
60
+ }),
61
+ );
62
+ return exitCodeFor(state);
63
+ },
64
+ };
65
+ }
66
+
67
+ const STRATEGIES: Readonly<Record<OneshotShape, () => OneshotStrategy>> = Object.freeze({
68
+ text: textStrategy,
69
+ ndjson: ndjsonStrategy,
70
+ });
71
+
72
+ /**
73
+ * Run one non-interactive request to settlement.
74
+ */
75
+ export async function runOneshot(ctx: ChannelContext, opts: OneshotRequest): Promise<number> {
76
+ const strategy = STRATEGIES[opts.shape]();
77
+ const unsubscribe = ctx.conductor.subscribe((signal) => {
78
+ strategy.onSignal?.(signal, ctx);
79
+ });
80
+ try {
81
+ await strategy.onStart?.(ctx);
82
+ let state = ctx.conductor.snapshot();
83
+ for (const prompt of opts.prompts) {
84
+ state = await ctx.conductor.submit(prompt);
85
+ }
86
+ return strategy.finish(state, ctx);
87
+ } finally {
88
+ unsubscribe();
89
+ }
90
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Operation registry — the single declarative source both halves of the link
3
+ * consume.
4
+ */
5
+ import { defineOps, OP_ERROR } from "./contract.js";
6
+ import type { ChannelContext, Op, OpRegistry } from "./contract.js";
7
+
8
+ export { defineOps };
9
+
10
+ export class UnknownOpError extends Error {
11
+ readonly code: number;
12
+ readonly method: string;
13
+
14
+ constructor(method: string) {
15
+ super("Unknown op: " + method);
16
+ this.name = "UnknownOpError";
17
+ this.method = method;
18
+ this.code = OP_ERROR.unknownOp;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Run one operation by name against the shared context.
24
+ */
25
+ export async function dispatch(
26
+ registry: OpRegistry,
27
+ method: string,
28
+ params: unknown,
29
+ ctx: ChannelContext,
30
+ ): Promise<unknown> {
31
+ const op = registry[method];
32
+ if (op === undefined) {
33
+ throw new UnknownOpError(method);
34
+ }
35
+ return op.handle(params, ctx);
36
+ }
37
+
38
+ export function hasOp(registry: OpRegistry, method: string): boolean {
39
+ return Object.prototype.hasOwnProperty.call(registry, method);
40
+ }
41
+
42
+ export function methodsOf<T extends OpRegistry>(registry: T): ReadonlyArray<keyof T & string> {
43
+ return Object.keys(registry).sort() as ReadonlyArray<keyof T & string>;
44
+ }
45
+
46
+ export interface OpRegistryHandle<T extends OpRegistry> {
47
+ readonly registry: T;
48
+ readonly methods: ReadonlyArray<keyof T & string>;
49
+ dispatch(method: string, params: unknown, ctx: ChannelContext): Promise<unknown>;
50
+ }
51
+
52
+ /**
53
+ * Build an {@link OpRegistryHandle} from a record of operation declarations.
54
+ */
55
+ export function buildOps<T extends Record<string, Op<any, any>>>(
56
+ ops: T,
57
+ ): OpRegistryHandle<Readonly<T>> {
58
+ const registry = defineOps(ops);
59
+ return Object.freeze({
60
+ registry,
61
+ methods: methodsOf(registry),
62
+ dispatch: (method: string, params: unknown, ctx: ChannelContext): Promise<unknown> =>
63
+ dispatch(registry, method, params, ctx),
64
+ });
65
+ }