chainlesschain 0.45.11 → 0.45.19

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 (81) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
  3. package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
  4. package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
  5. package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
  6. package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
  7. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
  8. package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
  9. package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
  10. package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
  11. package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
  12. package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
  14. package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
  15. package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
  16. package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
  17. package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
  18. package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
  19. package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
  20. package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
  21. package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
  22. package/src/assets/web-panel/index.html +2 -2
  23. package/src/commands/agent.js +7 -8
  24. package/src/commands/chat.js +9 -11
  25. package/src/commands/serve.js +11 -106
  26. package/src/commands/session.js +185 -18
  27. package/src/commands/ui.js +10 -151
  28. package/src/gateways/repl/agent-repl.js +1 -0
  29. package/src/gateways/repl/chat-repl.js +1 -0
  30. package/src/gateways/ui/web-ui-server.js +1 -0
  31. package/src/gateways/ws/action-protocol.js +83 -0
  32. package/src/gateways/ws/message-dispatcher.js +73 -0
  33. package/src/gateways/ws/session-protocol.js +396 -0
  34. package/src/gateways/ws/task-protocol.js +55 -0
  35. package/src/gateways/ws/worktree-protocol.js +315 -0
  36. package/src/gateways/ws/ws-server.js +4 -0
  37. package/src/gateways/ws/ws-session-gateway.js +1 -0
  38. package/src/harness/background-task-manager.js +506 -0
  39. package/src/harness/background-task-worker.js +48 -0
  40. package/src/harness/compression-telemetry.js +214 -0
  41. package/src/harness/feature-flags.js +157 -0
  42. package/src/harness/jsonl-session-store.js +452 -0
  43. package/src/harness/prompt-compressor.js +416 -0
  44. package/src/harness/worktree-isolator.js +845 -0
  45. package/src/lib/agent-core.js +246 -45
  46. package/src/lib/background-task-manager.js +1 -305
  47. package/src/lib/background-task-worker.js +1 -50
  48. package/src/lib/compression-telemetry.js +5 -0
  49. package/src/lib/feature-flags.js +7 -182
  50. package/src/lib/interaction-adapter.js +32 -6
  51. package/src/lib/jsonl-session-store.js +21 -237
  52. package/src/lib/prompt-compressor.js +10 -351
  53. package/src/lib/sub-agent-context.js +91 -0
  54. package/src/lib/worktree-isolator.js +13 -231
  55. package/src/lib/ws-agent-handler.js +1 -0
  56. package/src/lib/ws-server.js +155 -359
  57. package/src/lib/ws-session-manager.js +82 -1
  58. package/src/repl/agent-repl.js +114 -32
  59. package/src/runtime/agent-runtime.js +417 -0
  60. package/src/runtime/contracts/agent-turn.js +11 -0
  61. package/src/runtime/contracts/session-record.js +31 -0
  62. package/src/runtime/contracts/task-record.js +18 -0
  63. package/src/runtime/contracts/telemetry-record.js +23 -0
  64. package/src/runtime/contracts/worktree-record.js +14 -0
  65. package/src/runtime/index.js +13 -0
  66. package/src/runtime/policies/agent-policy.js +45 -0
  67. package/src/runtime/runtime-context.js +14 -0
  68. package/src/runtime/runtime-events.js +37 -0
  69. package/src/runtime/runtime-factory.js +50 -0
  70. package/src/tools/index.js +22 -0
  71. package/src/tools/legacy-agent-tools.js +171 -0
  72. package/src/tools/registry.js +141 -0
  73. package/src/tools/tool-context.js +28 -0
  74. package/src/tools/tool-permissions.js +28 -0
  75. package/src/tools/tool-telemetry.js +39 -0
  76. package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
  77. package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
  78. package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
  79. package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
  80. package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
  81. package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
