actoviq-agent-sdk 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README-zh.md +37 -2
  2. package/README.md +37 -2
  3. package/bin/actoviq-tui.js +5 -0
  4. package/dist/src/cli/actoviq-react.js +79 -8
  5. package/dist/src/cli/actoviq-react.js.map +1 -1
  6. package/dist/src/cli/actoviq-tui.d.ts +3 -0
  7. package/dist/src/cli/actoviq-tui.d.ts.map +1 -0
  8. package/dist/src/cli/actoviq-tui.js +76 -0
  9. package/dist/src/cli/actoviq-tui.js.map +1 -0
  10. package/dist/src/config/actoviqSettingsStore.d.ts.map +1 -1
  11. package/dist/src/config/actoviqSettingsStore.js +8 -2
  12. package/dist/src/config/actoviqSettingsStore.js.map +1 -1
  13. package/dist/src/config/anthropicEnvMapping.d.ts +1 -1
  14. package/dist/src/config/anthropicEnvMapping.d.ts.map +1 -1
  15. package/dist/src/config/anthropicEnvMapping.js +17 -8
  16. package/dist/src/config/anthropicEnvMapping.js.map +1 -1
  17. package/dist/src/config/modelTiers.d.ts +12 -0
  18. package/dist/src/config/modelTiers.d.ts.map +1 -0
  19. package/dist/src/config/modelTiers.js +31 -0
  20. package/dist/src/config/modelTiers.js.map +1 -0
  21. package/dist/src/config/projectSessionDirectory.d.ts +8 -0
  22. package/dist/src/config/projectSessionDirectory.d.ts.map +1 -0
  23. package/dist/src/config/projectSessionDirectory.js +83 -0
  24. package/dist/src/config/projectSessionDirectory.js.map +1 -0
  25. package/dist/src/config/resolveRuntimeConfig.d.ts.map +1 -1
  26. package/dist/src/config/resolveRuntimeConfig.js +44 -16
  27. package/dist/src/config/resolveRuntimeConfig.js.map +1 -1
  28. package/dist/src/index.d.ts +4 -0
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +4 -0
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/mcp/connectionManager.d.ts.map +1 -1
  33. package/dist/src/mcp/connectionManager.js +10 -1
  34. package/dist/src/mcp/connectionManager.js.map +1 -1
  35. package/dist/src/parity/actoviqCleanBridgeCompatSdk.d.ts.map +1 -1
  36. package/dist/src/parity/actoviqCleanBridgeCompatSdk.js +6 -2
  37. package/dist/src/parity/actoviqCleanBridgeCompatSdk.js.map +1 -1
  38. package/dist/src/provider/client.d.ts +2 -0
  39. package/dist/src/provider/client.d.ts.map +1 -1
  40. package/dist/src/provider/client.js +5 -2
  41. package/dist/src/provider/client.js.map +1 -1
  42. package/dist/src/provider/openai-model-api.d.ts.map +1 -1
  43. package/dist/src/provider/openai-model-api.js +3 -0
  44. package/dist/src/provider/openai-model-api.js.map +1 -1
  45. package/dist/src/provider/openai-types.d.ts +1 -0
  46. package/dist/src/provider/openai-types.d.ts.map +1 -1
  47. package/dist/src/runtime/actoviqAgentDefinitions.d.ts +8 -0
  48. package/dist/src/runtime/actoviqAgentDefinitions.d.ts.map +1 -0
  49. package/dist/src/runtime/actoviqAgentDefinitions.js +165 -0
  50. package/dist/src/runtime/actoviqAgentDefinitions.js.map +1 -0
  51. package/dist/src/runtime/actoviqAgents.d.ts +20 -2
  52. package/dist/src/runtime/actoviqAgents.d.ts.map +1 -1
  53. package/dist/src/runtime/actoviqAgents.js +143 -28
  54. package/dist/src/runtime/actoviqAgents.js.map +1 -1
  55. package/dist/src/runtime/actoviqBackgroundTasks.d.ts +14 -1
  56. package/dist/src/runtime/actoviqBackgroundTasks.d.ts.map +1 -1
  57. package/dist/src/runtime/actoviqBackgroundTasks.js +71 -10
  58. package/dist/src/runtime/actoviqBackgroundTasks.js.map +1 -1
  59. package/dist/src/runtime/actoviqCompact.d.ts +16 -0
  60. package/dist/src/runtime/actoviqCompact.d.ts.map +1 -1
  61. package/dist/src/runtime/actoviqCompact.js +112 -13
  62. package/dist/src/runtime/actoviqCompact.js.map +1 -1
  63. package/dist/src/runtime/actoviqModelApi.d.ts.map +1 -1
  64. package/dist/src/runtime/actoviqModelApi.js +20 -4
  65. package/dist/src/runtime/actoviqModelApi.js.map +1 -1
  66. package/dist/src/runtime/actoviqPermissions.d.ts +5 -2
  67. package/dist/src/runtime/actoviqPermissions.d.ts.map +1 -1
  68. package/dist/src/runtime/actoviqPermissions.js +83 -194
  69. package/dist/src/runtime/actoviqPermissions.js.map +1 -1
  70. package/dist/src/runtime/actoviqSessionPermissions.d.ts +5 -0
  71. package/dist/src/runtime/actoviqSessionPermissions.d.ts.map +1 -0
  72. package/dist/src/runtime/actoviqSessionPermissions.js +44 -0
  73. package/dist/src/runtime/actoviqSessionPermissions.js.map +1 -0
  74. package/dist/src/runtime/agentClient.d.ts +26 -3
  75. package/dist/src/runtime/agentClient.d.ts.map +1 -1
  76. package/dist/src/runtime/agentClient.js +824 -110
  77. package/dist/src/runtime/agentClient.js.map +1 -1
  78. package/dist/src/runtime/agentSession.d.ts +8 -5
  79. package/dist/src/runtime/agentSession.d.ts.map +1 -1
  80. package/dist/src/runtime/agentSession.js +11 -4
  81. package/dist/src/runtime/agentSession.js.map +1 -1
  82. package/dist/src/runtime/conversationEngine.d.ts +2 -0
  83. package/dist/src/runtime/conversationEngine.d.ts.map +1 -1
  84. package/dist/src/runtime/conversationEngine.js +81 -2
  85. package/dist/src/runtime/conversationEngine.js.map +1 -1
  86. package/dist/src/runtime/defaultActoviqAgents.d.ts.map +1 -1
  87. package/dist/src/runtime/defaultActoviqAgents.js +41 -0
  88. package/dist/src/runtime/defaultActoviqAgents.js.map +1 -1
  89. package/dist/src/swarm/actoviqSwarm.d.ts.map +1 -1
  90. package/dist/src/swarm/actoviqSwarm.js +6 -6
  91. package/dist/src/swarm/actoviqSwarm.js.map +1 -1
  92. package/dist/src/tools/bash/BashTool.d.ts.map +1 -1
  93. package/dist/src/tools/bash/BashTool.js +43 -10
  94. package/dist/src/tools/bash/BashTool.js.map +1 -1
  95. package/dist/src/tui/actoviqTui.d.ts +13 -0
  96. package/dist/src/tui/actoviqTui.d.ts.map +1 -0
  97. package/dist/src/tui/actoviqTui.js +1594 -0
  98. package/dist/src/tui/actoviqTui.js.map +1 -0
  99. package/dist/src/tui/ansi.d.ts +33 -0
  100. package/dist/src/tui/ansi.d.ts.map +1 -0
  101. package/dist/src/tui/ansi.js +127 -0
  102. package/dist/src/tui/ansi.js.map +1 -0
  103. package/dist/src/tui/editor.d.ts +56 -0
  104. package/dist/src/tui/editor.d.ts.map +1 -0
  105. package/dist/src/tui/editor.js +253 -0
  106. package/dist/src/tui/editor.js.map +1 -0
  107. package/dist/src/tui/pluginCatalog.d.ts +13 -0
  108. package/dist/src/tui/pluginCatalog.d.ts.map +1 -0
  109. package/dist/src/tui/pluginCatalog.js +78 -0
  110. package/dist/src/tui/pluginCatalog.js.map +1 -0
  111. package/dist/src/tui/screen.d.ts +28 -0
  112. package/dist/src/tui/screen.d.ts.map +1 -0
  113. package/dist/src/tui/screen.js +119 -0
  114. package/dist/src/tui/screen.js.map +1 -0
  115. package/dist/src/tui/selection.d.ts +9 -0
  116. package/dist/src/tui/selection.d.ts.map +1 -0
  117. package/dist/src/tui/selection.js +16 -0
  118. package/dist/src/tui/selection.js.map +1 -0
  119. package/dist/src/tui/transcript.d.ts +41 -0
  120. package/dist/src/tui/transcript.d.ts.map +1 -0
  121. package/dist/src/tui/transcript.js +147 -0
  122. package/dist/src/tui/transcript.js.map +1 -0
  123. package/dist/src/types.d.ts +118 -2
  124. package/dist/src/types.d.ts.map +1 -1
  125. package/dist/src/types.js.map +1 -1
  126. package/package.json +2 -1
@@ -1,8 +1,12 @@
1
1
  import path from 'node:path';
2
+ import { execFile as execFileCallback } from 'node:child_process';
3
+ import { mkdir, readFile } from 'node:fs/promises';
4
+ import { promisify } from 'node:util';
2
5
  import { z } from 'zod';
3
6
  import { createActoviqBuddyApi } from '../buddy/actoviqBuddy.js';
4
7
  import { createActoviqComputerUseMcpServer, createActoviqComputerUseTools, } from '../computer/actoviqComputerUse.js';
5
8
  import { resolveRuntimeConfig } from '../config/resolveRuntimeConfig.js';
9
+ import { resolveActoviqModelReference } from '../config/modelTiers.js';
6
10
  import { mergeActoviqHooks, normalizeActoviqHookMessages, resolveActoviqPostRunHooks, resolveActoviqSessionStartHooks, } from '../hooks/actoviqHooks.js';
7
11
  import { createActoviqMemoryApi } from '../memory/actoviqMemory.js';
8
12
  import { appendMessagesToTranscript } from '../memory/actoviqTranscriptLogger.js';
@@ -15,12 +19,15 @@ import { SessionStore } from '../storage/sessionStore.js';
15
19
  import { TeammateStore } from '../storage/teammateStore.js';
16
20
  import { ActoviqSwarmApi } from '../swarm/actoviqSwarm.js';
