@doingdev/opencode-claude-manager-plugin 0.1.64 → 0.1.66

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 (125) hide show
  1. package/README.md +106 -120
  2. package/dist/claude/claude-agent-sdk-adapter.js +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/manager/team-orchestrator.js +1 -1
  5. package/dist/plugin/agents/common.d.ts +2 -2
  6. package/dist/plugin/agents/common.js +5 -0
  7. package/dist/plugin/claude-manager.plugin.js +104 -0
  8. package/dist/plugin/inbox-ops.d.ts +50 -0
  9. package/dist/plugin/inbox-ops.js +166 -0
  10. package/dist/types/contracts.d.ts +18 -0
  11. package/package.json +13 -13
  12. package/dist/claude/session-live-tailer.d.ts +0 -51
  13. package/dist/claude/session-live-tailer.js +0 -269
  14. package/dist/manager/session-controller.d.ts +0 -41
  15. package/dist/manager/session-controller.js +0 -97
  16. package/dist/metadata/claude-metadata.service.d.ts +0 -12
  17. package/dist/metadata/claude-metadata.service.js +0 -38
  18. package/dist/metadata/repo-claude-config-reader.d.ts +0 -7
  19. package/dist/metadata/repo-claude-config-reader.js +0 -154
  20. package/dist/plugin/orchestrator.plugin.d.ts +0 -2
  21. package/dist/plugin/orchestrator.plugin.js +0 -116
  22. package/dist/providers/claude-code-wrapper.d.ts +0 -13
  23. package/dist/providers/claude-code-wrapper.js +0 -13
  24. package/dist/safety/bash-safety.d.ts +0 -21
  25. package/dist/safety/bash-safety.js +0 -62
  26. package/dist/src/claude/claude-agent-sdk-adapter.d.ts +0 -28
  27. package/dist/src/claude/claude-agent-sdk-adapter.js +0 -559
  28. package/dist/src/claude/claude-session.service.d.ts +0 -9
  29. package/dist/src/claude/claude-session.service.js +0 -15
  30. package/dist/src/claude/session-live-tailer.d.ts +0 -51
  31. package/dist/src/claude/session-live-tailer.js +0 -269
  32. package/dist/src/claude/tool-approval-manager.d.ts +0 -30
  33. package/dist/src/claude/tool-approval-manager.js +0 -279
  34. package/dist/src/index.d.ts +0 -5
  35. package/dist/src/index.js +0 -3
  36. package/dist/src/manager/context-tracker.d.ts +0 -32
  37. package/dist/src/manager/context-tracker.js +0 -103
  38. package/dist/src/manager/git-operations.d.ts +0 -18
  39. package/dist/src/manager/git-operations.js +0 -86
  40. package/dist/src/manager/persistent-manager.d.ts +0 -39
  41. package/dist/src/manager/persistent-manager.js +0 -44
  42. package/dist/src/manager/session-controller.d.ts +0 -41
  43. package/dist/src/manager/session-controller.js +0 -97
  44. package/dist/src/manager/team-orchestrator.d.ts +0 -81
  45. package/dist/src/manager/team-orchestrator.js +0 -612
  46. package/dist/src/plugin/agent-hierarchy.d.ts +0 -1
  47. package/dist/src/plugin/agent-hierarchy.js +0 -2
  48. package/dist/src/plugin/agents/browser-qa.d.ts +0 -14
  49. package/dist/src/plugin/agents/browser-qa.js +0 -31
  50. package/dist/src/plugin/agents/common.d.ts +0 -36
  51. package/dist/src/plugin/agents/common.js +0 -59
  52. package/dist/src/plugin/agents/cto.d.ts +0 -9
  53. package/dist/src/plugin/agents/cto.js +0 -39
  54. package/dist/src/plugin/agents/engineers.d.ts +0 -9
  55. package/dist/src/plugin/agents/engineers.js +0 -11
  56. package/dist/src/plugin/agents/index.d.ts +0 -5
  57. package/dist/src/plugin/agents/index.js +0 -5
  58. package/dist/src/plugin/agents/team-planner.d.ts +0 -10
  59. package/dist/src/plugin/agents/team-planner.js +0 -23
  60. package/dist/src/plugin/claude-manager.plugin.d.ts +0 -10
  61. package/dist/src/plugin/claude-manager.plugin.js +0 -950
  62. package/dist/src/plugin/service-factory.d.ts +0 -38
  63. package/dist/src/plugin/service-factory.js +0 -101
  64. package/dist/src/prompts/registry.d.ts +0 -2
  65. package/dist/src/prompts/registry.js +0 -210
  66. package/dist/src/state/file-run-state-store.d.ts +0 -14
  67. package/dist/src/state/file-run-state-store.js +0 -85
  68. package/dist/src/state/team-state-store.d.ts +0 -14
  69. package/dist/src/state/team-state-store.js +0 -88
  70. package/dist/src/state/transcript-store.d.ts +0 -15
  71. package/dist/src/state/transcript-store.js +0 -44
  72. package/dist/src/team/roster.d.ts +0 -5
  73. package/dist/src/team/roster.js +0 -40
  74. package/dist/src/types/contracts.d.ts +0 -261
  75. package/dist/src/types/contracts.js +0 -2
  76. package/dist/src/util/fs-helpers.d.ts +0 -8
  77. package/dist/src/util/fs-helpers.js +0 -21
  78. package/dist/src/util/project-context.d.ts +0 -10
  79. package/dist/src/util/project-context.js +0 -105
  80. package/dist/src/util/transcript-append.d.ts +0 -7
  81. package/dist/src/util/transcript-append.js +0 -29
  82. package/dist/state/file-run-state-store.d.ts +0 -14
  83. package/dist/state/file-run-state-store.js +0 -85
  84. package/dist/test/claude-agent-sdk-adapter.test.d.ts +0 -1
  85. package/dist/test/claude-agent-sdk-adapter.test.js +0 -707
  86. package/dist/test/claude-manager.plugin.test.d.ts +0 -1
  87. package/dist/test/claude-manager.plugin.test.js +0 -316
  88. package/dist/test/context-tracker.test.d.ts +0 -1
  89. package/dist/test/context-tracker.test.js +0 -130
  90. package/dist/test/cto-active-team.test.d.ts +0 -1
  91. package/dist/test/cto-active-team.test.js +0 -199
  92. package/dist/test/file-run-state-store.test.d.ts +0 -1
  93. package/dist/test/file-run-state-store.test.js +0 -82
  94. package/dist/test/fs-helpers.test.d.ts +0 -1
  95. package/dist/test/fs-helpers.test.js +0 -56
  96. package/dist/test/git-operations.test.d.ts +0 -1
  97. package/dist/test/git-operations.test.js +0 -133
  98. package/dist/test/persistent-manager.test.d.ts +0 -1
  99. package/dist/test/persistent-manager.test.js +0 -48
  100. package/dist/test/project-context.test.d.ts +0 -1
  101. package/dist/test/project-context.test.js +0 -92
  102. package/dist/test/prompt-registry.test.d.ts +0 -1
  103. package/dist/test/prompt-registry.test.js +0 -117
  104. package/dist/test/report-claude-event.test.d.ts +0 -1
  105. package/dist/test/report-claude-event.test.js +0 -304
  106. package/dist/test/session-controller.test.d.ts +0 -1
  107. package/dist/test/session-controller.test.js +0 -149
  108. package/dist/test/session-live-tailer.test.d.ts +0 -1
  109. package/dist/test/session-live-tailer.test.js +0 -313
  110. package/dist/test/team-orchestrator.test.d.ts +0 -1
  111. package/dist/test/team-orchestrator.test.js +0 -583
  112. package/dist/test/team-state-store.test.d.ts +0 -1
  113. package/dist/test/team-state-store.test.js +0 -54
  114. package/dist/test/tool-approval-manager.test.d.ts +0 -1
  115. package/dist/test/tool-approval-manager.test.js +0 -260
  116. package/dist/test/transcript-append.test.d.ts +0 -1
  117. package/dist/test/transcript-append.test.js +0 -37
  118. package/dist/test/transcript-store.test.d.ts +0 -1
  119. package/dist/test/transcript-store.test.js +0 -50
  120. package/dist/test/undo-propagation.test.d.ts +0 -1
  121. package/dist/test/undo-propagation.test.js +0 -837
  122. package/dist/util/project-context.d.ts +0 -10
  123. package/dist/util/project-context.js +0 -105
  124. package/dist/vitest.config.d.ts +0 -2
  125. package/dist/vitest.config.js +0 -11
