mcp-server-opencode 1.1.5 → 1.2.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 (36) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/build/src/index.js +14 -10
  3. package/build/src/index.js.map +1 -1
  4. package/build/src/modules/shared/instructions.d.ts +8 -1
  5. package/build/src/modules/shared/instructions.d.ts.map +1 -1
  6. package/build/src/modules/shared/instructions.js +26 -27
  7. package/build/src/modules/shared/instructions.js.map +1 -1
  8. package/build/src/modules/shared/opencode-client.d.ts +31 -3
  9. package/build/src/modules/shared/opencode-client.d.ts.map +1 -1
  10. package/build/src/modules/shared/opencode-client.js +118 -25
  11. package/build/src/modules/shared/opencode-client.js.map +1 -1
  12. package/build/src/modules/shared/permissions.d.ts +108 -0
  13. package/build/src/modules/shared/permissions.d.ts.map +1 -0
  14. package/build/src/modules/shared/permissions.js +193 -0
  15. package/build/src/modules/shared/permissions.js.map +1 -0
  16. package/build/src/modules/shared/task-registry.d.ts +11 -0
  17. package/build/src/modules/shared/task-registry.d.ts.map +1 -1
  18. package/build/src/modules/shared/task-registry.js +10 -0
  19. package/build/src/modules/shared/task-registry.js.map +1 -1
  20. package/build/src/modules/shared/usage-limits.d.ts +70 -0
  21. package/build/src/modules/shared/usage-limits.d.ts.map +1 -0
  22. package/build/src/modules/shared/usage-limits.js +296 -0
  23. package/build/src/modules/shared/usage-limits.js.map +1 -0
  24. package/build/src/modules/tools/cancel_task.d.ts.map +1 -1
  25. package/build/src/modules/tools/cancel_task.js +5 -0
  26. package/build/src/modules/tools/cancel_task.js.map +1 -1
  27. package/build/src/modules/tools/get_task_result.d.ts.map +1 -1
  28. package/build/src/modules/tools/get_task_result.js +42 -7
  29. package/build/src/modules/tools/get_task_result.js.map +1 -1
  30. package/build/src/modules/tools/list_agents.d.ts.map +1 -1
  31. package/build/src/modules/tools/list_agents.js +22 -4
  32. package/build/src/modules/tools/list_agents.js.map +1 -1
  33. package/build/src/modules/tools/start_server.d.ts.map +1 -1
  34. package/build/src/modules/tools/start_server.js +22 -2
  35. package/build/src/modules/tools/start_server.js.map +1 -1
  36. package/package.json +3 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,100 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Releases before `1.2.0` predate this file and are not documented here.
