chainlesschain 0.47.6 → 0.47.7

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 (107) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/Analytics-BFI7jbwM.css +1 -0
  4. package/src/assets/web-panel/assets/Analytics-DQ135mAd.js +3 -0
  5. package/src/assets/web-panel/assets/AppLayout-6SPt_8Y_.js +1 -0
  6. package/src/assets/web-panel/assets/AppLayout-BFJ-Fofn.css +1 -0
  7. package/src/assets/web-panel/assets/{Backup-Ba9UybpT.js → Backup-DbVRG5vE.js} +1 -1
  8. package/src/assets/web-panel/assets/{Chat-BwXskT21.js → Chat-wVhrFK9C.js} +1 -1
  9. package/src/assets/web-panel/assets/{Cowork-UmOe7qvE.js → Cowork-lOC25IW2.js} +1 -1
  10. package/src/assets/web-panel/assets/{Cron-JHS-rc-4.js → Cron-3P0eVLTV.js} +1 -1
  11. package/src/assets/web-panel/assets/{Dashboard-B95cMCO7.js → Dashboard-Br7kCwKJ.js} +1 -1
  12. package/src/assets/web-panel/assets/{Git-CSYO0_zk.js → Git-CrDCcBig.js} +2 -2
  13. package/src/assets/web-panel/assets/{Logs-Hxw_K0km.js → Logs-BfTE8urP.js} +1 -1
  14. package/src/assets/web-panel/assets/{McpTools-DIE75TrB.js → McpTools-CsGIijNe.js} +1 -1
  15. package/src/assets/web-panel/assets/{Memory-C4KVnLlp.js → Memory-BXX_yMKJ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Notes-DuzrHMAk.js → Notes-DU6Vf2cL.js} +1 -1
  17. package/src/assets/web-panel/assets/{Organization-DTq6uF82.js → Organization-Bny6yOPV.js} +4 -4
  18. package/src/assets/web-panel/assets/{P2P-C0hjlhsR.js → P2P-BxFZ1Bit.js} +2 -2
  19. package/src/assets/web-panel/assets/{Permissions-Ec0NH-xC.js → Permissions-B1j3Mtms.js} +3 -3
  20. package/src/assets/web-panel/assets/{Projects-U8D0asCS.js → Projects-D-CGscDu.js} +1 -1
  21. package/src/assets/web-panel/assets/{Providers-BngtTLvJ.js → Providers-r6NaBYMf.js} +1 -1
  22. package/src/assets/web-panel/assets/{RssFeed-B9NbwCKM.js → RssFeed-D7b68C5q.js} +1 -1
  23. package/src/assets/web-panel/assets/{Security-BL5Rkr1T.js → Security-MJfKv0EJ.js} +3 -3
  24. package/src/assets/web-panel/assets/{Services-D4MJzLld.js → Services-Yb_Q1V3d.js} +1 -1
  25. package/src/assets/web-panel/assets/{Skills-CQTOMDwF.js → Skills-DLTHcH5T.js} +1 -1
  26. package/src/assets/web-panel/assets/{Tasks-DepbJMnL.js → Tasks-CqycpPjS.js} +1 -1
  27. package/src/assets/web-panel/assets/{Templates-C24PVZPu.js → Templates-y01u2Zis.js} +1 -1
  28. package/src/assets/web-panel/assets/VideoEditing-BA1N-5kq.css +1 -0
  29. package/src/assets/web-panel/assets/VideoEditing-B_nPKw6B.js +1 -0
  30. package/src/assets/web-panel/assets/{Wallet-PQoSpN_P.js → Wallet-CsRgnjJY.js} +1 -1
  31. package/src/assets/web-panel/assets/{WebAuthn-BcuyQ4Lr.js → WebAuthn-DWoR5ADp.js} +1 -1
  32. package/src/assets/web-panel/assets/{WorkflowEditor-C-SvXbHW.js → WorkflowEditor-DBJhFPMN.js} +1 -1
  33. package/src/assets/web-panel/assets/{antd-DEjZPGMj.js → antd-Dh2t0vGq.js} +84 -84
  34. package/src/assets/web-panel/assets/index-tN-8TosE.js +2 -0
  35. package/src/assets/web-panel/assets/{markdown-CusdXFxb.js → markdown-CBnGGMzE.js} +1 -1
  36. package/src/assets/web-panel/index.html +2 -2
  37. package/src/commands/agent.js +20 -0
  38. package/src/commands/mcp.js +86 -4
  39. package/src/commands/memory.js +85 -4
  40. package/src/commands/sandbox.js +80 -6
  41. package/src/commands/serve.js +10 -0
  42. package/src/commands/session.js +250 -0
  43. package/src/commands/stream.js +75 -0
  44. package/src/commands/video.js +363 -0
  45. package/src/gateways/http/envelope-http-server.js +194 -0
  46. package/src/gateways/ws/message-dispatcher.js +123 -0
  47. package/src/gateways/ws/session-core-protocol.js +427 -0
  48. package/src/gateways/ws/session-protocol.js +42 -1
  49. package/src/gateways/ws/video-protocol.js +230 -0
  50. package/src/gateways/ws/ws-server.js +72 -0
  51. package/src/gateways/ws/ws-session-gateway.js +7 -3
  52. package/src/harness/jsonl-session-store.js +17 -9
  53. package/src/index.js +8 -0
  54. package/src/lib/agent-stream.js +63 -0
  55. package/src/lib/chat-core.js +183 -6
  56. package/src/lib/cowork/ab-comparator-cli.js +44 -23
  57. package/src/lib/cowork/agent-group-runner.js +145 -0
  58. package/src/lib/cowork/debate-review-cli.js +47 -25
  59. package/src/lib/cowork/project-style-analyzer-cli.js +34 -7
  60. package/src/lib/interaction-adapter.js +59 -1
  61. package/src/lib/jsonl-session-store.js +2 -0
  62. package/src/lib/memory-injection.js +90 -0
  63. package/src/lib/provider-stream.js +120 -0
  64. package/src/lib/sandbox-v2.js +198 -3
  65. package/src/lib/session-consolidator.js +125 -0
  66. package/src/lib/session-core-singletons.js +56 -0
  67. package/src/lib/session-tail.js +128 -0
  68. package/src/lib/session-usage.js +166 -0
  69. package/src/lib/shell-approval.js +96 -0
  70. package/src/lib/ws-chat-handler.js +3 -0
  71. package/src/repl/agent-repl.js +271 -6
  72. package/src/repl/chat-repl.js +87 -100
  73. package/src/runtime/agent-core.js +98 -15
  74. package/src/runtime/agent-runtime.js +105 -3
  75. package/src/runtime/policies/agent-policy.js +10 -0
  76. package/src/skills/video-editing/SKILL.md +46 -0
  77. package/src/skills/video-editing/beat-snap.js +127 -0
  78. package/src/skills/video-editing/extractors/audio-extractor.js +212 -0
  79. package/src/skills/video-editing/extractors/subtitle-extractor.js +90 -0
  80. package/src/skills/video-editing/extractors/video-extractor.js +137 -0
  81. package/src/skills/video-editing/parallel-orchestrator.js +212 -0
  82. package/src/skills/video-editing/pipeline.js +480 -0
  83. package/src/skills/video-editing/prompts/aesthetic-analysis.md +21 -0
  84. package/src/skills/video-editing/prompts/audio-segment.md +15 -0
  85. package/src/skills/video-editing/prompts/character-identify.md +19 -0
  86. package/src/skills/video-editing/prompts/dense-caption.md +20 -0
  87. package/src/skills/video-editing/prompts/editor-system.md +29 -0
  88. package/src/skills/video-editing/prompts/hook-dialogue.md +17 -0
  89. package/src/skills/video-editing/prompts/protagonist-detect.md +20 -0
  90. package/src/skills/video-editing/prompts/scene-caption.md +16 -0
  91. package/src/skills/video-editing/prompts/shot-caption.md +25 -0
  92. package/src/skills/video-editing/prompts/shot-plan.md +28 -0
  93. package/src/skills/video-editing/prompts/structure-proposal.md +16 -0
  94. package/src/skills/video-editing/prompts/vlog-scene-caption.md +18 -0
  95. package/src/skills/video-editing/render/audio-mix.js +128 -0
  96. package/src/skills/video-editing/render/ffmpeg-concat.js +45 -0
  97. package/src/skills/video-editing/render/ffmpeg-extract.js +67 -0
  98. package/src/skills/video-editing/reviewer.js +161 -0
  99. package/src/skills/video-editing/tools/commit.js +108 -0
  100. package/src/skills/video-editing/tools/review-clip.js +46 -0
  101. package/src/skills/video-editing/tools/semantic-retrieval.js +56 -0
  102. package/src/skills/video-editing/tools/shot-trimming.js +73 -0
  103. package/src/assets/web-panel/assets/Analytics-B4OM8S8X.css +0 -1
  104. package/src/assets/web-panel/assets/Analytics-DgypYeUB.js +0 -3
  105. package/src/assets/web-panel/assets/AppLayout-Bzf3mSZI.js +0 -1
  106. package/src/assets/web-panel/assets/AppLayout-DQyDwGut.css +0 -1
  107. package/src/assets/web-panel/assets/index-CwvzTTw_.js +0 -2
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Video Editing Protocol — WebSocket routes for cc ui video editing.
3
+ *
4
+ * Follows the same pattern as session-core-protocol.js:
5
+ * - Request/response handlers return { ok, ... } or { ok: false, error }
6
+ * - Streaming handlers take (message, sender, signal) and emit stream.event
7
+ *
8
+ * Route keys use dot-case: video.assets.list, video.deconstruct, etc.
9
+ */
10
+
11
+ function ok(data = {}) {
12
+ return { ok: true, ...data };
13
+ }
14
+ function fail(code, message) {
15
+ return { ok: false, error: { code, message } };
16
+ }
17
+
18
+ function createPipeline(message) {
19
+ const { VideoPipeline } = require("../../skills/video-editing/pipeline.js");
20
+ return new VideoPipeline({
21
+ videoPath: message.videoPath,
22
+ audioPath: message.audioPath,
23
+ instruction: message.instruction || "",
24
+ outputPath: message.outputPath || "./output.mp4",
25
+ existingSrt: message.existingSrt,
26
+ fps: message.fps || 2,
27
+ mainCharacter: message.mainCharacter || "",
28
+ });
29
+ }
30
+
31
+ // ── Request/Response handlers ─────────────────────────────────
32
+
33
+ async function videoAssetsList(_message) {
34
+ const fs = require("fs").promises;
35
+ const path = require("path");
36
+ const base = process.env.APPDATA
37
+ ? path.join(
38
+ process.env.APPDATA,
39
+ "chainlesschain-desktop-vue",
40
+ ".chainlesschain",
41
+ "video-editing",
42
+ )
43
+ : path.join(process.env.HOME || "~", ".chainlesschain", "video-editing");
44
+ try {
45
+ const dirs = await fs.readdir(base);
46
+ const assets = [];
47
+ for (const d of dirs) {
48
+ const metaPath = path.join(base, d, "meta.json");
49
+ try {
50
+ const meta = JSON.parse(await fs.readFile(metaPath, "utf-8"));
51
+ const stat = await fs.stat(metaPath);
52
+ assets.push({
53
+ hash: d,
54
+ ...meta,
55
+ modifiedAt: stat.mtime.toISOString(),
56
+ });
57
+ } catch {
58
+ /* skip invalid */
59
+ }
60
+ }
61
+ return ok({ assets });
62
+ } catch {
63
+ return ok({ assets: [] });
64
+ }
65
+ }
66
+
67
+ // ── Streaming handlers ────────────────────────────────────────
68
+
69
+ async function videoDeconstruct(message, sender, signal) {
70
+ if (!message.videoPath) return fail("BAD_REQUEST", "videoPath required");
71
+ const pipeline = createPipeline(message);
72
+ pipeline.on("event", (ev) => {
73
+ if (signal?.aborted) return;
74
+ sender({ type: "stream.event", event: ev });
75
+ });
76
+ try {
77
+ const dir = await pipeline.deconstruct();
78
+ return ok({
79
+ assetDir: dir,
80
+ hash: dir.split(/[/\\]/).pop(),
81
+ });
82
+ } catch (err) {
83
+ return fail("DECONSTRUCT_ERROR", err.message);
84
+ }
85
+ }
86
+
87
+ async function videoPlan(message, sender, signal) {
88
+ if (!message.assetDir && !message.assetHash)
89
+ return fail("BAD_REQUEST", "assetDir or assetHash required");
90
+ const pipeline = createPipeline(message);
91
+ if (message.assetDir) pipeline.cacheDir = message.assetDir;
92
+ pipeline.on("event", (ev) => {
93
+ if (signal?.aborted) return;
94
+ sender({ type: "stream.event", event: ev });
95
+ });
96
+ try {
97
+ const plan = await pipeline.plan(message.assetDir);
98
+ return ok({ shotPlan: plan });
99
+ } catch (err) {
100
+ return fail("PLAN_ERROR", err.message);
101
+ }
102
+ }
103
+
104
+ async function videoAssemble(message, sender, signal) {
105
+ if (!message.assetDir) return fail("BAD_REQUEST", "assetDir required");
106
+ if (!message.shotPlan) return fail("BAD_REQUEST", "shotPlan required");
107
+ const pipeline = createPipeline(message);
108
+ pipeline.cacheDir = message.assetDir;
109
+ pipeline.on("event", (ev) => {
110
+ if (signal?.aborted) return;
111
+ sender({ type: "stream.event", event: ev });
112
+ });
113
+ try {
114
+ const points = await pipeline.assemble(message.shotPlan, message.assetDir);
115
+ return ok({ shotPoint: points });
116
+ } catch (err) {
117
+ return fail("ASSEMBLE_ERROR", err.message);
118
+ }
119
+ }
120
+
121
+ async function videoRender(message, sender, signal) {
122
+ if (!message.videoPath) return fail("BAD_REQUEST", "videoPath required");
123
+ if (!message.shotPoint) return fail("BAD_REQUEST", "shotPoint required");
124
+ const pipeline = createPipeline(message);
125
+ pipeline.on("event", (ev) => {
126
+ if (signal?.aborted) return;
127
+ sender({ type: "stream.event", event: ev });
128
+ });
129
+ try {
130
+ const outPath = await pipeline.render(message.shotPoint, message.assetDir);
131
+ return ok({ outputPath: outPath });
132
+ } catch (err) {
133
+ return fail("RENDER_ERROR", err.message);
134
+ }
135
+ }
136
+
137
+ async function videoEdit(message, sender, signal) {
138
+ if (!message.videoPath) return fail("BAD_REQUEST", "videoPath required");
139
+ const pipeline = createPipeline(message);
140
+ pipeline.on("event", (ev) => {
141
+ if (signal?.aborted) return;
142
+ sender({ type: "stream.event", event: ev });
143
+ });
144
+ try {
145
+ const result = await pipeline.run();
146
+ return ok({
147
+ assetDir: result.assetDir,
148
+ shotPlan: result.shotPlan,
149
+ shotPoints: result.shotPoints,
150
+ outputPath: result.outputPath,
151
+ });
152
+ } catch (err) {
153
+ return fail("EDIT_ERROR", err.message);
154
+ }
155
+ }
156
+
157
+ // ── Exports ───────────────────────────────────────────────────
158
+
159
+ export const VIDEO_STREAMING_HANDLERS = {
160
+ "video.deconstruct": videoDeconstruct,
161
+ "video.plan": videoPlan,
162
+ "video.assemble": videoAssemble,
163
+ "video.render": videoRender,
164
+ "video.edit": videoEdit,
165
+ };
166
+
167
+ export const VIDEO_HANDLERS = {
168
+ "video.assets.list": videoAssetsList,
169
+ };
170
+
171
+ /**
172
+ * Attach video routes to the dispatcher's routes object.
173
+ * Same pattern as attachSessionCoreRoutes.
174
+ */
175
+ export function attachVideoRoutes(routes, server) {
176
+ for (const [type, handler] of Object.entries(VIDEO_HANDLERS)) {
177
+ routes[type] = async (message, ws) => {
178
+ try {
179
+ const result = await handler(message);
180
+ server._send(ws, {
181
+ id: message.id,
182
+ type: `${type}.response`,
183
+ ...result,
184
+ });
185
+ } catch (err) {
186
+ server._send(ws, {
187
+ id: message.id,
188
+ type: "error",
189
+ code: "VIDEO_ERROR",
190
+ message: err?.message || String(err),
191
+ });
192
+ }
193
+ };
194
+ }
195
+
196
+ for (const [type, handler] of Object.entries(VIDEO_STREAMING_HANDLERS)) {
197
+ routes[type] = async (message, ws) => {
198
+ const ac = new AbortController();
199
+ const cancelKey = message.id;
200
+ if (cancelKey) {
201
+ const cancelHandler = (msg) => {
202
+ if (msg.type === "cancel" && msg.id === cancelKey) ac.abort();
203
+ };
204
+ server._cancelHandlers?.set(cancelKey, cancelHandler);
205
+ }
206
+ const sender = (ev) => {
207
+ server._send(ws, { id: message.id, ...ev });
208
+ };
209
+ try {
210
+ const result = await handler(message, sender, ac.signal);
211
+ server._send(ws, {
212
+ id: message.id,
213
+ type: `${type}.end`,
214
+ ...result,
215
+ });
216
+ } catch (err) {
217
+ server._send(ws, {
218
+ id: message.id,
219
+ type: `${type}.end`,
220
+ ok: false,
221
+ error: { code: "STREAM_ERROR", message: err?.message || String(err) },
222
+ });
223
+ } finally {
224
+ server._cancelHandlers?.delete(cancelKey);
225
+ }
226
+ };
227
+ }
228
+
229
+ return routes;
230
+ }
@@ -15,6 +15,11 @@ import { spawn } from "node:child_process";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { dirname, join } from "node:path";
17
17
  import { WebSocketServer } from "ws";