package/README.md CHANGED
@@ -1,48 +1,37 @@
1
- # OpenCode Claude Manager Plugin
1
+ # @doingdev/opencode-claude-manager-plugin
2
2
 
3
- This package provides an OpenCode plugin that lets an OpenCode-side agent hierarchy orchestrate Claude Code sessions through a stable local bridge.
3
+ OpenCode plugin that adds a manager-style orchestration layer over Claude Code sessions. A `cto` agent reads the repo, asks focused questions, and delegates to named engineers who each run work in their own persistent Claude Code session.
4
4
 
5
- ## Overview
5
+ Useful when you want OpenCode to investigate first, split work across named engineers with session continuity, and keep git and review controls at the manager layer.
6
6
 
7
- Use this when you want OpenCode to act like a real technical lead over Claude Code instead of being a thin relay. The plugin gives you a CTO agent that can ask better questions, explicitly assign named engineers, reuse each engineer's Claude session for continuity, preserve wrapper-level engineer memory, compare multiple plans, and keep git/review work at the manager layer.
7
+ ## What it adds
8
8
 
9
- ## Features
10
-
11
- - Runs Claude Code tasks from OpenCode through `@anthropic-ai/claude-agent-sdk`.
12
- - Creates a persistent named team: `Tom`, `John`, `Maya`, `Sara`, and `Alex`.
13
- - Reuses one Claude Code session per engineer within the active CTO team.
14
- - Reloads prior engineer wrapper context so each named subagent can prompt Claude better over time.
15
- - Uses named engineer subagents for live delegated work while keeping both wrapper memory and Claude session continuity underneath.
16
- - Keeps session babysitting out of the normal user flow — no public reset/fresh-session controls.
17
- - Discovers repo-local Claude metadata from `.claude/skills`, `.claude/commands`, `CLAUDE.md`, and settings hooks.
18
- - Git integration: diff, commit, and reset from the manager layer.
19
- - Tool approval policy for governing which Claude Code tools are allowed.
20
- - Persists local team state and transcripts under `.claude-manager/` for continuity and inspection.
9
+ - `cto` orchestrator that reads, delegates, reviews diffs, and owns all manager tools. Never edits files directly.
10
+ - Five persistent general engineers — `tom`, `john`, `maya`, `sara`, `alex` — each backed by its own Claude Code session that survives across assignments in the same CTO session.
11
+ - `team-planner` that runs two engineers in parallel and returns a single synthesized plan.
12
+ - `browser-qa` browser verification specialist using Playwright-oriented prompting. Does not implement code.
13
+ - Manager tools for team state, git, transcript/history inspection, and tool approval policy.
14
+ - Local runtime state under `.claude-manager/` persisted across turns.
21
15
 