9
+
10
+ ## [1.2.0] — 2026-08-28
11
+
12
+ Delegated tasks could hang forever, or report `completed` after doing nothing.
13
+ Both are fixed, and the result payload now carries evidence of what a task
14
+ actually did. See `docs/incident-2026-08-21-silent-noop-tasks.md` for the full
15
+ report this release closes out.
16
+
17
+ ### Behavior changes callers must handle
18
+
19
+ - `opencode_get_task_status` and `opencode_get_task_result` can now return two
20
+ new statuses: **`empty`** and **`cancelled`**. Both were previously reported
21
+ as `completed`. Code that switches exhaustively on task status needs a branch
22
+ for them; code that only special-cases `completed` will now correctly stop
23
+ treating a no-op or an aborted task as a success.
24
+ - `opencode_get_task_result` now includes a `progress` object. Previously it
25
+ returned only `status` and `result`.
26
+ - `opencode_start_server` now reports its permission posture under
27
+ `permissions` (`{ external_directory, auto_approved }`).
28
+
29
+ ### Added
30
+
31
+ - **Permission responder for headless servers.** A headless `opencode serve`
32
+ has no interactive approver, so any permission resolved to `ask` blocked the
33
+ agent forever — the session stayed genuinely busy and the task neither
34
+ completed nor failed. `opencode_start_server` now passes a permission config
35
+ to the OpenCode server *and* subscribes to its event stream to answer every
36
+ request.
37
+ - **`OPENCODE_MCP_EXTERNAL_DIR`** selects how the delegated agent may touch
38
+ paths outside the directory the server was launched in. Precedence: env var >
39
+ `OPENCODE_MCP_EXTERNAL_DIR=<policy>` CLI arg > default.
40
+ - `read-only` (default) — reads outside the cwd are approved, writes rejected.
41
+ - `allow` — no boundary.
42
+ - `deny` — nothing outside the cwd is reachable.
43
+
44
+ Inside the cwd everything is auto-approved; the caller already owns that
45
+ directory. The config is deep-merged per permission key, so existing
46
+ `read` / `edit` / `bash` deny globs (secrets, keys, `.env`) survive untouched.
47
+ - **Side-effect evidence in `TaskProgress`**: `mutating_tool_calls` and
48
+ `files_touched`, collected across every assistant turn in the session.
49
+ `completed` with an empty `files_touched` on a task whose whole purpose was
50
+ writing files is the signal that catches a silent no-op.
51
+ - **Per-model quota annotations.** `opencode_list_agents` now returns each model
52
+ as `{ id, quota }` with a `per_5h` estimate and a tier (`high-volume`,
53
+ `balanced`, `scarce`), scraped from the OpenCode Go docs and cached on disk
54
+ for 24h. A sibling `models.quota_snapshot` distinguishes "this model has no
55
+ quota row" from "we never got the table". Overridable with
56
+ `OPENCODE_MCP_CACHE_DIR` and `OPENCODE_MCP_LIMITS_TTL_MS`; a failed fetch
57
+ falls back to the stale snapshot, so startup never depends on the network.
58
+
59
+ ### Fixed
60
+
61
+ - **Tasks no longer hang on a permission prompt.** Previously, any task reading
62
+ or writing a path outside the server cwd wedged indefinitely with no way for a
63
+ caller to tell "working" from "stuck".
64
+ - **`completed` is now a work check, not a timestamp check.** An assistant turn
65
+ that ended with no text and no tool calls reports `empty` instead of
66
+ `completed` with an empty result. A completed timestamp says the turn ended,
67
+ not that it did anything.
68
+ - **Evidence is collected across the whole session, not just the last turn.** An
69
+ agent's edits land in earlier turns while the final turn is usually just
70
+ "DONE", so reading evidence off the last message alone reported
71
+ `files_touched: []` for a task that had rewritten the workspace.
72
+ - **Cancelled tasks report `cancelled`.** `opencode_cancel_task` now records the
73
+ cancellation, and status derivation checks it first. An aborted session keeps
74
+ its completed timestamp, so a deliberately killed task was previously
75
+ indistinguishable from one that finished on its own. A cancelled task still
76
+ returns whatever it produced before the abort, `files_touched` included.
77
+ - **The stall guard covers the empty-turn case.** `PENDING_STALL_MS` was only
78
+ reachable when no assistant message existed at all; once any message existed —
79
+ including an empty or aborted one — the guard was bypassed permanently.
80
+
81
+ ### Notes for contributors
82
+
83
+ - The generated `@opencode-ai/sdk` types lag the running binary. Verified
84
+ against opencode `1.18.18` with SDK `1.17.20`: the live permission event is
85
+ `permission.asked` (the SDK declares only `permission.updated`), its kind
86
+ arrives as `properties.permission` (the SDK says `type`), and the call id is
87
+ nested under `properties.tool.callID` (the SDK puts it at the top level).
88
+ Verify wire shapes against a live server before trusting a generated type — a
89
+ mismatch here fails silently, because a permission that is never answered just
90
+ hangs.
91
+ - Two bugs in this release passed a green unit suite and were caught only by
92
+ running against a live server. Mocks reproduce the contract you believe in,
93
+ not the one the system implements.
94
+
95
+ ### Not addressed
96
+
97
+ - **F3** from the incident report: there is still no staleness signal on the
98
+ `running` path. `buildProgress` reports `current_tool` and
99
+ `current_tool_status` but no timing, so a caller has no field to compare
100
+ across polls to tell a slow task from a wedged one.
@@ -1,19 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { DELEGATE_TASK_INSTRUCTIONS } from "./modules/shared/instructions.js";
4
+ import { createDelegateTaskInstructions } from "./modules/shared/instructions.js";
5
5
  import { killAllServers } from "./modules/shared/server-registry.js";
6
6
  import { registerTools } from "./modules/tools/index.js";
7
7
  // Create server instance
