@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
@@ -0,0 +1,478 @@
1
+ import type { PluginInput } from '@opencode-ai/plugin';
2
+ import { agents } from '../agents';
3
+ import type { AgentDefinition } from '../agents';
4
+ import type { BackgroundTask, BackgroundTaskConfig, LaunchInput, TaskProgress } from './types';
5
+ import { ConcurrencyManager } from './concurrency';
6
+
7
+ const DEFAULT_BACKGROUND_CONFIG: BackgroundTaskConfig = {
8
+ enabled: true,
9
+ defaultConcurrency: 1,
10
+ staleTimeoutMs: 30 * 60 * 1000,
11
+ };
12
+
13
+ type MessagePart = {
14
+ type?: string;
15
+ text?: string;
16
+ tool?: string;
17
+ callID?: string;
18
+ state?: { status?: string };
19
+ sessionID?: string;
20
+ };
21
+
22
+ type EventPayload = {
23
+ type: string;
24
+ properties?: Record<string, unknown>;
25
+ };
26
+
27
+ export interface BackgroundManagerCallbacks {
28
+ onSubagentSessionCreated?: (event: {
29
+ sessionId: string;
30
+ parentId: string;
31
+ title: string;
32
+ }) => void;
33
+ onSubagentSessionDeleted?: (event: { sessionId: string }) => void;
34
+ }
35
+
36
+ export class BackgroundManager {
37
+ private ctx: PluginInput;
38
+ private config: BackgroundTaskConfig;
39
+ private concurrency: ConcurrencyManager;
40
+ private callbacks?: BackgroundManagerCallbacks;
41
+ private tasks = new Map<string, BackgroundTask>();
42
+ private tasksByParent = new Map<string, Set<string>>();
43
+ private tasksBySession = new Map<string, string>();
44
+ private notifications = new Map<string, Set<string>>();
45
+ private toolCallIds = new Map<string, Set<string>>();
46
+ private shuttingDown = false;
47
+
48
+ constructor(
49
+ ctx: PluginInput,
50
+ config?: BackgroundTaskConfig,
51
+ callbacks?: BackgroundManagerCallbacks
52
+ ) {
53
+ this.ctx = ctx;
54
+ this.config = { ...DEFAULT_BACKGROUND_CONFIG, ...config };
55
+ this.concurrency = new ConcurrencyManager({
56
+ defaultLimit: this.config.defaultConcurrency,
57
+ limits: buildConcurrencyLimits(this.config),
58
+ });
59
+ this.callbacks = callbacks;
60
+ }
61
+
62
+ async launch(input: LaunchInput): Promise<BackgroundTask> {
63
+ const task: BackgroundTask = {
64
+ id: createTaskId(),
65
+ parentSessionId: input.parentSessionId,
66
+ parentMessageId: input.parentMessageId,
67
+ description: input.description,
68
+ prompt: input.prompt,
69
+ agent: input.agent,
70
+ status: 'pending',
71
+ queuedAt: new Date(),
72
+ concurrencyGroup: this.getConcurrencyGroup(input.agent),
73
+ };
74
+
75
+ this.tasks.set(task.id, task);
76
+ this.indexTask(task);
77
+
78
+ if (!this.config.enabled) {
79
+ task.status = 'error';
80
+ task.error = 'Background tasks are disabled.';
81
+ task.completedAt = new Date();
82
+ this.markForNotification(task);
83
+ return task;
84
+ }
85
+
86
+ void this.startTask(task);
87
+ return task;
88
+ }
89
+
90
+ getTask(id: string): BackgroundTask | undefined {
91
+ return this.tasks.get(id);
92
+ }
93
+
94
+ getTasksByParent(sessionId: string): BackgroundTask[] {
95
+ const ids = this.tasksByParent.get(sessionId);
96
+ if (!ids) return [];
97
+ return Array.from(ids)
98
+ .map((id) => this.tasks.get(id))
99
+ .filter((task): task is BackgroundTask => Boolean(task));
100
+ }
101
+
102
+ findBySession(sessionId: string): BackgroundTask | undefined {
103
+ const taskId = this.tasksBySession.get(sessionId);
104
+ if (!taskId) return undefined;
105
+ return this.tasks.get(taskId);
106
+ }
107
+
108
+ cancel(taskId: string): boolean {
109
+ const task = this.tasks.get(taskId);
110
+ if (!task || task.status === 'completed' || task.status === 'error') {
111
+ return false;
112
+ }
113
+
114
+ task.status = 'cancelled';
115
+ task.completedAt = new Date();
116
+ this.releaseConcurrency(task);
117
+ this.markForNotification(task);
118
+
119
+ if (task.sessionId) {
120
+ void this.abortSession(task.sessionId);
121
+ this.callbacks?.onSubagentSessionDeleted?.({ sessionId: task.sessionId });
122
+ }
123
+
124
+ return true;
125
+ }
126
+
127
+ handleEvent(event: EventPayload): void {
128
+ if (!event || typeof event.type !== 'string') return;
129
+
130
+ this.expireStaleTasks();
131
+
132
+ if (event.type === 'message.part.updated') {
133
+ const part = event.properties?.part as MessagePart | undefined;
134
+ if (!part) return;
135
+ const sessionId = part.sessionID;
136
+ if (!sessionId) return;
137
+ const task = this.findBySession(sessionId);
138
+ if (!task) return;
139
+ this.updateProgress(task, part);
140
+ return;
141
+ }
142
+
143
+ if (event.type === 'session.idle') {
144
+ const sessionId = extractSessionId(event.properties);
145
+ const task = sessionId ? this.findBySession(sessionId) : undefined;
146
+ if (!task) return;
147
+ void this.completeTask(task);
148
+ return;
149
+ }
150
+
151
+ if (event.type === 'session.error') {
152
+ const sessionId = extractSessionId(event.properties);
153
+ const task = sessionId ? this.findBySession(sessionId) : undefined;
154
+ if (!task) return;
155
+ const error = extractError(event.properties);
156
+ this.failTask(task, error ?? 'Session error.');
157
+ return;
158
+ }
159
+ }
160
+
161
+ markForNotification(task: BackgroundTask): void {
162
+ const sessionId = task.parentSessionId;
163
+ if (!sessionId) return;
164
+ const queue = this.notifications.get(sessionId) ?? new Set<string>();
165
+ queue.add(task.id);
166
+ this.notifications.set(sessionId, queue);
167
+ }
168
+
169
+ getPendingNotifications(sessionId: string): BackgroundTask[] {
170
+ const queue = this.notifications.get(sessionId);
171
+ if (!queue) return [];
172
+ return Array.from(queue)
173
+ .map((id) => this.tasks.get(id))
174
+ .filter((task): task is BackgroundTask => Boolean(task));
175
+ }
176
+
177
+ clearNotifications(sessionId: string): void {
178
+ this.notifications.delete(sessionId);
179
+ }
180
+
181
+ shutdown(): void {
182
+ this.shuttingDown = true;
183
+ this.concurrency.clear();
184
+ this.notifications.clear();
185
+ }
186
+
187
+ private indexTask(task: BackgroundTask): void {
188
+ const parentList = this.tasksByParent.get(task.parentSessionId) ?? new Set<string>();
189
+ parentList.add(task.id);
190
+ this.tasksByParent.set(task.parentSessionId, parentList);
191
+ }
192
+
193
+ private async startTask(task: BackgroundTask): Promise<void> {
194
+ if (this.shuttingDown) return;
195
+
196
+ const concurrencyKey = this.getConcurrencyKey(task.agent);
197
+ task.concurrencyKey = concurrencyKey;
198
+
199
+ try {
200
+ await this.concurrency.acquire(concurrencyKey);
201
+ } catch (error) {
202
+ if (task.status !== 'cancelled') {
203
+ task.status = 'error';
204
+ task.error = error instanceof Error ? error.message : 'Failed to acquire slot.';
205
+ task.completedAt = new Date();
206
+ this.markForNotification(task);
207
+ }
208
+ return;
209
+ }
210
+
211
+ if (task.status === 'cancelled') {
212
+ this.releaseConcurrency(task);
213
+ return;
214
+ }
215
+
216
+ try {
217
+ const sessionResult = await this.ctx.client.session.create({
218
+ body: {
219
+ parentID: task.parentSessionId,
220
+ title: task.description,
221
+ },
222
+ throwOnError: true,
223
+ });
224
+ const session = unwrapResponse<{ id: string }>(sessionResult);
225
+ if (!session?.id) {
226
+ throw new Error('Failed to create session.');
227
+ }
228
+
229
+ task.sessionId = session.id;
230
+ task.status = 'running';
231
+ task.startedAt = new Date();
232
+ this.tasksBySession.set(session.id, task.id);
233
+ this.callbacks?.onSubagentSessionCreated?.({
234
+ sessionId: session.id,
235
+ parentId: task.parentSessionId,
236
+ title: task.description,
237
+ });
238
+
239
+ await this.ctx.client.session.prompt({
240
+ path: { id: session.id },
241
+ body: {
242
+ agent: task.agent,
243
+ parts: [{ type: 'text', text: task.prompt }],
244
+ },
245
+ throwOnError: true,
246
+ });
247
+ } catch (error) {
248
+ this.failTask(
249
+ task,
250
+ error instanceof Error ? error.message : 'Failed to launch background task.'
251
+ );
252
+ }
253
+ }
254
+
255
+ private updateProgress(task: BackgroundTask, part: MessagePart): void {
256
+ const progress = task.progress ?? this.createProgress();
257
+ progress.lastUpdate = new Date();
258
+
259
+ if (part.type === 'tool') {
260
+ const callId = part.callID;
261
+ const toolName = part.tool;
262
+ if (toolName) {
263
+ progress.lastTool = toolName;
264
+ }
265
+ if (callId) {
266
+ const seen = this.toolCallIds.get(task.id) ?? new Set<string>();
267
+ if (!seen.has(callId)) {
268
+ seen.add(callId);
269
+ progress.toolCalls += 1;
270
+ this.toolCallIds.set(task.id, seen);
271
+ }
272
+ }
273
+ }
274
+
275
+ if (part.type === 'text' && part.text) {
276
+ progress.lastMessage = part.text;
277
+ progress.lastMessageAt = new Date();
278
+ }
279
+
280
+ task.progress = progress;
281
+ }
282
+
283
+ private createProgress(): TaskProgress {
284
+ return {
285
+ toolCalls: 0,
286
+ lastUpdate: new Date(),
287
+ };
288
+ }
289
+
290
+ private async completeTask(task: BackgroundTask): Promise<void> {
291
+ if (task.status !== 'running') return;
292
+
293
+ task.status = 'completed';
294
+ task.completedAt = new Date();
295
+ this.releaseConcurrency(task);
296
+
297
+ if (task.sessionId) {
298
+ const result = await this.fetchLatestResult(task.sessionId);
299
+ if (result) {
300
+ task.result = result;
301
+ }
302
+ this.callbacks?.onSubagentSessionDeleted?.({ sessionId: task.sessionId });
303
+ }
304
+
305
+ this.markForNotification(task);
306
+ void this.notifyParent(task);
307
+ }
308
+
309
+ private failTask(task: BackgroundTask, error: string): void {
310
+ if (task.status === 'completed' || task.status === 'error') return;
311
+ task.status = 'error';
312
+ task.error = error;
313
+ task.completedAt = new Date();
314
+ this.releaseConcurrency(task);
315
+ if (task.sessionId) {
316
+ this.callbacks?.onSubagentSessionDeleted?.({ sessionId: task.sessionId });
317
+ }
318
+ this.markForNotification(task);
319
+ void this.notifyParent(task);
320
+ }
321
+
322
+ private releaseConcurrency(task: BackgroundTask): void {
323
+ if (!task.concurrencyKey) return;
324
+ this.concurrency.release(task.concurrencyKey);
325
+ delete task.concurrencyKey;
326
+ }
327
+
328
+ private async notifyParent(task: BackgroundTask): Promise<void> {
329
+ if (!task.parentSessionId) return;
330
+
331
+ const statusLine = task.status === 'completed' ? 'completed' : task.status;
332
+ const message = `[BACKGROUND TASK ${statusLine.toUpperCase()}]
333
+
334
+ Task: ${task.description}
335
+ Agent: ${task.agent}
336
+ Status: ${task.status}
337
+ Task ID: ${task.id}
338
+
339
+ Use the background_output tool with task_id "${task.id}" to view the result.`;
340
+
341
+ try {
342
+ await this.ctx.client.session.prompt({
343
+ path: { id: task.parentSessionId },
344
+ body: {
345
+ parts: [{ type: 'text', text: message }],
346
+ },
347
+ throwOnError: true,
348
+ responseStyle: 'data',
349
+ });
350
+ } catch {
351
+ // Ignore notification errors
352
+ }
353
+ }
354
+
355
+ private async abortSession(sessionId: string): Promise<void> {
356
+ try {
357
+ await this.ctx.client.session.abort({
358
+ path: { id: sessionId },
359
+ throwOnError: false,
360
+ });
361
+ } catch {
362
+ // Ignore abort errors
363
+ }
364
+ }
365
+
366
+ private async fetchLatestResult(sessionId: string): Promise<string | undefined> {
367
+ try {
368
+ const messagesResult = await this.ctx.client.session.messages({
369
+ path: { id: sessionId },
370
+ throwOnError: true,
371
+ });
372
+ const messages = unwrapResponse<Array<unknown>>(messagesResult) ?? [];
373
+ const entries = Array.isArray(messages) ? messages : [];
374
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
375
+ const entry = entries[i] as { info?: { role?: string }; parts?: Array<unknown> };
376
+ if (entry?.info?.role !== 'assistant') continue;
377
+ const text = extractTextFromParts(entry.parts ?? []);
378
+ if (text) return text;
379
+ }
380
+ } catch {
381
+ return undefined;
382
+ }
383
+
384
+ return undefined;
385
+ }
386
+
387
+ private getConcurrencyGroup(agentName: string): string | undefined {
388
+ const model = getAgentModel(agentName);
389
+ if (!model) return undefined;
390
+ const provider = model.split('/')[0];
391
+ if (model && this.config.modelConcurrency?.[model] !== undefined) {
392
+ return `model:${model}`;
393
+ }
394
+ if (provider && this.config.providerConcurrency?.[provider] !== undefined) {
395
+ return `provider:${provider}`;
396
+ }
397
+ return undefined;
398
+ }
399
+
400
+ private getConcurrencyKey(agentName: string): string {
401
+ const group = this.getConcurrencyGroup(agentName);
402
+ return group ?? 'default';
403
+ }
404
+
405
+ private expireStaleTasks(): void {
406
+ const now = Date.now();
407
+ for (const task of this.tasks.values()) {
408
+ if (task.status !== 'pending' && task.status !== 'running') continue;
409
+ const start = task.startedAt?.getTime() ?? task.queuedAt?.getTime();
410
+ if (!start) continue;
411
+ if (now - start > this.config.staleTimeoutMs) {
412
+ this.failTask(task, 'Background task timed out.');
413
+ }
414
+ }
415
+ }
416
+ }
417
+
418
+ function buildConcurrencyLimits(config: BackgroundTaskConfig): Record<string, number> {
419
+ const limits: Record<string, number> = {};
420
+ if (config.providerConcurrency) {
421
+ for (const [provider, limit] of Object.entries(config.providerConcurrency)) {
422
+ limits[`provider:${provider}`] = limit;
423
+ }
424
+ }
425
+ if (config.modelConcurrency) {
426
+ for (const [model, limit] of Object.entries(config.modelConcurrency)) {
427
+ limits[`model:${model}`] = limit;
428
+ }
429
+ }
430
+ return limits;
431
+ }
432
+
433
+ function getAgentModel(agentName: string): string | undefined {
434
+ const agent = findAgentDefinition(agentName);
435
+ return agent?.defaultModel;
436
+ }
437
+
438
+ function findAgentDefinition(agentName: string): AgentDefinition | undefined {
439
+ return Object.values(agents).find(
440
+ (agent) =>
441
+ agent.displayName === agentName || agent.id === agentName || agent.role === agentName
442
+ );
443
+ }
444
+
445
+ function createTaskId(): string {
446
+ return `bg_${Math.random().toString(36).slice(2, 8)}`;
447
+ }
448
+
449
+ function extractSessionId(properties?: Record<string, unknown>): string | undefined {
450
+ return (
451
+ (properties?.sessionId as string | undefined) ?? (properties?.sessionID as string | undefined)
452
+ );
453
+ }
454
+
455
+ function extractError(properties?: Record<string, unknown>): string | undefined {
456
+ const error = properties?.error as { data?: { message?: string }; name?: string } | undefined;
457
+ return error?.data?.message ?? (typeof error?.name === 'string' ? error.name : undefined);
458
+ }
459
+
460
+ function extractTextFromParts(parts: Array<unknown>): string | undefined {
461
+ const textParts: string[] = [];
462
+ for (const part of parts) {
463
+ if (typeof part !== 'object' || part === null) continue;
464
+ const typed = part as { type?: string; text?: string };
465
+ if (typed.type === 'text' && typeof typed.text === 'string') {
466
+ textParts.push(typed.text);
467
+ }
468
+ }
469
+ if (textParts.length === 0) return undefined;
470
+ return textParts.join('\n');
471
+ }
472
+
473
+ function unwrapResponse<T>(result: unknown): T | undefined {
474
+ if (typeof result === 'object' && result !== null && 'data' in result) {
475
+ return (result as { data?: T }).data;
476
+ }
477
+ return result as T;
478
+ }
@@ -0,0 +1,52 @@
1
+ // Task status and interfaces
2
+ export type BackgroundTaskStatus = 'pending' | 'running' | 'completed' | 'error' | 'cancelled';
3
+
4
+ export interface TaskProgress {
5
+ toolCalls: number;
6
+ lastTool?: string;
7
+ lastUpdate: Date;
8
+ lastMessage?: string;
9
+ lastMessageAt?: Date;
10
+ }
11
+
12
+ export interface BackgroundTask {
13
+ id: string; // Format: bg_xxxxx
14
+ sessionId?: string; // OpenCode session ID (set when running)
15
+ parentSessionId: string; // Parent session that launched this
16
+ parentMessageId?: string;
17
+ description: string;
18
+ prompt: string;
19
+ agent: string; // Agent name
20
+ status: BackgroundTaskStatus;
21
+ queuedAt?: Date;
22
+ startedAt?: Date;
23
+ completedAt?: Date;
24
+ result?: string;
25
+ error?: string;
26
+ progress?: TaskProgress;
27
+ concurrencyKey?: string; // Active concurrency slot key
28
+ concurrencyGroup?: string; // Persistent key for re-acquiring on resume
29
+ }
30
+
31
+ export interface LaunchInput {
32
+ description: string;
33
+ prompt: string;
34
+ agent: string;
35
+ parentSessionId: string;
36
+ parentMessageId?: string;
37
+ }
38
+
39
+ export interface ResumeInput {
40
+ sessionId: string;
41
+ prompt: string;
42
+ parentSessionId: string;
43
+ parentMessageId?: string;
44
+ }
45
+
46
+ export interface BackgroundTaskConfig {
47
+ enabled: boolean;
48
+ defaultConcurrency: number;
49
+ staleTimeoutMs: number;
50
+ providerConcurrency?: Record<string, number>;
51
+ modelConcurrency?: Record<string, number>;
52
+ }
@@ -1 +1,3 @@
1
1
  export { loadCoderConfig, getConfigPath, getDefaultConfig, mergeConfig } from './loader';
2
+ export { isOpenAIModel, isAnthropicModel } from './presets';
3
+ export { validateAgentConfig, validateAndWarnConfigs, type ConfigWarning } from './validation';