22
16
  ## Requirements
23
17
 
24
- - Node `22+`
18
+ - Node `>=22`
25
19
  - OpenCode with plugin loading enabled
26
- - Access to Claude Code / Claude Agent SDK on the machine where OpenCode is running
27
-
28
- ## Installation
20
+ - Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`) available to the Node process
21
+ - Git - required for the git tools
22
+ - OpenCode Playwright skill/command - required for `browser-qa`
29
23
 
30
- Install from the npm registry:
24
+ ## Install
31
25
 
32
26
  ```bash
33
27
  pnpm add @doingdev/opencode-claude-manager-plugin
28
+ # npm install @doingdev/opencode-claude-manager-plugin
29
+ # yarn add @doingdev/opencode-claude-manager-plugin
34
30
  ```
35
31
 
36
- Or for local development in this repo:
37
-
38
- ```bash
39
- pnpm install
40
- pnpm run build
41
- ```
42
-
43
- ## OpenCode Config
32
+ ## Setup
44
33
 
45
- Add the plugin to your OpenCode config:
34
+ Add the plugin to `opencode.json` in your project root:
46
35
 
47
36
  ```json
48
37
  {
@@ -50,137 +39,134 @@ Add the plugin to your OpenCode config:
50
39
  }
51
40
  ```
52
41
 
53
- If you are testing locally, point OpenCode at the local package or plugin file using your normal local plugin workflow.
42
+ Agents register automatically at runtime. No manual agent entries needed.
54
43
 
55
- ## OpenCode tools
44
+ ## Key concepts
56
45
 
57
- ### CTO orchestration
46
+ **Delegation model.** The CTO reads the repo and context, decides what to do, and sends work to a named engineer via the `claude` tool. The engineer runs that assignment inside its own Claude Code session. The CTO never edits files directly.
58
47
 
59
- - Use the built-in OpenCode `task` tool to delegate to named engineers: `tom`, `john`, `maya`, `sara`, `alex`.
60
- - `team_status` — inspect the current CTO team's engineer bindings, Claude session IDs, busy flags, and context snapshots.
48
+ **Engineer persistence.** Each engineer's Claude Code session persists within a CTO session. A follow-up assignment to the same engineer resumes with prior context, which helps with multi-step tasks or when the engineer already understands a subsystem.
61
49
 
62
- ### Engineer bridge
50
+ **Modes.** When delegating, the CTO picks a mode:
51
+ - `explore` — read-only investigation. No file edits.
52
+ - `implement` — hands-on implementation work.
53
+ - `verify` — tests, lint, spot-checks after changes.
63
54
 
64
- - `claude` available only inside named engineer subagents. Sends work through that engineer's persistent Claude Code session.
65
- - `mode` (required) — `explore`, `implement`, or `verify`.
66
- - `message` (required) — the work to do.
67
- - `model` (optional) — `claude-opus-4-6` or `claude-sonnet-4-6`.
55
+ **Teams.** Each CTO session has its own team. State is keyed by CTO session ID under `.claude-manager/teams/`.
68
56
 
69
- ### Git operations
57
+ ## Agents
70
58
 
71
- - `git_diff` review all uncommitted changes (staged + unstaged).
72
- - `git_commit` — stage all changes and commit with a message.
73
- - `git_reset` hard reset + clean (destructive).
59
+ | Agent | Role |
60
+ |---|---|
61
+ | `cto` | Orchestrator. Reads, delegates, reviews, owns manager tools. Does not edit files. |
62
+ | `tom`, `john`, `maya`, `sara`, `alex` | General engineers. Each exposes only the `claude` tool backed by its own persistent Claude Code session. |
63
+ | `team-planner` | Thin wrapper around `plan_with_team`. Runs two engineers in parallel, returns a synthesized plan. |
64
+ | `browser-qa` | Browser verification specialist. Uses Playwright-oriented prompting. Does not implement code. |
74
65
 
75
- ### Inspection
66
+ ## Tools
76
67
 
77
- - `list_transcripts` — list available session transcripts or inspect a specific transcript by ID.
78
- - `list_history` — list saved CTO teams for the worktree or inspect one team by ID.
68
+ ### Engineer tool
79
69
 
80
- ### Tool approval
70
+ Each general engineer exposes one tool:
81
71
 
82
- - `approval_policy` view the current tool approval policy.
83
- - `approval_decisions` — view recent tool approval decisions.
84
- - `approval_update` add/remove rules, enable/disable approval, or clear decision history. Policy uses a **deny-list**: tools not matching any rule are **allowed**; use explicit **deny** rules to block. `defaultAction` is always `allow` (cannot be set to deny).
72
+ | Tool | Parameters | Notes |
73
+ |---|---|---|
74
+ | `claude` | `mode` (`explore`/`implement`/`verify`), `assignment` (text), optional `model` | Model choices: `claude-opus-4-6` or `claude-sonnet-4-6`. Defaults to the session default. |
85
75
 
86
- ## Agent hierarchy
76
+ ### CTO tools
87
77
 
88
- The plugin registers a CTO + named engineer team through the OpenCode plugin `config` hook:
78
+ | Tool | Description |
79
+ |---|---|
80
+ | `team_status` | Show current team: engineers, sessions, context levels. |
81
+ | `reset_engineer` | Clear a stuck engineer's Claude session, wrapper history, or both. |
82
+ | `git_diff` | Diff with optional path filter, staged flag, or ref. |
83
+ | `git_commit` | Commit staged changes with a message and optional file list. |
84
+ | `git_reset` | **Destructive.** Runs `git reset --hard HEAD && git clean -fd`. |
85
+ | `git_status` | Short status and cleanliness check. |
86
+ | `git_log` | Recent commit log. |
87
+ | `list_transcripts` | List saved Claude session transcripts in `.claude-manager/transcripts/`. |
88
+ | `list_history` | List saved team state in `.claude-manager/teams/`. |
89
+ | `approval_policy` | Read the active tool approval policy. |
90
+ | `approval_decisions` | Read the logged approval decisions. |
91
+ | `approval_update` | Update the tool approval policy. |
89
92
 
90
- - **`cto`** (primary agent) — owns the outcome, finds missing requirements, spawns named engineers with the Task tool, compares plans, reviews diffs, and manages git.
91
- - **`tom`**, **`john`**, **`maya`**, **`sara`**, **`alex`** (subagents) — thin named engineer wrappers. Each uses the `claude` tool and keeps one persistent Claude Code session.
92
- - **`team-planner`** (subagent) — thin planning wrapper that runs `plan_with_team` so the UI shows live planning activity.
93
- - **Claude Code sessions** — the underlying execution layer. One session per engineer inside the active team.
93
+ ### team-planner tool
94
94
 
95
- These are added to OpenCode config at runtime by the plugin, so they do not require separate manual `opencode.json` entries.
95
+ | Tool | Description |
96
+ |---|---|
97
+ | `plan_with_team` | Runs two general engineers in parallel on the same planning task, then synthesizes one recommended plan. `browser-qa` is not part of the planning pool. |
96
98
 
97
- ## Quick Start
99
+ ## Approval policy
98
100
 
99
- Typical flow inside OpenCode:
101
+ Each engineer's Claude Code session runs under a tool approval manager. The policy is **deny-list based**: unmatched tools stay allowed and `defaultAction` is always `allow`.
100
102
 
101
- 1. Ask the `cto` agent for the work.
102
- 2. Let `cto` investigate lightly, then spawn one or more named engineers with the Task tool.
103
- 3. Review changes with `git_diff`, then commit or reset.
104
- 4. Inspect saved Claude history with `list_transcripts` or saved team state with `list_history` / `team_status`.
103
+ Rule fields:
104
+ - `pattern` glob matching the tool name.
105
+ - `inputPattern` (optional) substring match against tool input.
106
+ - `action` `allow` or `deny`.
107
+ - `message` (optional) — shown when the rule fires.
105
108
 
106
- Example tasks:
109
+ Default rules block patterns like `rm -rf /`, `git push --force`, and `git reset --hard`. Read the active policy with `approval_policy`, inspect logged decisions with `approval_decisions`, and change rules with `approval_update`.
107
110
 
111
+ ## Workflows
112
+
113
+ **Investigate then implement:**
108
114
  ```text