18
+ import {
19
+ createEnvelope,
20
+ envelopeFromStreamEvent,
21
+ validateEnvelope,
22
+ } from "@chainlesschain/session-core";
18
23
  import { createTaskRecord } from "../../runtime/contracts/task-record.js";
19
24
  import {
20
25
  RUNTIME_EVENTS,
@@ -143,6 +148,9 @@ export class ChainlessChainWSServer extends EventEmitter {
143
148
  this.maxConnections = options.maxConnections || 10;
144
149
  this.timeout = options.timeout || 30000;
145
150
 
151
+ /** Optional Phase-5 envelope bus for fan-out to hosted HTTP SSE. */
152
+ this.envelopeBus = options.envelopeBus || null;
153
+
146
154
  /** @type {WebSocketServer|null} */
147
155
  this.wss = null;
148
156
 
@@ -803,4 +811,68 @@ export class ChainlessChainWSServer extends EventEmitter {
803
811
  }
804
812
  }
805
813
  }
814
+
815
+ /**
816
+ * Send a Phase-5 service envelope to a single client.
817
+ * Accepts either a pre-built envelope or `{ type, sessionId, runId, requestId, payload }`.
818
+ * Falls back to legacy `_send` shape if envelope construction fails so callers
819
+ * never lose messages because of a contract bug.
820
+ *
821
+ * @param {WebSocket} ws
822
+ * @param {object} envOrSpec
823
+ */
824
+ sendEnvelope(ws, envOrSpec) {
825
+ let env = envOrSpec;
826
+ if (!env || typeof env !== "object") return;
827
+ if (!("v" in env)) {
828
+ try {
829
+ env = createEnvelope(envOrSpec);
830
+ } catch (_e) {
831
+ this._send(ws, envOrSpec);
832
+ return;
833
+ }
834
+ }
835
+ const errors = validateEnvelope(env);
836
+ if (errors.length) {
837
+ this._send(ws, envOrSpec);
838
+ return;
839
+ }
840
+ this._send(ws, env);
841
+ }
842
+
843
+ /**
844
+ * Broadcast a Phase-5 service envelope to all authenticated clients.
845
+ */
846
+ broadcastEnvelope(envOrSpec) {
847
+ let env = envOrSpec;
848
+ if (!("v" in (env || {}))) {
849
+ try {
850
+ env = createEnvelope(envOrSpec);
851
+ } catch (_e) {
852
+ this._broadcast(envOrSpec);
853
+ return;
854
+ }
855
+ }
856
+ this._broadcast(env);
857
+ if (this.envelopeBus && env && env.sessionId) {
858
+ try {
859
+ this.envelopeBus.publish(env.sessionId, env);
860
+ } catch (_e) {
861
+ // HTTP fan-out must never break the WS path.
862
+ }
863
+ }
864
+ }
865
+
866
+ /**
867
+ * Adapt a StreamRouter event ({type:"token", content:"..."} etc.) into a
868
+ * Phase-5 run.* envelope and send it to a single client.
869
+ */
870
+ sendStreamEnvelope(ws, streamEvent, ctx) {
871
+ try {
872
+ const env = envelopeFromStreamEvent(streamEvent, ctx);
873
+ this._send(ws, env);
874
+ } catch (_e) {
875
+ this._send(ws, streamEvent);
876
+ }
877
+ }
806
878
  }
