praxis-agent 0.35.0 → 0.37.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 +20 -2
- package/dist/application/background-agent-manager.d.ts +16 -6
- package/dist/application/background-agent-manager.js +139 -97
- package/dist/application/context-engine.d.ts +56 -0
- package/dist/application/context-engine.js +129 -0
- package/dist/application/durable-follow-up.d.ts +14 -0
- package/dist/application/durable-follow-up.js +52 -0
- package/dist/application/native-session-transcript.d.ts +72 -0
- package/dist/application/native-session-transcript.js +351 -0
- package/dist/application/native-sidechain-transcript.d.ts +34 -0
- package/dist/application/native-sidechain-transcript.js +156 -0
- package/dist/application/session-service.d.ts +23 -4
- package/dist/application/session-service.js +1130 -494
- package/dist/application/subagent-service.d.ts +5 -1
- package/dist/application/subagent-service.js +749 -320
- package/dist/application/team-agent-runtime.d.ts +29 -0
- package/dist/application/team-agent-runtime.js +169 -0
- package/dist/application/team-capability.d.ts +27 -0
- package/dist/application/team-capability.js +41 -0
- package/dist/application/team-lead-operations.d.ts +38 -0
- package/dist/application/team-lead-operations.js +155 -0
- package/dist/application/team-mailbox.d.ts +54 -0
- package/dist/application/team-mailbox.js +196 -0
- package/dist/application/team-manager.d.ts +113 -0
- package/dist/application/team-manager.js +681 -0
- package/dist/application/team-workspace.d.ts +29 -0
- package/dist/application/team-workspace.js +197 -0
- package/dist/application/top-level-agent-manager.d.ts +1 -0
- package/dist/application/top-level-agent-manager.js +1025 -403
- package/dist/application/transcript-projection.d.ts +35 -0
- package/dist/application/transcript-projection.js +139 -0
- package/dist/application/turn-lifecycle.d.ts +2 -0
- package/dist/application/turn-memory-coordinator.d.ts +43 -0
- package/dist/application/turn-memory-coordinator.js +98 -0
- package/dist/cli/agents-dashboard.js +10 -5
- package/dist/cli/interactive.d.ts +7 -0
- package/dist/cli/interactive.js +645 -334
- package/dist/cli/tui/claude-style.d.ts +8 -25
- package/dist/cli/tui/claude-style.js +299 -277
- package/dist/cli/tui/decision-surface-model.d.ts +71 -0
- package/dist/cli/tui/decision-surface-model.js +131 -0
- package/dist/cli/tui/decision-surface.d.ts +7 -0
- package/dist/cli/tui/decision-surface.js +41 -0
- package/dist/cli/tui/help-surface-model.d.ts +68 -0
- package/dist/cli/tui/help-surface-model.js +131 -0
- package/dist/cli/tui/mcp-panel.js +1 -2
- package/dist/cli/tui/permission-surface-model.d.ts +195 -0
- package/dist/cli/tui/permission-surface-model.js +360 -0
- package/dist/cli/tui/permission-surface.d.ts +7 -0
- package/dist/cli/tui/permission-surface.js +69 -0
- package/dist/cli/tui/presentation-environment.d.ts +32 -0
- package/dist/cli/tui/presentation-environment.js +149 -0
- package/dist/cli/tui/task-panel.js +10 -1
- package/dist/cli/tui/theme.d.ts +46 -3
- package/dist/cli/tui/theme.js +414 -56
- package/dist/cli/tui/tool-permission.js +16 -8
- package/dist/cli/tui/transcript-presentation.d.ts +19 -1
- package/dist/cli/tui/transcript-presentation.js +26 -6
- package/dist/cli/tui/transcript-viewport.d.ts +18 -0
- package/dist/cli/tui/transcript-viewport.js +517 -111
- package/dist/cli/tui/transcript-window-model.d.ts +108 -0
- package/dist/cli/tui/transcript-window-model.js +772 -0
- package/dist/cli/tui/tui-interaction-router.d.ts +78 -0
- package/dist/cli/tui/tui-interaction-router.js +127 -0
- package/dist/cli/tui/tui-screen-model.d.ts +114 -0
- package/dist/cli/tui/tui-screen-model.js +210 -0
- package/dist/cli/tui/tui-view-model.d.ts +2 -49
- package/dist/cli/tui/tui-view-model.js +1 -68
- package/dist/cli-runtime.d.ts +4 -2
- package/dist/cli-runtime.js +397 -32
- package/dist/compatibility/claude/transcript-codec.js +6 -0
- package/dist/core/agent-orchestration.d.ts +31 -0
- package/dist/core/agent-orchestration.js +235 -0
- package/dist/core/permission-policy.d.ts +4 -0
- package/dist/core/permission-policy.js +21 -0
- package/dist/core/runtime.d.ts +6 -0
- package/dist/core/runtime.js +20 -2
- package/dist/core/team-mailbox.d.ts +54 -0
- package/dist/core/team-mailbox.js +212 -0
- package/dist/core/team-ownership.d.ts +84 -0
- package/dist/core/team-ownership.js +505 -0
- package/dist/core/tool-execution-scheduler.js +6 -1
- package/dist/core/transcript-event.d.ts +5 -1
- package/dist/core/transcript-event.js +12 -0
- package/dist/persistence/claude-job-store.d.ts +58 -0
- package/dist/persistence/claude-job-store.js +483 -1
- package/dist/persistence/claude-transcript-store.d.ts +2 -1
- package/dist/persistence/claude-transcript-store.js +2 -10
- package/dist/persistence/native-team-mailbox.d.ts +68 -0
- package/dist/persistence/native-team-mailbox.js +542 -0
- package/dist/persistence/native-team-store.d.ts +34 -0
- package/dist/persistence/native-team-store.js +257 -0
- package/dist/persistence/native-transcript-reader.d.ts +53 -0
- package/dist/persistence/native-transcript-reader.js +283 -0
- package/dist/persistence/native-transcript-store.d.ts +69 -0
- package/dist/persistence/native-transcript-store.js +321 -0
- package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
- package/dist/persistence/subagent-lifecycle-store.js +373 -146
- package/dist/persistence/transcript-file-append.d.ts +2 -0
- package/dist/persistence/transcript-file-append.js +10 -0
- package/dist/platform/exclusive-file-lease.d.ts +3 -0
- package/dist/platform/exclusive-file-lease.js +3 -0
- package/dist/tools/claude-capabilities.d.ts +4 -0
- package/dist/tools/claude-capabilities.js +18 -2
- package/dist/tools/team-lead-tools.d.ts +19 -0
- package/dist/tools/team-lead-tools.js +479 -0
- package/dist/tools/team-member-tools.d.ts +21 -0
- package/dist/tools/team-member-tools.js +259 -0
- package/package.json +1 -1
- package/dist/cli/tui/fullscreen-renderer.d.ts +0 -14
- package/dist/cli/tui/fullscreen-renderer.js +0 -13
|
@@ -12,17 +12,20 @@ import { injectFirstUserMessageContext, projectContextSnapshot, } from '../core/
|
|
|
12
12
|
import { assembleContextSnapshot } from '../core/prompt-composer.js';
|
|
13
13
|
import { ContextBudget } from '../core/context-budget.js';
|
|
14
14
|
import { AgentRuntime, } from '../core/runtime.js';
|
|
15
|
+
import { composePermissionResolvers } from '../core/permission-policy.js';
|
|
15
16
|
import { resolveToolSchedulingPolicy } from '../core/tool-scheduling-policy.js';
|
|
16
17
|
import { BUILTIN_STATUSLINE_AGENT_PATH, } from '../extensions/claude-extensions.js';
|
|
17
18
|
import { ClaudeHookToolCoordinator } from '../hooks/claude-hook-tools.js';
|
|
18
19
|
import { ClaudeSidechainStore } from '../persistence/claude-sidechain-store.js';
|
|
19
20
|
import { InMemorySidechainStore } from '../persistence/in-memory-sidechain-store.js';
|
|
20
|
-
import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.js';
|
|
21
|
+
import { SubagentLifecycleStore, } from '../persistence/subagent-lifecycle-store.js';
|
|
21
22
|
import { resolveDataPlanePaths, } from '../persistence/data-plane.js';
|
|
22
23
|
import { BackgroundAgentManager, BackgroundAgentRunError, BackgroundAgentShutdownError, backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
|
|
23
24
|
import { isBackgroundBashTaskId } from './background-task-id.js';
|
|
24
25
|
import { createManagedWorktree, restoreManagedWorktree, } from './managed-worktree.js';
|
|
25
26
|
import { createWorkflowWorktree } from './workflow-worktree.js';
|
|
27
|
+
import { NativeSidechainTranscript, } from './native-sidechain-transcript.js';
|
|
28
|
+
import { DurableFollowUpTracker, } from './durable-follow-up.js';
|
|
26
29
|
const DEFAULT_MAX_DEPTH = 4;
|
|
27
30
|
const DEFAULT_MAX_CALLS = 16;
|
|
28
31
|
const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
@@ -128,6 +131,48 @@ async function readSidechainMetadataName(metadataFile) {
|
|
|
128
131
|
return undefined;
|
|
129
132
|
}
|
|
130
133
|
}
|
|
134
|
+
function projectNativeSidechainContinuationMessages(messages) {
|
|
135
|
+
const callCounts = new Map();
|
|
136
|
+
const resultCounts = new Map();
|
|
137
|
+
for (const message of messages) {
|
|
138
|
+
if (message.role === 'assistant') {
|
|
139
|
+
for (const call of message.toolCalls ?? [])
|
|
140
|
+
callCounts.set(call.id, (callCounts.get(call.id) ?? 0) + 1);
|
|
141
|
+
}
|
|
142
|
+
else if (message.role === 'tool') {
|
|
143
|
+
resultCounts.set(message.toolCallId, (resultCounts.get(message.toolCallId) ?? 0) + 1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const [callId, count] of callCounts) {
|
|
147
|
+
if (count > 1)
|
|
148
|
+
throw new Error(`Native sidechain continuation has duplicate tool ID ${callId}`);
|
|
149
|
+
}
|
|
150
|
+
for (const [callId, count] of resultCounts) {
|
|
151
|
+
if (count > 1)
|
|
152
|
+
throw new Error(`Native sidechain continuation has duplicate tool result ${callId}`);
|
|
153
|
+
}
|
|
154
|
+
const resolved = new Set([...callCounts.keys()].filter((callId) => resultCounts.get(callId) === 1));
|
|
155
|
+
return messages.flatMap((message) => {
|
|
156
|
+
if (message.role === 'assistant') {
|
|
157
|
+
const toolCalls = (message.toolCalls ?? []).filter((call) => resolved.has(call.id));
|
|
158
|
+
if (message.content.trim() === '' && toolCalls.length === 0)
|
|
159
|
+
return [];
|
|
160
|
+
return [
|
|
161
|
+
{
|
|
162
|
+
role: 'assistant',
|
|
163
|
+
content: message.content,
|
|
164
|
+
...(message.thinkingBlocks
|
|
165
|
+
? { thinkingBlocks: message.thinkingBlocks }
|
|
166
|
+
: {}),
|
|
167
|
+
...(toolCalls.length === 0 ? {} : { toolCalls }),
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
if (message.role === 'tool' && !resolved.has(message.toolCallId))
|
|
172
|
+
return [];
|
|
173
|
+
return [message];
|
|
174
|
+
});
|
|
175
|
+
}
|
|
131
176
|
export class StructuredOutputRegistry {
|
|
132
177
|
base;
|
|
133
178
|
schema;
|
|
@@ -459,8 +504,19 @@ export class ClaudeSubagentExecutor {
|
|
|
459
504
|
}
|
|
460
505
|
constructor(options) {
|
|
461
506
|
this.options = options;
|
|
507
|
+
if (options.experimentalNativeTranscriptWrites === true &&
|
|
508
|
+
(options.dataPlane !== 'native' || options.persistence !== 'disk')) {
|
|
509
|
+
throw new Error('experimental native transcript writes require native dataPlane and disk persistence');
|
|
510
|
+
}
|
|
511
|
+
if (options.experimentalNativeTranscriptWrites === true &&
|
|
512
|
+
options.hooks !== undefined) {
|
|
513
|
+
throw new Error('experimental native sidechain writes do not support Claude hooks');
|
|
514
|
+
}
|
|
462
515
|
this.schema = selectClaudeSchemaAdapter(options.claudeVersion);
|
|
463
516
|
}
|
|
517
|
+
nativeSidechainWritesEnabled() {
|
|
518
|
+
return this.options.experimentalNativeTranscriptWrites === true;
|
|
519
|
+
}
|
|
464
520
|
async close() {
|
|
465
521
|
for (const agentId of [...this.foreground.keys()]) {
|
|
466
522
|
this.backgroundForegroundTask(agentId);
|
|
@@ -716,25 +772,77 @@ export class ClaudeSubagentExecutor {
|
|
|
716
772
|
const agentId = `a${randomBytes(8).toString('hex')}`;
|
|
717
773
|
const parentCwd = context.cwd;
|
|
718
774
|
const paths = this.sessionPaths(sessionId, parentCwd);
|
|
719
|
-
const
|
|
720
|
-
|
|
775
|
+
const sidechainPaths = resolveClaudeSidechainPaths(paths.projectRoot, sessionId, agentId);
|
|
776
|
+
const lifecycleStore = this.options.persistence === 'memory'
|
|
777
|
+
? undefined
|
|
778
|
+
: new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId, sidechainPaths.transcriptFile);
|
|
779
|
+
const initialExecution = lifecycleStore
|
|
780
|
+
? await lifecycleStore.start()
|
|
721
781
|
: undefined;
|
|
782
|
+
let initialIsolation;
|
|
783
|
+
const settleInitialSetupFailure = async (primary, isolation) => {
|
|
784
|
+
const secondary = [];
|
|
785
|
+
const message = primary instanceof Error ? primary.message : String(primary);
|
|
786
|
+
try {
|
|
787
|
+
await initialExecution?.finish('failed', {
|
|
788
|
+
text: message,
|
|
789
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
790
|
+
toolUseCount: 0,
|
|
791
|
+
durationMs: 0,
|
|
792
|
+
}, message);
|
|
793
|
+
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
secondary.push(error);
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
await initialExecution?.release();
|
|
799
|
+
}
|
|
800
|
+
catch (error) {
|
|
801
|
+
secondary.push(error);
|
|
802
|
+
}
|
|
803
|
+
try {
|
|
804
|
+
await isolation?.cleanup();
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
secondary.push(error);
|
|
808
|
+
}
|
|
809
|
+
if (secondary.length > 0)
|
|
810
|
+
throw new AggregateError([primary, ...secondary], 'Background agent setup and cleanup failed');
|
|
811
|
+
throw primary;
|
|
812
|
+
};
|
|
813
|
+
try {
|
|
814
|
+
initialIsolation = input.isolation
|
|
815
|
+
? await this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd)
|
|
816
|
+
: undefined;
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
return settleInitialSetupFailure(error, initialIsolation);
|
|
820
|
+
}
|
|
722
821
|
const agentCwd = initialIsolation?.cwd ?? parentCwd;
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
822
|
+
const nativeSidechain = this.nativeSidechainWritesEnabled()
|
|
823
|
+
? new NativeSidechainTranscript({
|
|
824
|
+
...sidechainPaths,
|
|
825
|
+
lockFile: join(paths.praxisRoot, 'locks', `${sessionId}-${agentId}.lock`),
|
|
826
|
+
})
|
|
827
|
+
: undefined;
|
|
828
|
+
const claudeSidechain = nativeSidechain
|
|
829
|
+
? undefined
|
|
830
|
+
: this.sidechainStore(sessionId, agentId, sidechainPaths, paths.praxisRoot);
|
|
831
|
+
const root = nativeSidechain
|
|
832
|
+
? undefined
|
|
833
|
+
: createClaudeSidechainRoot({
|
|
834
|
+
sessionId,
|
|
835
|
+
promptId,
|
|
836
|
+
prompt: input.prompt,
|
|
837
|
+
agentId,
|
|
838
|
+
cwd: agentCwd,
|
|
839
|
+
claudeVersion: this.options.claudeVersion,
|
|
840
|
+
gitBranch: null,
|
|
841
|
+
uuid: randomUUID(),
|
|
842
|
+
timestamp: new Date().toISOString(),
|
|
843
|
+
});
|
|
736
844
|
try {
|
|
737
|
-
|
|
845
|
+
const metadata = {
|
|
738
846
|
agentType: input.subagentType,
|
|
739
847
|
description: input.description,
|
|
740
848
|
toolUseId: call.id,
|
|
@@ -746,39 +854,55 @@ export class ClaudeSubagentExecutor {
|
|
|
746
854
|
...(input.isolation ? { isolation: input.isolation } : {}),
|
|
747
855
|
...(parentAgentId ? { parentAgentId } : {}),
|
|
748
856
|
...(initialIsolation ? { worktreePath: initialIsolation.cwd } : {}),
|
|
749
|
-
}
|
|
857
|
+
};
|
|
858
|
+
if (nativeSidechain) {
|
|
859
|
+
await nativeSidechain.create(input.prompt, {
|
|
860
|
+
...metadata,
|
|
861
|
+
cwd: agentCwd,
|
|
862
|
+
promptId,
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
if (!claudeSidechain || !root)
|
|
867
|
+
throw new Error('Claude sidechain initialization is unavailable');
|
|
868
|
+
await claudeSidechain.create(root, metadata);
|
|
869
|
+
}
|
|
750
870
|
}
|
|
751
871
|
catch (error) {
|
|
752
|
-
|
|
753
|
-
throw error;
|
|
872
|
+
return settleInitialSetupFailure(error, initialIsolation);
|
|
754
873
|
}
|
|
755
874
|
const provider = input.model
|
|
756
875
|
? (this.options.providerForModel?.(input.model) ?? this.options.provider)
|
|
757
876
|
: this.options.provider;
|
|
758
|
-
const lifecycleStore = this.options.persistence === 'memory'
|
|
759
|
-
? undefined
|
|
760
|
-
: new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId, sidechainPaths.transcriptFile);
|
|
761
877
|
const backgroundRun = this.createBackgroundAgentRun({
|
|
762
878
|
input,
|
|
763
879
|
parentCwd,
|
|
764
880
|
...(lifecycleStore ? { lifecycle: lifecycleStore } : {}),
|
|
881
|
+
...(initialExecution ? { initialExecution } : {}),
|
|
765
882
|
...(initialIsolation ? { initialIsolation } : {}),
|
|
766
883
|
createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd),
|
|
767
|
-
execute: (cwd, message, signal, continuation) =>
|
|
768
|
-
lease
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
884
|
+
execute: async (cwd, message, signal, continuation) => {
|
|
885
|
+
const run = (lease) => this.runSidechain({
|
|
886
|
+
...lease,
|
|
887
|
+
sessionId,
|
|
888
|
+
input,
|
|
889
|
+
provider,
|
|
890
|
+
agentId,
|
|
891
|
+
spawnDepth,
|
|
892
|
+
promptId,
|
|
893
|
+
toolUseId: call.id,
|
|
894
|
+
transcriptPath: sidechainPaths.transcriptFile,
|
|
895
|
+
toolResultDirectory: join(paths.projectRoot, sessionId, 'tool-results'),
|
|
896
|
+
...(continuation ? { continuationMessage: message } : {}),
|
|
897
|
+
signal,
|
|
898
|
+
cwd,
|
|
899
|
+
});
|
|
900
|
+
if (nativeSidechain)
|
|
901
|
+
return nativeSidechain.withLease((nativeLease) => run({ nativeLease }));
|
|
902
|
+
if (!claudeSidechain)
|
|
903
|
+
throw new Error('Claude sidechain execution is unavailable');
|
|
904
|
+
return claudeSidechain.withLease((claudeLease) => run({ claudeLease }));
|
|
905
|
+
},
|
|
782
906
|
});
|
|
783
907
|
const resolvedModel = provider.model ?? 'praxis/provider';
|
|
784
908
|
const spec = {
|
|
@@ -790,6 +914,7 @@ export class ClaudeSubagentExecutor {
|
|
|
790
914
|
toolUseId: call.id,
|
|
791
915
|
outputFile: sidechainPaths.transcriptFile,
|
|
792
916
|
resolvedModel,
|
|
917
|
+
lifecycle: backgroundRun.lifecycle,
|
|
793
918
|
run: backgroundRun.run,
|
|
794
919
|
markBackground: backgroundRun.markBackground,
|
|
795
920
|
...(lifecycleStore
|
|
@@ -820,7 +945,18 @@ export class ClaudeSubagentExecutor {
|
|
|
820
945
|
},
|
|
821
946
|
});
|
|
822
947
|
if (input.runInBackground) {
|
|
823
|
-
|
|
948
|
+
try {
|
|
949
|
+
this.background.launch(spec);
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
try {
|
|
953
|
+
await backgroundRun.disposeBeforeStart();
|
|
954
|
+
}
|
|
955
|
+
catch (cleanupError) {
|
|
956
|
+
throw new AggregateError([error, cleanupError], 'Background agent launch and pre-start cleanup failed');
|
|
957
|
+
}
|
|
958
|
+
throw error;
|
|
959
|
+
}
|
|
824
960
|
return asyncResult();
|
|
825
961
|
}
|
|
826
962
|
const controller = new AbortController();
|
|
@@ -947,183 +1083,288 @@ export class ClaudeSubagentExecutor {
|
|
|
947
1083
|
let availableIsolation = options.initialIsolation;
|
|
948
1084
|
let retainedIsolation;
|
|
949
1085
|
let notificationExpected = options.input.runInBackground;
|
|
1086
|
+
let recoverNext = options.recover ?? false;
|
|
1087
|
+
let pendingExecution = options.initialExecution;
|
|
1088
|
+
let disposed = false;
|
|
1089
|
+
let lifecycleState = options.initialExecution?.snapshot.state ??
|
|
1090
|
+
options.initialLifecycleState ??
|
|
1091
|
+
'queued';
|
|
1092
|
+
const lifecycleListeners = new Set();
|
|
1093
|
+
const publishLifecycle = (state) => {
|
|
1094
|
+
lifecycleState = state;
|
|
1095
|
+
if (state === 'orphaned')
|
|
1096
|
+
recoverNext = true;
|
|
1097
|
+
for (const listener of lifecycleListeners)
|
|
1098
|
+
listener(state);
|
|
1099
|
+
};
|
|
1100
|
+
const lifecycle = {
|
|
1101
|
+
current: () => lifecycleState,
|
|
1102
|
+
subscribe(listener) {
|
|
1103
|
+
lifecycleListeners.add(listener);
|
|
1104
|
+
return () => lifecycleListeners.delete(listener);
|
|
1105
|
+
},
|
|
1106
|
+
};
|
|
1107
|
+
const disposeBeforeStart = async () => {
|
|
1108
|
+
if (disposed)
|
|
1109
|
+
return;
|
|
1110
|
+
disposed = true;
|
|
1111
|
+
const execution = pendingExecution;
|
|
1112
|
+
pendingExecution = undefined;
|
|
1113
|
+
const isolation = availableIsolation;
|
|
1114
|
+
availableIsolation = undefined;
|
|
1115
|
+
const errors = [];
|
|
1116
|
+
if (execution) {
|
|
1117
|
+
try {
|
|
1118
|
+
const snapshot = await execution.finish('failed', {
|
|
1119
|
+
text: 'Background agent launch was rejected before execution started',
|
|
1120
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1121
|
+
toolUseCount: 0,
|
|
1122
|
+
durationMs: 0,
|
|
1123
|
+
}, 'Background agent launch was rejected before execution started');
|
|
1124
|
+
publishLifecycle(snapshot.state);
|
|
1125
|
+
}
|
|
1126
|
+
catch (error) {
|
|
1127
|
+
errors.push(error);
|
|
1128
|
+
}
|
|
1129
|
+
finally {
|
|
1130
|
+
try {
|
|
1131
|
+
await execution.release();
|
|
1132
|
+
publishLifecycle(execution.snapshot.state);
|
|
1133
|
+
}
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
errors.push(error);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (isolation) {
|
|
1140
|
+
try {
|
|
1141
|
+
await isolation.cleanup();
|
|
1142
|
+
}
|
|
1143
|
+
catch (error) {
|
|
1144
|
+
errors.push(error);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (errors.length > 0) {
|
|
1148
|
+
throw new AggregateError(errors, 'Background agent pre-start cleanup failed');
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
950
1151
|
const run = async (message, signal, continuation, currentToolUseId) => {
|
|
951
|
-
|
|
1152
|
+
let execution;
|
|
1153
|
+
let cancellation;
|
|
1154
|
+
let lifecycleTerminalized = false;
|
|
1155
|
+
let onAbort;
|
|
952
1156
|
const startedAt = Date.now();
|
|
953
1157
|
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;
|
|
992
1158
|
let result;
|
|
993
1159
|
let failure;
|
|
994
1160
|
let cleanup;
|
|
1161
|
+
const finishLifecycle = async (state, result, detail, notification) => {
|
|
1162
|
+
if (execution) {
|
|
1163
|
+
const snapshot = await execution.finish(state, result, detail, notification);
|
|
1164
|
+
publishLifecycle(snapshot.state);
|
|
1165
|
+
}
|
|
1166
|
+
else
|
|
1167
|
+
publishLifecycle(state);
|
|
1168
|
+
};
|
|
995
1169
|
try {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1170
|
+
if (pendingExecution) {
|
|
1171
|
+
execution = pendingExecution;
|
|
1172
|
+
pendingExecution = undefined;
|
|
1173
|
+
publishLifecycle(execution.snapshot.state);
|
|
1174
|
+
}
|
|
1175
|
+
else if (options.lifecycle) {
|
|
1176
|
+
execution = await options.lifecycle.acquire(recoverNext ? 'recover' : continuation ? 'continue' : 'start');
|
|
1177
|
+
publishLifecycle(execution.snapshot.state);
|
|
1178
|
+
recoverNext = false;
|
|
1179
|
+
}
|
|
1180
|
+
if (execution) {
|
|
1181
|
+
const snapshot = await execution.running();
|
|
1182
|
+
publishLifecycle(snapshot.state);
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
publishLifecycle('running');
|
|
1186
|
+
}
|
|
1187
|
+
onAbort = () => {
|
|
1188
|
+
if (cancellation)
|
|
1189
|
+
return;
|
|
1190
|
+
if (!execution) {
|
|
1191
|
+
cancellation = Promise.resolve(publishLifecycle('cancelling'));
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const activeExecution = execution;
|
|
1195
|
+
const transition = activeExecution.beginCancellation();
|
|
1196
|
+
transition.catch(() => undefined);
|
|
1197
|
+
const observedCancellation = transition
|
|
1198
|
+
.then((snapshot) => publishLifecycle(snapshot.state))
|
|
1199
|
+
.finally(() => publishLifecycle(activeExecution.snapshot.state));
|
|
1200
|
+
observedCancellation.catch(() => undefined);
|
|
1201
|
+
cancellation = observedCancellation;
|
|
1202
|
+
};
|
|
1203
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
1204
|
+
if (signal.aborted)
|
|
1205
|
+
onAbort();
|
|
1002
1206
|
try {
|
|
1003
|
-
|
|
1207
|
+
isolation = options.input.isolation
|
|
1208
|
+
? (retainedIsolation ??
|
|
1209
|
+
availableIsolation ??
|
|
1210
|
+
(await options.createIsolation()))
|
|
1211
|
+
: undefined;
|
|
1212
|
+
availableIsolation = undefined;
|
|
1213
|
+
result = await options.execute(isolation?.cwd ?? options.parentCwd, message, signal, continuation);
|
|
1004
1214
|
}
|
|
1005
1215
|
catch (error) {
|
|
1006
|
-
failure
|
|
1216
|
+
failure = error;
|
|
1007
1217
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1218
|
+
finally {
|
|
1219
|
+
try {
|
|
1220
|
+
cleanup = await isolation?.cleanup();
|
|
1221
|
+
}
|
|
1222
|
+
catch (error) {
|
|
1223
|
+
failure ??= error;
|
|
1224
|
+
}
|
|
1225
|
+
retainedIsolation = cleanup?.retained ? isolation : undefined;
|
|
1226
|
+
}
|
|
1227
|
+
if (failure !== undefined) {
|
|
1228
|
+
const underlyingFailure = failure instanceof SubagentExecutionFailure
|
|
1229
|
+
? failure.cause
|
|
1230
|
+
: failure;
|
|
1231
|
+
const failureMessage = underlyingFailure instanceof Error
|
|
1232
|
+
? underlyingFailure.message
|
|
1233
|
+
: String(underlyingFailure);
|
|
1234
|
+
const detail = cleanup?.reason
|
|
1235
|
+
? `${failureMessage}\n${cleanup.reason}`
|
|
1236
|
+
: failureMessage;
|
|
1237
|
+
const partialResult = failure instanceof SubagentExecutionFailure
|
|
1238
|
+
? failure.result
|
|
1239
|
+
: undefined;
|
|
1240
|
+
const failureResult = {
|
|
1241
|
+
...partialResult,
|
|
1242
|
+
text: failureMessage,
|
|
1243
|
+
usage: partialResult?.usage ?? {
|
|
1244
|
+
inputTokens: 0,
|
|
1245
|
+
outputTokens: 0,
|
|
1246
|
+
},
|
|
1247
|
+
toolUseCount: partialResult?.toolUseCount ?? 0,
|
|
1248
|
+
durationMs: Date.now() - startedAt,
|
|
1249
|
+
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1250
|
+
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1251
|
+
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1252
|
+
};
|
|
1253
|
+
await cancellation;
|
|
1254
|
+
const status = signal.aborted ? 'killed' : 'failed';
|
|
1255
|
+
const notificationId = notificationExpected &&
|
|
1256
|
+
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1257
|
+
? randomUUID()
|
|
1258
|
+
: undefined;
|
|
1259
|
+
await finishLifecycle(signal.aborted ? 'cancelled' : 'failed', failureResult, detail, notificationId
|
|
1042
1260
|
? {
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
error: detail,
|
|
1048
|
-
},
|
|
1261
|
+
id: notificationId,
|
|
1262
|
+
status,
|
|
1263
|
+
toolUseId: currentToolUseId,
|
|
1264
|
+
error: detail,
|
|
1049
1265
|
}
|
|
1050
|
-
:
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1266
|
+
: undefined);
|
|
1267
|
+
lifecycleTerminalized = true;
|
|
1268
|
+
if (!notificationExpected && !isolation && !cleanup?.reason) {
|
|
1269
|
+
throw underlyingFailure;
|
|
1270
|
+
}
|
|
1271
|
+
throw new BackgroundAgentRunError(detail, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, underlyingFailure);
|
|
1054
1272
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1273
|
+
if (!result) {
|
|
1274
|
+
const error = new Error('Agent completed without a result');
|
|
1275
|
+
const failureResult = {
|
|
1276
|
+
text: error.message,
|
|
1277
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1278
|
+
toolUseCount: 0,
|
|
1279
|
+
durationMs: Date.now() - startedAt,
|
|
1280
|
+
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1281
|
+
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1282
|
+
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1283
|
+
};
|
|
1284
|
+
const notificationId = notificationExpected &&
|
|
1285
|
+
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1286
|
+
? randomUUID()
|
|
1287
|
+
: undefined;
|
|
1288
|
+
await cancellation;
|
|
1289
|
+
await finishLifecycle(signal.aborted ? 'cancelled' : 'failed', failureResult, error.message, notificationId
|
|
1290
|
+
? {
|
|
1291
|
+
id: notificationId,
|
|
1292
|
+
status: signal.aborted ? 'killed' : 'failed',
|
|
1293
|
+
toolUseId: currentToolUseId,
|
|
1294
|
+
error: error.message,
|
|
1295
|
+
}
|
|
1296
|
+
: undefined);
|
|
1297
|
+
lifecycleTerminalized = true;
|
|
1298
|
+
if (!notificationExpected)
|
|
1299
|
+
throw error;
|
|
1300
|
+
throw new BackgroundAgentRunError(error.message, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, error);
|
|
1301
|
+
}
|
|
1302
|
+
const finalResult = {
|
|
1303
|
+
...result,
|
|
1063
1304
|
durationMs: Date.now() - startedAt,
|
|
1064
1305
|
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1065
1306
|
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1066
1307
|
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1067
1308
|
};
|
|
1309
|
+
await cancellation;
|
|
1310
|
+
const status = signal.aborted ? 'killed' : 'completed';
|
|
1311
|
+
const detail = signal.aborted
|
|
1312
|
+
? 'Agent was aborted before terminal cleanup'
|
|
1313
|
+
: undefined;
|
|
1068
1314
|
const notificationId = notificationExpected &&
|
|
1069
1315
|
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1070
1316
|
? randomUUID()
|
|
1071
1317
|
: undefined;
|
|
1072
|
-
await
|
|
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 = {
|
|
1090
|
-
...result,
|
|
1091
|
-
durationMs: Date.now() - startedAt,
|
|
1092
|
-
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1093
|
-
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1094
|
-
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
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
|
|
1318
|
+
await finishLifecycle(signal.aborted ? 'cancelled' : 'completed', finalResult, detail, notificationId
|
|
1107
1319
|
? {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
error: status === 'killed' ? (detail ?? null) : null,
|
|
1113
|
-
},
|
|
1320
|
+
id: notificationId,
|
|
1321
|
+
status,
|
|
1322
|
+
toolUseId: currentToolUseId,
|
|
1323
|
+
error: status === 'killed' ? (detail ?? null) : null,
|
|
1114
1324
|
}
|
|
1115
|
-
:
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1325
|
+
: undefined);
|
|
1326
|
+
lifecycleTerminalized = true;
|
|
1327
|
+
return {
|
|
1328
|
+
...finalResult,
|
|
1329
|
+
...(notificationId ? { notificationId } : {}),
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
finally {
|
|
1333
|
+
if (onAbort)
|
|
1334
|
+
signal.removeEventListener('abort', onAbort);
|
|
1335
|
+
if (!lifecycleTerminalized) {
|
|
1336
|
+
try {
|
|
1337
|
+
await execution?.release();
|
|
1338
|
+
}
|
|
1339
|
+
catch (releaseError) {
|
|
1340
|
+
this.options.eventSink?.({
|
|
1341
|
+
type: 'warning',
|
|
1342
|
+
message: `Background agent execution cleanup could not release its durable owner; durable owner reconciliation is required: ${releaseError instanceof Error ? releaseError.message : String(releaseError)}`,
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
finally {
|
|
1346
|
+
if (execution)
|
|
1347
|
+
publishLifecycle(execution.snapshot.state);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
else {
|
|
1351
|
+
try {
|
|
1352
|
+
await execution?.release();
|
|
1353
|
+
}
|
|
1354
|
+
finally {
|
|
1355
|
+
if (execution)
|
|
1356
|
+
publishLifecycle(execution.snapshot.state);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1121
1360
|
};
|
|
1122
1361
|
return {
|
|
1123
1362
|
run,
|
|
1124
1363
|
markBackground: () => {
|
|
1125
1364
|
notificationExpected = true;
|
|
1126
1365
|
},
|
|
1366
|
+
disposeBeforeStart,
|
|
1367
|
+
lifecycle,
|
|
1127
1368
|
};
|
|
1128
1369
|
}
|
|
1129
1370
|
async runWorkflowAgent(options) {
|
|
@@ -1158,7 +1399,13 @@ export class ClaudeSubagentExecutor {
|
|
|
1158
1399
|
metadataFile: agentFiles.metadataFile,
|
|
1159
1400
|
};
|
|
1160
1401
|
const sessionPaths = this.sessionPaths(options.sessionId);
|
|
1161
|
-
const
|
|
1402
|
+
const lockFile = join(sessionPaths.praxisRoot, 'locks', `${options.sessionId}-${options.runId}-${options.agentId}.lock`);
|
|
1403
|
+
const nativeSidechain = this.nativeSidechainWritesEnabled()
|
|
1404
|
+
? new NativeSidechainTranscript({ ...paths, lockFile })
|
|
1405
|
+
: undefined;
|
|
1406
|
+
const claudeSidechain = nativeSidechain
|
|
1407
|
+
? undefined
|
|
1408
|
+
: new ClaudeSidechainStore(paths, lockFile, this.schema);
|
|
1162
1409
|
const isolation = options.isolation
|
|
1163
1410
|
? await createWorkflowWorktree({
|
|
1164
1411
|
cwd: this.cwd(),
|
|
@@ -1168,29 +1415,47 @@ export class ClaudeSubagentExecutor {
|
|
|
1168
1415
|
})
|
|
1169
1416
|
: null;
|
|
1170
1417
|
const agentCwd = isolation?.cwd ?? this.cwd();
|
|
1171
|
-
const root =
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1418
|
+
const root = nativeSidechain
|
|
1419
|
+
? undefined
|
|
1420
|
+
: createClaudeSidechainRoot({
|
|
1421
|
+
sessionId: options.sessionId,
|
|
1422
|
+
promptId: options.promptId,
|
|
1423
|
+
prompt: options.prompt,
|
|
1424
|
+
agentId: options.agentId,
|
|
1425
|
+
cwd: agentCwd,
|
|
1426
|
+
claudeVersion: this.options.claudeVersion,
|
|
1427
|
+
gitBranch: null,
|
|
1428
|
+
uuid: randomUUID(),
|
|
1429
|
+
timestamp: new Date().toISOString(),
|
|
1430
|
+
});
|
|
1182
1431
|
const startedAt = Date.now();
|
|
1183
1432
|
const structured = { calls: 0, value: undefined };
|
|
1184
1433
|
let run;
|
|
1185
1434
|
let cleanup;
|
|
1186
1435
|
try {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1436
|
+
if (nativeSidechain) {
|
|
1437
|
+
await nativeSidechain.create(options.prompt, {
|
|
1438
|
+
agentType: options.agentType ?? 'workflow-subagent',
|
|
1439
|
+
description: input.description,
|
|
1440
|
+
toolUseId: `workflow:${options.runId}`,
|
|
1441
|
+
spawnDepth: 1,
|
|
1442
|
+
cwd: agentCwd,
|
|
1443
|
+
promptId: options.promptId,
|
|
1444
|
+
...(options.isolation ? { isolation: options.isolation } : {}),
|
|
1445
|
+
...(isolation ? { worktreePath: isolation.cwd } : {}),
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
else {
|
|
1449
|
+
if (!claudeSidechain || !root)
|
|
1450
|
+
throw new Error('Claude workflow sidechain is unavailable');
|
|
1451
|
+
await claudeSidechain.createWorkflow(root, {
|
|
1452
|
+
agentType: options.agentType ?? 'workflow-subagent',
|
|
1453
|
+
spawnDepth: 1,
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
const runWithLease = (lease) => this.runSidechain({
|
|
1457
|
+
...lease,
|
|
1458
|
+
sessionId: options.sessionId,
|
|
1194
1459
|
input,
|
|
1195
1460
|
provider,
|
|
1196
1461
|
agentId: options.agentId,
|
|
@@ -1204,7 +1469,15 @@ export class ClaudeSubagentExecutor {
|
|
|
1204
1469
|
...(options.effort ? { effort: options.effort } : {}),
|
|
1205
1470
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1206
1471
|
cwd: agentCwd,
|
|
1207
|
-
})
|
|
1472
|
+
});
|
|
1473
|
+
if (nativeSidechain) {
|
|
1474
|
+
run = await nativeSidechain.withLease((nativeLease) => runWithLease({ nativeLease }));
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
if (!claudeSidechain)
|
|
1478
|
+
throw new Error('Claude workflow sidechain is unavailable');
|
|
1479
|
+
run = await claudeSidechain.withLease((claudeLease) => runWithLease({ claudeLease }));
|
|
1480
|
+
}
|
|
1208
1481
|
}
|
|
1209
1482
|
finally {
|
|
1210
1483
|
cleanup = await isolation?.cleanup();
|
|
@@ -1309,7 +1582,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1309
1582
|
}
|
|
1310
1583
|
const agentId = String(call.input.to);
|
|
1311
1584
|
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);
|
|
1585
|
+
const owned = await this.options.sendOwnedBackgroundAgent?.(sessionId, agentId, String(call.input.message), summary, call.id);
|
|
1313
1586
|
if (owned !== undefined && owned !== null) {
|
|
1314
1587
|
return { content: owned, isError: false };
|
|
1315
1588
|
}
|
|
@@ -1372,40 +1645,74 @@ export class ClaudeSubagentExecutor {
|
|
|
1372
1645
|
if (!sidechainPaths)
|
|
1373
1646
|
return;
|
|
1374
1647
|
const agentId = sidechainPaths.agentId;
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1648
|
+
const lockFile = join(paths.praxisRoot, 'locks', `${sessionId}-${agentId}.lock`);
|
|
1649
|
+
const nativeSidechain = this.nativeSidechainWritesEnabled()
|
|
1650
|
+
? new NativeSidechainTranscript({ ...sidechainPaths, lockFile })
|
|
1651
|
+
: undefined;
|
|
1652
|
+
const claudeSidechain = nativeSidechain
|
|
1653
|
+
? undefined
|
|
1654
|
+
: new ClaudeSidechainStore(sidechainPaths, lockFile, this.schema);
|
|
1655
|
+
let metadata = null;
|
|
1656
|
+
let root;
|
|
1657
|
+
let projected;
|
|
1377
1658
|
try {
|
|
1378
|
-
|
|
1659
|
+
if (nativeSidechain) {
|
|
1660
|
+
await nativeSidechain.loadReadOnly();
|
|
1661
|
+
metadata = await nativeSidechain.metadata();
|
|
1662
|
+
projected = await nativeSidechain.withLease(async (lease) => lease.activeMessages());
|
|
1663
|
+
}
|
|
1664
|
+
else {
|
|
1665
|
+
if (!claudeSidechain)
|
|
1666
|
+
throw new Error('Claude persisted sidechain is unavailable');
|
|
1667
|
+
const snapshot = await claudeSidechain.loadReadOnly();
|
|
1668
|
+
metadata = await claudeSidechain.metadata().catch((error) => {
|
|
1669
|
+
if (error.code === 'ENOENT')
|
|
1670
|
+
return null;
|
|
1671
|
+
throw error;
|
|
1672
|
+
});
|
|
1673
|
+
root = snapshot.entries[0];
|
|
1674
|
+
if (!root || root.type !== 'user') {
|
|
1675
|
+
throw new Error(`Background agent ${agentId} has no sidechain root`);
|
|
1676
|
+
}
|
|
1677
|
+
projected = projectClaudeModelMessages(snapshot.entries);
|
|
1678
|
+
}
|
|
1379
1679
|
}
|
|
1380
1680
|
catch (error) {
|
|
1381
1681
|
if (error.code === 'ENOENT')
|
|
1382
1682
|
return;
|
|
1383
1683
|
throw error;
|
|
1384
1684
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
metadata = await sidechain.metadata();
|
|
1388
|
-
}
|
|
1389
|
-
catch (error) {
|
|
1390
|
-
if (error.code !== 'ENOENT')
|
|
1391
|
-
throw error;
|
|
1392
|
-
}
|
|
1393
|
-
const root = snapshot.entries[0];
|
|
1394
|
-
if (!root || root.type !== 'user') {
|
|
1685
|
+
const rootMessage = projected[0];
|
|
1686
|
+
if (rootMessage?.role !== 'user' || rootMessage.content.trim() === '') {
|
|
1395
1687
|
throw new Error(`Background agent ${agentId} has no sidechain root`);
|
|
1396
1688
|
}
|
|
1397
|
-
const prompt =
|
|
1398
|
-
root.message !== null &&
|
|
1399
|
-
typeof root.message.content === 'string'
|
|
1400
|
-
? String(root.message.content)
|
|
1401
|
-
: '';
|
|
1402
|
-
const projected = projectClaudeModelMessages(snapshot.entries);
|
|
1689
|
+
const prompt = rootMessage.content;
|
|
1403
1690
|
const lastAssistant = projected.at(-1);
|
|
1404
1691
|
const completed = lastAssistant?.role === 'assistant' &&
|
|
1405
1692
|
lastAssistant.content.trim().length > 0 &&
|
|
1406
1693
|
(lastAssistant.toolCalls?.length ?? 0) === 0;
|
|
1407
1694
|
const lifecycleStore = new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId, sidechainPaths.transcriptFile);
|
|
1408
|
-
const
|
|
1695
|
+
const ownership = await lifecycleStore.reconcileOwnerLoss();
|
|
1696
|
+
if (ownership.owned)
|
|
1697
|
+
return;
|
|
1698
|
+
let lifecycle = await lifecycleStore.read();
|
|
1699
|
+
if (!lifecycle) {
|
|
1700
|
+
const seed = await lifecycleStore.start();
|
|
1701
|
+
await seed.running();
|
|
1702
|
+
if (completed) {
|
|
1703
|
+
await seed.finish('completed', {
|
|
1704
|
+
text: lastAssistant?.content ?? '',
|
|
1705
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1706
|
+
toolUseCount: 0,
|
|
1707
|
+
durationMs: 0,
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
await seed.release();
|
|
1711
|
+
lifecycle = await lifecycleStore.read();
|
|
1712
|
+
}
|
|
1713
|
+
if (!lifecycle) {
|
|
1714
|
+
throw new Error(`Unable to establish lifecycle state for background agent ${agentId}`);
|
|
1715
|
+
}
|
|
1409
1716
|
const lifecycleIsCurrent = lifecycle !== null && (await lifecycleStore.matchesTranscript(lifecycle));
|
|
1410
1717
|
if (lifecycleIsCurrent && lifecycle.status === 'completed' && !completed) {
|
|
1411
1718
|
throw new Error(`Background agent ${agentId} has completed lifecycle state but an incomplete sidechain`);
|
|
@@ -1445,7 +1752,9 @@ export class ClaudeSubagentExecutor {
|
|
|
1445
1752
|
}
|
|
1446
1753
|
}
|
|
1447
1754
|
else {
|
|
1448
|
-
const persistedCwd =
|
|
1755
|
+
const persistedCwd = nativeSidechain
|
|
1756
|
+
? metadata.cwd
|
|
1757
|
+
: root?.cwd;
|
|
1449
1758
|
if (typeof persistedCwd === 'string' &&
|
|
1450
1759
|
persistedCwd.length > 0 &&
|
|
1451
1760
|
!persistedCwd.includes('\0') &&
|
|
@@ -1480,27 +1789,39 @@ export class ClaudeSubagentExecutor {
|
|
|
1480
1789
|
runInBackground: true,
|
|
1481
1790
|
};
|
|
1482
1791
|
const provider = this.options.provider;
|
|
1792
|
+
const recoveredPromptId = nativeSidechain
|
|
1793
|
+
? metadata.promptId
|
|
1794
|
+
: String(root?.promptId ?? randomUUID());
|
|
1483
1795
|
const backgroundRun = this.createBackgroundAgentRun({
|
|
1484
1796
|
input,
|
|
1485
1797
|
parentCwd: restoredCwd,
|
|
1486
1798
|
lifecycle: lifecycleStore,
|
|
1799
|
+
recover: lifecycle.lifecycle.state === 'orphaned',
|
|
1800
|
+
initialLifecycleState: lifecycle.lifecycle.state,
|
|
1487
1801
|
...(restoredIsolation ? { initialIsolation: restoredIsolation } : {}),
|
|
1488
1802
|
createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd),
|
|
1489
|
-
execute: (cwd, message, signal, continuation) =>
|
|
1490
|
-
lease
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1803
|
+
execute: async (cwd, message, signal, continuation) => {
|
|
1804
|
+
const run = (lease) => this.runSidechain({
|
|
1805
|
+
...lease,
|
|
1806
|
+
sessionId,
|
|
1807
|
+
input,
|
|
1808
|
+
provider,
|
|
1809
|
+
agentId,
|
|
1810
|
+
spawnDepth,
|
|
1811
|
+
promptId: recoveredPromptId,
|
|
1812
|
+
toolUseId,
|
|
1813
|
+
transcriptPath: sidechainPaths.transcriptFile,
|
|
1814
|
+
toolResultDirectory: join(paths.projectRoot, sessionId, 'tool-results'),
|
|
1815
|
+
...(continuation ? { continuationMessage: message } : {}),
|
|
1816
|
+
signal,
|
|
1817
|
+
cwd,
|
|
1818
|
+
});
|
|
1819
|
+
if (nativeSidechain)
|
|
1820
|
+
return nativeSidechain.withLease((nativeLease) => run({ nativeLease }));
|
|
1821
|
+
if (!claudeSidechain)
|
|
1822
|
+
throw new Error('Claude persisted sidechain is unavailable');
|
|
1823
|
+
return claudeSidechain.withLease((claudeLease) => run({ claudeLease }));
|
|
1824
|
+
},
|
|
1504
1825
|
});
|
|
1505
1826
|
const spec = {
|
|
1506
1827
|
agentId,
|
|
@@ -1511,31 +1832,34 @@ export class ClaudeSubagentExecutor {
|
|
|
1511
1832
|
toolUseId,
|
|
1512
1833
|
outputFile: sidechainPaths.transcriptFile,
|
|
1513
1834
|
resolvedModel: provider.model ?? 'praxis/provider',
|
|
1835
|
+
lifecycle: backgroundRun.lifecycle,
|
|
1514
1836
|
run: backgroundRun.run,
|
|
1515
1837
|
markBackground: backgroundRun.markBackground,
|
|
1516
1838
|
acknowledgeNotification: (notificationId) => lifecycleStore.acknowledgeNotification(notificationId),
|
|
1517
1839
|
prepareNotificationDetached: (notificationId, model) => lifecycleStore.prepareNotificationDetached(notificationId, model),
|
|
1518
1840
|
confirmNotificationDetached: (notificationId) => lifecycleStore.confirmNotificationDetached(notificationId),
|
|
1519
1841
|
};
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1842
|
+
this.background.registerPersisted(spec, {
|
|
1843
|
+
...(lifecycleIsCurrent && lifecycle.result
|
|
1844
|
+
? { result: lifecycle.result }
|
|
1845
|
+
: completed
|
|
1846
|
+
? {
|
|
1847
|
+
result: {
|
|
1848
|
+
text: lastAssistant.content,
|
|
1849
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1850
|
+
toolUseCount: 0,
|
|
1851
|
+
durationMs: 0,
|
|
1852
|
+
},
|
|
1853
|
+
}
|
|
1854
|
+
: {}),
|
|
1855
|
+
...(lifecycleIsCurrent && lifecycle.status === 'failed'
|
|
1856
|
+
? { error: lifecycle.detail ?? 'Persisted agent failed' }
|
|
1857
|
+
: lifecycleIsCurrent && lifecycle.status === 'killed'
|
|
1858
|
+
? { error: lifecycle.detail ?? 'Persisted agent was killed' }
|
|
1859
|
+
: !completed && lifecycle.lifecycle.state === 'orphaned'
|
|
1860
|
+
? { error: 'Persisted agent was interrupted before completion' }
|
|
1861
|
+
: {}),
|
|
1862
|
+
});
|
|
1539
1863
|
for (const notification of lifecycle?.notifications ?? []) {
|
|
1540
1864
|
if (notification.consumed)
|
|
1541
1865
|
continue;
|
|
@@ -1553,7 +1877,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1553
1877
|
}
|
|
1554
1878
|
this.background.registerPersistedNotification(agentId, {
|
|
1555
1879
|
id: notification.id,
|
|
1556
|
-
status: notification.status === 'killed' ? '
|
|
1880
|
+
status: notification.status === 'killed' ? 'cancelled' : notification.status,
|
|
1557
1881
|
result: notification.result,
|
|
1558
1882
|
error: notification.error,
|
|
1559
1883
|
toolUseId: notification.toolUseId,
|
|
@@ -1680,42 +2004,77 @@ export class ClaudeSubagentExecutor {
|
|
|
1680
2004
|
async runSidechain(options) {
|
|
1681
2005
|
const cwd = options.cwd ?? this.cwd();
|
|
1682
2006
|
const permissions = options.input.permissionMode && options.input.permissionMode !== 'default'
|
|
1683
|
-
?
|
|
2007
|
+
? (() => {
|
|
2008
|
+
const mode = options.input.permissionMode;
|
|
2009
|
+
if (!mode)
|
|
2010
|
+
return undefined;
|
|
2011
|
+
const child = this.options.permissionResolverForMode?.(mode);
|
|
2012
|
+
return child
|
|
2013
|
+
? composePermissionResolvers(this.options.permissions, child)
|
|
2014
|
+
: undefined;
|
|
2015
|
+
})()
|
|
1684
2016
|
: this.options.permissions;
|
|
1685
2017
|
if (!permissions) {
|
|
1686
2018
|
throw new Error('Agent permission mode overrides are unavailable');
|
|
1687
2019
|
}
|
|
1688
|
-
|
|
2020
|
+
if ((options.claudeLease === undefined) ===
|
|
2021
|
+
(options.nativeLease === undefined))
|
|
2022
|
+
throw new Error('Exactly one sidechain transcript lease is required');
|
|
2023
|
+
const claudeLease = options.claudeLease;
|
|
2024
|
+
const nativeLease = options.nativeLease;
|
|
2025
|
+
let snapshot = claudeLease
|
|
2026
|
+
? await claudeLease.load()
|
|
2027
|
+
: undefined;
|
|
2028
|
+
const requireClaudeSnapshot = () => {
|
|
2029
|
+
if (!snapshot)
|
|
2030
|
+
throw new Error('Claude sidechain snapshot is unavailable');
|
|
2031
|
+
return snapshot;
|
|
2032
|
+
};
|
|
2033
|
+
const nativeInterruption = nativeLease?.interruption();
|
|
2034
|
+
if (nativeInterruption?.kind === 'indeterminate-tools')
|
|
2035
|
+
throw new Error(`Native sidechain tool execution is indeterminate: ${nativeInterruption.callIds.join(', ')}`);
|
|
2036
|
+
const nativeRecoveryCalls = options.continuationMessage === undefined &&
|
|
2037
|
+
nativeInterruption?.kind === 'recoverable-tools'
|
|
2038
|
+
? [...nativeInterruption.calls]
|
|
2039
|
+
: [];
|
|
1689
2040
|
if (options.continuationMessage !== undefined) {
|
|
1690
2041
|
// Validate ambiguous tool linkage before the append-only sidechain is
|
|
1691
2042
|
// mutated. The projection is rebuilt after appending the one explicit
|
|
1692
2043
|
// continuation prompt so that prompt is visible to the provider.
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2044
|
+
const continuation = `The coordinator sent a message while you were working:\n${options.continuationMessage}\n\nAddress this before completing your current task.`;
|
|
2045
|
+
if (nativeLease) {
|
|
2046
|
+
projectNativeSidechainContinuationMessages(nativeLease.activeMessages());
|
|
2047
|
+
await nativeLease.appendMessages({
|
|
2048
|
+
messages: [{ role: 'user', content: continuation }],
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
else {
|
|
2052
|
+
const current = requireClaudeSnapshot();
|
|
2053
|
+
projectClaudeSidechainContinuationMessages(current.entries);
|
|
2054
|
+
const [entry] = translateProviderEvents([{ type: 'user-text-block', text: continuation }], {
|
|
2055
|
+
sessionId: options.sessionId,
|
|
2056
|
+
parentUuid: current.tail.lastUuid,
|
|
2057
|
+
cwd,
|
|
2058
|
+
claudeVersion: this.options.claudeVersion,
|
|
2059
|
+
gitBranch: null,
|
|
2060
|
+
history: current.entries,
|
|
2061
|
+
});
|
|
2062
|
+
if (!entry)
|
|
2063
|
+
throw new Error('Could not translate subagent message');
|
|
2064
|
+
const sidechainEntry = toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType);
|
|
2065
|
+
if (!claudeLease)
|
|
2066
|
+
throw new Error('Claude sidechain continuation is unavailable');
|
|
2067
|
+
const appendResult = await claudeLease.append(current.tail, sidechainEntry);
|
|
2068
|
+
if (appendResult.status === 'conflict') {
|
|
2069
|
+
throw new Error(`Claude sidechain append conflict: ${appendResult.reason}`);
|
|
2070
|
+
}
|
|
2071
|
+
snapshot = {
|
|
2072
|
+
entries: [...current.entries, sidechainEntry],
|
|
2073
|
+
tail: appendResult.tail,
|
|
2074
|
+
};
|
|
1713
2075
|
}
|
|
1714
|
-
snapshot = {
|
|
1715
|
-
entries: [...snapshot.entries, sidechainEntry],
|
|
1716
|
-
tail: appendResult.tail,
|
|
1717
|
-
};
|
|
1718
2076
|
}
|
|
2077
|
+
const durableFollowUps = new DurableFollowUpTracker();
|
|
1719
2078
|
let toolUseCount = 0;
|
|
1720
2079
|
let taskTokenTotal = 0;
|
|
1721
2080
|
let recordedUsage = { inputTokens: 0, outputTokens: 0 };
|
|
@@ -1726,29 +2085,37 @@ export class ClaudeSubagentExecutor {
|
|
|
1726
2085
|
let recordedApiDurationSeen = false;
|
|
1727
2086
|
let lastToolName;
|
|
1728
2087
|
const taskStartedAt = Date.now();
|
|
1729
|
-
const
|
|
1730
|
-
|
|
2088
|
+
const appendClaude = async (entry) => {
|
|
2089
|
+
if (!claudeLease)
|
|
2090
|
+
throw new Error('Claude sidechain append is unavailable');
|
|
2091
|
+
const current = requireClaudeSnapshot();
|
|
2092
|
+
const result = await claudeLease.append(current.tail, entry);
|
|
1731
2093
|
if (result.status === 'conflict') {
|
|
1732
2094
|
throw new Error(`Claude sidechain append conflict: ${result.reason}`);
|
|
1733
2095
|
}
|
|
1734
|
-
snapshot = { entries: [...
|
|
2096
|
+
snapshot = { entries: [...current.entries, entry], tail: result.tail };
|
|
2097
|
+
};
|
|
2098
|
+
const translationContext = () => {
|
|
2099
|
+
const current = requireClaudeSnapshot();
|
|
2100
|
+
return {
|
|
2101
|
+
sessionId: options.sessionId,
|
|
2102
|
+
parentUuid: current.tail.lastUuid,
|
|
2103
|
+
cwd,
|
|
2104
|
+
claudeVersion: this.options.claudeVersion,
|
|
2105
|
+
gitBranch: null,
|
|
2106
|
+
history: current.entries,
|
|
2107
|
+
};
|
|
1735
2108
|
};
|
|
1736
|
-
const translationContext = () => ({
|
|
1737
|
-
sessionId: String(options.root.sessionId),
|
|
1738
|
-
parentUuid: snapshot.tail.lastUuid,
|
|
1739
|
-
cwd,
|
|
1740
|
-
claudeVersion: this.options.claudeVersion,
|
|
1741
|
-
gitBranch: null,
|
|
1742
|
-
history: snapshot.entries,
|
|
1743
|
-
});
|
|
1744
2109
|
const recordHookOutcome = async (outcome) => {
|
|
2110
|
+
if (nativeLease)
|
|
2111
|
+
throw new Error('Claude hooks are unavailable for native sidechains');
|
|
1745
2112
|
for (const entry of createClaudeHookAttachmentEntries(outcome, translationContext())) {
|
|
1746
|
-
await
|
|
2113
|
+
await appendClaude(toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType));
|
|
1747
2114
|
}
|
|
1748
2115
|
};
|
|
1749
2116
|
const customAgent = this.agentDefinition(options.input);
|
|
1750
2117
|
const hookSession = {
|
|
1751
|
-
session_id:
|
|
2118
|
+
session_id: options.sessionId,
|
|
1752
2119
|
transcript_path: options.transcriptPath,
|
|
1753
2120
|
cwd,
|
|
1754
2121
|
permission_mode: options.input.permissionMode ?? 'default',
|
|
@@ -1798,7 +2165,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1798
2165
|
recordedDurationApiWithoutRetriesMs += withoutRetries;
|
|
1799
2166
|
recordedApiDurationSeen = true;
|
|
1800
2167
|
};
|
|
1801
|
-
const nestedTools = this.registry(
|
|
2168
|
+
const nestedTools = this.registry(options.sessionId, options.spawnDepth, () => options.promptId, options.agentId);
|
|
1802
2169
|
const agentMcp = customAgent?.mcpServers?.length
|
|
1803
2170
|
? await this.options.mcp?.connectAgent?.({
|
|
1804
2171
|
specs: customAgent.mcpServers,
|
|
@@ -1893,8 +2260,29 @@ export class ClaudeSubagentExecutor {
|
|
|
1893
2260
|
maxToolInputBytes: 1024 * 1024,
|
|
1894
2261
|
});
|
|
1895
2262
|
const observer = {
|
|
2263
|
+
toolExecutionStarted: async (call) => {
|
|
2264
|
+
await nativeLease?.beginToolExecution(call.id);
|
|
2265
|
+
},
|
|
1896
2266
|
assistantCompleted: async (message) => {
|
|
1897
2267
|
commitProviderUsage();
|
|
2268
|
+
if (nativeLease) {
|
|
2269
|
+
await nativeLease.appendMessages({
|
|
2270
|
+
messages: [
|
|
2271
|
+
{
|
|
2272
|
+
role: 'assistant',
|
|
2273
|
+
content: message.content,
|
|
2274
|
+
...(message.thinkingBlocks
|
|
2275
|
+
? { thinkingBlocks: message.thinkingBlocks }
|
|
2276
|
+
: {}),
|
|
2277
|
+
...(message.toolCalls?.length
|
|
2278
|
+
? { toolCalls: message.toolCalls }
|
|
2279
|
+
: {}),
|
|
2280
|
+
},
|
|
2281
|
+
],
|
|
2282
|
+
model: options.provider.model ?? 'praxis/provider',
|
|
2283
|
+
});
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
1898
2286
|
const [entry] = translateProviderEvents([
|
|
1899
2287
|
{
|
|
1900
2288
|
type: 'assistant-message',
|
|
@@ -1909,7 +2297,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1909
2297
|
], translationContext());
|
|
1910
2298
|
if (!entry)
|
|
1911
2299
|
throw new Error('Could not translate subagent response');
|
|
1912
|
-
await
|
|
2300
|
+
await appendClaude(toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType));
|
|
1913
2301
|
},
|
|
1914
2302
|
toolCompleted: async (call, result) => {
|
|
1915
2303
|
if (result.usage) {
|
|
@@ -1933,6 +2321,16 @@ export class ClaudeSubagentExecutor {
|
|
|
1933
2321
|
nestedToolUseCount > 0) {
|
|
1934
2322
|
toolUseCount += nestedToolUseCount;
|
|
1935
2323
|
}
|
|
2324
|
+
if (nativeLease) {
|
|
2325
|
+
await nativeLease.appendToolCompletion({
|
|
2326
|
+
callId: call.id,
|
|
2327
|
+
result,
|
|
2328
|
+
...(result.followUpUserMessages?.length
|
|
2329
|
+
? { followUpUserMessages: result.followUpUserMessages }
|
|
2330
|
+
: {}),
|
|
2331
|
+
});
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
1936
2334
|
const [entry] = translateProviderEvents([
|
|
1937
2335
|
{
|
|
1938
2336
|
type: 'tool-result',
|
|
@@ -1954,16 +2352,19 @@ export class ClaudeSubagentExecutor {
|
|
|
1954
2352
|
], translationContext());
|
|
1955
2353
|
if (!entry)
|
|
1956
2354
|
throw new Error('Could not translate subagent tool result');
|
|
1957
|
-
await
|
|
2355
|
+
await appendClaude(toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType));
|
|
1958
2356
|
},
|
|
1959
2357
|
followUpUserMessagesCompleted: async (messages) => {
|
|
1960
2358
|
for (const text of messages) {
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2359
|
+
if (!nativeLease) {
|
|
2360
|
+
const [entry] = translateProviderEvents([{ type: 'user-text-block', text }], translationContext());
|
|
2361
|
+
if (!entry)
|
|
2362
|
+
throw new Error('Could not translate subagent follow-up');
|
|
2363
|
+
await appendClaude(toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType));
|
|
2364
|
+
}
|
|
1965
2365
|
await this.background.acknowledge([text]);
|
|
1966
2366
|
}
|
|
2367
|
+
await durableFollowUps.followUpUserMessagesCompleted(messages);
|
|
1967
2368
|
},
|
|
1968
2369
|
};
|
|
1969
2370
|
this.options.eventSink?.({
|
|
@@ -2031,11 +2432,16 @@ export class ClaudeSubagentExecutor {
|
|
|
2031
2432
|
});
|
|
2032
2433
|
const contextProjection = projectContextSnapshot(assembledContext);
|
|
2033
2434
|
stableSystemMessageCount = contextProjection.stableSystemSectionCount;
|
|
2435
|
+
const transcriptMessages = nativeLease
|
|
2436
|
+
? options.continuationMessage === undefined
|
|
2437
|
+
? nativeLease.activeMessages()
|
|
2438
|
+
: projectNativeSidechainContinuationMessages(nativeLease.activeMessages())
|
|
2439
|
+
: options.continuationMessage === undefined
|
|
2440
|
+
? projectClaudeModelMessages(requireClaudeSnapshot().entries)
|
|
2441
|
+
: projectClaudeSidechainContinuationMessages(requireClaudeSnapshot().entries);
|
|
2034
2442
|
const messages = [
|
|
2035
2443
|
...contextProjection.systemMessages,
|
|
2036
|
-
...injectFirstUserMessageContext(
|
|
2037
|
-
? projectClaudeModelMessages(snapshot.entries)
|
|
2038
|
-
: projectClaudeSidechainContinuationMessages(snapshot.entries), contextProjection.firstUserMessageContext),
|
|
2444
|
+
...injectFirstUserMessageContext(transcriptMessages, contextProjection.firstUserMessageContext),
|
|
2039
2445
|
...preloadedSkills,
|
|
2040
2446
|
];
|
|
2041
2447
|
observedMessages = messages;
|
|
@@ -2046,9 +2452,27 @@ export class ClaudeSubagentExecutor {
|
|
|
2046
2452
|
};
|
|
2047
2453
|
const configuredEffort = typeof customAgent?.effort === 'string' ? customAgent.effort : undefined;
|
|
2048
2454
|
const effectiveEffort = options.effort ?? configuredEffort;
|
|
2455
|
+
if (nativeRecoveryCalls.length > 0) {
|
|
2456
|
+
if (!nativeLease)
|
|
2457
|
+
throw new Error('Native sidechain recovery lease is unavailable');
|
|
2458
|
+
await runtime.recoverToolCalls(nativeRecoveryCalls, {
|
|
2459
|
+
cwd,
|
|
2460
|
+
toolResultDirectory: options.toolResultDirectory,
|
|
2461
|
+
messages: nativeLease.activeMessages(),
|
|
2462
|
+
observer,
|
|
2463
|
+
...(this.options.approveTool
|
|
2464
|
+
? { approveTool: this.options.approveTool }
|
|
2465
|
+
: {}),
|
|
2466
|
+
permissionUpdates: this.options.permissionUpdates?.() ?? [],
|
|
2467
|
+
...(this.options.onPermissionUpdates
|
|
2468
|
+
? { onPermissionUpdates: this.options.onPermissionUpdates }
|
|
2469
|
+
: {}),
|
|
2470
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2049
2473
|
const initialMessages = await assembleMessages();
|
|
2050
2474
|
const runtimeRequest = {
|
|
2051
|
-
sessionId:
|
|
2475
|
+
sessionId: options.sessionId,
|
|
2052
2476
|
messages: initialMessages,
|
|
2053
2477
|
collectMetrics: true,
|
|
2054
2478
|
stableSystemMessageCount,
|
|
@@ -2068,10 +2492,17 @@ export class ClaudeSubagentExecutor {
|
|
|
2068
2492
|
...(this.options.onPermissionUpdates
|
|
2069
2493
|
? { onPermissionUpdates: this.options.onPermissionUpdates }
|
|
2070
2494
|
: {}),
|
|
2071
|
-
...(scopedHooks ||
|
|
2495
|
+
...(scopedHooks ||
|
|
2496
|
+
this.options.backgroundTaskNotifications ||
|
|
2497
|
+
this.options.durableFollowUpSource
|
|
2072
2498
|
? {
|
|
2073
2499
|
onStop: async (text) => {
|
|
2074
2500
|
const messages = [];
|
|
2501
|
+
const durableBatch = await this.options.durableFollowUpSource?.();
|
|
2502
|
+
if (durableBatch) {
|
|
2503
|
+
durableFollowUps.register(durableBatch);
|
|
2504
|
+
messages.push(...durableBatch.messages);
|
|
2505
|
+
}
|
|
2075
2506
|
const outcome = await scopedHooks?.run({
|
|
2076
2507
|
...hookSession,
|
|
2077
2508
|
hook_event_name: 'SubagentStop',
|
|
@@ -2088,15 +2519,13 @@ export class ClaudeSubagentExecutor {
|
|
|
2088
2519
|
messages.push(`SubagentStop hook error: ${outcome.blockedReason}`);
|
|
2089
2520
|
}
|
|
2090
2521
|
}
|
|
2091
|
-
await this.hydratePersistedTasks(
|
|
2522
|
+
await this.hydratePersistedTasks(options.sessionId, options.cwd ?? this.cwd());
|
|
2092
2523
|
await this.reconcileDeliveredNotifications((notification) => {
|
|
2093
2524
|
const markers = backgroundAgentNotificationMarkers(notification);
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
return markers.every((marker) => source.includes(marker));
|
|
2099
|
-
});
|
|
2525
|
+
const source = JSON.stringify(nativeLease
|
|
2526
|
+
? nativeLease.activeMessages()
|
|
2527
|
+
: requireClaudeSnapshot().entries);
|
|
2528
|
+
return markers.every((marker) => source.includes(marker));
|
|
2100
2529
|
});
|
|
2101
2530
|
const background = await this.background.notifications({
|
|
2102
2531
|
waitForRunning: false,
|