chainlesschain 0.47.6 → 0.47.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/Analytics-BFI7jbwM.css +1 -0
- package/src/assets/web-panel/assets/Analytics-DQ135mAd.js +3 -0
- package/src/assets/web-panel/assets/AppLayout-6SPt_8Y_.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-BFJ-Fofn.css +1 -0
- package/src/assets/web-panel/assets/{Backup-Ba9UybpT.js → Backup-DbVRG5vE.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BwXskT21.js → Chat-wVhrFK9C.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-UmOe7qvE.js → Cowork-lOC25IW2.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-JHS-rc-4.js → Cron-3P0eVLTV.js} +1 -1
- package/src/assets/web-panel/assets/{Dashboard-B95cMCO7.js → Dashboard-Br7kCwKJ.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CSYO0_zk.js → Git-CrDCcBig.js} +2 -2
- package/src/assets/web-panel/assets/{Logs-Hxw_K0km.js → Logs-BfTE8urP.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DIE75TrB.js → McpTools-CsGIijNe.js} +1 -1
- package/src/assets/web-panel/assets/{Memory-C4KVnLlp.js → Memory-BXX_yMKJ.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DuzrHMAk.js → Notes-DU6Vf2cL.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DTq6uF82.js → Organization-Bny6yOPV.js} +4 -4
- package/src/assets/web-panel/assets/{P2P-C0hjlhsR.js → P2P-BxFZ1Bit.js} +2 -2
- package/src/assets/web-panel/assets/{Permissions-Ec0NH-xC.js → Permissions-B1j3Mtms.js} +3 -3
- package/src/assets/web-panel/assets/{Projects-U8D0asCS.js → Projects-D-CGscDu.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BngtTLvJ.js → Providers-r6NaBYMf.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B9NbwCKM.js → RssFeed-D7b68C5q.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BL5Rkr1T.js → Security-MJfKv0EJ.js} +3 -3
- package/src/assets/web-panel/assets/{Services-D4MJzLld.js → Services-Yb_Q1V3d.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CQTOMDwF.js → Skills-DLTHcH5T.js} +1 -1
- package/src/assets/web-panel/assets/{Tasks-DepbJMnL.js → Tasks-CqycpPjS.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-C24PVZPu.js → Templates-y01u2Zis.js} +1 -1
- package/src/assets/web-panel/assets/VideoEditing-BA1N-5kq.css +1 -0
- package/src/assets/web-panel/assets/VideoEditing-B_nPKw6B.js +1 -0
- package/src/assets/web-panel/assets/{Wallet-PQoSpN_P.js → Wallet-CsRgnjJY.js} +1 -1
- package/src/assets/web-panel/assets/{WebAuthn-BcuyQ4Lr.js → WebAuthn-DWoR5ADp.js} +1 -1
- package/src/assets/web-panel/assets/{WorkflowEditor-C-SvXbHW.js → WorkflowEditor-DBJhFPMN.js} +1 -1
- package/src/assets/web-panel/assets/{antd-DEjZPGMj.js → antd-Dh2t0vGq.js} +84 -84
- package/src/assets/web-panel/assets/index-tN-8TosE.js +2 -0
- package/src/assets/web-panel/assets/{markdown-CusdXFxb.js → markdown-CBnGGMzE.js} +1 -1
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/agent.js +20 -0
- package/src/commands/mcp.js +86 -4
- package/src/commands/memory.js +85 -4
- package/src/commands/sandbox.js +80 -6
- package/src/commands/serve.js +10 -0
- package/src/commands/session.js +250 -0
- package/src/commands/stream.js +75 -0
- package/src/commands/video.js +363 -0
- package/src/gateways/http/envelope-http-server.js +194 -0
- package/src/gateways/ws/message-dispatcher.js +123 -0
- package/src/gateways/ws/session-core-protocol.js +427 -0
- package/src/gateways/ws/session-protocol.js +42 -1
- package/src/gateways/ws/video-protocol.js +230 -0
- package/src/gateways/ws/ws-server.js +72 -0
- package/src/gateways/ws/ws-session-gateway.js +7 -3
- package/src/harness/jsonl-session-store.js +17 -9
- package/src/index.js +8 -0
- package/src/lib/agent-stream.js +63 -0
- package/src/lib/chat-core.js +183 -6
- package/src/lib/cowork/ab-comparator-cli.js +44 -23
- package/src/lib/cowork/agent-group-runner.js +145 -0
- package/src/lib/cowork/debate-review-cli.js +47 -25
- package/src/lib/cowork/project-style-analyzer-cli.js +34 -7
- package/src/lib/interaction-adapter.js +59 -1
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/memory-injection.js +90 -0
- package/src/lib/provider-stream.js +120 -0
- package/src/lib/sandbox-v2.js +198 -3
- package/src/lib/session-consolidator.js +125 -0
- package/src/lib/session-core-singletons.js +56 -0
- package/src/lib/session-tail.js +128 -0
- package/src/lib/session-usage.js +166 -0
- package/src/lib/shell-approval.js +96 -0
- package/src/lib/ws-chat-handler.js +3 -0
- package/src/repl/agent-repl.js +294 -6
- package/src/repl/chat-repl.js +87 -100
- package/src/runtime/agent-core.js +98 -15
- package/src/runtime/agent-runtime.js +105 -3
- package/src/runtime/policies/agent-policy.js +10 -0
- package/src/skills/video-editing/SKILL.md +46 -0
- package/src/skills/video-editing/beat-snap.js +127 -0
- package/src/skills/video-editing/extractors/audio-extractor.js +212 -0
- package/src/skills/video-editing/extractors/subtitle-extractor.js +90 -0
- package/src/skills/video-editing/extractors/video-extractor.js +137 -0
- package/src/skills/video-editing/parallel-orchestrator.js +212 -0
- package/src/skills/video-editing/pipeline.js +480 -0
- package/src/skills/video-editing/prompts/aesthetic-analysis.md +21 -0
- package/src/skills/video-editing/prompts/audio-segment.md +15 -0
- package/src/skills/video-editing/prompts/character-identify.md +19 -0
- package/src/skills/video-editing/prompts/dense-caption.md +20 -0
- package/src/skills/video-editing/prompts/editor-system.md +29 -0
- package/src/skills/video-editing/prompts/hook-dialogue.md +17 -0
- package/src/skills/video-editing/prompts/protagonist-detect.md +20 -0
- package/src/skills/video-editing/prompts/scene-caption.md +16 -0
- package/src/skills/video-editing/prompts/shot-caption.md +25 -0
- package/src/skills/video-editing/prompts/shot-plan.md +28 -0
- package/src/skills/video-editing/prompts/structure-proposal.md +16 -0
- package/src/skills/video-editing/prompts/vlog-scene-caption.md +18 -0
- package/src/skills/video-editing/render/audio-mix.js +128 -0
- package/src/skills/video-editing/render/ffmpeg-concat.js +45 -0
- package/src/skills/video-editing/render/ffmpeg-extract.js +67 -0
- package/src/skills/video-editing/reviewer.js +161 -0
- package/src/skills/video-editing/tools/commit.js +108 -0
- package/src/skills/video-editing/tools/review-clip.js +46 -0
- package/src/skills/video-editing/tools/semantic-retrieval.js +56 -0
- package/src/skills/video-editing/tools/shot-trimming.js +73 -0
- package/src/assets/web-panel/assets/Analytics-B4OM8S8X.css +0 -1
- package/src/assets/web-panel/assets/Analytics-DgypYeUB.js +0 -3
- package/src/assets/web-panel/assets/AppLayout-Bzf3mSZI.js +0 -1
- package/src/assets/web-panel/assets/AppLayout-DQyDwGut.css +0 -1
- 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
|
-
|
|
325
|
-
|
|
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 (
|
|
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:
|
|
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(
|
|
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 };
|