@ai-setting/roy-plugin-task-show 0.6.11 → 0.8.4
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/README.md +121 -1
- package/dist/cli-tasks-adapter.d.ts +142 -0
- package/dist/cli-tasks-adapter.d.ts.map +1 -0
- package/dist/cli-tasks-adapter.js +379 -0
- package/dist/cli-tasks-adapter.js.map +1 -0
- package/dist/cli-tasks-tree-adapter.d.ts +143 -0
- package/dist/cli-tasks-tree-adapter.d.ts.map +1 -0
- package/dist/cli-tasks-tree-adapter.js +400 -0
- package/dist/cli-tasks-tree-adapter.js.map +1 -0
- package/dist/operations-cache.d.ts +44 -0
- package/dist/operations-cache.d.ts.map +1 -0
- package/dist/operations-cache.js +103 -0
- package/dist/operations-cache.js.map +1 -0
- package/dist/plugin.d.ts +14 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +82 -0
- package/dist/plugin.js.map +1 -1
- package/dist/server.d.ts +30 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +289 -89
- package/dist/server.js.map +1 -1
- package/dist/task-detail-mermaid.d.ts +171 -0
- package/dist/task-detail-mermaid.d.ts.map +1 -0
- package/dist/task-detail-mermaid.js +697 -0
- package/dist/task-detail-mermaid.js.map +1 -0
- package/dist/task-operations-html.d.ts +50 -0
- package/dist/task-operations-html.d.ts.map +1 -0
- package/dist/task-operations-html.js +187 -0
- package/dist/task-operations-html.js.map +1 -0
- package/dist/tasks-tree-cache.d.ts +44 -0
- package/dist/tasks-tree-cache.d.ts.map +1 -0
- package/dist/tasks-tree-cache.js +111 -0
- package/dist/tasks-tree-cache.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -1
- package/plugin.json +2 -2
- package/public/app.js +534 -34
- package/public/index.html +54 -21
- package/public/style.css +697 -1
- package/public/task-operations.js +290 -0
- package/public/tasks-tree.js +449 -0
package/README.md
CHANGED
|
@@ -23,6 +23,14 @@ When loaded into a `roy-agent` host, this plugin:
|
|
|
23
23
|
4. Serves the visualization on a local HTTP server (default
|
|
24
24
|
`http://127.0.0.1:7788/`) — index page, per-task detail page, and the
|
|
25
25
|
`/api/events` SSE stream.
|
|
26
|
+
5. **v0.7.0+ — Task lifecycle pipeline.** The per-task detail page now
|
|
27
|
+
also renders a vertical timeline of the task's own operation history
|
|
28
|
+
(`create`, `progress`, `milestone`, `problem`, `solution`, `decision`,
|
|
29
|
+
`review`, `completed`). The data comes from `roy-agent tasks get <id>
|
|
30
|
+
--operations --json`, served by a new endpoint `GET
|
|
31
|
+
/api/tasks/:id/operations`. See
|
|
32
|
+
[Task lifecycle pipeline (v0.7.0+)](#task-lifecycle-pipeline-v070) for
|
|
33
|
+
details.
|
|
26
34
|
|
|
27
35
|
The result: every task gets a clickable link the user can open in a
|
|
28
36
|
browser. Once open, the page **stays in sync** with the running agent
|
|
@@ -224,6 +232,31 @@ healthy.
|
|
|
224
232
|
|
|
225
233
|
## Changelog
|
|
226
234
|
|
|
235
|
+
### v0.6.12 — Task lifecycle pipeline integration fix (2026-07-24)
|
|
236
|
+
|
|
237
|
+
**For end users**: `npm install` as usual — no host-side change required.
|
|
238
|
+
Re-published as a patch release to fix a regression in the v0.7.0-pre
|
|
239
|
+
"Task lifecycle pipeline" feature where the page rendered the pipeline
|
|
240
|
+
panel but never actually populated it with operations.
|
|
241
|
+
|
|
242
|
+
- `renderOperationsPipeline(input)` now accepts `input.taskId` and emits
|
|
243
|
+
`data-task-id="<id>"` on the `<section data-pipeline-root>` element
|
|
244
|
+
(empty, populated, and error states). Non-numeric ids are silently
|
|
245
|
+
dropped, so bad input cannot break HTML attribute quoting.
|
|
246
|
+
- `renderTaskPage()` in `src/server.ts` now passes `taskId: session.taskId`
|
|
247
|
+
so every `/task/<id>` response carries the attribute and the client-side
|
|
248
|
+
`boot()` no longer silently bails out.
|
|
249
|
+
- Client-side defense in depth: `boot()` in `public/task-operations.js`
|
|
250
|
+
falls back to `document.body.dataset.taskId` when the section is missing
|
|
251
|
+
the attribute, and `renderPipelineHtml` / `renderErrorHtml` preserve
|
|
252
|
+
the attribute across `swapIn()` calls.
|
|
253
|
+
|
|
254
|
+
12 new TDD cases in `test/task-operations-browser-integration.test.ts`
|
|
255
|
+
(happy-dom browser integration + unit + server boot). New
|
|
256
|
+
`scripts/verify-task-operations-browser.ts` boots a real Playwright
|
|
257
|
+
Chromium against `TaskShowServer` + the real `roy-agent` CLI and asserts
|
|
258
|
+
7 op-node cards are rendered for Task #2388. All 161 tests pass.
|
|
259
|
+
|
|
227
260
|
### v0.6.11 — Mermaid SVG regression fix (2026-07-24)
|
|
228
261
|
|
|
229
262
|
**For end users**: `npm install` as usual — no host-side change required.
|
|
@@ -713,4 +746,91 @@ broadcast.
|
|
|
713
746
|
|
|
714
747
|
## License
|
|
715
748
|
|
|
716
|
-
MIT
|
|
749
|
+
MIT
|
|
750
|
+
## Task lifecycle pipeline (v0.7.0+)
|
|
751
|
+
|
|
752
|
+
In addition to the tool-call flow (the existing Mermaid diagram), every
|
|
753
|
+
`/task/:id` page now includes a **Task lifecycle pipeline** section that
|
|
754
|
+
shows the task's own operation history — the create / progress / milestone /
|
|
755
|
+
problem / solution / decision / review / completed events that the
|
|
756
|
+
`roy-agent tasks` CLI records for that task.
|
|
757
|
+
|
|
758
|
+
### Where the data comes from
|
|
759
|
+
|
|
760
|
+
The plugin spawns `roy-agent tasks get <id> --operations --json` (via
|
|
761
|
+
discrete argv — no shell string), parses the `{ task, operations }`
|
|
762
|
+
envelope, and serves a stable schema from the new endpoint:
|
|
763
|
+
|
|
764
|
+
```
|
|
765
|
+
GET /api/tasks/:id/operations
|
|
766
|
+
→ 200 {
|
|
767
|
+
task: { id, title, status, priority, type, progress?, createdAt, updatedAt, tags, projectPath? },
|
|
768
|
+
operations: [
|
|
769
|
+
{ id, sequence, milestoneType, title, description, timestamp, sessionShort }
|
|
770
|
+
],
|
|
771
|
+
fetchedAt: ISO8601,
|
|
772
|
+
stale: boolean
|
|
773
|
+
}
|
|
774
|
+
→ 404 { error: "not_found", id } // CLI reported "Task not found"
|
|
775
|
+
→ 400 { error: "bad_id", id } // invalid task id
|
|
776
|
+
→ 503 { error: "cli_failed", id } // CLI exited with non-zero status
|
|
777
|
+
→ 500 { error: "internal_error", id } // unexpected exception
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
The CLI is invoked safely:
|
|
781
|
+
|
|
782
|
+
- Arg array form: `["node", "<cliPath>", "tasks", "get", "<id>",
|
|
783
|
+
"--operations", "--json"]`. Shell metacharacters in the task id are
|
|
784
|
+
never interpreted.
|
|
785
|
+
- Wall-clock timeout (`cliTimeoutMs`, default 5 s).
|
|
786
|
+
- Max stdout bytes (`cliMaxBytes`, default 1 MiB).
|
|
787
|
+
- Stderr is captured so the CLI's `✗ Task not found: <id>` message is
|
|
788
|
+
mapped to HTTP 404.
|
|
789
|
+
|
|
790
|
+
### Caching & dedup
|
|
791
|
+
|
|
792
|
+
A TTL cache (default 5 s) coalesces concurrent requests for the same
|
|
793
|
+
task id and limits how often the plugin spawns the host CLI. Stale
|
|
794
|
+
entries are served with `stale: true` while a background refresh runs.
|
|
795
|
+
|
|
796
|
+
### Polling
|
|
797
|
+
|
|
798
|
+
For active tasks, the page polls `/api/tasks/:id/operations` every 5 s.
|
|
799
|
+
The client merges new operations into the existing timeline by id
|
|
800
|
+
(idempotent — no duplicates) and stops polling once the task reaches a
|
|
801
|
+
terminal status (`completed` / `failed` / `cancelled`). On any CLI
|
|
802
|
+
failure the page shows a "Retry" button and applies exponential backoff
|
|
803
|
+
up to 30 s.
|
|
804
|
+
|
|
805
|
+
### Security
|
|
806
|
+
|
|
807
|
+
- Task ids are validated as positive integers in the safe range.
|
|
808
|
+
- All operation titles, descriptions, and process notes are HTML-escaped.
|
|
809
|
+
- `processDescription` and `current_status` are stripped from public
|
|
810
|
+
responses (only safe fields reach the browser).
|
|
811
|
+
- The full session id is shortened to 8 hex chars (`sessionShort`).
|
|
812
|
+
|
|
813
|
+
### Configuration
|
|
814
|
+
|
|
815
|
+
| Key | Default | Purpose |
|
|
816
|
+
|---|---|---|
|
|
817
|
+
| `royAgentCliPath` | auto-detect | Absolute path to the `roy-agent` executable. |
|
|
818
|
+
| `operationsCacheTtlMs` | 5000 | TTL for parsed envelopes. |
|
|
819
|
+
| `maxOperations` | 200 | Hard cap on operations per task. |
|
|
820
|
+
| `maxDescriptionChars` | 2000 | Per-description char cap. |
|
|
821
|
+
| `cliTimeoutMs` | 5000 | Wall-clock timeout for the CLI spawn. |
|
|
822
|
+
| `cliMaxBytes` | 1048576 | Maximum stdout bytes. |
|
|
823
|
+
|
|
824
|
+
### CLI path resolution
|
|
825
|
+
|
|
826
|
+
The plugin auto-detects the `roy-agent` CLI from these locations
|
|
827
|
+
(first match wins):
|
|
828
|
+
|
|
829
|
+
1. `cfg.royAgentCliPath` (explicit override).
|
|
830
|
+
2. `$ROY_AGENT_CLI` environment variable.
|
|
831
|
+
3. `<cwd>/../roy-agent/packages/cli/dist/bin/roy-agent.js`
|
|
832
|
+
(sibling-repo layout — the typical dev setup).
|
|
833
|
+
4. The monorepo layout
|
|
834
|
+
(`<pluginDist>/../../../../packages/cli/dist/bin/roy-agent.js`).
|
|
835
|
+
5. `roy-agent` on PATH (fallback).
|
|
836
|
+
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CLI adapter for `roy-agent tasks get <id> --operations --json`.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* - Spawn the host CLI as a discrete arg array — never via shell string.
|
|
6
|
+
* - Enforce a wall-clock timeout and a maximum stdout size.
|
|
7
|
+
* - Locate the leading `{` in stdout to skip INFO/log lines the real
|
|
8
|
+
* CLI emits before its JSON envelope.
|
|
9
|
+
* - Validate the parsed envelope against a stable schema.
|
|
10
|
+
* - Sort operations ascending by timestamp and assign `sequence` numbers.
|
|
11
|
+
* - Truncate over-long descriptions defensively.
|
|
12
|
+
* - Surface typed errors so callers can map them to HTTP status codes.
|
|
13
|
+
*
|
|
14
|
+
* The adapter is intentionally narrow: it knows nothing about HTTP, the
|
|
15
|
+
* collector, or the frontend. The `runner` is injectable so tests never
|
|
16
|
+
* need a real subprocess.
|
|
17
|
+
*/
|
|
18
|
+
import type { TaskShowConfig } from "./types.js";
|
|
19
|
+
export type CanonicalMilestoneType = "create" | "progress" | "milestone" | "problem" | "solution" | "decision" | "review" | "completed" | "unknown";
|
|
20
|
+
/** Subset of the CLI's `task` payload that we surface to the UI. */
|
|
21
|
+
export interface TaskMetadata {
|
|
22
|
+
id: number;
|
|
23
|
+
title: string;
|
|
24
|
+
status: string;
|
|
25
|
+
priority: string;
|
|
26
|
+
type: string;
|
|
27
|
+
progress?: number;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
tags: string[];
|
|
31
|
+
projectPath?: string;
|
|
32
|
+
}
|
|
33
|
+
/** One row in the operations pipeline. */
|
|
34
|
+
export interface OperationRecord {
|
|
35
|
+
id: number;
|
|
36
|
+
/** 1-based sequence number, ascending by timestamp. */
|
|
37
|
+
sequence: number;
|
|
38
|
+
/** Canonical milestone type (or 'unknown' if the CLI emits an unknown value). */
|
|
39
|
+
milestoneType: CanonicalMilestoneType;
|
|
40
|
+
title: string;
|
|
41
|
+
description: string;
|
|
42
|
+
processDescription: string;
|
|
43
|
+
timestamp: string;
|
|
44
|
+
/** Truncated session id for display; the full id is never exposed. */
|
|
45
|
+
sessionShort: string;
|
|
46
|
+
}
|
|
47
|
+
export interface TaskOperationsEnvelope {
|
|
48
|
+
task: TaskMetadata;
|
|
49
|
+
operations: OperationRecord[];
|
|
50
|
+
}
|
|
51
|
+
export interface CachedEnvelope {
|
|
52
|
+
task: TaskMetadata;
|
|
53
|
+
operations: OperationRecord[];
|
|
54
|
+
/** When the source data was last fetched (ISO 8601). */
|
|
55
|
+
fetchedAt: string;
|
|
56
|
+
/** True if the cache entry is past TTL but still served. */
|
|
57
|
+
stale: boolean;
|
|
58
|
+
}
|
|
59
|
+
export interface AdapterRunnerResult {
|
|
60
|
+
stdout: string;
|
|
61
|
+
stderr: string;
|
|
62
|
+
exitCode: number;
|
|
63
|
+
}
|
|
64
|
+
export type AdapterRunner = (args: string[]) => Promise<AdapterRunnerResult>;
|
|
65
|
+
export interface AdapterOptions {
|
|
66
|
+
/** Absolute path to the `roy-agent` executable. */
|
|
67
|
+
cliPath: string;
|
|
68
|
+
/** Mockable subprocess runner (defaults to `defaultRunner`). */
|
|
69
|
+
runner?: AdapterRunner;
|
|
70
|
+
/** Wall-clock timeout (default 5000 ms). */
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
/** Maximum stdout bytes to keep (default 1 MiB). */
|
|
73
|
+
maxBytes?: number;
|
|
74
|
+
/** TaskShowConfig for maxOperations / maxDescriptionChars. */
|
|
75
|
+
cfg: TaskShowConfig;
|
|
76
|
+
}
|
|
77
|
+
/** Source interface used by OperationsCache and the HTTP layer. */
|
|
78
|
+
export interface TaskOperationsSource {
|
|
79
|
+
getTaskOperations(taskId: number): Promise<TaskOperationsEnvelope>;
|
|
80
|
+
}
|
|
81
|
+
export declare class AdapterError extends Error {
|
|
82
|
+
readonly code: string;
|
|
83
|
+
readonly cause?: unknown;
|
|
84
|
+
readonly exitCode?: number;
|
|
85
|
+
constructor(message: string, opts?: {
|
|
86
|
+
code?: string;
|
|
87
|
+
exitCode?: number;
|
|
88
|
+
cause?: unknown;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export declare class TaskNotFoundError extends AdapterError {
|
|
92
|
+
readonly taskId: number;
|
|
93
|
+
constructor(message: string, taskId: number);
|
|
94
|
+
}
|
|
95
|
+
export declare class ParseError extends AdapterError {
|
|
96
|
+
constructor(message: string, cause?: unknown);
|
|
97
|
+
}
|
|
98
|
+
export declare class TimeoutError extends AdapterError {
|
|
99
|
+
constructor(message: string, cause?: unknown);
|
|
100
|
+
}
|
|
101
|
+
export declare class SchemaError extends AdapterError {
|
|
102
|
+
constructor(message: string);
|
|
103
|
+
}
|
|
104
|
+
export declare function assertValidTaskId(id: unknown): asserts id is number;
|
|
105
|
+
/**
|
|
106
|
+
* Default subprocess runner. We deliberately avoid `shell: true` to keep
|
|
107
|
+
* argv as a literal array — no shell metacharacter interpretation. The
|
|
108
|
+
* `AbortController` enforces the wall-clock timeout by killing the child.
|
|
109
|
+
*/
|
|
110
|
+
export declare const defaultRunner: AdapterRunner;
|
|
111
|
+
/** Build the argv array. Pure function — easy to unit-test. */
|
|
112
|
+
export declare function buildTasksGetArgs(cliPath: string, taskId: number): string[];
|
|
113
|
+
/**
|
|
114
|
+
* Locate the first top-level `{` in stdout and return everything from
|
|
115
|
+
* there to the end. Tolerant of:
|
|
116
|
+
* - INFO / log lines printed before the JSON envelope
|
|
117
|
+
* - mixed CRLF / LF
|
|
118
|
+
* - `✗ Task not found: N` lines (handled separately by exit code)
|
|
119
|
+
*/
|
|
120
|
+
export declare function extractJsonEnvelope(stdout: string): string;
|
|
121
|
+
/**
|
|
122
|
+
* Map any string to a CanonicalMilestoneType. Unknown values fall back
|
|
123
|
+
* to 'unknown' (with the original string preserved under `title` if we
|
|
124
|
+
* ever want to display it).
|
|
125
|
+
*/
|
|
126
|
+
export declare function canonicalMilestoneType(raw: unknown): CanonicalMilestoneType;
|
|
127
|
+
/** Shorten a session id like `session_392d70e0-07bc-4a41-8669-ece72f325aad`
|
|
128
|
+
* to `s_392d70e0` (12 chars max, never leaks the full UUID). */
|
|
129
|
+
export declare function shortSessionId(raw: unknown): string;
|
|
130
|
+
/**
|
|
131
|
+
* Run `roy-agent tasks get <id> --operations --json` and return a parsed
|
|
132
|
+
* envelope. Errors are typed so the HTTP layer can map them to status codes.
|
|
133
|
+
*
|
|
134
|
+
* Pass `runner: defaultRunner` in production. Tests pass a `fixedRunner`.
|
|
135
|
+
*/
|
|
136
|
+
export declare function runTasksGetOperations(taskId: number, options: AdapterOptions): Promise<TaskOperationsEnvelope>;
|
|
137
|
+
/**
|
|
138
|
+
* Convenience wrapper used by OperationsCache: a `TaskOperationsSource`
|
|
139
|
+
* whose `getTaskOperations(taskId)` returns the parsed envelope or throws.
|
|
140
|
+
*/
|
|
141
|
+
export declare function makeTaskOperationsSource(opts: AdapterOptions): TaskOperationsSource;
|
|
142
|
+
//# sourceMappingURL=cli-tasks-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-tasks-adapter.d.ts","sourceRoot":"","sources":["../src/cli-tasks-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CLI adapter for `roy-agent tasks get <id> --operations --json`.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* - Spawn the host CLI as a discrete arg array — never via shell string.
|
|
6
|
+
* - Enforce a wall-clock timeout and a maximum stdout size.
|
|
7
|
+
* - Locate the leading `{` in stdout to skip INFO/log lines the real
|
|
8
|
+
* CLI emits before its JSON envelope.
|
|
9
|
+
* - Validate the parsed envelope against a stable schema.
|
|
10
|
+
* - Sort operations ascending by timestamp and assign `sequence` numbers.
|
|
11
|
+
* - Truncate over-long descriptions defensively.
|
|
12
|
+
* - Surface typed errors so callers can map them to HTTP status codes.
|
|
13
|
+
*
|
|
14
|
+
* The adapter is intentionally narrow: it knows nothing about HTTP, the
|
|
15
|
+
* collector, or the frontend. The `runner` is injectable so tests never
|
|
16
|
+
* need a real subprocess.
|
|
17
|
+
*/
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Error classes
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
export class AdapterError extends Error {
|
|
22
|
+
code;
|
|
23
|
+
cause;
|
|
24
|
+
exitCode;
|
|
25
|
+
constructor(message, opts = {}) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "AdapterError";
|
|
28
|
+
this.code = opts.code ?? "adapter_error";
|
|
29
|
+
if (opts.exitCode !== undefined)
|
|
30
|
+
this.exitCode = opts.exitCode;
|
|
31
|
+
if (opts.cause !== undefined)
|
|
32
|
+
this.cause = opts.cause;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class TaskNotFoundError extends AdapterError {
|
|
36
|
+
taskId;
|
|
37
|
+
constructor(message, taskId) {
|
|
38
|
+
super(message, { code: "not_found" });
|
|
39
|
+
this.name = "TaskNotFoundError";
|
|
40
|
+
this.taskId = taskId;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class ParseError extends AdapterError {
|
|
44
|
+
constructor(message, cause) {
|
|
45
|
+
super(message, { code: "parse_error", cause });
|
|
46
|
+
this.name = "ParseError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export class TimeoutError extends AdapterError {
|
|
50
|
+
constructor(message, cause) {
|
|
51
|
+
super(message, { code: "timeout", cause });
|
|
52
|
+
this.name = "TimeoutError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export class SchemaError extends AdapterError {
|
|
56
|
+
constructor(message) {
|
|
57
|
+
super(message, { code: "schema_error" });
|
|
58
|
+
this.name = "SchemaError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Task id validator
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
export function assertValidTaskId(id) {
|
|
65
|
+
if (typeof id !== "number" || !Number.isFinite(id)) {
|
|
66
|
+
throw new AdapterError(`task id must be a finite number (got ${String(id)})`, { code: "bad_id" });
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isInteger(id)) {
|
|
69
|
+
throw new AdapterError(`task id must be an integer (got ${id})`, { code: "bad_id" });
|
|
70
|
+
}
|
|
71
|
+
if (id <= 0) {
|
|
72
|
+
throw new AdapterError(`task id must be positive (got ${id})`, { code: "bad_id" });
|
|
73
|
+
}
|
|
74
|
+
if (id > Number.MAX_SAFE_INTEGER) {
|
|
75
|
+
throw new AdapterError(`task id out of safe range (got ${id})`, { code: "bad_id" });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Default runner — spawns the CLI as discrete args with timeout & byte cap
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
/**
|
|
82
|
+
* Default subprocess runner. We deliberately avoid `shell: true` to keep
|
|
83
|
+
* argv as a literal array — no shell metacharacter interpretation. The
|
|
84
|
+
* `AbortController` enforces the wall-clock timeout by killing the child.
|
|
85
|
+
*/
|
|
86
|
+
export const defaultRunner = async (args) => {
|
|
87
|
+
// Lazy import so this file stays test-friendly when the runner is mocked.
|
|
88
|
+
const { spawn } = await import("node:child_process");
|
|
89
|
+
const executable = args[0];
|
|
90
|
+
const argv = args.slice(1);
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
let settled = false;
|
|
93
|
+
const chunks = [];
|
|
94
|
+
const stderrChunks = [];
|
|
95
|
+
let totalBytes = 0;
|
|
96
|
+
const MAX_BYTES_DEFAULT = 1024 * 1024; // 1 MiB; the real CLI is well under this.
|
|
97
|
+
let truncated = false;
|
|
98
|
+
const child = spawn(executable, argv, {
|
|
99
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
100
|
+
env: process.env,
|
|
101
|
+
});
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
if (settled)
|
|
104
|
+
return;
|
|
105
|
+
settled = true;
|
|
106
|
+
try {
|
|
107
|
+
child.kill("SIGKILL");
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* ignore */
|
|
111
|
+
}
|
|
112
|
+
const err = new Error("CLI spawn timed out");
|
|
113
|
+
err.name = "AbortError";
|
|
114
|
+
reject(err);
|
|
115
|
+
}, 30_000); // default; honored by callers who pass timeoutMs via wrapper
|
|
116
|
+
child.stdout.on("data", (buf) => {
|
|
117
|
+
totalBytes += buf.length;
|
|
118
|
+
if (totalBytes > MAX_BYTES_DEFAULT) {
|
|
119
|
+
truncated = true;
|
|
120
|
+
try {
|
|
121
|
+
child.kill("SIGKILL");
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
/* ignore */
|
|
125
|
+
}
|
|
126
|
+
if (!settled) {
|
|
127
|
+
settled = true;
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
resolve({
|
|
130
|
+
stdout: Buffer.concat(chunks).toString("utf8"),
|
|
131
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
132
|
+
exitCode: 0,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
chunks.push(buf);
|
|
138
|
+
});
|
|
139
|
+
// The real CLI emits "✗ Task not found: <id>" to stderr. We keep
|
|
140
|
+
// it so the adapter can detect not-found and map it to 404.
|
|
141
|
+
child.stderr.on("data", (buf) => {
|
|
142
|
+
stderrChunks.push(buf);
|
|
143
|
+
});
|
|
144
|
+
child.on("error", (err) => {
|
|
145
|
+
if (settled)
|
|
146
|
+
return;
|
|
147
|
+
settled = true;
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
reject(err);
|
|
150
|
+
});
|
|
151
|
+
child.on("close", (code) => {
|
|
152
|
+
if (settled)
|
|
153
|
+
return;
|
|
154
|
+
settled = true;
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
resolve({
|
|
157
|
+
stdout: Buffer.concat(chunks).toString("utf8"),
|
|
158
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
159
|
+
exitCode: code ?? 0,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
/** Build the argv array. Pure function — easy to unit-test. */
|
|
165
|
+
export function buildTasksGetArgs(cliPath, taskId) {
|
|
166
|
+
// The CLI is bundled for Node — its dist has a `#!/usr/bin/env node`
|
|
167
|
+
// shebang. We invoke it through whatever runtime is available; on
|
|
168
|
+
// dev machines that is usually `bun` (TS loader), but `bun` has known
|
|
169
|
+
// TS decorator bugs that crash the CLI. Prefer `node` when available
|
|
170
|
+
// and fall back to `bun`. If neither is on PATH we still hand back
|
|
171
|
+
// a best-effort argv that the OS spawn will resolve.
|
|
172
|
+
const lower = cliPath.toLowerCase();
|
|
173
|
+
const looksLikeScript = lower.endsWith(".js") || lower.endsWith(".ts") || lower.endsWith(".mjs") || lower.endsWith(".cjs");
|
|
174
|
+
if (looksLikeScript) {
|
|
175
|
+
const runtime = pickScriptRuntime();
|
|
176
|
+
return [runtime, cliPath, "tasks", "get", String(taskId), "--operations", "--json"];
|
|
177
|
+
}
|
|
178
|
+
return [cliPath, "tasks", "get", String(taskId), "--operations", "--json"];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Pick the best script runtime. Cached on first call.
|
|
182
|
+
* - `node` is preferred (matches the CLI's shebang).
|
|
183
|
+
* - `bun` is the fallback when running under bun's test runner.
|
|
184
|
+
*/
|
|
185
|
+
let cachedRuntime = null;
|
|
186
|
+
function pickScriptRuntime() {
|
|
187
|
+
if (cachedRuntime)
|
|
188
|
+
return cachedRuntime;
|
|
189
|
+
// Prefer an explicit env override
|
|
190
|
+
if (process.env?.TASK_SHOW_CLI_RUNTIME) {
|
|
191
|
+
cachedRuntime = process.env.TASK_SHOW_CLI_RUNTIME;
|
|
192
|
+
return cachedRuntime;
|
|
193
|
+
}
|
|
194
|
+
// Cheap heuristic: Node is preferred.
|
|
195
|
+
cachedRuntime = "node";
|
|
196
|
+
return cachedRuntime;
|
|
197
|
+
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// stdout → JSON envelope
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
/**
|
|
202
|
+
* Locate the first top-level `{` in stdout and return everything from
|
|
203
|
+
* there to the end. Tolerant of:
|
|
204
|
+
* - INFO / log lines printed before the JSON envelope
|
|
205
|
+
* - mixed CRLF / LF
|
|
206
|
+
* - `✗ Task not found: N` lines (handled separately by exit code)
|
|
207
|
+
*/
|
|
208
|
+
export function extractJsonEnvelope(stdout) {
|
|
209
|
+
const start = stdout.indexOf("{");
|
|
210
|
+
if (start < 0) {
|
|
211
|
+
throw new ParseError("stdout contains no JSON object");
|
|
212
|
+
}
|
|
213
|
+
return stdout.slice(start);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Map any string to a CanonicalMilestoneType. Unknown values fall back
|
|
217
|
+
* to 'unknown' (with the original string preserved under `title` if we
|
|
218
|
+
* ever want to display it).
|
|
219
|
+
*/
|
|
220
|
+
export function canonicalMilestoneType(raw) {
|
|
221
|
+
const known = [
|
|
222
|
+
"create",
|
|
223
|
+
"progress",
|
|
224
|
+
"milestone",
|
|
225
|
+
"problem",
|
|
226
|
+
"solution",
|
|
227
|
+
"decision",
|
|
228
|
+
"review",
|
|
229
|
+
"completed",
|
|
230
|
+
];
|
|
231
|
+
if (typeof raw === "string" && known.includes(raw)) {
|
|
232
|
+
return raw;
|
|
233
|
+
}
|
|
234
|
+
return "unknown";
|
|
235
|
+
}
|
|
236
|
+
/** Truncate a string defensively. */
|
|
237
|
+
function clip(s, max) {
|
|
238
|
+
if (s.length <= max)
|
|
239
|
+
return s;
|
|
240
|
+
return s.slice(0, Math.max(0, max - 1)) + "…";
|
|
241
|
+
}
|
|
242
|
+
/** Shorten a session id like `session_392d70e0-07bc-4a41-8669-ece72f325aad`
|
|
243
|
+
* to `s_392d70e0` (12 chars max, never leaks the full UUID). */
|
|
244
|
+
export function shortSessionId(raw) {
|
|
245
|
+
if (typeof raw !== "string" || raw.length === 0)
|
|
246
|
+
return "";
|
|
247
|
+
// strip the well-known prefix if present
|
|
248
|
+
let id = raw;
|
|
249
|
+
if (id.startsWith("session_"))
|
|
250
|
+
id = id.slice("session_".length);
|
|
251
|
+
// take the first 8 hex chars (or all if shorter)
|
|
252
|
+
const clean = id.replace(/[^a-zA-Z0-9]/g, "");
|
|
253
|
+
return "s_" + clean.slice(0, 8);
|
|
254
|
+
}
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Main entry point
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
/**
|
|
259
|
+
* Run `roy-agent tasks get <id> --operations --json` and return a parsed
|
|
260
|
+
* envelope. Errors are typed so the HTTP layer can map them to status codes.
|
|
261
|
+
*
|
|
262
|
+
* Pass `runner: defaultRunner` in production. Tests pass a `fixedRunner`.
|
|
263
|
+
*/
|
|
264
|
+
export async function runTasksGetOperations(taskId, options) {
|
|
265
|
+
assertValidTaskId(taskId);
|
|
266
|
+
const runner = options.runner ?? defaultRunner;
|
|
267
|
+
const args = buildTasksGetArgs(options.cliPath, taskId);
|
|
268
|
+
// Apply the caller's timeout by wrapping the runner.
|
|
269
|
+
const timeoutMs = options.timeoutMs ?? 5000;
|
|
270
|
+
const maxBytes = options.maxBytes ?? 1024 * 1024;
|
|
271
|
+
let raw;
|
|
272
|
+
try {
|
|
273
|
+
raw = await withTimeout(runner(args), timeoutMs);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
if (err?.name === "AbortError" || /timeout|abort/i.test(String(err?.message ?? ""))) {
|
|
277
|
+
throw new TimeoutError(`CLI timeout after ${timeoutMs}ms`, err);
|
|
278
|
+
}
|
|
279
|
+
if (err instanceof AdapterError)
|
|
280
|
+
throw err;
|
|
281
|
+
throw new AdapterError(`CLI runner failed: ${err?.message ?? String(err)}`, { cause: err });
|
|
282
|
+
}
|
|
283
|
+
if (raw.stdout.length > maxBytes) {
|
|
284
|
+
throw new AdapterError(`CLI stdout exceeded maxBytes=${maxBytes} (got ${raw.stdout.length})`, { code: "oversize" });
|
|
285
|
+
}
|
|
286
|
+
// Order matters: first try to parse the envelope; only treat as "not
|
|
287
|
+
// found" when stdout OR stderr literally says so AND we cannot extract
|
|
288
|
+
// a valid JSON object. Many CLIs print migration / OTel logs that
|
|
289
|
+
// include the phrase "not found" in unrelated contexts.
|
|
290
|
+
let parsed;
|
|
291
|
+
try {
|
|
292
|
+
const jsonText = extractJsonEnvelope(raw.stdout);
|
|
293
|
+
parsed = JSON.parse(jsonText);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
if (/task not found/i.test(raw.stdout) || /task not found/i.test(raw.stderr ?? "")) {
|
|
297
|
+
throw new TaskNotFoundError(`Task not found: ${taskId}`, taskId);
|
|
298
|
+
}
|
|
299
|
+
if (raw.exitCode !== 0) {
|
|
300
|
+
throw new AdapterError(`CLI exited with code ${raw.exitCode} and no parseable JSON envelope`, { code: "cli_failed", exitCode: raw.exitCode });
|
|
301
|
+
}
|
|
302
|
+
if (err instanceof ParseError || err instanceof SchemaError)
|
|
303
|
+
throw err;
|
|
304
|
+
throw new ParseError("Failed to parse CLI JSON envelope", err);
|
|
305
|
+
}
|
|
306
|
+
if (raw.exitCode !== 0 && !parsed) {
|
|
307
|
+
throw new AdapterError(`CLI exited with code ${raw.exitCode}`, { code: "cli_failed", exitCode: raw.exitCode });
|
|
308
|
+
}
|
|
309
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
310
|
+
throw new SchemaError("envelope root is not an object");
|
|
311
|
+
}
|
|
312
|
+
if (!parsed.task || typeof parsed.task !== "object") {
|
|
313
|
+
throw new SchemaError("envelope missing `task` object");
|
|
314
|
+
}
|
|
315
|
+
if (!Array.isArray(parsed.operations)) {
|
|
316
|
+
throw new SchemaError("envelope missing `operations` array");
|
|
317
|
+
}
|
|
318
|
+
if (parsed.task.id !== taskId) {
|
|
319
|
+
throw new SchemaError(`envelope task.id=${parsed.task.id} does not match requested ${taskId}`);
|
|
320
|
+
}
|
|
321
|
+
// Normalize into our shape.
|
|
322
|
+
const maxOps = options.cfg.maxOperations ?? 200;
|
|
323
|
+
const maxDesc = options.cfg.maxDescriptionChars ?? 2000;
|
|
324
|
+
const normalized = parsed.operations
|
|
325
|
+
.map((op) => ({
|
|
326
|
+
id: Number(op?.id ?? 0),
|
|
327
|
+
sequence: 0, // assigned after sort
|
|
328
|
+
milestoneType: canonicalMilestoneType(op?.milestoneType ?? op?.actionType),
|
|
329
|
+
title: String(op?.milestoneTitle ?? op?.actionTitle ?? ""),
|
|
330
|
+
description: clip(String(op?.milestoneDescription ?? op?.actionDescription ?? ""), maxDesc),
|
|
331
|
+
processDescription: clip(String(op?.processDescription ?? ""), maxDesc),
|
|
332
|
+
timestamp: String(op?.timestamp ?? ""),
|
|
333
|
+
sessionShort: shortSessionId(op?.sessionId),
|
|
334
|
+
}))
|
|
335
|
+
.filter((o) => Number.isFinite(o.id) && o.id > 0)
|
|
336
|
+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
|
|
337
|
+
.slice(-maxOps)
|
|
338
|
+
.map((o, idx) => ({ ...o, sequence: idx + 1 }));
|
|
339
|
+
const task = {
|
|
340
|
+
id: Number(parsed.task.id),
|
|
341
|
+
title: String(parsed.task.title ?? ""),
|
|
342
|
+
status: String(parsed.task.status ?? "unknown"),
|
|
343
|
+
priority: String(parsed.task.priority ?? "medium"),
|
|
344
|
+
type: String(parsed.task.type ?? "normal"),
|
|
345
|
+
progress: typeof parsed.task.progress === "number" ? parsed.task.progress : undefined,
|
|
346
|
+
createdAt: String(parsed.task.createdAt ?? ""),
|
|
347
|
+
updatedAt: String(parsed.task.updatedAt ?? ""),
|
|
348
|
+
tags: Array.isArray(parsed.task.tags) ? parsed.task.tags.map((t) => String(t)) : [],
|
|
349
|
+
projectPath: typeof parsed.task.project_path === "string" ? parsed.task.project_path : undefined,
|
|
350
|
+
};
|
|
351
|
+
return { task, operations: normalized };
|
|
352
|
+
}
|
|
353
|
+
/** Wrap a promise with a wall-clock timeout. */
|
|
354
|
+
function withTimeout(p, ms) {
|
|
355
|
+
let timer;
|
|
356
|
+
const timeout = new Promise((_, reject) => {
|
|
357
|
+
timer = setTimeout(() => {
|
|
358
|
+
const err = new Error(`timed out after ${ms}ms`);
|
|
359
|
+
err.name = "AbortError";
|
|
360
|
+
reject(err);
|
|
361
|
+
}, ms);
|
|
362
|
+
});
|
|
363
|
+
return Promise.race([p, timeout]).finally(() => {
|
|
364
|
+
if (timer)
|
|
365
|
+
clearTimeout(timer);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Convenience wrapper used by OperationsCache: a `TaskOperationsSource`
|
|
370
|
+
* whose `getTaskOperations(taskId)` returns the parsed envelope or throws.
|
|
371
|
+
*/
|
|
372
|
+
export function makeTaskOperationsSource(opts) {
|
|
373
|
+
return {
|
|
374
|
+
async getTaskOperations(taskId) {
|
|
375
|
+
return runTasksGetOperations(taskId, opts);
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
//# sourceMappingURL=cli-tasks-adapter.js.map
|