actoviq-agent-sdk 0.3.0 → 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 (204) 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 +93 -13
  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 +18 -0
  14. package/dist/src/config/anthropicEnvMapping.d.ts.map +1 -0
  15. package/dist/src/config/anthropicEnvMapping.js +49 -0
  16. package/dist/src/config/anthropicEnvMapping.js.map +1 -0
  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 +73 -21
  27. package/dist/src/config/resolveRuntimeConfig.js.map +1 -1
  28. package/dist/src/index.d.ts +7 -0
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +7 -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/memory/actoviqMemory.d.ts.map +1 -1
  36. package/dist/src/memory/actoviqMemory.js +10 -6
  37. package/dist/src/memory/actoviqMemory.js.map +1 -1
  38. package/dist/src/memory/actoviqTranscriptLogger.d.ts.map +1 -1
  39. package/dist/src/memory/actoviqTranscriptLogger.js +2 -2
  40. package/dist/src/memory/actoviqTranscriptLogger.js.map +1 -1
  41. package/dist/src/parity/actoviqBridgeEvents.d.ts.map +1 -1
  42. package/dist/src/parity/actoviqBridgeEvents.js +4 -2
  43. package/dist/src/parity/actoviqBridgeEvents.js.map +1 -1
  44. package/dist/src/parity/actoviqBridgeSdk.d.ts.map +1 -1
  45. package/dist/src/parity/actoviqBridgeSdk.js +9 -1
  46. package/dist/src/parity/actoviqBridgeSdk.js.map +1 -1
  47. package/dist/src/parity/actoviqCleanBridgeCompatSdk.d.ts +277 -0
  48. package/dist/src/parity/actoviqCleanBridgeCompatSdk.d.ts.map +1 -0
  49. package/dist/src/parity/actoviqCleanBridgeCompatSdk.js +1395 -0
  50. package/dist/src/parity/actoviqCleanBridgeCompatSdk.js.map +1 -0
  51. package/dist/src/parity/portableSessions.d.ts.map +1 -1
  52. package/dist/src/parity/portableSessions.js +6 -2
  53. package/dist/src/parity/portableSessions.js.map +1 -1
  54. package/dist/src/provider/client.d.ts +5 -0
  55. package/dist/src/provider/client.d.ts.map +1 -1
  56. package/dist/src/provider/client.js +82 -8
  57. package/dist/src/provider/client.js.map +1 -1
  58. package/dist/src/provider/openai-client.d.ts.map +1 -1
  59. package/dist/src/provider/openai-client.js +43 -4
  60. package/dist/src/provider/openai-client.js.map +1 -1
  61. package/dist/src/provider/openai-model-api.d.ts.map +1 -1
  62. package/dist/src/provider/openai-model-api.js +3 -0
  63. package/dist/src/provider/openai-model-api.js.map +1 -1
  64. package/dist/src/provider/openai-types.d.ts +1 -0
  65. package/dist/src/provider/openai-types.d.ts.map +1 -1
  66. package/dist/src/runtime/actoviqAgentDefinitions.d.ts +8 -0
  67. package/dist/src/runtime/actoviqAgentDefinitions.d.ts.map +1 -0
  68. package/dist/src/runtime/actoviqAgentDefinitions.js +165 -0
  69. package/dist/src/runtime/actoviqAgentDefinitions.js.map +1 -0
  70. package/dist/src/runtime/actoviqAgents.d.ts +26 -2
  71. package/dist/src/runtime/actoviqAgents.d.ts.map +1 -1
  72. package/dist/src/runtime/actoviqAgents.js +237 -26
  73. package/dist/src/runtime/actoviqAgents.js.map +1 -1
  74. package/dist/src/runtime/actoviqApiMicrocompact.d.ts +14 -0
  75. package/dist/src/runtime/actoviqApiMicrocompact.d.ts.map +1 -1
  76. package/dist/src/runtime/actoviqApiMicrocompact.js +102 -0
  77. package/dist/src/runtime/actoviqApiMicrocompact.js.map +1 -1
  78. package/dist/src/runtime/actoviqBackgroundTasks.d.ts +15 -1
  79. package/dist/src/runtime/actoviqBackgroundTasks.d.ts.map +1 -1
  80. package/dist/src/runtime/actoviqBackgroundTasks.js +72 -10
  81. package/dist/src/runtime/actoviqBackgroundTasks.js.map +1 -1
  82. package/dist/src/runtime/actoviqCompact.d.ts +59 -0
  83. package/dist/src/runtime/actoviqCompact.d.ts.map +1 -1
  84. package/dist/src/runtime/actoviqCompact.js +344 -15
  85. package/dist/src/runtime/actoviqCompact.js.map +1 -1
  86. package/dist/src/runtime/actoviqModelApi.d.ts.map +1 -1
  87. package/dist/src/runtime/actoviqModelApi.js +20 -4
  88. package/dist/src/runtime/actoviqModelApi.js.map +1 -1
  89. package/dist/src/runtime/actoviqPermissions.d.ts +5 -2
  90. package/dist/src/runtime/actoviqPermissions.d.ts.map +1 -1
  91. package/dist/src/runtime/actoviqPermissions.js +91 -202
  92. package/dist/src/runtime/actoviqPermissions.js.map +1 -1
  93. package/dist/src/runtime/actoviqSessionPermissions.d.ts +5 -0
  94. package/dist/src/runtime/actoviqSessionPermissions.d.ts.map +1 -0
  95. package/dist/src/runtime/actoviqSessionPermissions.js +44 -0
  96. package/dist/src/runtime/actoviqSessionPermissions.js.map +1 -0
  97. package/dist/src/runtime/agentClient.d.ts +37 -3
  98. package/dist/src/runtime/agentClient.d.ts.map +1 -1
  99. package/dist/src/runtime/agentClient.js +1112 -100
  100. package/dist/src/runtime/agentClient.js.map +1 -1
  101. package/dist/src/runtime/agentSession.d.ts +8 -5
  102. package/dist/src/runtime/agentSession.d.ts.map +1 -1
  103. package/dist/src/runtime/agentSession.js +11 -4
  104. package/dist/src/runtime/agentSession.js.map +1 -1
  105. package/dist/src/runtime/conversationEngine.d.ts +2 -0
  106. package/dist/src/runtime/conversationEngine.d.ts.map +1 -1
  107. package/dist/src/runtime/conversationEngine.js +575 -36
  108. package/dist/src/runtime/conversationEngine.js.map +1 -1
  109. package/dist/src/runtime/defaultActoviqAgents.d.ts +3 -0
  110. package/dist/src/runtime/defaultActoviqAgents.d.ts.map +1 -0
  111. package/dist/src/runtime/defaultActoviqAgents.js +85 -0
  112. package/dist/src/runtime/defaultActoviqAgents.js.map +1 -0
  113. package/dist/src/runtime/safetyChecks.d.ts.map +1 -1
  114. package/dist/src/runtime/safetyChecks.js +15 -9
  115. package/dist/src/runtime/safetyChecks.js.map +1 -1
  116. package/dist/src/runtime/sessionManager.d.ts.map +1 -1
  117. package/dist/src/runtime/sessionManager.js +10 -5
  118. package/dist/src/runtime/sessionManager.js.map +1 -1
  119. package/dist/src/runtime/tools.d.ts.map +1 -1
  120. package/dist/src/runtime/tools.js +1 -0
  121. package/dist/src/runtime/tools.js.map +1 -1
  122. package/dist/src/scheduling/cron.d.ts.map +1 -1
  123. package/dist/src/scheduling/cron.js +3 -1
  124. package/dist/src/scheduling/cron.js.map +1 -1
  125. package/dist/src/storage/backgroundTaskStore.d.ts.map +1 -1
  126. package/dist/src/storage/backgroundTaskStore.js +3 -2
  127. package/dist/src/storage/backgroundTaskStore.js.map +1 -1
  128. package/dist/src/storage/mailboxStore.d.ts.map +1 -1
  129. package/dist/src/storage/mailboxStore.js +3 -3
  130. package/dist/src/storage/mailboxStore.js.map +1 -1
  131. package/dist/src/storage/pathSafety.d.ts +4 -0
  132. package/dist/src/storage/pathSafety.d.ts.map +1 -0
  133. package/dist/src/storage/pathSafety.js +28 -0
  134. package/dist/src/storage/pathSafety.js.map +1 -0
  135. package/dist/src/storage/sessionStore.d.ts +2 -2
  136. package/dist/src/storage/sessionStore.d.ts.map +1 -1
  137. package/dist/src/storage/sessionStore.js +23 -8
  138. package/dist/src/storage/sessionStore.js.map +1 -1
  139. package/dist/src/storage/teammateStore.d.ts.map +1 -1
  140. package/dist/src/storage/teammateStore.js +3 -2
  141. package/dist/src/storage/teammateStore.js.map +1 -1
  142. package/dist/src/swarm/actoviqSwarm.d.ts.map +1 -1
  143. package/dist/src/swarm/actoviqSwarm.js +6 -6
  144. package/dist/src/swarm/actoviqSwarm.js.map +1 -1
  145. package/dist/src/tools/actoviqCoreTools.d.ts +7 -2
  146. package/dist/src/tools/actoviqCoreTools.d.ts.map +1 -1
  147. package/dist/src/tools/actoviqCoreTools.js +2 -2
  148. package/dist/src/tools/actoviqCoreTools.js.map +1 -1
  149. package/dist/src/tools/actoviqFileTools.d.ts.map +1 -1
  150. package/dist/src/tools/actoviqFileTools.js +16 -5
  151. package/dist/src/tools/actoviqFileTools.js.map +1 -1
  152. package/dist/src/tools/actoviqNotebookEdit.d.ts.map +1 -1
  153. package/dist/src/tools/actoviqNotebookEdit.js +121 -4
  154. package/dist/src/tools/actoviqNotebookEdit.js.map +1 -1
  155. package/dist/src/tools/actoviqShellTools.d.ts.map +1 -1
  156. package/dist/src/tools/actoviqShellTools.js +38 -9
  157. package/dist/src/tools/actoviqShellTools.js.map +1 -1
  158. package/dist/src/tools/actoviqWebTools.d.ts.map +1 -1
  159. package/dist/src/tools/actoviqWebTools.js +50 -18
  160. package/dist/src/tools/actoviqWebTools.js.map +1 -1
  161. package/dist/src/tools/bash/BashTool.d.ts.map +1 -1
  162. package/dist/src/tools/bash/BashTool.js +47 -14
  163. package/dist/src/tools/bash/BashTool.js.map +1 -1
  164. package/dist/src/tools/todo/TodoWriteTool.d.ts +14 -0
  165. package/dist/src/tools/todo/TodoWriteTool.d.ts.map +1 -1
  166. package/dist/src/tools/todo/TodoWriteTool.js +45 -2
  167. package/dist/src/tools/todo/TodoWriteTool.js.map +1 -1
  168. package/dist/src/tui/actoviqTui.d.ts +13 -0
  169. package/dist/src/tui/actoviqTui.d.ts.map +1 -0
  170. package/dist/src/tui/actoviqTui.js +1594 -0
  171. package/dist/src/tui/actoviqTui.js.map +1 -0
  172. package/dist/src/tui/ansi.d.ts +33 -0
  173. package/dist/src/tui/ansi.d.ts.map +1 -0
  174. package/dist/src/tui/ansi.js +127 -0
  175. package/dist/src/tui/ansi.js.map +1 -0
  176. package/dist/src/tui/editor.d.ts +56 -0
  177. package/dist/src/tui/editor.d.ts.map +1 -0
  178. package/dist/src/tui/editor.js +253 -0
  179. package/dist/src/tui/editor.js.map +1 -0
  180. package/dist/src/tui/pluginCatalog.d.ts +13 -0
  181. package/dist/src/tui/pluginCatalog.d.ts.map +1 -0
  182. package/dist/src/tui/pluginCatalog.js +78 -0
  183. package/dist/src/tui/pluginCatalog.js.map +1 -0
  184. package/dist/src/tui/screen.d.ts +28 -0
  185. package/dist/src/tui/screen.d.ts.map +1 -0
  186. package/dist/src/tui/screen.js +119 -0
  187. package/dist/src/tui/screen.js.map +1 -0
  188. package/dist/src/tui/selection.d.ts +9 -0
  189. package/dist/src/tui/selection.d.ts.map +1 -0
  190. package/dist/src/tui/selection.js +16 -0
  191. package/dist/src/tui/selection.js.map +1 -0
  192. package/dist/src/tui/transcript.d.ts +41 -0
  193. package/dist/src/tui/transcript.d.ts.map +1 -0
  194. package/dist/src/tui/transcript.js +147 -0
  195. package/dist/src/tui/transcript.js.map +1 -0
  196. package/dist/src/types.d.ts +233 -2
  197. package/dist/src/types.d.ts.map +1 -1
  198. package/dist/src/types.js.map +1 -1
  199. package/dist/src/workflow/workflowEngine.js +26 -3
  200. package/dist/src/workflow/workflowEngine.js.map +1 -1
  201. package/dist/src/workspace/actoviqWorkspace.d.ts.map +1 -1
  202. package/dist/src/workspace/actoviqWorkspace.js +25 -2
  203. package/dist/src/workspace/actoviqWorkspace.js.map +1 -1
  204. package/package.json +26 -1
