@agentuity/opencode 0.1.40 → 0.1.41

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 (161) hide show
  1. package/README.md +321 -9
  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 +183 -19
  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/keyword.d.ts.map +1 -1
  63. package/dist/plugin/hooks/keyword.js +3 -0
  64. package/dist/plugin/hooks/keyword.js.map +1 -1
  65. package/dist/plugin/plugin.d.ts.map +1 -1
  66. package/dist/plugin/plugin.js +297 -36
  67. package/dist/plugin/plugin.js.map +1 -1
  68. package/dist/skills/frontmatter.d.ts +7 -0
  69. package/dist/skills/frontmatter.d.ts.map +1 -0
  70. package/dist/skills/frontmatter.js +17 -0
  71. package/dist/skills/frontmatter.js.map +1 -0
  72. package/dist/skills/index.d.ts +4 -0
  73. package/dist/skills/index.d.ts.map +1 -0
  74. package/dist/skills/index.js +4 -0
  75. package/dist/skills/index.js.map +1 -0
  76. package/dist/skills/loader.d.ts +20 -0
  77. package/dist/skills/loader.d.ts.map +1 -0
  78. package/dist/skills/loader.js +152 -0
  79. package/dist/skills/loader.js.map +1 -0
  80. package/dist/skills/types.d.ts +41 -0
  81. package/dist/skills/types.d.ts.map +1 -0
  82. package/dist/skills/types.js +2 -0
  83. package/dist/skills/types.js.map +1 -0
  84. package/dist/tmux/decision-engine.d.ts +24 -0
  85. package/dist/tmux/decision-engine.d.ts.map +1 -0
  86. package/dist/tmux/decision-engine.js +193 -0
  87. package/dist/tmux/decision-engine.js.map +1 -0
  88. package/dist/tmux/executor.d.ts +56 -0
  89. package/dist/tmux/executor.d.ts.map +1 -0
  90. package/dist/tmux/executor.js +231 -0
  91. package/dist/tmux/executor.js.map +1 -0
  92. package/dist/tmux/index.d.ts +7 -0
  93. package/dist/tmux/index.d.ts.map +1 -0
  94. package/dist/tmux/index.js +7 -0
  95. package/dist/tmux/index.js.map +1 -0
  96. package/dist/tmux/manager.d.ts +80 -0
  97. package/dist/tmux/manager.d.ts.map +1 -0
  98. package/dist/tmux/manager.js +276 -0
  99. package/dist/tmux/manager.js.map +1 -0
  100. package/dist/tmux/state-query.d.ts +7 -0
  101. package/dist/tmux/state-query.d.ts.map +1 -0
  102. package/dist/tmux/state-query.js +67 -0
  103. package/dist/tmux/state-query.js.map +1 -0
  104. package/dist/tmux/types.d.ts +96 -0
  105. package/dist/tmux/types.d.ts.map +1 -0
  106. package/dist/tmux/types.js +8 -0
  107. package/dist/tmux/types.js.map +1 -0
  108. package/dist/tmux/utils.d.ts +32 -0
  109. package/dist/tmux/utils.d.ts.map +1 -0
  110. package/dist/tmux/utils.js +80 -0
  111. package/dist/tmux/utils.js.map +1 -0
  112. package/dist/tools/background.d.ts +61 -0
  113. package/dist/tools/background.d.ts.map +1 -0
  114. package/dist/tools/background.js +78 -0
  115. package/dist/tools/background.js.map +1 -0
  116. package/dist/tools/delegate.d.ts +6 -0
  117. package/dist/tools/delegate.d.ts.map +1 -1
  118. package/dist/tools/delegate.js +8 -2
  119. package/dist/tools/delegate.js.map +1 -1
  120. package/dist/tools/index.d.ts +1 -0
  121. package/dist/tools/index.d.ts.map +1 -1
  122. package/dist/tools/index.js +1 -0
  123. package/dist/tools/index.js.map +1 -1
  124. package/dist/types.d.ts +118 -18
  125. package/dist/types.d.ts.map +1 -1
  126. package/dist/types.js +49 -7
  127. package/dist/types.js.map +1 -1
  128. package/package.json +4 -3
  129. package/src/agents/architect.ts +262 -0
  130. package/src/agents/builder.ts +44 -1
  131. package/src/agents/index.ts +6 -0
  132. package/src/agents/lead.ts +183 -19
  133. package/src/agents/planner.ts +161 -0
  134. package/src/agents/runner.ts +367 -0
  135. package/src/agents/types.ts +5 -1
  136. package/src/background/concurrency.ts +116 -0
  137. package/src/background/index.ts +4 -0
  138. package/src/background/manager.ts +478 -0
  139. package/src/background/types.ts +52 -0
  140. package/src/config/index.ts +2 -0
  141. package/src/config/loader.ts +128 -31
  142. package/src/config/presets.ts +21 -0
  143. package/src/config/validation.ts +70 -0
  144. package/src/index.ts +1 -0
  145. package/src/plugin/hooks/keyword.ts +3 -0
  146. package/src/plugin/plugin.ts +323 -42
  147. package/src/skills/frontmatter.ts +25 -0
  148. package/src/skills/index.ts +3 -0
  149. package/src/skills/loader.ts +185 -0
  150. package/src/skills/types.ts +43 -0
  151. package/src/tmux/decision-engine.ts +246 -0
  152. package/src/tmux/executor.ts +286 -0
  153. package/src/tmux/index.ts +11 -0
  154. package/src/tmux/manager.ts +331 -0
  155. package/src/tmux/state-query.ts +74 -0
  156. package/src/tmux/types.ts +106 -0
  157. package/src/tmux/utils.ts +85 -0
  158. package/src/tools/background.ts +145 -0
  159. package/src/tools/delegate.ts +8 -2
  160. package/src/tools/index.ts +9 -0
  161. 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,10 @@ 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);