109
- Ask CTO to send Tom to implement the new validation logic in src/auth.ts, then review with git_diff.
115
+ Ask cto to inspect the failing billing tests, send tom to implement the smallest safe fix, then review with git_diff.
110
116
  ```
111
117
 
112
- For a larger feature where you want investigation first and then a stronger combined plan:
113
-
118
+ **Dual-engineer planning:**
114
119
  ```text
115
- Ask CTO to inspect the billing feature scope, then use team-planner to run two independent investigations and synthesize the best implementation plan.
120
+ Ask cto to use team-planner to produce a two-engineer plan for adding SSO to the auth module.
116
121
  ```
117
122
 
118
- ## Local Development
119
-
120
- Clone the repo and run:
121
-
122
- ```bash
123
- pnpm install
124
- pnpm run lint
125
- pnpm run typecheck
126
- pnpm run test
127
- pnpm run build
123
+ **Browser verification:**
124
+ ```text
125
+ Ask cto to send browser-qa to verify the signup flow on http://localhost:3000 and report failures.
128
126
  ```
129
127
 
130
- The compiled plugin output is written to `dist/`.
128
+ **Reuse engineer context:**
129
+ ```text
130
+ Ask cto to send john to add error handling to the function he just implemented.
131
+ ```
131
132
 