17
21
  import { createActoviqFileTools } from '../tools/actoviqFileTools.js';
22
+ import { createGitWorktreeWorkspace, } from '../workspace/actoviqWorkspace.js';
18
23
  import { ActoviqAgentsApi, createActoviqTaskTool, summarizeActoviqAgentDefinition, } from './actoviqAgents.js';
19
24
  import { getDefaultActoviqAgents } from './defaultActoviqAgents.js';
25
+ import { loadActoviqAgentDefinitions } from './actoviqAgentDefinitions.js';
20
26
  import { ActoviqSkillsApi, loadActoviqSkillDefinitions, resolveActoviqSkillPrompt, summarizeActoviqSkillDefinition, } from './actoviqSkills.js';
21
27
  import { ActoviqBackgroundTaskManager, ActoviqBackgroundTasksApi, } from './actoviqBackgroundTasks.js';
22
28
  import { ActoviqContextApi, ActoviqSlashCommandsApi, } from './actoviqSlashCommands.js';
23
- import { compactActoviqSession, getPersistedActoviqCompactHistory, getPersistedActoviqCompactState, isActoviqPromptTooLongError, trackRecentFile, trackRecentSkill, } from './actoviqCompact.js';
29
+ import { compactActoviqSession, getPersistedActoviqCompactHistory, getPersistedActoviqCompactState, isActoviqPromptTooLongError, recordActoviqLoopCompactionsOnSession, trackRecentFile, trackRecentSkill, } from './actoviqCompact.js';
30
+ import { ACTOVIQ_SESSION_PERMISSION_STATE_KEY, getPersistedActoviqSessionPermissionState, serializeActoviqSessionPermissionState, } from './actoviqSessionPermissions.js';
24
31
  const RECENT_FILE_TOOL_NAMES = new Set(['Read', 'Write', 'Edit', 'NotebookEdit']);
25
32
  import { ActoviqToolsApi, buildActoviqCleanToolCatalog, resolveActoviqCleanToolMetadata, } from './actoviqToolCatalog.js';
26
33
  import { WorkflowApi } from '../workflow/workflowBuilder.js';
@@ -42,6 +49,7 @@ const RELEVANT_MEMORY_MAX_SESSION_BYTES = 60 * 1024;
42
49
  const DEFAULT_SESSION_MEMORY_MAX_TOKENS = 4_096;
43
50
  const DEFAULT_DREAM_MAX_TOKENS = 4_096;
44
51
  const MAX_REACTIVE_COMPACT_ATTEMPTS = 3;
