@agentuity/opencode 0.1.40 → 0.1.42

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 (165) hide show
  1. package/README.md +324 -19
  2. package/dist/agents/architect.d.ts +4 -0
  3. package/dist/agents/architect.d.ts.map +1 -0
  4. package/dist/agents/architect.js +259 -0
  5. package/dist/agents/architect.js.map +1 -0
  6. package/dist/agents/builder.d.ts +1 -1
  7. package/dist/agents/builder.d.ts.map +1 -1
  8. package/dist/agents/builder.js +44 -1
  9. package/dist/agents/builder.js.map +1 -1
  10. package/dist/agents/index.d.ts.map +1 -1
  11. package/dist/agents/index.js +6 -0
  12. package/dist/agents/index.js.map +1 -1
  13. package/dist/agents/lead.d.ts +1 -1
  14. package/dist/agents/lead.d.ts.map +1 -1
  15. package/dist/agents/lead.js +185 -22
  16. package/dist/agents/lead.js.map +1 -1
  17. package/dist/agents/planner.d.ts +4 -0
  18. package/dist/agents/planner.d.ts.map +1 -0
  19. package/dist/agents/planner.js +158 -0
  20. package/dist/agents/planner.js.map +1 -0
  21. package/dist/agents/runner.d.ts +4 -0
  22. package/dist/agents/runner.d.ts.map +1 -0
  23. package/dist/agents/runner.js +364 -0
  24. package/dist/agents/runner.js.map +1 -0
  25. package/dist/agents/types.d.ts +5 -1
  26. package/dist/agents/types.d.ts.map +1 -1
  27. package/dist/background/concurrency.d.ts +36 -0
  28. package/dist/background/concurrency.d.ts.map +1 -0
  29. package/dist/background/concurrency.js +92 -0
  30. package/dist/background/concurrency.js.map +1 -0
  31. package/dist/background/index.d.ts +5 -0
  32. package/dist/background/index.d.ts.map +1 -0
  33. package/dist/background/index.js +4 -0
  34. package/dist/background/index.js.map +1 -0
  35. package/dist/background/manager.d.ts +54 -0
  36. package/dist/background/manager.d.ts.map +1 -0
  37. package/dist/background/manager.js +409 -0
  38. package/dist/background/manager.js.map +1 -0
  39. package/dist/background/types.d.ts +47 -0
  40. package/dist/background/types.d.ts.map +1 -0
  41. package/dist/background/types.js +2 -0
  42. package/dist/background/types.js.map +1 -0
  43. package/dist/config/index.d.ts +2 -0
  44. package/dist/config/index.d.ts.map +1 -1
  45. package/dist/config/index.js +2 -0
  46. package/dist/config/index.js.map +1 -1
  47. package/dist/config/loader.d.ts +24 -0
  48. package/dist/config/loader.d.ts.map +1 -1
  49. package/dist/config/loader.js +102 -23
  50. package/dist/config/loader.js.map +1 -1
  51. package/dist/config/presets.d.ts +16 -0
  52. package/dist/config/presets.d.ts.map +1 -0
  53. package/dist/config/presets.js +20 -0
  54. package/dist/config/presets.js.map +1 -0
  55. package/dist/config/validation.d.ts +26 -0
  56. package/dist/config/validation.d.ts.map +1 -0
  57. package/dist/config/validation.js +48 -0
  58. package/dist/config/validation.js.map +1 -0
  59. package/dist/index.d.ts +1 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/plugin/hooks/cadence.d.ts.map +1 -1
  63. package/dist/plugin/hooks/cadence.js +3 -1
  64. package/dist/plugin/hooks/cadence.js.map +1 -1
  65. package/dist/plugin/hooks/keyword.d.ts.map +1 -1
  66. package/dist/plugin/hooks/keyword.js +3 -0
  67. package/dist/plugin/hooks/keyword.js.map +1 -1
  68. package/dist/plugin/plugin.d.ts.map +1 -1
  69. package/dist/plugin/plugin.js +335 -36
  70. package/dist/plugin/plugin.js.map +1 -1
  71. package/dist/skills/frontmatter.d.ts +7 -0
  72. package/dist/skills/frontmatter.d.ts.map +1 -0
  73. package/dist/skills/frontmatter.js +17 -0
  74. package/dist/skills/frontmatter.js.map +1 -0
  75. package/dist/skills/index.d.ts +4 -0
  76. package/dist/skills/index.d.ts.map +1 -0
  77. package/dist/skills/index.js +4 -0
  78. package/dist/skills/index.js.map +1 -0
  79. package/dist/skills/loader.d.ts +20 -0
  80. package/dist/skills/loader.d.ts.map +1 -0
  81. package/dist/skills/loader.js +152 -0
  82. package/dist/skills/loader.js.map +1 -0
  83. package/dist/skills/types.d.ts +41 -0
  84. package/dist/skills/types.d.ts.map +1 -0
  85. package/dist/skills/types.js +2 -0
  86. package/dist/skills/types.js.map +1 -0
  87. package/dist/tmux/decision-engine.d.ts +24 -0
  88. package/dist/tmux/decision-engine.d.ts.map +1 -0
  89. package/dist/tmux/decision-engine.js +193 -0
  90. package/dist/tmux/decision-engine.js.map +1 -0
  91. package/dist/tmux/executor.d.ts +84 -0
  92. package/dist/tmux/executor.d.ts.map +1 -0
  93. package/dist/tmux/executor.js +546 -0
  94. package/dist/tmux/executor.js.map +1 -0
  95. package/dist/tmux/index.d.ts +7 -0
  96. package/dist/tmux/index.d.ts.map +1 -0
  97. package/dist/tmux/index.js +7 -0
  98. package/dist/tmux/index.js.map +1 -0
  99. package/dist/tmux/manager.d.ts +116 -0
  100. package/dist/tmux/manager.d.ts.map +1 -0
  101. package/dist/tmux/manager.js +488 -0
  102. package/dist/tmux/manager.js.map +1 -0
  103. package/dist/tmux/state-query.d.ts +7 -0
  104. package/dist/tmux/state-query.d.ts.map +1 -0
  105. package/dist/tmux/state-query.js +70 -0
  106. package/dist/tmux/state-query.js.map +1 -0
  107. package/dist/tmux/types.d.ts +97 -0
  108. package/dist/tmux/types.d.ts.map +1 -0
  109. package/dist/tmux/types.js +8 -0
  110. package/dist/tmux/types.js.map +1 -0
  111. package/dist/tmux/utils.d.ts +32 -0
  112. package/dist/tmux/utils.d.ts.map +1 -0
  113. package/dist/tmux/utils.js +80 -0
  114. package/dist/tmux/utils.js.map +1 -0
  115. package/dist/tools/background.d.ts +61 -0
  116. package/dist/tools/background.d.ts.map +1 -0
  117. package/dist/tools/background.js +78 -0
  118. package/dist/tools/background.js.map +1 -0
  119. package/dist/tools/delegate.d.ts +6 -0
  120. package/dist/tools/delegate.d.ts.map +1 -1
  121. package/dist/tools/delegate.js +8 -2
  122. package/dist/tools/delegate.js.map +1 -1
  123. package/dist/tools/index.d.ts +1 -0
  124. package/dist/tools/index.d.ts.map +1 -1
  125. package/dist/tools/index.js +1 -0
  126. package/dist/tools/index.js.map +1 -1
  127. package/dist/types.d.ts +118 -18
  128. package/dist/types.d.ts.map +1 -1
  129. package/dist/types.js +49 -7
  130. package/dist/types.js.map +1 -1
  131. package/package.json +4 -3
  132. package/src/agents/architect.ts +262 -0
  133. package/src/agents/builder.ts +44 -1
  134. package/src/agents/index.ts +6 -0
  135. package/src/agents/lead.ts +185 -22
  136. package/src/agents/planner.ts +161 -0
  137. package/src/agents/runner.ts +367 -0
  138. package/src/agents/types.ts +5 -1
  139. package/src/background/concurrency.ts +116 -0
  140. package/src/background/index.ts +4 -0
  141. package/src/background/manager.ts +478 -0
  142. package/src/background/types.ts +52 -0
  143. package/src/config/index.ts +2 -0
  144. package/src/config/loader.ts +128 -31
  145. package/src/config/presets.ts +21 -0
  146. package/src/config/validation.ts +70 -0
  147. package/src/index.ts +1 -0
  148. package/src/plugin/hooks/cadence.ts +2 -1
  149. package/src/plugin/hooks/keyword.ts +3 -0
  150. package/src/plugin/plugin.ts +374 -42
  151. package/src/skills/frontmatter.ts +25 -0
  152. package/src/skills/index.ts +3 -0
  153. package/src/skills/loader.ts +185 -0
  154. package/src/skills/types.ts +43 -0
  155. package/src/tmux/decision-engine.ts +246 -0
  156. package/src/tmux/executor.ts +618 -0
  157. package/src/tmux/index.ts +14 -0
  158. package/src/tmux/manager.ts +577 -0
  159. package/src/tmux/state-query.ts +77 -0
  160. package/src/tmux/types.ts +107 -0
  161. package/src/tmux/utils.ts +85 -0
  162. package/src/tools/background.ts +145 -0
  163. package/src/tools/delegate.ts +8 -2
  164. package/src/tools/index.ts +9 -0
  165. package/src/types.ts +88 -15
