@glrs-dev/cli 2.3.0 → 2.4.0

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 (62) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/{chunk-EM4MJBOD.js → chunk-2AZKRWC6.js} +4 -4
  3. package/dist/{chunk-UXBOTMDY.js → chunk-2P3ETOT2.js} +2 -2
  4. package/dist/chunk-2VMFXAJH.js +795 -0
  5. package/dist/chunk-5ZVUFNCP.js +140 -0
  6. package/dist/{chunk-W37UX3U2.js → chunk-6Y27RQQL.js} +2 -2
  7. package/dist/{chunk-RZWOWTKF.js → chunk-EKNRKZWR.js} +4 -4
  8. package/dist/{chunk-YGNDPKIW.js → chunk-HQUCVJ4G.js} +3 -1
  9. package/dist/{chunk-OABVEBWW.js → chunk-MBEVC327.js} +1 -1
  10. package/dist/{chunk-MIWZLETC.js → chunk-MCM47HH4.js} +1 -1
  11. package/dist/{chunk-F3AFRUT2.js → chunk-PTIO556V.js} +2 -2
  12. package/dist/{chunk-E2UNZIZT.js → chunk-R2WXQ54P.js} +1 -1
  13. package/dist/{chunk-I2KUXY3I.js → chunk-SMDIOB5B.js} +2 -2
  14. package/dist/{chunk-SPULDN7P.js → chunk-YY7EWHMA.js} +5 -3
  15. package/dist/cli.js +31 -20
  16. package/dist/commands/autopilot-interactive.d.ts +89 -0
  17. package/dist/commands/autopilot-interactive.js +248 -0
  18. package/dist/commands/autopilot-raw.d.ts +1 -0
  19. package/dist/commands/autopilot-raw.js +368 -0
  20. package/dist/commands/autopilot-tui.d.ts +7 -0
  21. package/dist/commands/autopilot-tui.js +7 -0
  22. package/dist/commands/autopilot.d.ts +39 -0
  23. package/dist/commands/autopilot.js +395 -0
  24. package/dist/commands/cleanup.js +3 -3
  25. package/dist/commands/create.js +4 -4
  26. package/dist/commands/dashboard.d.ts +3 -0
  27. package/dist/commands/dashboard.js +1549 -0
  28. package/dist/commands/debrief.d.ts +57 -0
  29. package/dist/commands/debrief.js +9 -0
  30. package/dist/commands/delete.js +3 -3
  31. package/dist/commands/go.js +2 -2
  32. package/dist/commands/list.js +3 -3
  33. package/dist/commands/loop.d.ts +42 -0
  34. package/dist/commands/loop.js +133 -0
  35. package/dist/commands/plan-picker.d.ts +15 -0
  36. package/dist/commands/plan-picker.js +76 -0
  37. package/dist/commands/scoper.d.ts +54 -0
  38. package/dist/{vendor/harness-opencode/dist/scoper-S77SOK7X.js → commands/scoper.js} +30 -15
  39. package/dist/commands/switch.js +3 -3
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +1 -1
  42. package/dist/lib/auto-update.js +1 -1
  43. package/dist/lib/config.d.ts +3 -2
  44. package/dist/lib/config.js +1 -1
  45. package/dist/lib/registry.d.ts +2 -0
  46. package/dist/lib/registry.js +1 -1
  47. package/dist/lib/worktree.js +3 -3
  48. package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +7 -0
  49. package/dist/vendor/harness-opencode/dist/chunk-GILWWWMB.js +66 -0
  50. package/dist/vendor/harness-opencode/dist/cli.js +335 -639
  51. package/dist/vendor/harness-opencode/dist/index.js +35 -8
  52. package/dist/vendor/harness-opencode/dist/plugin-check-GJRD2OK6.js +14 -0
  53. package/dist/vendor/harness-opencode/package.json +1 -1
  54. package/package.json +8 -2
  55. package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +0 -104
  56. package/dist/vendor/harness-opencode/dist/chunk-GCWHRUOK.js +0 -259
  57. package/dist/vendor/harness-opencode/dist/chunk-MJSMBY2Y.js +0 -87
  58. package/dist/vendor/harness-opencode/dist/chunk-NIFAVPNN.js +0 -544
  59. package/dist/vendor/harness-opencode/dist/loop-session-J35NILUZ.js +0 -30
  60. package/dist/vendor/harness-opencode/dist/opencode-server-KPCDFYAX.js +0 -22
  61. package/dist/vendor/harness-opencode/dist/plan-parser-TMHEKT22.js +0 -6
  62. package/dist/vendor/harness-opencode/dist/plan-session-7VS32P52.js +0 -117