52
+ const execFile = promisify(execFileCallback);
45
53
  const SESSION_MEMORY_SYSTEM_PROMPT = `You maintain the persistent session-memory markdown file for an ongoing engineering conversation.
46
54
 
47
55
  Return only the full updated markdown document.
@@ -76,6 +84,13 @@ function cloneAgentDefinition(definition) {
76
84
  hooks: cloneHooks(definition.hooks),
77
85
  tools: definition.tools ? [...definition.tools] : undefined,
78
86
  mcpServers: definition.mcpServers ? deepClone(definition.mcpServers) : undefined,
87
+ allowedTools: definition.allowedTools ? [...definition.allowedTools] : undefined,
88
+ disallowedTools: definition.disallowedTools ? [...definition.disallowedTools] : undefined,
89
+ allowedAgents: definition.allowedAgents ? [...definition.allowedAgents] : undefined,
90
+ skills: definition.skills ? [...definition.skills] : undefined,
91
+ requiredMcpServers: definition.requiredMcpServers
92
+ ? [...definition.requiredMcpServers]
93
+ : undefined,
79
94
  };
80
95
  }
81
96
  function cloneSkillDefinition(definition) {
@@ -105,6 +120,17 @@ export class AgentSessionsApi {
105
120
  get(sessionId) {
106
121
  return this.resumeSession(sessionId);
107
122
  }
123
+ resume(sessionId, options = {}) {
124
+ return this.resumeSession(sessionId, options);
125
+ }
126
+ async continueMostRecent(options = {}) {
127
+ const sessions = await this.store.list();
128
+ const mostRecent = sessions.find(session => session.status !== 'closed') ?? sessions[0];
129
+ if (!mostRecent) {
130
+ throw new Error('No stored sessions are available to resume.');
131
+ }
132
+ return this.resumeSession(mostRecent.id, options);
133
+ }
108
134
  delete(sessionId) {
109
135
  return this.store.delete(sessionId);
110
136
  }
@@ -178,7 +204,10 @@ function getAgentContinuityState(metadata) {
178
204
  lastDescription: typeof record.lastDescription === 'string' ? record.lastDescription : undefined,
179
205
  lastRunId: typeof record.lastRunId === 'string' ? record.lastRunId : undefined,
180
206
  lastSessionId: typeof record.lastSessionId === 'string' ? record.lastSessionId : undefined,
181
- lastStatus: record.lastStatus === 'completed' || record.lastStatus === 'async_launched'
207
+ lastStatus: record.lastStatus === 'completed' ||
208
+ record.lastStatus === 'async_launched' ||
209
+ record.lastStatus === 'failed' ||
210
+ record.lastStatus === 'cancelled'
182
211
  ? record.lastStatus
183
212
  : undefined,
184
213
  lastTaskId: typeof record.lastTaskId === 'string' ? record.lastTaskId : undefined,
@@ -186,6 +215,9 @@ function getAgentContinuityState(metadata) {
186
215
  runIds: readStringArray(record.runIds),
187
216
  sessionIds: readStringArray(record.sessionIds),
188
217
  taskIds: readStringArray(record.taskIds),
218
+ totalRequestCount: typeof record.totalRequestCount === 'number'
219
+ ? record.totalRequestCount
220
+ : undefined,
189
221
  totalToolCallCount: typeof record.totalToolCallCount === 'number'
190
222
  ? record.totalToolCallCount
191
223
  : undefined,
@@ -223,6 +255,7 @@ function mergeDelegatedAgents(existing, pending) {
223
255
  runIds: record.runId ? [record.runId] : undefined,
224
256
  sessionIds: record.sessionId ? [record.sessionId] : undefined,
225
257
  taskIds: record.taskId ? [record.taskId] : undefined,
258
+ totalRequestCount: record.requestCount,
226
259
  totalToolCallCount: record.toolCallCount,
227
260
  totalToolErrorCount: record.toolErrorCount,
228
261
  });
@@ -239,6 +272,7 @@ function mergeDelegatedAgents(existing, pending) {
239
272
  current.runIds = appendUnique(current.runIds, record.runId);
240
273
  current.sessionIds = appendUnique(current.sessionIds, record.sessionId);
241
274
  current.taskIds = appendUnique(current.taskIds, record.taskId);
275
+ current.totalRequestCount = sumOptional(current.totalRequestCount, record.requestCount);
242
276
  current.totalToolCallCount = sumOptional(current.totalToolCallCount, record.toolCallCount);
243
277
  current.totalToolErrorCount = sumOptional(current.totalToolErrorCount, record.toolErrorCount);
244
278
  }
@@ -320,6 +354,8 @@ export class ActoviqAgentClient {
320
354
  defaultTools;
321
355
  defaultMcpServers;
322
356
  hooks;
357
+ maxSubagentDepth;
358
+ maxSubagentFanout;
323
359
  sessions;
324
360
  agents;
325
361
  skills;
@@ -336,13 +372,15 @@ export class ActoviqAgentClient {
336
372
  agentDefinitions;
337
373
  skillDefinitions;
338
374
  pendingDelegations = new Map();
375
+ pendingRuntimeNotifications = new Map();
376
+ subagentInputQueues = new Map();
339
377
  sessionRuntimeOverrides = new Map();
340
378
  backgroundTaskManager;
341
379
  defaultPermissionMode;
342
380
  defaultPermissions;
343
381
  defaultClassifier;
344
382
  defaultApprover;
345
- constructor(config, store, backgroundTaskStore, mailboxStore, teammateStore, modelApi, mcpManager, defaultTools, defaultMcpServers, hooks, agentDefinitions = [], skillDefinitions = [], defaultPermissionMode, defaultPermissions, defaultClassifier, defaultApprover, sessionManagerConfig) {
383
+ constructor(config, store, backgroundTaskStore, mailboxStore, teammateStore, modelApi, mcpManager, defaultTools, defaultMcpServers, hooks, agentDefinitions = [], skillDefinitions = [], defaultPermissionMode, defaultPermissions, defaultClassifier, defaultApprover, sessionManagerConfig, maxSubagentDepth = 1, maxSubagentFanout = 8) {
346
384
  this.config = config;
347
385
  this.store = store;
348
386
  this.backgroundTaskStore = backgroundTaskStore;
@@ -353,8 +391,10 @@ export class ActoviqAgentClient {
353
391
  this.defaultTools = defaultTools;
354
392
  this.defaultMcpServers = defaultMcpServers;
355
393
  this.hooks = hooks;
394
+ this.maxSubagentDepth = maxSubagentDepth;
395
+ this.maxSubagentFanout = maxSubagentFanout;
356
396
  this.sessionManager = new SessionManager(this.store, sessionManagerConfig);
357
- this.sessions = new AgentSessionsApi(this.store, (sessionId) => this.resumeSession(sessionId), this.sessionManager);
397
+ this.sessions = new AgentSessionsApi(this.store, (sessionId, options) => this.resumeSession(sessionId, options), this.sessionManager);
358
398
  this.agentDefinitions = new Map(agentDefinitions.map(definition => [definition.name, cloneAgentDefinition(definition)]));
359
399
  this.skillDefinitions = new Map(skillDefinitions.map(definition => [definition.name, cloneSkillDefinition(definition)]));
360
400
  this.backgroundTaskManager = new ActoviqBackgroundTaskManager(this.backgroundTaskStore);
@@ -419,9 +459,16 @@ export class ActoviqAgentClient {
419
459
  resumeSession: (sessionId) => this.resumeSession(sessionId),
420
460
  getBackgroundTask: (taskId) => this.backgroundTaskManager.get(taskId),
421
461
  }, this.teammateStore, this.mailboxStore);
422
- if (!this.defaultTools.some(tool => tool.name === 'Task')) {
462
+ const existingDelegationTool = this.defaultTools.find(tool => tool.name === 'Agent' || tool.name === 'Task');
463
+ if (!existingDelegationTool) {
423
464
  this.defaultTools.unshift(this.createTaskTool());
424
465
  }
466
+ else if (existingDelegationTool.name === 'Task') {
467
+ existingDelegationTool.aliases = [
468
+ ...new Set([...(existingDelegationTool.aliases ?? []), 'Agent']),
469
+ ];
470
+ }
471
+ this.replaceDefaultTool(this.createSendMessageTool());
425
472
  this.replaceDefaultTool(this.createBackgroundTaskListTool());
426
473
  this.replaceDefaultTool(this.createBackgroundTaskGetTool());
427
474
  this.replaceDefaultTool(this.createBackgroundTaskStopTool());
@@ -443,7 +490,7 @@ export class ActoviqAgentClient {
443
490
  }
444
491
  /** Resolve a tool definition by name from the default tool registry. */
445
492
  getTool(name) {
446
- return this.defaultTools.find(t => t.name === name);
493
+ return this.defaultTools.find(t => t.name === name || t.aliases?.includes(name));
447
494
  }
448
495
  async getToolCatalog(options) {
449
496
  return buildActoviqCleanToolCatalog(await this.listToolMetadata(options));
@@ -489,9 +536,13 @@ export class ActoviqAgentClient {
489
536
  disableDefaultSkills: options.disableDefaultSkills,
490
537
  loadDefaultSkillDirectories: options.loadDefaultSkillDirectories,
491
538
  });
492
- const agentDefinitions = options.disableDefaultAgents === true
493
- ? [...(options.agents ?? [])]
494
- : [...getDefaultActoviqAgents(), ...(options.agents ?? [])];
539
+ const loadedAgents = await loadActoviqAgentDefinitions({
540
+ homeDir: config.homeDir,
541
+ workDir: config.workDir,
542
+ agentDirectories: options.agentDirectories,
543
+ loadDefaultAgentDirectories: options.loadDefaultAgentDirectories,
544
+ });
545
+ const agentDefinitions = mergeAgentDefinitions(options.disableDefaultAgents === true ? [] : getDefaultActoviqAgents(), loadedAgents, options.agents ?? []);
495
546
  const defaultTools = [...(options.tools ?? [])];
496
547
  const defaultMcpServers = [...(options.mcpServers ?? [])];
497
548
  if (options.computerUse) {
@@ -503,7 +554,9 @@ export class ActoviqAgentClient {
503
554
  defaultTools.push(...createActoviqComputerUseTools(computerUseOptions));
504
555
  }
505
556
  }
506
- return new ActoviqAgentClient(config, store, backgroundTaskStore, mailboxStore, teammateStore, modelApi, mcpManager, defaultTools, defaultMcpServers, options.hooks, agentDefinitions, [...loadedSkills, ...(options.skills ?? [])], options.permissionMode, options.permissions, options.classifier, options.approver, options.sessionManager);
557
+ const client = new ActoviqAgentClient(config, store, backgroundTaskStore, mailboxStore, teammateStore, modelApi, mcpManager, defaultTools, defaultMcpServers, options.hooks, agentDefinitions, [...loadedSkills, ...(options.skills ?? [])], options.permissionMode, options.permissions, options.classifier, options.approver, options.sessionManager, options.maxSubagentDepth, options.maxSubagentFanout);
558
+ await client.backgroundTaskManager.reconcileInterruptedTasks();
559
+ return client;
507
560
  }
508
561
  async run(input, options = {}) {
509
562
  const runId = createId();
@@ -564,24 +617,74 @@ export class ActoviqAgentClient {
564
617
  return race(tasks, options);
565
618
  }
566
619
  async createSession(options = {}) {
620
+ const model = this.resolveModel(options.model);
567
621
  const stored = await this.store.create({
568
622
  id: options.id,
569
623
  title: options.title,
570
624
  systemPrompt: options.systemPrompt ?? this.config.systemPrompt,
571
- model: options.model ?? this.config.model,
625
+ model,
572
626
  tags: options.tags,
573
627
  metadata: {
574
- __actoviqWorkDir: this.config.workDir,
575
628
  ...(options.metadata ?? {}),
629
+ __actoviqWorkDir: this.config.workDir,
630
+ ...(options.permissionMode || options.permissions
631
+ ? {
632
+ [ACTOVIQ_SESSION_PERMISSION_STATE_KEY]: serializeActoviqSessionPermissionState({
633
+ mode: options.permissionMode,
634
+ permissions: clonePermissionRules(options.permissions) ?? [],
635
+ }),
636
+ }
637
+ : {}),
576
638
  },
577
639
  initialMessages: options.initialMessages,
578
640
  });
579
641
  return this.hydrateSession(stored);
580
642
  }
581
- async resumeSession(sessionId) {
582
- const stored = await this.store.load(sessionId);
643
+ async resumeSession(sessionId, options = {}) {
644
+ const loaded = options.fork
645
+ ? await this.store.fork(sessionId, {
646
+ title: options.title,
647
+ tags: options.tags,
648
+ metadata: options.metadata,
649
+ })
650
+ : await this.store.load(sessionId);
651
+ const stored = deepClone(loaded);
652
+ if (options.model) {
653
+ stored.model = this.resolveModel(options.model);
654
+ }
655
+ if (options.permissionMode !== undefined || options.permissions !== undefined) {
656
+ const currentPermissionState = getPersistedActoviqSessionPermissionState(stored.metadata);
657
+ stored.metadata[ACTOVIQ_SESSION_PERMISSION_STATE_KEY] =
658
+ serializeActoviqSessionPermissionState({
659
+ mode: options.permissionMode ?? currentPermissionState.mode,
660
+ permissions: options.permissions !== undefined
661
+ ? clonePermissionRules(options.permissions) ?? []
662
+ : currentPermissionState.permissions,
663
+ });
664
+ }
665
+ if (!options.fork) {
666
+ if (options.title?.trim()) {
667
+ stored.title = options.title.trim();
668
+ stored.titleSource = 'manual';
669
+ }
670
+ if (options.tags) {
671
+ stored.tags = [...options.tags];
672
+ }
673
+ if (options.metadata) {
674
+ stored.metadata = { ...stored.metadata, ...options.metadata };
675
+ }
676
+ }
677
+ stored.status = 'active';
678
+ stored.lastActiveAt = nowIso();
679
+ stored.updatedAt = stored.lastActiveAt;
680
+ await this.store.save(stored);
583
681
  return this.hydrateSession(stored);
584
682
  }
683
+ resolveModel(model) {
684
+ return model
685
+ ? resolveActoviqModelReference(model, this.config.modelTiers).model
686
+ : this.config.model;
687
+ }
585
688
  async compactSessionById(sessionId, options = {}) {
586
689
  const session = await this.resumeSession(sessionId);
587
690
  return this.compactSessionForSession(session, options);
@@ -603,8 +706,28 @@ export class ActoviqAgentClient {
603
706
  return this.dream.maybeAutoDream(options);
604
707
  }
605
708
  async close() {
606
- this.sessionManager.dispose();
607
- await this.mcpManager.closeAll();
709
+ const errors = [];
710
+ try {
711
+ await this.backgroundTaskManager.cancelAll();
712
+ }
713
+ catch (error) {
714
+ errors.push(error);
715
+ }
716
+ try {
717
+ this.sessionManager.dispose();
718
+ }
719
+ catch (error) {
720
+ errors.push(error);
721
+ }
722
+ try {
723
+ await this.mcpManager.closeAll();
724
+ }
725
+ catch (error) {
726
+ errors.push(error);
727
+ }
728
+ if (errors.length > 0) {
729
+ throw new AggregateError(errors, 'Errors occurred while closing the agent SDK.');
730
+ }
608
731
  }
609
732
  listAgentDefinitions() {
610
733
  return [...this.agentDefinitions.values()].map(summarizeActoviqAgentDefinition);
@@ -628,6 +751,8 @@ export class ActoviqAgentClient {
628
751
  ...(definition.metadata ?? {}),
629
752
  ...(options.metadata ?? {}),
630
753
  __actoviqAgentDefinition: definition.name,
754
+ __actoviqAgentMemory: definition.memory,
755
+ __actoviqAgentSource: definition.source,
631
756
  [AGENT_CONTINUITY_STATE_KEY]: {
632
757
  currentAgent: definition.name,
633
758
  delegatedAgents: [],
@@ -665,8 +790,10 @@ export class ActoviqAgentClient {
665
790
  ...options,
666
791
  listAgentDefinitions: () => this.listAgentDefinitions(),
667
792
  getAgentDefinition: (agent) => this.getAgentDefinition(agent),
668
- runAgent: (agent, prompt, runOptions) => this.runWithAgent(agent, prompt, runOptions),
669
- onDelegated: ({ subagentType, description, parentSessionId, parentRunId, runId, sessionId, status, taskId, toolCallCount, toolErrorCount, textSummary, }) => {
793
+ runAgent: (agent, prompt, runOptions, delegation) => this.runDelegatedAgentTask(agent, prompt, runOptions, delegation),
794
+ maxDepth: this.maxSubagentDepth,
795
+ maxFanout: this.maxSubagentFanout,
796
+ onDelegated: ({ subagentType, description, parentSessionId, parentRunId, runId, sessionId, status, taskId, requestCount, toolCallCount, toolErrorCount, textSummary, }) => {
670
797
  this.recordPendingDelegation(parentSessionId ?? parentRunId, {
671
798
  name: subagentType,
672
799
  description,
@@ -675,12 +802,13 @@ export class ActoviqAgentClient {
675
802
  sessionId,
676
803
  status,
677
804
  taskId,
805
+ requestCount,
678
806
  toolCallCount,
679
807
  toolErrorCount,
680
808
  textSummary,
681
809
  });
682
810
  },
683
- launchBackgroundAgent: (agent, prompt, backgroundOptions, runOptions) => this.launchBackgroundAgentTask(agent, prompt, backgroundOptions, runOptions),
811
+ launchBackgroundAgent: (agent, prompt, backgroundOptions, runOptions, delegation) => this.launchBackgroundAgentTask(agent, prompt, backgroundOptions, runOptions, delegation),
684
812
  });
685
813
  }
686
814
  replaceDefaultTool(replacement) {
@@ -691,6 +819,39 @@ export class ActoviqAgentClient {
691
819
  }
692
820
  this.defaultTools.push(replacement);
693
821
  }
822
+ createSendMessageTool() {
823
+ return tool({
824
+ name: 'SendMessage',
825
+ description: 'Send a follow-up message to a running or previously completed agent by agent id, task id, session id, or assigned name. Running agents receive it at the next tool boundary; stopped agents resume in the background with full session context.',
826
+ inputSchema: z.strictObject({
827
+ to: z.string().min(1).describe('Agent id, task id, session id, or assigned name'),
828
+ message: z.union([z.string(), z.record(z.string(), z.unknown())]),
829
+ summary: z.string().optional(),
830
+ }),
831
+ isConcurrencySafe: () => true,
832
+ }, async ({ to, message, summary }, context) => {
833
+ const text = typeof message === 'string'
834
+ ? message
835
+ : JSON.stringify(message);
836
+ const routed = await this.routeMessageToAgent(to.trim(), text, {
837
+ parentRunId: context.runId,
838
+ parentSessionId: context.sessionId,
839
+ runOptions: {
840
+ permissionMode: context.permissionMode,
841
+ permissions: context.permissions,
842
+ classifier: context.classifier,
843
+ approver: context.approver,
844
+ hooks: context.hooks,
845
+ effort: context.effort,
846
+ metadata: context.metadata,
847
+ },
848
+ });
849
+ return {
850
+ ...routed,
851
+ summary,
852
+ };
853
+ });
854
+ }
694
855
  /**
695
856
  * Model-facing Skill tool backed by the real skill registry. Loads the
696
857
  * resolved skill content into the conversation as the tool result
@@ -897,12 +1058,30 @@ export class ActoviqAgentClient {
897
1058
  return session.send(execution.prompt, execution.options);
898
1059
  }
899
1060
  async forwardStreamResult(stream, emit) {
1061
+ const errors = [];
900
1062
  const pump = (async () => {
901
- for await (const event of stream) {
902
- emit(event);
1063
+ try {
1064
+ for await (const event of stream) {
1065
+ emit(event);
1066
+ }
1067
+ }
1068
+ catch (error) {
1069
+ errors.push(error);
903
1070
  }
904
1071
  })();
905
- const [result] = await Promise.all([stream.result, pump]);
1072
+ let result;
1073
+ try {
1074
+ [result] = await Promise.all([stream.result, pump]);
1075
+ }
1076
+ catch (error) {
1077
+ if (errors.length > 0) {
1078
+ throw new AggregateError([error, ...errors], 'Stream result and event pump both failed.');
1079
+ }
1080
+ throw error;
1081
+ }
1082
+ if (errors.length > 0) {
1083
+ throw errors[0];
1084
+ }
906
1085
  return result;
907
1086
  }
908
1087
  getSessionRuntimeOverrides(sessionId) {
@@ -949,14 +1128,19 @@ export class ActoviqAgentClient {
949
1128
  }
950
1129
  this.sessionRuntimeOverrides.set(sessionId, next);
951
1130
  }
952
- setSessionRuntimePermissionContext(sessionId, context) {
1131
+ async setSessionRuntimePermissionContext(session, context) {
1132
+ const sessionId = session.id;
953
1133
  const current = this.sessionRuntimeOverrides.get(sessionId) ?? {};
1134
+ const stored = session.snapshot();
1135
+ const persisted = getPersistedActoviqSessionPermissionState(stored.metadata);
954
1136
  const next = {
955
1137
  ...current,
956
- permissionMode: context.mode,
957
- permissions: clonePermissionRules(context.permissions),
958
- classifier: context.classifier,
959
- approver: context.approver,
1138
+ permissionMode: context.mode ?? current.permissionMode ?? persisted.mode,
1139
+ permissions: context.permissions !== undefined
1140
+ ? clonePermissionRules(context.permissions)
1141
+ : current.permissions ?? persisted.permissions,
1142
+ classifier: context.classifier ?? current.classifier,
1143
+ approver: context.approver ?? current.approver,
960
1144
  };
961
1145
  if (isHooksEmpty(next.hooks) &&
962
1146
  !next.permissionMode &&
@@ -964,27 +1148,42 @@ export class ActoviqAgentClient {
964
1148
  !next.classifier &&
965
1149
  !next.approver) {
966
1150
  this.sessionRuntimeOverrides.delete(sessionId);
967
- return;
968
1151
  }
969
- this.sessionRuntimeOverrides.set(sessionId, next);
1152
+ else {
1153
+ this.sessionRuntimeOverrides.set(sessionId, next);
1154
+ }
1155
+ stored.metadata[ACTOVIQ_SESSION_PERMISSION_STATE_KEY] =
1156
+ serializeActoviqSessionPermissionState({
1157
+ mode: next.permissionMode,
1158
+ permissions: clonePermissionRules(next.permissions) ?? [],
1159
+ });
1160
+ stored.updatedAt = nowIso();
1161
+ await this.store.save(stored);
1162
+ return stored;
970
1163
  }
971
- clearSessionRuntimePermissionContext(sessionId) {
1164
+ async clearSessionRuntimePermissionContext(session) {
1165
+ const sessionId = session.id;
972
1166
  const current = this.sessionRuntimeOverrides.get(sessionId);
973
- if (!current) {
974
- return;
975
- }
976
- const next = {
977
- ...current,
978
- permissionMode: undefined,
979
- permissions: undefined,
980
- classifier: undefined,
981
- approver: undefined,
982
- };
983
- if (isHooksEmpty(next.hooks)) {
984
- this.sessionRuntimeOverrides.delete(sessionId);
985
- return;
1167
+ if (current) {
1168
+ const next = {
1169
+ ...current,
1170
+ permissionMode: undefined,
1171
+ permissions: undefined,
1172
+ classifier: undefined,
1173
+ approver: undefined,
1174
+ };
1175
+ if (isHooksEmpty(next.hooks)) {
1176
+ this.sessionRuntimeOverrides.delete(sessionId);
1177
+ }
1178
+ else {
1179
+ this.sessionRuntimeOverrides.set(sessionId, next);
1180
+ }
986
1181
  }
987
- this.sessionRuntimeOverrides.set(sessionId, next);
1182
+ const stored = session.snapshot();
1183
+ delete stored.metadata[ACTOVIQ_SESSION_PERMISSION_STATE_KEY];
1184
+ stored.updatedAt = nowIso();
1185
+ await this.store.save(stored);
1186
+ return stored;
988
1187
  }
989
1188
  applySessionRuntimeOverrides(sessionId, options) {
990
1189
  const overrides = this.getSessionRuntimeOverrides(sessionId);
@@ -1001,6 +1200,16 @@ export class ActoviqAgentClient {
1001
1200
  };
1002
1201
  }
1003
1202
  hydrateSession(stored) {
1203
+ const persistedPermissionState = getPersistedActoviqSessionPermissionState(stored.metadata);
1204
+ if (persistedPermissionState.mode ||
1205
+ persistedPermissionState.permissions.length > 0) {
1206
+ const current = this.sessionRuntimeOverrides.get(stored.id) ?? {};
1207
+ this.sessionRuntimeOverrides.set(stored.id, {
1208
+ ...current,
1209
+ permissionMode: current.permissionMode ?? persistedPermissionState.mode,
1210
+ permissions: current.permissions ?? persistedPermissionState.permissions,
1211
+ });
1212
+ }
1004
1213
  return new AgentSession({
1005
1214
  runSession: (session, input, options) => this.runOnSession(session, input, options),
1006
1215
  streamSession: (session, input, options) => this.streamOnSession(session, input, options),
@@ -1021,8 +1230,9 @@ export class ActoviqAgentClient {
1021
1230
  getAgentContinuity: (session) => this.getAgentContinuityForSession(session),
1022
1231
  setRuntimeHooks: (session, hooks) => this.setSessionRuntimeHooks(session.id, hooks),
1023
1232
  clearRuntimeHooks: (session) => this.clearSessionRuntimeHooks(session.id),
1024
- setRuntimePermissionContext: (session, context) => this.setSessionRuntimePermissionContext(session.id, context),
1025
- clearRuntimePermissionContext: (session) => this.clearSessionRuntimePermissionContext(session.id),
1233
+ setModel: (session, model) => this.setSessionModel(session, model),
1234
+ setRuntimePermissionContext: (session, context) => this.setSessionRuntimePermissionContext(session, context),
1235
+ clearRuntimePermissionContext: (session) => this.clearSessionRuntimePermissionContext(session),
1026
1236
  hydrate: (next) => this.hydrateSession(next),
1027
1237
  saveCheckpoint: (_session, label) => this.store.saveCheckpoint(stored.id, label),
1028
1238
  restoreCheckpoint: (session, checkpointId) => this.restoreCheckpointToSession(session, checkpointId),
@@ -1030,6 +1240,13 @@ export class ActoviqAgentClient {
1030
1240
  deleteCheckpoint: (_session, checkpointId) => this.store.deleteCheckpoint(stored.id, checkpointId),
1031
1241
  }, this.store, stored);
1032
1242
  }
1243
+ async setSessionModel(session, model) {
1244
+ const next = session.snapshot();
1245
+ next.model = this.resolveModel(model);
1246
+ next.updatedAt = nowIso();
1247
+ await this.store.save(next);
1248
+ return next;
1249
+ }
1033
1250
  async restoreCheckpointToSession(session, checkpointId) {
1034
1251
  const checkpoint = await this.store.loadCheckpoint(session.id, checkpointId);
1035
1252
  session.replace(checkpoint.snapshot);
@@ -1132,19 +1349,30 @@ export class ActoviqAgentClient {
1132
1349
  ...(augmentations?.metadata ?? {}),
1133
1350
  ...(options.metadata ?? {}),
1134
1351
  };
1135
- const mergedTools = mergeUniqueByName(options.__actoviqUseDefaultTools === false ? [] : this.defaultTools, options.tools ?? []);
1352
+ const workDir = options.__actoviqWorkDir ?? options.workDir ?? this.config.workDir;
1353
+ const mergedTools = filterAgentTools(mergeUniqueByName(options.__actoviqUseDefaultTools === false ? [] : this.defaultTools, options.tools ?? []), options.__actoviqAllowedTools, options.__actoviqDisallowedTools);
1136
1354
  // Collect tool prompts for system prompt assembly
1137
1355
  const toolPromptParts = await collectToolPrompts(mergedTools, {
1138
- workDir: this.config.workDir,
1356
+ workDir,
1139
1357
  permissionMode: options.permissionMode ?? this.defaultPermissionMode,
1140
1358
  });
1141
1359
  const systemPrompt = await this.resolveSystemPrompt(options, session, [...(augmentations?.systemPromptParts ?? []), ...toolPromptParts]);
1142
- const runtimeConfig = options.__actoviqMaxToolIterations
1360
+ const runtimeConfig = options.__actoviqMaxToolIterations || workDir !== this.config.workDir
1143
1361
  ? {
1144
1362
  ...this.config,
1145
- maxToolIterations: options.__actoviqMaxToolIterations,
1363
+ workDir,
1364
+ ...(options.__actoviqMaxToolIterations
1365
+ ? { maxToolIterations: options.__actoviqMaxToolIterations }
1366
+ : {}),
1146
1367
  }
1147
1368
  : this.config;
1369
+ const notificationKey = session?.id ?? runId;
1370
+ const drainQueuedInputs = notificationKey || options.drainQueuedInputs
1371
+ ? () => [
1372
+ ...(options.drainQueuedInputs?.() ?? []),
1373
+ ...this.drainRuntimeNotifications(notificationKey),
1374
+ ]
1375
+ : undefined;
1148
1376
  return executeConversation({
1149
1377
  runId,
1150
1378
  input,
@@ -1154,9 +1382,10 @@ export class ActoviqAgentClient {
1154
1382
  systemPrompt,
1155
1383
  tools: mergedTools,
1156
1384
  mcpServers: mergeUniqueByName(options.__actoviqUseDefaultMcpServers === false ? [] : this.defaultMcpServers, options.mcpServers ?? []),
1157
- model: options.model ?? session?.model ?? this.config.model,
1385
+ model: this.resolveModel(options.model ?? session?.model),
1158
1386
  maxTokens: options.maxTokens,
1159
1387
  temperature: options.temperature,
1388
+ effort: options.effort,
1160
1389
  toolChoice: options.toolChoice,
1161
1390
  userId: options.userId ?? this.config.userId,
1162
1391
  metadata,
@@ -1167,6 +1396,7 @@ export class ActoviqAgentClient {
1167
1396
  approver: options.approver ?? this.defaultApprover,
1168
1397
  canUseTool: options.canUseTool,
1169
1398
  hooks: augmentations?.hooks,
1399
+ drainQueuedInputs,
1170
1400
  streaming,
1171
1401
  emit,
1172
1402
  skipRunStartedEvent,
@@ -1231,11 +1461,32 @@ export class ActoviqAgentClient {
1231
1461
  return promptParts.join('\n\n');
1232
1462
  }
1233
1463
  async prepareRunAugmentations(runId, input, options, session) {
1464
+ const internalOptions = options;
1234
1465
  const promptText = typeof input === 'string' ? input : extractTextFromContent(input);
1235
1466
  const memoryContext = await this.prepareRelevantMemoryContext(input, session);
1236
1467
  const invokedSkillContext = this.prepareInvokedSkillContext(session);
1468
+ const notificationMessages = session
1469
+ ? (await this.collectPendingTaskNotifications(session.id)).map(text => ({
1470
+ role: 'user',
1471
+ content: text,
1472
+ }))
1473
+ : [];
1474
+ const initialPromptMessages = internalOptions.__actoviqInitialPrompt
1475
+ ? [{
1476
+ role: 'user',
1477
+ content: internalOptions.__actoviqInitialPrompt,
1478
+ }]
1479
+ : [];
1480
+ const agentMemoryMessages = session
1481
+ ? await this.prepareAgentMemoryMessages(session, internalOptions)
1482
+ : [];
1483
+ const preloadedSkillMessages = await this.preparePreloadedAgentSkillMessages(internalOptions.__actoviqPreloadedSkills, session?.id, internalOptions.__actoviqWorkDir ?? internalOptions.workDir ?? this.config.workDir);
1237
1484
  const hooks = mergeActoviqHooks(this.hooks, options.hooks);
1238
1485
  const prefixedMessages = [
1486
+ ...notificationMessages,
1487
+ ...initialPromptMessages,
1488
+ ...agentMemoryMessages,
1489
+ ...preloadedSkillMessages,
1239
1490
  ...invokedSkillContext.prefixedMessages,
1240
1491
  ...memoryContext.prefixedMessages,
1241
1492
  ];
@@ -1248,7 +1499,7 @@ export class ActoviqAgentClient {
1248
1499
  promptText,
1249
1500
  sessionId: session?.id,
1250
1501
  session: session ? deepClone(session) : undefined,
1251
- workDir: this.config.workDir,
1502
+ workDir: internalOptions.__actoviqWorkDir ?? internalOptions.workDir ?? this.config.workDir,
1252
1503
  options,
1253
1504
  });
1254
1505
  if (!result) {
@@ -1271,6 +1522,70 @@ export class ActoviqAgentClient {
1271
1522
  metadata,
1272
1523
  };
1273
1524
  }
1525
+ async preparePreloadedAgentSkillMessages(skillNames, sessionId, workDir) {
1526
+ if (!skillNames?.length) {
1527
+ return [];
1528
+ }
1529
+ const messages = [];
1530
+ for (const skillName of skillNames) {
1531
+ const definition = this.getSkillDefinition(skillName);
1532
+ if (!definition) {
1533
+ messages.push({
1534
+ role: 'user',
1535
+ content: `<agent_skill_warning>Skill "${skillName}" was requested by the agent definition but is not registered.</agent_skill_warning>`,
1536
+ });
1537
+ continue;
1538
+ }
1539
+ const resolved = await resolveActoviqSkillPrompt(definition, '', {
1540
+ args: '',
1541
+ workDir,
1542
+ homeDir: this.config.homeDir,
1543
+ sessionId,
1544
+ userId: this.config.userId,
1545
+ });
1546
+ messages.push({
1547
+ role: 'user',
1548
+ content: [
1549
+ `<agent_skill name="${skillName}">`,
1550
+ extractTextFromContent(resolved.content),
1551
+ '</agent_skill>',
1552
+ ].join('\n'),
1553
+ });
1554
+ }
1555
+ return messages;
1556
+ }
1557
+ async prepareAgentMemoryMessages(session, options) {
1558
+ const scope = session.metadata.__actoviqAgentMemory;
1559
+ const agentName = session.metadata.__actoviqAgentDefinition;
1560
+ if ((scope !== 'user' && scope !== 'project' && scope !== 'local') ||
1561
+ typeof agentName !== 'string') {
1562
+ return [];
1563
+ }
1564
+ const workDir = options.__actoviqWorkDir ?? options.workDir ?? this.config.workDir;
1565
+ const root = scope === 'user'
1566
+ ? path.join(this.config.homeDir, 'agent-memory', agentName)
1567
+ : scope === 'project'
1568
+ ? path.join(workDir, '.actoviq', 'agent-memory', agentName)
1569
+ : path.join(workDir, '.actoviq', 'agent-memory-local', agentName);
1570
+ const memoryPath = path.join(root, 'MEMORY.md');
1571
+ await mkdir(root, { recursive: true });
1572
+ let content = '';
1573
+ try {
1574
+ content = await readFile(memoryPath, 'utf8');
1575
+ }
1576
+ catch {
1577
+ // A new agent memory starts empty and can be updated by normal file tools.
1578
+ }
1579
+ return [{
1580
+ role: 'user',
1581
+ content: [
1582
+ `<agent_memory scope="${scope}" path="${memoryPath}">`,
1583
+ content.trim() || '(empty)',
1584
+ '</agent_memory>',
1585
+ `Persist durable lessons for future ${agentName} runs by updating ${memoryPath}.`,
1586
+ ].join('\n'),
1587
+ }];
1588
+ }
1274
1589
  async applyPostRunHooks(runId, input, options, result, session) {
1275
1590
  const promptText = typeof input === 'string' ? input : extractTextFromContent(input);
1276
1591
  const hooks = mergeActoviqHooks(this.hooks, options.hooks);
@@ -1283,7 +1598,9 @@ export class ActoviqAgentClient {
1283
1598
  promptText,
1284
1599
  sessionId: session?.id,
1285
1600
  session: session ? deepClone(session) : undefined,
1286
- workDir: this.config.workDir,
1601
+ workDir: options.__actoviqWorkDir ??
1602
+ options.workDir ??
1603
+ this.config.workDir,
1287
1604
  options,
1288
1605
  result,
1289
1606
  });
@@ -1309,6 +1626,27 @@ export class ActoviqAgentClient {
1309
1626
  existing.push(record);
1310
1627
  this.pendingDelegations.set(key, existing);
1311
1628
  }
1629
+ updatePendingDelegation(key, task) {
1630
+ const records = this.pendingDelegations.get(key);
1631
+ const record = records?.find(candidate => candidate.taskId === task.id);
1632
+ if (!record) {
1633
+ return;
1634
+ }
1635
+ record.runId = task.runId ?? record.runId;
1636
+ record.sessionId = task.sessionId ?? record.sessionId;
1637
+ record.status =
1638
+ task.status === 'completed'
1639
+ ? 'completed'
1640
+ : task.status === 'failed'
1641
+ ? 'failed'
1642
+ : task.status === 'cancelled'
1643
+ ? 'cancelled'
1644
+ : record.status;
1645
+ record.requestCount = task.requestCount ?? record.requestCount;
1646
+ record.toolCallCount = task.toolCallCount ?? record.toolCallCount;
1647
+ record.toolErrorCount = task.toolErrorCount ?? record.toolErrorCount;
1648
+ record.textSummary = task.text ?? task.partialText ?? record.textSummary;
1649
+ }
1312
1650
  consumePendingDelegations(key) {
1313
1651
  if (!key) {
1314
1652
  return [];
@@ -1409,6 +1747,9 @@ export class ActoviqAgentClient {
1409
1747
  latestBoundary,
1410
1748
  compactCount: Math.max(compactState.compactCount, persistedCompactState.compactCount),
1411
1749
  microcompactCount: Math.max(compactState.microcompactCount, persistedCompactState.microcompactCount),
1750
+ consecutiveCompactFailures: persistedCompactState.consecutiveFailures,
1751
+ lastCompactFailureAt: persistedCompactState.lastFailureAt,
1752
+ lastCompactError: persistedCompactState.lastError,
1412
1753
  hasCompacted: compactState.hasCompacted ||
1413
1754
  persistedCompactState.compactCount + persistedCompactState.microcompactCount > 0,
1414
1755
  summaryMessage: compactState.summaryMessage ?? persistedCompactState.lastSummaryMessage,
@@ -1437,12 +1778,16 @@ export class ActoviqAgentClient {
1437
1778
  }, {
1438
1779
  workDir: this.config.workDir,
1439
1780
  systemPrompt: snapshot.systemPrompt ?? this.config.systemPrompt,
1440
- model: options.model ?? snapshot.model ?? this.config.model,
1781
+ model: this.resolveModel(options.model ?? snapshot.model),
1441
1782
  modelApi: this.modelApi,
1442
1783
  compactConfig: this.config.compact,
1443
1784
  runtimeState: this.getSessionMemoryRuntimeState(snapshot),
1444
1785
  });
1445
- if (reactive.session === snapshot) {
1786
+ if (!reactive.result.compacted) {
1787
+ if (reactive.session !== snapshot) {
1788
+ await this.store.save(reactive.session);
1789
+ session.replace(reactive.session);
1790
+ }
1446
1791
  return undefined;
1447
1792
  }
1448
1793
  await this.store.save(reactive.session);
@@ -1460,49 +1805,239 @@ export class ActoviqAgentClient {
1460
1805
  result: reactive.result,
1461
1806
  };
1462
1807
  }
1463
- async launchBackgroundAgentTask(agent, prompt, options, runOptions = {}) {
1808
+ async runDelegatedAgentTask(agent, prompt, runOptions = {}, delegation = { description: prompt }) {
1464
1809
  const definition = this.requireAgentDefinition(agent);
1465
- const session = await this.createAgentSession(agent, {
1466
- title: `${definition.name}: ${truncateText(prompt, 80)}`,
1810
+ const prepared = await this.prepareDelegatedWorkspace(definition, delegation);
1811
+ try {
1812
+ const session = await this.createAgentSession(agent, {
1813
+ title: `${delegation.name ?? definition.name}: ${truncateText(delegation.description, 80)}`,
1814
+ metadata: {
1815
+ __actoviqAgentName: delegation.name,
1816
+ __actoviqAgentWorkDir: prepared.workDir,
1817
+ __actoviqAgentWorktreePath: prepared.workspace?.path,
1818
+ __actoviqAgentWorktreeBranch: prepared.workspace?.metadata.branch,
1819
+ },
1820
+ });
1821
+ const result = await session.send(prompt, this.prepareDelegatedRunOptions(runOptions, prepared.workDir, session.id));
1822
+ const retained = await this.finalizeDelegatedWorkspace(prepared.workspace);
1823
+ return {
1824
+ result,
1825
+ sessionId: session.id,
1826
+ worktreePath: retained ? prepared.workspace?.path : undefined,
1827
+ worktreeBranch: retained ? prepared.workspace?.metadata.branch : undefined,
1828
+ };
1829
+ }
1830
+ catch (error) {
1831
+ await this.finalizeDelegatedWorkspace(prepared.workspace);
1832
+ throw error;
1833
+ }
1834
+ }
1835
+ async routeMessageToAgent(address, message, context) {
1836
+ const tasks = await this.backgroundTaskManager.list();
1837
+ const task = tasks.find(candidate => candidate.id === address ||
1838
+ candidate.sessionId === address ||
1839
+ candidate.agentName === address);
1840
+ if (task?.sessionId && (task.status === 'queued' || task.status === 'running')) {
1841
+ const queue = this.subagentInputQueues.get(task.sessionId) ?? [];
1842
+ queue.push(message);
1843
+ this.subagentInputQueues.set(task.sessionId, queue);
1844
+ await this.backgroundTaskManager.updateProgress(task.id, {
1845
+ queuedMessageCount: queue.length,
1846
+ progressSummary: `Queued follow-up message for ${task.agentName ?? task.subagentType}.`,
1847
+ });
1848
+ return {
1849
+ status: 'queued',
1850
+ taskId: task.id,
1851
+ agentId: task.sessionId,
1852
+ agentName: task.agentName,
1853
+ };
1854
+ }
1855
+ const sessionId = task?.sessionId ?? address;
1856
+ let session;
1857
+ try {
1858
+ session = await this.resumeSession(sessionId);
1859
+ }
1860
+ catch {
1861
+ throw new Error(`No addressable agent found for "${address}".`);
1862
+ }
1863
+ const agentName = typeof session.metadata.__actoviqAgentDefinition === 'string'
1864
+ ? session.metadata.__actoviqAgentDefinition
1865
+ : task?.subagentType;
1866
+ if (!agentName) {
1867
+ throw new Error(`Session "${sessionId}" is not an agent session.`);
1868
+ }
1869
+ const resumed = await this.launchBackgroundOnSession(session, agentName, message, {
1870
+ parentRunId: context.parentRunId,
1871
+ parentSessionId: context.parentSessionId,
1872
+ }, context.runOptions, {
1873
+ description: `Continue ${task?.agentName ?? agentName}`,
1874
+ name: task?.agentName,
1875
+ cwd: typeof session.metadata.__actoviqAgentWorkDir === 'string'
1876
+ ? session.metadata.__actoviqAgentWorkDir
1877
+ : undefined,
1878
+ }, task?.id);
1879
+ return {
1880
+ status: 'resumed',
1881
+ taskId: resumed.id,
1882
+ agentId: session.id,
1883
+ agentName: task?.agentName,
1884
+ };
1885
+ }
1886
+ async prepareDelegatedWorkspace(definition, delegation) {
1887
+ if (delegation.cwd) {
1888
+ return { workDir: path.resolve(delegation.cwd) };
1889
+ }
1890
+ if ((delegation.isolation ?? definition.isolation) !== 'worktree') {
1891
+ return { workDir: path.resolve(definition.cwd ?? this.config.workDir) };
1892
+ }
1893
+ const branch = `actoviq-agent-${createId().slice(0, 8)}`;
1894
+ const workspace = await createGitWorktreeWorkspace({
1895
+ repositoryPath: this.config.workDir,
1896
+ name: delegation.name
1897
+ ? `actoviq-${sanitizeWorkspaceName(delegation.name)}-${createId().slice(0, 6)}`
1898
+ : undefined,
1899
+ branch,
1467
1900
  metadata: {
1468
- __actoviqBackgroundParentRunId: options.parentRunId,
1469
- __actoviqBackgroundParentSessionId: options.parentSessionId,
1901
+ agent: definition.name,
1470
1902
  },
1471
1903
  });
1472
- if (runOptions.hooks) {
1473
- session.setHooks(runOptions.hooks);
1904
+ return { workDir: workspace.path, workspace };
1905
+ }
1906
+ prepareDelegatedRunOptions(runOptions, workDir, sessionId) {
1907
+ return {
1908
+ ...runOptions,
1909
+ workDir,
1910
+ tools: [
1911
+ ...createActoviqFileTools({ cwd: workDir }),
1912
+ ...(runOptions.tools ?? []),
1913
+ ],
1914
+ drainQueuedInputs: () => [
1915
+ ...(runOptions.drainQueuedInputs?.() ?? []),
1916
+ ...this.drainSubagentInputs(sessionId),
1917
+ ],
1918
+ };
1919
+ }
1920
+ async finalizeDelegatedWorkspace(workspace) {
1921
+ if (!workspace) {
1922
+ return false;
1923
+ }
1924
+ const dirty = await isGitWorkspaceDirty(workspace.path);
1925
+ if (!dirty) {
1926
+ await workspace.dispose();
1927
+ return false;
1928
+ }
1929
+ return true;
1930
+ }
1931
+ drainSubagentInputs(sessionId) {
1932
+ const queued = this.subagentInputQueues.get(sessionId) ?? [];
1933
+ this.subagentInputQueues.delete(sessionId);
1934
+ return queued;
1935
+ }
1936
+ async collectPendingTaskNotifications(parentSessionId) {
1937
+ this.pendingRuntimeNotifications.delete(parentSessionId);
1938
+ const notifications = [];
1939
+ for (const task of await this.backgroundTaskManager.list()) {
1940
+ if (task.parentSessionId !== parentSessionId ||
1941
+ task.notificationDeliveredAt ||
1942
+ (task.status !== 'completed' &&
1943
+ task.status !== 'failed' &&
1944
+ task.status !== 'cancelled')) {
1945
+ continue;
1946
+ }
1947
+ notifications.push(formatTaskNotification(task));
1948
+ await this.markTaskNotificationDelivered(task.id);
1474
1949
  }
1475
- if (runOptions.permissionMode ||
1476
- runOptions.permissions ||
1477
- runOptions.classifier ||
1478
- runOptions.approver) {
1479
- session.setPermissionContext({
1480
- mode: runOptions.permissionMode,
1481
- permissions: runOptions.permissions,
1482
- classifier: runOptions.classifier,
1483
- approver: runOptions.approver,
1484
- });
1950
+ return notifications;
1951
+ }
1952
+ enqueueTaskNotification(task) {
1953
+ const notificationKey = task.parentSessionId ?? task.parentRunId;
1954
+ if (!notificationKey || task.notificationDeliveredAt) {
1955
+ return;
1485
1956
  }
1486
- return this.backgroundTaskManager.launch({
1487
- subagentType: definition.name,
1488
- description: prompt,
1489
- workDir: this.config.workDir,
1490
- parentRunId: options.parentRunId,
1491
- parentSessionId: options.parentSessionId,
1492
- onRun: async (signal) => {
1493
- const result = await session.send(prompt, { signal });
1494
- return {
1495
- runId: result.runId,
1496
- sessionId: session.id,
1497
- model: result.model,
1498
- text: result.text,
1499
- toolCallCount: result.toolCalls.length,
1500
- toolErrorCount: result.toolCalls.filter(call => call.isError).length,
1501
- };
1502
- },
1957
+ const queue = this.pendingRuntimeNotifications.get(notificationKey) ?? [];
1958
+ if (!queue.some(entry => entry.taskId === task.id)) {
1959
+ queue.push({ taskId: task.id, text: formatTaskNotification(task) });
1960
+ this.pendingRuntimeNotifications.set(notificationKey, queue);
1961
+ }
1962
+ }
1963
+ drainRuntimeNotifications(sessionId) {
1964
+ const queued = this.pendingRuntimeNotifications.get(sessionId) ?? [];
1965
+ this.pendingRuntimeNotifications.delete(sessionId);
1966
+ for (const entry of queued) {
1967
+ void this.markTaskNotificationDelivered(entry.taskId);
1968
+ }
1969
+ return queued.map(entry => entry.text);
1970
+ }
1971
+ async markTaskNotificationDelivered(taskId) {
1972
+ const task = await this.backgroundTaskManager.get(taskId);
1973
+ if (!task || task.notificationDeliveredAt) {
1974
+ return;
1975
+ }
1976
+ await this.backgroundTaskStore.save({
1977
+ ...task,
1978
+ notificationDeliveredAt: nowIso(),
1979
+ updatedAt: nowIso(),
1503
1980
  });
1504
1981
  }
1505
- async launchBackgroundOnSession(session, agent, prompt, options, runOptions = {}) {
1982
+ async launchBackgroundAgentTask(agent, prompt, options, runOptions = {}, delegation = { description: prompt }) {
1983
+ const definition = this.requireAgentDefinition(agent);
1984
+ const prepared = await this.prepareDelegatedWorkspace(definition, delegation);
1985
+ try {
1986
+ const session = await this.createAgentSession(agent, {
1987
+ title: `${delegation.name ?? definition.name}: ${truncateText(delegation.description, 80)}`,
1988
+ metadata: {
1989
+ __actoviqBackgroundParentRunId: options.parentRunId,
1990
+ __actoviqBackgroundParentSessionId: options.parentSessionId,
1991
+ __actoviqAgentName: delegation.name,
1992
+ __actoviqAgentWorkDir: prepared.workDir,
1993
+ __actoviqAgentWorktreePath: prepared.workspace?.path,
1994
+ __actoviqAgentWorktreeBranch: prepared.workspace?.metadata.branch,
1995
+ },
1996
+ });
1997
+ if (runOptions.hooks) {
1998
+ session.setHooks(runOptions.hooks);
1999
+ }
2000
+ if (runOptions.permissionMode ||
2001
+ runOptions.permissions ||
2002
+ runOptions.classifier ||
2003
+ runOptions.approver) {
2004
+ await session.setPermissionContext({
2005
+ mode: runOptions.permissionMode,
2006
+ permissions: runOptions.permissions,
2007
+ classifier: runOptions.classifier,
2008
+ approver: runOptions.approver,
2009
+ });
2010
+ }
2011
+ return this.backgroundTaskManager.launch({
2012
+ subagentType: definition.name,
2013
+ description: delegation.description,
2014
+ workDir: prepared.workDir,
2015
+ parentRunId: options.parentRunId,
2016
+ parentSessionId: options.parentSessionId,
2017
+ sessionId: session.id,
2018
+ agentName: delegation.name,
2019
+ worktreePath: prepared.workspace?.path,
2020
+ worktreeBranch: prepared.workspace?.metadata.branch,
2021
+ onRun: (signal, updateProgress) => this.runBackgroundAgentSession({
2022
+ session,
2023
+ prompt,
2024
+ signal,
2025
+ updateProgress,
2026
+ runOptions: this.prepareDelegatedRunOptions(runOptions, prepared.workDir, session.id),
2027
+ workspace: prepared.workspace,
2028
+ }),
2029
+ onSettled: task => {
2030
+ this.updatePendingDelegation(options.parentSessionId ?? options.parentRunId, task);
2031
+ this.enqueueTaskNotification(task);
2032
+ },
2033
+ });
2034
+ }
2035
+ catch (error) {
2036
+ await this.finalizeDelegatedWorkspace(prepared.workspace);
2037
+ throw error;
2038
+ }
2039
+ }
2040
+ async launchBackgroundOnSession(session, agent, prompt, options, runOptions = {}, delegation = { description: prompt }, resumedFromTaskId) {
1506
2041
  const definition = this.requireAgentDefinition(agent);
1507
2042
  if (runOptions.hooks) {
1508
2043
  session.setHooks(runOptions.hooks);
@@ -1511,7 +2046,7 @@ export class ActoviqAgentClient {
1511
2046
  runOptions.permissions ||
1512
2047
  runOptions.classifier ||
1513
2048
  runOptions.approver) {
1514
- session.setPermissionContext({
2049
+ await session.setPermissionContext({
1515
2050
  mode: runOptions.permissionMode,
1516
2051
  permissions: runOptions.permissions,
1517
2052
  classifier: runOptions.classifier,
@@ -1520,33 +2055,82 @@ export class ActoviqAgentClient {
1520
2055
  }
1521
2056
  return this.backgroundTaskManager.launch({
1522
2057
  subagentType: definition.name,
1523
- description: prompt,
1524
- workDir: this.config.workDir,
2058
+ description: delegation.description,
2059
+ workDir: delegation.cwd ?? this.config.workDir,
1525
2060
  parentRunId: options.parentRunId,
1526
2061
  parentSessionId: options.parentSessionId ?? session.id,
1527
- onRun: async (signal) => {
1528
- const result = await session.send(prompt, { signal });
1529
- return {
1530
- runId: result.runId,
1531
- sessionId: session.id,
1532
- model: result.model,
1533
- text: result.text,
1534
- toolCallCount: result.toolCalls.length,
1535
- toolErrorCount: result.toolCalls.filter(call => call.isError).length,
1536
- };
2062
+ sessionId: session.id,
2063
+ agentName: delegation.name,
2064
+ resumedFromTaskId,
2065
+ onRun: (signal, updateProgress) => this.runBackgroundAgentSession({
2066
+ session,
2067
+ prompt,
2068
+ signal,
2069
+ updateProgress,
2070
+ runOptions: this.prepareDelegatedRunOptions(runOptions, delegation.cwd ?? this.config.workDir, session.id),
2071
+ }),
2072
+ onSettled: task => {
2073
+ this.updatePendingDelegation(options.parentSessionId ?? options.parentRunId, task);
2074
+ this.enqueueTaskNotification(task);
1537
2075
  },
1538
2076
  });
1539
2077
  }
2078
+ async runBackgroundAgentSession(args) {
2079
+ try {
2080
+ await args.updateProgress({
2081
+ progressSummary: 'Agent is running.',
2082
+ });
2083
+ const result = await args.session.send(args.prompt, {
2084
+ ...args.runOptions,
2085
+ signal: args.signal,
2086
+ });
2087
+ await args.updateProgress({
2088
+ partialText: result.text,
2089
+ requestCount: result.requests.length,
2090
+ toolCallCount: result.toolCalls.length,
2091
+ toolErrorCount: result.toolCalls.filter(call => call.isError).length,
2092
+ currentIteration: result.requests.at(-1)?.iteration,
2093
+ currentToolName: undefined,
2094
+ progressSummary: 'Agent completed.',
2095
+ });
2096
+ const retainedWorktree = await this.finalizeDelegatedWorkspace(args.workspace);
2097
+ return {
2098
+ runId: result.runId,
2099
+ sessionId: args.session.id,
2100
+ model: result.model,
2101
+ text: result.text,
2102
+ toolCallCount: result.toolCalls.length,
2103
+ toolErrorCount: result.toolCalls.filter(call => call.isError).length,
2104
+ requestCount: result.requests.length,
2105
+ retainedWorktree,
2106
+ worktreePath: retainedWorktree ? args.workspace?.path : undefined,
2107
+ worktreeBranch: retainedWorktree ? args.workspace?.metadata.branch : undefined,
2108
+ };
2109
+ }
2110
+ catch (error) {
2111
+ await args.updateProgress({
2112
+ progressSummary: args.signal.aborted
2113
+ ? 'Agent stopped before completion.'
2114
+ : 'Agent failed before completion.',
2115
+ });
2116
+ await this.finalizeDelegatedWorkspace(args.workspace);
2117
+ throw error;
2118
+ }
2119
+ finally {
2120
+ this.subagentInputQueues.delete(args.session.id);
2121
+ }
2122
+ }
1540
2123
  async compactSessionForSession(session, options = {}) {
1541
2124
  const snapshot = session.snapshot();
1542
2125
  const { session: compactedSession, result } = await compactActoviqSession(snapshot, {
1543
2126
  ...options,
2127
+ model: options.model ? this.resolveModel(options.model) : undefined,
1544
2128
  force: options.force ?? true,
1545
2129
  trigger: 'manual',
1546
2130
  }, {
1547
2131
  workDir: this.config.workDir,
1548
2132
  systemPrompt: snapshot.systemPrompt ?? this.config.systemPrompt,
1549
- model: snapshot.model ?? this.config.model,
2133
+ model: this.resolveModel(snapshot.model),
1550
2134
  modelApi: this.modelApi,
1551
2135
  compactConfig: this.config.compact,
1552
2136
  runtimeState: this.getSessionMemoryRuntimeState(snapshot),
@@ -1567,7 +2151,7 @@ export class ActoviqAgentClient {
1567
2151
  systemPrompt: await this.buildDreamSystemPrompt(),
1568
2152
  tools: createActoviqFileTools({ cwd: this.config.workDir }),
1569
2153
  mcpServers: [],
1570
- model: request.model ?? this.config.model,
2154
+ model: this.resolveModel(request.model),
1571
2155
  maxTokens: request.maxTokens ?? DEFAULT_DREAM_MAX_TOKENS,
1572
2156
  userId: this.config.userId,
1573
2157
  metadata: {
@@ -1781,7 +2365,7 @@ export class ActoviqAgentClient {
1781
2365
  const stored = session.snapshot();
1782
2366
  const extraction = await this.performSessionMemoryExtraction(stored, {
1783
2367
  force: options.force ?? true,
1784
- model: options.model ?? stored.model ?? this.config.model,
2368
+ model: this.resolveModel(options.model ?? stored.model),
1785
2369
  systemPrompt: stored.systemPrompt ?? this.config.systemPrompt,
1786
2370
  trigger: 'manual',
1787
2371
  maxTokens: options.maxTokens,
@@ -1803,6 +2387,9 @@ export class ActoviqAgentClient {
1803
2387
  ...(options.metadata ?? {}),
1804
2388
  ...(hookOutcome.sessionMetadata ?? {}),
1805
2389
  };
2390
+ if (result.loopCompactions?.length) {
2391
+ recordActoviqLoopCompactionsOnSession(next, result.loopCompactions);
2392
+ }
1806
2393
  const runtimeState = this.getSessionMemoryRuntimeState(next);
1807
2394
  if (runtimeState.pendingPostCompaction) {
1808
2395
  runtimeState.pendingPostCompaction = false;
@@ -1896,7 +2483,7 @@ export class ActoviqAgentClient {
1896
2483
  await appendMessagesToTranscript(paths.projectStateDir, session.id, this.config.workDir, newMessages);
1897
2484
  }
1898
2485
  const extraction = await this.performSessionMemoryExtraction(next, {
1899
- model: options.model ?? next.model ?? this.config.model,
2486
+ model: this.resolveModel(options.model ?? next.model),
1900
2487
  systemPrompt: next.systemPrompt ?? this.config.systemPrompt,
1901
2488
  trigger: 'auto',
1902
2489
  maxTokens: Math.min(options.maxTokens ?? this.config.maxTokens, DEFAULT_SESSION_MEMORY_MAX_TOKENS),
@@ -1906,7 +2493,7 @@ export class ActoviqAgentClient {
1906
2493
  const compacted = await compactActoviqSession(next, { trigger: 'auto' }, {
1907
2494
  workDir: this.config.workDir,
1908
2495
  systemPrompt: next.systemPrompt ?? this.config.systemPrompt,
1909
- model: options.model ?? next.model ?? this.config.model,
2496
+ model: this.resolveModel(options.model ?? next.model),
1910
2497
  modelApi: this.modelApi,
1911
2498
  compactConfig: this.config.compact,
1912
2499
  runtimeState: extraction.state,
@@ -1952,10 +2539,22 @@ export class ActoviqAgentClient {
1952
2539
  return this.mergeAgentRunOptions(this.requireAgentDefinition(agentName), options);
1953
2540
  }
1954
2541
  mergeAgentRunOptions(definition, options) {
2542
+ const availableMcpServers = new Set([
2543
+ ...this.defaultMcpServers.map(server => server.name),
2544
+ ...(definition.mcpServers ?? []).map(server => server.name),
2545
+ ...(options.mcpServers ?? []).map(server => server.name),
2546
+ ]);
2547
+ const missingMcpServers = (definition.requiredMcpServers ?? []).filter(server => !availableMcpServers.has(server));
2548
+ if (missingMcpServers.length > 0) {
2549
+ throw new Error(`Agent "${definition.name}" requires unavailable MCP servers: ${missingMcpServers.join(', ')}.`);
2550
+ }
2551
+ const nestedAgentDenylist = definition.allowNestedAgents === true ? [] : ['Agent', 'Task'];
1955
2552
  return {
1956
2553
  ...options,
1957
2554
  systemPrompt: joinPromptParts(definition.systemPrompt, options.systemPrompt),
1958
2555
  model: options.model ?? definition.model,
2556
+ effort: options.effort ?? definition.effort,
2557
+ permissionMode: options.permissionMode ?? definition.permissionMode,
1959
2558
  metadata: {
1960
2559
  ...(definition.metadata ?? {}),
1961
2560
  ...(options.metadata ?? {}),
@@ -1966,7 +2565,19 @@ export class ActoviqAgentClient {
1966
2565
  mcpServers: [...(definition.mcpServers ?? []), ...(options.mcpServers ?? [])],
1967
2566
  __actoviqUseDefaultTools: definition.inheritDefaultTools !== false,
1968
2567
  __actoviqUseDefaultMcpServers: definition.inheritDefaultMcpServers !== false,
1969
- __actoviqMaxToolIterations: definition.maxToolIterations,
2568
+ __actoviqMaxToolIterations: definition.maxToolIterations ?? definition.maxTurns,
2569
+ __actoviqAllowedTools: definition.allowedTools
2570
+ ? [...definition.allowedTools]
2571
+ : undefined,
2572
+ __actoviqDisallowedTools: [
2573
+ ...(definition.disallowedTools ?? []),
2574
+ ...nestedAgentDenylist,
2575
+ ],
2576
+ __actoviqPreloadedSkills: definition.skills
2577
+ ? [...definition.skills]
2578
+ : undefined,
2579
+ __actoviqWorkDir: options.workDir ?? definition.cwd,
2580
+ __actoviqInitialPrompt: definition.initialPrompt,
1970
2581
  };
1971
2582
  }
1972
2583
  mergeSkillRunOptions(definition, options) {
@@ -2008,13 +2619,23 @@ function serializeBackgroundTaskOutput(task) {
2008
2619
  `Task id: ${task.id}`,
2009
2620
  `Status: ${task.status}`,
2010
2621
  `Subagent: ${task.subagentType}`,
2622
+ task.agentName ? `Agent name: ${task.agentName}` : undefined,
2011
2623
  task.runId ? `Run id: ${task.runId}` : undefined,
2012
2624
  task.sessionId ? `Session id: ${task.sessionId}` : undefined,
2013
2625
  task.model ? `Model: ${task.model}` : undefined,
2014
2626
  typeof task.toolCallCount === 'number' ? `Tool calls: ${task.toolCallCount}` : undefined,
2015
2627
  typeof task.toolErrorCount === 'number' ? `Tool errors: ${task.toolErrorCount}` : undefined,
2628
+ typeof task.requestCount === 'number' ? `Requests: ${task.requestCount}` : undefined,
2629
+ task.currentToolName ? `Current tool: ${task.currentToolName}` : undefined,
2630
+ task.progressSummary ? `Progress: ${task.progressSummary}` : undefined,
2631
+ task.worktreePath ? `Worktree: ${task.worktreePath}` : undefined,
2632
+ task.worktreeBranch ? `Branch: ${task.worktreeBranch}` : undefined,
2016
2633
  task.error ? `Error:\n${task.error}` : undefined,
2017
- task.text ? `Output:\n${task.text}` : 'Output: <not available yet>',
2634
+ task.text
2635
+ ? `Output:\n${task.text}`
2636
+ : task.partialText
2637
+ ? `Partial output:\n${task.partialText}`
2638
+ : 'Output: <not available yet>',
2018
2639
  ].filter(Boolean).join('\n');
2019
2640
  }
2020
2641
  function joinPromptParts(...parts) {
@@ -2024,6 +2645,99 @@ function joinPromptParts(...parts) {
2024
2645
  }
2025
2646
  return normalized.join('\n\n');
2026
2647
  }
2648
+ function mergeAgentDefinitions(...groups) {
2649
+ const merged = new Map();
2650
+ for (const group of groups) {
2651
+ for (const definition of group) {
2652
+ merged.set(definition.name, cloneAgentDefinition(definition));
2653
+ }
2654
+ }
2655
+ return [...merged.values()];
2656
+ }
2657
+ function filterAgentTools(tools, allowedTools, disallowedTools) {
2658
+ const allowed = allowedTools?.length ? new Set(allowedTools) : undefined;
2659
+ const denied = new Set(disallowedTools ?? []);
2660
+ return tools.filter(toolDefinition => {
2661
+ const names = [toolDefinition.name, ...(toolDefinition.aliases ?? [])];
2662
+ if (names.some(name => denied.has(name))) {
2663
+ return false;
2664
+ }
2665
+ return !allowed || names.some(name => allowed.has(name));
2666
+ });
2667
+ }
2668
+ function formatTaskNotification(task) {
2669
+ const result = task.status === 'completed'
2670
+ ? task.text ?? task.partialText ?? ''
2671
+ : task.partialText ?? '';
2672
+ return [
2673
+ '<task_notification>',
2674
+ `<task_id>${escapeXml(task.id)}</task_id>`,
2675
+ task.sessionId ? `<agent_id>${escapeXml(task.sessionId)}</agent_id>` : undefined,
2676
+ task.agentName ? `<agent_name>${escapeXml(task.agentName)}</agent_name>` : undefined,
2677
+ `<status>${task.status}</status>`,
2678
+ `<summary>${escapeXml(task.status === 'completed'
2679
+ ? `Agent "${task.agentName ?? task.subagentType}" completed.`
2680
+ : `Agent "${task.agentName ?? task.subagentType}" ${task.status}.`)}</summary>`,
2681
+ result ? `<result>${escapeXml(result)}</result>` : undefined,
2682
+ task.error ? `<error>${escapeXml(task.error)}</error>` : undefined,
2683
+ `<usage><requests>${task.requestCount ?? 0}</requests><tool_uses>${task.toolCallCount ?? 0}</tool_uses><tool_errors>${task.toolErrorCount ?? 0}</tool_errors></usage>`,
2684
+ task.retainedWorktree && task.worktreePath
2685
+ ? `<worktree><path>${escapeXml(task.worktreePath)}</path>${task.worktreeBranch ? `<branch>${escapeXml(task.worktreeBranch)}</branch>` : ''}</worktree>`
2686
+ : undefined,
2687
+ '</task_notification>',
2688
+ ]
2689
+ .filter((line) => Boolean(line))
2690
+ .join('\n');
2691
+ }
2692
+ function escapeXml(value) {
2693
+ return value
2694
+ .replace(/&/gu, '&amp;')
2695
+ .replace(/</gu, '&lt;')
2696
+ .replace(/>/gu, '&gt;');
2697
+ }
2698
+ function sanitizeWorkspaceName(value) {
2699
+ return value
2700
+ .trim()
2701
+ .replace(/[^A-Za-z0-9_-]+/gu, '-')
2702
+ .replace(/^-+|-+$/gu, '')
2703
+ .slice(0, 40) || 'agent';
2704
+ }
2705
+ async function isGitWorkspaceDirty(workDir) {
2706
+ try {
2707
+ const controller = new AbortController();
2708
+ const timeout = setTimeout(() => controller.abort(), 10_000);
2709
+ try {
2710
+ const { stdout } = await new Promise((resolve, reject) => {
2711
+ const child = execFileCallback('git', ['-C', workDir, 'status', '--porcelain'], {
2712
+ windowsHide: true,
2713
+ signal: controller.signal,
2714
+ });
2715
+ child.on('error', reject);
2716
+ let stdout = '';
2717
+ let stderr = '';
2718
+ child.stdout?.on('data', (data) => { stdout += data.toString(); });
2719
+ child.stderr?.on('data', (data) => { stderr += data.toString(); });
2720
+ child.on('close', (code) => {
2721
+ if (code === 0) {
2722
+ resolve({ stdout, stderr });
2723
+ }
2724
+ else {
2725
+ reject(new Error(`git status exited with code ${code}: ${stderr}`));
2726
+ }
2727
+ });
2728
+ });
2729
+ return stdout.trim().length > 0;
2730
+ }
2731
+ finally {
2732
+ clearTimeout(timeout);
2733
+ }
2734
+ }
2735
+ catch {
2736
+ // If git is unavailable, timed out, or the repo is broken,
2737
+ // conservatively treat as not dirty to avoid leaking worktrees.
2738
+ return false;
2739
+ }
2740
+ }
2027
2741
  function mergeUniqueByName(defaults, overrides) {
2028
2742
  const merged = new Map();
2029
2743
  for (const item of defaults) {