@@ -1,7 +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';
5
+ import { z } from 'zod';
2
6
  import { createActoviqBuddyApi } from '../buddy/actoviqBuddy.js';
3
7
  import { createActoviqComputerUseMcpServer, createActoviqComputerUseTools, } from '../computer/actoviqComputerUse.js';
4
8
  import { resolveRuntimeConfig } from '../config/resolveRuntimeConfig.js';
9
+ import { resolveActoviqModelReference } from '../config/modelTiers.js';
5
10
  import { mergeActoviqHooks, normalizeActoviqHookMessages, resolveActoviqPostRunHooks, resolveActoviqSessionStartHooks, } from '../hooks/actoviqHooks.js';
6
11
  import { createActoviqMemoryApi } from '../memory/actoviqMemory.js';
7
12
  import { appendMessagesToTranscript } from '../memory/actoviqTranscriptLogger.js';
@@ -14,11 +19,16 @@ import { SessionStore } from '../storage/sessionStore.js';
14
19
  import { TeammateStore } from '../storage/teammateStore.js';
15
20
  import { ActoviqSwarmApi } from '../swarm/actoviqSwarm.js';
16
21
  import { createActoviqFileTools } from '../tools/actoviqFileTools.js';
22
+ import { createGitWorktreeWorkspace, } from '../workspace/actoviqWorkspace.js';
17
23
  import { ActoviqAgentsApi, createActoviqTaskTool, summarizeActoviqAgentDefinition, } from './actoviqAgents.js';
24
+ import { getDefaultActoviqAgents } from './defaultActoviqAgents.js';
25
+ import { loadActoviqAgentDefinitions } from './actoviqAgentDefinitions.js';
18
26
  import { ActoviqSkillsApi, loadActoviqSkillDefinitions, resolveActoviqSkillPrompt, summarizeActoviqSkillDefinition, } from './actoviqSkills.js';
19
27
  import { ActoviqBackgroundTaskManager, ActoviqBackgroundTasksApi, } from './actoviqBackgroundTasks.js';