@@ -1,24 +1,58 @@
1
1
  import type { PluginInput, Hooks } from '@opencode-ai/plugin';
2
+ import { tool } from '@opencode-ai/plugin';
2
3
  import type { AgentConfig, CommandDefinition } from '../types';
4
+ import { loadAllSkills, type LoadedSkill } from '../skills';
3
5
  import { agents } from '../agents';
4
- import { loadCoderConfig, getDefaultConfig, mergeConfig } from '../config';
6
+ import { loadCoderConfig, getDefaultConfig, mergeConfig, validateAndWarnConfigs } from '../config';
5
7
  import { createSessionHooks } from './hooks/session';
6
8
  import { createToolHooks } from './hooks/tools';
7
9
  import { createKeywordHooks } from './hooks/keyword';
8
10
  import { createParamsHooks } from './hooks/params';
9
11
  import { createCadenceHooks } from './hooks/cadence';
10
12
  import { createSessionMemoryHooks } from './hooks/session-memory';
11
- import { z } from 'zod';
12
13
  import type { AgentRole } from '../types';
14
+ import { BackgroundManager } from '../background';
15
+ import { TmuxSessionManager } from '../tmux';
16
+
17
+ // Sandbox environment detection
18
+ const SANDBOX_ID = process.env.AGENTUITY_SANDBOX_ID;
19
+ const IN_SANDBOX = !!SANDBOX_ID;
20
+
21
+ // Sandbox context injected into Lead, Builder, and Architect prompts
22
+ const SANDBOX_CONTEXT = IN_SANDBOX
23
+ ? `
24
+ ## Sandbox Environment
25
+
26
+ You are running inside an Agentuity Sandbox (ID: ${SANDBOX_ID}).
27
+
28
+ **Permissions:** All file operations are allowed without prompts.
29
+
30
+ **File Locations:**
31
+ - Working directory: \`/home/agentuity\`
32
+ - Temp files: \`/home/agentuity/tmp/\` (preferred over \`/tmp/\`)
33
+ - Artifacts: \`/home/agentuity/.agentuity/\`
34
+
35
+ **Tips:**
36
+ - No permission prompts - you can read/write freely
37
+ - Sandbox is isolated - safe to experiment
38
+ - Use \`/home/agentuity/\` paths for all file operations
39
+ `
40
+ : '';
41
+
42
+ // Agents that should receive sandbox context in their prompts
43
+ const SANDBOX_AWARE_AGENTS: AgentRole[] = ['lead', 'builder', 'architect'];
13
44
 