52
108
 
53
- const tools = toolHelper ? createTools(toolHelper) : undefined;
109
+ registerShutdownHandler(backgroundManager, tmuxManager);
54
110
 
55
111
  // Show startup toast (fire and forget, don't block)
56
112
  try {
@@ -73,6 +129,10 @@ export async function createCoderPlugin(ctx: PluginInput): Promise<Hooks> {
73
129
  'tool.execute.before': toolHooks.before,
74
130
  'tool.execute.after': toolHooks.after,
75
131
  event: async (input) => {
132
+ const event = extractEventFromInput(input);
133
+ if (event) {
134
+ backgroundManager.handleEvent(event);
135
+ }
76
136
  // Orchestrate: route to appropriate module based on session type
77
137
  const sessionId = extractSessionIdFromEvent(input);
78
138
  if (sessionId && cadenceHooks.isActiveCadenceSession(sessionId)) {
@@ -101,27 +161,60 @@ function createConfigHandler(
101
161
  return async (config: Record<string, unknown>) => {
102
162
  const agentConfigs = createAgentConfigs(coderConfig);
103
163
  const commands = createCommands();
164
+ const loadedSkills = await loadAllSkills(coderConfig.skills);
165
+ const skillCommands = createSkillCommands(loadedSkills);
166
+
167
+ // Merge agent configs: our defaults first, then user's opencode.json overrides on top
168
+ // This allows users to customize any agent via their opencode.json
169
+ const userAgentConfigs = config.agent as Record<string, AgentConfig> | undefined;
170
+ const mergedAgents: Record<string, AgentConfig> = { ...agentConfigs };
171
+
172
+ // Shallow merge user overrides on top of our defaults (nested objects like tools are replaced, not merged)
173
+ if (userAgentConfigs) {
174
+ for (const [name, userConfig] of Object.entries(userAgentConfigs)) {
175
+ if (mergedAgents[name]) {
176
+ // Merge user config on top of our default
177
+ mergedAgents[name] = {
178
+ ...mergedAgents[name],
179
+ ...userConfig,
180
+ };
181
+ } else {
182
+ // User defined a new agent not in our defaults
183
+ mergedAgents[name] = userConfig;
184
+ }
185
+ }
186
+ }
104
187
 
105
- config.agent = {
106
- ...(config.agent as Record<string, AgentConfig> | undefined),
107
- ...agentConfigs,
108
- };
188
+ config.agent = mergedAgents;
189
+
190
+ // Validate merged configs and warn about mismatches
191
+ validateAndWarnConfigs(mergedAgents);
192
+
193
+ // In sandbox, allow all permissions without prompts
194
+ if (IN_SANDBOX) {
195
+ config.permission = {
196
+ '*': 'allow',
197
+ external_directory: {
198
+ '/home/agentuity/**': 'allow',
199
+ '*': 'allow',
200
+ },
201
+ };
202
+ }
109
203
 
110
204
  config.command = {
111
205
  ...(config.command as Record<string, CommandDefinition> | undefined),
112
206
  ...commands,
207
+ ...skillCommands,
113
208
  };
114
209
  };
115
210
  }
116
211
 
117
212
  function createAgentConfigs(
118
- config: ReturnType<typeof getDefaultConfig>
213
+ _config: ReturnType<typeof getDefaultConfig>
119
214
  ): Record<string, AgentConfig> {
120
215
  const result: Record<string, AgentConfig> = {};
121
216
 
122
217
  for (const agent of Object.values(agents)) {
123
- const modelConfig = config.agents?.[agent.role];
124
-
125
218
  // Convert tools.exclude to Open Code format (tool: false)
126
219
  const tools: Record<string, boolean> = {};
127
220
  if (agent.tools?.exclude) {
@@ -130,16 +223,25 @@ function createAgentConfigs(
130
223
  }
131
224
  }
132
225
 
226
+ // Inject sandbox context into specific agents when running in sandbox
227
+ const shouldInjectSandbox =
228
+ IN_SANDBOX && SANDBOX_AWARE_AGENTS.includes(agent.role as AgentRole);
229
+ const prompt = shouldInjectSandbox
230
+ ? `${agent.systemPrompt}\n${SANDBOX_CONTEXT}`
231
+ : agent.systemPrompt;
232
+
233
+ // Use agent defaults directly - user overrides happen in createConfigHandler
133
234
  result[agent.displayName] = {
134
235
  description: agent.description,
135
- model: modelConfig?.model ?? agent.defaultModel,
136
- prompt: agent.systemPrompt,
236
+ model: agent.defaultModel,
237
+ prompt,
137
238
  mode: agent.mode ?? 'subagent',
138
239
  ...(Object.keys(tools).length > 0 ? { tools } : {}),
139
- // Pass through thinking/reasoning settings
140
240
  ...(agent.variant ? { variant: agent.variant } : {}),
141
241
  ...(agent.temperature !== undefined ? { temperature: agent.temperature } : {}),
142
242
  ...(agent.maxSteps !== undefined ? { maxSteps: agent.maxSteps } : {}),
243
+ ...(agent.reasoningEffort ? { reasoningEffort: agent.reasoningEffort } : {}),
244
+ ...(agent.thinking ? { thinking: agent.thinking } : {}),
143
245
  };
144
246
  }
145
247
 
@@ -158,20 +260,24 @@ You are the Agentuity Coder Lead agent orchestrating the Agentuity Coder team.
158
260
  ## Your Team (use @mentions to invoke)
159
261
  - **@Agentuity Coder Scout**: Explore codebase, find patterns, research docs (read-only)
160
262
  - **@Agentuity Coder Builder**: Implement features, write code, run tests
263
+ - **@Agentuity Coder Architect**: Complex autonomous tasks, Cadence mode (GPT Codex)
161
264
  - **@Agentuity Coder Reviewer**: Review changes, catch issues, apply fixes
162
265
  - **@Agentuity Coder Memory**: Store context, remember decisions
163
266
  - **@Agentuity Coder Expert**: Agentuity CLI and cloud services specialist
267
+ - **@Agentuity Coder Planner**: Deep planning for complex architecture decisions
268
+ - **@Agentuity Coder Runner**: Run lint/build/test commands, returns structured results
164
269
 
165
270
  ## Task
166
271
  $ARGUMENTS
167
272
 
168
273
  ## Guidelines
169
274
  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**
275
+ 2. Delegate implementation to @Agentuity Coder Builder (or Architect for complex work)
276
+ 3. Delegate lint/build/test commands to @Agentuity Coder Runner for structured results
277
+ 4. Have @Agentuity Coder Reviewer check the work
278
+ 5. Use @Agentuity Coder Expert for Agentuity CLI questions
279
+ 6. Only use cloud services when genuinely helpful
280
+ 7. **When done, tell @Agentuity Coder Memory to memorialize the session**
175
281
  </coder-mode>`,
176
282
  agent: 'Agentuity Coder Lead',
177
283
  argumentHint: '"task description"',
@@ -289,10 +395,13 @@ You are the Agentuity Coder Lead in **Cadence mode** — a long-running autonomo
289
395
 
290
396
  ## Your Team (use @mentions to invoke)
291
397
  - **@Agentuity Coder Scout**: Explore codebase, find patterns, research docs (read-only)
292
- - **@Agentuity Coder Builder**: Implement features, write code, run tests
398
+ - **@Agentuity Coder Architect**: Complex autonomous implementation (GPT Codex with high reasoning) — **USE THIS FOR CADENCE**
399
+ - **@Agentuity Coder Builder**: Quick fixes, simple changes (for minor iterations only)
293
400
  - **@Agentuity Coder Reviewer**: Review changes, catch issues, apply fixes
294
401
  - **@Agentuity Coder Memory**: Store context, remember decisions, checkpoints
295
402
  - **@Agentuity Coder Expert**: Agentuity CLI and cloud services specialist
403
+ - **@Agentuity Coder Planner**: Deep planning for complex architecture decisions
404
+ - **@Agentuity Coder Runner**: Run lint/build/test commands, returns structured results
296
405
 
297
406
  ## Task
298
407
  $ARGUMENTS
@@ -306,7 +415,8 @@ $ARGUMENTS
306
415
  2. **Each iteration**:
307
416
  - Ask @Agentuity Coder Memory for relevant context
308
417
  - Use @Agentuity Coder Scout to understand what's needed
309
- - Delegate implementation to @Agentuity Coder Builder
418
+ - For complex planning, consult @Agentuity Coder Planner
419
+ - Delegate implementation to **@Agentuity Coder Architect** (preferred for Cadence)
310
420
  - Have @Agentuity Coder Reviewer verify the work
311
421
  - Tell @Agentuity Coder Memory to store checkpoint
312
422
 
@@ -318,10 +428,11 @@ $ARGUMENTS
318
428
  4. **Tell @Agentuity Coder Memory to memorialize** the completed session
319
429
 
320
430
  ## Guidelines
321
- - **Always delegate** — use Scout for research, Builder for code, Reviewer for verification
431
+ - **Use Architect for implementation** — Architect has GPT Codex with maximum reasoning, ideal for autonomous work
432
+ - Use regular Builder only for trivial fixes within an iteration
322
433
  - Ask Memory for context at each iteration start
323
434
  - Store checkpoints at each iteration end
324
- - If stuck, ask Scout to re-evaluate before pausing
435
+ - If stuck on architecture, consult Planner before trying more approaches
325
436
  - Use @Agentuity Coder Expert for sandbox/cloud operations
326
437
  - Respect max iterations (50 default)`,
327
438
  agent: 'Agentuity Coder Lead',
@@ -330,40 +441,178 @@ $ARGUMENTS
330
441
  };
331
442
  }
332
443
 
333
- function createTools(tool: (schema: (s: typeof z) => unknown) => unknown): Hooks['tool'] {
334
- const coderDelegate = tool((s) => ({
444
+ function createSkillCommands(skills: LoadedSkill[]): Record<string, CommandDefinition> {
445
+ const commands: Record<string, CommandDefinition> = {};
446
+
447
+ for (const skill of skills) {
448
+ const baseDir = normalizeBaseDir(skill.resolvedPath);
449
+ commands[skill.name] = {
450
+ name: skill.name,
451
+ description: skill.metadata.description,
452
+ template: `<skill-instruction>
453
+ Base directory for this skill: ${baseDir}/
454
+ File references (@path) in this skill are relative to this directory.
455
+
456
+ ${skill.content}
457
+ </skill-instruction>
458
+
459
+ <user-request>
460
+ $ARGUMENTS
461
+ </user-request>`,
462
+ ...(skill.metadata.agent ? { agent: skill.metadata.agent } : {}),
463
+ ...(skill.metadata.model ? { model: skill.metadata.model } : {}),
464
+ ...(skill.metadata['argument-hint']
465
+ ? { argumentHint: skill.metadata['argument-hint'] }
466
+ : {}),
467
+ ...(skill.metadata.subtask ? { subtask: true } : {}),
468
+ };
469
+ }
470
+
471
+ return commands;
472
+ }
473
+
474
+ function normalizeBaseDir(path: string): string {
475
+ return path.replace(/[\\/]+$/, '');
476
+ }
477
+
478
+ function createTools(backgroundManager: BackgroundManager): Hooks['tool'] {
479
+ // Use the schema from @opencode-ai/plugin's tool helper to avoid Zod version mismatches
480
+ const s = tool.schema;
481
+
482
+ const coderDelegate = tool({
335
483
  description: `Delegate a task to a specialized Agentuity Coder agent.
336
484
 
337
485
  Use this to:
338
486
  - Scout: Explore codebase, find patterns, research documentation
339
- - Builder: Implement features, write code, run tests
487
+ - Builder: Implement features, write code, run tests (interactive work)
488
+ - Architect: Complex autonomous tasks, Cadence mode, deep reasoning (GPT Codex)
340
489
  - Reviewer: Review changes, catch issues, apply fixes
341
490
  - Memory: Store context, remember decisions across sessions
342
- - Expert: Get help with Agentuity CLI and cloud services`,
343
- args: s.object({
491
+ - Expert: Get help with Agentuity CLI and cloud services
492
+ - Planner: Strategic advisor for complex architecture and deep planning (read-only)`,
493
+ args: {
344
494
  agent: s
345
- .enum(['scout', 'builder', 'reviewer', 'memory', 'expert'])
495
+ .enum([
496
+ 'scout',
497
+ 'builder',
498
+ 'architect',
499
+ 'reviewer',
500
+ 'memory',
501
+ 'expert',
502
+ 'planner',
503
+ 'runner',
504
+ ])
346
505
  .describe('Which agent to delegate to'),
347
506
  task: s.string().describe('Clear description of the task'),
348
507
  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];
508
+ },
509
+ async execute(args) {
510
+ const mention = AGENT_MENTIONS[args.agent as AgentRole];
352
511
  let prompt = `${mention}\n\n## Task\n${args.task}`;
353
512
  if (args.context) {
354
513
  prompt = `${mention}\n\n## Context\n${args.context}\n\n## Task\n${args.task}`;
355
514
  }
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
- };
515
+ return `To delegate this task, use the Task tool with this prompt:\n\n${prompt}\n\nThe ${args.agent} agent will handle this task.`;
516
+ },
517
+ });
518
+
519
+ const backgroundTask = tool({
520
+ description: `Launch a task to run in the background. Use this for parallel execution of multiple independent tasks.
521
+
522
+ IMPORTANT: Use this tool instead of the 'task' tool when:
523
+ - You need to run multiple agents in parallel
524
+ - Tasks are independent and don't need sequential execution
525
+ - The user asks for "parallel", "background", or "concurrent" work`,
526
+ args: {
527
+ agent: s
528
+ .enum([
529
+ 'lead',
530
+ 'scout',
531
+ 'builder',
532
+ 'architect',
533
+ 'reviewer',
534
+ 'memory',
535
+ 'expert',
536
+ 'planner',
537
+ 'runner',
538
+ ])
539
+ .describe('Agent role to run the task'),
540
+ task: s.string().describe('Task prompt to run in the background'),
541
+ description: s.string().optional().describe('Short description of the task'),
542
+ },
543
+ async execute(args, context) {
544
+ const parentSessionId = context.sessionID;
545
+ if (!parentSessionId) {
546
+ return JSON.stringify({
547
+ taskId: 'unknown',
548
+ status: 'error',
549
+ message: 'Missing session context for background task.',
550
+ });
551
+ }
552
+
553
+ const agentName = resolveAgentName(args.agent as AgentRole);
554
+ const bgTask = await backgroundManager.launch({
555
+ description: args.description ?? args.task,
556
+ prompt: args.task,
557
+ agent: agentName,
558
+ parentSessionId,
559
+ parentMessageId: context.messageID,
560
+ });
561
+ return JSON.stringify({
562
+ taskId: bgTask.id,
563
+ status: bgTask.status,
564
+ message:
565
+ bgTask.status === 'error'
566
+ ? (bgTask.error ?? 'Failed to launch background task.')
567
+ : 'Background task launched.',
568
+ });
569
+ },
570
+ });
571
+
572
+ const backgroundOutput = tool({
573
+ description: 'Retrieve output for a background task.',
574
+ args: {
575
+ task_id: s.string().describe('Background task ID'),
576
+ },
577
+ async execute(args) {
578
+ const bgTask = backgroundManager.getTask(args.task_id);
579
+ if (!bgTask) {
580
+ return JSON.stringify({
581
+ taskId: args.task_id,
582
+ status: 'error',
583
+ error: 'Task not found.',
584
+ });
585
+ }
586
+ return JSON.stringify({
587
+ taskId: bgTask.id,
588
+ status: bgTask.status,
589
+ result: bgTask.result,
590
+ error: bgTask.error,
591
+ });
359
592
  },
360
- }));
593
+ });
594
+
595
+ const backgroundCancel = tool({
596
+ description: 'Cancel a running background task.',
597
+ args: {
598
+ task_id: s.string().describe('Background task ID'),
599
+ },
600
+ async execute(args) {
601
+ const success = backgroundManager.cancel(args.task_id);
602
+ return JSON.stringify({
603
+ taskId: args.task_id,
604
+ success,
605
+ message: success ? 'Background task cancelled.' : 'Unable to cancel task.',
606
+ });
607
+ },
608
+ });
361
609
 
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
610
  return {
365
611
  coder_delegate: coderDelegate,
366
- } as Hooks['tool'];
612
+ background_task: backgroundTask,
613
+ background_output: backgroundOutput,
614
+ background_cancel: backgroundCancel,
615
+ };
367
616
  }
368
617
 
369
618
  // ─────────────────────────────────────────────────────────────────────────────
@@ -381,3 +630,35 @@ function extractSessionIdFromEvent(input: unknown): string | undefined {
381
630
  (inp.event.properties.sessionID as string | undefined)
382
631
  );
383
632
  }
633
+
634
+ function resolveAgentName(role: AgentRole): string {
635
+ const agent = agents[role];
636
+ return agent?.displayName ?? role;
637
+ }
638
+
639
+ function extractEventFromInput(
640
+ input: unknown
641
+ ): { type: string; properties?: Record<string, unknown> } | undefined {
642
+ if (typeof input !== 'object' || input === null) return undefined;
643
+ const inp = input as { event?: { type?: string; properties?: Record<string, unknown> } };
644
+ if (!inp.event || typeof inp.event.type !== 'string') return undefined;
645
+ return { type: inp.event.type, properties: inp.event.properties };
646
+ }
647
+
648
+ function registerShutdownHandler(
649
+ manager: BackgroundManager,
650
+ tmuxManager?: TmuxSessionManager
651
+ ): void {
652
+ if (typeof process === 'undefined') return;
653
+ const shutdown = () => {
654
+ manager.shutdown();
655
+ if (tmuxManager) {
656
+ // Use sync version to ensure cleanup completes before process exits
657
+ tmuxManager.cleanupSync();
658
+ }
659
+ };
660
+ process.once('beforeExit', shutdown);
661
+ process.once('SIGINT', shutdown);
662
+ process.once('SIGTERM', shutdown);
663
+ process.once('exit', shutdown); // Also handle exit event for extra safety
664
+ }
@@ -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';