praxis-agent 0.14.0 → 0.16.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.
- package/README.md +5 -2
- package/dist/application/session-service.d.ts +18 -0
- package/dist/application/session-service.js +180 -41
- package/dist/application/subagent-service.d.ts +7 -1
- package/dist/application/subagent-service.js +238 -53
- package/dist/cli/interactive.d.ts +7 -1
- package/dist/cli/interactive.js +77 -3
- package/dist/cli/protocol.d.ts +13 -0
- package/dist/cli/protocol.js +34 -0
- package/dist/cli/tui/claude-command-inventory.d.ts +1 -2
- package/dist/cli/tui/claude-command-inventory.js +1 -6
- package/dist/cli/tui/claude-style.d.ts +4 -1
- package/dist/cli/tui/claude-style.js +8 -3
- package/dist/cli/tui/slash-commands.d.ts +1 -0
- package/dist/cli/tui/slash-commands.js +11 -0
- package/dist/cli/tui/theme.d.ts +2 -0
- package/dist/cli/tui/theme.js +69 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +95 -27
- package/dist/compatibility/claude/agent-color.d.ts +25 -0
- package/dist/compatibility/claude/agent-color.js +79 -0
- package/dist/compatibility/claude/fork.js +5 -1
- package/dist/compatibility/claude/schema.js +13 -1
- package/dist/extensions/claude-extensions.d.ts +19 -1
- package/dist/extensions/claude-extensions.js +126 -1
- package/dist/hooks/claude-hooks.d.ts +3 -1
- package/dist/hooks/claude-hooks.js +14 -0
- package/dist/mcp/claude-mcp-tools.d.ts +18 -0
- package/dist/mcp/claude-mcp-tools.js +46 -0
- package/dist/persistence/claude-transcript-store.js +1 -0
- package/dist/tools/claude-interactive-tools.d.ts +1 -0
- package/dist/tools/claude-interactive-tools.js +3 -0
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, readFile, readdir, stat } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
5
5
|
import { resolveClaudePaths } from '../compatibility/claude/paths.js';
|
|
@@ -103,6 +103,155 @@ class RestrictedToolRegistry {
|
|
|
103
103
|
return this.base.execute(call, context);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
const AGENT_UNAVAILABLE_TOOLS = new Set([
|
|
107
|
+
'Agent',
|
|
108
|
+
'Task',
|
|
109
|
+
'TaskOutput',
|
|
110
|
+
'TaskStop',
|
|
111
|
+
'ExitPlanMode',
|
|
112
|
+
'EnterPlanMode',
|
|
113
|
+
'AskUserQuestion',
|
|
114
|
+
'Workflow',
|
|
115
|
+
]);
|
|
116
|
+
const BACKGROUND_AGENT_TOOLS = new Set([
|
|
117
|
+
'Read',
|
|
118
|
+
'WebSearch',
|
|
119
|
+
'TodoWrite',
|
|
120
|
+
'Grep',
|
|
121
|
+
'WebFetch',
|
|
122
|
+
'Glob',
|
|
123
|
+
'Bash',
|
|
124
|
+
'Edit',
|
|
125
|
+
'Write',
|
|
126
|
+
'NotebookEdit',
|
|
127
|
+
'Skill',
|
|
128
|
+
'StructuredOutput',
|
|
129
|
+
'ToolSearch',
|
|
130
|
+
'EnterWorktree',
|
|
131
|
+
'ExitWorktree',
|
|
132
|
+
]);
|
|
133
|
+
function agentToolRuleName(rule) {
|
|
134
|
+
const opening = rule.indexOf('(');
|
|
135
|
+
return (opening < 0 ? rule : rule.slice(0, opening)).trim();
|
|
136
|
+
}
|
|
137
|
+
function enabledAgentToolNames(base, definition, background, additiveTools = new Set()) {
|
|
138
|
+
const requested = definition?.tools
|
|
139
|
+
? new Set(definition.tools.map(agentToolRuleName))
|
|
140
|
+
: null;
|
|
141
|
+
if (requested && definition?.memory) {
|
|
142
|
+
requested.add('Read');
|
|
143
|
+
requested.add('Edit');
|
|
144
|
+
requested.add('Write');
|
|
145
|
+
}
|
|
146
|
+
const disallowed = new Set(definition?.disallowedTools?.map(agentToolRuleName) ?? []);
|
|
147
|
+
return base
|
|
148
|
+
.definitions()
|
|
149
|
+
.map(({ name }) => name)
|
|
150
|
+
.filter((name) => {
|
|
151
|
+
if (additiveTools.has(name))
|
|
152
|
+
return true;
|
|
153
|
+
if (AGENT_UNAVAILABLE_TOOLS.has(name))
|
|
154
|
+
return false;
|
|
155
|
+
if (background &&
|
|
156
|
+
!name.startsWith('mcp__') &&
|
|
157
|
+
!BACKGROUND_AGENT_TOOLS.has(name)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (requested && !requested.has(name))
|
|
161
|
+
return false;
|
|
162
|
+
return !disallowed.has(name);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function agentMemoryDirectory(configRoot, cwd, agentType, scope) {
|
|
166
|
+
const directoryName = agentType.replaceAll(':', '-');
|
|
167
|
+
if (scope === 'user') {
|
|
168
|
+
return join(configRoot, 'agent-memory', directoryName);
|
|
169
|
+
}
|
|
170
|
+
return join(cwd, '.claude', scope === 'project' ? 'agent-memory' : 'agent-memory-local', directoryName);
|
|
171
|
+
}
|
|
172
|
+
export async function agentMemoryPrompt(configRoot, cwd, definition) {
|
|
173
|
+
if (!definition?.memory)
|
|
174
|
+
return null;
|
|
175
|
+
const directory = agentMemoryDirectory(configRoot, cwd, definition.name, definition.memory);
|
|
176
|
+
await mkdir(directory, { recursive: true }).catch(() => undefined);
|
|
177
|
+
let source = '';
|
|
178
|
+
try {
|
|
179
|
+
source = await readFile(join(directory, 'MEMORY.md'), 'utf8');
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
source = '';
|
|
183
|
+
}
|
|
184
|
+
const lines = source.trim().split('\n');
|
|
185
|
+
const truncated = lines.slice(0, 200).join('\n').slice(0, 25_000);
|
|
186
|
+
const scopeGuidance = definition.memory === 'user'
|
|
187
|
+
? 'Keep entries general enough to apply across projects.'
|
|
188
|
+
: definition.memory === 'project'
|
|
189
|
+
? 'Keep entries specific to this project and suitable for version control.'
|
|
190
|
+
: 'Keep entries specific to this project and machine; this scope is not version controlled.';
|
|
191
|
+
return [
|
|
192
|
+
'# Persistent Agent Memory',
|
|
193
|
+
'',
|
|
194
|
+
`Use the file-based memory directory at \`${directory}\`. The directory already exists.`,
|
|
195
|
+
'Keep MEMORY.md as a concise index and store detailed durable knowledge in linked Markdown files. Update existing entries instead of duplicating them, and do not save transient task state.',
|
|
196
|
+
scopeGuidance,
|
|
197
|
+
'',
|
|
198
|
+
'## MEMORY.md',
|
|
199
|
+
'',
|
|
200
|
+
truncated || 'The memory index is currently empty.',
|
|
201
|
+
].join('\n');
|
|
202
|
+
}
|
|
203
|
+
function agentSkillMessages(catalog, definition) {
|
|
204
|
+
if (!catalog || !definition?.skills?.length)
|
|
205
|
+
return [];
|
|
206
|
+
const available = catalog.modelInvocableSkills();
|
|
207
|
+
const prefix = definition.name.split(':')[0];
|
|
208
|
+
return definition.skills.flatMap((requested) => {
|
|
209
|
+
const skill = available.find(({ name }) => name === requested) ??
|
|
210
|
+
available.find(({ name }) => name === `${prefix}:${requested}`) ??
|
|
211
|
+
available.find(({ name }) => name.endsWith(`:${requested}`));
|
|
212
|
+
if (!skill)
|
|
213
|
+
return [];
|
|
214
|
+
const content = catalog.renderSkill(skill.name, '');
|
|
215
|
+
if (content === null)
|
|
216
|
+
return [];
|
|
217
|
+
return [
|
|
218
|
+
{
|
|
219
|
+
role: 'user',
|
|
220
|
+
content: [
|
|
221
|
+
`<command-message>${skill.name}</command-message>`,
|
|
222
|
+
`<command-name>${skill.name}</command-name>`,
|
|
223
|
+
'<skill-format>true</skill-format>',
|
|
224
|
+
'',
|
|
225
|
+
content,
|
|
226
|
+
].join('\n'),
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function agentHookSettings(definition) {
|
|
232
|
+
if (!definition?.hooks)
|
|
233
|
+
return [];
|
|
234
|
+
const { Stop, SubagentStop, ...hooks } = definition.hooks;
|
|
235
|
+
const stopHooks = Stop === undefined
|
|
236
|
+
? SubagentStop
|
|
237
|
+
: SubagentStop === undefined
|
|
238
|
+
? Stop
|
|
239
|
+
: Array.isArray(Stop) && Array.isArray(SubagentStop)
|
|
240
|
+
? [...SubagentStop, ...Stop]
|
|
241
|
+
: Stop;
|
|
242
|
+
return [
|
|
243
|
+
{
|
|
244
|
+
path: definition.path,
|
|
245
|
+
scope: definition.scope,
|
|
246
|
+
value: {
|
|
247
|
+
hooks: {
|
|
248
|
+
...hooks,
|
|
249
|
+
...(stopHooks === undefined ? {} : { SubagentStop: stopHooks }),
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
];
|
|
254
|
+
}
|
|
106
255
|
function parseAgentInput(call) {
|
|
107
256
|
const allowed = new Set([
|
|
108
257
|
'description',
|
|
@@ -180,7 +329,7 @@ function parseAgentInput(call) {
|
|
|
180
329
|
? {}
|
|
181
330
|
: { permissionMode: permissionMode }),
|
|
182
331
|
...(isolation === undefined ? {} : { isolation }),
|
|
183
|
-
runInBackground: call.input.run_in_background
|
|
332
|
+
runInBackground: call.input.run_in_background === true,
|
|
184
333
|
};
|
|
185
334
|
}
|
|
186
335
|
export class ClaudeSubagentExecutor {
|
|
@@ -209,6 +358,35 @@ export class ClaudeSubagentExecutor {
|
|
|
209
358
|
cwd() {
|
|
210
359
|
return this.options.cwdProvider?.() ?? this.options.cwd;
|
|
211
360
|
}
|
|
361
|
+
agentDefinition(input) {
|
|
362
|
+
return this.options.extensions?.agent(input.subagentType) ?? null;
|
|
363
|
+
}
|
|
364
|
+
resolveAgentInput(input) {
|
|
365
|
+
const definition = this.agentDefinition(input);
|
|
366
|
+
const environmentModel = process.env.CLAUDE_CODE_SUBAGENT_MODEL?.trim();
|
|
367
|
+
const configuredModel = definition?.model && definition.model !== 'inherit'
|
|
368
|
+
? definition.model
|
|
369
|
+
: undefined;
|
|
370
|
+
const model = environmentModel || input.model || configuredModel;
|
|
371
|
+
const parentPermissionMode = this.options.parentPermissionMode?.();
|
|
372
|
+
const definitionCanOverridePermissionMode = ![
|
|
373
|
+
'acceptEdits',
|
|
374
|
+
'auto',
|
|
375
|
+
'bypassPermissions',
|
|
376
|
+
].includes(parentPermissionMode ?? 'default');
|
|
377
|
+
const permissionMode = input.permissionMode ??
|
|
378
|
+
(definitionCanOverridePermissionMode
|
|
379
|
+
? definition?.permissionMode
|
|
380
|
+
: parentPermissionMode);
|
|
381
|
+
const isolation = input.isolation ?? definition?.isolation;
|
|
382
|
+
return {
|
|
383
|
+
...input,
|
|
384
|
+
...(model ? { model } : {}),
|
|
385
|
+
...(permissionMode ? { permissionMode } : {}),
|
|
386
|
+
...(isolation ? { isolation } : {}),
|
|
387
|
+
runInBackground: input.runInBackground || definition?.background === true,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
212
390
|
registry(sessionId, depth, promptIdForCall) {
|
|
213
391
|
return new ClaudeSubagentToolRegistry(this.options.baseTools, this, sessionId, depth, promptIdForCall);
|
|
214
392
|
}
|
|
@@ -328,7 +506,7 @@ export class ClaudeSubagentExecutor {
|
|
|
328
506
|
prepare(call, depth) {
|
|
329
507
|
if (!this.isEnabled('Agent'))
|
|
330
508
|
throw new Error('Tool Agent is unavailable');
|
|
331
|
-
const input = parseAgentInput(call);
|
|
509
|
+
const input = this.resolveAgentInput(parseAgentInput(call));
|
|
332
510
|
if (input.runInBackground && this.options.persistence === 'memory') {
|
|
333
511
|
throw new Error('Background agents require session persistence');
|
|
334
512
|
}
|
|
@@ -367,7 +545,7 @@ export class ClaudeSubagentExecutor {
|
|
|
367
545
|
if (this.calls > maxCalls) {
|
|
368
546
|
throw new Error(`Agent call count exceeded ${maxCalls}`);
|
|
369
547
|
}
|
|
370
|
-
const input = parseAgentInput(call);
|
|
548
|
+
const input = this.resolveAgentInput(parseAgentInput(call));
|
|
371
549
|
const spawnDepth = depth + 1;
|
|
372
550
|
const agentId = `a${randomBytes(8).toString('hex')}`;
|
|
373
551
|
const paths = resolveClaudePaths({
|
|
@@ -959,33 +1137,52 @@ export class ClaudeSubagentExecutor {
|
|
|
959
1137
|
await append(toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType));
|
|
960
1138
|
}
|
|
961
1139
|
};
|
|
1140
|
+
const customAgent = this.agentDefinition(options.input);
|
|
962
1141
|
const hookSession = {
|
|
963
1142
|
session_id: String(options.root.sessionId),
|
|
964
1143
|
transcript_path: options.transcriptPath,
|
|
965
1144
|
cwd,
|
|
966
1145
|
permission_mode: options.input.permissionMode ?? 'default',
|
|
967
1146
|
};
|
|
1147
|
+
const scopedHookSettings = agentHookSettings(customAgent);
|
|
1148
|
+
const scopedHooks = scopedHookSettings.length > 0
|
|
1149
|
+
? this.options.hooks?.withAdditionalSettings(scopedHookSettings)
|
|
1150
|
+
: this.options.hooks;
|
|
968
1151
|
const nestedTools = this.registry(String(options.root.sessionId), options.spawnDepth, () => options.promptId);
|
|
969
|
-
const
|
|
970
|
-
|
|
1152
|
+
const agentMcp = customAgent?.mcpServers?.length
|
|
1153
|
+
? await this.options.mcp?.connectAgent?.({
|
|
1154
|
+
specs: customAgent.mcpServers,
|
|
1155
|
+
base: nestedTools,
|
|
1156
|
+
cwd,
|
|
1157
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
1158
|
+
})
|
|
1159
|
+
: null;
|
|
1160
|
+
const agentToolBase = agentMcp?.tools ?? nestedTools;
|
|
1161
|
+
const inheritedToolNames = new Set(nestedTools.definitions().map(({ name }) => name));
|
|
1162
|
+
const additiveAgentToolNames = new Set(agentToolBase
|
|
1163
|
+
.definitions()
|
|
1164
|
+
.map(({ name }) => name)
|
|
1165
|
+
.filter((name) => !inheritedToolNames.has(name)));
|
|
1166
|
+
const agentScopedTools = new RestrictedToolRegistry(agentToolBase, enabledAgentToolNames(agentToolBase, customAgent, options.input.runInBackground, additiveAgentToolNames));
|
|
1167
|
+
const builtInStatusLineAgent = customAgent?.path === BUILTIN_STATUSLINE_AGENT_PATH;
|
|
971
1168
|
const scopedTools = builtInStatusLineAgent
|
|
972
|
-
? new RestrictedToolRegistry(
|
|
973
|
-
:
|
|
1169
|
+
? new RestrictedToolRegistry(agentScopedTools, ['Read', 'Edit'])
|
|
1170
|
+
: agentScopedTools;
|
|
974
1171
|
const agentTools = options.outputSchema
|
|
975
1172
|
? new StructuredOutputRegistry(builtInStatusLineAgent
|
|
976
1173
|
? new RestrictedToolRegistry(structuredOnlyTools, ['Read', 'Edit'])
|
|
977
1174
|
: structuredOnlyTools, options.outputSchema, options.structuredOutput)
|
|
978
1175
|
: scopedTools;
|
|
979
|
-
const runtimeTools =
|
|
1176
|
+
const runtimeTools = scopedHooks
|
|
980
1177
|
? new ClaudeHookToolCoordinator({
|
|
981
1178
|
tools: agentTools,
|
|
982
1179
|
permissions,
|
|
983
|
-
hooks:
|
|
1180
|
+
hooks: scopedHooks,
|
|
984
1181
|
session: hookSession,
|
|
985
1182
|
recordOutcome: recordHookOutcome,
|
|
986
1183
|
})
|
|
987
1184
|
: agentTools;
|
|
988
|
-
const runtimePermissions =
|
|
1185
|
+
const runtimePermissions = scopedHooks
|
|
989
1186
|
? runtimeTools
|
|
990
1187
|
: permissions;
|
|
991
1188
|
const emit = (event) => {
|
|
@@ -1032,7 +1229,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1032
1229
|
const runtime = new AgentRuntime(options.provider, emit, {
|
|
1033
1230
|
tools: runtimeTools,
|
|
1034
1231
|
permissions: runtimePermissions,
|
|
1035
|
-
maxModelTurns: 16,
|
|
1232
|
+
maxModelTurns: customAgent?.maxTurns ?? 16,
|
|
1036
1233
|
maxModelOutputBytes: this.options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
|
|
1037
1234
|
maxToolCallsPerTurn: 32,
|
|
1038
1235
|
maxToolInputBytes: 1024 * 1024,
|
|
@@ -1104,36 +1301,31 @@ export class ClaudeSubagentExecutor {
|
|
|
1104
1301
|
prompt: options.input.prompt,
|
|
1105
1302
|
});
|
|
1106
1303
|
try {
|
|
1107
|
-
if (
|
|
1108
|
-
const start = await
|
|
1304
|
+
if (scopedHooks) {
|
|
1305
|
+
const start = await scopedHooks.run({
|
|
1109
1306
|
...hookSession,
|
|
1110
|
-
hook_event_name: '
|
|
1111
|
-
|
|
1112
|
-
|
|
1307
|
+
hook_event_name: 'SubagentStart',
|
|
1308
|
+
agent_id: options.agentId,
|
|
1309
|
+
agent_type: options.input.subagentType,
|
|
1310
|
+
}, options.input.subagentType, options.signal);
|
|
1113
1311
|
await recordHookOutcome(start);
|
|
1114
1312
|
if (start.blockedReason) {
|
|
1115
|
-
throw new Error(`
|
|
1116
|
-
}
|
|
1117
|
-
const prompt = await this.options.hooks.run({
|
|
1118
|
-
...hookSession,
|
|
1119
|
-
hook_event_name: 'UserPromptSubmit',
|
|
1120
|
-
prompt_id: options.promptId,
|
|
1121
|
-
prompt: options.input.prompt,
|
|
1122
|
-
}, undefined, options.signal);
|
|
1123
|
-
await recordHookOutcome(prompt);
|
|
1124
|
-
if (prompt.blockedReason) {
|
|
1125
|
-
throw new Error(`UserPromptSubmit hook error: ${prompt.blockedReason}`);
|
|
1313
|
+
throw new Error(`SubagentStart hook error: ${start.blockedReason}`);
|
|
1126
1314
|
}
|
|
1127
1315
|
}
|
|
1128
|
-
const customAgent = this.options.extensions?.agent(options.input.subagentType);
|
|
1129
1316
|
const baseSystem = options.input.subagentType === 'general-purpose'
|
|
1130
1317
|
? 'You are a general-purpose subagent. Complete the isolated task and return a concise result.'
|
|
1131
1318
|
: options.input.subagentType === 'workflow-subagent'
|
|
1132
1319
|
? 'You are a workflow subagent. Complete the isolated task. Your final text is the raw return value consumed by the workflow, not a user-facing message.'
|
|
1133
1320
|
: `# Agent definition: ${options.input.subagentType}\n\n${customAgent?.body ?? ''}`;
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1321
|
+
const memoryPrompt = await agentMemoryPrompt(this.options.configRoot, cwd, customAgent);
|
|
1322
|
+
const agentSystem = memoryPrompt
|
|
1323
|
+
? `${baseSystem}\n\n${memoryPrompt}`
|
|
1136
1324
|
: baseSystem;
|
|
1325
|
+
const system = options.outputSchema
|
|
1326
|
+
? `${agentSystem}\n\nYou MUST call StructuredOutput exactly once at the end with a value matching its schema.`
|
|
1327
|
+
: agentSystem;
|
|
1328
|
+
const preloadedSkills = agentSkillMessages(this.options.extensions, customAgent);
|
|
1137
1329
|
let stopHookActive = false;
|
|
1138
1330
|
const contextBudget = options.provider.capabilities.contextWindowTokens
|
|
1139
1331
|
? new ContextBudget({
|
|
@@ -1154,17 +1346,20 @@ export class ClaudeSubagentExecutor {
|
|
|
1154
1346
|
...(assembledContext?.systemMessages ?? []),
|
|
1155
1347
|
{ role: 'system', content: system },
|
|
1156
1348
|
...injectFirstUserMessageContext(projectClaudeModelMessages(snapshot.entries), assembledContext?.firstUserMessageContext),
|
|
1349
|
+
...preloadedSkills,
|
|
1157
1350
|
];
|
|
1158
1351
|
if (contextBudget) {
|
|
1159
1352
|
contextBudget.assertFits(contextBudget.evaluate(messages, definitions));
|
|
1160
1353
|
}
|
|
1161
1354
|
return messages;
|
|
1162
1355
|
};
|
|
1356
|
+
const configuredEffort = typeof customAgent?.effort === 'string' ? customAgent.effort : undefined;
|
|
1357
|
+
const effectiveEffort = options.effort ?? configuredEffort;
|
|
1163
1358
|
const result = await runtime.run({
|
|
1164
1359
|
messages: await assembleMessages(),
|
|
1165
1360
|
reloadMessages: assembleMessages,
|
|
1166
1361
|
cwd,
|
|
1167
|
-
...(
|
|
1362
|
+
...(effectiveEffort ? { effort: effectiveEffort } : {}),
|
|
1168
1363
|
toolResultDirectory: options.toolResultDirectory,
|
|
1169
1364
|
observer,
|
|
1170
1365
|
...(this.options.approveTool
|
|
@@ -1174,21 +1369,24 @@ export class ClaudeSubagentExecutor {
|
|
|
1174
1369
|
...(this.options.onPermissionUpdates
|
|
1175
1370
|
? { onPermissionUpdates: this.options.onPermissionUpdates }
|
|
1176
1371
|
: {}),
|
|
1177
|
-
...(
|
|
1372
|
+
...(scopedHooks || this.options.backgroundTaskNotifications
|
|
1178
1373
|
? {
|
|
1179
1374
|
onStop: async (text) => {
|
|
1180
1375
|
const messages = [];
|
|
1181
|
-
const outcome = await
|
|
1376
|
+
const outcome = await scopedHooks?.run({
|
|
1182
1377
|
...hookSession,
|
|
1183
|
-
hook_event_name: '
|
|
1378
|
+
hook_event_name: 'SubagentStop',
|
|
1184
1379
|
stop_hook_active: stopHookActive,
|
|
1380
|
+
agent_id: options.agentId,
|
|
1381
|
+
agent_transcript_path: options.transcriptPath,
|
|
1382
|
+
agent_type: options.input.subagentType,
|
|
1185
1383
|
last_assistant_message: text,
|
|
1186
|
-
},
|
|
1384
|
+
}, options.input.subagentType, options.signal);
|
|
1187
1385
|
if (outcome) {
|
|
1188
1386
|
await recordHookOutcome(outcome);
|
|
1189
1387
|
if (outcome.blockedReason) {
|
|
1190
1388
|
stopHookActive = true;
|
|
1191
|
-
messages.push(`
|
|
1389
|
+
messages.push(`SubagentStop hook error: ${outcome.blockedReason}`);
|
|
1192
1390
|
}
|
|
1193
1391
|
}
|
|
1194
1392
|
const background = await this.background.notifications({
|
|
@@ -1251,25 +1449,12 @@ export class ClaudeSubagentExecutor {
|
|
|
1251
1449
|
throw error;
|
|
1252
1450
|
}
|
|
1253
1451
|
finally {
|
|
1254
|
-
|
|
1255
|
-
const outcome = await this.options.hooks?.run({
|
|
1256
|
-
...hookSession,
|
|
1257
|
-
hook_event_name: 'SessionEnd',
|
|
1258
|
-
reason: 'other',
|
|
1259
|
-
}, 'other');
|
|
1260
|
-
for (const execution of outcome?.executions.filter((value) => value.exitCode !== 0) ?? []) {
|
|
1261
|
-
this.options.eventSink?.({
|
|
1262
|
-
type: 'warning',
|
|
1263
|
-
message: `Subagent SessionEnd hook failed: ${execution.stderr.trim() || execution.stdout.trim() || `exit code ${execution.exitCode}`}`,
|
|
1264
|
-
});
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
catch (error) {
|
|
1452
|
+
await agentMcp?.close().catch((error) => {
|
|
1268
1453
|
this.options.eventSink?.({
|
|
1269
1454
|
type: 'warning',
|
|
1270
|
-
message: `
|
|
1455
|
+
message: `Agent MCP cleanup failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1271
1456
|
});
|
|
1272
|
-
}
|
|
1457
|
+
});
|
|
1273
1458
|
}
|
|
1274
1459
|
}
|
|
1275
1460
|
}
|
|
@@ -8,6 +8,8 @@ import { type TuiMemoryFiles } from './tui/memory-files.js';
|
|
|
8
8
|
import { type TuiDiffSnapshot } from './tui/git-diff.js';
|
|
9
9
|
import { type TuiPermissionBehavior, type TuiPermissionRule } from './tui/permission-settings.js';
|
|
10
10
|
import { type RecentlyDeniedStore } from './tui/recently-denied.js';
|
|
11
|
+
import { type AgentColorSelection } from '../compatibility/claude/agent-color.js';
|
|
12
|
+
import type { AgentColorName } from '../compatibility/claude/agent-color.js';
|
|
11
13
|
import type { ClaudeResourceScope } from '../compatibility/claude/shared-resources.js';
|
|
12
14
|
import type { TuiHookConfiguration } from './tui/hook-settings.js';
|
|
13
15
|
import { type TuiSlashCommand } from './tui/slash-commands.js';
|
|
@@ -41,6 +43,8 @@ interface InteractiveSessionCommands {
|
|
|
41
43
|
retryRecentlyDenied?(sessionId: string, display: string, signal?: AbortSignal): Promise<SessionRunResult>;
|
|
42
44
|
answerSideQuestion?(sessionId: string | undefined, question: string, signal?: AbortSignal, onDelta?: (delta: string) => void, permissionMode?: ClaudePermissionMode): Promise<SideQuestionResult>;
|
|
43
45
|
recordBtwUsage?(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
46
|
+
recordColorUsage?(sessionId: string | undefined, selection: AgentColorSelection, display: string, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
47
|
+
agentColor?(sessionId: string): Promise<AgentColorName | undefined>;
|
|
44
48
|
recordBackgroundUsage?(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
45
49
|
recordBackgroundLaunch?(sessionId: string): Promise<SessionForkCheckpoint>;
|
|
46
50
|
forkSideQuestion?(sessionId: string, question: string, signal?: AbortSignal): Promise<SideQuestionForkResult>;
|
|
@@ -58,6 +62,7 @@ interface InteractiveSessionCommands {
|
|
|
58
62
|
slashCommands?(): readonly TuiSlashCommand[];
|
|
59
63
|
agentDefinitions?(): readonly TuiAgentEntry[];
|
|
60
64
|
runtimeInfo?(): CliRuntimeInfo;
|
|
65
|
+
initialAgentPrompt?(): string | undefined;
|
|
61
66
|
setPermissionMode?(sessionId: string, mode: ClaudePermissionMode): Promise<void>;
|
|
62
67
|
nextScheduledPrompt?(signal?: AbortSignal): Promise<{
|
|
63
68
|
id: string;
|
|
@@ -110,6 +115,7 @@ interface InteractiveAppProps {
|
|
|
110
115
|
initialSessions: readonly SessionSummary[];
|
|
111
116
|
initialPrompt?: string;
|
|
112
117
|
initialHistory?: readonly TranscriptItem[];
|
|
118
|
+
initialSessionColor?: AgentColorName;
|
|
113
119
|
signal?: AbortSignal;
|
|
114
120
|
onCancel?: () => void;
|
|
115
121
|
onTurnChange?: (turn: Promise<void> | null) => void;
|
|
@@ -178,7 +184,7 @@ interface InteractiveAppProps {
|
|
|
178
184
|
releaseNotesLoader?: (configRoot: string) => Promise<string>;
|
|
179
185
|
settingSources?: readonly ClaudeResourceScope[];
|
|
180
186
|
}
|
|
181
|
-
export declare function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
|
|
187
|
+
export declare function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory, initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
|
|
182
188
|
export declare function runInteractive(options: {
|
|
183
189
|
factory: InteractiveServiceFactory;
|
|
184
190
|
initialPrompt?: string;
|