20
28
  import { ActoviqContextApi, ActoviqSlashCommandsApi, } from './actoviqSlashCommands.js';
21
- import { compactActoviqSession, getPersistedActoviqCompactHistory, getPersistedActoviqCompactState, isActoviqPromptTooLongError, } 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';
31
+ const RECENT_FILE_TOOL_NAMES = new Set(['Read', 'Write', 'Edit', 'NotebookEdit']);
22
32
  import { ActoviqToolsApi, buildActoviqCleanToolCatalog, resolveActoviqCleanToolMetadata, } from './actoviqToolCatalog.js';
23
33
  import { WorkflowApi } from '../workflow/workflowBuilder.js';
24
34
  import { SessionManager } from './sessionManager.js';
@@ -29,6 +39,7 @@ import { createOpenaiModelApi } from '../provider/openai-model-api.js';
29
39
  import { AgentRunStream } from './asyncQueue.js';
30
40
  import { executeConversation } from './conversationEngine.js';
31
41
  import { asError, createId, deepClone, isRecord, nowIso, truncateText } from './helpers.js';
42
+ import { tool } from './tools.js';
32
43
  import { buildInvokedSkillMessages, buildRelevantMemoryMessages, extractTextFromContent, } from './messageUtils.js';
33
44
  import { AgentSession } from './agentSession.js';
34
45
  const RELEVANT_MEMORY_SESSION_STATE_KEY = '__actoviqRelevantMemoryState';
@@ -38,6 +49,7 @@ const RELEVANT_MEMORY_MAX_SESSION_BYTES = 60 * 1024;
38
49
  const DEFAULT_SESSION_MEMORY_MAX_TOKENS = 4_096;
39
50
  const DEFAULT_DREAM_MAX_TOKENS = 4_096;
40
51
  const MAX_REACTIVE_COMPACT_ATTEMPTS = 3;