8
- const server = new McpServer({
9
- name: "opencode-mcp",
10
- version: "1.0.0",
11
- title: "OpenCode MCP",
12
- description: "MCP server that drives an OpenCode instance and delegates work asynchronously to its agents",
13
- websiteUrl: "https://opencode-mcp.alejandrotechnology.com",
14
- }, {
15
- instructions: DELEGATE_TASK_INSTRUCTIONS,
16
- });
8
+ async function createServer() {
9
+ const instructions = await createDelegateTaskInstructions();
10
+ return new McpServer({
11
+ name: "opencode-mcp",
12
+ version: "1.0.0",
13
+ title: "OpenCode MCP",
14
+ description: "MCP server that drives an OpenCode instance and delegates work asynchronously to its agents",
15
+ websiteUrl: "https://opencode-mcp.alejandrotechnology.com",
16
+ }, {
17
+ instructions,
18
+ });
19
+ }
17
20
  function shutdown(signal) {
18
21
  console.error(`Received ${signal}, stopping tracked OpenCode servers...`);
19
22
  killAllServers();
@@ -27,6 +30,7 @@ process.on("SIGTERM", shutdown);
27
30
  // Safety net for any other exit path (uncaught exception, process.exit elsewhere).
28
31
  process.on("exit", killAllServers);
29
32
  async function main() {
33
+ const server = await createServer();
30
34
  registerTools(server);
31
35
  const transport = new StdioServerTransport();
32
36
  await server.connect(transport);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,yBAAyB;AACzB,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;IACE,IAAI,EAAE,cAAc;IACpB,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,cAAc;IACrB,WAAW,EACT,6FAA6F;IAC/F,UAAU,EAAE,8CAA8C;CAC3D,EACD;IACE,YAAY,EAAE,0BAA0B;CACzC,CACF,CAAC;AAEF,SAAS,QAAQ,CAAC,MAAsB;IACtC,OAAO,CAAC,KAAK,CAAC,YAAY,MAAM,wCAAwC,CAAC,CAAC;IAC1E,cAAc,EAAE,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,yEAAyE;AACzE,kEAAkE;AAClE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAChC,mFAAmF;AACnF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAEnC,KAAK,UAAU,IAAI;IACjB,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,yBAAyB;AACzB,KAAK,UAAU,YAAY;IACzB,MAAM,YAAY,GAAG,MAAM,8BAA8B,EAAE,CAAC;IAC5D,OAAO,IAAI,SAAS,CAClB;QACE,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,6FAA6F;QAC/F,UAAU,EAAE,8CAA8C;KAC3D,EACD;QACE,YAAY;KACb,CACF,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,MAAsB;IACtC,OAAO,CAAC,KAAK,CAAC,YAAY,MAAM,wCAAwC,CAAC,CAAC;IAC1E,cAAc,EAAE,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,yEAAyE;AACzE,kEAAkE;AAClE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAChC,mFAAmF;AACnF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAEnC,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;IACpC,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -1,2 +1,9 @@
1
- export declare const DELEGATE_TASK_INSTRUCTIONS = "You have access to the opencode-mcp server, which lets you delegate work to OpenCode agents. Follow this workflow whenever you delegate tasks:\n\nModel selection guide \u2014 pick the model per task via the optional \"model\" input of opencode_start_task ('providerID/modelID'). Match the model tier to the task's difficulty; do not burn a scarce frontier model on work a cheap model handles well. Usage quotas (requests per 5h) are noted as a scarcity signal \u2014 lower quota means reserve it for harder work.\n\nIMPORTANT \u2014 model IDs: the display names below are NOT model IDs. Never guess or construct a 'providerID/modelID' string from a display name. Always copy the exact provider id and model id verbatim from opencode_list_agents output (models.providers[].provider + '/' + an entry of its models list). The same model may be exposed under different providers (e.g. 'opencode-go/minimax-m3' vs 'opencode/some-other-model'), so the provider prefix cannot be inferred. If opencode_start_task returns status \"unknown_model\", pick a model from the available_models list it returns instead of retrying a guessed id.\n\nFrontier tier (scarce \u2014 architecture, hard debugging, critical decisions):\n- Grok 4.5 (~80 req/5h): top-end reasoning and coding; reserve for the hardest problems, cross-cutting refactors, and tasks where a wrong answer is expensive.\n- Kimi K3 (~140 req/5h): frontier reasoning with very strong agentic stability and tool calling over long sessions; best for long-horizon multi-step tasks that must not derail.\n\nHigh tier (strong daily drivers \u2014 feature implementation, non-trivial debugging, review):\n- GLM-5.2 (~880 req/5h): open-weight leader on coding and agentic-coding benchmarks (SWE-bench Pro ~62%); best default for substantial implementation and refactoring work.\n- Kimi K2.7 Code (~1,150 req/5h): coding-specialized; ~30% fewer thinking tokens, excellent precise tool invocation (MCP-heavy pipelines) and 4,000+ tool-call sequences; best for long agentic coding runs.\n\nMid tier (high volume \u2014 standard features, tests, medium complexity):\n- MiniMax M3 (~3,200 req/5h): frontier-adjacent coding plus 1M context and native multimodality; best for large-codebase context ingestion or tasks involving images/video.\n- MiMo-V2.5-Pro (~3,250 req/5h): strong general agentic capability and complex software engineering (high SWE-bench Pro rankings); good GLM alternative at higher volume.\n- DeepSeek V4 Pro (~3,450 req/5h): solid all-round flagship; good balance of capability and quota for everyday implementation tasks.\n- Qwen3.7 Plus (~4,300 req/5h): capable mid-range generalist; good for standard features and tests at volume.\n\nFast tier (near-unlimited \u2014 mechanical work, boilerplate, formatting, simple fixes, docs, exploration):\n- MiMo-V2.5 (~30,100 req/5h): lightweight reasoning model; better than Flash on agentic tasks, at some latency/token cost.\n- DeepSeek V4 Flash (~31,650 req/5h): cheapest and fastest, non-reasoning; best for mechanical edits, boilerplate, renames, doc updates, and quick lookups where latency matters.\n\nRule of thumb: exploration/mechanical \u2192 Fast tier; standard implementation \u2192 Mid tier; complex features and long agentic runs \u2192 High tier; architecture or hardest debugging \u2192 Frontier tier.\n\nWorkflow:\n1. Ensure an OpenCode server is running. If you don't already have a server_id from a previous opencode_start_server call, call opencode_start_server first.\n2. Call opencode_list_agents with the server_id to discover the available agents (native and custom) and the exact model ids. Agents may have a pre-assigned model (\"provider/model\"); you can still override it per task.\n3. For each task, call opencode_start_task with the server_id and the task's prompt. Optionally pass an agent name and/or a model override copied verbatim from opencode_list_agents \u2014 never a guessed id. Collect the returned task_id for every call.\n4. Wait for completion with opencode_wait_for_task:\n - Single task: mode \"all\" with the single task_id.\n - Multiple tasks, incremental results: call it repeatedly with mode \"any\", removing completed task_ids each time.\n - Multiple tasks, all at once: call it once with mode \"all\" and every task_id.\n5. Once a task is reported finished, call opencode_get_task_result for that task_id to retrieve its output.\n6. Use opencode_get_task_status only for quick, non-blocking checks on a task's progress (e.g. to report status to the user) \u2014 it does not replace opencode_wait_for_task or opencode_get_task_result.\n\nDo not call opencode_get_task_result before the task has finished, and prefer opencode_wait_for_task over polling opencode_get_task_status in a loop.";
1
+ /**
2
+ * Build the MCP server instructions.
3
+ *
4
+ * Only the spend budget is embedded from the daily snapshot: per-model quotas
5
+ * ride along with `opencode_list_agents`, keyed by the real model id, so they
6
+ * are deliberately not repeated here.
7
+ */
8
+ export declare function createDelegateTaskInstructions(): Promise<string>;
2
9
  //# sourceMappingURL=instructions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/instructions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,0BAA0B,kpJAqC+G,CAAC"}
1
+ {"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/instructions.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,MAAM,CAAC,CA2BtE"}
@@ -1,33 +1,31 @@
1
- export const DELEGATE_TASK_INSTRUCTIONS = `You have access to the opencode-mcp server, which lets you delegate work to OpenCode agents. Follow this workflow whenever you delegate tasks:
2
-
3
- Model selection guide pick the model per task via the optional "model" input of opencode_start_task ('providerID/modelID'). Match the model tier to the task's difficulty; do not burn a scarce frontier model on work a cheap model handles well. Usage quotas (requests per 5h) are noted as a scarcity signal — lower quota means reserve it for harder work.
4
-
5
- IMPORTANT model IDs: the display names below are NOT model IDs. Never guess or construct a 'providerID/modelID' string from a display name. Always copy the exact provider id and model id verbatim from opencode_list_agents output (models.providers[].provider + '/' + an entry of its models list). The same model may be exposed under different providers (e.g. 'opencode-go/minimax-m3' vs 'opencode/some-other-model'), so the provider prefix cannot be inferred. If opencode_start_task returns status "unknown_model", pick a model from the available_models list it returns instead of retrying a guessed id.
6
-
7
- Frontier tier (scarce architecture, hard debugging, critical decisions):
8
- - Grok 4.5 (~80 req/5h): top-end reasoning and coding; reserve for the hardest problems, cross-cutting refactors, and tasks where a wrong answer is expensive.
9
- - Kimi K3 (~140 req/5h): frontier reasoning with very strong agentic stability and tool calling over long sessions; best for long-horizon multi-step tasks that must not derail.
10
-
11
- High tier (strong daily drivers — feature implementation, non-trivial debugging, review):
12
- - GLM-5.2 (~880 req/5h): open-weight leader on coding and agentic-coding benchmarks (SWE-bench Pro ~62%); best default for substantial implementation and refactoring work.
13
- - Kimi K2.7 Code (~1,150 req/5h): coding-specialized; ~30% fewer thinking tokens, excellent precise tool invocation (MCP-heavy pipelines) and 4,000+ tool-call sequences; best for long agentic coding runs.
14
-
15
- Mid tier (high volume — standard features, tests, medium complexity):
16
- - MiniMax M3 (~3,200 req/5h): frontier-adjacent coding plus 1M context and native multimodality; best for large-codebase context ingestion or tasks involving images/video.
17
- - MiMo-V2.5-Pro (~3,250 req/5h): strong general agentic capability and complex software engineering (high SWE-bench Pro rankings); good GLM alternative at higher volume.
18
- - DeepSeek V4 Pro (~3,450 req/5h): solid all-round flagship; good balance of capability and quota for everyday implementation tasks.
19
- - Qwen3.7 Plus (~4,300 req/5h): capable mid-range generalist; good for standard features and tests at volume.
20
-
21
- Fast tier (near-unlimited mechanical work, boilerplate, formatting, simple fixes, docs, exploration):
22
- - MiMo-V2.5 (~30,100 req/5h): lightweight reasoning model; better than Flash on agentic tasks, at some latency/token cost.
23
- - DeepSeek V4 Flash (~31,650 req/5h): cheapest and fastest, non-reasoning; best for mechanical edits, boilerplate, renames, doc updates, and quick lookups where latency matters.
24
-
25
- Rule of thumb: exploration/mechanical → Fast tier; standard implementation → Mid tier; complex features and long agentic runs → High tier; architecture or hardest debugging → Frontier tier.
1
+ import { formatTierGuide, formatUsageLimits, getUsageLimits } from "./usage-limits.js";
2
+ const QUOTA_FALLBACK = `OpenCode Go usage limits — unavailable right now (the docs could not be fetched and no cached snapshot exists).
3
+ All models share a single dollar budget per 5h / week / month, so an expensive model drains it in far fewer requests.`;
4
+ /**
5
+ * Build the MCP server instructions.
6
+ *
7
+ * Only the spend budget is embedded from the daily snapshot: per-model quotas
8
+ * ride along with `opencode_list_agents`, keyed by the real model id, so they
9
+ * are deliberately not repeated here.
10
+ */
11
+ export async function createDelegateTaskInstructions() {
12
+ const limits = await getUsageLimits();
13
+ const quotaBlock = limits ? formatUsageLimits(limits) : QUOTA_FALLBACK;
14
+ return `You have access to the opencode-mcp server, which lets you delegate work to OpenCode agents. Follow this workflow whenever you delegate tasks:
15
+
16
+ ${quotaBlock}
17
+ Burning a scarce model on work a cheap model handles well is how a session runs out of budget in one shot. Default to the cheapest tier that can do the job and escalate only when the task actually demands it.
18
+
19
+ Choosing a model — pass it per task via the optional "model" input of opencode_start_task as 'providerID/modelID'. Build that string only from opencode_list_agents output: models.providers[].provider + '/' + models.providers[].models[].id, both copied verbatim. Never derive an id from a model's display name, and never assume a provider prefix — read it from the same models.providers[] entry that supplied the id. If opencode_start_task returns status "unknown_model", pick one of the available_models it returns instead of retrying a guessed id.
20
+
21
+ Every model in that output carries a quota field: per_5h (estimated requests per 5 hours) and tier. Choose by tier:
22
+ ${formatTierGuide()}
23
+ Spread parallel tasks across models instead of firing every task at the same scarce one. models.providers[] is also the authority on what is actually enabled for the connected account.
26
24
 
27
25
  Workflow:
28
26
  1. Ensure an OpenCode server is running. If you don't already have a server_id from a previous opencode_start_server call, call opencode_start_server first.
29
- 2. Call opencode_list_agents with the server_id to discover the available agents (native and custom) and the exact model ids. Agents may have a pre-assigned model ("provider/model"); you can still override it per task.
30
- 3. For each task, call opencode_start_task with the server_id and the task's prompt. Optionally pass an agent name and/or a model override copied verbatim from opencode_list_agents — never a guessed id. Collect the returned task_id for every call.
27
+ 2. Call opencode_list_agents with the server_id to discover the available agents (native and custom), the exact model ids and their quotas. Agents may have a pre-assigned model ("provider/model"); you can still override it per task.
28
+ 3. For each task, call opencode_start_task with the server_id and the task's prompt, optionally with an agent name and/or a model override. Collect the returned task_id for every call.
31
29
  4. Wait for completion with opencode_wait_for_task:
32
30
  - Single task: mode "all" with the single task_id.
33
31
  - Multiple tasks, incremental results: call it repeatedly with mode "any", removing completed task_ids each time.
@@ -36,4 +34,5 @@ Workflow:
36
34
  6. Use opencode_get_task_status only for quick, non-blocking checks on a task's progress (e.g. to report status to the user) — it does not replace opencode_wait_for_task or opencode_get_task_result.
37
35
 
38
36
  Do not call opencode_get_task_result before the task has finished, and prefer opencode_wait_for_task over polling opencode_get_task_status in a loop.`;
37
+ }
39
38
  //# sourceMappingURL=instructions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"instructions.js","sourceRoot":"","sources":["../../../../src/modules/shared/instructions.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,0BAA0B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sJAqC4G,CAAC"}
1
+ {"version":3,"file":"instructions.js","sourceRoot":"","sources":["../../../../src/modules/shared/instructions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEvF,MAAM,cAAc,GAAG;sHAC+F,CAAC;AAEvH;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B;IAClD,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAC;IACtC,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAEvE,OAAO;;EAEP,UAAU;;;;;;EAMV,eAAe,EAAE;;;;;;;;;;;;;;sJAcmI,CAAC;AACvJ,CAAC"}
@@ -10,14 +10,22 @@ export interface AssistantEntry {
10
10
  info: AssistantMessage;
11
11
  parts: Part[];
12
12
  }
13
+ /** Fetch every assistant message (with its parts) for a session, oldest first. */
14
+ export declare function assistantEntries(client: OpencodeClient, sessionId: string): Promise<AssistantEntry[]>;
15
+ /** Every part the assistant emitted across the whole session, oldest first. */
16
+ export declare function sessionParts(entries: AssistantEntry[]): Part[];
13
17
  /** Fetch the most recent assistant message (with its parts) for a session. */
14
18
  export declare function lastAssistantEntry(client: OpencodeClient, sessionId: string): Promise<AssistantEntry | undefined>;
15
- export type TaskStatus = "pending" | "running" | "completed" | "failed";
19
+ export type TaskStatus = "pending" | "running" | "completed" | "failed" | "empty" | "cancelled";
16
20
  export interface TaskProgress {
17
21
  /** Last ~500 chars of concatenated TextPart text from the last assistant message. */
18
22
  text_snippet: string;
19
23
  /** Count of ToolPart parts whose state.status is "completed". */
20
24
  tool_calls_completed: number;
25
+ /** Completed tool calls that can mutate the workspace. */
26
+ mutating_tool_calls: number;
27
+ /** Distinct file paths touched by completed mutating tool calls. */
28
+ files_touched: string[];
21
29
  /** Tool name of the last ToolPart whose state.status is "running" or "pending". */
22
30
  current_tool?: string;
23
31
  /** state.status of that tool part. */
@@ -29,13 +37,33 @@ export interface TaskStatusResult {
29
37
  error?: string;
30
38
  progress?: TaskProgress;
31
39
  }
40
+ /**
41
+ * Whether an assistant turn actually produced anything.
42
+ *
43
+ * A completed timestamp only says the turn ended, not that it did work: an
44
+ * aborted or rejected turn carries a completed timestamp with no parts at all,
45
+ * which is byte-for-byte indistinguishable from a task that succeeded quietly.
46
+ */
47
+ export declare function hasWork(parts: Part[]): boolean;
32
48
  /**
33
49
  * How long an idle session may stay without any assistant message before the
34
50
  * task is considered dead (e.g. the fire-and-forget prompt was rejected).
35
51
  */
36
52
  export declare const PENDING_STALL_MS = 15000;
37
- /** Build a TaskProgress summary from a message's parts. */
38
- export declare function buildProgress(parts: Part[]): TaskProgress;
53
+ /**
54
+ * Build a TaskProgress summary.
55
+ *
56
+ * `latest` is the current assistant turn — it drives the text snippet and the
57
+ * in-flight tool. `whole` is every assistant part in the session and drives the
58
+ * side-effect evidence, because an agent's edits land in earlier turns while
59
+ * the final turn is usually just "DONE". Reading evidence off the last message
60
+ * alone reports `files_touched: []` for a task that rewrote the workspace.
61
+ */
62
+ export declare function buildProgress(latest: Part[], whole?: Part[]): TaskProgress;
63
+ /** Message returned alongside the `cancelled` status, shared by status and result. */
64
+ export declare const CANCELLED_TASK_MESSAGE = "the task was cancelled via opencode_cancel_task; any output below is whatever the agent produced before the abort";
65
+ /** Message returned alongside the `empty` status, shared by status and result. */
66
+ export declare const EMPTY_TURN_MESSAGE = "the session finished without producing any text or tool calls; the prompt was likely rejected or the turn was aborted";
39
67
  export interface DeriveTaskStatusOptions {
40
68
  includeProgress?: boolean;
41
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"opencode-client.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/opencode-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,cAAc,EACnB,KAAK,IAAI,EACV,MAAM,kBAAkB,CAAC;AAI1B,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAI5E;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,GACb;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAM3D;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,8EAA8E;AAC9E,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAUrC;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAExE,MAAM,WAAW,YAAY;IAC3B,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAID;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,YAAY,CA2BzD;AAED,MAAM,WAAW,uBAAuB;IACtC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,gBAAgB,CAAC,CA8C3B"}
1
+ {"version":3,"file":"opencode-client.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/opencode-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,cAAc,EACnB,KAAK,IAAI,EACV,MAAM,kBAAkB,CAAC;AAI1B,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAI5E;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,GACb;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAM3D;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,kFAAkF;AAClF,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,EAAE,CAAC,CAS3B;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,CAE9D;AAED,8EAA8E;AAC9E,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAErC;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAC;AAEhG,MAAM,WAAW,YAAY;IAC3B,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,0DAA0D;IAC1D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oEAAoE;IACpE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAYD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAM9C;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAcvC;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,GAAE,IAAI,EAAW,GAAG,YAAY,CAwClF;AAED,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,sHACkF,CAAC;AAEtH,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,0HAC0F,CAAC;AAE1H,MAAM,WAAW,uBAAuB;IACtC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,gBAAgB,CAAC,CA4E3B"}
@@ -18,52 +18,116 @@ export function clientForTask(taskId) {
18
18
  return undefined;
19
19
  return { client, sessionId: task.sessionId };
20
20
  }
21
- /** Fetch the most recent assistant message (with its parts) for a session. */
22
- export async function lastAssistantEntry(client, sessionId) {
21
+ /** Fetch every assistant message (with its parts) for a session, oldest first. */
22
+ export async function assistantEntries(client, sessionId) {
23
23
  const res = await client.session.messages({ path: { id: sessionId } });
24
- const messages = res.data ?? [];
25
- for (let i = messages.length - 1; i >= 0; i--) {
26
- const entry = messages[i];
27
- if (entry.info.role === "assistant") {
28
- return { info: entry.info, parts: entry.parts };
24
+ const entries = [];
25
+ for (const message of res.data ?? []) {
26
+ if (message.info.role === "assistant") {
27
+ entries.push({ info: message.info, parts: message.parts });
29
28
  }
30
29
  }
31
- return undefined;
30
+ return entries;
31
+ }
32
+ /** Every part the assistant emitted across the whole session, oldest first. */
33
+ export function sessionParts(entries) {
34
+ return entries.flatMap((entry) => entry.parts);
35
+ }
36
+ /** Fetch the most recent assistant message (with its parts) for a session. */
37
+ export async function lastAssistantEntry(client, sessionId) {
38
+ return (await assistantEntries(client, sessionId)).at(-1);
32
39
  }
33
40
  const TEXT_SNIPPET_MAX_LENGTH = 500;
41
+ /**
42
+ * Tools that can change the workspace. Verified against the live tool catalog
43
+ * (`client.tool.ids()` on opencode 1.18.18): the patch tool is `apply_patch`,
44
+ * and there is no `list` tool. `task` delegates to a subagent, which can write
45
+ * through any of the others.
46
+ */
47
+ const MUTATING_TOOLS = new Set(["write", "edit", "apply_patch", "bash", "task"]);
48
+ /**
49
+ * Whether an assistant turn actually produced anything.
50
+ *
51
+ * A completed timestamp only says the turn ended, not that it did work: an
52
+ * aborted or rejected turn carries a completed timestamp with no parts at all,
53
+ * which is byte-for-byte indistinguishable from a task that succeeded quietly.
54
+ */
55
+ export function hasWork(parts) {
56
+ for (const part of parts) {
57
+ if (part.type === "text" && part.text.trim() !== "")
58
+ return true;
59
+ if (part.type === "tool")
60
+ return true;
61
+ }
62
+ return false;
63
+ }
34
64
  /**
35
65
  * How long an idle session may stay without any assistant message before the
36
66
  * task is considered dead (e.g. the fire-and-forget prompt was rejected).
37
67
  */
38
68
  export const PENDING_STALL_MS = 15_000;
39
- /** Build a TaskProgress summary from a message's parts. */
40
- export function buildProgress(parts) {
69
+ /**
70
+ * The workspace path a completed tool call wrote to, if it names one.
71
+ *
72
+ * Verified against a live server: `write` and `edit` carry the path as
73
+ * `state.input.filePath`; `bash` carries only `command`, so it counts as
74
+ * mutating without contributing a path.
75
+ */
76
+ function touchedFilePath(input) {
77
+ const filePath = input.filePath;
78
+ return typeof filePath === "string" && filePath !== "" ? filePath : undefined;
79
+ }
80
+ /**
81
+ * Build a TaskProgress summary.
82
+ *
83
+ * `latest` is the current assistant turn — it drives the text snippet and the
84
+ * in-flight tool. `whole` is every assistant part in the session and drives the
85
+ * side-effect evidence, because an agent's edits land in earlier turns while
86
+ * the final turn is usually just "DONE". Reading evidence off the last message
87
+ * alone reports `files_touched: []` for a task that rewrote the workspace.
88
+ */
89
+ export function buildProgress(latest, whole = latest) {
41
90
  let text = "";
42
- let toolCallsCompleted = 0;
43
91
  let currentTool;
44
92
  let currentToolStatus;
45
- for (const part of parts) {
93
+ for (const part of latest) {
46
94
  if (part.type === "text") {
47
95
  text += part.text;
48
96
  continue;
49
97
  }
50
- if (part.type === "tool") {
51
- if (part.state.status === "completed") {
52
- toolCallsCompleted++;
53
- }
54
- else if (part.state.status === "running" || part.state.status === "pending") {
55
- currentTool = part.tool;
56
- currentToolStatus = part.state.status;
57
- }
98
+ if (part.type === "tool" &&
99
+ (part.state.status === "running" || part.state.status === "pending")) {
100
+ currentTool = part.tool;
101
+ currentToolStatus = part.state.status;
58
102
  }
59
103
  }
104
+ let toolCallsCompleted = 0;
105
+ let mutatingToolCalls = 0;
106
+ const filesTouched = new Set();
107
+ for (const part of whole) {
108
+ if (part.type !== "tool" || part.state.status !== "completed")
109
+ continue;
110
+ toolCallsCompleted++;
111
+ if (!MUTATING_TOOLS.has(part.tool))
112
+ continue;
113
+ mutatingToolCalls++;
114
+ const filePath = touchedFilePath(part.state.input);
115
+ if (filePath !== undefined)
116
+ filesTouched.add(filePath);
117
+ }
60
118
  return {
61
119
  text_snippet: text.slice(-TEXT_SNIPPET_MAX_LENGTH),
62
120
  tool_calls_completed: toolCallsCompleted,
121
+ mutating_tool_calls: mutatingToolCalls,
122
+ files_touched: [...filesTouched],
63
123
  current_tool: currentTool,
64
124
  current_tool_status: currentToolStatus,
65
125
  };
66
126
  }
127
+ /** Message returned alongside the `cancelled` status, shared by status and result. */
128
+ export const CANCELLED_TASK_MESSAGE = "the task was cancelled via opencode_cancel_task; any output below is whatever the agent produced before the abort";
129
+ /** Message returned alongside the `empty` status, shared by status and result. */
130
+ export const EMPTY_TURN_MESSAGE = "the session finished without producing any text or tool calls; the prompt was likely rejected or the turn was aborted";
67
131
  /**
68
132
  * Derive the current status of a task by inspecting session busy state and
69
133
  * the last assistant message. Shared by opencode_get_task_status and
@@ -71,22 +135,36 @@ export function buildProgress(parts) {
71
135
  */
72
136
  export async function deriveTaskStatus(client, sessionId, taskId, options) {
73
137
  const includeProgress = options?.includeProgress ?? false;
138
+ // Cancellation wins over everything else: an aborted session keeps its
139
+ // completed timestamp, so any later check would call it `completed`.
140
+ if (getTask(taskId)?.cancelledAt !== undefined) {
141
+ const entries = includeProgress ? await assistantEntries(client, sessionId) : [];
142
+ const latest = entries.at(-1);
143
+ return {
144
+ task_id: taskId,
145
+ status: "cancelled",
146
+ error: CANCELLED_TASK_MESSAGE,
147
+ progress: latest ? buildProgress(latest.parts, sessionParts(entries)) : undefined,
148
+ };
149
+ }
74
150
  // The status map only lists sessions that are actively working.
75
151
  const statusRes = await client.session.status();
76
152
  const sessionStatus = statusRes.data?.[sessionId];
77
153
  if (sessionStatus?.type === "busy" || sessionStatus?.type === "retry") {
78
154
  if (includeProgress) {
79
- const entry = await lastAssistantEntry(client, sessionId);
155
+ const entries = await assistantEntries(client, sessionId);
156
+ const latest = entries.at(-1);
80
157
  return {
81
158
  task_id: taskId,
82
159
  status: "running",
83
- progress: entry ? buildProgress(entry.parts) : undefined,
160
+ progress: latest ? buildProgress(latest.parts, sessionParts(entries)) : undefined,
84
161
  };
85
162
  }
86
163
  return { task_id: taskId, status: "running" };
87
164
  }
88
165
  // Not busy: inspect the last assistant message to tell pending from done.
89
- const entry = await lastAssistantEntry(client, sessionId);
166
+ const entries = await assistantEntries(client, sessionId);
167
+ const entry = entries.at(-1);
90
168
  if (!entry) {
91
169
  // Idle session with no assistant output: either the prompt was just accepted,
92
170
  // or it was silently rejected (fire-and-forget) and will never run. Past the
@@ -105,12 +183,27 @@ export async function deriveTaskStatus(client, sessionId, taskId, options) {
105
183
  return { task_id: taskId, status: "failed", error: entry.info.error.name };
106
184
  }
107
185
  if (entry.info.time.completed) {
108
- return { task_id: taskId, status: "completed" };
186
+ // A completed timestamp is not a work check. An assistant turn that ended
187
+ // with no text and no tool calls did nothing, and reporting that as
188
+ // `completed` is what lets a caller build on top of unchanged files.
189
+ if (!hasWork(entry.parts)) {
190
+ return {
191
+ task_id: taskId,
192
+ status: "empty",
193
+ error: EMPTY_TURN_MESSAGE,
194
+ progress: includeProgress ? buildProgress(entry.parts, sessionParts(entries)) : undefined,
195
+ };
196
+ }
197
+ return {
198
+ task_id: taskId,
199
+ status: "completed",
200
+ progress: includeProgress ? buildProgress(entry.parts, sessionParts(entries)) : undefined,
201
+ };
109
202
  }
110
203
  return {
111
204
  task_id: taskId,
112
205
  status: "running",
113
- progress: includeProgress ? buildProgress(entry.parts) : undefined,
206
+ progress: includeProgress ? buildProgress(entry.parts, sessionParts(entries)) : undefined,
114
207
  };
115
208
  }
116
209
  //# sourceMappingURL=opencode-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"opencode-client.js","sourceRoot":"","sources":["../../../../src/modules/shared/opencode-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"opencode-client.js","sourceRoot":"","sources":["../../../../src/modules/shared/opencode-client.ts"],"names":[],"mappings":"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"}