@@ -0,0 +1,417 @@
1
+ import chalk from "chalk";
2
+ import { execSync } from "child_process";
3
+ import path from "path";
4
+ import {
5
+ RuntimeEventEmitter,
6
+ RUNTIME_EVENTS,
7
+ createRuntimeEvent,
8
+ } from "./runtime-events.js";
9
+ import { createRuntimeContext } from "./runtime-context.js";
10
+ import { createAgentTurnRecord } from "./contracts/agent-turn.js";
11
+ import { logger } from "../lib/logger.js";
12
+ import { bootstrap } from "./bootstrap.js";
13
+ import { startAgentRepl } from "../gateways/repl/agent-repl.js";
14
+ import { startChatRepl } from "../gateways/repl/chat-repl.js";
15
+ import { ChainlessChainWSServer } from "../gateways/ws/ws-server.js";
16
+ import { WSSessionManager } from "../gateways/ws/ws-session-gateway.js";
17
+ import { createWebUIServer } from "../gateways/ui/web-ui-server.js";
18
+ import {
19
+ findProjectRoot,
20
+ loadProjectConfig,
21
+ } from "../lib/project-detector.js";
22
+ import { loadConfig } from "../lib/config-manager.js";
23
+
24
+ function openBrowser(url) {
25
+ try {
26
+ const platform = process.platform;
27
+ if (platform === "win32") {
28
+ execSync(`start "" "${url}"`, { stdio: "ignore" });
29
+ } else if (platform === "darwin") {
30
+ execSync(`open "${url}"`, { stdio: "ignore" });
31
+ } else {
32
+ execSync(`xdg-open "${url}"`, { stdio: "ignore" });
33
+ }
34
+ } catch (_err) {
35
+ // Non-critical.
36
+ }
37
+ }
38
+
39
+ export class AgentRuntime {
40
+ constructor({ kind, policy, config = null, deps = {} } = {}) {
41
+ this.kind = kind;
42
+ this.policy = policy;
43
+ this.config = config;
44
+ this.context = createRuntimeContext({ kind, policy, config });
45
+ this.events = deps.events || new RuntimeEventEmitter();
46
+ this.deps = {
47
+ startAgentRepl: deps.startAgentRepl || startAgentRepl,
48
+ startChatRepl: deps.startChatRepl || startChatRepl,
49
+ bootstrap: deps.bootstrap || bootstrap,
50
+ createServer:
51
+ deps.createServer ||
52
+ ((options) => new ChainlessChainWSServer(options)),
53
+ createSessionManager:
54
+ deps.createSessionManager ||
55
+ ((options) => new WSSessionManager(options)),
56
+ createWebServer:
57
+ deps.createWebServer || ((options) => createWebUIServer(options)),
58
+ findProjectRoot: deps.findProjectRoot || findProjectRoot,
59
+ loadProjectConfig: deps.loadProjectConfig || loadProjectConfig,
60
+ loadConfig: deps.loadConfig || loadConfig,
61
+ openBrowser: deps.openBrowser || openBrowser,
62
+ runTurn: deps.runTurn || null,
63
+ logger: deps.logger || logger,
64
+ };
65
+ }
66
+
67
+ on(eventName, listener) {
68
+ this.events.on(eventName, listener);
69
+ return this;
70
+ }
71
+
72
+ emit(eventName, payload) {
73
+ const event = createRuntimeEvent(eventName, payload, {
74
+ kind: this.kind,
75
+ sessionId: this.policy?.sessionId || null,
76
+ });
77
+ this.events.emit(eventName, event);
78
+ }
79
+
80
+ async resumeSession(sessionId) {
81
+ const nextSessionId = sessionId || this.policy.sessionId;
82
+ if (!nextSessionId) {
83
+ throw new Error("resumeSession requires a sessionId.");
84
+ }
85
+
86
+ this.policy = {
87
+ ...this.policy,
88
+ sessionId: nextSessionId,
89
+ };
90
+ this.context = createRuntimeContext({
91
+ kind: this.kind,
92
+ policy: this.policy,
93
+ config: this.config,
94
+ });
95
+
96
+ this.emit(RUNTIME_EVENTS.SESSION_RESUME, {
97
+ kind: this.kind,
98
+ sessionId: nextSessionId,
99
+ });
100
+
101
+ if (this.kind === "chat") {
102
+ return this.startChatSession();
103
+ }
104
+ if (this.kind === "agent") {
105
+ return this.startAgentSession();
106
+ }
107
+
108
+ throw new Error(`resumeSession is not supported for runtime kind "${this.kind}".`);
109
+ }
110
+
111
+ async runTurn(input, meta = {}) {
112
+ if (typeof this.deps.runTurn !== "function") {
113
+ throw new Error(`runTurn is not configured for runtime kind "${this.kind}".`);
114
+ }
115
+
116
+ const startedAt = Date.now();
117
+ this.emit(RUNTIME_EVENTS.TURN_START, createAgentTurnRecord({
118
+ kind: this.kind,
119
+ input,
120
+ meta,
121
+ sessionId: this.policy.sessionId || null,
122
+ startedAt,
123
+ }));
124
+
125
+ const result = await this.deps.runTurn({
126
+ input,
127
+ meta,
128
+ kind: this.kind,
129
+ policy: this.policy,
130
+ context: this.context,
131
+ });
132
+
133
+ this.emit(RUNTIME_EVENTS.TURN_END, createAgentTurnRecord({
134
+ kind: this.kind,
135
+ input,
136
+ meta,
137
+ result,
138
+ sessionId: this.policy.sessionId || null,
139
+ startedAt,
140
+ endedAt: Date.now(),
141
+ }));
142
+
143
+ return result;
144
+ }
145
+
146
+ async startAgentSession() {
147
+ this.emit(RUNTIME_EVENTS.RUNTIME_START, {
148
+ kind: this.kind,
149
+ policy: this.policy,
150
+ });
151
+ this.emit(RUNTIME_EVENTS.SESSION_START, {
152
+ kind: this.kind,
153
+ sessionId: this.policy.sessionId || null,
154
+ });
155
+ return this.deps.startAgentRepl(this.policy);
156
+ }
157
+
158
+ async startChatSession() {
159
+ this.emit(RUNTIME_EVENTS.RUNTIME_START, {
160
+ kind: this.kind,
161
+ policy: this.policy,
162
+ });
163
+ this.emit(RUNTIME_EVENTS.SESSION_START, {
164
+ kind: this.kind,
165
+ sessionId: this.policy.sessionId || null,
166
+ });
167
+ return this.deps.startChatRepl(this.policy);
168
+ }
169
+
170
+ async startServer() {
171
+ const { logger: runtimeLogger } = this.deps;
172
+ const {
173
+ port,
174
+ maxConnections,
175
+ timeout,
176
+ token,
177
+ allowRemote,
178
+ project,
179
+ } = this.policy;
180
+ let { host } = this.policy;
181
+
182
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
183
+ throw new Error("Invalid port number. Must be between 1 and 65535.");
184
+ }
185
+
186
+ if (allowRemote) {
187
+ if (!token) {
188
+ throw new Error("--allow-remote requires --token for security.");
189
+ }
190
+ host = "0.0.0.0";
191
+ }
192
+
193
+ let db = null;
194
+ try {
195
+ const ctx = await this.deps.bootstrap({ skipDb: false });
196
+ db = ctx.db?.getDb?.() || null;
197
+ } catch (_err) {
198
+ runtimeLogger.log(
199
+ chalk.yellow(
200
+ " Warning: Database not available, sessions will be in-memory only",
201
+ ),
202
+ );
203
+ }
204
+
205
+ const sessionManager = this.deps.createSessionManager({
206
+ db,
207
+ defaultProjectRoot: project,
208
+ });
209
+
210
+ const server = this.deps.createServer({
211
+ port,
212
+ host,
213
+ token,
214
+ maxConnections,
215
+ timeout,
216
+ sessionManager,
217
+ });
218
+
219
+ server.on("connection", ({ clientId, ip }) => {
220
+ runtimeLogger.log(chalk.green(` + Client connected: ${clientId} (${ip})`));
221
+ });
222
+
223
+ server.on("disconnection", ({ clientId, reason }) => {
224
+ const extra = reason ? ` (${reason})` : "";
225
+ runtimeLogger.log(
226
+ chalk.yellow(` - Client disconnected: ${clientId}${extra}`),
227
+ );
228
+ });
229
+
230
+ server.on("command:start", ({ id, command }) => {
231
+ runtimeLogger.log(chalk.cyan(` > [${id}] ${command}`));
232
+ });
233
+
234
+ server.on("command:end", ({ id, exitCode }) => {
235
+ const color = exitCode === 0 ? chalk.green : chalk.red;
236
+ runtimeLogger.log(color(` < [${id}] exit ${exitCode}`));
237
+ });
238
+
239
+ server.on("session:create", ({ sessionId, type }) => {
240
+ runtimeLogger.log(
241
+ chalk.green(` + Session created: ${sessionId} (${type})`),
242
+ );
243
+ });
244
+
245
+ server.on("session:close", ({ sessionId }) => {
246
+ runtimeLogger.log(chalk.yellow(` - Session closed: ${sessionId}`));
247
+ });
248
+
249
+ const shutdownHandler = async () => {
250
+ runtimeLogger.log(
251
+ "\n" + chalk.yellow("Shutting down WebSocket server..."),
252
+ );
253
+ await server.stop();
254
+ process.exit(0);
255
+ };
256
+
257
+ process.on("SIGINT", shutdownHandler);
258
+ process.on("SIGTERM", shutdownHandler);
259
+
260
+ await server.start();
261
+
262
+ this.emit(RUNTIME_EVENTS.RUNTIME_START, {
263
+ kind: this.kind,
264
+ policy: { ...this.policy, host },
265
+ });
266
+ this.emit(RUNTIME_EVENTS.SERVER_START, {
267
+ host,
268
+ port,
269
+ project,
270
+ });
271
+
272
+ runtimeLogger.log("");
273
+ runtimeLogger.log(chalk.bold(" ChainlessChain WebSocket Server"));
274
+ runtimeLogger.log("");
275
+ runtimeLogger.log(` Address: ${chalk.cyan(`ws://${host}:${port}`)}`);
276
+ runtimeLogger.log(
277
+ ` Auth: ${token ? chalk.green("enabled") : chalk.yellow("disabled")}`,
278
+ );
279
+ runtimeLogger.log(` Sessions: ${chalk.green("enabled")}`);
280
+ runtimeLogger.log(` Project: ${project}`);
281
+ runtimeLogger.log(` Max conn: ${maxConnections}`);
282
+ runtimeLogger.log(` Timeout: ${timeout}ms`);
283
+ runtimeLogger.log("");
284
+ runtimeLogger.log(chalk.dim(" Press Ctrl+C to stop"));
285
+ runtimeLogger.log("");
286
+
287
+ return server;
288
+ }
289
+
290
+ async startUiServer() {
291
+ const { logger: runtimeLogger } = this.deps;
292
+ const httpPort = this.policy.port;
293
+ const wsPort = this.policy.wsPort;
294
+ const host = this.policy.host;
295
+
296
+ if (Number.isNaN(httpPort) || httpPort < 1 || httpPort > 65535) {
297
+ throw new Error("Invalid --port. Must be between 1 and 65535.");
298
+ }
299
+ if (Number.isNaN(wsPort) || wsPort < 1 || wsPort > 65535) {
300
+ throw new Error("Invalid --ws-port. Must be between 1 and 65535.");
301
+ }
302
+
303
+ const projectRoot = this.deps.findProjectRoot(process.cwd());
304
+ const projectConfig = projectRoot
305
+ ? this.deps.loadProjectConfig(projectRoot)
306
+ : null;
307
+ const projectName =
308
+ projectConfig?.name ||
309
+ (projectRoot ? path.basename(projectRoot) : null);
310
+ const mode = projectRoot ? "project" : "global";
311
+
312
+ let db = null;
313
+ try {
314
+ const ctx = await this.deps.bootstrap({ skipDb: false });
315
+ db = ctx.db?.getDb?.() || null;
316
+ } catch (_err) {
317
+ runtimeLogger.log(
318
+ chalk.yellow(
319
+ " Warning: Database not available, sessions will be in-memory only",
320
+ ),
321
+ );
322
+ }
323
+
324
+ const appConfig = this.deps.loadConfig();
325
+ const sessionManager = this.deps.createSessionManager({
326
+ db,
327
+ defaultProjectRoot: projectRoot || process.cwd(),
328
+ config: appConfig,
329
+ });
330
+
331
+ const wsServer = this.deps.createServer({
332
+ port: wsPort,
333
+ host,
334
+ token: this.policy.token,
335
+ maxConnections: 20,
336
+ timeout: 60000,
337
+ sessionManager,
338
+ });
339
+ await wsServer.start();
340
+
341
+ const httpServer = this.deps.createWebServer({
342
+ wsPort,
343
+ wsToken: this.policy.token,
344
+ wsHost: host === "0.0.0.0" ? "127.0.0.1" : host,
345
+ projectRoot,
346
+ projectName,
347
+ mode,
348
+ staticDir: this.policy.webPanelDir,
349
+ });
350
+
351
+ await new Promise((resolve, reject) => {
352
+ httpServer.listen(httpPort, host, () => resolve());
353
+ httpServer.on("error", reject);
354
+ });
355
+
356
+ const uiUrl = `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${httpPort}`;
357
+
358
+ this.emit(RUNTIME_EVENTS.RUNTIME_START, {
359
+ kind: this.kind,
360
+ policy: this.policy,
361
+ });
362
+ this.emit(RUNTIME_EVENTS.SERVER_START, {
363
+ host,
364
+ port: httpPort,
365
+ wsPort,
366
+ mode,
367
+ projectRoot,
368
+ });
369
+
370
+ runtimeLogger.log("");
371
+ runtimeLogger.log(chalk.bold(" ChainlessChain 管理面板"));
372
+ runtimeLogger.log("");
373
+ if (mode === "project") {
374
+ runtimeLogger.log(
375
+ ` Mode: ${chalk.cyan("project")} ${chalk.dim(projectRoot)}`,
376
+ );
377
+ if (projectName) {
378
+ runtimeLogger.log(` Project: ${chalk.green(projectName)}`);
379
+ }
380
+ } else {
381
+ runtimeLogger.log(` Mode: ${chalk.cyan("global")}`);
382
+ }
383
+ runtimeLogger.log(` UI: ${chalk.cyan(uiUrl)}`);
384
+ runtimeLogger.log(` WS: ${chalk.dim(`ws://${host}:${wsPort}`)}`);
385
+ runtimeLogger.log(
386
+ ` Auth: ${this.policy.token ? chalk.green("enabled") : chalk.yellow("disabled")}`,
387
+ );
388
+ runtimeLogger.log("");
389
+ runtimeLogger.log(chalk.dim(" Press Ctrl+C to stop"));
390
+ runtimeLogger.log("");
391
+
392
+ if (this.policy.open) {
393
+ this.deps.openBrowser(uiUrl);
394
+ }
395
+
396
+ const shutdown = async () => {
397
+ runtimeLogger.log("\n" + chalk.yellow("Shutting down UI server..."));
398
+ await Promise.all([
399
+ new Promise((resolve) => httpServer.close(resolve)),
400
+ wsServer.stop(),
401
+ ]);
402
+ process.exit(0);
403
+ };
404
+
405
+ process.on("SIGINT", shutdown);
406
+ process.on("SIGTERM", shutdown);
407
+
408
+ return {
409
+ wsServer,
410
+ httpServer,
411
+ uiUrl,
412
+ mode,
413
+ projectRoot,
414
+ projectName,
415
+ };
416
+ }
417
+ }
@@ -0,0 +1,11 @@
1
+ export function createAgentTurnRecord(turn = {}) {
2
+ return {
3
+ kind: turn.kind || null,
4
+ sessionId: turn.sessionId || null,
5
+ input: turn.input,
6
+ meta: turn.meta || {},
7
+ result: turn.result,
8
+ startedAt: turn.startedAt || null,
9
+ endedAt: turn.endedAt || null,
10
+ };
11
+ }
@@ -0,0 +1,31 @@
1
+ import { createWorktreeRecord } from "./worktree-record.js";
2
+
3
+ export function createSessionRecord(session = {}, extras = {}) {
4
+ const history = Array.isArray(extras.history)
5
+ ? extras.history
6
+ : Array.isArray(session.messages)
7
+ ? session.messages.filter((item) => item.role !== "system")
8
+ : [];
9
+
10
+ return {
11
+ id: session.id || extras.sessionId || null,
12
+ type: session.type || extras.sessionType || null,
13
+ provider: session.provider || extras.provider || null,
14
+ model: session.model || extras.model || null,
15
+ projectRoot: session.projectRoot || extras.projectRoot || null,
16
+ baseProjectRoot: session.baseProjectRoot || extras.baseProjectRoot || null,
17
+ worktreeIsolation:
18
+ session.worktreeIsolation === true || extras.worktreeIsolation === true,
19
+ worktree:
20
+ session.worktree || extras.worktree
21
+ ? createWorktreeRecord(session.worktree || extras.worktree, {
22
+ requested:
23
+ session.worktreeIsolation === true ||
24
+ extras.worktreeIsolation === true,
25
+ })
26
+ : null,
27
+ messageCount: extras.messageCount ?? history.length,
28
+ history,
29
+ status: extras.status || null,
30
+ };
31
+ }
@@ -0,0 +1,18 @@
1
+ export function createTaskRecord(task = {}, meta = {}) {
2
+ return {
3
+ id: task.id || null,
4
+ status: task.status || null,
5
+ type: task.type || null,
6
+ description: task.description || null,
7
+ ownerNodeId: task.ownerNodeId || null,
8
+ createdAt: task.createdAt || null,
9
+ startedAt: task.startedAt || null,
10
+ completedAt: task.completedAt || null,
11
+ result: task.result ?? null,
12
+ error: task.error ?? null,
13
+ recoveredFromRestart: Boolean(task.recoveredFromRestart),
14
+ recoverySourceStatus: task.recoverySourceStatus || null,
15
+ outputSummary: task.outputSummary || null,
16
+ meta,
17
+ };
18
+ }
@@ -0,0 +1,23 @@
1
+ export function createTelemetryRecord(metric = {}, meta = {}) {
2
+ return {
3
+ kind: metric.kind || "telemetry",
4
+ provider: metric.provider || null,
5
+ model: metric.model || null,
6
+ source: metric.source || null,
7
+ strategy: metric.strategy || null,
8
+ variant: metric.variant || metric.abVariant || null,
9
+ savedTokens:
10
+ typeof metric.savedTokens === "number"
11
+ ? metric.savedTokens
12
+ : typeof metric.saved === "number"
13
+ ? metric.saved
14
+ : null,
15
+ originalTokens:
16
+ typeof metric.originalTokens === "number" ? metric.originalTokens : null,
17
+ compressedTokens:
18
+ typeof metric.compressedTokens === "number" ? metric.compressedTokens : null,
19
+ ratio: typeof metric.ratio === "number" ? metric.ratio : null,
20
+ timestamp: metric.timestamp || Date.now(),
21
+ meta,
22
+ };
23
+ }
@@ -0,0 +1,14 @@
1
+ export function createWorktreeRecord(worktree = {}, meta = {}) {
2
+ return {
3
+ branch: worktree.branch || null,
4
+ path: worktree.path || worktree.worktreePath || null,
5
+ baseBranch: worktree.baseBranch || null,
6
+ hasChanges: typeof worktree.hasChanges === "boolean" ? worktree.hasChanges : null,
7
+ summary: worktree.summary || null,
8
+ conflicts: Array.isArray(worktree.conflicts) ? worktree.conflicts : [],
9
+ previewEntrypoints: Array.isArray(worktree.previewEntrypoints)
10
+ ? worktree.previewEntrypoints
11
+ : [],
12
+ meta,
13
+ };
14
+ }
@@ -0,0 +1,13 @@
1
+ export { AgentRuntime } from "./agent-runtime.js";
2
+ export { createAgentRuntimeFactory } from "./runtime-factory.js";
3
+ export { createRuntimeContext } from "./runtime-context.js";
4
+ export {
5
+ RuntimeEventEmitter,
6
+ RUNTIME_EVENTS,
7
+ createRuntimeEvent,
8
+ } from "./runtime-events.js";
9
+ export { createAgentTurnRecord } from "./contracts/agent-turn.js";
10
+ export { createSessionRecord } from "./contracts/session-record.js";
11
+ export { createTaskRecord } from "./contracts/task-record.js";
12
+ export { createWorktreeRecord } from "./contracts/worktree-record.js";
13
+ export { createTelemetryRecord } from "./contracts/telemetry-record.js";
@@ -0,0 +1,45 @@
1
+ export function resolveAgentPolicy({
2
+ config = null,
3
+ overrides = {},
4
+ defaults = {},
5
+ } = {}) {
6
+ const llm = config?.llm || {};
7
+
8
+ return {
9
+ model: overrides.model || llm.model || defaults.model || "qwen2:7b",
10
+ provider:
11
+ overrides.provider || llm.provider || defaults.provider || "ollama",
12
+ baseUrl:
13
+ overrides.baseUrl !== undefined
14
+ ? overrides.baseUrl
15
+ : llm.baseUrl || defaults.baseUrl,
16
+ apiKey:
17
+ overrides.apiKey !== undefined
18
+ ? overrides.apiKey
19
+ : llm.apiKey || defaults.apiKey || null,
20
+ sessionId: overrides.sessionId || null,
21
+ };
22
+ }
23
+
24
+ export function resolveServerPolicy(overrides = {}) {
25
+ return {
26
+ port: overrides.port,
27
+ host: overrides.host,
28
+ token: overrides.token || null,
29
+ maxConnections: overrides.maxConnections,
30
+ timeout: overrides.timeout,
31
+ allowRemote: Boolean(overrides.allowRemote),
32
+ project: overrides.project || process.cwd(),
33
+ };
34
+ }
35
+
36
+ export function resolveUiPolicy(overrides = {}) {
37
+ return {
38
+ port: overrides.port,
39
+ wsPort: overrides.wsPort,
40
+ host: overrides.host,
41
+ open: overrides.open !== false,
42
+ token: overrides.token || null,
43
+ webPanelDir: overrides.webPanelDir || null,
44
+ };
45
+ }
@@ -0,0 +1,14 @@
1
+ export function createRuntimeContext({
2
+ kind,
3
+ policy,
4
+ config = null,
5
+ cwd = process.cwd(),
6
+ } = {}) {
7
+ return {
8
+ kind,
9
+ policy,
10
+ config,
11
+ cwd,
12
+ createdAt: Date.now(),
13
+ };
14
+ }
@@ -0,0 +1,37 @@
1
+ import { EventEmitter } from "node:events";
2
+
3
+ export const RUNTIME_EVENTS = {
4
+ RUNTIME_START: "runtime:start",
5
+ RUNTIME_STOP: "runtime:stop",
6
+ SESSION_START: "session:start",
7
+ SESSION_RESUME: "session:resume",
8
+ SESSION_END: "session:end",
9
+ SESSION_MESSAGE: "session:message",
10
+ TURN_START: "turn:start",
11
+ TURN_END: "turn:end",
12
+ TASK_CREATED: "task:created",
13
+ TASK_COMPLETED: "task:completed",
14
+ TASK_NOTIFICATION: "task:notification",
15
+ WORKTREE_DIFF_READY: "worktree:diff:ready",
16
+ WORKTREE_MERGED: "worktree:merge:completed",
17
+ COMPRESSION_APPLIED: "compression:applied",
18
+ COMPRESSION_SUMMARY: "compression:summary",
19
+ SERVER_START: "server:start",
20
+ SERVER_STOP: "server:stop",
21
+ ERROR: "runtime:error",
22
+ };
23
+
24
+ export function createRuntimeEvent(type, payload = {}, context = {}) {
25
+ return {
26
+ type,
27
+ toolDescriptor: payload.toolDescriptor || context.toolDescriptor || null,
28
+ toolTelemetryRecord:
29
+ payload.toolTelemetryRecord || context.toolTelemetryRecord || null,
30
+ kind: context.kind || payload.kind || null,
31
+ sessionId: context.sessionId || payload.sessionId || null,
32
+ timestamp: context.timestamp || Date.now(),
33
+ payload,
34
+ };
35
+ }
36
+
37
+ export class RuntimeEventEmitter extends EventEmitter {}
@@ -0,0 +1,50 @@
1
+ import { loadConfig } from "../lib/config-manager.js";
2
+ import { AgentRuntime } from "./agent-runtime.js";
3
+ import {
4
+ resolveAgentPolicy,
5
+ resolveServerPolicy,
6
+ resolveUiPolicy,
7
+ } from "./policies/agent-policy.js";
8
+
9
+ export function createAgentRuntimeFactory({
10
+ config = loadConfig(),
11
+ deps = {},
12
+ } = {}) {
13
+ return {
14
+ createAgentRuntime(overrides = {}) {
15
+ return new AgentRuntime({
16
+ kind: "agent",
17
+ policy: resolveAgentPolicy({ config, overrides }),
18
+ config,
19
+ deps,
20
+ });
21
+ },
22
+
23
+ createChatRuntime(overrides = {}) {
24
+ return new AgentRuntime({
25
+ kind: "chat",
26
+ policy: resolveAgentPolicy({ config, overrides }),
27
+ config,
28
+ deps,
29
+ });
30
+ },
31
+
32
+ createServerRuntime(overrides = {}) {
33
+ return new AgentRuntime({
34
+ kind: "server",
35
+ policy: resolveServerPolicy(overrides),
36
+ config,
37
+ deps,
38
+ });
39
+ },
40
+
41
+ createUiRuntime(overrides = {}) {
42
+ return new AgentRuntime({
43
+ kind: "ui",
44
+ policy: resolveUiPolicy(overrides),
45
+ config,
46
+ deps,
47
+ });
48
+ },
49
+ };
50
+ }