132
- ## Publishing
133
+ **Inspect state before committing:**
134
+ ```text
135
+ Ask cto to run git_diff, summarize the changes, then run git_commit with a descriptive message.
136
+ ```
133
137
 
134
- This package is configured for the npm scope `@doingdev`.
138
+ ## State
135
139
 
136
- This repository uses npm trusted publishing with GitHub Actions OIDC, so you do not need an `NPM_TOKEN` secret once npm is configured correctly.
140
+ Runtime state is local to the worktree and gitignored:
137
141
 
138
- Before the first automated publish, configure npm trusted publishing for `@doingdev/opencode-claude-manager-plugin` on npmjs.com:
142
+ ```text
143
+ .claude-manager/
144
+ teams/ # One JSON file per CTO session (team ID)
145
+ transcripts/ # Claude session event logs
146
+ approval-policy.json # Active policy, if customized
147
+ debug.log # NDJSON debug log from plugin hooks
148
+ ```
139
149
 
140
- 1. Open the package settings on npmjs.com.
141
- 2. Go to the `Trusted Publisher` section.
142
- 3. Choose `GitHub Actions`.
143
- 4. Set the GitHub owner/user to your account or org.
144
- 5. Set the repository name.
145
- 6. Set the workflow filename to `publish.yml`.
146
- 7. Leave the environment name empty unless you later add a GitHub Actions environment back to the workflow.
150
+ - State is not shared across machines or worktrees.
151
+ - Continuity is strongest within a single CTO session. Restarting the CTO session starts a new team.
152
+ - Undo at the CTO level propagates to engineer wrapper sessions and inner Claude Code sessions.
147
153
 
148
- Notes for trusted publishing:
154
+ ## Limits and caveats
149
155
 
150
- - npm trusted publishing requires GitHub-hosted runners.
151
- - npm recommends Node `22.14.0+` with npm CLI `11.5.1+`; the workflows use Node `24`.
152
- - Provenance is generated automatically by npm for trusted publishes from public GitHub repositories.
156
+ - Context usage tracking is heuristic, not exact SDK-reported truth. Actual token counts may differ.
157
+ - `browser-qa` returns `PLAYWRIGHT_UNAVAILABLE: <reason>` if the Playwright skill or command is missing.
158
+ - `plan_with_team` runs two general engineers. `browser-qa` is excluded from the planning pool.
159
+ - `git_reset` is destructive and immediate. Run `git_diff` first to inspect state.
160
+ - Engineer context degrades if sessions are reset or if the CTO session is restarted mid-task.
153
161
 
154
- Release flow:
162
+ ## Development
155
163
 
156
164
  ```bash
157
- pnpm login
158
- pnpm whoami
159
- pnpm version patch
165
+ pnpm install
160
166
  pnpm run lint
161
167
  pnpm run typecheck
162
168
  pnpm run test
163
169
  pnpm run build
164
170
  ```
165
171
 