@@ -547,6 +547,7 @@ var AGENT_TIERS = {
547
547
  prime: "deep",
548
548
  scoper: "deep",
549
549
  "autopilot-prime": "deep",
550
+ "autopilot-fast": "autopilot-execute",
550
551
  plan: "deep",
551
552
  "architecture-advisor": "deep",
552
553
  "plan-reviewer": "deep",
@@ -588,7 +589,21 @@ function createAgents() {
588
589
  mode: "subagent",
589
590
  model: "anthropic/claude-opus-4-7",
590
591
  temperature: 0.2,
591
- permission: PRIME_PERMISSIONS,
592
+ permission: {
593
+ ...PRIME_PERMISSIONS,
594
+ question: "deny"
595
+ },
596
+ tools: AUTOPILOT_PRIME_DISABLED_TOOLS
597
+ }),
598
+ "autopilot-fast": agentFromPrompt(primePrompt, {
599
+ description: "Fast executor for autopilot sessions. Same prompt as autopilot-prime but runs on the mid-execute tier (Kimi 2.5/2.6, GLM-5). Used when --fast is passed. Plans must be enriched with mirror refs and code pointers before using this agent.",
600
+ mode: "subagent",
601
+ model: "anthropic/claude-sonnet-4-6",
602
+ temperature: 0.1,
603
+ permission: {
604
+ ...PRIME_PERMISSIONS,
605
+ question: "deny"
606
+ },
592
607
  tools: AUTOPILOT_PRIME_DISABLED_TOOLS
593
608
  }),
