@ai-setting/roy-plugin-task-show 2.5.0 → 2.5.2
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/dist/chat-panel-html.d.ts +76 -0
- package/dist/chat-panel-html.d.ts.map +1 -0
- package/dist/chat-panel-html.js +117 -0
- package/dist/chat-panel-html.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +223 -43
- package/dist/server.js.map +1 -1
- package/dist/task-chat-context-builder.d.ts +94 -0
- package/dist/task-chat-context-builder.d.ts.map +1 -0
- package/dist/task-chat-context-builder.js +130 -0
- package/dist/task-chat-context-builder.js.map +1 -0
- package/dist/task-session-store.d.ts.map +1 -1
- package/dist/task-session-store.js +5 -0
- package/dist/task-session-store.js.map +1 -1
- package/package.json +4 -2
- package/public/chat-panel.js +502 -197
- package/public/dompurify.min.js +3 -0
- package/public/index.html +11 -0
- package/public/markdown-renderer.js +176 -0
- package/public/marked.min.js +6 -0
- package/public/style.css +175 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Chat panel HTML — extracted v2.5.1 for cross-page reuse.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this module exists (v2.5.1 REQ-2 fix + v2.5 REQ-3 extension)
|
|
5
|
+
*
|
|
6
|
+
* v2.5.0 introduced the home-page chat panel via `renderChatPanel()`
|
|
7
|
+
* defined inside `src/server.ts`. Only `renderIndexPage` (the legacy
|
|
8
|
+
* home page, hit when no `TaskSessionStore` is configured) actually
|
|
9
|
+
* called it. The plugin always wires a `TaskSessionStore`, so the
|
|
10
|
+
* production home page (`renderSessionForestPage` in
|
|
11
|
+
* `src/task-session-store.ts`) shipped **without** the chat panel.
|
|
12
|
+
*
|
|
13
|
+
* The pre-existing `home-chat-v25.test.ts` constructed a server
|
|
14
|
+
* WITHOUT `sessionStore`, so the green test exercised the legacy
|
|
15
|
+
* path and the v2.5.0 bug slipped through.
|
|
16
|
+
*
|
|
17
|
+
* The v2.5.1 fix:
|
|
18
|
+
* 1. Extract `renderChatPanel()` into this shared module.
|
|
19
|
+
* 2. Have BOTH `renderIndexPage` (legacy) AND
|
|
20
|
+
* `_renderSessionForestPageImpl` (production home) import it.
|
|
21
|
+
* 3. Both pages also need `<script src="/static/chat-panel.js"
|
|
22
|
+
* defer></script>` for the JS to actually wire up the form.
|
|
23
|
+
*
|
|
24
|
+
* v2.5 REQ-3 extends `renderChatPanel()` to accept an `opts` bag
|
|
25
|
+
* with `scope` ("home" | "task") and `taskId` (when scope=task).
|
|
26
|
+
* The HTML carries `data-chat-scope` and `data-task-id` hooks that
|
|
27
|
+
* the chat-panel.js factory picks up to route to `/api/chat/...`
|
|
28
|
+
* vs `/api/task-chat/...` and to select the right localStorage key.
|
|
29
|
+
*
|
|
30
|
+
* Extracting to a dedicated module avoids a circular import between
|
|
31
|
+
* `server.ts` ↔ `task-session-store.ts` (server already imports from
|
|
32
|
+
* task-session-store, and task-session-store previously imported
|
|
33
|
+
* nothing from server, but future plugin evolution may want
|
|
34
|
+
* session-store to depend on other server-side types — keeping
|
|
35
|
+
* chat-panel-html a leaf module is a safer contract).
|
|
36
|
+
*
|
|
37
|
+
* The asset contract pinned by `test/home-chat-v25.test.ts`,
|
|
38
|
+
* `test/chat-panel-session-forest-v251.test.ts` and
|
|
39
|
+
* `test/task-chat-v25.test.ts`:
|
|
40
|
+
* • `data-chat-shell` — the section root
|
|
41
|
+
* • `data-chat-scope` — "home" | "task" (read by the JS factory)
|
|
42
|
+
* • `data-task-id` — task id when scope=task
|
|
43
|
+
* • `data-chat-input` — the text input
|
|
44
|
+
* • `data-chat-send` — the submit button
|
|
45
|
+
* • `data-chat-form` — the `<form>` (used by the JS to bind Enter)
|
|
46
|
+
* • `data-chat-messages` — the message log container
|
|
47
|
+
* • `<script src="/static/chat-panel.js" defer></script>` — must
|
|
48
|
+
* follow the markup on the same page (defer is fine because the
|
|
49
|
+
* JS module polls for the DOM on load).
|
|
50
|
+
*
|
|
51
|
+
* Disabled gracefully: when the host didn't pass
|
|
52
|
+
* `chatOptions.enabled` we render the empty section so SSR layout
|
|
53
|
+
* stays predictable; the JS short-circuits and the `/api/chat/*`
|
|
54
|
+
* endpoints return 503.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* Render the home-page chat panel markup.
|
|
58
|
+
*
|
|
59
|
+
* Markup is intentionally minimal — the heavy lifting (EventSource
|
|
60
|
+
* handling, message rendering, sessionId persistence) lives in
|
|
61
|
+
* `public/chat-panel.js`. The HTML below carries `data-*` hooks that
|
|
62
|
+
* the JS module targets; the contracts are pinned by
|
|
63
|
+
* `test/home-chat-v25.test.ts`,
|
|
64
|
+
* `test/chat-panel-session-forest-v251.test.ts` and
|
|
65
|
+
* `test/task-chat-v25.test.ts`.
|
|
66
|
+
*
|
|
67
|
+
* @param opts.scope "home" (default) | "task" — controls the
|
|
68
|
+
* API endpoint the JS targets and the UI badge.
|
|
69
|
+
* @param opts.taskId Required when scope === "task". Used to label
|
|
70
|
+
* the panel and bind `data-task-id`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function renderChatPanel(opts?: {
|
|
73
|
+
scope?: "home" | "task";
|
|
74
|
+
taskId?: number;
|
|
75
|
+
}): string;
|
|
76
|
+
//# sourceMappingURL=chat-panel-html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-panel-html.d.ts","sourceRoot":"","sources":["../src/chat-panel-html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CA6C/F"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Chat panel HTML — extracted v2.5.1 for cross-page reuse.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this module exists (v2.5.1 REQ-2 fix + v2.5 REQ-3 extension)
|
|
5
|
+
*
|
|
6
|
+
* v2.5.0 introduced the home-page chat panel via `renderChatPanel()`
|
|
7
|
+
* defined inside `src/server.ts`. Only `renderIndexPage` (the legacy
|
|
8
|
+
* home page, hit when no `TaskSessionStore` is configured) actually
|
|
9
|
+
* called it. The plugin always wires a `TaskSessionStore`, so the
|
|
10
|
+
* production home page (`renderSessionForestPage` in
|
|
11
|
+
* `src/task-session-store.ts`) shipped **without** the chat panel.
|
|
12
|
+
*
|
|
13
|
+
* The pre-existing `home-chat-v25.test.ts` constructed a server
|
|
14
|
+
* WITHOUT `sessionStore`, so the green test exercised the legacy
|
|
15
|
+
* path and the v2.5.0 bug slipped through.
|
|
16
|
+
*
|
|
17
|
+
* The v2.5.1 fix:
|
|
18
|
+
* 1. Extract `renderChatPanel()` into this shared module.
|
|
19
|
+
* 2. Have BOTH `renderIndexPage` (legacy) AND
|
|
20
|
+
* `_renderSessionForestPageImpl` (production home) import it.
|
|
21
|
+
* 3. Both pages also need `<script src="/static/chat-panel.js"
|
|
22
|
+
* defer></script>` for the JS to actually wire up the form.
|
|
23
|
+
*
|
|
24
|
+
* v2.5 REQ-3 extends `renderChatPanel()` to accept an `opts` bag
|
|
25
|
+
* with `scope` ("home" | "task") and `taskId` (when scope=task).
|
|
26
|
+
* The HTML carries `data-chat-scope` and `data-task-id` hooks that
|
|
27
|
+
* the chat-panel.js factory picks up to route to `/api/chat/...`
|
|
28
|
+
* vs `/api/task-chat/...` and to select the right localStorage key.
|
|
29
|
+
*
|
|
30
|
+
* Extracting to a dedicated module avoids a circular import between
|
|
31
|
+
* `server.ts` ↔ `task-session-store.ts` (server already imports from
|
|
32
|
+
* task-session-store, and task-session-store previously imported
|
|
33
|
+
* nothing from server, but future plugin evolution may want
|
|
34
|
+
* session-store to depend on other server-side types — keeping
|
|
35
|
+
* chat-panel-html a leaf module is a safer contract).
|
|
36
|
+
*
|
|
37
|
+
* The asset contract pinned by `test/home-chat-v25.test.ts`,
|
|
38
|
+
* `test/chat-panel-session-forest-v251.test.ts` and
|
|
39
|
+
* `test/task-chat-v25.test.ts`:
|
|
40
|
+
* • `data-chat-shell` — the section root
|
|
41
|
+
* • `data-chat-scope` — "home" | "task" (read by the JS factory)
|
|
42
|
+
* • `data-task-id` — task id when scope=task
|
|
43
|
+
* • `data-chat-input` — the text input
|
|
44
|
+
* • `data-chat-send` — the submit button
|
|
45
|
+
* • `data-chat-form` — the `<form>` (used by the JS to bind Enter)
|
|
46
|
+
* • `data-chat-messages` — the message log container
|
|
47
|
+
* • `<script src="/static/chat-panel.js" defer></script>` — must
|
|
48
|
+
* follow the markup on the same page (defer is fine because the
|
|
49
|
+
* JS module polls for the DOM on load).
|
|
50
|
+
*
|
|
51
|
+
* Disabled gracefully: when the host didn't pass
|
|
52
|
+
* `chatOptions.enabled` we render the empty section so SSR layout
|
|
53
|
+
* stays predictable; the JS short-circuits and the `/api/chat/*`
|
|
54
|
+
* endpoints return 503.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* Render the home-page chat panel markup.
|
|
58
|
+
*
|
|
59
|
+
* Markup is intentionally minimal — the heavy lifting (EventSource
|
|
60
|
+
* handling, message rendering, sessionId persistence) lives in
|
|
61
|
+
* `public/chat-panel.js`. The HTML below carries `data-*` hooks that
|
|
62
|
+
* the JS module targets; the contracts are pinned by
|
|
63
|
+
* `test/home-chat-v25.test.ts`,
|
|
64
|
+
* `test/chat-panel-session-forest-v251.test.ts` and
|
|
65
|
+
* `test/task-chat-v25.test.ts`.
|
|
66
|
+
*
|
|
67
|
+
* @param opts.scope "home" (default) | "task" — controls the
|
|
68
|
+
* API endpoint the JS targets and the UI badge.
|
|
69
|
+
* @param opts.taskId Required when scope === "task". Used to label
|
|
70
|
+
* the panel and bind `data-task-id`.
|
|
71
|
+
*/
|
|
72
|
+
export function renderChatPanel(opts = {}) {
|
|
73
|
+
const scope = opts.scope ?? "home";
|
|
74
|
+
const taskId = opts.taskId;
|
|
75
|
+
const isTask = scope === "task" && typeof taskId === "number";
|
|
76
|
+
const ariaLabel = isTask ? `Task #${taskId} chat panel` : "Home chat panel";
|
|
77
|
+
const heading = isTask ? "💬 Task chat" : "💬 roy-agent chat";
|
|
78
|
+
const badge = isTask
|
|
79
|
+
? `<span class="chat-scope-badge" data-chat-scope-badge>Connected to task #${taskId}</span>`
|
|
80
|
+
: "";
|
|
81
|
+
const subline = isTask
|
|
82
|
+
? `Multi-turn conversation powered by <code>roy-agent act -s task-${taskId}</code>. The task context (title, status, recent operations) is auto-injected into every message.`
|
|
83
|
+
: `Multi-turn conversation powered by <code>roy-agent act -s <sessionId></code>.`;
|
|
84
|
+
const placeholder = isTask ? `Ask anything about task #${taskId}…` : "Type a message and press Enter…";
|
|
85
|
+
// Scope + taskId hooks the JS factory reads to pick the right
|
|
86
|
+
// endpoint and the right localStorage key.
|
|
87
|
+
const scopeAttrs = isTask
|
|
88
|
+
? ` data-chat-scope="task" data-task-id="${taskId}"`
|
|
89
|
+
: ` data-chat-scope="home"`;
|
|
90
|
+
const extraClass = isTask ? " chat-panel--task" : "";
|
|
91
|
+
return `
|
|
92
|
+
<section class="panel chat-panel${extraClass}"${scopeAttrs} data-chat-shell aria-label="${ariaLabel}">
|
|
93
|
+
<header class="chat-panel-header">
|
|
94
|
+
<h2>${heading} ${badge}</h2>
|
|
95
|
+
<p class="meta chat-panel-meta">${subline}</p>
|
|
96
|
+
</header>
|
|
97
|
+
<div class="chat-messages" data-chat-messages role="log" aria-live="polite" aria-relevant="additions">
|
|
98
|
+
<p class="chat-empty">No messages yet — ask away!</p>
|
|
99
|
+
</div>
|
|
100
|
+
<form class="chat-form" data-chat-form autocomplete="off">
|
|
101
|
+
<label class="visually-hidden" for="chat-input">Message</label>
|
|
102
|
+
<input
|
|
103
|
+
id="chat-input"
|
|
104
|
+
type="text"
|
|
105
|
+
class="chat-input"
|
|
106
|
+
data-chat-input
|
|
107
|
+
placeholder="${placeholder}"
|
|
108
|
+
autocomplete="off"
|
|
109
|
+
/>
|
|
110
|
+
<button type="submit" class="chat-send" data-chat-send aria-label="Send message">
|
|
111
|
+
Send
|
|
112
|
+
</button>
|
|
113
|
+
</form>
|
|
114
|
+
</section>
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=chat-panel-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-panel-html.js","sourceRoot":"","sources":["../src/chat-panel-html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,OAAqD,EAAE;IACrF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,MAAM,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC;IAC9D,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,MAAM,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC;IAC5E,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,mBAAmB,CAAC;IAC9D,MAAM,KAAK,GAAG,MAAM;QAClB,CAAC,CAAC,2EAA2E,MAAM,SAAS;QAC5F,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,OAAO,GAAG,MAAM;QACpB,CAAC,CAAC,kEAAkE,MAAM,mGAAmG;QAC7K,CAAC,CAAC,qFAAqF,CAAC;IAC1F,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,4BAA4B,MAAM,GAAG,CAAC,CAAC,CAAC,iCAAiC,CAAC;IACvG,8DAA8D;IAC9D,2CAA2C;IAC3C,MAAM,UAAU,GAAG,MAAM;QACvB,CAAC,CAAC,yCAAyC,MAAM,GAAG;QACpD,CAAC,CAAC,yBAAyB,CAAC;IAC9B,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;IAErD,OAAO;oCAC2B,UAAU,IAAI,UAAU,gCAAgC,SAAS;;YAEzF,OAAO,IAAI,KAAK;wCACY,OAAO;;;;;;;;;;;;uBAYxB,WAAW;;;;;;;;CAQjC,CAAC;AACF,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -202,6 +202,30 @@ export declare class TaskShowServer {
|
|
|
202
202
|
private handleChatMessage;
|
|
203
203
|
/** Abort an in-flight chat query for the given session. */
|
|
204
204
|
private handleChatAbort;
|
|
205
|
+
/**
|
|
206
|
+
* v2.5.0+ REQ-3: task-aware chat endpoint.
|
|
207
|
+
*
|
|
208
|
+
* Resolves the task by id (from the collector session OR the
|
|
209
|
+
* operations cache), builds an additionInfo block from the task
|
|
210
|
+
* context, wraps the user's message, and forwards it to the same
|
|
211
|
+
* ChatProcessManager REQ-2 already plumbs. The sessionId is the
|
|
212
|
+
* deterministic `task-${taskId}` so reloads land on the same
|
|
213
|
+
* SQLite-backed conversation.
|
|
214
|
+
*
|
|
215
|
+
* Returns 404 when the task is unknown — we deliberately do NOT
|
|
216
|
+
* answer off-topic questions for tasks that don't exist (which is
|
|
217
|
+
* the same behaviour REQ-2 already shows for unknown chat
|
|
218
|
+
* sessions, just gated by a task id instead of a random uuid).
|
|
219
|
+
*/
|
|
220
|
+
private handleTaskChatMessage;
|
|
221
|
+
/**
|
|
222
|
+
* Resolve the task context for a chat turn. Returns the
|
|
223
|
+
* TaskChatContextBuilder input or null when the task is unknown.
|
|
224
|
+
* Prefers the live collector session (rich `context.worktree` +
|
|
225
|
+
* current status); falls back to the operations cache envelope
|
|
226
|
+
* for tasks that have already been finalized.
|
|
227
|
+
*/
|
|
228
|
+
private resolveTaskChatContext;
|
|
205
229
|
private handleFileContent;
|
|
206
230
|
/**
|
|
207
231
|
* v2.0.0: GET /api/task/:id/file-tree — returns the git-tracked
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EACL,eAAe,EAChB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,cAAc,EACf,MAAM,uBAAuB,CAAC;AAkB/B,OAAO,EACL,gBAAgB,EAEjB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -37,6 +37,8 @@ import { buildSessionFileTree, } from "./file-tree.js";
|
|
|
37
37
|
import { renderToolCallDetail, } from "./tool-call-detail.js";
|
|
38
38
|
import { renderSessionForestPage, } from "./task-session-store.js";
|
|
39
39
|
import { ChatProcessManager } from "./chat-process-manager.js";
|
|
40
|
+
import { renderChatPanel } from "./chat-panel-html.js";
|
|
41
|
+
import { TaskChatContextBuilder } from "./task-chat-context-builder.js";
|
|
40
42
|
/** Upper bound of the TCP user port range. Probing never wraps past this. */
|
|
41
43
|
export const MAX_TCP_PORT = 65535;
|
|
42
44
|
/** Smallest legal TCP user port (avoid binding to well-known 0..1023). */
|
|
@@ -408,6 +410,24 @@ export class TaskShowServer {
|
|
|
408
410
|
this.handleChatAbort(sid, res);
|
|
409
411
|
return;
|
|
410
412
|
}
|
|
413
|
+
// v2.5.0+ REQ-3: /api/task-chat/:taskId/message — task-aware chat.
|
|
414
|
+
// Same SSE protocol as /api/chat/:id/message but the subprocess
|
|
415
|
+
// sees a `task-${taskId}` sessionId (deterministic, long-lived)
|
|
416
|
+
// and the user message is wrapped with an additionInfo block
|
|
417
|
+
// describing the task context. The taskId itself must exist in
|
|
418
|
+
// either the collector OR the operations cache — otherwise we
|
|
419
|
+
// 404 instead of silently answering off-topic.
|
|
420
|
+
const taskChatMatch = pathname.match(/^\/api\/task-chat\/([^/]+)\/message$/);
|
|
421
|
+
if (taskChatMatch) {
|
|
422
|
+
const rawId = decodeURIComponent(taskChatMatch[1]);
|
|
423
|
+
const numericId = Number(rawId);
|
|
424
|
+
if (!Number.isFinite(numericId)) {
|
|
425
|
+
this.text(res, 400, `Invalid task id: ${rawId}`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
await this.handleTaskChatMessage(numericId, req, res);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
411
431
|
// All remaining routes are GET/HEAD only.
|
|
412
432
|
if (method !== "GET" && method !== "HEAD") {
|
|
413
433
|
this.text(res, 405, "Method Not Allowed");
|
|
@@ -639,6 +659,184 @@ export class TaskShowServer {
|
|
|
639
659
|
this.chatManager.abort(sessionId);
|
|
640
660
|
this.json(res, 200, { sessionId, aborted: true });
|
|
641
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* v2.5.0+ REQ-3: task-aware chat endpoint.
|
|
664
|
+
*
|
|
665
|
+
* Resolves the task by id (from the collector session OR the
|
|
666
|
+
* operations cache), builds an additionInfo block from the task
|
|
667
|
+
* context, wraps the user's message, and forwards it to the same
|
|
668
|
+
* ChatProcessManager REQ-2 already plumbs. The sessionId is the
|
|
669
|
+
* deterministic `task-${taskId}` so reloads land on the same
|
|
670
|
+
* SQLite-backed conversation.
|
|
671
|
+
*
|
|
672
|
+
* Returns 404 when the task is unknown — we deliberately do NOT
|
|
673
|
+
* answer off-topic questions for tasks that don't exist (which is
|
|
674
|
+
* the same behaviour REQ-2 already shows for unknown chat
|
|
675
|
+
* sessions, just gated by a task id instead of a random uuid).
|
|
676
|
+
*/
|
|
677
|
+
async handleTaskChatMessage(taskId, req, res) {
|
|
678
|
+
if (!this.chatManager) {
|
|
679
|
+
this.json(res, 503, { error: "chat_disabled" });
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
683
|
+
if (method !== "POST" && method !== "GET") {
|
|
684
|
+
this.text(res, 405, "Method Not Allowed");
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
let message = "";
|
|
688
|
+
if (method === "POST") {
|
|
689
|
+
const body = await readJsonBody(req);
|
|
690
|
+
if (body && typeof body === "object" && typeof body.message === "string") {
|
|
691
|
+
message = body.message;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (typeof message !== "string" || message.length === 0) {
|
|
695
|
+
this.json(res, 400, { error: "bad_request", message: "Missing `message` field in JSON body." });
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
// Resolve the task context. Collector sessions are the primary
|
|
699
|
+
// source for in-progress tasks (live `title` / `status` /
|
|
700
|
+
// `context.worktree`); the operations cache is the fallback for
|
|
701
|
+
// tasks the user is asking about retroactively (session is gone
|
|
702
|
+
// but the CLI still has the description / operations). If neither
|
|
703
|
+
// knows the task, return 404 — answering without context would
|
|
704
|
+
// produce a misleading answer.
|
|
705
|
+
const ctx = await this.resolveTaskChatContext(taskId);
|
|
706
|
+
if (!ctx) {
|
|
707
|
+
this.json(res, 404, { error: "not_found", id: taskId });
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const builder = new TaskChatContextBuilder(ctx);
|
|
711
|
+
const sessionId = builder.sessionId();
|
|
712
|
+
const wrapped = builder.wrapMessage(message);
|
|
713
|
+
// SSE headers (mirror /api/chat/:id/message so the frontend SSE
|
|
714
|
+
// parser can stay identical).
|
|
715
|
+
res.statusCode = 200;
|
|
716
|
+
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
717
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
718
|
+
res.setHeader("Connection", "keep-alive");
|
|
719
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
720
|
+
res.flushHeaders?.();
|
|
721
|
+
let chunkIndex = 0;
|
|
722
|
+
const sendFrame = (event, payload) => {
|
|
723
|
+
if (res.writableEnded || res.destroyed)
|
|
724
|
+
return;
|
|
725
|
+
const id = ++chunkIndex;
|
|
726
|
+
try {
|
|
727
|
+
res.write(`id: ${id}\nevent: ${event}\ndata: ${JSON.stringify(payload)}\n\n`);
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
/* socket closed mid-write */
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
// Heartbeat so the client knows the connection is live.
|
|
734
|
+
sendFrame("ready", { sessionId, taskId, truncated: builder.truncated });
|
|
735
|
+
try {
|
|
736
|
+
const result = await this.chatManager.sendStream(sessionId, wrapped, (chunk) => {
|
|
737
|
+
sendFrame("chunk", chunk);
|
|
738
|
+
if (chunk.type === "done" || chunk.type === "error") {
|
|
739
|
+
sendFrame(chunk.type, chunk);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
sendFrame("done", {
|
|
743
|
+
sessionId,
|
|
744
|
+
taskId,
|
|
745
|
+
exitCode: result.exitCode,
|
|
746
|
+
durationMs: result.durationMs,
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
catch (err) {
|
|
750
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
751
|
+
sendFrame("error", { sessionId, taskId, error: { message: msg } });
|
|
752
|
+
}
|
|
753
|
+
try {
|
|
754
|
+
res.end();
|
|
755
|
+
}
|
|
756
|
+
catch {
|
|
757
|
+
/* ignore */
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Resolve the task context for a chat turn. Returns the
|
|
762
|
+
* TaskChatContextBuilder input or null when the task is unknown.
|
|
763
|
+
* Prefers the live collector session (rich `context.worktree` +
|
|
764
|
+
* current status); falls back to the operations cache envelope
|
|
765
|
+
* for tasks that have already been finalized.
|
|
766
|
+
*/
|
|
767
|
+
async resolveTaskChatContext(taskId) {
|
|
768
|
+
const live = this.collector.getSession(taskId);
|
|
769
|
+
if (live) {
|
|
770
|
+
// Best-effort: pull description / priority / operations from
|
|
771
|
+
// the operations cache if available (the CLI is the canonical
|
|
772
|
+
// source for those — collector only tracks tool calls).
|
|
773
|
+
let description;
|
|
774
|
+
let priority = live.status; // sensible fallback
|
|
775
|
+
let recentOperations = [];
|
|
776
|
+
if (this.operationsCache) {
|
|
777
|
+
try {
|
|
778
|
+
const cached = await this.operationsCache.get(taskId, { allowStale: true });
|
|
779
|
+
description = cached.task.context && typeof cached.task.context === "object"
|
|
780
|
+
? JSON.stringify(cached.task.context)
|
|
781
|
+
: undefined;
|
|
782
|
+
if (cached.task.goals_and_expected_deliverables && !description) {
|
|
783
|
+
description = cached.task.goals_and_expected_deliverables;
|
|
784
|
+
}
|
|
785
|
+
priority = cached.task.priority ?? priority;
|
|
786
|
+
recentOperations = cached.operations.slice(0, 10).map((op) => ({
|
|
787
|
+
id: op.id,
|
|
788
|
+
sequence: op.sequence,
|
|
789
|
+
milestoneType: op.milestoneType,
|
|
790
|
+
title: op.title,
|
|
791
|
+
description: op.description,
|
|
792
|
+
processDescription: op.processDescription,
|
|
793
|
+
timestamp: op.timestamp,
|
|
794
|
+
sessionShort: op.sessionShort,
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
catch {
|
|
798
|
+
/* fall through with defaults — we still answer */
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return {
|
|
802
|
+
taskId: live.taskId,
|
|
803
|
+
title: live.title,
|
|
804
|
+
description,
|
|
805
|
+
status: live.status,
|
|
806
|
+
priority,
|
|
807
|
+
worktree: live.context?.worktree,
|
|
808
|
+
recentOperations,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
if (this.operationsCache) {
|
|
812
|
+
try {
|
|
813
|
+
const cached = await this.operationsCache.get(taskId, { allowStale: true });
|
|
814
|
+
const ctx = cached.task.context;
|
|
815
|
+
return {
|
|
816
|
+
taskId: cached.task.id,
|
|
817
|
+
title: cached.task.title,
|
|
818
|
+
description: ctx && typeof ctx === "object" ? JSON.stringify(ctx) : cached.task.goals_and_expected_deliverables,
|
|
819
|
+
status: cached.task.status,
|
|
820
|
+
priority: cached.task.priority,
|
|
821
|
+
worktree: cached.task.projectPath,
|
|
822
|
+
recentOperations: cached.operations.slice(0, 10).map((op) => ({
|
|
823
|
+
id: op.id,
|
|
824
|
+
sequence: op.sequence,
|
|
825
|
+
milestoneType: op.milestoneType,
|
|
826
|
+
title: op.title,
|
|
827
|
+
description: op.description,
|
|
828
|
+
processDescription: op.processDescription,
|
|
829
|
+
timestamp: op.timestamp,
|
|
830
|
+
sessionShort: op.sessionShort,
|
|
831
|
+
})),
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
642
840
|
handleFileContent(url, req, res) {
|
|
643
841
|
const wantsHtml = prefersHtml(req);
|
|
644
842
|
const raw = url.searchParams.get("path") ?? "";
|
|
@@ -1368,6 +1566,8 @@ export function renderTaskPage(session) {
|
|
|
1368
1566
|
<pre class="json">${htmlEscape(JSON.stringify(session, null, 2))}</pre>
|
|
1369
1567
|
</details>
|
|
1370
1568
|
</section>
|
|
1569
|
+
|
|
1570
|
+
${renderChatPanel({ scope: "task", taskId: session.taskId })}
|
|
1371
1571
|
</div>
|
|
1372
1572
|
</div>
|
|
1373
1573
|
|
|
@@ -1376,6 +1576,7 @@ export function renderTaskPage(session) {
|
|
|
1376
1576
|
<script src="/static/task-operations.js"></script>
|
|
1377
1577
|
<script src="/static/file-tree.js"></script>
|
|
1378
1578
|
<script src="/static/tool-call-detail.js"></script>
|
|
1579
|
+
<script src="/static/chat-panel.js" defer></script>
|
|
1379
1580
|
<script>
|
|
1380
1581
|
if (window.mermaid) {
|
|
1381
1582
|
window.mermaid.initialize({
|
|
@@ -1515,49 +1716,18 @@ function walkTreeFlat(node, depth, out, affectedSet) {
|
|
|
1515
1716
|
walkTreeFlat(child, depth + 1, out, affectedSet);
|
|
1516
1717
|
}
|
|
1517
1718
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
* endpoints return 503.
|
|
1531
|
-
*/
|
|
1532
|
-
function renderChatPanel() {
|
|
1533
|
-
return `
|
|
1534
|
-
<section class="panel chat-panel" data-chat-shell aria-label="Home chat panel">
|
|
1535
|
-
<header class="chat-panel-header">
|
|
1536
|
-
<h2>💬 roy-agent chat</h2>
|
|
1537
|
-
<p class="meta chat-panel-meta">
|
|
1538
|
-
Multi-turn conversation powered by <code>roy-agent act -s <sessionId></code>.
|
|
1539
|
-
</p>
|
|
1540
|
-
</header>
|
|
1541
|
-
<div class="chat-messages" data-chat-messages role="log" aria-live="polite" aria-relevant="additions">
|
|
1542
|
-
<p class="chat-empty">No messages yet — ask away!</p>
|
|
1543
|
-
</div>
|
|
1544
|
-
<form class="chat-form" data-chat-form autocomplete="off">
|
|
1545
|
-
<label class="visually-hidden" for="chat-input">Message</label>
|
|
1546
|
-
<input
|
|
1547
|
-
id="chat-input"
|
|
1548
|
-
type="text"
|
|
1549
|
-
class="chat-input"
|
|
1550
|
-
data-chat-input
|
|
1551
|
-
placeholder="Type a message and press Enter…"
|
|
1552
|
-
autocomplete="off"
|
|
1553
|
-
/>
|
|
1554
|
-
<button type="submit" class="chat-send" data-chat-send aria-label="Send message">
|
|
1555
|
-
Send
|
|
1556
|
-
</button>
|
|
1557
|
-
</form>
|
|
1558
|
-
</section>
|
|
1559
|
-
`;
|
|
1560
|
-
}
|
|
1719
|
+
// v2.5.1+ REQ-2 fix: `renderChatPanel` now lives in `./chat-panel-html.ts`
|
|
1720
|
+
// so BOTH `renderIndexPage` (legacy home, server.ts) and
|
|
1721
|
+
// `_renderSessionForestPageImpl` (production home, task-session-store.ts)
|
|
1722
|
+
// can embed the same markup. See chat-panel-html.ts for the asset
|
|
1723
|
+
// contract that home-chat-v25.test.ts and
|
|
1724
|
+
// chat-panel-session-forest-v251.test.ts pin.
|
|
1725
|
+
//
|
|
1726
|
+
// v2.5 REQ-3: `renderChatPanel()` accepts `{scope, taskId}` so the
|
|
1727
|
+
// task-detail page can embed a task-aware panel (with
|
|
1728
|
+
// `data-chat-scope="task" data-task-id="N"` hooks the JS factory reads
|
|
1729
|
+
// to route to /api/task-chat/<N>/message). The contract is pinned by
|
|
1730
|
+
// test/task-chat-v25.test.ts.
|
|
1561
1731
|
function renderIndexPage(sessions) {
|
|
1562
1732
|
// SSR hint — the tree container is always rendered; if /api/tasks/tree
|
|
1563
1733
|
// returns 503 the JS shows a graceful empty state with a hint to
|
|
@@ -1672,6 +1842,16 @@ function renderIndexPage(sessions) {
|
|
|
1672
1842
|
<script src="/static/app.js"></script>
|
|
1673
1843
|
<script src="/static/mermaid-renderer.js"></script>
|
|
1674
1844
|
<script src="/static/tasks-tree.js"></script>
|
|
1845
|
+
<!--
|
|
1846
|
+
v2.5.x REQ-4: vendor markdown rendering for the home chat panel.
|
|
1847
|
+
Load order matters - marked must register globals before DOMPurify
|
|
1848
|
+
is constructed, and both must register before markdown-renderer.js
|
|
1849
|
+
resolves them at import time. chat-panel.js only depends on
|
|
1850
|
+
window.markdownRenderer, which is set by markdown-renderer.js.
|
|
1851
|
+
-->
|
|
1852
|
+
<script src="/static/marked.min.js" defer></script>
|
|
1853
|
+
<script src="/static/dompurify.min.js" defer></script>
|
|
1854
|
+
<script src="/static/markdown-renderer.js" defer></script>
|
|
1675
1855
|
<script src="/static/chat-panel.js" defer></script>
|
|
1676
1856
|
</body>
|
|
1677
1857
|
</html>`;
|