52
+ const execFile = promisify(execFileCallback);
41
53
  const SESSION_MEMORY_SYSTEM_PROMPT = `You maintain the persistent session-memory markdown file for an ongoing engineering conversation.
42
54
 
43
55
  Return only the full updated markdown document.
@@ -72,6 +84,13 @@ function cloneAgentDefinition(definition) {
72
84
  hooks: cloneHooks(definition.hooks),
73
85
  tools: definition.tools ? [...definition.tools] : undefined,
74
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,
75
94
  };
76
95
  }
77
96
  function cloneSkillDefinition(definition) {
@@ -101,6 +120,17 @@ export class AgentSessionsApi {
101
120
  get(sessionId) {
102
121
  return this.resumeSession(sessionId);
103
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
+ }
104
134
  delete(sessionId) {
105
135
  return this.store.delete(sessionId);
106
136
  }
@@ -172,12 +202,41 @@ function getAgentContinuityState(metadata) {
172
202
  count: typeof record.count === 'number' ? record.count : 1,
173
203
  lastInvokedAt: record.lastInvokedAt,
174
204
  lastDescription: typeof record.lastDescription === 'string' ? record.lastDescription : undefined,
205
+ lastRunId: typeof record.lastRunId === 'string' ? record.lastRunId : undefined,
206
+ lastSessionId: typeof record.lastSessionId === 'string' ? record.lastSessionId : undefined,
207
+ lastStatus: record.lastStatus === 'completed' ||
208
+ record.lastStatus === 'async_launched' ||
209
+ record.lastStatus === 'failed' ||
210
+ record.lastStatus === 'cancelled'
211
+ ? record.lastStatus
212
+ : undefined,
213
+ lastTaskId: typeof record.lastTaskId === 'string' ? record.lastTaskId : undefined,
214
+ lastTextSummary: typeof record.lastTextSummary === 'string' ? record.lastTextSummary : undefined,
215
+ runIds: readStringArray(record.runIds),
216
+ sessionIds: readStringArray(record.sessionIds),
217
+ taskIds: readStringArray(record.taskIds),
218
+ totalRequestCount: typeof record.totalRequestCount === 'number'
219
+ ? record.totalRequestCount
220
+ : undefined,
221
+ totalToolCallCount: typeof record.totalToolCallCount === 'number'
222
+ ? record.totalToolCallCount
223
+ : undefined,
224
+ totalToolErrorCount: typeof record.totalToolErrorCount === 'number'
225
+ ? record.totalToolErrorCount
226
+ : undefined,
175
227
  },
176
228
  ];
177
229
  })
178
230
  : [],
179
231
  };
180
232
  }
233
+ function readStringArray(value) {
234
+ if (!Array.isArray(value)) {
235
+ return undefined;
236
+ }
237
+ const values = value.filter((entry) => typeof entry === 'string');
238
+ return values.length > 0 ? values : undefined;
239
+ }
181
240
  function mergeDelegatedAgents(existing, pending) {
182
241
  const merged = new Map(existing.map(record => [record.name, { ...record }]));
183
242
  for (const record of pending) {
@@ -188,15 +247,53 @@ function mergeDelegatedAgents(existing, pending) {
188
247
  count: 1,
189
248
  lastInvokedAt: record.invokedAt,
190
249
  lastDescription: record.description,
250
+ lastRunId: record.runId,
251
+ lastSessionId: record.sessionId,
252
+ lastStatus: record.status,
253
+ lastTaskId: record.taskId,
254
+ lastTextSummary: record.textSummary,
255
+ runIds: record.runId ? [record.runId] : undefined,
256
+ sessionIds: record.sessionId ? [record.sessionId] : undefined,
257
+ taskIds: record.taskId ? [record.taskId] : undefined,
258
+ totalRequestCount: record.requestCount,
259
+ totalToolCallCount: record.toolCallCount,
260
+ totalToolErrorCount: record.toolErrorCount,
191
261
  });
192
262
  continue;
193
263
  }
194
264
  current.count += 1;
195
265
  current.lastInvokedAt = record.invokedAt;
196
266
  current.lastDescription = record.description ?? current.lastDescription;
267
+ current.lastRunId = record.runId ?? current.lastRunId;
268
+ current.lastSessionId = record.sessionId ?? current.lastSessionId;
269
+ current.lastStatus = record.status ?? current.lastStatus;
270
+ current.lastTaskId = record.taskId ?? current.lastTaskId;
271
+ current.lastTextSummary = record.textSummary ?? current.lastTextSummary;
272
+ current.runIds = appendUnique(current.runIds, record.runId);
273
+ current.sessionIds = appendUnique(current.sessionIds, record.sessionId);
274
+ current.taskIds = appendUnique(current.taskIds, record.taskId);
275
+ current.totalRequestCount = sumOptional(current.totalRequestCount, record.requestCount);
276
+ current.totalToolCallCount = sumOptional(current.totalToolCallCount, record.toolCallCount);
277
+ current.totalToolErrorCount = sumOptional(current.totalToolErrorCount, record.toolErrorCount);
197
278
  }
198
279
  return [...merged.values()].sort((left, right) => right.lastInvokedAt.localeCompare(left.lastInvokedAt));
199
280
  }
281
+ function appendUnique(existing, value) {
282
+ if (!value) {
283
+ return existing;
284
+ }
285
+ const next = existing ? [...existing] : [];
286
+ if (!next.includes(value)) {
287
+ next.push(value);
288
+ }
289
+ return next;
290
+ }
291
+ function sumOptional(current, next) {
292
+ if (next === undefined) {
293
+ return current;
294
+ }
295
+ return (current ?? 0) + next;
296
+ }
200
297
  function getInvokedSkillState(metadata) {
201
298
  const raw = metadata?.[INVOKED_SKILLS_STATE_KEY];
202
299
  if (!Array.isArray(raw)) {
@@ -257,6 +354,8 @@ export class ActoviqAgentClient {
257
354
  defaultTools;
258
355
  defaultMcpServers;
259
356
  hooks;
357
+ maxSubagentDepth;
358
+ maxSubagentFanout;
260
359
  sessions;
261
360
  agents;
262
361
  skills;
@@ -273,13 +372,15 @@ export class ActoviqAgentClient {
273
372
  agentDefinitions;
274
373
  skillDefinitions;
275
374
  pendingDelegations = new Map();
375
+ pendingRuntimeNotifications = new Map();
376
+ subagentInputQueues = new Map();
276
377
  sessionRuntimeOverrides = new Map();
277
378
  backgroundTaskManager;
278
379
  defaultPermissionMode;
279
380
  defaultPermissions;
280
381
  defaultClassifier;
281
382
  defaultApprover;
282
- 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) {
283
384
  this.config = config;
284
385
  this.store = store;
285
386
  this.backgroundTaskStore = backgroundTaskStore;
@@ -290,8 +391,10 @@ export class ActoviqAgentClient {
290
391
  this.defaultTools = defaultTools;
291
392
  this.defaultMcpServers = defaultMcpServers;
292
393
  this.hooks = hooks;
394
+ this.maxSubagentDepth = maxSubagentDepth;
395
+ this.maxSubagentFanout = maxSubagentFanout;
293
396
  this.sessionManager = new SessionManager(this.store, sessionManagerConfig);
294
- 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);
295
398
  this.agentDefinitions = new Map(agentDefinitions.map(definition => [definition.name, cloneAgentDefinition(definition)]));
296
399
  this.skillDefinitions = new Map(skillDefinitions.map(definition => [definition.name, cloneSkillDefinition(definition)]));
297
400
  this.backgroundTaskManager = new ActoviqBackgroundTaskManager(this.backgroundTaskStore);
@@ -356,9 +459,23 @@ export class ActoviqAgentClient {
356
459
  resumeSession: (sessionId) => this.resumeSession(sessionId),
357
460
  getBackgroundTask: (taskId) => this.backgroundTaskManager.get(taskId),
358
461
  }, this.teammateStore, this.mailboxStore);
359
- if (!this.defaultTools.some(tool => tool.name === 'Task')) {
462
+ const existingDelegationTool = this.defaultTools.find(tool => tool.name === 'Agent' || tool.name === 'Task');
463
+ if (!existingDelegationTool) {
360
464
  this.defaultTools.unshift(this.createTaskTool());
361
465
  }
466
+ else if (existingDelegationTool.name === 'Task') {
467
+ existingDelegationTool.aliases = [
468
+ ...new Set([...(existingDelegationTool.aliases ?? []), 'Agent']),
469
+ ];
470
+ }
471
+ this.replaceDefaultTool(this.createSendMessageTool());
472
+ this.replaceDefaultTool(this.createBackgroundTaskListTool());
473
+ this.replaceDefaultTool(this.createBackgroundTaskGetTool());
474
+ this.replaceDefaultTool(this.createBackgroundTaskStopTool());
475
+ this.replaceDefaultTool(this.createBackgroundTaskOutputTool());
476
+ if (this.listSkillDefinitions().length > 0) {
477
+ this.replaceDefaultTool(this.createSkillRegistryTool());
478
+ }
362
479
  }
363
480
  async listToolMetadata(options) {
364
481
  return resolveActoviqCleanToolMetadata({
@@ -373,7 +490,7 @@ export class ActoviqAgentClient {
373
490
  }
374
491
  /** Resolve a tool definition by name from the default tool registry. */
375
492
  getTool(name) {
376
- return this.defaultTools.find(t => t.name === name);
493
+ return this.defaultTools.find(t => t.name === name || t.aliases?.includes(name));
377
494
  }
378
495
  async getToolCatalog(options) {
379
496
  return buildActoviqCleanToolCatalog(await this.listToolMetadata(options));
@@ -419,6 +536,13 @@ export class ActoviqAgentClient {
419
536
  disableDefaultSkills: options.disableDefaultSkills,
420
537
  loadDefaultSkillDirectories: options.loadDefaultSkillDirectories,
421
538
  });
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 ?? []);
422
546
  const defaultTools = [...(options.tools ?? [])];
423
547
  const defaultMcpServers = [...(options.mcpServers ?? [])];
424
548
  if (options.computerUse) {
@@ -430,7 +554,9 @@ export class ActoviqAgentClient {
430
554
  defaultTools.push(...createActoviqComputerUseTools(computerUseOptions));
431
555
  }
432
556
  }
433
- return new ActoviqAgentClient(config, store, backgroundTaskStore, mailboxStore, teammateStore, modelApi, mcpManager, defaultTools, defaultMcpServers, options.hooks, options.agents ?? [], [...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;
434
560
  }
435
561
  async run(input, options = {}) {
436
562
  const runId = createId();
@@ -491,23 +617,74 @@ export class ActoviqAgentClient {
491
617
  return race(tasks, options);
492
618
  }
493
619
  async createSession(options = {}) {
620
+ const model = this.resolveModel(options.model);
494
621
  const stored = await this.store.create({
622
+ id: options.id,
495
623
  title: options.title,
496
624
  systemPrompt: options.systemPrompt ?? this.config.systemPrompt,
497
- model: options.model ?? this.config.model,
625
+ model,
498
626
  tags: options.tags,
499
627
  metadata: {
500
- __actoviqWorkDir: this.config.workDir,
501
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
+ : {}),
502
638
  },
503
639
  initialMessages: options.initialMessages,
504
640
  });
505
641
  return this.hydrateSession(stored);
506
642
  }
507
- async resumeSession(sessionId) {
508
- 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);
509
681
  return this.hydrateSession(stored);
510
682
  }
683
+ resolveModel(model) {
684
+ return model
685
+ ? resolveActoviqModelReference(model, this.config.modelTiers).model
686
+ : this.config.model;
687
+ }
511
688
  async compactSessionById(sessionId, options = {}) {
512
689
  const session = await this.resumeSession(sessionId);
513
690
  return this.compactSessionForSession(session, options);
@@ -529,7 +706,28 @@ export class ActoviqAgentClient {
529
706
  return this.dream.maybeAutoDream(options);
530
707
  }
531
708
  async close() {
532
- 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
+ }
533
731
  }
534
732
  listAgentDefinitions() {
535
733
  return [...this.agentDefinitions.values()].map(summarizeActoviqAgentDefinition);
@@ -553,6 +751,8 @@ export class ActoviqAgentClient {
553
751
  ...(definition.metadata ?? {}),
554
752
  ...(options.metadata ?? {}),
555
753
  __actoviqAgentDefinition: definition.name,
754
+ __actoviqAgentMemory: definition.memory,
755
+ __actoviqAgentSource: definition.source,
556
756
  [AGENT_CONTINUITY_STATE_KEY]: {
557
757
  currentAgent: definition.name,
558
758
  delegatedAgents: [],
@@ -588,16 +788,223 @@ export class ActoviqAgentClient {
588
788
  createTaskTool(options = {}) {
589
789
  return createActoviqTaskTool({
590
790
  ...options,
791
+ listAgentDefinitions: () => this.listAgentDefinitions(),
591
792
  getAgentDefinition: (agent) => this.getAgentDefinition(agent),
592
- runAgent: (agent, prompt, runOptions) => this.runWithAgent(agent, prompt, runOptions),
593
- onDelegated: ({ subagentType, description, parentSessionId, parentRunId }) => {
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, }) => {
594
797
  this.recordPendingDelegation(parentSessionId ?? parentRunId, {
595
798
  name: subagentType,
596
799
  description,
597
800
  invokedAt: nowIso(),
801
+ runId,
802
+ sessionId,
803
+ status,
804
+ taskId,
805
+ requestCount,
806
+ toolCallCount,
807
+ toolErrorCount,
808
+ textSummary,
598
809
  });
599
810
  },
600
- 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),
812
+ });
813
+ }
814
+ replaceDefaultTool(replacement) {
815
+ const existingIndex = this.defaultTools.findIndex(tool => tool.name === replacement.name);
816
+ if (existingIndex >= 0) {
817
+ this.defaultTools.splice(existingIndex, 1, replacement);
818
+ return;
819
+ }
820
+ this.defaultTools.push(replacement);
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
+ }
855
+ /**
856
+ * Model-facing Skill tool backed by the real skill registry. Loads the
857
+ * resolved skill content into the conversation as the tool result
858
+ * (progressive disclosure), instead of the former no-op stub.
859
+ */
860
+ createSkillRegistryTool() {
861
+ return tool({
862
+ name: 'Skill',
863
+ description: 'Load a registered skill by name. The skill content (workflow, checklist, or domain knowledge) is returned so you can follow it for the current task.',
864
+ inputSchema: z.strictObject({
865
+ skill: z.string().describe('The name of the skill to load'),
866
+ args: z.string().optional().describe('Optional arguments for the skill'),
867
+ }),
868
+ isReadOnly: () => true,
869
+ prompt: () => {
870
+ const names = this.listSkillDefinitions()
871
+ .filter((definition) => definition.disableModelInvocation !== true)
872
+ .map((definition) => definition.description
873
+ ? `- ${definition.name}: ${definition.description}`
874
+ : `- ${definition.name}`);
875
+ if (names.length === 0) {
876
+ return '';
877
+ }
878
+ return [
879
+ 'Use the Skill tool to load a registered skill when the task matches its description.',
880
+ 'Available skills:',
881
+ ...names,
882
+ ].join('\n');
883
+ },
884
+ }, async ({ skill, args }, context) => {
885
+ const definition = this.getSkillDefinition(skill);
886
+ if (!definition || definition.disableModelInvocation === true) {
887
+ const available = this.listSkillDefinitions()
888
+ .filter((entry) => entry.disableModelInvocation !== true)
889
+ .map((entry) => entry.name)
890
+ .join(', ');
891
+ throw new Error(`No skill named "${skill}" is registered.${available ? ` Available skills: ${available}.` : ''}`);
892
+ }
893
+ const resolved = await resolveActoviqSkillPrompt(definition, args ?? '', {
894
+ args: args ?? '',
895
+ workDir: this.config.workDir,
896
+ homeDir: this.config.homeDir,
897
+ sessionId: context.sessionId,
898
+ userId: this.config.userId,
899
+ });
900
+ const content = extractTextFromContent(resolved.content);
901
+ return [
902
+ `Loaded skill "${definition.name}".`,
903
+ '',
904
+ '<skill-content>',
905
+ content,
906
+ '</skill-content>',
907
+ '',
908
+ 'Apply this skill to the current task now.',
909
+ ].join('\n');
910
+ });
911
+ }
912
+ createBackgroundTaskListTool() {
913
+ return tool({
914
+ name: 'TaskList',
915
+ description: 'List background subagent tasks for the current SDK runtime.',
916
+ inputSchema: z.strictObject({}),
917
+ isReadOnly: () => true,
918
+ serialize: (output) => {
919
+ if (output.tasks.length === 0) {
920
+ return 'No background tasks.';
921
+ }
922
+ return output.tasks
923
+ .map(task => [
924
+ `Task id: ${task.id}`,
925
+ `Status: ${task.status}`,
926
+ `Subagent: ${task.subagentType}`,
927
+ `Description: ${task.description}`,
928
+ task.runId ? `Run id: ${task.runId}` : undefined,
929
+ task.sessionId ? `Session id: ${task.sessionId}` : undefined,
930
+ task.completedAt ? `Completed at: ${task.completedAt}` : undefined,
931
+ ].filter(Boolean).join('\n'))
932
+ .join('\n\n');
933
+ },
934
+ }, async () => ({
935
+ tasks: await this.backgroundTaskManager.list(),
936
+ }));
937
+ }
938
+ createBackgroundTaskGetTool() {
939
+ return tool({
940
+ name: 'TaskGet',
941
+ description: 'Retrieve background subagent task status by ID.',
942
+ inputSchema: z.strictObject({
943
+ taskId: z.string().optional().describe('The ID of the task to get'),
944
+ task_id: z.string().optional().describe('The ID of the task to get'),
945
+ }).superRefine((input, ctx) => {
946
+ if (!resolveTaskId(input)) {
947
+ ctx.addIssue({
948
+ code: 'custom',
949
+ path: ['task_id'],
950
+ message: 'Provide `task_id` or `taskId`.',
951
+ });
952
+ }
953
+ }),
954
+ isReadOnly: () => true,
955
+ serialize: serializeBackgroundTaskOutput,
956
+ }, async (input) => {
957
+ const taskId = resolveTaskId(input);
958
+ if (!taskId) {
959
+ throw new Error('TaskGet requires `task_id` or `taskId`.');
960
+ }
961
+ const task = await this.backgroundTaskManager.get(taskId);
962
+ if (!task) {
963
+ throw new Error(`No background task with id "${taskId}" exists.`);
964
+ }
965
+ return task;
966
+ });
967
+ }
968
+ createBackgroundTaskStopTool() {
969
+ return tool({
970
+ name: 'TaskStop',
971
+ description: 'Stop a running background subagent task.',
972
+ inputSchema: z.strictObject({
973
+ task_id: z.string().describe('The ID of the task to stop'),
974
+ }),
975
+ isConcurrencySafe: () => true,
976
+ serialize: serializeBackgroundTaskOutput,
977
+ }, async ({ task_id }) => {
978
+ const task = await this.backgroundTaskManager.cancel(task_id);
979
+ if (!task) {
980
+ throw new Error(`No background task with id "${task_id}" exists.`);
981
+ }
982
+ return task;
983
+ });
984
+ }
985
+ createBackgroundTaskOutputTool() {
986
+ return tool({
987
+ name: 'TaskOutput',
988
+ description: 'Retrieve output from a completed or running background subagent task.',
989
+ inputSchema: z.strictObject({
990
+ task_id: z.string().describe('The ID of the task to get output for'),
991
+ block: z.boolean().optional().describe('Wait for task to complete before returning'),
992
+ timeout: z.number().int().positive().optional().describe('Max wait time in milliseconds'),
993
+ }),
994
+ isReadOnly: () => true,
995
+ serialize: serializeBackgroundTaskOutput,
996
+ }, async ({ task_id, block, timeout }, context) => {
997
+ if (block) {
998
+ return this.backgroundTaskManager.wait(task_id, {
999
+ timeoutMs: timeout,
1000
+ signal: context.signal,
1001
+ });
1002
+ }
1003
+ const task = await this.backgroundTaskManager.get(task_id);
1004
+ if (!task) {
1005
+ throw new Error(`No background task with id "${task_id}" exists.`);
1006
+ }
1007
+ return task;
601
1008
  });
602
1009
  }
603
1010
  async prepareSkillExecution(skillName, args, options, sessionId) {
@@ -651,12 +1058,30 @@ export class ActoviqAgentClient {
651
1058
  return session.send(execution.prompt, execution.options);
652
1059
  }
653
1060
  async forwardStreamResult(stream, emit) {
1061
+ const errors = [];
654
1062
  const pump = (async () => {
655
- for await (const event of stream) {
656
- emit(event);
1063
+ try {
1064
+ for await (const event of stream) {
1065
+ emit(event);
1066
+ }
1067
+ }
1068
+ catch (error) {
1069
+ errors.push(error);
657
1070
  }
658
1071
  })();
659
- 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
+ }
660
1085
  return result;
661
1086
  }
662
1087
  getSessionRuntimeOverrides(sessionId) {
@@ -703,14 +1128,19 @@ export class ActoviqAgentClient {
703
1128
  }
704
1129
  this.sessionRuntimeOverrides.set(sessionId, next);
705
1130
  }
706
- setSessionRuntimePermissionContext(sessionId, context) {
1131
+ async setSessionRuntimePermissionContext(session, context) {
1132
+ const sessionId = session.id;
707
1133
  const current = this.sessionRuntimeOverrides.get(sessionId) ?? {};
1134
+ const stored = session.snapshot();
1135
+ const persisted = getPersistedActoviqSessionPermissionState(stored.metadata);
708
1136
  const next = {
709
1137
  ...current,
710
- permissionMode: context.mode,
711
- permissions: clonePermissionRules(context.permissions),
712
- classifier: context.classifier,
713
- 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,
714
1144
  };
715
1145
  if (isHooksEmpty(next.hooks) &&
716
1146
  !next.permissionMode &&
@@ -718,27 +1148,42 @@ export class ActoviqAgentClient {
718
1148
  !next.classifier &&
719
1149
  !next.approver) {
720
1150
  this.sessionRuntimeOverrides.delete(sessionId);
721
- return;
722
1151
  }
723
- 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;
724
1163
  }
725
- clearSessionRuntimePermissionContext(sessionId) {
1164
+ async clearSessionRuntimePermissionContext(session) {
1165
+ const sessionId = session.id;
726
1166
  const current = this.sessionRuntimeOverrides.get(sessionId);
727
- if (!current) {
728
- return;
729
- }
730
- const next = {
731
- ...current,
732
- permissionMode: undefined,
733
- permissions: undefined,
734
- classifier: undefined,
735
- approver: undefined,
736
- };
737
- if (isHooksEmpty(next.hooks)) {
738
- this.sessionRuntimeOverrides.delete(sessionId);
739
- 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
+ }
740
1181
  }
741
- 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;
742
1187
  }
743
1188
  applySessionRuntimeOverrides(sessionId, options) {
744
1189
  const overrides = this.getSessionRuntimeOverrides(sessionId);
@@ -755,6 +1200,16 @@ export class ActoviqAgentClient {
755
1200
  };
756
1201
  }
757
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
+ }
758
1213
  return new AgentSession({
759
1214
  runSession: (session, input, options) => this.runOnSession(session, input, options),
760
1215
  streamSession: (session, input, options) => this.streamOnSession(session, input, options),
@@ -775,8 +1230,9 @@ export class ActoviqAgentClient {
775
1230
  getAgentContinuity: (session) => this.getAgentContinuityForSession(session),
776
1231
  setRuntimeHooks: (session, hooks) => this.setSessionRuntimeHooks(session.id, hooks),
777
1232
  clearRuntimeHooks: (session) => this.clearSessionRuntimeHooks(session.id),
778
- setRuntimePermissionContext: (session, context) => this.setSessionRuntimePermissionContext(session.id, context),
779
- 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),
780
1236
  hydrate: (next) => this.hydrateSession(next),
781
1237
  saveCheckpoint: (_session, label) => this.store.saveCheckpoint(stored.id, label),
782
1238
  restoreCheckpoint: (session, checkpointId) => this.restoreCheckpointToSession(session, checkpointId),
@@ -784,6 +1240,13 @@ export class ActoviqAgentClient {
784
1240
  deleteCheckpoint: (_session, checkpointId) => this.store.deleteCheckpoint(stored.id, checkpointId),
785
1241
  }, this.store, stored);
786
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
+ }
787
1250
  async restoreCheckpointToSession(session, checkpointId) {
788
1251
  const checkpoint = await this.store.loadCheckpoint(session.id, checkpointId);
789
1252
  session.replace(checkpoint.snapshot);
@@ -886,13 +1349,30 @@ export class ActoviqAgentClient {
886
1349
  ...(augmentations?.metadata ?? {}),
887
1350
  ...(options.metadata ?? {}),
888
1351
  };
889
- 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);
890
1354
  // Collect tool prompts for system prompt assembly
891
1355
  const toolPromptParts = await collectToolPrompts(mergedTools, {
892
- workDir: this.config.workDir,
1356
+ workDir,
893
1357
  permissionMode: options.permissionMode ?? this.defaultPermissionMode,
894
1358
  });
895
1359
  const systemPrompt = await this.resolveSystemPrompt(options, session, [...(augmentations?.systemPromptParts ?? []), ...toolPromptParts]);
1360
+ const runtimeConfig = options.__actoviqMaxToolIterations || workDir !== this.config.workDir
1361
+ ? {
1362
+ ...this.config,
1363
+ workDir,
1364
+ ...(options.__actoviqMaxToolIterations
1365
+ ? { maxToolIterations: options.__actoviqMaxToolIterations }
1366
+ : {}),
1367
+ }
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;
896
1376
  return executeConversation({
897
1377
  runId,
898
1378
  input,
@@ -902,9 +1382,10 @@ export class ActoviqAgentClient {
902
1382
  systemPrompt,
903
1383
  tools: mergedTools,
904
1384
  mcpServers: mergeUniqueByName(options.__actoviqUseDefaultMcpServers === false ? [] : this.defaultMcpServers, options.mcpServers ?? []),
905
- model: options.model ?? session?.model ?? this.config.model,
1385
+ model: this.resolveModel(options.model ?? session?.model),
906
1386
  maxTokens: options.maxTokens,
907
1387
  temperature: options.temperature,
1388
+ effort: options.effort,
908
1389
  toolChoice: options.toolChoice,
909
1390
  userId: options.userId ?? this.config.userId,
910
1391
  metadata,
@@ -915,11 +1396,12 @@ export class ActoviqAgentClient {
915
1396
  approver: options.approver ?? this.defaultApprover,
916
1397
  canUseTool: options.canUseTool,
917
1398
  hooks: augmentations?.hooks,
1399
+ drainQueuedInputs,
918
1400
  streaming,
919
1401
  emit,
920
1402
  skipRunStartedEvent,
921
1403
  modelApi: this.modelApi,
922
- config: this.config,
1404
+ config: runtimeConfig,
923
1405
  mcpManager: this.mcpManager,
924
1406
  }).then(result => ({
925
1407
  ...result,
@@ -979,11 +1461,32 @@ export class ActoviqAgentClient {
979
1461
  return promptParts.join('\n\n');
980
1462
  }
981
1463
  async prepareRunAugmentations(runId, input, options, session) {
1464
+ const internalOptions = options;
982
1465
  const promptText = typeof input === 'string' ? input : extractTextFromContent(input);
983
1466
  const memoryContext = await this.prepareRelevantMemoryContext(input, session);
984
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);
985
1484
  const hooks = mergeActoviqHooks(this.hooks, options.hooks);
986
1485
  const prefixedMessages = [
1486
+ ...notificationMessages,
1487
+ ...initialPromptMessages,
1488
+ ...agentMemoryMessages,
1489
+ ...preloadedSkillMessages,
987
1490
  ...invokedSkillContext.prefixedMessages,
988
1491
  ...memoryContext.prefixedMessages,
989
1492
  ];
@@ -996,7 +1499,7 @@ export class ActoviqAgentClient {
996
1499
  promptText,
997
1500
  sessionId: session?.id,
998
1501
  session: session ? deepClone(session) : undefined,
999
- workDir: this.config.workDir,
1502
+ workDir: internalOptions.__actoviqWorkDir ?? internalOptions.workDir ?? this.config.workDir,
1000
1503
  options,
1001
1504
  });
1002
1505
  if (!result) {
@@ -1019,6 +1522,70 @@ export class ActoviqAgentClient {
1019
1522
  metadata,
1020
1523
  };
1021
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
+ }
1022
1589
  async applyPostRunHooks(runId, input, options, result, session) {
1023
1590
  const promptText = typeof input === 'string' ? input : extractTextFromContent(input);
1024
1591
  const hooks = mergeActoviqHooks(this.hooks, options.hooks);
@@ -1031,7 +1598,9 @@ export class ActoviqAgentClient {
1031
1598
  promptText,
1032
1599
  sessionId: session?.id,
1033
1600
  session: session ? deepClone(session) : undefined,
1034
- workDir: this.config.workDir,
1601
+ workDir: options.__actoviqWorkDir ??
1602
+ options.workDir ??
1603
+ this.config.workDir,
1035
1604
  options,
1036
1605
  result,
1037
1606
  });
@@ -1057,6 +1626,27 @@ export class ActoviqAgentClient {
1057
1626
  existing.push(record);
1058
1627
  this.pendingDelegations.set(key, existing);
1059
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
+ }
1060
1650
  consumePendingDelegations(key) {
1061
1651
  if (!key) {
1062
1652
  return [];
@@ -1157,6 +1747,9 @@ export class ActoviqAgentClient {
1157
1747
  latestBoundary,
1158
1748
  compactCount: Math.max(compactState.compactCount, persistedCompactState.compactCount),
1159
1749
  microcompactCount: Math.max(compactState.microcompactCount, persistedCompactState.microcompactCount),
1750
+ consecutiveCompactFailures: persistedCompactState.consecutiveFailures,
1751
+ lastCompactFailureAt: persistedCompactState.lastFailureAt,
1752
+ lastCompactError: persistedCompactState.lastError,
1160
1753
  hasCompacted: compactState.hasCompacted ||
1161
1754
  persistedCompactState.compactCount + persistedCompactState.microcompactCount > 0,
1162
1755
  summaryMessage: compactState.summaryMessage ?? persistedCompactState.lastSummaryMessage,
@@ -1185,12 +1778,16 @@ export class ActoviqAgentClient {
1185
1778
  }, {
1186
1779
  workDir: this.config.workDir,
1187
1780
  systemPrompt: snapshot.systemPrompt ?? this.config.systemPrompt,
1188
- model: options.model ?? snapshot.model ?? this.config.model,
1781
+ model: this.resolveModel(options.model ?? snapshot.model),
1189
1782
  modelApi: this.modelApi,
1190
1783
  compactConfig: this.config.compact,
1191
1784
  runtimeState: this.getSessionMemoryRuntimeState(snapshot),
1192
1785
  });
1193
- 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
+ }
1194
1791
  return undefined;
1195
1792
  }
1196
1793
  await this.store.save(reactive.session);
@@ -1208,48 +1805,239 @@ export class ActoviqAgentClient {
1208
1805
  result: reactive.result,
1209
1806
  };
1210
1807
  }
1211
- async launchBackgroundAgentTask(agent, prompt, options, runOptions = {}) {
1808
+ async runDelegatedAgentTask(agent, prompt, runOptions = {}, delegation = { description: prompt }) {
1212
1809
  const definition = this.requireAgentDefinition(agent);
1213
- const session = await this.createAgentSession(agent, {
1214
- 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,
1215
1900
  metadata: {
1216
- __actoviqBackgroundParentRunId: options.parentRunId,
1217
- __actoviqBackgroundParentSessionId: options.parentSessionId,
1901
+ agent: definition.name,
1218
1902
  },
1219
1903
  });
1220
- if (runOptions.hooks) {
1221
- 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;
1222
1923
  }
1223
- if (runOptions.permissionMode ||
1224
- runOptions.permissions ||
1225
- runOptions.classifier ||
1226
- runOptions.approver) {
1227
- session.setPermissionContext({
1228
- mode: runOptions.permissionMode,
1229
- permissions: runOptions.permissions,
1230
- classifier: runOptions.classifier,
1231
- approver: runOptions.approver,
1232
- });
1924
+ const dirty = await isGitWorkspaceDirty(workspace.path);
1925
+ if (!dirty) {
1926
+ await workspace.dispose();
1927
+ return false;
1233
1928
  }
1234
- return this.backgroundTaskManager.launch({
1235
- subagentType: definition.name,
1236
- description: prompt,
1237
- workDir: this.config.workDir,
1238
- parentRunId: options.parentRunId,
1239
- parentSessionId: options.parentSessionId,
1240
- onRun: async (signal) => {
1241
- const result = await session.send(prompt, { signal });
1242
- return {
1243
- runId: result.runId,
1244
- sessionId: session.id,
1245
- model: result.model,
1246
- text: result.text,
1247
- toolCallCount: result.toolCalls.length,
1248
- };
1249
- },
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);
1949
+ }
1950
+ return notifications;
1951
+ }
1952
+ enqueueTaskNotification(task) {
1953
+ const notificationKey = task.parentSessionId ?? task.parentRunId;
1954
+ if (!notificationKey || task.notificationDeliveredAt) {
1955
+ return;
1956
+ }
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(),
1250
1980
  });
1251
1981
  }
1252
- 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) {
1253
2041
  const definition = this.requireAgentDefinition(agent);
1254
2042
  if (runOptions.hooks) {
1255
2043
  session.setHooks(runOptions.hooks);
@@ -1258,7 +2046,7 @@ export class ActoviqAgentClient {
1258
2046
  runOptions.permissions ||
1259
2047
  runOptions.classifier ||
1260
2048
  runOptions.approver) {
1261
- session.setPermissionContext({
2049
+ await session.setPermissionContext({
1262
2050
  mode: runOptions.permissionMode,
1263
2051
  permissions: runOptions.permissions,
1264
2052
  classifier: runOptions.classifier,
@@ -1267,32 +2055,82 @@ export class ActoviqAgentClient {
1267
2055
  }
1268
2056
  return this.backgroundTaskManager.launch({
1269
2057
  subagentType: definition.name,
1270
- description: prompt,
1271
- workDir: this.config.workDir,
2058
+ description: delegation.description,
2059
+ workDir: delegation.cwd ?? this.config.workDir,
1272
2060
  parentRunId: options.parentRunId,
1273
2061
  parentSessionId: options.parentSessionId ?? session.id,
1274
- onRun: async (signal) => {
1275
- const result = await session.send(prompt, { signal });
1276
- return {
1277
- runId: result.runId,
1278
- sessionId: session.id,
1279
- model: result.model,
1280
- text: result.text,
1281
- toolCallCount: result.toolCalls.length,
1282
- };
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);
1283
2075
  },
1284
2076
  });
1285
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
+ }
1286
2123
  async compactSessionForSession(session, options = {}) {
1287
2124
  const snapshot = session.snapshot();
1288
2125
  const { session: compactedSession, result } = await compactActoviqSession(snapshot, {
1289
2126
  ...options,
2127
+ model: options.model ? this.resolveModel(options.model) : undefined,
1290
2128
  force: options.force ?? true,
1291
2129
  trigger: 'manual',
1292
2130
  }, {
1293
2131
  workDir: this.config.workDir,
1294
2132
  systemPrompt: snapshot.systemPrompt ?? this.config.systemPrompt,
1295
- model: snapshot.model ?? this.config.model,
2133
+ model: this.resolveModel(snapshot.model),
1296
2134
  modelApi: this.modelApi,
1297
2135
  compactConfig: this.config.compact,
1298
2136
  runtimeState: this.getSessionMemoryRuntimeState(snapshot),
@@ -1313,7 +2151,7 @@ export class ActoviqAgentClient {
1313
2151
  systemPrompt: await this.buildDreamSystemPrompt(),
1314
2152
  tools: createActoviqFileTools({ cwd: this.config.workDir }),
1315
2153
  mcpServers: [],
1316
- model: request.model ?? this.config.model,
2154
+ model: this.resolveModel(request.model),
1317
2155
  maxTokens: request.maxTokens ?? DEFAULT_DREAM_MAX_TOKENS,
1318
2156
  userId: this.config.userId,
1319
2157
  metadata: {
@@ -1527,7 +2365,7 @@ export class ActoviqAgentClient {
1527
2365
  const stored = session.snapshot();
1528
2366
  const extraction = await this.performSessionMemoryExtraction(stored, {
1529
2367
  force: options.force ?? true,
1530
- model: options.model ?? stored.model ?? this.config.model,
2368
+ model: this.resolveModel(options.model ?? stored.model),
1531
2369
  systemPrompt: stored.systemPrompt ?? this.config.systemPrompt,
1532
2370
  trigger: 'manual',
1533
2371
  maxTokens: options.maxTokens,
@@ -1549,6 +2387,9 @@ export class ActoviqAgentClient {
1549
2387
  ...(options.metadata ?? {}),
1550
2388
  ...(hookOutcome.sessionMetadata ?? {}),
1551
2389
  };
2390
+ if (result.loopCompactions?.length) {
2391
+ recordActoviqLoopCompactionsOnSession(next, result.loopCompactions);
2392
+ }
1552
2393
  const runtimeState = this.getSessionMemoryRuntimeState(next);
1553
2394
  if (runtimeState.pendingPostCompaction) {
1554
2395
  runtimeState.pendingPostCompaction = false;
@@ -1575,6 +2416,30 @@ export class ActoviqAgentClient {
1575
2416
  next.metadata[INVOKED_SKILLS_STATE_KEY] = invokedSkills;
1576
2417
  result.invokedSkills = invokedSkills;
1577
2418
  }
2419
+ // Track recently touched files and skills so post-compact context
2420
+ // reminders can point the model back to its working set.
2421
+ for (const call of result.toolCalls) {
2422
+ if (call.isError) {
2423
+ continue;
2424
+ }
2425
+ const callInput = call.input;
2426
+ if (RECENT_FILE_TOOL_NAMES.has(call.publicName)) {
2427
+ const filePath = typeof callInput?.file_path === 'string'
2428
+ ? callInput.file_path
2429
+ : typeof callInput?.notebook_path === 'string'
2430
+ ? callInput.notebook_path
2431
+ : undefined;
2432
+ if (filePath) {
2433
+ trackRecentFile(next, filePath);
2434
+ }
2435
+ }
2436
+ if (call.publicName === 'Skill' && typeof callInput?.skill === 'string') {
2437
+ trackRecentSkill(next, callInput.skill);
2438
+ }
2439
+ }
2440
+ for (const record of result.invokedSkills ?? []) {
2441
+ trackRecentSkill(next, record.name);
2442
+ }
1578
2443
  const previousRelevantMemoryState = getRelevantMemorySessionState(next.metadata);
1579
2444
  const surfacedPaths = new Set(previousRelevantMemoryState.surfacedPaths);
1580
2445
  let totalBytes = previousRelevantMemoryState.totalBytes;
@@ -1618,7 +2483,7 @@ export class ActoviqAgentClient {
1618
2483
  await appendMessagesToTranscript(paths.projectStateDir, session.id, this.config.workDir, newMessages);
1619
2484
  }
1620
2485
  const extraction = await this.performSessionMemoryExtraction(next, {
1621
- model: options.model ?? next.model ?? this.config.model,
2486
+ model: this.resolveModel(options.model ?? next.model),
1622
2487
  systemPrompt: next.systemPrompt ?? this.config.systemPrompt,
1623
2488
  trigger: 'auto',
1624
2489
  maxTokens: Math.min(options.maxTokens ?? this.config.maxTokens, DEFAULT_SESSION_MEMORY_MAX_TOKENS),
@@ -1628,7 +2493,7 @@ export class ActoviqAgentClient {
1628
2493
  const compacted = await compactActoviqSession(next, { trigger: 'auto' }, {
1629
2494
  workDir: this.config.workDir,
1630
2495
  systemPrompt: next.systemPrompt ?? this.config.systemPrompt,
1631
- model: options.model ?? next.model ?? this.config.model,
2496
+ model: this.resolveModel(options.model ?? next.model),
1632
2497
  modelApi: this.modelApi,
1633
2498
  compactConfig: this.config.compact,
1634
2499
  runtimeState: extraction.state,
@@ -1674,10 +2539,22 @@ export class ActoviqAgentClient {
1674
2539
  return this.mergeAgentRunOptions(this.requireAgentDefinition(agentName), options);
1675
2540
  }
1676
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'];
1677
2552
  return {
1678
2553
  ...options,
1679
2554
  systemPrompt: joinPromptParts(definition.systemPrompt, options.systemPrompt),
1680
2555
  model: options.model ?? definition.model,
2556
+ effort: options.effort ?? definition.effort,
2557
+ permissionMode: options.permissionMode ?? definition.permissionMode,
1681
2558
  metadata: {
1682
2559
  ...(definition.metadata ?? {}),
1683
2560
  ...(options.metadata ?? {}),
@@ -1688,6 +2565,19 @@ export class ActoviqAgentClient {
1688
2565
  mcpServers: [...(definition.mcpServers ?? []), ...(options.mcpServers ?? [])],
1689
2566
  __actoviqUseDefaultTools: definition.inheritDefaultTools !== false,
1690
2567
  __actoviqUseDefaultMcpServers: definition.inheritDefaultMcpServers !== false,
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,
1691
2581
  };
1692
2582
  }
1693
2583
  mergeSkillRunOptions(definition, options) {
@@ -1719,6 +2609,35 @@ export class ActoviqAgentClient {
1719
2609
  export async function createAgentSdk(options = {}) {
1720
2610
  return ActoviqAgentClient.create(options);
1721
2611
  }
2612
+ function resolveTaskId(input) {
2613
+ return [input.task_id, input.taskId]
2614
+ .map(value => value?.trim())
2615
+ .find((value) => Boolean(value));
2616
+ }
2617
+ function serializeBackgroundTaskOutput(task) {
2618
+ return [
2619
+ `Task id: ${task.id}`,
2620
+ `Status: ${task.status}`,
2621
+ `Subagent: ${task.subagentType}`,
2622
+ task.agentName ? `Agent name: ${task.agentName}` : undefined,
2623
+ task.runId ? `Run id: ${task.runId}` : undefined,
2624
+ task.sessionId ? `Session id: ${task.sessionId}` : undefined,
2625
+ task.model ? `Model: ${task.model}` : undefined,
2626
+ typeof task.toolCallCount === 'number' ? `Tool calls: ${task.toolCallCount}` : undefined,
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,
2633
+ task.error ? `Error:\n${task.error}` : undefined,
2634
+ task.text
2635
+ ? `Output:\n${task.text}`
2636
+ : task.partialText
2637
+ ? `Partial output:\n${task.partialText}`
2638
+ : 'Output: <not available yet>',
2639
+ ].filter(Boolean).join('\n');
2640
+ }
1722
2641
  function joinPromptParts(...parts) {
1723
2642
  const normalized = parts.filter((value) => typeof value === 'string' && value.trim().length > 0);
1724
2643
  if (normalized.length === 0) {
@@ -1726,6 +2645,99 @@ function joinPromptParts(...parts) {
1726
2645
  }
1727
2646
  return normalized.join('\n\n');
1728
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
+ }
1729
2741
  function mergeUniqueByName(defaults, overrides) {
1730
2742
  const merged = new Map();
1731
2743
  for (const item of defaults) {