14
45
  // Agent display names for @mentions
15
46
  const AGENT_MENTIONS: Record<AgentRole, string> = {
16
47
  lead: '@Agentuity Coder Lead',
17
48
  scout: '@Agentuity Coder Scout',
18
49
  builder: '@Agentuity Coder Builder',
50
+ architect: '@Agentuity Coder Architect',
19
51
  reviewer: '@Agentuity Coder Reviewer',
20
52
  memory: '@Agentuity Coder Memory',
21
53
  expert: '@Agentuity Coder Expert',
54
+ planner: '@Agentuity Coder Planner',
55
+ runner: '@Agentuity Coder Runner',
22
56
  };
23
57
 
24
58
  export async function createCoderPlugin(ctx: PluginInput): Promise<Hooks> {
@@ -38,6 +72,30 @@ export async function createCoderPlugin(ctx: PluginInput): Promise<Hooks> {
38
72
  const keywordHooks = createKeywordHooks(ctx, coderConfig);
39
73
  const paramsHooks = createParamsHooks(ctx, coderConfig);
40
74
  const cadenceHooks = createCadenceHooks(ctx, coderConfig);
75
+ const tmuxManager = coderConfig.tmux?.enabled
76
+ ? new TmuxSessionManager(ctx, coderConfig.tmux, {
77
+ onLog: (message) =>
78
+ ctx.client.app.log({
79
+ body: {
80
+ service: 'agentuity-coder',
81
+ level: 'info',
82
+ message,
83
+ },
84
+ }),
85
+ })
86
+ : undefined;
87
+ const backgroundManager = new BackgroundManager(ctx, coderConfig.background, {
88
+ onSubagentSessionCreated: tmuxManager
89
+ ? (event) => {
90
+ void tmuxManager.onSessionCreated(event);
91
+ }
92
+ : undefined,
93
+ onSubagentSessionDeleted: tmuxManager
94
+ ? (event) => {
95
+ void tmuxManager.onSessionDeleted(event);
96
+ }
97
+ : undefined,
98
+ });
41
99
 
42
100
  // Session memory hooks handle checkpointing and compaction for non-Cadence sessions
43
101
  // Orchestration (deciding which module handles which session) happens below in the hooks
@@ -45,12 +103,20 @@ export async function createCoderPlugin(ctx: PluginInput): Promise<Hooks> {
45
103
 
46
104
  const configHandler = createConfigHandler(coderConfig);
47
105
 
48
- // Get the tool helper from Open Code context if available
49
- const toolHelper = (ctx as { tool?: unknown }).tool as
50
- | ((schema: (s: typeof z) => unknown) => unknown)
51
- | undefined;
106
+ // Create plugin tools using the @opencode-ai/plugin tool helper
107
+ const tools = createTools(backgroundManager);
108
+
109
+ // Create a logger for shutdown handler
110
+ const shutdownLogger = (message: string) =>
111
+ ctx.client.app.log({
112
+ body: {
113
+ service: 'agentuity-coder',
114
+ level: 'info',
115
+ message: `[shutdown] ${message}`,
116
+ },
117
+ });
52
118
 
53
- const tools = toolHelper ? createTools(toolHelper) : undefined;
119
+ registerShutdownHandler(backgroundManager, tmuxManager, shutdownLogger);
54
120
 
55
121
  // Show startup toast (fire and forget, don't block)
56
122
  try {
@@ -73,6 +139,10 @@ export async function createCoderPlugin(ctx: PluginInput): Promise<Hooks> {
73
139
  'tool.execute.before': toolHooks.before,
74
140
  'tool.execute.after': toolHooks.after,
75
141
  event: async (input) => {
142
+ const event = extractEventFromInput(input);
143
+ if (event) {
144
+ backgroundManager.handleEvent(event);
145
+ }
76
146
  // Orchestrate: route to appropriate module based on session type
77
147
  const sessionId = extractSessionIdFromEvent(input);
78
148
  if (sessionId && cadenceHooks.isActiveCadenceSession(sessionId)) {
@@ -101,27 +171,60 @@ function createConfigHandler(
101
171
  return async (config: Record<string, unknown>) => {
102
172
  const agentConfigs = createAgentConfigs(coderConfig);
103
173
  const commands = createCommands();
174
+ const loadedSkills = await loadAllSkills(coderConfig.skills);
175
+ const skillCommands = createSkillCommands(loadedSkills);
176
+
177
+ // Merge agent configs: our defaults first, then user's opencode.json overrides on top
178
+ // This allows users to customize any agent via their opencode.json
179
+ const userAgentConfigs = config.agent as Record<string, AgentConfig> | undefined;
180
+ const mergedAgents: Record<string, AgentConfig> = { ...agentConfigs };
181
+
182
+ // Shallow merge user overrides on top of our defaults (nested objects like tools are replaced, not merged)
183
+ if (userAgentConfigs) {
184
+ for (const [name, userConfig] of Object.entries(userAgentConfigs)) {
185
+ if (mergedAgents[name]) {
186
+ // Merge user config on top of our default
187
+ mergedAgents[name] = {
188
+ ...mergedAgents[name],
189
+ ...userConfig,
190
+ };
191
+ } else {
192
+ // User defined a new agent not in our defaults
193
+ mergedAgents[name] = userConfig;
194
+ }
195
+ }
196
+ }
104
197
 
105
- config.agent = {
106
- ...(config.agent as Record<string, AgentConfig> | undefined),
107
- ...agentConfigs,
108
- };
198
+ config.agent = mergedAgents;
199
+
200
+ // Validate merged configs and warn about mismatches
201
+ validateAndWarnConfigs(mergedAgents);
202
+
203
+ // In sandbox, allow all permissions without prompts
204
+ if (IN_SANDBOX) {
205
+ config.permission = {
206
+ '*': 'allow',
207
+ external_directory: {
208
+ '/home/agentuity/**': 'allow',
209
+ '*': 'allow',
210
+ },
211
+ };
212
+ }
109
213
 
110
214
  config.command = {
111
215
  ...(config.command as Record<string, CommandDefinition> | undefined),
112
216
  ...commands,
217
+ ...skillCommands,
113
218
  };
114
219
  };
115
220
  }
116
221
 
117
222
  function createAgentConfigs(
118
- config: ReturnType<typeof getDefaultConfig>
223
+ _config: ReturnType<typeof getDefaultConfig>
119
224
  ): Record<string, AgentConfig> {
120
225
  const result: Record<string, AgentConfig> = {};
121
226
 
122
227
  for (const agent of Object.values(agents)) {
123
- const modelConfig = config.agents?.[agent.role];
124
-
125
228
  // Convert tools.exclude to Open Code format (tool: false)
126
229
  const tools: Record<string, boolean> = {};
127
230
  if (agent.tools?.exclude) {
@@ -130,16 +233,25 @@ function createAgentConfigs(
130
233
  }
131
234
  }
132
235
 
236
+ // Inject sandbox context into specific agents when running in sandbox
237
+ const shouldInjectSandbox =
238
+ IN_SANDBOX && SANDBOX_AWARE_AGENTS.includes(agent.role as AgentRole);
239
+ const prompt = shouldInjectSandbox
240
+ ? `${agent.systemPrompt}\n${SANDBOX_CONTEXT}`
241
+ : agent.systemPrompt;
242
+
243
+ // Use agent defaults directly - user overrides happen in createConfigHandler
133
244
  result[agent.displayName] = {
134
245
  description: agent.description,
135
- model: modelConfig?.model ?? agent.defaultModel,
136
- prompt: agent.systemPrompt,
246
+ model: agent.defaultModel,
247
+ prompt,
137
248
  mode: agent.mode ?? 'subagent',
138
249
  ...(Object.keys(tools).length > 0 ? { tools } : {}),
139
- // Pass through thinking/reasoning settings
140
250
  ...(agent.variant ? { variant: agent.variant } : {}),
141
251
  ...(agent.temperature !== undefined ? { temperature: agent.temperature } : {}),
142
252
  ...(agent.maxSteps !== undefined ? { maxSteps: agent.maxSteps } : {}),
253
+ ...(agent.reasoningEffort ? { reasoningEffort: agent.reasoningEffort } : {}),
254
+ ...(agent.thinking ? { thinking: agent.thinking } : {}),
143
255
  };
144
256
  }
145
257
 
@@ -158,20 +270,24 @@ You are the Agentuity Coder Lead agent orchestrating the Agentuity Coder team.
158
270
  ## Your Team (use @mentions to invoke)
159
271
  - **@Agentuity Coder Scout**: Explore codebase, find patterns, research docs (read-only)
160
272
  - **@Agentuity Coder Builder**: Implement features, write code, run tests
273
+ - **@Agentuity Coder Architect**: Complex autonomous tasks, Cadence mode (GPT Codex)
161
274
  - **@Agentuity Coder Reviewer**: Review changes, catch issues, apply fixes
162
275
  - **@Agentuity Coder Memory**: Store context, remember decisions
163
276
  - **@Agentuity Coder Expert**: Agentuity CLI and cloud services specialist
277
+ - **@Agentuity Coder Planner**: Deep planning for complex architecture decisions
278
+ - **@Agentuity Coder Runner**: Run lint/build/test commands, returns structured results
164
279
 
165
280
  ## Task
166
281
  $ARGUMENTS
167
282
 
168
283
  ## Guidelines
169
284
  1. Use @Agentuity Coder Scout first to understand context
170
- 2. Delegate implementation to @Agentuity Coder Builder
171
- 3. Have @Agentuity Coder Reviewer check the work
172
- 4. Use @Agentuity Coder Expert for Agentuity CLI questions
173
- 5. Only use cloud services when genuinely helpful
174
- 6. **When done, tell @Agentuity Coder Memory to memorialize the session**
285
+ 2. Delegate implementation to @Agentuity Coder Builder (or Architect for complex work)
286
+ 3. Delegate lint/build/test commands to @Agentuity Coder Runner for structured results
287
+ 4. Have @Agentuity Coder Reviewer check the work
288
+ 5. Use @Agentuity Coder Expert for Agentuity CLI questions
289
+ 6. Only use cloud services when genuinely helpful
290
+ 7. **When done, tell @Agentuity Coder Memory to memorialize the session**
175
291
  </coder-mode>`,
176
292
  agent: 'Agentuity Coder Lead',
177
293
  argumentHint: '"task description"',
@@ -289,10 +405,13 @@ You are the Agentuity Coder Lead in **Cadence mode** — a long-running autonomo
289
405
 
290
406
  ## Your Team (use @mentions to invoke)
291
407
  - **@Agentuity Coder Scout**: Explore codebase, find patterns, research docs (read-only)
292
- - **@Agentuity Coder Builder**: Implement features, write code, run tests
408
+ - **@Agentuity Coder Architect**: Complex autonomous implementation (GPT Codex with high reasoning) — **USE THIS FOR CADENCE**
409
+ - **@Agentuity Coder Builder**: Quick fixes, simple changes (for minor iterations only)
293
410
  - **@Agentuity Coder Reviewer**: Review changes, catch issues, apply fixes
294
411
  - **@Agentuity Coder Memory**: Store context, remember decisions, checkpoints
295
412
  - **@Agentuity Coder Expert**: Agentuity CLI and cloud services specialist
413
+ - **@Agentuity Coder Planner**: Deep planning for complex architecture decisions
414
+ - **@Agentuity Coder Runner**: Run lint/build/test commands, returns structured results
296
415
 
297
416
  ## Task
298
417
  $ARGUMENTS
@@ -306,7 +425,8 @@ $ARGUMENTS
306
425
  2. **Each iteration**:
307
426
  - Ask @Agentuity Coder Memory for relevant context
308
427
  - Use @Agentuity Coder Scout to understand what's needed
309
- - Delegate implementation to @Agentuity Coder Builder
428
+ - For complex planning, consult @Agentuity Coder Planner
429
+ - Delegate implementation to **@Agentuity Coder Architect** (preferred for Cadence)
310
430
  - Have @Agentuity Coder Reviewer verify the work
311
431
  - Tell @Agentuity Coder Memory to store checkpoint
312
432
 
@@ -318,10 +438,11 @@ $ARGUMENTS
318
438
  4. **Tell @Agentuity Coder Memory to memorialize** the completed session
319
439
 
320
440
  ## Guidelines
321
- - **Always delegate** — use Scout for research, Builder for code, Reviewer for verification
441
+ - **Use Architect for implementation** — Architect has GPT Codex with maximum reasoning, ideal for autonomous work
442
+ - Use regular Builder only for trivial fixes within an iteration
322
443
  - Ask Memory for context at each iteration start
323
444
  - Store checkpoints at each iteration end
324
- - If stuck, ask Scout to re-evaluate before pausing
445
+ - If stuck on architecture, consult Planner before trying more approaches
325
446
  - Use @Agentuity Coder Expert for sandbox/cloud operations
326
447
  - Respect max iterations (50 default)`,
327
448
  agent: 'Agentuity Coder Lead',
@@ -330,40 +451,178 @@ $ARGUMENTS
330
451
  };
331
452
  }
332
453
 
333
- function createTools(tool: (schema: (s: typeof z) => unknown) => unknown): Hooks['tool'] {
334
- const coderDelegate = tool((s) => ({
454
+ function createSkillCommands(skills: LoadedSkill[]): Record<string, CommandDefinition> {
455
+ const commands: Record<string, CommandDefinition> = {};
456
+
457
+ for (const skill of skills) {
458
+ const baseDir = normalizeBaseDir(skill.resolvedPath);
459
+ commands[skill.name] = {
460
+ name: skill.name,
461
+ description: skill.metadata.description,
462
+ template: `<skill-instruction>
463
+ Base directory for this skill: ${baseDir}/
464
+ File references (@path) in this skill are relative to this directory.
465
+
466
+ ${skill.content}
467
+ </skill-instruction>
468
+
469
+ <user-request>
470
+ $ARGUMENTS
471
+ </user-request>`,
472
+ ...(skill.metadata.agent ? { agent: skill.metadata.agent } : {}),
473
+ ...(skill.metadata.model ? { model: skill.metadata.model } : {}),
474
+ ...(skill.metadata['argument-hint']
475
+ ? { argumentHint: skill.metadata['argument-hint'] }
476
+ : {}),
477
+ ...(skill.metadata.subtask ? { subtask: true } : {}),
478
+ };
479
+ }
480
+
481
+ return commands;
482
+ }
483
+
484
+ function normalizeBaseDir(path: string): string {
485
+ return path.replace(/[\\/]+$/, '');
486
+ }
487
+
488
+ function createTools(backgroundManager: BackgroundManager): Hooks['tool'] {
489
+ // Use the schema from @opencode-ai/plugin's tool helper to avoid Zod version mismatches
490
+ const s = tool.schema;
491
+
492
+ const coderDelegate = tool({
335
493
  description: `Delegate a task to a specialized Agentuity Coder agent.
336
494
 
337
495
  Use this to:
338
496
  - Scout: Explore codebase, find patterns, research documentation
339
- - Builder: Implement features, write code, run tests
497
+ - Builder: Implement features, write code, run tests (interactive work)
498
+ - Architect: Complex autonomous tasks, Cadence mode, deep reasoning (GPT Codex)
340
499
  - Reviewer: Review changes, catch issues, apply fixes
341
500
  - Memory: Store context, remember decisions across sessions
342
- - Expert: Get help with Agentuity CLI and cloud services`,
343
- args: s.object({
501
+ - Expert: Get help with Agentuity CLI and cloud services
502
+ - Planner: Strategic advisor for complex architecture and deep planning (read-only)`,
503
+ args: {
344
504
  agent: s
345
- .enum(['scout', 'builder', 'reviewer', 'memory', 'expert'])
505
+ .enum([
506
+ 'scout',
507
+ 'builder',
508
+ 'architect',
509
+ 'reviewer',
510
+ 'memory',
511
+ 'expert',
512
+ 'planner',
513
+ 'runner',
514
+ ])
346
515
  .describe('Which agent to delegate to'),
347
516
  task: s.string().describe('Clear description of the task'),
348
517
  context: s.string().optional().describe('Additional context from previous tasks'),
349
- }),
350
- execute: async (args: { agent: AgentRole; task: string; context?: string }) => {
351
- const mention = AGENT_MENTIONS[args.agent];
518
+ },
519
+ async execute(args) {
520
+ const mention = AGENT_MENTIONS[args.agent as AgentRole];
352
521
  let prompt = `${mention}\n\n## Task\n${args.task}`;
353
522
  if (args.context) {
354
523
  prompt = `${mention}\n\n## Context\n${args.context}\n\n## Task\n${args.task}`;
355
524
  }
356
- return {
357
- output: `To delegate this task, use the Task tool with this prompt:\n\n${prompt}\n\nThe ${args.agent} agent will handle this task.`,
358
- };
525
+ return `To delegate this task, use the Task tool with this prompt:\n\n${prompt}\n\nThe ${args.agent} agent will handle this task.`;
526
+ },
527
+ });
528
+
529
+ const backgroundTask = tool({
530
+ description: `Launch a task to run in the background. Use this for parallel execution of multiple independent tasks.
531
+
532
+ IMPORTANT: Use this tool instead of the 'task' tool when:
533
+ - You need to run multiple agents in parallel
534
+ - Tasks are independent and don't need sequential execution
535
+ - The user asks for "parallel", "background", or "concurrent" work`,
536
+ args: {
537
+ agent: s
538
+ .enum([
539
+ 'lead',
540
+ 'scout',
541
+ 'builder',
542
+ 'architect',
543
+ 'reviewer',
544
+ 'memory',
545
+ 'expert',
546
+ 'planner',
547
+ 'runner',
548
+ ])
549
+ .describe('Agent role to run the task'),
550
+ task: s.string().describe('Task prompt to run in the background'),
551
+ description: s.string().optional().describe('Short description of the task'),
552
+ },
553
+ async execute(args, context) {
554
+ const parentSessionId = context.sessionID;
555
+ if (!parentSessionId) {
556
+ return JSON.stringify({
557
+ taskId: 'unknown',
558
+ status: 'error',
559
+ message: 'Missing session context for background task.',
560
+ });
561
+ }
562
+
563
+ const agentName = resolveAgentName(args.agent as AgentRole);
564
+ const bgTask = await backgroundManager.launch({
565
+ description: args.description ?? args.task,
566
+ prompt: args.task,
567
+ agent: agentName,
568
+ parentSessionId,
569
+ parentMessageId: context.messageID,
570
+ });
571
+ return JSON.stringify({
572
+ taskId: bgTask.id,
573
+ status: bgTask.status,
574
+ message:
575
+ bgTask.status === 'error'
576
+ ? (bgTask.error ?? 'Failed to launch background task.')
577
+ : 'Background task launched.',
578
+ });
579
+ },
580
+ });
581
+
582
+ const backgroundOutput = tool({
583
+ description: 'Retrieve output for a background task.',
584
+ args: {
585
+ task_id: s.string().describe('Background task ID'),
359
586
  },
360
- }));
587
+ async execute(args) {
588
+ const bgTask = backgroundManager.getTask(args.task_id);
589
+ if (!bgTask) {
590
+ return JSON.stringify({
591
+ taskId: args.task_id,
592
+ status: 'error',
593
+ error: 'Task not found.',
594
+ });
595
+ }
596
+ return JSON.stringify({
597
+ taskId: bgTask.id,
598
+ status: bgTask.status,
599
+ result: bgTask.result,
600
+ error: bgTask.error,
601
+ });
602
+ },
603
+ });
604
+
605
+ const backgroundCancel = tool({
606
+ description: 'Cancel a running background task.',
607
+ args: {
608
+ task_id: s.string().describe('Background task ID'),
609
+ },
610
+ async execute(args) {
611
+ const success = backgroundManager.cancel(args.task_id);
612
+ return JSON.stringify({
613
+ taskId: args.task_id,
614
+ success,
615
+ message: success ? 'Background task cancelled.' : 'Unable to cancel task.',
616
+ });
617
+ },
618
+ });
361
619
 
362
- // Type assertion needed because the tool() helper returns unknown
363
- // but the runtime type is correct (it's created by OpenCode's tool helper)
364
620
  return {
365
621
  coder_delegate: coderDelegate,
366
- } as Hooks['tool'];
622
+ background_task: backgroundTask,
623
+ background_output: backgroundOutput,
624
+ background_cancel: backgroundCancel,
625
+ };
367
626
  }
368
627
 
369
628
  // ─────────────────────────────────────────────────────────────────────────────
@@ -381,3 +640,76 @@ function extractSessionIdFromEvent(input: unknown): string | undefined {
381
640
  (inp.event.properties.sessionID as string | undefined)
382
641
  );
383
642
  }
643
+
644
+ function resolveAgentName(role: AgentRole): string {
645
+ const agent = agents[role];
646
+ return agent?.displayName ?? role;
647
+ }
648
+
649
+ function extractEventFromInput(
650
+ input: unknown
651
+ ): { type: string; properties?: Record<string, unknown> } | undefined {
652
+ if (typeof input !== 'object' || input === null) return undefined;
653
+ const inp = input as { event?: { type?: string; properties?: Record<string, unknown> } };
654
+ if (!inp.event || typeof inp.event.type !== 'string') return undefined;
655
+ return { type: inp.event.type, properties: inp.event.properties };
656
+ }
657
+
658
+ function registerShutdownHandler(
659
+ manager: BackgroundManager,
660
+ tmuxManager?: TmuxSessionManager,
661
+ logger?: (msg: string) => void
662
+ ): void {
663
+ if (typeof process === 'undefined') {
664
+ logger?.('[shutdown] process is undefined, cannot register handlers');
665
+ return;
666
+ }
667
+
668
+ const log = logger ?? (() => {});
669
+ let shutdownCalled = false;
670
+
671
+ log(
672
+ `Registering shutdown handlers (PID: ${process.pid}, tmuxManager: ${tmuxManager ? 'yes' : 'no'})`
673
+ );
674
+ log(`Current tracked sessions in tmuxManager: ${tmuxManager ? 'checking...' : 'N/A'}`);
675
+
676
+ const shutdown = (signal?: string) => {
677
+ // Prevent multiple shutdown calls
678
+ if (shutdownCalled) {
679
+ log(`Shutdown already in progress, ignoring ${signal ?? 'unknown'} signal`);
680
+ return;
681
+ }
682
+ shutdownCalled = true;
683
+
684
+ log(`Shutdown triggered by ${signal ?? 'unknown'} signal`);
685
+
686
+ try {
687
+ log('Shutting down background manager...');
688
+ manager.shutdown();
689
+ log('Background manager shutdown complete');
690
+ } catch (error) {
691
+ log(`Background manager shutdown error: ${error}`);
692
+ }
693
+
694
+ if (tmuxManager) {
695
+ try {
696
+ log('Cleaning up tmux sessions...');
697
+ // Use sync version to ensure cleanup completes before process exits
698
+ tmuxManager.cleanupSync();
699
+ log('Tmux cleanup complete');
700
+ } catch (error) {
701
+ log(`Tmux cleanup error: ${error}`);
702
+ }
703
+ }
704
+
705
+ log('Shutdown complete');
706
+ };
707
+
708
+ process.once('beforeExit', () => shutdown('beforeExit'));
709
+ process.once('SIGINT', () => shutdown('SIGINT'));
710
+ process.once('SIGTERM', () => shutdown('SIGTERM'));
711
+ process.once('SIGHUP', () => shutdown('SIGHUP')); // Handle tmux pane close
712
+ process.once('exit', () => shutdown('exit')); // Also handle exit event for extra safety
713
+
714
+ log('Shutdown handlers registered for: beforeExit, SIGINT, SIGTERM, SIGHUP, exit');
715
+ }
@@ -0,0 +1,25 @@
1
+ import yaml from 'yaml';
2
+
3
+ interface ParsedFrontmatter<T = Record<string, unknown>> {
4
+ data: T;
5
+ body: string;
6
+ }
7
+
8
+ export function parseFrontmatter<T = Record<string, unknown>>(
9
+ content: string
10
+ ): ParsedFrontmatter<T> {
11
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
12
+ if (!match) {
13
+ return { data: {} as T, body: content.trim() };
14
+ }
15
+
16
+ const frontmatter = match[1] ?? '';
17
+ const body = content.slice(match[0].length).trim();
18
+
19
+ try {
20
+ const data = yaml.parse(frontmatter) as T;
21
+ return { data: (data ?? {}) as T, body };
22
+ } catch {
23
+ return { data: {} as T, body: content.trim() };
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './frontmatter';
3
+ export * from './loader';