praxis-agent 0.33.0 → 0.35.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 +4 -0
- package/dist/application/background-agent-manager.d.ts +48 -3
- package/dist/application/background-agent-manager.js +249 -73
- package/dist/application/background-task-runtime.d.ts +20 -1
- package/dist/application/background-task-runtime.js +45 -6
- package/dist/application/managed-worktree.d.ts +8 -0
- package/dist/application/managed-worktree.js +54 -2
- package/dist/application/session-service.d.ts +12 -1
- package/dist/application/session-service.js +1849 -1628
- package/dist/application/subagent-service.d.ts +34 -7
- package/dist/application/subagent-service.js +720 -150
- package/dist/application/turn-lifecycle.d.ts +38 -0
- package/dist/application/turn-lifecycle.js +36 -0
- package/dist/cli/interactive.d.ts +1 -0
- package/dist/cli/interactive.js +43 -8
- package/dist/cli/protocol.js +5 -2
- package/dist/cli/tui/claude-style.d.ts +5 -48
- package/dist/cli/tui/claude-style.js +19 -73
- package/dist/cli/tui/task-panel.d.ts +1 -1
- package/dist/cli/tui/task-panel.js +2 -1
- package/dist/cli/tui/transcript-presentation.d.ts +89 -0
- package/dist/cli/tui/transcript-presentation.js +169 -0
- package/dist/cli/tui/transcript-viewport.d.ts +5 -34
- package/dist/cli/tui/transcript-viewport.js +639 -158
- package/dist/cli/tui/tui-view-model.d.ts +6 -2
- package/dist/cli/tui/tui-view-model.js +23 -6
- package/dist/compatibility/claude/context.d.ts +2 -2
- package/dist/compatibility/claude/context.js +2 -14
- package/dist/compatibility/claude/data-plane-adapter.d.ts +9 -0
- package/dist/compatibility/claude/data-plane-adapter.js +44 -0
- package/dist/compatibility/claude/paths.js +2 -2
- package/dist/compatibility/claude/projection.d.ts +4 -0
- package/dist/compatibility/claude/projection.js +82 -0
- package/dist/compatibility/claude/shared-resources.d.ts +7 -38
- package/dist/compatibility/claude/sidechain.d.ts +5 -0
- package/dist/compatibility/claude/sidechain.js +13 -4
- package/dist/compatibility/claude/transcript-codec.d.ts +21 -0
- package/dist/compatibility/claude/transcript-codec.js +512 -0
- package/dist/core/context.d.ts +25 -7
- package/dist/core/context.js +46 -0
- package/dist/core/prompt-composer.d.ts +8 -16
- package/dist/core/prompt-composer.js +65 -25
- package/dist/core/resources.d.ts +39 -0
- package/dist/core/resources.js +2 -0
- package/dist/core/runtime.d.ts +1 -1
- package/dist/core/session.d.ts +2 -0
- package/dist/core/session.js +5 -0
- package/dist/core/transcript-codec.d.ts +70 -0
- package/dist/core/transcript-codec.js +60 -0
- package/dist/core/transcript-event.d.ts +33 -0
- package/dist/core/transcript-event.js +198 -0
- package/dist/persistence/claude-sidechain-store.js +19 -3
- package/dist/persistence/data-plane-adapter.d.ts +32 -0
- package/dist/persistence/data-plane-adapter.js +2 -0
- package/dist/persistence/data-plane.d.ts +4 -20
- package/dist/persistence/data-plane.js +10 -47
- package/dist/persistence/native-data-plane-adapter.d.ts +9 -0
- package/dist/persistence/native-data-plane-adapter.js +39 -0
- package/dist/persistence/native-resources.d.ts +4 -4
- package/dist/persistence/native-transcript-codec.d.ts +13 -0
- package/dist/persistence/native-transcript-codec.js +102 -0
- package/dist/persistence/subagent-lifecycle-store.d.ts +66 -0
- package/dist/persistence/subagent-lifecycle-store.js +299 -0
- package/dist/tools/claude-user-message.d.ts +0 -1
- package/dist/tools/claude-user-message.js +0 -1
- package/package.json +6 -2
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
2
|
import { lstat, mkdir, readFile, readdir } from 'node:fs/promises';
|
|
3
|
-
import { join, relative } from 'node:path';
|
|
3
|
+
import { isAbsolute, join, relative } from 'node:path';
|
|
4
4
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
5
5
|
import { resolveClaudePaths } from '../compatibility/claude/paths.js';
|
|
6
6
|
import { workflowAgentFiles } from '../compatibility/claude/workflow.js';
|
|
7
|
-
import { createClaudeAsyncAgentToolUseResult, createClaudeAgentToolUseResult, createClaudeSidechainRoot, resolveClaudeSidechainPaths, toClaudeSidechainEntry, } from '../compatibility/claude/sidechain.js';
|
|
8
|
-
import { projectClaudeModelMessages } from '../compatibility/claude/projection.js';
|
|
7
|
+
import { createClaudeAsyncAgentToolUseResult, createClaudeAgentToolUseResult, createClaudeSidechainRoot, isClaudeAgentId, resolveClaudeSidechainPaths, toClaudeSidechainEntry, } from '../compatibility/claude/sidechain.js';
|
|
8
|
+
import { projectClaudeModelMessages, projectClaudeSidechainContinuationMessages, } from '../compatibility/claude/projection.js';
|
|
9
9
|
import { selectClaudeSchemaAdapter, } from '../compatibility/claude/schema.js';
|
|
10
10
|
import { createClaudeHookAttachmentEntries, translateProviderEvents, } from '../compatibility/claude/translation.js';
|
|
11
|
-
import { injectFirstUserMessageContext, } from '../core/context.js';
|
|
11
|
+
import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/context.js';
|
|
12
|
+
import { assembleContextSnapshot } from '../core/prompt-composer.js';
|
|
12
13
|
import { ContextBudget } from '../core/context-budget.js';
|
|
13
14
|
import { AgentRuntime, } from '../core/runtime.js';
|
|
14
15
|
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
@@ -16,21 +17,105 @@ import { BUILTIN_STATUSLINE_AGENT_PATH, } from '../extensions/claude-extensions.
|
|
|
16
17
|
import { ClaudeHookToolCoordinator } from '../hooks/claude-hook-tools.js';
|
|
17
18
|
import { ClaudeSidechainStore } from '../persistence/claude-sidechain-store.js';
|
|
18
19
|
import { InMemorySidechainStore } from '../persistence/in-memory-sidechain-store.js';
|
|
20
|
+
import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.js';
|
|
19
21
|
import { resolveDataPlanePaths, } from '../persistence/data-plane.js';
|
|
20
|
-
import { BackgroundAgentManager, BackgroundAgentRunError, } from './background-agent-manager.js';
|
|
22
|
+
import { BackgroundAgentManager, BackgroundAgentRunError, BackgroundAgentShutdownError, backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
|
|
21
23
|
import { isBackgroundBashTaskId } from './background-task-id.js';
|
|
22
|
-
import { createManagedWorktree, } from './managed-worktree.js';
|
|
24
|
+
import { createManagedWorktree, restoreManagedWorktree, } from './managed-worktree.js';
|
|
23
25
|
import { createWorkflowWorktree } from './workflow-worktree.js';
|
|
24
26
|
const DEFAULT_MAX_DEPTH = 4;
|
|
25
27
|
const DEFAULT_MAX_CALLS = 16;
|
|
26
28
|
const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
27
29
|
const SIDECHAIN_DISCOVERY_MAX_DEPTH = 4;
|
|
30
|
+
function mergeSubagentUsage(left, right) {
|
|
31
|
+
for (const usage of [left, right]) {
|
|
32
|
+
for (const field of [
|
|
33
|
+
'inputTokens',
|
|
34
|
+
'outputTokens',
|
|
35
|
+
'cacheReadInputTokens',
|
|
36
|
+
'cacheCreationInputTokens',
|
|
37
|
+
'webSearchRequests',
|
|
38
|
+
]) {
|
|
39
|
+
const value = usage[field];
|
|
40
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
41
|
+
throw new Error(`Subagent usage has an invalid ${field}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const field of ['contextWindow', 'maxOutputTokens']) {
|
|
45
|
+
const value = usage[field];
|
|
46
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {
|
|
47
|
+
throw new Error(`Subagent usage has an invalid ${field}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const counters = {
|
|
52
|
+
inputTokens: left.inputTokens + right.inputTokens,
|
|
53
|
+
outputTokens: left.outputTokens + right.outputTokens,
|
|
54
|
+
cacheReadInputTokens: (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0),
|
|
55
|
+
cacheCreationInputTokens: (left.cacheCreationInputTokens ?? 0) +
|
|
56
|
+
(right.cacheCreationInputTokens ?? 0),
|
|
57
|
+
webSearchRequests: (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0),
|
|
58
|
+
};
|
|
59
|
+
if (Object.values(counters).some((value) => !Number.isSafeInteger(value))) {
|
|
60
|
+
throw new Error('Subagent usage total overflow');
|
|
61
|
+
}
|
|
62
|
+
const metadata = (field) => {
|
|
63
|
+
const leftValue = left[field];
|
|
64
|
+
const rightValue = right[field];
|
|
65
|
+
if (leftValue !== undefined &&
|
|
66
|
+
rightValue !== undefined &&
|
|
67
|
+
leftValue !== rightValue) {
|
|
68
|
+
throw new Error(`Subagent usage has conflicting ${field}`);
|
|
69
|
+
}
|
|
70
|
+
return leftValue ?? rightValue;
|
|
71
|
+
};
|
|
72
|
+
const contextWindow = metadata('contextWindow');
|
|
73
|
+
const maxOutputTokens = metadata('maxOutputTokens');
|
|
74
|
+
return {
|
|
75
|
+
inputTokens: counters.inputTokens,
|
|
76
|
+
outputTokens: counters.outputTokens,
|
|
77
|
+
...(counters.cacheReadInputTokens === 0
|
|
78
|
+
? {}
|
|
79
|
+
: { cacheReadInputTokens: counters.cacheReadInputTokens }),
|
|
80
|
+
...(counters.cacheCreationInputTokens === 0
|
|
81
|
+
? {}
|
|
82
|
+
: { cacheCreationInputTokens: counters.cacheCreationInputTokens }),
|
|
83
|
+
...(counters.webSearchRequests === 0
|
|
84
|
+
? {}
|
|
85
|
+
: { webSearchRequests: counters.webSearchRequests }),
|
|
86
|
+
...(contextWindow === undefined ? {} : { contextWindow }),
|
|
87
|
+
...(maxOutputTokens === undefined ? {} : { maxOutputTokens }),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function mergeSubagentModelUsage(target, source) {
|
|
91
|
+
for (const [model, usage] of Object.entries(source)) {
|
|
92
|
+
if (model.trim().length === 0) {
|
|
93
|
+
throw new Error('Subagent model usage has a blank model name');
|
|
94
|
+
}
|
|
95
|
+
target.set(model, mergeSubagentUsage(target.get(model) ?? { inputTokens: 0, outputTokens: 0 }, usage));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
class SubagentExecutionFailure extends Error {
|
|
99
|
+
result;
|
|
100
|
+
name = 'SubagentExecutionFailure';
|
|
101
|
+
constructor(cause, result) {
|
|
102
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
103
|
+
this.result = result;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
28
106
|
const structuredOnlyTools = {
|
|
29
107
|
definitions: () => [],
|
|
30
108
|
schedulingPolicy: () => ({ concurrency: 'exclusive' }),
|
|
31
109
|
prepare: async (call) => call,
|
|
32
110
|
execute: async () => ({ content: '', isError: false }),
|
|
33
111
|
};
|
|
112
|
+
function sidechainAgentIdFromFile(fileName, suffix) {
|
|
113
|
+
if (!fileName.startsWith('agent-') || !fileName.endsWith(suffix)) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const agentId = fileName.slice('agent-'.length, -suffix.length);
|
|
117
|
+
return isClaudeAgentId(agentId) ? agentId : undefined;
|
|
118
|
+
}
|
|
34
119
|
async function readSidechainMetadataName(metadataFile) {
|
|
35
120
|
try {
|
|
36
121
|
const value = JSON.parse(await readFile(metadataFile, 'utf8'));
|
|
@@ -368,6 +453,7 @@ export class ClaudeSubagentExecutor {
|
|
|
368
453
|
calls = 0;
|
|
369
454
|
background = new BackgroundAgentManager();
|
|
370
455
|
ephemeralSidechains = new Map();
|
|
456
|
+
foreground = new Map();
|
|
371
457
|
isEnabled(name) {
|
|
372
458
|
return this.options.toolNames?.includes(name) ?? true;
|
|
373
459
|
}
|
|
@@ -376,14 +462,55 @@ export class ClaudeSubagentExecutor {
|
|
|
376
462
|
this.schema = selectClaudeSchemaAdapter(options.claudeVersion);
|
|
377
463
|
}
|
|
378
464
|
async close() {
|
|
465
|
+
for (const agentId of [...this.foreground.keys()]) {
|
|
466
|
+
this.backgroundForegroundTask(agentId);
|
|
467
|
+
}
|
|
379
468
|
await this.background.close();
|
|
469
|
+
this.foreground.clear();
|
|
380
470
|
this.ephemeralSidechains.clear();
|
|
381
471
|
}
|
|
382
472
|
backgroundSnapshots() {
|
|
383
473
|
return this.background.snapshots();
|
|
384
474
|
}
|
|
475
|
+
hasForegroundTask() {
|
|
476
|
+
return this.foreground.size > 0;
|
|
477
|
+
}
|
|
478
|
+
backgroundForegroundTask(identifier) {
|
|
479
|
+
const candidates = [...this.foreground.entries()];
|
|
480
|
+
const selected = identifier === undefined
|
|
481
|
+
? candidates.at(-1)
|
|
482
|
+
: candidates.find(([agentId, task]) => agentId === identifier || task.spec.name === identifier);
|
|
483
|
+
if (!selected) {
|
|
484
|
+
throw new Error(identifier === undefined
|
|
485
|
+
? 'No foreground agent is running'
|
|
486
|
+
: `No foreground agent found with ID or name: ${identifier}`);
|
|
487
|
+
}
|
|
488
|
+
const [, task] = selected;
|
|
489
|
+
if (task.backgrounded) {
|
|
490
|
+
throw new Error(`Agent ${task.spec.agentId} is already backgrounded`);
|
|
491
|
+
}
|
|
492
|
+
const snapshot = this.background.adopt({
|
|
493
|
+
spec: task.spec,
|
|
494
|
+
controller: task.controller,
|
|
495
|
+
operation: task.operation,
|
|
496
|
+
startedAt: task.startedAt,
|
|
497
|
+
});
|
|
498
|
+
task.backgrounded = true;
|
|
499
|
+
task.detachParentSignal();
|
|
500
|
+
task.resolveHandoff();
|
|
501
|
+
return snapshot;
|
|
502
|
+
}
|
|
385
503
|
stopBackgroundTask(taskId) {
|
|
386
|
-
return this.background.
|
|
504
|
+
return this.background.stopAndWait(taskId);
|
|
505
|
+
}
|
|
506
|
+
outputBackgroundTask(taskId, options) {
|
|
507
|
+
return this.background.output(taskId, options);
|
|
508
|
+
}
|
|
509
|
+
sendBackgroundMessage(agentId, message, summary, toolUseId) {
|
|
510
|
+
return this.background.send(agentId, message, summary, toolUseId);
|
|
511
|
+
}
|
|
512
|
+
stopAllBackgroundTasks() {
|
|
513
|
+
return this.background.stopAll();
|
|
387
514
|
}
|
|
388
515
|
cwd() {
|
|
389
516
|
return this.options.cwdProvider?.() ?? this.options.cwd;
|
|
@@ -398,7 +525,7 @@ export class ClaudeSubagentExecutor {
|
|
|
398
525
|
? definition.model
|
|
399
526
|
: undefined;
|
|
400
527
|
const model = environmentModel || input.model || configuredModel;
|
|
401
|
-
const parentPermissionMode = this.options.parentPermissionMode?.();
|
|
528
|
+
const parentPermissionMode = this.options.parentPermissionMode?.() ?? 'default';
|
|
402
529
|
const definitionCanOverridePermissionMode = ![
|
|
403
530
|
'acceptEdits',
|
|
404
531
|
'auto',
|
|
@@ -406,7 +533,7 @@ export class ClaudeSubagentExecutor {
|
|
|
406
533
|
].includes(parentPermissionMode ?? 'default');
|
|
407
534
|
const permissionMode = input.permissionMode ??
|
|
408
535
|
(definitionCanOverridePermissionMode
|
|
409
|
-
? definition?.permissionMode
|
|
536
|
+
? (definition?.permissionMode ?? parentPermissionMode)
|
|
410
537
|
: parentPermissionMode);
|
|
411
538
|
const isolation = input.isolation ?? definition?.isolation;
|
|
412
539
|
return {
|
|
@@ -417,8 +544,8 @@ export class ClaudeSubagentExecutor {
|
|
|
417
544
|
runInBackground: input.runInBackground || definition?.background === true,
|
|
418
545
|
};
|
|
419
546
|
}
|
|
420
|
-
registry(sessionId, depth, promptIdForCall) {
|
|
421
|
-
return new ClaudeSubagentToolRegistry(this.options.baseTools, this, sessionId, depth, promptIdForCall);
|
|
547
|
+
registry(sessionId, depth, promptIdForCall, parentAgentId) {
|
|
548
|
+
return new ClaudeSubagentToolRegistry(this.options.baseTools, this, sessionId, depth, promptIdForCall, parentAgentId);
|
|
422
549
|
}
|
|
423
550
|
definitions() {
|
|
424
551
|
return {
|
|
@@ -558,7 +685,9 @@ export class ClaudeSubagentExecutor {
|
|
|
558
685
|
if (input.model && !this.options.providerForModel) {
|
|
559
686
|
throw new Error('Agent model overrides are unavailable for this provider');
|
|
560
687
|
}
|
|
561
|
-
if (input.permissionMode &&
|
|
688
|
+
if (input.permissionMode &&
|
|
689
|
+
input.permissionMode !== 'default' &&
|
|
690
|
+
!this.options.permissionResolverForMode) {
|
|
562
691
|
throw new Error('Agent permission mode overrides are unavailable');
|
|
563
692
|
}
|
|
564
693
|
return {
|
|
@@ -576,7 +705,7 @@ export class ClaudeSubagentExecutor {
|
|
|
576
705
|
},
|
|
577
706
|
};
|
|
578
707
|
}
|
|
579
|
-
async execute(call, sessionId, depth, promptId, context) {
|
|
708
|
+
async execute(call, sessionId, depth, promptId, context, parentAgentId) {
|
|
580
709
|
this.calls += 1;
|
|
581
710
|
const maxCalls = this.options.maxCalls ?? DEFAULT_MAX_CALLS;
|
|
582
711
|
if (this.calls > maxCalls) {
|
|
@@ -585,11 +714,12 @@ export class ClaudeSubagentExecutor {
|
|
|
585
714
|
const input = this.resolveAgentInput(parseAgentInput(call));
|
|
586
715
|
const spawnDepth = depth + 1;
|
|
587
716
|
const agentId = `a${randomBytes(8).toString('hex')}`;
|
|
588
|
-
const
|
|
717
|
+
const parentCwd = context.cwd;
|
|
718
|
+
const paths = this.sessionPaths(sessionId, parentCwd);
|
|
589
719
|
const initialIsolation = input.isolation
|
|
590
|
-
? await this.createAgentWorktree(paths.praxisRoot, sessionId, agentId)
|
|
720
|
+
? await this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd)
|
|
591
721
|
: undefined;
|
|
592
|
-
const agentCwd = initialIsolation?.cwd ??
|
|
722
|
+
const agentCwd = initialIsolation?.cwd ?? parentCwd;
|
|
593
723
|
const sidechainPaths = resolveClaudeSidechainPaths(paths.projectRoot, sessionId, agentId);
|
|
594
724
|
const sidechain = this.sidechainStore(sessionId, agentId, sidechainPaths, paths.praxisRoot);
|
|
595
725
|
const root = createClaudeSidechainRoot({
|
|
@@ -614,6 +744,8 @@ export class ClaudeSubagentExecutor {
|
|
|
614
744
|
? { permissionMode: input.permissionMode }
|
|
615
745
|
: {}),
|
|
616
746
|
...(input.isolation ? { isolation: input.isolation } : {}),
|
|
747
|
+
...(parentAgentId ? { parentAgentId } : {}),
|
|
748
|
+
...(initialIsolation ? { worktreePath: initialIsolation.cwd } : {}),
|
|
617
749
|
});
|
|
618
750
|
}
|
|
619
751
|
catch (error) {
|
|
@@ -623,10 +755,15 @@ export class ClaudeSubagentExecutor {
|
|
|
623
755
|
const provider = input.model
|
|
624
756
|
? (this.options.providerForModel?.(input.model) ?? this.options.provider)
|
|
625
757
|
: this.options.provider;
|
|
626
|
-
const
|
|
758
|
+
const lifecycleStore = this.options.persistence === 'memory'
|
|
759
|
+
? undefined
|
|
760
|
+
: new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId, sidechainPaths.transcriptFile);
|
|
761
|
+
const backgroundRun = this.createBackgroundAgentRun({
|
|
627
762
|
input,
|
|
763
|
+
parentCwd,
|
|
764
|
+
...(lifecycleStore ? { lifecycle: lifecycleStore } : {}),
|
|
628
765
|
...(initialIsolation ? { initialIsolation } : {}),
|
|
629
|
-
createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId),
|
|
766
|
+
createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd),
|
|
630
767
|
execute: (cwd, message, signal, continuation) => sidechain.withLease(async (lease) => this.runSidechain({
|
|
631
768
|
lease,
|
|
632
769
|
root,
|
|
@@ -643,48 +780,82 @@ export class ClaudeSubagentExecutor {
|
|
|
643
780
|
cwd,
|
|
644
781
|
})),
|
|
645
782
|
});
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
783
|
+
const resolvedModel = provider.model ?? 'praxis/provider';
|
|
784
|
+
const spec = {
|
|
785
|
+
agentId,
|
|
786
|
+
...(input.name ? { name: input.name } : {}),
|
|
787
|
+
agentType: input.subagentType,
|
|
788
|
+
description: input.description,
|
|
789
|
+
prompt: input.prompt,
|
|
790
|
+
toolUseId: call.id,
|
|
791
|
+
outputFile: sidechainPaths.transcriptFile,
|
|
792
|
+
resolvedModel,
|
|
793
|
+
run: backgroundRun.run,
|
|
794
|
+
markBackground: backgroundRun.markBackground,
|
|
795
|
+
...(lifecycleStore
|
|
796
|
+
? {
|
|
797
|
+
acknowledgeNotification: (notificationId) => lifecycleStore.acknowledgeNotification(notificationId),
|
|
798
|
+
prepareNotificationDetached: (notificationId, model) => lifecycleStore.prepareNotificationDetached(notificationId, model),
|
|
799
|
+
confirmNotificationDetached: (notificationId) => lifecycleStore.confirmNotificationDetached(notificationId),
|
|
800
|
+
}
|
|
801
|
+
: {}),
|
|
802
|
+
};
|
|
803
|
+
const asyncResult = () => ({
|
|
804
|
+
content: this.asyncLaunchResult({
|
|
649
805
|
agentId,
|
|
650
|
-
...(input.name ? { name: input.name } : {}),
|
|
651
|
-
agentType: input.subagentType,
|
|
652
806
|
description: input.description,
|
|
653
|
-
prompt: input.prompt,
|
|
654
|
-
toolUseId: call.id,
|
|
655
807
|
outputFile: sidechainPaths.transcriptFile,
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
808
|
+
...(initialIsolation ? { worktreePath: initialIsolation.cwd } : {}),
|
|
809
|
+
}),
|
|
810
|
+
isError: false,
|
|
811
|
+
nativeToolUseResult: {
|
|
812
|
+
...createClaudeAsyncAgentToolUseResult({
|
|
813
|
+
prompt: input.prompt,
|
|
661
814
|
agentId,
|
|
662
815
|
description: input.description,
|
|
816
|
+
resolvedModel,
|
|
663
817
|
outputFile: sidechainPaths.transcriptFile,
|
|
664
|
-
...(initialIsolation ? { worktreePath: initialIsolation.cwd } : {}),
|
|
665
818
|
}),
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
resolvedModel,
|
|
673
|
-
outputFile: sidechainPaths.transcriptFile,
|
|
674
|
-
}),
|
|
675
|
-
...(initialIsolation ? { worktreePath: initialIsolation.cwd } : {}),
|
|
676
|
-
},
|
|
677
|
-
};
|
|
819
|
+
...(initialIsolation ? { worktreePath: initialIsolation.cwd } : {}),
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
if (input.runInBackground) {
|
|
823
|
+
this.background.launch(spec);
|
|
824
|
+
return asyncResult();
|
|
678
825
|
}
|
|
679
826
|
const controller = new AbortController();
|
|
680
|
-
const abort = () => controller.abort();
|
|
827
|
+
const abort = () => controller.abort(context.signal?.reason);
|
|
681
828
|
context.signal?.addEventListener('abort', abort, { once: true });
|
|
829
|
+
if (context.signal?.aborted)
|
|
830
|
+
abort();
|
|
831
|
+
const operation = backgroundRun.run(input.prompt, controller.signal, false, call.id);
|
|
832
|
+
let resolveHandoff;
|
|
833
|
+
const handoff = new Promise((resolve) => {
|
|
834
|
+
resolveHandoff = resolve;
|
|
835
|
+
});
|
|
836
|
+
const foreground = {
|
|
837
|
+
spec,
|
|
838
|
+
controller,
|
|
839
|
+
operation,
|
|
840
|
+
startedAt: Date.now(),
|
|
841
|
+
detachParentSignal: () => context.signal?.removeEventListener('abort', abort),
|
|
842
|
+
resolveHandoff,
|
|
843
|
+
backgrounded: false,
|
|
844
|
+
};
|
|
845
|
+
this.foreground.set(agentId, foreground);
|
|
682
846
|
let result;
|
|
683
847
|
try {
|
|
684
|
-
|
|
848
|
+
const outcome = await Promise.race([
|
|
849
|
+
operation.then((value) => ({ kind: 'completed', value })),
|
|
850
|
+
handoff.then(() => ({ kind: 'backgrounded' })),
|
|
851
|
+
]);
|
|
852
|
+
if (outcome.kind === 'backgrounded')
|
|
853
|
+
return asyncResult();
|
|
854
|
+
result = outcome.value;
|
|
685
855
|
}
|
|
686
856
|
finally {
|
|
687
|
-
|
|
857
|
+
foreground.detachParentSignal();
|
|
858
|
+
this.foreground.delete(agentId);
|
|
688
859
|
}
|
|
689
860
|
const maxOutputBytes = this.options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
690
861
|
if (Buffer.byteLength(result.text) > maxOutputBytes) {
|
|
@@ -721,7 +892,7 @@ export class ClaudeSubagentExecutor {
|
|
|
721
892
|
agentId,
|
|
722
893
|
agentType: input.subagentType,
|
|
723
894
|
text: result.text,
|
|
724
|
-
resolvedModel
|
|
895
|
+
resolvedModel,
|
|
725
896
|
durationMs: result.durationMs,
|
|
726
897
|
usage: result.usage,
|
|
727
898
|
toolUseCount: result.toolUseCount,
|
|
@@ -750,23 +921,23 @@ export class ClaudeSubagentExecutor {
|
|
|
750
921
|
}
|
|
751
922
|
return store;
|
|
752
923
|
}
|
|
753
|
-
sessionPaths(sessionId) {
|
|
924
|
+
sessionPaths(sessionId, cwd = this.cwd()) {
|
|
754
925
|
return this.options.dataPlane === 'native'
|
|
755
926
|
? resolveDataPlanePaths({
|
|
756
927
|
dataPlane: 'native',
|
|
757
928
|
root: this.options.configRoot,
|
|
758
|
-
cwd
|
|
929
|
+
cwd,
|
|
759
930
|
sessionId,
|
|
760
931
|
})
|
|
761
932
|
: resolveClaudePaths({
|
|
762
933
|
configDir: this.options.configRoot,
|
|
763
|
-
cwd
|
|
934
|
+
cwd,
|
|
764
935
|
sessionId,
|
|
765
936
|
});
|
|
766
937
|
}
|
|
767
|
-
createAgentWorktree(praxisRoot, sessionId, agentId) {
|
|
938
|
+
createAgentWorktree(praxisRoot, sessionId, agentId, cwd = this.cwd()) {
|
|
768
939
|
return createManagedWorktree({
|
|
769
|
-
cwd
|
|
940
|
+
cwd,
|
|
770
941
|
parentDirectory: join(praxisRoot, 'agent-worktrees'),
|
|
771
942
|
directoryName: `${sessionId}-${agentId}`,
|
|
772
943
|
label: 'Agent',
|
|
@@ -775,55 +946,184 @@ export class ClaudeSubagentExecutor {
|
|
|
775
946
|
createBackgroundAgentRun(options) {
|
|
776
947
|
let availableIsolation = options.initialIsolation;
|
|
777
948
|
let retainedIsolation;
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
availableIsolation ??
|
|
782
|
-
(await options.createIsolation()))
|
|
783
|
-
: undefined;
|
|
784
|
-
availableIsolation = undefined;
|
|
949
|
+
let notificationExpected = options.input.runInBackground;
|
|
950
|
+
const run = async (message, signal, continuation, currentToolUseId) => {
|
|
951
|
+
await options.lifecycle?.write('running');
|
|
785
952
|
const startedAt = Date.now();
|
|
953
|
+
let isolation;
|
|
954
|
+
try {
|
|
955
|
+
isolation = options.input.isolation
|
|
956
|
+
? (retainedIsolation ??
|
|
957
|
+
availableIsolation ??
|
|
958
|
+
(await options.createIsolation()))
|
|
959
|
+
: undefined;
|
|
960
|
+
}
|
|
961
|
+
catch (error) {
|
|
962
|
+
const failureMessage = error instanceof Error ? error.message : String(error);
|
|
963
|
+
const failureResult = {
|
|
964
|
+
text: failureMessage,
|
|
965
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
966
|
+
toolUseCount: 0,
|
|
967
|
+
durationMs: Date.now() - startedAt,
|
|
968
|
+
};
|
|
969
|
+
const status = signal.aborted ? 'killed' : 'failed';
|
|
970
|
+
const notificationId = notificationExpected &&
|
|
971
|
+
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
972
|
+
? randomUUID()
|
|
973
|
+
: undefined;
|
|
974
|
+
await options.lifecycle?.write(status, failureMessage, {
|
|
975
|
+
result: failureResult,
|
|
976
|
+
...(notificationId
|
|
977
|
+
? {
|
|
978
|
+
notification: {
|
|
979
|
+
id: notificationId,
|
|
980
|
+
status,
|
|
981
|
+
toolUseId: currentToolUseId,
|
|
982
|
+
error: failureMessage,
|
|
983
|
+
},
|
|
984
|
+
}
|
|
985
|
+
: {}),
|
|
986
|
+
});
|
|
987
|
+
if (!notificationExpected)
|
|
988
|
+
throw error;
|
|
989
|
+
throw new BackgroundAgentRunError(failureMessage, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, error);
|
|
990
|
+
}
|
|
991
|
+
availableIsolation = undefined;
|
|
786
992
|
let result;
|
|
787
993
|
let failure;
|
|
788
994
|
let cleanup;
|
|
789
995
|
try {
|
|
790
|
-
result = await options.execute(isolation?.cwd ??
|
|
996
|
+
result = await options.execute(isolation?.cwd ?? options.parentCwd, message, signal, continuation);
|
|
791
997
|
}
|
|
792
998
|
catch (error) {
|
|
793
999
|
failure = error;
|
|
794
1000
|
}
|
|
795
1001
|
finally {
|
|
796
|
-
|
|
1002
|
+
try {
|
|
1003
|
+
cleanup = await isolation?.cleanup();
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
failure ??= error;
|
|
1007
|
+
}
|
|
797
1008
|
retainedIsolation = cleanup?.retained ? isolation : undefined;
|
|
798
1009
|
}
|
|
799
1010
|
if (failure !== undefined) {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1011
|
+
const underlyingFailure = failure instanceof SubagentExecutionFailure ? failure.cause : failure;
|
|
1012
|
+
const failureMessage = underlyingFailure instanceof Error
|
|
1013
|
+
? underlyingFailure.message
|
|
1014
|
+
: String(underlyingFailure);
|
|
1015
|
+
const detail = cleanup?.reason
|
|
1016
|
+
? `${failureMessage}\n${cleanup.reason}`
|
|
1017
|
+
: failureMessage;
|
|
1018
|
+
const partialResult = failure instanceof SubagentExecutionFailure
|
|
1019
|
+
? failure.result
|
|
1020
|
+
: undefined;
|
|
1021
|
+
const failureResult = {
|
|
1022
|
+
...partialResult,
|
|
1023
|
+
text: failureMessage,
|
|
1024
|
+
usage: partialResult?.usage ?? {
|
|
1025
|
+
inputTokens: 0,
|
|
1026
|
+
outputTokens: 0,
|
|
1027
|
+
},
|
|
1028
|
+
toolUseCount: partialResult?.toolUseCount ?? 0,
|
|
1029
|
+
durationMs: Date.now() - startedAt,
|
|
1030
|
+
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1031
|
+
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1032
|
+
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1033
|
+
};
|
|
1034
|
+
const status = signal.aborted ? 'killed' : 'failed';
|
|
1035
|
+
const notificationId = notificationExpected &&
|
|
1036
|
+
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1037
|
+
? randomUUID()
|
|
1038
|
+
: undefined;
|
|
1039
|
+
await options.lifecycle?.write(status, detail, {
|
|
1040
|
+
result: failureResult,
|
|
1041
|
+
...(notificationId
|
|
1042
|
+
? {
|
|
1043
|
+
notification: {
|
|
1044
|
+
id: notificationId,
|
|
1045
|
+
status,
|
|
1046
|
+
toolUseId: currentToolUseId,
|
|
1047
|
+
error: detail,
|
|
1048
|
+
},
|
|
1049
|
+
}
|
|
1050
|
+
: {}),
|
|
1051
|
+
});
|
|
1052
|
+
if (!notificationExpected && !isolation && !cleanup?.reason) {
|
|
1053
|
+
throw underlyingFailure;
|
|
815
1054
|
}
|
|
816
|
-
throw
|
|
1055
|
+
throw new BackgroundAgentRunError(detail, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, underlyingFailure);
|
|
817
1056
|
}
|
|
818
|
-
if (!result)
|
|
819
|
-
|
|
820
|
-
|
|
1057
|
+
if (!result) {
|
|
1058
|
+
const error = new Error('Agent completed without a result');
|
|
1059
|
+
const failureResult = {
|
|
1060
|
+
text: error.message,
|
|
1061
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1062
|
+
toolUseCount: 0,
|
|
1063
|
+
durationMs: Date.now() - startedAt,
|
|
1064
|
+
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1065
|
+
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1066
|
+
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1067
|
+
};
|
|
1068
|
+
const notificationId = notificationExpected &&
|
|
1069
|
+
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1070
|
+
? randomUUID()
|
|
1071
|
+
: undefined;
|
|
1072
|
+
await options.lifecycle?.write('failed', error.message, {
|
|
1073
|
+
result: failureResult,
|
|
1074
|
+
...(notificationId
|
|
1075
|
+
? {
|
|
1076
|
+
notification: {
|
|
1077
|
+
id: notificationId,
|
|
1078
|
+
status: 'failed',
|
|
1079
|
+
toolUseId: currentToolUseId,
|
|
1080
|
+
error: error.message,
|
|
1081
|
+
},
|
|
1082
|
+
}
|
|
1083
|
+
: {}),
|
|
1084
|
+
});
|
|
1085
|
+
if (!notificationExpected)
|
|
1086
|
+
throw error;
|
|
1087
|
+
throw new BackgroundAgentRunError(error.message, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, error);
|
|
1088
|
+
}
|
|
1089
|
+
const finalResult = {
|
|
821
1090
|
...result,
|
|
822
1091
|
durationMs: Date.now() - startedAt,
|
|
823
1092
|
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
824
1093
|
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
825
1094
|
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
826
1095
|
};
|
|
1096
|
+
const status = signal.aborted ? 'killed' : 'completed';
|
|
1097
|
+
const detail = signal.aborted
|
|
1098
|
+
? 'Agent was aborted before terminal cleanup'
|
|
1099
|
+
: undefined;
|
|
1100
|
+
const notificationId = notificationExpected &&
|
|
1101
|
+
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1102
|
+
? randomUUID()
|
|
1103
|
+
: undefined;
|
|
1104
|
+
await options.lifecycle?.write(status, detail, {
|
|
1105
|
+
result: finalResult,
|
|
1106
|
+
...(notificationId
|
|
1107
|
+
? {
|
|
1108
|
+
notification: {
|
|
1109
|
+
id: notificationId,
|
|
1110
|
+
status,
|
|
1111
|
+
toolUseId: currentToolUseId,
|
|
1112
|
+
error: status === 'killed' ? (detail ?? null) : null,
|
|
1113
|
+
},
|
|
1114
|
+
}
|
|
1115
|
+
: {}),
|
|
1116
|
+
});
|
|
1117
|
+
return {
|
|
1118
|
+
...finalResult,
|
|
1119
|
+
...(notificationId ? { notificationId } : {}),
|
|
1120
|
+
};
|
|
1121
|
+
};
|
|
1122
|
+
return {
|
|
1123
|
+
run,
|
|
1124
|
+
markBackground: () => {
|
|
1125
|
+
notificationExpected = true;
|
|
1126
|
+
},
|
|
827
1127
|
};
|
|
828
1128
|
}
|
|
829
1129
|
async runWorkflowAgent(options) {
|
|
@@ -976,10 +1276,17 @@ export class ClaudeSubagentExecutor {
|
|
|
976
1276
|
input: { to, message, ...(summary === undefined ? {} : { summary }) },
|
|
977
1277
|
};
|
|
978
1278
|
}
|
|
979
|
-
async executeManagement(call, sessionId) {
|
|
1279
|
+
async executeManagement(call, sessionId, context) {
|
|
980
1280
|
if (call.name === 'TaskOutput') {
|
|
981
1281
|
const taskId = String(call.input.task_id);
|
|
982
|
-
await this.
|
|
1282
|
+
const owned = await this.options.outputOwnedBackgroundAgent?.(sessionId, taskId, {
|
|
1283
|
+
block: Boolean(call.input.block),
|
|
1284
|
+
timeout: Number(call.input.timeout),
|
|
1285
|
+
});
|
|
1286
|
+
if (owned !== undefined && owned !== null) {
|
|
1287
|
+
return { content: owned, isError: false };
|
|
1288
|
+
}
|
|
1289
|
+
await this.hydratePersistedTask(sessionId, taskId, context.cwd);
|
|
983
1290
|
return {
|
|
984
1291
|
content: await this.background.output(taskId, {
|
|
985
1292
|
block: Boolean(call.input.block),
|
|
@@ -990,23 +1297,77 @@ export class ClaudeSubagentExecutor {
|
|
|
990
1297
|
}
|
|
991
1298
|
if (call.name === 'TaskStop') {
|
|
992
1299
|
const taskId = String(call.input.task_id);
|
|
993
|
-
await this.
|
|
994
|
-
|
|
1300
|
+
const owned = await this.options.stopOwnedBackgroundAgent?.(sessionId, taskId);
|
|
1301
|
+
if (owned !== undefined && owned !== null) {
|
|
1302
|
+
return { content: owned, isError: false };
|
|
1303
|
+
}
|
|
1304
|
+
await this.hydratePersistedTask(sessionId, taskId, context.cwd);
|
|
1305
|
+
return {
|
|
1306
|
+
content: await this.background.stopAndWait(taskId),
|
|
1307
|
+
isError: false,
|
|
1308
|
+
};
|
|
995
1309
|
}
|
|
996
1310
|
const agentId = String(call.input.to);
|
|
997
|
-
|
|
1311
|
+
const summary = typeof call.input.summary === 'string' ? call.input.summary : undefined;
|
|
1312
|
+
const owned = this.options.sendOwnedBackgroundAgent?.(sessionId, agentId, String(call.input.message), summary, call.id);
|
|
1313
|
+
if (owned !== undefined && owned !== null) {
|
|
1314
|
+
return { content: owned, isError: false };
|
|
1315
|
+
}
|
|
1316
|
+
await this.hydratePersistedTask(sessionId, agentId, context.cwd);
|
|
998
1317
|
return {
|
|
999
|
-
content: this.background.send(agentId, String(call.input.message),
|
|
1318
|
+
content: this.background.send(agentId, String(call.input.message), summary, call.id),
|
|
1000
1319
|
isError: false,
|
|
1001
1320
|
};
|
|
1002
1321
|
}
|
|
1003
|
-
notifications(waitForRunning) {
|
|
1004
|
-
return this.background.notifications({ waitForRunning });
|
|
1322
|
+
notifications(waitForRunning, consume = true) {
|
|
1323
|
+
return this.background.notifications({ waitForRunning, consume });
|
|
1324
|
+
}
|
|
1325
|
+
acknowledgeNotifications(messages) {
|
|
1326
|
+
return this.background.acknowledge(messages);
|
|
1327
|
+
}
|
|
1328
|
+
prepareNotificationsDetached(messages) {
|
|
1329
|
+
return this.background.prepareNotificationsDetached(messages);
|
|
1330
|
+
}
|
|
1331
|
+
confirmNotificationsDetached(messages) {
|
|
1332
|
+
return this.background.confirmNotificationsDetached(messages);
|
|
1333
|
+
}
|
|
1334
|
+
reconcileDeliveredNotifications(delivered = this.options.notificationDelivered ?? (() => false)) {
|
|
1335
|
+
return this.background.acknowledgeDelivered(delivered);
|
|
1336
|
+
}
|
|
1337
|
+
reconcileDetachedNotifications(delivered) {
|
|
1338
|
+
return this.background.acknowledgeDeliveredAsDetached(delivered);
|
|
1339
|
+
}
|
|
1340
|
+
notificationClaimAgentIds() {
|
|
1341
|
+
return this.background.notificationClaimAgentIds();
|
|
1005
1342
|
}
|
|
1006
|
-
async
|
|
1343
|
+
async hydratePersistedTasks(sessionId, parentCwd, excludedAgentIds = new Set()) {
|
|
1344
|
+
if (this.options.persistence === 'memory')
|
|
1345
|
+
return;
|
|
1346
|
+
const paths = this.sessionPaths(sessionId, parentCwd);
|
|
1347
|
+
const candidates = await this.discoverSidechainCandidates(paths.projectRoot, sessionId);
|
|
1348
|
+
for (const agentId of new Set(candidates.map((candidate) => candidate.agentId))) {
|
|
1349
|
+
if (excludedAgentIds.has(agentId) || this.background.has(agentId)) {
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
1352
|
+
try {
|
|
1353
|
+
const lifecycle = await new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId).read();
|
|
1354
|
+
if (!lifecycle?.notifications?.some((notification) => !notification.consumed)) {
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
await this.hydratePersistedTask(sessionId, agentId, parentCwd);
|
|
1358
|
+
}
|
|
1359
|
+
catch (error) {
|
|
1360
|
+
this.options.eventSink?.({
|
|
1361
|
+
type: 'warning',
|
|
1362
|
+
message: `Background agent ${agentId} could not be recovered automatically: ${error instanceof Error ? error.message : String(error)}`,
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
async hydratePersistedTask(sessionId, identifier, parentCwd) {
|
|
1007
1368
|
if (this.background.has(identifier))
|
|
1008
1369
|
return;
|
|
1009
|
-
const paths = this.sessionPaths(sessionId);
|
|
1370
|
+
const paths = this.sessionPaths(sessionId, parentCwd);
|
|
1010
1371
|
const sidechainPaths = await this.resolvePersistedSidechain(paths.projectRoot, sessionId, identifier);
|
|
1011
1372
|
if (!sidechainPaths)
|
|
1012
1373
|
return;
|
|
@@ -1040,10 +1401,14 @@ export class ClaudeSubagentExecutor {
|
|
|
1040
1401
|
: '';
|
|
1041
1402
|
const projected = projectClaudeModelMessages(snapshot.entries);
|
|
1042
1403
|
const lastAssistant = projected.at(-1);
|
|
1043
|
-
|
|
1044
|
-
lastAssistant.
|
|
1045
|
-
(lastAssistant.toolCalls?.length ?? 0)
|
|
1046
|
-
|
|
1404
|
+
const completed = lastAssistant?.role === 'assistant' &&
|
|
1405
|
+
lastAssistant.content.trim().length > 0 &&
|
|
1406
|
+
(lastAssistant.toolCalls?.length ?? 0) === 0;
|
|
1407
|
+
const lifecycleStore = new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId, sidechainPaths.transcriptFile);
|
|
1408
|
+
const lifecycle = await lifecycleStore.read();
|
|
1409
|
+
const lifecycleIsCurrent = lifecycle !== null && (await lifecycleStore.matchesTranscript(lifecycle));
|
|
1410
|
+
if (lifecycleIsCurrent && lifecycle.status === 'completed' && !completed) {
|
|
1411
|
+
throw new Error(`Background agent ${agentId} has completed lifecycle state but an incomplete sidechain`);
|
|
1047
1412
|
}
|
|
1048
1413
|
const agentType = metadata?.agentType ?? 'general-purpose';
|
|
1049
1414
|
const description = metadata?.description ?? 'Recovered Claude sidechain';
|
|
@@ -1051,7 +1416,60 @@ export class ClaudeSubagentExecutor {
|
|
|
1051
1416
|
const spawnDepth = metadata?.spawnDepth ?? 1;
|
|
1052
1417
|
const name = metadata?.name;
|
|
1053
1418
|
const permissionMode = metadata?.permissionMode;
|
|
1054
|
-
|
|
1419
|
+
let isolation = metadata?.isolation;
|
|
1420
|
+
let restoredIsolation;
|
|
1421
|
+
let restoredCwd = parentCwd;
|
|
1422
|
+
if (isolation === 'worktree') {
|
|
1423
|
+
if (metadata?.worktreePath === undefined) {
|
|
1424
|
+
this.options.eventSink?.({
|
|
1425
|
+
type: 'warning',
|
|
1426
|
+
message: `Background agent ${agentId} has no retained worktree path; falling back to parent cwd ${parentCwd}`,
|
|
1427
|
+
});
|
|
1428
|
+
isolation = undefined;
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
try {
|
|
1432
|
+
restoredIsolation = await restoreManagedWorktree({
|
|
1433
|
+
cwd: parentCwd,
|
|
1434
|
+
path: metadata.worktreePath,
|
|
1435
|
+
label: 'Agent',
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
catch (error) {
|
|
1439
|
+
this.options.eventSink?.({
|
|
1440
|
+
type: 'warning',
|
|
1441
|
+
message: `Background agent ${agentId} could not restore its retained worktree; falling back to parent cwd ${parentCwd}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1442
|
+
});
|
|
1443
|
+
isolation = undefined;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
else {
|
|
1448
|
+
const persistedCwd = root.cwd;
|
|
1449
|
+
if (typeof persistedCwd === 'string' &&
|
|
1450
|
+
persistedCwd.length > 0 &&
|
|
1451
|
+
!persistedCwd.includes('\0') &&
|
|
1452
|
+
isAbsolute(persistedCwd)) {
|
|
1453
|
+
try {
|
|
1454
|
+
if (!(await lstat(persistedCwd)).isDirectory()) {
|
|
1455
|
+
throw new Error('persisted cwd is not a directory');
|
|
1456
|
+
}
|
|
1457
|
+
restoredCwd = persistedCwd;
|
|
1458
|
+
}
|
|
1459
|
+
catch (error) {
|
|
1460
|
+
this.options.eventSink?.({
|
|
1461
|
+
type: 'warning',
|
|
1462
|
+
message: `Background agent ${agentId} could not restore its persisted cwd; falling back to parent cwd ${parentCwd}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
else {
|
|
1467
|
+
this.options.eventSink?.({
|
|
1468
|
+
type: 'warning',
|
|
1469
|
+
message: `Background agent ${agentId} has an invalid persisted cwd; falling back to parent cwd ${parentCwd}`,
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1055
1473
|
const input = {
|
|
1056
1474
|
description,
|
|
1057
1475
|
prompt,
|
|
@@ -1062,9 +1480,12 @@ export class ClaudeSubagentExecutor {
|
|
|
1062
1480
|
runInBackground: true,
|
|
1063
1481
|
};
|
|
1064
1482
|
const provider = this.options.provider;
|
|
1065
|
-
const
|
|
1483
|
+
const backgroundRun = this.createBackgroundAgentRun({
|
|
1066
1484
|
input,
|
|
1067
|
-
|
|
1485
|
+
parentCwd: restoredCwd,
|
|
1486
|
+
lifecycle: lifecycleStore,
|
|
1487
|
+
...(restoredIsolation ? { initialIsolation: restoredIsolation } : {}),
|
|
1488
|
+
createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd),
|
|
1068
1489
|
execute: (cwd, message, signal, continuation) => sidechain.withLease(async (lease) => this.runSidechain({
|
|
1069
1490
|
lease,
|
|
1070
1491
|
root,
|
|
@@ -1081,7 +1502,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1081
1502
|
cwd,
|
|
1082
1503
|
})),
|
|
1083
1504
|
});
|
|
1084
|
-
|
|
1505
|
+
const spec = {
|
|
1085
1506
|
agentId,
|
|
1086
1507
|
...(name ? { name } : {}),
|
|
1087
1508
|
agentType,
|
|
@@ -1090,16 +1511,57 @@ export class ClaudeSubagentExecutor {
|
|
|
1090
1511
|
toolUseId,
|
|
1091
1512
|
outputFile: sidechainPaths.transcriptFile,
|
|
1092
1513
|
resolvedModel: provider.model ?? 'praxis/provider',
|
|
1093
|
-
run,
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1514
|
+
run: backgroundRun.run,
|
|
1515
|
+
markBackground: backgroundRun.markBackground,
|
|
1516
|
+
acknowledgeNotification: (notificationId) => lifecycleStore.acknowledgeNotification(notificationId),
|
|
1517
|
+
prepareNotificationDetached: (notificationId, model) => lifecycleStore.prepareNotificationDetached(notificationId, model),
|
|
1518
|
+
confirmNotificationDetached: (notificationId) => lifecycleStore.confirmNotificationDetached(notificationId),
|
|
1519
|
+
};
|
|
1520
|
+
if (lifecycleIsCurrent && lifecycle.status === 'failed') {
|
|
1521
|
+
this.background.registerTerminal(spec, 'failed', lifecycle.detail ?? 'Persisted agent failed');
|
|
1522
|
+
}
|
|
1523
|
+
else if (lifecycleIsCurrent && lifecycle.status === 'killed') {
|
|
1524
|
+
this.background.registerTerminal(spec, 'stopped', lifecycle.detail ?? 'Persisted agent was killed');
|
|
1525
|
+
}
|
|
1526
|
+
else if (completed) {
|
|
1527
|
+
this.background.registerCompleted(spec, lifecycleIsCurrent && lifecycle.result
|
|
1528
|
+
? lifecycle.result
|
|
1529
|
+
: {
|
|
1530
|
+
text: lastAssistant.content,
|
|
1531
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1532
|
+
toolUseCount: 0,
|
|
1533
|
+
durationMs: 0,
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
else {
|
|
1537
|
+
this.background.registerInterrupted(spec);
|
|
1538
|
+
}
|
|
1539
|
+
for (const notification of lifecycle?.notifications ?? []) {
|
|
1540
|
+
if (notification.consumed)
|
|
1541
|
+
continue;
|
|
1542
|
+
if (this.options.notificationDelivered?.({
|
|
1543
|
+
agentId,
|
|
1544
|
+
toolUseId: notification.toolUseId,
|
|
1545
|
+
status: notification.status,
|
|
1546
|
+
})) {
|
|
1547
|
+
if (notification.accounting?.kind === 'detached' &&
|
|
1548
|
+
!notification.accounting.delivered) {
|
|
1549
|
+
await lifecycleStore.confirmNotificationDetached(notification.id);
|
|
1550
|
+
}
|
|
1551
|
+
await lifecycleStore.acknowledgeNotification(notification.id);
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
this.background.registerPersistedNotification(agentId, {
|
|
1555
|
+
id: notification.id,
|
|
1556
|
+
status: notification.status === 'killed' ? 'stopped' : notification.status,
|
|
1557
|
+
result: notification.result,
|
|
1558
|
+
error: notification.error,
|
|
1559
|
+
toolUseId: notification.toolUseId,
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1100
1562
|
}
|
|
1101
1563
|
async resolvePersistedSidechain(projectRoot, sessionId, identifier) {
|
|
1102
|
-
const isAgentId =
|
|
1564
|
+
const isAgentId = isClaudeAgentId(identifier);
|
|
1103
1565
|
const isName = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u.test(identifier);
|
|
1104
1566
|
if (!isAgentId && !isName)
|
|
1105
1567
|
return null;
|
|
@@ -1107,6 +1569,9 @@ export class ClaudeSubagentExecutor {
|
|
|
1107
1569
|
const matches = candidates.filter((candidate) => isAgentId
|
|
1108
1570
|
? candidate.agentId === identifier
|
|
1109
1571
|
: candidate.name === identifier);
|
|
1572
|
+
if (matches.length > 1) {
|
|
1573
|
+
throw new Error(`Ambiguous persisted background agent ${identifier}: ${matches.length} sidechains match`);
|
|
1574
|
+
}
|
|
1110
1575
|
matches.sort((left, right) => (right.metadataModifiedAt ?? -Infinity) -
|
|
1111
1576
|
(left.metadataModifiedAt ?? -Infinity) ||
|
|
1112
1577
|
left.agentId.localeCompare(right.agentId) ||
|
|
@@ -1151,11 +1616,9 @@ export class ClaudeSubagentExecutor {
|
|
|
1151
1616
|
}
|
|
1152
1617
|
if (!entry.isFile())
|
|
1153
1618
|
return;
|
|
1154
|
-
const
|
|
1155
|
-
if (
|
|
1156
|
-
const agentId =
|
|
1157
|
-
if (agentId === undefined)
|
|
1158
|
-
return;
|
|
1619
|
+
const metadataAgentId = sidechainAgentIdFromFile(entry.name, '.meta.json');
|
|
1620
|
+
if (metadataAgentId !== undefined) {
|
|
1621
|
+
const agentId = metadataAgentId;
|
|
1159
1622
|
const transcriptEntry = byName.get(`agent-${agentId}.jsonl`);
|
|
1160
1623
|
if (!transcriptEntry ||
|
|
1161
1624
|
transcriptEntry.isSymbolicLink() ||
|
|
@@ -1183,12 +1646,10 @@ export class ClaudeSubagentExecutor {
|
|
|
1183
1646
|
});
|
|
1184
1647
|
return;
|
|
1185
1648
|
}
|
|
1186
|
-
const
|
|
1187
|
-
if (
|
|
1188
|
-
return;
|
|
1189
|
-
const agentId = transcriptMatch[1];
|
|
1190
|
-
if (agentId === undefined)
|
|
1649
|
+
const transcriptAgentId = sidechainAgentIdFromFile(entry.name, '.jsonl');
|
|
1650
|
+
if (transcriptAgentId === undefined)
|
|
1191
1651
|
return;
|
|
1652
|
+
const agentId = transcriptAgentId;
|
|
1192
1653
|
// A metadata companion (even an invalid one) is owned by the
|
|
1193
1654
|
// metadata branch; only a bare transcript without metadata is a
|
|
1194
1655
|
// legacy candidate.
|
|
@@ -1218,7 +1679,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1218
1679
|
}
|
|
1219
1680
|
async runSidechain(options) {
|
|
1220
1681
|
const cwd = options.cwd ?? this.cwd();
|
|
1221
|
-
const permissions = options.input.permissionMode
|
|
1682
|
+
const permissions = options.input.permissionMode && options.input.permissionMode !== 'default'
|
|
1222
1683
|
? this.options.permissionResolverForMode?.(options.input.permissionMode)
|
|
1223
1684
|
: this.options.permissions;
|
|
1224
1685
|
if (!permissions) {
|
|
@@ -1226,6 +1687,10 @@ export class ClaudeSubagentExecutor {
|
|
|
1226
1687
|
}
|
|
1227
1688
|
let snapshot = await options.lease.load();
|
|
1228
1689
|
if (options.continuationMessage !== undefined) {
|
|
1690
|
+
// Validate ambiguous tool linkage before the append-only sidechain is
|
|
1691
|
+
// mutated. The projection is rebuilt after appending the one explicit
|
|
1692
|
+
// continuation prompt so that prompt is visible to the provider.
|
|
1693
|
+
projectClaudeSidechainContinuationMessages(snapshot.entries);
|
|
1229
1694
|
const [entry] = translateProviderEvents([
|
|
1230
1695
|
{
|
|
1231
1696
|
type: 'user-text-block',
|
|
@@ -1253,6 +1718,12 @@ export class ClaudeSubagentExecutor {
|
|
|
1253
1718
|
}
|
|
1254
1719
|
let toolUseCount = 0;
|
|
1255
1720
|
let taskTokenTotal = 0;
|
|
1721
|
+
let recordedUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1722
|
+
let pendingProviderUsage;
|
|
1723
|
+
const recordedModelUsage = new Map();
|
|
1724
|
+
let recordedDurationApiMs = 0;
|
|
1725
|
+
let recordedDurationApiWithoutRetriesMs = 0;
|
|
1726
|
+
let recordedApiDurationSeen = false;
|
|
1256
1727
|
let lastToolName;
|
|
1257
1728
|
const taskStartedAt = Date.now();
|
|
1258
1729
|
const append = async (entry) => {
|
|
@@ -1286,7 +1757,48 @@ export class ClaudeSubagentExecutor {
|
|
|
1286
1757
|
const scopedHooks = scopedHookSettings.length > 0
|
|
1287
1758
|
? this.options.hooks?.withAdditionalSettings(scopedHookSettings)
|
|
1288
1759
|
: this.options.hooks;
|
|
1289
|
-
const
|
|
1760
|
+
const commitProviderUsage = () => {
|
|
1761
|
+
if (!pendingProviderUsage)
|
|
1762
|
+
return;
|
|
1763
|
+
recordedUsage = mergeSubagentUsage(recordedUsage, pendingProviderUsage);
|
|
1764
|
+
const model = options.provider.model?.trim();
|
|
1765
|
+
if (model) {
|
|
1766
|
+
mergeSubagentModelUsage(recordedModelUsage, {
|
|
1767
|
+
[model]: {
|
|
1768
|
+
...pendingProviderUsage,
|
|
1769
|
+
...(options.provider.capabilities.contextWindowTokens === undefined
|
|
1770
|
+
? {}
|
|
1771
|
+
: {
|
|
1772
|
+
contextWindow: options.provider.capabilities.contextWindowTokens,
|
|
1773
|
+
}),
|
|
1774
|
+
...(options.provider.capabilities.maxOutputTokens === undefined
|
|
1775
|
+
? {}
|
|
1776
|
+
: {
|
|
1777
|
+
maxOutputTokens: options.provider.capabilities.maxOutputTokens,
|
|
1778
|
+
}),
|
|
1779
|
+
},
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
pendingProviderUsage = undefined;
|
|
1783
|
+
};
|
|
1784
|
+
const recordApiDuration = (options) => {
|
|
1785
|
+
if (options.durationApiMs === undefined &&
|
|
1786
|
+
options.durationApiWithoutRetriesMs === undefined) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
const total = options.durationApiMs ?? 0;
|
|
1790
|
+
const withoutRetries = options.durationApiWithoutRetriesMs ?? total;
|
|
1791
|
+
if (!Number.isFinite(total) ||
|
|
1792
|
+
total < 0 ||
|
|
1793
|
+
!Number.isFinite(withoutRetries) ||
|
|
1794
|
+
withoutRetries < 0) {
|
|
1795
|
+
throw new Error('Subagent API duration must be finite and nonnegative');
|
|
1796
|
+
}
|
|
1797
|
+
recordedDurationApiMs += total;
|
|
1798
|
+
recordedDurationApiWithoutRetriesMs += withoutRetries;
|
|
1799
|
+
recordedApiDurationSeen = true;
|
|
1800
|
+
};
|
|
1801
|
+
const nestedTools = this.registry(String(options.root.sessionId), options.spawnDepth, () => options.promptId, options.agentId);
|
|
1290
1802
|
const agentMcp = customAgent?.mcpServers?.length
|
|
1291
1803
|
? await this.options.mcp?.connectAgent?.({
|
|
1292
1804
|
specs: customAgent.mcpServers,
|
|
@@ -1330,6 +1842,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1330
1842
|
: permissions;
|
|
1331
1843
|
const emit = (event) => {
|
|
1332
1844
|
if (event.type === 'usage') {
|
|
1845
|
+
pendingProviderUsage = event.usage;
|
|
1333
1846
|
taskTokenTotal += event.usage.inputTokens + event.usage.outputTokens;
|
|
1334
1847
|
this.options.eventSink?.({
|
|
1335
1848
|
type: 'task-progress',
|
|
@@ -1381,6 +1894,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1381
1894
|
});
|
|
1382
1895
|
const observer = {
|
|
1383
1896
|
assistantCompleted: async (message) => {
|
|
1897
|
+
commitProviderUsage();
|
|
1384
1898
|
const [entry] = translateProviderEvents([
|
|
1385
1899
|
{
|
|
1386
1900
|
type: 'assistant-message',
|
|
@@ -1398,6 +1912,13 @@ export class ClaudeSubagentExecutor {
|
|
|
1398
1912
|
await append(toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType));
|
|
1399
1913
|
},
|
|
1400
1914
|
toolCompleted: async (call, result) => {
|
|
1915
|
+
if (result.usage) {
|
|
1916
|
+
recordedUsage = mergeSubagentUsage(recordedUsage, result.usage);
|
|
1917
|
+
}
|
|
1918
|
+
if (result.isError === false && result.modelUsage) {
|
|
1919
|
+
mergeSubagentModelUsage(recordedModelUsage, result.modelUsage);
|
|
1920
|
+
}
|
|
1921
|
+
recordApiDuration(result);
|
|
1401
1922
|
if (result.isError === false) {
|
|
1402
1923
|
const linesAdded = result.linesAdded ?? 0;
|
|
1403
1924
|
const linesRemoved = result.linesRemoved ?? 0;
|
|
@@ -1441,6 +1962,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1441
1962
|
if (!entry)
|
|
1442
1963
|
throw new Error('Could not translate subagent follow-up');
|
|
1443
1964
|
await append(toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType));
|
|
1965
|
+
await this.background.acknowledge([text]);
|
|
1444
1966
|
}
|
|
1445
1967
|
},
|
|
1446
1968
|
};
|
|
@@ -1474,9 +1996,6 @@ export class ClaudeSubagentExecutor {
|
|
|
1474
1996
|
const agentSystem = memoryPrompt
|
|
1475
1997
|
? `${baseSystem}\n\n${memoryPrompt}`
|
|
1476
1998
|
: baseSystem;
|
|
1477
|
-
const system = options.outputSchema
|
|
1478
|
-
? `${agentSystem}\n\nYou MUST call StructuredOutput exactly once at the end with a value matching its schema.`
|
|
1479
|
-
: agentSystem;
|
|
1480
1999
|
const preloadedSkills = agentSkillMessages(this.options.extensions, customAgent);
|
|
1481
2000
|
let stopHookActive = false;
|
|
1482
2001
|
const contextBudget = options.provider.capabilities.contextWindowTokens
|
|
@@ -1495,20 +2014,28 @@ export class ClaudeSubagentExecutor {
|
|
|
1495
2014
|
? runtimeTools.definitions()
|
|
1496
2015
|
: [];
|
|
1497
2016
|
let observedMessages;
|
|
2017
|
+
let stableSystemMessageCount = 0;
|
|
1498
2018
|
const assembleMessages = async () => {
|
|
1499
|
-
const assembledContext = await this.options.contextAssembler
|
|
2019
|
+
const assembledContext = await assembleContextSnapshot(this.options.contextAssembler, {
|
|
1500
2020
|
cwd,
|
|
1501
2021
|
lifecycleId: options.agentId,
|
|
1502
2022
|
mode: 'subagent',
|
|
1503
|
-
baseSystemPrompt:
|
|
2023
|
+
baseSystemPrompt: agentSystem,
|
|
2024
|
+
...(options.outputSchema
|
|
2025
|
+
? {
|
|
2026
|
+
turn: {
|
|
2027
|
+
structuredOutput: true,
|
|
2028
|
+
},
|
|
2029
|
+
}
|
|
2030
|
+
: {}),
|
|
1504
2031
|
});
|
|
1505
|
-
const
|
|
2032
|
+
const contextProjection = projectContextSnapshot(assembledContext);
|
|
2033
|
+
stableSystemMessageCount = contextProjection.stableSystemSectionCount;
|
|
1506
2034
|
const messages = [
|
|
1507
|
-
...
|
|
1508
|
-
...(
|
|
1509
|
-
?
|
|
1510
|
-
:
|
|
1511
|
-
...injectFirstUserMessageContext(projectClaudeModelMessages(snapshot.entries), assembledContext?.firstUserMessageContext),
|
|
2035
|
+
...contextProjection.systemMessages,
|
|
2036
|
+
...injectFirstUserMessageContext(options.continuationMessage === undefined
|
|
2037
|
+
? projectClaudeModelMessages(snapshot.entries)
|
|
2038
|
+
: projectClaudeSidechainContinuationMessages(snapshot.entries), contextProjection.firstUserMessageContext),
|
|
1512
2039
|
...preloadedSkills,
|
|
1513
2040
|
];
|
|
1514
2041
|
observedMessages = messages;
|
|
@@ -1519,11 +2046,17 @@ export class ClaudeSubagentExecutor {
|
|
|
1519
2046
|
};
|
|
1520
2047
|
const configuredEffort = typeof customAgent?.effort === 'string' ? customAgent.effort : undefined;
|
|
1521
2048
|
const effectiveEffort = options.effort ?? configuredEffort;
|
|
1522
|
-
const
|
|
2049
|
+
const initialMessages = await assembleMessages();
|
|
2050
|
+
const runtimeRequest = {
|
|
1523
2051
|
sessionId: String(options.root.sessionId),
|
|
1524
|
-
messages:
|
|
2052
|
+
messages: initialMessages,
|
|
1525
2053
|
collectMetrics: true,
|
|
1526
|
-
|
|
2054
|
+
stableSystemMessageCount,
|
|
2055
|
+
reloadMessages: async () => {
|
|
2056
|
+
const messages = await assembleMessages();
|
|
2057
|
+
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
2058
|
+
return messages;
|
|
2059
|
+
},
|
|
1527
2060
|
cwd,
|
|
1528
2061
|
...(effectiveEffort ? { effort: effectiveEffort } : {}),
|
|
1529
2062
|
toolResultDirectory: options.toolResultDirectory,
|
|
@@ -1555,10 +2088,26 @@ export class ClaudeSubagentExecutor {
|
|
|
1555
2088
|
messages.push(`SubagentStop hook error: ${outcome.blockedReason}`);
|
|
1556
2089
|
}
|
|
1557
2090
|
}
|
|
2091
|
+
await this.hydratePersistedTasks(String(options.root.sessionId), options.cwd ?? this.cwd());
|
|
2092
|
+
await this.reconcileDeliveredNotifications((notification) => {
|
|
2093
|
+
const markers = backgroundAgentNotificationMarkers(notification);
|
|
2094
|
+
return snapshot.entries.some((entry) => {
|
|
2095
|
+
if (entry.type !== 'user')
|
|
2096
|
+
return false;
|
|
2097
|
+
const source = JSON.stringify(entry.message);
|
|
2098
|
+
return markers.every((marker) => source.includes(marker));
|
|
2099
|
+
});
|
|
2100
|
+
});
|
|
1558
2101
|
const background = await this.background.notifications({
|
|
1559
2102
|
waitForRunning: false,
|
|
1560
2103
|
excludeAgentId: options.agentId,
|
|
2104
|
+
consume: false,
|
|
1561
2105
|
});
|
|
2106
|
+
recordedUsage = mergeSubagentUsage(recordedUsage, background.usage);
|
|
2107
|
+
if (background.modelUsage) {
|
|
2108
|
+
mergeSubagentModelUsage(recordedModelUsage, background.modelUsage);
|
|
2109
|
+
}
|
|
2110
|
+
recordApiDuration(background);
|
|
1562
2111
|
messages.push(...background.messages);
|
|
1563
2112
|
messages.push(...((await this.options.backgroundTaskNotifications?.(true)) ?? []));
|
|
1564
2113
|
return {
|
|
@@ -1580,7 +2129,8 @@ export class ClaudeSubagentExecutor {
|
|
|
1580
2129
|
}
|
|
1581
2130
|
: {}),
|
|
1582
2131
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1583
|
-
}
|
|
2132
|
+
};
|
|
2133
|
+
const result = await runtime.run(runtimeRequest);
|
|
1584
2134
|
contextBudget?.observeUsage(result.usage, observedMessages ?? [], definitions);
|
|
1585
2135
|
this.options.eventSink?.({
|
|
1586
2136
|
type: 'task-progress',
|
|
@@ -1614,20 +2164,38 @@ export class ClaudeSubagentExecutor {
|
|
|
1614
2164
|
};
|
|
1615
2165
|
}
|
|
1616
2166
|
catch (error) {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
2167
|
+
commitProviderUsage();
|
|
2168
|
+
const partialModelUsage = recordedModelUsage.size === 0
|
|
2169
|
+
? undefined
|
|
2170
|
+
: Object.fromEntries(recordedModelUsage);
|
|
2171
|
+
if (!(options.signal?.reason instanceof BackgroundAgentShutdownError)) {
|
|
2172
|
+
this.options.eventSink?.({
|
|
2173
|
+
type: 'task-notification',
|
|
2174
|
+
taskId: options.agentId,
|
|
2175
|
+
...(options.toolUseId ? { toolUseId: options.toolUseId } : {}),
|
|
2176
|
+
status: options.signal?.aborted ? 'stopped' : 'failed',
|
|
2177
|
+
outputFile: options.transcriptPath,
|
|
2178
|
+
summary: error instanceof Error ? error.message : String(error),
|
|
2179
|
+
usage: {
|
|
2180
|
+
totalTokens: recordedUsage.inputTokens + recordedUsage.outputTokens,
|
|
2181
|
+
toolUses: toolUseCount,
|
|
2182
|
+
durationMs: Date.now() - taskStartedAt,
|
|
2183
|
+
},
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
throw new SubagentExecutionFailure(error, {
|
|
2187
|
+
text: error instanceof Error ? error.message : String(error),
|
|
2188
|
+
usage: recordedUsage,
|
|
2189
|
+
...(partialModelUsage ? { modelUsage: partialModelUsage } : {}),
|
|
2190
|
+
...(recordedApiDurationSeen
|
|
2191
|
+
? {
|
|
2192
|
+
durationApiMs: recordedDurationApiMs,
|
|
2193
|
+
durationApiWithoutRetriesMs: recordedDurationApiWithoutRetriesMs,
|
|
2194
|
+
}
|
|
2195
|
+
: {}),
|
|
2196
|
+
toolUseCount,
|
|
2197
|
+
durationMs: Date.now() - taskStartedAt,
|
|
1629
2198
|
});
|
|
1630
|
-
throw error;
|
|
1631
2199
|
}
|
|
1632
2200
|
finally {
|
|
1633
2201
|
await agentMcp?.close().catch((error) => {
|
|
@@ -1645,12 +2213,14 @@ class ClaudeSubagentToolRegistry {
|
|
|
1645
2213
|
sessionId;
|
|
1646
2214
|
depth;
|
|
1647
2215
|
promptIdForCall;
|
|
1648
|
-
|
|
2216
|
+
parentAgentId;
|
|
2217
|
+
constructor(base, executor, sessionId, depth, promptIdForCall, parentAgentId) {
|
|
1649
2218
|
this.base = base;
|
|
1650
2219
|
this.executor = executor;
|
|
1651
2220
|
this.sessionId = sessionId;
|
|
1652
2221
|
this.depth = depth;
|
|
1653
2222
|
this.promptIdForCall = promptIdForCall;
|
|
2223
|
+
this.parentAgentId = parentAgentId;
|
|
1654
2224
|
}
|
|
1655
2225
|
definitions() {
|
|
1656
2226
|
const base = this.base.definitions();
|
|
@@ -1698,21 +2268,21 @@ class ClaudeSubagentToolRegistry {
|
|
|
1698
2268
|
}
|
|
1699
2269
|
async execute(call, context) {
|
|
1700
2270
|
if (call.name === 'SendMessage') {
|
|
1701
|
-
return this.executor.executeManagement(call, this.sessionId);
|
|
2271
|
+
return this.executor.executeManagement(call, this.sessionId, context);
|
|
1702
2272
|
}
|
|
1703
2273
|
if (call.name === 'TaskOutput' || call.name === 'TaskStop') {
|
|
1704
2274
|
const taskId = call.input.task_id ?? call.input.shell_id;
|
|
1705
2275
|
if (typeof taskId === 'string' && isBackgroundBashTaskId(taskId)) {
|
|
1706
2276
|
return this.base.execute(call, context);
|
|
1707
2277
|
}
|
|
1708
|
-
return this.executor.executeManagement(call, this.sessionId);
|
|
2278
|
+
return this.executor.executeManagement(call, this.sessionId, context);
|
|
1709
2279
|
}
|
|
1710
2280
|
if (call.name !== 'Agent')
|
|
1711
2281
|
return this.base.execute(call, context);
|
|
1712
2282
|
const promptId = this.promptIdForCall(call.id);
|
|
1713
2283
|
if (!promptId)
|
|
1714
2284
|
throw new Error(`Could not locate prompt for Agent ${call.id}`);
|
|
1715
|
-
return this.executor.execute(call, this.sessionId, this.depth, promptId, context);
|
|
2285
|
+
return this.executor.execute(call, this.sessionId, this.depth, promptId, context, this.parentAgentId);
|
|
1716
2286
|
}
|
|
1717
2287
|
}
|
|
1718
2288
|
//# sourceMappingURL=subagent-service.js.map
|