166
- Then publish from GitHub by either:
167
-
168
- - creating a GitHub Release, or
169
- - running the `Publish` workflow manually from the Actions tab
170
-
171
- After trusted publishing is working, you can tighten npm package security by disabling token-based publishing for the package in npm settings.
172
-
173
- ## Limitations
174
-
175
- - Claude slash commands and skills come primarily from filesystem discovery; SDK probing is available but optional.
176
- - Session state is local to the repo under `.claude-manager/` and is ignored by git.
177
- - The strongest team continuity comes when engineers are spawned from the active `cto` session; the plugin maps named engineers back to that active team automatically.
178
- - Context tracking is heuristic-based; actual SDK context usage may differ slightly.
179
-
180
- ## Scripts
181
-
182
- - `pnpm run build`
183
- - `pnpm run typecheck`
184
- - `pnpm run lint`
185
- - `pnpm run format`
186
- - `pnpm run test`
172
+ `dist/` is generated output. Do not edit it directly.
@@ -120,7 +120,7 @@ export class ClaudeAgentSdkAdapter {
120
120
  async getTranscript(sessionId, cwd) {
121
121
  const messages = await this.sdkFacade.getSessionMessages(sessionId, cwd ? { dir: cwd } : undefined);
122
122
  return messages.map((message) => ({
123
- role: message.type,
123
+ role: message.type === 'user' ? 'user' : 'assistant',
124
124
  sessionId: message.session_id,
125
125
  messageId: message.uuid,
126
126
  text: extractText(message.message),
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Plugin } from '@opencode-ai/plugin';
2
2
  import { ClaudeManagerPlugin } from './plugin/claude-manager.plugin.js';
3
- export type { ClaudeCapabilitySnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, ContextWarningLevel, SessionMode, EngineerName, EngineerWorkMode, EngineerFailureKind, EngineerFailureResult, WrapperHistoryEntry, TeamEngineerRecord, TeamRecord, EngineerTaskResult, PlanDraft, SynthesizedPlanResult, ToolApprovalRule, ToolApprovalPolicy, ToolApprovalDecision, } from './types/contracts.js';
3
+ export type { ClaudeCapabilitySnapshot, ClaudeSessionRunResult, ClaudeSessionSummary, ClaudeSessionTranscriptMessage, ManagerPromptRegistry, RunClaudeSessionInput, SessionContextSnapshot, GitDiffResult, GitOperationResult, ContextWarningLevel, SessionMode, EngineerName, EngineerWorkMode, EngineerFailureKind, EngineerFailureResult, WrapperHistoryEntry, AgentMessageStatus, AgentMessage, TeamEngineerRecord, TeamRecord, EngineerTaskResult, PlanDraft, SynthesizedPlanResult, ToolApprovalRule, ToolApprovalPolicy, ToolApprovalDecision, } from './types/contracts.js';
4
4
  export { ClaudeManagerPlugin };
5
5
  export declare const plugin: Plugin;
@@ -223,7 +223,7 @@ export class TeamOrchestrator {
223
223
  }
224
224
  static classifyError(error) {
225
225
  const message = error instanceof Error ? error.message : String(error);
226
- let failureKind = 'unknown';
226
+ let failureKind;
227
227
  if (message.includes('already working on another assignment')) {
228
228
  failureKind = 'engineerBusy';
229
229
  }
@@ -11,8 +11,8 @@ export declare const ENGINEER_AGENT_IDS: {
11
11
  };
12
12
  /** General named engineers only (Tom/John/Maya/Sara/Alex). BrowserQA is a specialist registered separately. */
13
13
  export declare const ENGINEER_AGENT_NAMES: readonly ["Tom", "John", "Maya", "Sara", "Alex"];
14
- export declare const CTO_ONLY_TOOL_IDS: readonly ["team_status", "reset_engineer", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update"];
15
- export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["team_status", "reset_engineer", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update", "claude"];
14
+ export declare const CTO_ONLY_TOOL_IDS: readonly ["team_status", "reset_engineer", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update", "inspect_team_inbox"];
15
+ export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["team_status", "reset_engineer", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update", "inspect_team_inbox", "claude"];
16
16
  export type ToolPermission = 'allow' | 'ask' | 'deny';
17
17
  export type AgentPermission = {
18
18
  '*'?: ToolPermission;
@@ -25,6 +25,7 @@ export const CTO_ONLY_TOOL_IDS = [
25
25
  'approval_policy',
26
26
  'approval_decisions',
27
27
  'approval_update',
28
+ 'inspect_team_inbox',
28
29
  ];
29
30
  const ENGINEER_TOOL_IDS = ['claude'];
30
31
  export const ALL_RESTRICTED_TOOL_IDS = [...CTO_ONLY_TOOL_IDS, ...ENGINEER_TOOL_IDS];
@@ -50,6 +51,10 @@ export function buildEngineerPermissions() {
50
51
  '*': 'deny',
51
52
  ...denied,
52
53
  claude: 'allow',
54
+ send_message: 'allow',
55
+ list_inbox: 'allow',
56
+ get_message: 'allow',
57
+ ack_message: 'allow',
53
58
  };
54
59
  }
55
60
  export function denyRestrictedToolsGlobally(permissions) {
@@ -4,6 +4,7 @@ import { appendDebugLog } from '../util/fs-helpers.js';
4
4
  import { isEngineerName } from '../team/roster.js';
5
5
  import { TeamOrchestrator, createActionableError, getFailureGuidanceText, } from '../manager/team-orchestrator.js';
6
6
  import { AGENT_BROWSER_QA, AGENT_CTO, AGENT_TEAM_PLANNER, buildBrowserQaAgentConfig, buildCtoAgentConfig, buildEngineerAgentConfig, buildTeamPlannerAgentConfig, denyRestrictedToolsGlobally, ENGINEER_AGENT_IDS, ENGINEER_AGENT_NAMES, } from './agents/index.js';
7
+ import { ackMessage, buildInboxNotice, getMessage, getUnreadCount, listAllInboxes, listInbox, sendMessage, } from './inbox-ops.js';
7
8
  import { clearLatestRevertProcessed, clearRevertProcessed, getOrCreatePluginServices, getParentSessionId, getSessionTeam, getWrapperSessionMapping, isRevertAlreadyProcessed, markRevertProcessed, registerParentSession, registerSessionTeam, setWrapperSessionMapping, } from './service-factory.js';
8
9
  const MODEL_ENUM = ['claude-opus-4-6', 'claude-sonnet-4-6'];
9
10
  const MODE_ENUM = ['explore', 'implement', 'verify'];
@@ -314,6 +315,14 @@ export const ClaudeManagerPlugin = async ({ worktree, client }) => {
314
315
  if (wrapperContext) {
315
316
  output.system.push(wrapperContext);
316
317
  }
318
+ // Inject inbox notice if the engineer has unread peer messages.
319
+ const team = await services.teamStore.getTeam(worktree, mapping.teamId);
320
+ if (team) {
321
+ const unreadCount = getUnreadCount(team, mapping.workerName);
322
+ if (unreadCount > 0) {
323
+ output.system.push(buildInboxNotice(unreadCount));
324
+ }
325
+ }
317
326
  },
318
327
  tool: {
319
328
  claude: tool({
@@ -436,6 +445,101 @@ export const ClaudeManagerPlugin = async ({ worktree, client }) => {
436
445
  return JSON.stringify({ reset: true, engineer: engineer ?? args.engineer }, null, 2);
437
446
  },
438
447
  }),
448
+ send_message: tool({
449
+ description: 'Send a peer message to another engineer on your team. Use this for requests, questions, and recommendations — not to assign work (only CTO assigns work). Reference files by path using the filePaths arg — never paste file contents. Supply replyToId when replying to keep thread context (reply chains are capped at 5 deep; loop in the CTO if deeper coordination is needed). If you receive conflicting guidance from multiple peers, stop and ask the CTO for direction.',
450
+ args: {
451
+ to: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex', 'BrowserQA']),
452
+ subject: tool.schema.string().min(1),
453
+ body: tool.schema.string().min(1),
454
+ filePaths: tool.schema.string().array().optional(),
455
+ replyToId: tool.schema.string().optional(),
456
+ },
457
+ async execute(args, context) {
458
+ const engineer = engineerFromAgent(context.agent);
459
+ const existing = getWrapperSessionMapping(context.worktree, context.sessionID);
460
+ const persisted = existing ??
461
+ (await services.orchestrator.findTeamByWrapperSession(context.worktree, context.sessionID));
462
+ const teamId = persisted?.teamId ?? (await resolveTeamId(context.sessionID));
463
+ annotateToolRun(context, `${engineer} → send_message to ${args.to}`, { teamId });
464
+ const result = await sendMessage(services.teamStore, context.worktree, teamId, engineer, args.to, args.subject, args.body, { filePaths: args.filePaths, replyToId: args.replyToId });
465
+ return JSON.stringify(result, null, 2);
466
+ },
467
+ }),
468
+ list_inbox: tool({
469
+ description: 'List your unread peer messages (ID, sender, subject, timestamp). Use get_message to read a full message body.',
470
+ args: {},
471
+ async execute(_args, context) {
472
+ const engineer = engineerFromAgent(context.agent);
473
+ const existing = getWrapperSessionMapping(context.worktree, context.sessionID);
474
+ const persisted = existing ??
475
+ (await services.orchestrator.findTeamByWrapperSession(context.worktree, context.sessionID));
476
+ const teamId = persisted?.teamId ?? (await resolveTeamId(context.sessionID));
477
+ annotateToolRun(context, `${engineer} → list_inbox`, { teamId });
478
+ const messages = await listInbox(services.teamStore, context.worktree, teamId, engineer);
479
+ const summaries = messages.map(({ id, fromEngineer, subject, createdAt, depth, replyToId, filePaths }) => ({
480
+ id,
481
+ from: fromEngineer,
482
+ subject,
483
+ createdAt,
484
+ depth,
485
+ ...(replyToId !== undefined && { replyToId }),
486
+ ...(filePaths !== undefined && filePaths.length > 0 && { filePaths }),
487
+ }));
488
+ return JSON.stringify({ count: summaries.length, messages: summaries }, null, 2);
489
+ },
490
+ }),
491
+ get_message: tool({
492
+ description: 'Read the full body of a peer message by its ID.',
493
+ args: {
494
+ messageId: tool.schema.string().min(1),
495
+ },
496
+ async execute(args, context) {
497
+ const engineer = engineerFromAgent(context.agent);
498
+ const existing = getWrapperSessionMapping(context.worktree, context.sessionID);
499
+ const persisted = existing ??
500
+ (await services.orchestrator.findTeamByWrapperSession(context.worktree, context.sessionID));
501
+ const teamId = persisted?.teamId ?? (await resolveTeamId(context.sessionID));
502
+ annotateToolRun(context, `${engineer} → get_message`, { teamId, messageId: args.messageId });
503
+ const message = await getMessage(services.teamStore, context.worktree, teamId, engineer, args.messageId);
504
+ if (!message) {
505
+ return JSON.stringify({ error: 'Message not found.' });
506
+ }
507
+ return JSON.stringify(message, null, 2);
508
+ },
509
+ }),
510
+ ack_message: tool({
511
+ description: 'Acknowledge a peer message to hide it from your inbox. The message is retained for CTO inspection but will not appear in future list_inbox results.',
512
+ args: {
513
+ messageId: tool.schema.string().min(1),
514
+ },
515
+ async execute(args, context) {
516
+ const engineer = engineerFromAgent(context.agent);
517
+ const existing = getWrapperSessionMapping(context.worktree, context.sessionID);
518
+ const persisted = existing ??
519
+ (await services.orchestrator.findTeamByWrapperSession(context.worktree, context.sessionID));
520
+ const teamId = persisted?.teamId ?? (await resolveTeamId(context.sessionID));
521
+ annotateToolRun(context, `${engineer} → ack_message`, { teamId, messageId: args.messageId });
522
+ const ok = await ackMessage(services.teamStore, context.worktree, teamId, engineer, args.messageId);
523
+ return JSON.stringify({ acked: ok }, null, 2);
524
+ },
525
+ }),
526
+ inspect_team_inbox: tool({
527
+ description: 'Read all peer messages across all engineers on the team, including acked messages. Optionally filter by engineer.',
528
+ args: {
529
+ teamId: tool.schema.string().optional(),
530
+ engineer: tool.schema.enum(['Tom', 'John', 'Maya', 'Sara', 'Alex', 'BrowserQA']).optional(),
531
+ },
532
+ async execute(args, context) {
533
+ const teamId = args.teamId ?? context.sessionID;
534
+ annotateToolRun(context, 'Inspecting team inbox', { teamId, engineer: args.engineer });
535
+ const team = await services.orchestrator.getOrCreateTeam(context.worktree, teamId);
536
+ const allInboxes = listAllInboxes(team);
537
+ if (args.engineer) {
538
+ return JSON.stringify({ [args.engineer]: allInboxes[args.engineer] ?? [] }, null, 2);
539
+ }
540
+ return JSON.stringify(allInboxes, null, 2);
541
+ },
542
+ }),
439
543
  git_diff: tool({
440
544
  description: 'Show diff of uncommitted changes. Use paths to filter to specific files or use ref to compare against another branch, tag, or commit.',
441
545
  args: {
@@ -0,0 +1,50 @@
1
+ import type { AgentMessage, EngineerName, TeamRecord } from '../types/contracts.js';
2
+ import type { TeamStateStore } from '../state/team-state-store.js';
3
+ export declare const MAX_INBOX_SIZE = 50;
4
+ export declare const MAX_MESSAGE_BODY_LENGTH = 4000;
5
+ export declare const MAX_MESSAGE_SUBJECT_LENGTH = 200;
6
+ /**
7
+ * Maximum reply-chain depth. A top-level message has depth 0; each reply
8
+ * increments by 1. Attempts to exceed this cap are rejected with a message
9
+ * directing the agent to loop in the CTO.
10
+ */
11
+ export declare const MAX_THREAD_DEPTH = 5;
12
+ export type SendMessageResult = {
13
+ ok: true;
14
+ message: AgentMessage;
15
+ } | {
16
+ ok: false;
17
+ error: string;
18
+ };
19
+ export interface SendMessageOptions {
20
+ /** File paths referenced in the message. Paths only — contents are never embedded. */
21
+ filePaths?: string[];
22
+ /**
23
+ * ID of the message being replied to. Must be a message in the sender's own inbox.
24
+ * Providing this increments the thread depth and enforces the MAX_THREAD_DEPTH cap.
25
+ */
26
+ replyToId?: string;
27
+ }
28
+ /**
29
+ * Deliver a peer message from one engineer to another on the same team.
30
+ * All validation and persistence happen atomically via the write queue.
31
+ */
32
+ export declare function sendMessage(store: TeamStateStore, cwd: string, teamId: string, fromEngineer: EngineerName, toEngineer: EngineerName, subject: string, body: string, options?: SendMessageOptions): Promise<SendMessageResult>;
33
+ /**
34
+ * Return all unread messages for an engineer, in FIFO order.
35
+ * Acked messages are excluded.
36
+ */
37
+ export declare function listInbox(store: TeamStateStore, cwd: string, teamId: string, engineerName: EngineerName): Promise<AgentMessage[]>;
38
+ /** Fetch a single message by ID. Returns null if not found or if it belongs to a different engineer. */
39
+ export declare function getMessage(store: TeamStateStore, cwd: string, teamId: string, engineerName: EngineerName, messageId: string): Promise<AgentMessage | null>;
40
+ /**
41
+ * Mark a message as acked (hidden from list_inbox).
42
+ * Returns true if the ack was applied, false if the message was not found or already acked.
43
+ */
44
+ export declare function ackMessage(store: TeamStateStore, cwd: string, teamId: string, engineerName: EngineerName, messageId: string): Promise<boolean>;
45
+ /** Count unread messages for one engineer without hitting the store. */
46
+ export declare function getUnreadCount(team: TeamRecord, engineerName: EngineerName): number;
47
+ /** Build a concise notice to inject into the engineer's system prompt. */
48
+ export declare function buildInboxNotice(unreadCount: number): string;
49
+ /** Return all inboxes on a team (including acked messages) for CTO inspection. */
50
+ export declare function listAllInboxes(team: TeamRecord): Record<string, AgentMessage[]>;