@@ -93,6 +93,8 @@ export class WSSessionManager {
93
93
  this.trustedMcpServers = createTrustedMcpServerMap(
94
94
  options.mcpServerRegistry || null,
95
95
  );
96
+ this.defaultSystemPromptExtension =
97
+ options.defaultSystemPromptExtension || null;
96
98
 
97
99
  /** @type {Map<string, Session>} */
98
100
  this.sessions = new Map();
@@ -320,9 +322,11 @@ export class WSSessionManager {
320
322
  // Build initial system prompt (includes persona + rules.md)
321
323
  let systemPrompt = buildSystemPrompt(projectRoot);
322
324
 
323
- // Append optional extension (e.g. cowork template instructions)
324
- if (options.systemPromptExtension) {
325
- systemPrompt += "\n\n" + options.systemPromptExtension;
325
+ // Append optional extension (e.g. cowork template instructions, or bundle AGENTS.md)
326
+ const promptExtension =
327
+ options.systemPromptExtension || this.defaultSystemPromptExtension;
328
+ if (promptExtension) {
329
+ systemPrompt += "\n\n" + promptExtension;
326
330
  }
327
331
 
328
332
  const messages = [{ role: "system", content: systemPrompt }];
@@ -21,10 +21,14 @@ function getSessionsDir() {
21
21
  return dir;
22
22
  }
23
23
 
24
- function sessionPath(sessionId) {
24
+ export function sessionPath(sessionId) {
25
25
  return join(getSessionsDir(), `${sessionId}.jsonl`);
26
26
  }
27
27
 
28
+ export function appendTokenUsage(sessionId, usage) {
29
+ appendEvent(sessionId, "token_usage", usage || {});
30
+ }
31
+
28
32
  export function appendEvent(sessionId, type, data) {
29
33
  const line = JSON.stringify({ type, timestamp: Date.now(), data }) + "\n";
30
34
  appendFileSync(sessionPath(sessionId), line, "utf-8");
@@ -356,7 +360,10 @@ function performLegacySessionMigration(sourcePath, options) {
356
360
  }
357
361
 
358
362
  const validation = validateJsonlSession(sessionId);
359
- if (!validation.valid || validation.messageCount !== legacy.messages.length) {
363
+ if (
364
+ !validation.valid ||
365
+ validation.messageCount !== legacy.messages.length
366
+ ) {
360
367
  throw new Error(
361
368
  `post-migration validation failed for ${sessionId} (${validation.messageCount}/${legacy.messages.length} messages)`,
362
369
  );
@@ -408,7 +415,8 @@ function normalizeLegacySession(payload, fallbackId) {
408
415
  return {
409
416
  id: payload?.id || fallbackId || `session-${Date.now()}`,
410
417
  meta: {
411
- title: payload?.title || payload?.name || fallbackId || "Migrated Session",
418
+ title:
419
+ payload?.title || payload?.name || fallbackId || "Migrated Session",
412
420
  provider: payload?.provider || "",
413
421
  model: payload?.model || "",
414
422
  },
@@ -425,11 +433,7 @@ function normalizeLegacyMessage(message) {
425
433
 
426
434
  const role = message.role || message.sender || message.type || "user";
427
435
  const content =
428
- message.content ??
429
- message.text ??
430
- message.message ??
431
- message.result ??
432
- "";
436
+ message.content ?? message.text ?? message.message ?? message.result ?? "";
433
437
 
434
438
  return {
435
439
  role,
@@ -445,7 +449,11 @@ function appendLegacyMessage(sessionId, message) {
445
449
  appendAssistantMessage(sessionId, message.content);
446
450
  break;
447
451
  case "tool":
448
- appendToolResult(sessionId, message.tool || "legacy-tool", message.content);
452
+ appendToolResult(
453
+ sessionId,
454
+ message.tool || "legacy-tool",
455
+ message.content,
456
+ );
449
457
  break;
450
458
  case "system":
451
459
  appendEvent(sessionId, "system", {
package/src/index.js CHANGED
@@ -14,6 +14,7 @@ import { registerChatCommand } from "./commands/chat.js";
14
14
  import { registerAskCommand } from "./commands/ask.js";
15
15
  import { registerLlmCommand } from "./commands/llm.js";
16
16
  import { registerAgentCommand } from "./commands/agent.js";
17
+ import { registerStreamCommand } from "./commands/stream.js";
17
18
  import { registerSkillCommand } from "./commands/skill.js";
18
19
  import { registerSearchCommand } from "./commands/search.js";
19
20
  import { registerTokensCommand } from "./commands/tokens.js";
@@ -90,6 +91,9 @@ import { registerServeCommand } from "./commands/serve.js";
90
91
  // Web UI
91
92
  import { registerUiCommand } from "./commands/ui.js";
92
93
 
94
+ // Video Editing Agent (CutClaw-inspired)
95
+ import { registerVideoCommand } from "./commands/video.js";
96
+
93
97
  // Orchestration Layer: ChainlessChain → Claude Code/Codex agents → CI → Notify
94
98
  import { registerOrchestrateCommand } from "./commands/orchestrate.js";
95
99
 
@@ -126,6 +130,7 @@ export function createProgram() {
126
130
  registerAskCommand(program);
127
131
  registerLlmCommand(program);
128
132
  registerAgentCommand(program);
133
+ registerStreamCommand(program);
129
134
  registerSkillCommand(program);
130
135
 
131
136
  // Phase 1: AI intelligence layer
@@ -215,5 +220,8 @@ export function createProgram() {
215
220
  // Orchestration Layer
216
221
  registerOrchestrateCommand(program);
217
222
 
223
+ // Video Editing Agent
224
+ registerVideoCommand(program);
225
+
218
226
  return program;
219
227
  }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * agent-stream — thin wrapper over session-core StreamRouter for REPL output.
3
+ *
4
+ * Managed Agents parity Phase G item #2: every agent response — streaming or
5
+ * not — goes through a uniform StreamEvent protocol. Non-streaming providers
6
+ * are "fake-streamed" as `start → message → end` so downstream consumers
7
+ * (REPL, WS IPC, tests) only need one event contract.
8
+ *
9
+ * Exports:
10
+ * - streamAgentResponse(source, { onEvent, onToken, noStream, writer })
11
+ * Returns `{ text, events, errored, error }`. When `noStream` is true,
12
+ * events are still collected but no per-event side effects are emitted
13
+ * beyond the final writer flush.
14
+ */
15
+
16
+ import {
17
+ StreamRouter,
18
+ STREAM_EVENT as EVENT,
19
+ } from "@chainlesschain/session-core";
20
+
21
+ export async function streamAgentResponse(source, options = {}) {
22
+ const {
23
+ onEvent = null,
24
+ onToken = null,
25
+ noStream = false,
26
+ writer = null,
27
+ } = options;
28
+
29
+ let writtenByToken = false;
30
+
31
+ const router = new StreamRouter({
32
+ onEvent: (ev) => {
33
+ try {
34
+ if (onEvent) onEvent(ev);
35
+ } catch {
36
+ /* swallow */
37
+ }
38
+ if (noStream || !writer) return;
39
+ if (ev.type === EVENT.TOKEN && typeof ev.text === "string") {
40
+ writer(ev.text);
41
+ writtenByToken = true;
42
+ }
43
+ },
44
+ onToken: (t) => {
45
+ try {
46
+ if (onToken) onToken(t);
47
+ } catch {
48
+ /* swallow */
49
+ }
50
+ },
51
+ });
52
+
53
+ const result = await router.collect(source);
54
+
55
+ // Flush MESSAGE content if we haven't already written via TOKEN events.
56
+ if (writer && !noStream && !writtenByToken && result.text) {
57
+ writer(result.text);
58
+ }
59
+
60
+ return result;
61
+ }
62
+
63
+ export { EVENT };