594
609
  plan: agentFromPrompt(planPrompt, {
@@ -849,6 +864,9 @@ function resolveHarnessModels(agents, config, pluginOptions) {
849
864
  const tier = AGENT_TIERS[agentName];
850
865
  if (tier) {
851
866
  let perTier = modelsConfig[tier];
867
+ if (tier === "autopilot-execute" && perTier === void 0) {
868
+ perTier = modelsConfig["mid-execute"] ?? modelsConfig["mid"];
869
+ }
852
870
  if (tier === "mid-execute" && perTier === void 0) {
853
871
  perTier = modelsConfig["mid"];
854
872
  }
@@ -1442,8 +1460,14 @@ var plugin = async ({ $, client }) => {
1442
1460
  }
1443
1461
  }
1444
1462
  return {
1445
- // Notify when a permission prompt fires (replaces the old permission.asked event)
1463
+ // Notify when a permission prompt fires (replaces the old permission.asked event).
1464
+ // In headless autopilot mode, auto-deny question permissions at the plugin level
1465
+ // so they never reach the event stream — prevents wasted loop iterations.
1446
1466
  "permission.ask": async (input, output) => {
1467
+ if (process.env["GLRS_AUTOPILOT_HEADLESS"] === "1" && (input?.type === "question" || input?.title === "")) {
1468
+ output.status = "deny";
1469
+ return;
1470
+ }
1447
1471
  const tool6 = input?.tool ?? input?.title ?? "a tool";
1448
1472
  await notify("opencode permission required", `Approval needed for ${tool6}.`);
1449
1473
  }
@@ -2101,7 +2125,7 @@ import { join as join9 } from "path";
2101
2125
  var APP_KEY = "A-US-3617699429";
2102
2126
  var ENDPOINT = "https://us.aptabase.com/api/v0/event";
2103
2127
  var PKG_NAME = "@glrs-dev/harness-plugin-opencode";
2104
- var PKG_VERSION = true ? "2.3.0" : "dev";
2128
+ var PKG_VERSION = true ? "2.4.0" : "dev";
2105
2129
  var DISABLED = process.env.HARNESS_OPENCODE_TELEMETRY === "0" || process.env.HARNESS_OPENCODE_TELEMETRY === "false" || process.env.DO_NOT_TRACK === "1" || process.env.CI === "true";
2106
2130
  var SESSION_ID = randomUUID();
2107
2131
  function getInstallId() {
@@ -2307,11 +2331,14 @@ var plugin5 = async (input, options) => {
2307
2331
  }
2308
2332
  };
2309
2333
  const hasTelemetryBefore = telemetryHooks["tool.execute.before"] !== void 0;
2310
- if (hasTelemetryBefore) {
2311
- hooks["tool.execute.before"] = async (input2, output) => {
2312
- if (hasTelemetryBefore) await telemetryHooks["tool.execute.before"](input2, output);
2313
- };
2314
- }
2334
+ hooks["tool.execute.before"] = async (input2, output) => {
2335
+ if (process.env["GLRS_AUTOPILOT_HEADLESS"] === "1" && input2.tool === "question") {
2336
+ throw new Error(
2337
+ "The question tool is not available in autopilot mode. Pick a sensible default and continue without asking the user."
2338
+ );
2339
+ }
2340
+ if (hasTelemetryBefore) await telemetryHooks["tool.execute.before"](input2, output);
2341
+ };
2315
2342
  const hasToolHooksAfter = toolHooks["tool.execute.after"] !== void 0;
2316
2343
  const hasTelemetryAfter = telemetryHooks["tool.execute.after"] !== void 0;
2317
2344
  if (hasToolHooksAfter || hasTelemetryAfter) {
@@ -0,0 +1,14 @@
1
+ import {
2
+ getOpencodeConfigPath,
3
+ isPluginInstalled,
4
+ promptChoice,
5
+ promptMulti,
6
+ promptYesNo
7
+ } from "./chunk-GILWWWMB.js";
8
+ export {
9
+ getOpencodeConfigPath,
10
+ isPluginInstalled,
11
+ promptChoice,
12
+ promptMulti,
13
+ promptYesNo
14
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glrs-dev/harness-plugin-opencode",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glrs-dev/cli",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Unified CLI for the @glrs-dev ecosystem — OpenCode agent harness dispatch + worktree management.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,17 +38,22 @@
38
38
  "scripts": {
39
39
  "build": "bun scripts/build.ts",
40
40
  "typecheck": "tsc --noEmit",
41
- "test": "bun test src/",
41
+ "test": "bun test src/ test/",
42
42
  "lint": "echo 'no linter configured yet'"
43
43
  },
44
44
  "dependencies": {
45
+ "@glrs-dev/autopilot": "workspace:*",
46
+ "@glrs-dev/adapter-opencode": "workspace:*",
47
+ "@inkjs/ui": "^2.0.0",
45
48
  "@inquirer/prompts": "^8.4.2",
46
49
  "@opencode-ai/plugin": "^1.14",
47
50
  "@opencode-ai/sdk": "^1.14",
48
51
  "cmd-ts": "^0.15.0",
52
+ "ink": "^5.0.0",
49
53
  "picomatch": "^4.0.4",
50
54
  "pino": "^10.3.1",
51
55
  "pino-pretty": "^13.1.3",
56
+ "react": "^18.3.1",
52
57
  "ulid": "^3.0.2",
53
58
  "yaml": "^2.8.3",
54
59
  "zod": "4.1.8"
@@ -57,6 +62,7 @@
57
62
  "@glrs-dev/harness-plugin-opencode": "workspace:*",
58
63
  "@types/bun": "latest",
59
64
  "@types/node": "^22",
65
+ "@types/react": "^18.3.0",
60
66
  "tsup": "^8",
61
67
  "typescript": "^5"
62
68
  }
@@ -1,104 +0,0 @@
1
- ---
2
- description: Self-driving PRIME run. Accepts an issue-tracker reference, a free-form task description, or a question.
3
- ---
4
-
5
- You are running in autopilot mode. The user is reviewing your output **asynchronously** — not during the run, but after it. Every decision you make becomes either a commit the user can `git blame`, a bullet in the plan's `## Open questions`, or a line in the autopilot log. Nothing you do blocks — everything you do is observable.
6
-
7
- This changes your default behavior in exactly one way, and you must internalize it: **do not ask the user anything.** Not via the `question` tool, not via chat prose, not by any means. The `question` tool will abort your session if invoked — the Ralph loop driver terminates any session that emits a `question.asked` event. You cannot recover from that; plan around it.
8
-
9
- ## Replace questions with decisions
10
-
11
- For every situation where the interactive PRIME would ask a question, take the specific action below instead. These are not suggestions — they are your defaults in autopilot mode:
12
-
13
- | Normal-PRIME behavior | Autopilot replacement |
14
- |---|---|
15
- | Frame confirmation on low-confidence Scope | Announce the frame as `→ Frame:` and proceed. If wrong, the user corrects after the run. |
16
- | Two-stage Assess fork (spec vs. code) | Always run spec-reviewer first, then code-reviewer on `[PASS_SPEC]`. Never ask which variant. |
17
- | Workflow-mechanics (branch / worktree / isolation) | Apply the deterministic heuristic from `prime.md` § `Workflow-mechanics decisions`. Announce in one line of chat. |
18
- | STOP-with-reorganization proposal | Write the proposal to the plan's `## Open questions` as a bullet, mark relevant acceptance boxes with `[ ]` and a note, emit `STOP: <reason>` and stop. The user resolves at the next run. |
19
- | Ambiguous input interpretation | Pick the most plausible interpretation. Record your reading in the plan's `## Goal` so the user can see what you decided. |
20
- | Scope-expansion check (> 2 files outside plan) | Expand silently if the expansion is mechanically obvious (test files for the new code, AGENTS.md updates in touched directories). Otherwise STOP with a bullet in `## Open questions`. |
21
- | Plan-reviewer rejection on a judgment call | 1st reject: fix. 2nd reject: narrow scope, move disputed items to `## Out of scope`. 3rd reject: emit `STOP: plan-reviewer disagreement unresolvable; needs human input`. Never ask the user. |
22
- | Merge-conflict / rebase resolution | STOP with `STOP: merge conflict in <file>; needs human review`. Do not attempt. |
23
-
24
- **The meta-rule: if the interactive PRIME would use `question`, write to the plan file instead.** Plans are the artifact the user reads after the run — that's where deferred decisions belong.
25
-
26
- ## Sentinel contract
27
-
28
- When all work in the user's prompt is complete (plan executed, Resolve stage done, PR open), emit `<autopilot-done>` as the **first token** of your final message. The Ralph loop watches for this to stop. Emit it only when truly finished — not when you think you're close, not when one iteration's acceptance criteria are met, not as a "checkpoint." Premature `<autopilot-done>` ends the whole run.
29
-
30
- If the loop is structurally stuck (dirty tree on default branch; merge conflict; un-tickable AC; missing upstream work), emit `STOP: <one-sentence reason>` instead. The loop logs it and exits.
31
-
32
- When invoked from the TUI (not the CLI driver), there is no external loop. Run SPEAR once to completion. Emit `<autopilot-done>` anyway for output consistency.
33
-
34
- ## Kill switch
35
-
36
- If `.agent/autopilot-disable` exists in the worktree, the CLI driver has already stopped before sending this prompt. No action needed.
37
-
38
- ## The user's request
39
-
40
- $ARGUMENTS
41
-
42
- ## Workflow
43
-
44
- ### 0. Workflow-mechanics first
45
-
46
- Before classifying the argument, apply the heuristic from `prime.md` § `Workflow-mechanics decisions`. Announce the result in one line of chat prefixed with `→ Workflow:`. No `question` tool, no notification.
47
-
48
- Abort paths (dirty tree on default branch; dirty tree on feature branch with unrelated work) mean emit `STOP: <reason>` and exit.
49
-
50
- If you auto-invoke `/fresh`, do NOT pass `--clean` — cleanup stays user-triggered.
51
-
52
- ### 1. Classify the argument
53
-
54
- Pick ONE:
55
-
56
- - **Issue-tracker reference** (single issue) — `<PROJECT>-<NUMBER>` (2-10 uppercase letters, e.g. `ENG-1234`), `#<NUMBER>`, or a URL to a recognized tracker (`github.com/.../issues/N`, `linear.app/.../issue/...`, `*.atlassian.net/browse/...`).
57
- - **Free-form task description** — any natural-language request that isn't a recognized issue ref.
58
- - **Question** — starts with what/why/how/when/where/which/who, or ends with `?`. For questions, answer in a single assistant message then emit `<autopilot-done>`. Do not enter SPEAR.
59
-
60
- ### 2. Fetch issue content (issue refs only)
61
-
62
- Probe in order, stop at the first with content:
63
-
64
- 1. **Linear MCP** — for `<PROJECT>-<NUMBER>` or `linear.app` URL: `linear_get_issue`.
65
- 2. **GitHub MCP** — for `#<NUMBER>` with `gh` available, or a `github.com/.../issues/...` URL.
66
- 3. **Jira / Atlassian MCP** — for `<PROJECT>-<NUMBER>` or `*.atlassian.net` URL.
67
-
68
- If no probe resolves, emit `STOP: ticket ref "<arg>" but no MCP configured; paste the issue body or use free-form` and exit. Do not guess at issue content.
69
-
70
- Treat the fetched title + description + acceptance criteria as the intent baseline. Map the plan's `## Acceptance criteria` 1:1 to the ticket, in order.
71
-
72
- ### 3. Run the SPEAR arc
73
-
74
- Normal SPEAR per `prime.md`, with these autopilot substitutions:
75
-
76
- - **Scope.** Argument already classified. Write the frame as `→ Frame:` and proceed. No confirmation.
77
- - **Plan.** Delegate to `@plan`. For ref-originated requests, cite the issue ID in the plan's `## Goal`.
78
- - **Execute.** Delegate to `@build`. Do not invoke Assess yourself during Execute — that's Phase 4's job.
79
- - **Assess.** Always dispatch `@spec-reviewer` first. On `[PASS_SPEC]`, dispatch `@code-reviewer` (or `@code-reviewer-thorough` if the diff meets the thorough thresholds). Iterate to `[PASS]`. Never prompt the user between rounds.
80
- - **Resolve.** When Assess returns `[PASS]`, push the branch and open the PR via `gh pr create`. Print the PR URL. Resolve auto-ships — do not invoke `/ship` yourself; `/ship` exists only as a manual resume path.
81
-
82
- For multi-issue prompts: use `/fresh` between issues to isolate each on its own branch. Complete each issue's full SPEAR arc (including Resolve) before starting the next.
83
-
84
- ### 4. Guardrails (beyond "no questions")
85
-
86
- - **Precedent defaults.** For helper-file location, naming conventions, logging verbosity, error-wrapper style: `git log` for a recent similar PR and mirror. Cite the precedent commit in the plan's `## Constraints`.
87
- - **Hard rules from Resolve still apply.** Never `--force`-push. Never push to `main`/`master`. Never `--no-verify`. Never merge the PR yourself. Resolve pushes the feature branch and opens the PR; human gate is review + merge.
88
- - **Circular failure.** If the same test fails after the same fix twice, delegate to `@architecture-advisor` before a third attempt. Do not churn.
89
- - **STOP when stuck, don't churn.** Structurally stuck (wrong branch, un-tickable AC, missing upstream work) → emit `STOP: <reason>` and exit.
90
-
91
- ### 5. Completion
92
-
93
- When all work is done, emit:
94
-
95
- ```
96
- <autopilot-done>
97
-
98
- Done. <One-sentence summary.>
99
- PR(s): <url(s)>
100
- ```
101
-
102
- If Resolve failed or was interrupted, report the failure and suggest `/ship <plan-path>` as the resume command.
103
-
104
- If you stopped early due to a structural block, emit `STOP: <reason>` instead — do not emit `<autopilot-done>`.
@@ -1,259 +0,0 @@
1
- // src/lib/opencode-server.ts
2
- import { execFile } from "child_process";
3
- import { promisify } from "util";
4
- import {
5
- createOpencodeServer,
6
- createOpencodeClient
7
- } from "@opencode-ai/sdk";
8
- var execFileP = promisify(execFile);
9
- var DEFAULT_STARTUP_TIMEOUT_MS = 3e4;
10
- async function ensureOpencodeOnPath() {
11
- try {
12
- await execFileP("opencode", ["--version"]);
13
- } catch {
14
- throw new Error(
15
- "opencode CLI not found on PATH.\n Install: https://opencode.ai\n Or: bunx opencode upgrade"
16
- );
17
- }
18
- }
19
- async function startServer(opts) {
20
- await ensureOpencodeOnPath();
21
- const timeoutMs = opts.timeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
22
- const port = opts.port ?? 0;
23
- const server = await createOpencodeServer({
24
- port,
25
- timeout: timeoutMs,
26
- hostname: "127.0.0.1"
27
- });
28
- const client = createOpencodeClient({ baseUrl: server.url });
29
- let shutdownCalled = false;
30
- const shutdown = async () => {
31
- if (shutdownCalled) return;
32
- shutdownCalled = true;
33
- try {
34
- await server.close();
35
- } catch {
36
- }
37
- };
38
- return { url: server.url, client, shutdown };
39
- }
40
- async function selfTest(client) {
41
- try {
42
- await client.session.list();
43
- } catch (err) {
44
- throw new Error(
45
- `OpenCode server self-test failed \u2014 the server started but isn't responding to API calls.
46
- Error: ${err instanceof Error ? err.message : String(err)}
47
- Run \`opencode --version\` to verify your installation.`
48
- );
49
- }
50
- }
51
- async function createSession(client, opts) {
52
- const result = await client.session.create({
53
- query: { directory: opts.cwd },
54
- body: {}
55
- });
56
- if (!result.data) {
57
- throw new Error("session.create returned no data");
58
- }
59
- return result.data.id;
60
- }
61
- async function sendAndWait(client, opts) {
62
- const stallMs = opts.stallMs ?? 60 * 60 * 1e3;
63
- const idlePromise = waitForIdle(client, {
64
- sessionId: opts.sessionId,
65
- stallMs,
66
- abortSignal: opts.abortSignal,
67
- onToolCall: opts.onToolCall,
68
- onTextDelta: opts.onTextDelta,
69
- onCostUpdate: opts.onCostUpdate,
70
- autoRejectPermissions: opts.autoRejectPermissions,
71
- serverUrl: opts.serverUrl,
72
- onPermissionRejected: opts.onPermissionRejected
73
- });
74
- await client.session.prompt({
75
- path: { id: opts.sessionId },
76
- body: {
77
- parts: [{ type: "text", text: opts.message }],
78
- ...opts.agentName ? { agent: opts.agentName } : {}
79
- }
80
- });
81
- return idlePromise;
82
- }
83
- async function waitForIdle(client, opts) {
84
- const stallMs = opts.stallMs ?? 60 * 60 * 1e3;
85
- const sse = await client.event.subscribe();
86
- const reportedToolCalls = /* @__PURE__ */ new Set();
87
- return new Promise((resolve) => {
88
- let stallTimer = null;
89
- let settled = false;
90
- const settle = (result) => {
91
- if (settled) return;
92
- settled = true;
93
- if (stallTimer) clearTimeout(stallTimer);
94
- resolve(result);
95
- };
96
- const resetStall = () => {
97
- if (stallTimer) clearTimeout(stallTimer);
98
- stallTimer = setTimeout(() => settle({ kind: "stall", stallMs }), stallMs);
99
- };
100
- if (opts.abortSignal) {
101
- if (opts.abortSignal.aborted) {
102
- settle({ kind: "abort" });
103
- return;
104
- }
105
- opts.abortSignal.addEventListener("abort", () => settle({ kind: "abort" }), { once: true });
106
- }
107
- resetStall();
108
- (async () => {
109
- try {
110
- for await (const event of sse.stream) {
111
- if (settled) break;
112
- const ev = event;
113
- const props = ev.properties ?? {};
114
- const type = ev.type ?? "";
115
- if (opts.onCostUpdate && type === "message.updated") {
116
- const info = props["info"];
117
- if (info && info.role === "assistant" && typeof info.cost === "number") {
118
- resetStall();
119
- try {
120
- opts.onCostUpdate(
121
- info.cost,
122
- {
123
- input: info.tokens?.input ?? 0,
124
- output: info.tokens?.output ?? 0
125
- }
126
- );
127
- } catch {
128
- }
129
- }
130
- }
131
- if (opts.onTextDelta && (type === "message.part.delta" || type === "message.part.updated")) {
132
- const delta = props["delta"];
133
- if (typeof delta === "string" && delta.length > 0) {
134
- resetStall();
135
- try {
136
- opts.onTextDelta(delta.length);
137
- } catch {
138
- }
139
- }
140
- }
141
- if (opts.onToolCall && type === "message.part.updated") {
142
- const part = props["part"];
143
- if (part && part.type === "tool" && part.sessionID === opts.sessionId && part.state?.status === "completed" && part.callID && !reportedToolCalls.has(part.callID)) {
144
- reportedToolCalls.add(part.callID);
145
- resetStall();
146
- try {
147
- opts.onToolCall(part.tool ?? "unknown");
148
- } catch {
149
- }
150
- continue;
151
- }
152
- }
153
- const eventSessionId = props["sessionID"];
154
- if (opts.autoRejectPermissions && (type === "permission.updated" || type === "question.asked")) {
155
- const permissionId = props["id"];
156
- const permissionType = type === "question.asked" ? "question" : props["type"] ?? "unknown";
157
- const permissionTitle = props["title"] ?? "";
158
- if (opts.onPermissionRejected) {
159
- try {
160
- opts.onPermissionRejected({
161
- id: permissionId ?? "unknown",
162
- type: permissionType,
163
- title: permissionTitle
164
- });
165
- } catch {
166
- }
167
- }
168
- if (type === "permission.updated" && permissionId) {
169
- (async () => {
170
- try {
171
- await client.postSessionIdPermissionsPermissionId({
172
- path: { id: opts.sessionId, permissionID: permissionId },
173
- body: { response: "reject" }
174
- });
175
- } catch {
176
- }
177
- })();
178
- continue;
179
- }
180
- if (type === "question.asked") {
181
- if (opts.serverUrl && permissionId) {
182
- (async () => {
183
- try {
184
- await fetch(`${opts.serverUrl}/question/${permissionId}/reject`, {
185
- method: "POST"
186
- });
187
- } catch {
188
- }
189
- })();
190
- }
191
- settle({
192
- kind: "question_rejected",
193
- title: permissionTitle
194
- });
195
- break;
196
- }
197
- }
198
- if (eventSessionId !== opts.sessionId) continue;
199
- resetStall();
200
- if (type === "session.idle") {
201
- settle({ kind: "idle" });
202
- break;
203
- }
204
- if (type === "session.error") {
205
- const msg = props["message"] ?? "session error";
206
- settle({ kind: "error", message: msg });
207
- break;
208
- }
209
- }
210
- } catch (err) {
211
- if (!settled) {
212
- settle({ kind: "error", message: err instanceof Error ? err.message : String(err) });
213
- }
214
- }
215
- })();
216
- });
217
- }
218
- async function getSessionCost(client, sessionId) {
219
- try {
220
- const result = await client.session.messages({ path: { id: sessionId } });
221
- if (!result.data) return 0;
222
- const messages = result.data;
223
- let total = 0;
224
- for (const m of messages) {
225
- if (m.info.role === "assistant" && typeof m.info.cost === "number") {
226
- total += m.info.cost;
227
- }
228
- }
229
- return total;
230
- } catch {
231
- return 0;
232
- }
233
- }
234
- async function getLastAssistantMessage(client, sessionId) {
235
- try {
236
- const result = await client.session.messages({ path: { id: sessionId } });
237
- if (!result.data) return "";
238
- const messages = result.data;
239
- const assistantMessages = messages.filter((m) => m.info.role === "assistant");
240
- if (assistantMessages.length === 0) return "";
241
- const last = assistantMessages[assistantMessages.length - 1];
242
- if (!last) return "";
243
- return last.parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
244
- } catch {
245
- return "";
246
- }
247
- }
248
-
249
- export {
250
- execFileP,
251
- DEFAULT_STARTUP_TIMEOUT_MS,
252
- startServer,
253
- selfTest,
254
- createSession,
255
- sendAndWait,
256
- waitForIdle,
257
- getSessionCost,
258
- getLastAssistantMessage
259
- };
@@ -1,87 +0,0 @@
1
- // src/autopilot/plan-parser.ts
2
- import * as fs from "fs";
3
- import * as path from "path";
4
- var DEGRADED = {
5
- type: "single",
6
- totalItems: 0,
7
- checkedItems: 0,
8
- phaseCount: 0,
9
- phasesCompleted: 0,
10
- phases: []
11
- };
12
- function countCheckboxes(content) {
13
- let total = 0;
14
- let checked = 0;
15
- const checkboxRe = /^[ \t]*-\s+\[([ xX])\]/gm;
16
- let match;
17
- while ((match = checkboxRe.exec(content)) !== null) {
18
- total++;
19
- if (match[1] !== " ") {
20
- checked++;
21
- }
22
- }
23
- return { total, checked };
24
- }
25
- function parseSingleFile(filePath) {
26
- const content = fs.readFileSync(filePath, "utf8");
27
- return countCheckboxes(content);
28
- }
29
- function detectPhaseFiles(dir) {
30
- const entries = fs.readdirSync(dir);
31
- return entries.filter((f) => /^phase_\d+\.md$/.test(f)).sort((a, b) => {
32
- const na = parseInt(a.replace(/[^0-9]/g, ""), 10);
33
- const nb = parseInt(b.replace(/[^0-9]/g, ""), 10);
34
- return na - nb;
35
- });
36
- }
37
- function parseMultiFile(dir) {
38
- const mainPath = path.join(dir, "main.md");
39
- const mainContent = fs.readFileSync(mainPath, "utf8");
40
- const mainCounts = countCheckboxes(mainContent);
41
- const phaseFiles = detectPhaseFiles(dir);
42
- const phases = [];
43
- let phasesCompleted = 0;
44
- for (const phaseFile of phaseFiles) {
45
- const phasePath = path.join(dir, phaseFile);
46
- const { total, checked } = parseSingleFile(phasePath);
47
- phases.push({ file: phaseFile, totalItems: total, checkedItems: checked });
48
- if (total > 0 && checked === total) {
49
- phasesCompleted++;
50
- }
51
- }
52
- return {
53
- type: "multi",
54
- totalItems: mainCounts.total,
55
- checkedItems: mainCounts.checked,
56
- phaseCount: phaseFiles.length,
57
- phasesCompleted,
58
- phases
59
- };
60
- }
61
- function parsePlanState(planPath) {
62
- try {
63
- const stat = fs.statSync(planPath);
64
- if (stat.isDirectory()) {
65
- const mainPath = path.join(planPath, "main.md");
66
- if (fs.existsSync(mainPath)) {
67
- return parseMultiFile(planPath);
68
- }
69
- return { ...DEGRADED, type: "multi" };
70
- }
71
- const { total, checked } = parseSingleFile(planPath);
72
- return {
73
- type: "single",
74
- totalItems: total,
75
- checkedItems: checked,
76
- phaseCount: 0,
77
- phasesCompleted: 0,
78
- phases: []
79
- };
80
- } catch {
81
- return { ...DEGRADED };
82
- }
83
- }
84
-
85
- export {
86
- parsePlanState
87
- };