praxis-agent 0.34.0 → 0.36.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 +9 -1
- 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/session-service.d.ts +8 -5
- package/dist/application/session-service.js +1824 -1728
- package/dist/application/subagent-service.js +385 -193
- 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 +33 -0
- package/dist/application/transcript-projection.js +110 -0
- package/dist/application/turn-lifecycle.d.ts +40 -0
- package/dist/application/turn-lifecycle.js +36 -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.js +279 -209
- package/dist/cli/protocol.js +5 -2
- package/dist/cli/tui/claude-style.d.ts +5 -48
- package/dist/cli/tui/claude-style.js +167 -197
- 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 +9 -5
- 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-interaction-router.d.ts +78 -0
- package/dist/cli/tui/tui-interaction-router.js +127 -0
- package/dist/cli/tui/tui-view-model.d.ts +6 -2
- package/dist/cli/tui/tui-view-model.js +23 -6
- package/dist/cli-runtime.d.ts +2 -2
- 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/shared-resources.d.ts +7 -38
- package/dist/compatibility/claude/transcript-codec.d.ts +21 -0
- package/dist/compatibility/claude/transcript-codec.js +512 -0
- package/dist/core/agent-orchestration.d.ts +31 -0
- package/dist/core/agent-orchestration.js +235 -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 +6 -1
- package/dist/core/runtime.js +15 -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-job-store.d.ts +58 -0
- package/dist/persistence/claude-job-store.js +483 -1
- 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/native-transcript-reader.d.ts +53 -0
- package/dist/persistence/native-transcript-reader.js +283 -0
- package/dist/persistence/subagent-lifecycle-store.d.ts +41 -8
- package/dist/persistence/subagent-lifecycle-store.js +373 -146
- package/dist/platform/exclusive-file-lease.d.ts +3 -0
- package/dist/platform/exclusive-file-lease.js +3 -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
|
@@ -8,7 +8,8 @@ import { createClaudeAsyncAgentToolUseResult, createClaudeAgentToolUseResult, cr
|
|
|
8
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,7 +17,7 @@ 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';
|
|
19
|
-
import { SubagentLifecycleStore } from '../persistence/subagent-lifecycle-store.js';
|
|
20
|
+
import { SubagentLifecycleStore, } from '../persistence/subagent-lifecycle-store.js';
|
|
20
21
|
import { resolveDataPlanePaths, } from '../persistence/data-plane.js';
|
|
21
22
|
import { BackgroundAgentManager, BackgroundAgentRunError, BackgroundAgentShutdownError, backgroundAgentNotificationMarkers, } from './background-agent-manager.js';
|
|
22
23
|
import { isBackgroundBashTaskId } from './background-task-id.js';
|
|
@@ -715,11 +716,53 @@ export class ClaudeSubagentExecutor {
|
|
|
715
716
|
const agentId = `a${randomBytes(8).toString('hex')}`;
|
|
716
717
|
const parentCwd = context.cwd;
|
|
717
718
|
const paths = this.sessionPaths(sessionId, parentCwd);
|
|
718
|
-
const
|
|
719
|
-
|
|
719
|
+
const sidechainPaths = resolveClaudeSidechainPaths(paths.projectRoot, sessionId, agentId);
|
|
720
|
+
const lifecycleStore = this.options.persistence === 'memory'
|
|
721
|
+
? undefined
|
|
722
|
+
: new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId, sidechainPaths.transcriptFile);
|
|
723
|
+
const initialExecution = lifecycleStore
|
|
724
|
+
? await lifecycleStore.start()
|
|
720
725
|
: undefined;
|
|
726
|
+
let initialIsolation;
|
|
727
|
+
const settleInitialSetupFailure = async (primary, isolation) => {
|
|
728
|
+
const secondary = [];
|
|
729
|
+
const message = primary instanceof Error ? primary.message : String(primary);
|
|
730
|
+
try {
|
|
731
|
+
await initialExecution?.finish('failed', {
|
|
732
|
+
text: message,
|
|
733
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
734
|
+
toolUseCount: 0,
|
|
735
|
+
durationMs: 0,
|
|
736
|
+
}, message);
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
secondary.push(error);
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
await initialExecution?.release();
|
|
743
|
+
}
|
|
744
|
+
catch (error) {
|
|
745
|
+
secondary.push(error);
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
await isolation?.cleanup();
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
secondary.push(error);
|
|
752
|
+
}
|
|
753
|
+
if (secondary.length > 0)
|
|
754
|
+
throw new AggregateError([primary, ...secondary], 'Background agent setup and cleanup failed');
|
|
755
|
+
throw primary;
|
|
756
|
+
};
|
|
757
|
+
try {
|
|
758
|
+
initialIsolation = input.isolation
|
|
759
|
+
? await this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd)
|
|
760
|
+
: undefined;
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
return settleInitialSetupFailure(error, initialIsolation);
|
|
764
|
+
}
|
|
721
765
|
const agentCwd = initialIsolation?.cwd ?? parentCwd;
|
|
722
|
-
const sidechainPaths = resolveClaudeSidechainPaths(paths.projectRoot, sessionId, agentId);
|
|
723
766
|
const sidechain = this.sidechainStore(sessionId, agentId, sidechainPaths, paths.praxisRoot);
|
|
724
767
|
const root = createClaudeSidechainRoot({
|
|
725
768
|
sessionId,
|
|
@@ -748,19 +791,16 @@ export class ClaudeSubagentExecutor {
|
|
|
748
791
|
});
|
|
749
792
|
}
|
|
750
793
|
catch (error) {
|
|
751
|
-
|
|
752
|
-
throw error;
|
|
794
|
+
return settleInitialSetupFailure(error, initialIsolation);
|
|
753
795
|
}
|
|
754
796
|
const provider = input.model
|
|
755
797
|
? (this.options.providerForModel?.(input.model) ?? this.options.provider)
|
|
756
798
|
: this.options.provider;
|
|
757
|
-
const lifecycleStore = this.options.persistence === 'memory'
|
|
758
|
-
? undefined
|
|
759
|
-
: new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId, sidechainPaths.transcriptFile);
|
|
760
799
|
const backgroundRun = this.createBackgroundAgentRun({
|
|
761
800
|
input,
|
|
762
801
|
parentCwd,
|
|
763
802
|
...(lifecycleStore ? { lifecycle: lifecycleStore } : {}),
|
|
803
|
+
...(initialExecution ? { initialExecution } : {}),
|
|
764
804
|
...(initialIsolation ? { initialIsolation } : {}),
|
|
765
805
|
createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd),
|
|
766
806
|
execute: (cwd, message, signal, continuation) => sidechain.withLease(async (lease) => this.runSidechain({
|
|
@@ -789,6 +829,7 @@ export class ClaudeSubagentExecutor {
|
|
|
789
829
|
toolUseId: call.id,
|
|
790
830
|
outputFile: sidechainPaths.transcriptFile,
|
|
791
831
|
resolvedModel,
|
|
832
|
+
lifecycle: backgroundRun.lifecycle,
|
|
792
833
|
run: backgroundRun.run,
|
|
793
834
|
markBackground: backgroundRun.markBackground,
|
|
794
835
|
...(lifecycleStore
|
|
@@ -819,7 +860,18 @@ export class ClaudeSubagentExecutor {
|
|
|
819
860
|
},
|
|
820
861
|
});
|
|
821
862
|
if (input.runInBackground) {
|
|
822
|
-
|
|
863
|
+
try {
|
|
864
|
+
this.background.launch(spec);
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
try {
|
|
868
|
+
await backgroundRun.disposeBeforeStart();
|
|
869
|
+
}
|
|
870
|
+
catch (cleanupError) {
|
|
871
|
+
throw new AggregateError([error, cleanupError], 'Background agent launch and pre-start cleanup failed');
|
|
872
|
+
}
|
|
873
|
+
throw error;
|
|
874
|
+
}
|
|
823
875
|
return asyncResult();
|
|
824
876
|
}
|
|
825
877
|
const controller = new AbortController();
|
|
@@ -946,183 +998,288 @@ export class ClaudeSubagentExecutor {
|
|
|
946
998
|
let availableIsolation = options.initialIsolation;
|
|
947
999
|
let retainedIsolation;
|
|
948
1000
|
let notificationExpected = options.input.runInBackground;
|
|
1001
|
+
let recoverNext = options.recover ?? false;
|
|
1002
|
+
let pendingExecution = options.initialExecution;
|
|
1003
|
+
let disposed = false;
|
|
1004
|
+
let lifecycleState = options.initialExecution?.snapshot.state ??
|
|
1005
|
+
options.initialLifecycleState ??
|
|
1006
|
+
'queued';
|
|
1007
|
+
const lifecycleListeners = new Set();
|
|
1008
|
+
const publishLifecycle = (state) => {
|
|
1009
|
+
lifecycleState = state;
|
|
1010
|
+
if (state === 'orphaned')
|
|
1011
|
+
recoverNext = true;
|
|
1012
|
+
for (const listener of lifecycleListeners)
|
|
1013
|
+
listener(state);
|
|
1014
|
+
};
|
|
1015
|
+
const lifecycle = {
|
|
1016
|
+
current: () => lifecycleState,
|
|
1017
|
+
subscribe(listener) {
|
|
1018
|
+
lifecycleListeners.add(listener);
|
|
1019
|
+
return () => lifecycleListeners.delete(listener);
|
|
1020
|
+
},
|
|
1021
|
+
};
|
|
1022
|
+
const disposeBeforeStart = async () => {
|
|
1023
|
+
if (disposed)
|
|
1024
|
+
return;
|
|
1025
|
+
disposed = true;
|
|
1026
|
+
const execution = pendingExecution;
|
|
1027
|
+
pendingExecution = undefined;
|
|
1028
|
+
const isolation = availableIsolation;
|
|
1029
|
+
availableIsolation = undefined;
|
|
1030
|
+
const errors = [];
|
|
1031
|
+
if (execution) {
|
|
1032
|
+
try {
|
|
1033
|
+
const snapshot = await execution.finish('failed', {
|
|
1034
|
+
text: 'Background agent launch was rejected before execution started',
|
|
1035
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1036
|
+
toolUseCount: 0,
|
|
1037
|
+
durationMs: 0,
|
|
1038
|
+
}, 'Background agent launch was rejected before execution started');
|
|
1039
|
+
publishLifecycle(snapshot.state);
|
|
1040
|
+
}
|
|
1041
|
+
catch (error) {
|
|
1042
|
+
errors.push(error);
|
|
1043
|
+
}
|
|
1044
|
+
finally {
|
|
1045
|
+
try {
|
|
1046
|
+
await execution.release();
|
|
1047
|
+
publishLifecycle(execution.snapshot.state);
|
|
1048
|
+
}
|
|
1049
|
+
catch (error) {
|
|
1050
|
+
errors.push(error);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (isolation) {
|
|
1055
|
+
try {
|
|
1056
|
+
await isolation.cleanup();
|
|
1057
|
+
}
|
|
1058
|
+
catch (error) {
|
|
1059
|
+
errors.push(error);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (errors.length > 0) {
|
|
1063
|
+
throw new AggregateError(errors, 'Background agent pre-start cleanup failed');
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
949
1066
|
const run = async (message, signal, continuation, currentToolUseId) => {
|
|
950
|
-
|
|
1067
|
+
let execution;
|
|
1068
|
+
let cancellation;
|
|
1069
|
+
let lifecycleTerminalized = false;
|
|
1070
|
+
let onAbort;
|
|
951
1071
|
const startedAt = Date.now();
|
|
952
1072
|
let isolation;
|
|
953
|
-
try {
|
|
954
|
-
isolation = options.input.isolation
|
|
955
|
-
? (retainedIsolation ??
|
|
956
|
-
availableIsolation ??
|
|
957
|
-
(await options.createIsolation()))
|
|
958
|
-
: undefined;
|
|
959
|
-
}
|
|
960
|
-
catch (error) {
|
|
961
|
-
const failureMessage = error instanceof Error ? error.message : String(error);
|
|
962
|
-
const failureResult = {
|
|
963
|
-
text: failureMessage,
|
|
964
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
965
|
-
toolUseCount: 0,
|
|
966
|
-
durationMs: Date.now() - startedAt,
|
|
967
|
-
};
|
|
968
|
-
const status = signal.aborted ? 'killed' : 'failed';
|
|
969
|
-
const notificationId = notificationExpected &&
|
|
970
|
-
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
971
|
-
? randomUUID()
|
|
972
|
-
: undefined;
|
|
973
|
-
await options.lifecycle?.write(status, failureMessage, {
|
|
974
|
-
result: failureResult,
|
|
975
|
-
...(notificationId
|
|
976
|
-
? {
|
|
977
|
-
notification: {
|
|
978
|
-
id: notificationId,
|
|
979
|
-
status,
|
|
980
|
-
toolUseId: currentToolUseId,
|
|
981
|
-
error: failureMessage,
|
|
982
|
-
},
|
|
983
|
-
}
|
|
984
|
-
: {}),
|
|
985
|
-
});
|
|
986
|
-
if (!notificationExpected)
|
|
987
|
-
throw error;
|
|
988
|
-
throw new BackgroundAgentRunError(failureMessage, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, error);
|
|
989
|
-
}
|
|
990
|
-
availableIsolation = undefined;
|
|
991
1073
|
let result;
|
|
992
1074
|
let failure;
|
|
993
1075
|
let cleanup;
|
|
1076
|
+
const finishLifecycle = async (state, result, detail, notification) => {
|
|
1077
|
+
if (execution) {
|
|
1078
|
+
const snapshot = await execution.finish(state, result, detail, notification);
|
|
1079
|
+
publishLifecycle(snapshot.state);
|
|
1080
|
+
}
|
|
1081
|
+
else
|
|
1082
|
+
publishLifecycle(state);
|
|
1083
|
+
};
|
|
994
1084
|
try {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1085
|
+
if (pendingExecution) {
|
|
1086
|
+
execution = pendingExecution;
|
|
1087
|
+
pendingExecution = undefined;
|
|
1088
|
+
publishLifecycle(execution.snapshot.state);
|
|
1089
|
+
}
|
|
1090
|
+
else if (options.lifecycle) {
|
|
1091
|
+
execution = await options.lifecycle.acquire(recoverNext ? 'recover' : continuation ? 'continue' : 'start');
|
|
1092
|
+
publishLifecycle(execution.snapshot.state);
|
|
1093
|
+
recoverNext = false;
|
|
1094
|
+
}
|
|
1095
|
+
if (execution) {
|
|
1096
|
+
const snapshot = await execution.running();
|
|
1097
|
+
publishLifecycle(snapshot.state);
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
publishLifecycle('running');
|
|
1101
|
+
}
|
|
1102
|
+
onAbort = () => {
|
|
1103
|
+
if (cancellation)
|
|
1104
|
+
return;
|
|
1105
|
+
if (!execution) {
|
|
1106
|
+
cancellation = Promise.resolve(publishLifecycle('cancelling'));
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const activeExecution = execution;
|
|
1110
|
+
const transition = activeExecution.beginCancellation();
|
|
1111
|
+
transition.catch(() => undefined);
|
|
1112
|
+
const observedCancellation = transition
|
|
1113
|
+
.then((snapshot) => publishLifecycle(snapshot.state))
|
|
1114
|
+
.finally(() => publishLifecycle(activeExecution.snapshot.state));
|
|
1115
|
+
observedCancellation.catch(() => undefined);
|
|
1116
|
+
cancellation = observedCancellation;
|
|
1117
|
+
};
|
|
1118
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
1119
|
+
if (signal.aborted)
|
|
1120
|
+
onAbort();
|
|
1001
1121
|
try {
|
|
1002
|
-
|
|
1122
|
+
isolation = options.input.isolation
|
|
1123
|
+
? (retainedIsolation ??
|
|
1124
|
+
availableIsolation ??
|
|
1125
|
+
(await options.createIsolation()))
|
|
1126
|
+
: undefined;
|
|
1127
|
+
availableIsolation = undefined;
|
|
1128
|
+
result = await options.execute(isolation?.cwd ?? options.parentCwd, message, signal, continuation);
|
|
1003
1129
|
}
|
|
1004
1130
|
catch (error) {
|
|
1005
|
-
failure
|
|
1131
|
+
failure = error;
|
|
1006
1132
|
}
|
|
1007
|
-
|
|
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
|
-
|
|
1133
|
+
finally {
|
|
1134
|
+
try {
|
|
1135
|
+
cleanup = await isolation?.cleanup();
|
|
1136
|
+
}
|
|
1137
|
+
catch (error) {
|
|
1138
|
+
failure ??= error;
|
|
1139
|
+
}
|
|
1140
|
+
retainedIsolation = cleanup?.retained ? isolation : undefined;
|
|
1141
|
+
}
|
|
1142
|
+
if (failure !== undefined) {
|
|
1143
|
+
const underlyingFailure = failure instanceof SubagentExecutionFailure
|
|
1144
|
+
? failure.cause
|
|
1145
|
+
: failure;
|
|
1146
|
+
const failureMessage = underlyingFailure instanceof Error
|
|
1147
|
+
? underlyingFailure.message
|
|
1148
|
+
: String(underlyingFailure);
|
|
1149
|
+
const detail = cleanup?.reason
|
|
1150
|
+
? `${failureMessage}\n${cleanup.reason}`
|
|
1151
|
+
: failureMessage;
|
|
1152
|
+
const partialResult = failure instanceof SubagentExecutionFailure
|
|
1153
|
+
? failure.result
|
|
1154
|
+
: undefined;
|
|
1155
|
+
const failureResult = {
|
|
1156
|
+
...partialResult,
|
|
1157
|
+
text: failureMessage,
|
|
1158
|
+
usage: partialResult?.usage ?? {
|
|
1159
|
+
inputTokens: 0,
|
|
1160
|
+
outputTokens: 0,
|
|
1161
|
+
},
|
|
1162
|
+
toolUseCount: partialResult?.toolUseCount ?? 0,
|
|
1163
|
+
durationMs: Date.now() - startedAt,
|
|
1164
|
+
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1165
|
+
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1166
|
+
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1167
|
+
};
|
|
1168
|
+
await cancellation;
|
|
1169
|
+
const status = signal.aborted ? 'killed' : 'failed';
|
|
1170
|
+
const notificationId = notificationExpected &&
|
|
1171
|
+
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1172
|
+
? randomUUID()
|
|
1173
|
+
: undefined;
|
|
1174
|
+
await finishLifecycle(signal.aborted ? 'cancelled' : 'failed', failureResult, detail, notificationId
|
|
1041
1175
|
? {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
error: detail,
|
|
1047
|
-
},
|
|
1176
|
+
id: notificationId,
|
|
1177
|
+
status,
|
|
1178
|
+
toolUseId: currentToolUseId,
|
|
1179
|
+
error: detail,
|
|
1048
1180
|
}
|
|
1049
|
-
:
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1181
|
+
: undefined);
|
|
1182
|
+
lifecycleTerminalized = true;
|
|
1183
|
+
if (!notificationExpected && !isolation && !cleanup?.reason) {
|
|
1184
|
+
throw underlyingFailure;
|
|
1185
|
+
}
|
|
1186
|
+
throw new BackgroundAgentRunError(detail, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, underlyingFailure);
|
|
1053
1187
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1188
|
+
if (!result) {
|
|
1189
|
+
const error = new Error('Agent completed without a result');
|
|
1190
|
+
const failureResult = {
|
|
1191
|
+
text: error.message,
|
|
1192
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1193
|
+
toolUseCount: 0,
|
|
1194
|
+
durationMs: Date.now() - startedAt,
|
|
1195
|
+
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1196
|
+
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1197
|
+
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1198
|
+
};
|
|
1199
|
+
const notificationId = notificationExpected &&
|
|
1200
|
+
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1201
|
+
? randomUUID()
|
|
1202
|
+
: undefined;
|
|
1203
|
+
await cancellation;
|
|
1204
|
+
await finishLifecycle(signal.aborted ? 'cancelled' : 'failed', failureResult, error.message, notificationId
|
|
1205
|
+
? {
|
|
1206
|
+
id: notificationId,
|
|
1207
|
+
status: signal.aborted ? 'killed' : 'failed',
|
|
1208
|
+
toolUseId: currentToolUseId,
|
|
1209
|
+
error: error.message,
|
|
1210
|
+
}
|
|
1211
|
+
: undefined);
|
|
1212
|
+
lifecycleTerminalized = true;
|
|
1213
|
+
if (!notificationExpected)
|
|
1214
|
+
throw error;
|
|
1215
|
+
throw new BackgroundAgentRunError(error.message, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, error);
|
|
1216
|
+
}
|
|
1217
|
+
const finalResult = {
|
|
1218
|
+
...result,
|
|
1062
1219
|
durationMs: Date.now() - startedAt,
|
|
1063
1220
|
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1064
1221
|
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1065
1222
|
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1066
1223
|
};
|
|
1224
|
+
await cancellation;
|
|
1225
|
+
const status = signal.aborted ? 'killed' : 'completed';
|
|
1226
|
+
const detail = signal.aborted
|
|
1227
|
+
? 'Agent was aborted before terminal cleanup'
|
|
1228
|
+
: undefined;
|
|
1067
1229
|
const notificationId = notificationExpected &&
|
|
1068
1230
|
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1069
1231
|
? randomUUID()
|
|
1070
1232
|
: undefined;
|
|
1071
|
-
await
|
|
1072
|
-
result: failureResult,
|
|
1073
|
-
...(notificationId
|
|
1074
|
-
? {
|
|
1075
|
-
notification: {
|
|
1076
|
-
id: notificationId,
|
|
1077
|
-
status: 'failed',
|
|
1078
|
-
toolUseId: currentToolUseId,
|
|
1079
|
-
error: error.message,
|
|
1080
|
-
},
|
|
1081
|
-
}
|
|
1082
|
-
: {}),
|
|
1083
|
-
});
|
|
1084
|
-
if (!notificationExpected)
|
|
1085
|
-
throw error;
|
|
1086
|
-
throw new BackgroundAgentRunError(error.message, { ...failureResult, ...(notificationId ? { notificationId } : {}) }, error);
|
|
1087
|
-
}
|
|
1088
|
-
const finalResult = {
|
|
1089
|
-
...result,
|
|
1090
|
-
durationMs: Date.now() - startedAt,
|
|
1091
|
-
...(isolation ? { isolationPath: isolation.cwd } : {}),
|
|
1092
|
-
...(cleanup ? { isolationRetained: cleanup.retained } : {}),
|
|
1093
|
-
...(cleanup?.reason ? { isolationWarning: cleanup.reason } : {}),
|
|
1094
|
-
};
|
|
1095
|
-
const status = signal.aborted ? 'killed' : 'completed';
|
|
1096
|
-
const detail = signal.aborted
|
|
1097
|
-
? 'Agent was aborted before terminal cleanup'
|
|
1098
|
-
: undefined;
|
|
1099
|
-
const notificationId = notificationExpected &&
|
|
1100
|
-
!(signal.reason instanceof BackgroundAgentShutdownError)
|
|
1101
|
-
? randomUUID()
|
|
1102
|
-
: undefined;
|
|
1103
|
-
await options.lifecycle?.write(status, detail, {
|
|
1104
|
-
result: finalResult,
|
|
1105
|
-
...(notificationId
|
|
1233
|
+
await finishLifecycle(signal.aborted ? 'cancelled' : 'completed', finalResult, detail, notificationId
|
|
1106
1234
|
? {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
error: status === 'killed' ? (detail ?? null) : null,
|
|
1112
|
-
},
|
|
1235
|
+
id: notificationId,
|
|
1236
|
+
status,
|
|
1237
|
+
toolUseId: currentToolUseId,
|
|
1238
|
+
error: status === 'killed' ? (detail ?? null) : null,
|
|
1113
1239
|
}
|
|
1114
|
-
:
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1240
|
+
: undefined);
|
|
1241
|
+
lifecycleTerminalized = true;
|
|
1242
|
+
return {
|
|
1243
|
+
...finalResult,
|
|
1244
|
+
...(notificationId ? { notificationId } : {}),
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
finally {
|
|
1248
|
+
if (onAbort)
|
|
1249
|
+
signal.removeEventListener('abort', onAbort);
|
|
1250
|
+
if (!lifecycleTerminalized) {
|
|
1251
|
+
try {
|
|
1252
|
+
await execution?.release();
|
|
1253
|
+
}
|
|
1254
|
+
catch (releaseError) {
|
|
1255
|
+
this.options.eventSink?.({
|
|
1256
|
+
type: 'warning',
|
|
1257
|
+
message: `Background agent execution cleanup could not release its durable owner; durable owner reconciliation is required: ${releaseError instanceof Error ? releaseError.message : String(releaseError)}`,
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
finally {
|
|
1261
|
+
if (execution)
|
|
1262
|
+
publishLifecycle(execution.snapshot.state);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
try {
|
|
1267
|
+
await execution?.release();
|
|
1268
|
+
}
|
|
1269
|
+
finally {
|
|
1270
|
+
if (execution)
|
|
1271
|
+
publishLifecycle(execution.snapshot.state);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1120
1275
|
};
|
|
1121
1276
|
return {
|
|
1122
1277
|
run,
|
|
1123
1278
|
markBackground: () => {
|
|
1124
1279
|
notificationExpected = true;
|
|
1125
1280
|
},
|
|
1281
|
+
disposeBeforeStart,
|
|
1282
|
+
lifecycle,
|
|
1126
1283
|
};
|
|
1127
1284
|
}
|
|
1128
1285
|
async runWorkflowAgent(options) {
|
|
@@ -1404,7 +1561,27 @@ export class ClaudeSubagentExecutor {
|
|
|
1404
1561
|
lastAssistant.content.trim().length > 0 &&
|
|
1405
1562
|
(lastAssistant.toolCalls?.length ?? 0) === 0;
|
|
1406
1563
|
const lifecycleStore = new SubagentLifecycleStore(paths.praxisRoot, sessionId, agentId, sidechainPaths.transcriptFile);
|
|
1407
|
-
const
|
|
1564
|
+
const ownership = await lifecycleStore.reconcileOwnerLoss();
|
|
1565
|
+
if (ownership.owned)
|
|
1566
|
+
return;
|
|
1567
|
+
let lifecycle = await lifecycleStore.read();
|
|
1568
|
+
if (!lifecycle) {
|
|
1569
|
+
const seed = await lifecycleStore.start();
|
|
1570
|
+
await seed.running();
|
|
1571
|
+
if (completed) {
|
|
1572
|
+
await seed.finish('completed', {
|
|
1573
|
+
text: lastAssistant?.content ?? '',
|
|
1574
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1575
|
+
toolUseCount: 0,
|
|
1576
|
+
durationMs: 0,
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
await seed.release();
|
|
1580
|
+
lifecycle = await lifecycleStore.read();
|
|
1581
|
+
}
|
|
1582
|
+
if (!lifecycle) {
|
|
1583
|
+
throw new Error(`Unable to establish lifecycle state for background agent ${agentId}`);
|
|
1584
|
+
}
|
|
1408
1585
|
const lifecycleIsCurrent = lifecycle !== null && (await lifecycleStore.matchesTranscript(lifecycle));
|
|
1409
1586
|
if (lifecycleIsCurrent && lifecycle.status === 'completed' && !completed) {
|
|
1410
1587
|
throw new Error(`Background agent ${agentId} has completed lifecycle state but an incomplete sidechain`);
|
|
@@ -1483,6 +1660,8 @@ export class ClaudeSubagentExecutor {
|
|
|
1483
1660
|
input,
|
|
1484
1661
|
parentCwd: restoredCwd,
|
|
1485
1662
|
lifecycle: lifecycleStore,
|
|
1663
|
+
recover: lifecycle.lifecycle.state === 'orphaned',
|
|
1664
|
+
initialLifecycleState: lifecycle.lifecycle.state,
|
|
1486
1665
|
...(restoredIsolation ? { initialIsolation: restoredIsolation } : {}),
|
|
1487
1666
|
createIsolation: () => this.createAgentWorktree(paths.praxisRoot, sessionId, agentId, parentCwd),
|
|
1488
1667
|
execute: (cwd, message, signal, continuation) => sidechain.withLease(async (lease) => this.runSidechain({
|
|
@@ -1510,31 +1689,34 @@ export class ClaudeSubagentExecutor {
|
|
|
1510
1689
|
toolUseId,
|
|
1511
1690
|
outputFile: sidechainPaths.transcriptFile,
|
|
1512
1691
|
resolvedModel: provider.model ?? 'praxis/provider',
|
|
1692
|
+
lifecycle: backgroundRun.lifecycle,
|
|
1513
1693
|
run: backgroundRun.run,
|
|
1514
1694
|
markBackground: backgroundRun.markBackground,
|
|
1515
1695
|
acknowledgeNotification: (notificationId) => lifecycleStore.acknowledgeNotification(notificationId),
|
|
1516
1696
|
prepareNotificationDetached: (notificationId, model) => lifecycleStore.prepareNotificationDetached(notificationId, model),
|
|
1517
1697
|
confirmNotificationDetached: (notificationId) => lifecycleStore.confirmNotificationDetached(notificationId),
|
|
1518
1698
|
};
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1699
|
+
this.background.registerPersisted(spec, {
|
|
1700
|
+
...(lifecycleIsCurrent && lifecycle.result
|
|
1701
|
+
? { result: lifecycle.result }
|
|
1702
|
+
: completed
|
|
1703
|
+
? {
|
|
1704
|
+
result: {
|
|
1705
|
+
text: lastAssistant.content,
|
|
1706
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1707
|
+
toolUseCount: 0,
|
|
1708
|
+
durationMs: 0,
|
|
1709
|
+
},
|
|
1710
|
+
}
|
|
1711
|
+
: {}),
|
|
1712
|
+
...(lifecycleIsCurrent && lifecycle.status === 'failed'
|
|
1713
|
+
? { error: lifecycle.detail ?? 'Persisted agent failed' }
|
|
1714
|
+
: lifecycleIsCurrent && lifecycle.status === 'killed'
|
|
1715
|
+
? { error: lifecycle.detail ?? 'Persisted agent was killed' }
|
|
1716
|
+
: !completed && lifecycle.lifecycle.state === 'orphaned'
|
|
1717
|
+
? { error: 'Persisted agent was interrupted before completion' }
|
|
1718
|
+
: {}),
|
|
1719
|
+
});
|
|
1538
1720
|
for (const notification of lifecycle?.notifications ?? []) {
|
|
1539
1721
|
if (notification.consumed)
|
|
1540
1722
|
continue;
|
|
@@ -1552,7 +1734,7 @@ export class ClaudeSubagentExecutor {
|
|
|
1552
1734
|
}
|
|
1553
1735
|
this.background.registerPersistedNotification(agentId, {
|
|
1554
1736
|
id: notification.id,
|
|
1555
|
-
status: notification.status === 'killed' ? '
|
|
1737
|
+
status: notification.status === 'killed' ? 'cancelled' : notification.status,
|
|
1556
1738
|
result: notification.result,
|
|
1557
1739
|
error: notification.error,
|
|
1558
1740
|
toolUseId: notification.toolUseId,
|
|
@@ -1995,9 +2177,6 @@ export class ClaudeSubagentExecutor {
|
|
|
1995
2177
|
const agentSystem = memoryPrompt
|
|
1996
2178
|
? `${baseSystem}\n\n${memoryPrompt}`
|
|
1997
2179
|
: baseSystem;
|
|
1998
|
-
const system = options.outputSchema
|
|
1999
|
-
? `${agentSystem}\n\nYou MUST call StructuredOutput exactly once at the end with a value matching its schema.`
|
|
2000
|
-
: agentSystem;
|
|
2001
2180
|
const preloadedSkills = agentSkillMessages(this.options.extensions, customAgent);
|
|
2002
2181
|
let stopHookActive = false;
|
|
2003
2182
|
const contextBudget = options.provider.capabilities.contextWindowTokens
|
|
@@ -2016,22 +2195,28 @@ export class ClaudeSubagentExecutor {
|
|
|
2016
2195
|
? runtimeTools.definitions()
|
|
2017
2196
|
: [];
|
|
2018
2197
|
let observedMessages;
|
|
2198
|
+
let stableSystemMessageCount = 0;
|
|
2019
2199
|
const assembleMessages = async () => {
|
|
2020
|
-
const assembledContext = await this.options.contextAssembler
|
|
2200
|
+
const assembledContext = await assembleContextSnapshot(this.options.contextAssembler, {
|
|
2021
2201
|
cwd,
|
|
2022
2202
|
lifecycleId: options.agentId,
|
|
2023
2203
|
mode: 'subagent',
|
|
2024
|
-
baseSystemPrompt:
|
|
2204
|
+
baseSystemPrompt: agentSystem,
|
|
2205
|
+
...(options.outputSchema
|
|
2206
|
+
? {
|
|
2207
|
+
turn: {
|
|
2208
|
+
structuredOutput: true,
|
|
2209
|
+
},
|
|
2210
|
+
}
|
|
2211
|
+
: {}),
|
|
2025
2212
|
});
|
|
2026
|
-
const
|
|
2213
|
+
const contextProjection = projectContextSnapshot(assembledContext);
|
|
2214
|
+
stableSystemMessageCount = contextProjection.stableSystemSectionCount;
|
|
2027
2215
|
const messages = [
|
|
2028
|
-
...
|
|
2029
|
-
...(composedSubagentPolicy
|
|
2030
|
-
? []
|
|
2031
|
-
: [{ role: 'system', content: system }]),
|
|
2216
|
+
...contextProjection.systemMessages,
|
|
2032
2217
|
...injectFirstUserMessageContext(options.continuationMessage === undefined
|
|
2033
2218
|
? projectClaudeModelMessages(snapshot.entries)
|
|
2034
|
-
: projectClaudeSidechainContinuationMessages(snapshot.entries),
|
|
2219
|
+
: projectClaudeSidechainContinuationMessages(snapshot.entries), contextProjection.firstUserMessageContext),
|
|
2035
2220
|
...preloadedSkills,
|
|
2036
2221
|
];
|
|
2037
2222
|
observedMessages = messages;
|
|
@@ -2042,11 +2227,17 @@ export class ClaudeSubagentExecutor {
|
|
|
2042
2227
|
};
|
|
2043
2228
|
const configuredEffort = typeof customAgent?.effort === 'string' ? customAgent.effort : undefined;
|
|
2044
2229
|
const effectiveEffort = options.effort ?? configuredEffort;
|
|
2045
|
-
const
|
|
2230
|
+
const initialMessages = await assembleMessages();
|
|
2231
|
+
const runtimeRequest = {
|
|
2046
2232
|
sessionId: String(options.root.sessionId),
|
|
2047
|
-
messages:
|
|
2233
|
+
messages: initialMessages,
|
|
2048
2234
|
collectMetrics: true,
|
|
2049
|
-
|
|
2235
|
+
stableSystemMessageCount,
|
|
2236
|
+
reloadMessages: async () => {
|
|
2237
|
+
const messages = await assembleMessages();
|
|
2238
|
+
runtimeRequest.stableSystemMessageCount = stableSystemMessageCount;
|
|
2239
|
+
return messages;
|
|
2240
|
+
},
|
|
2050
2241
|
cwd,
|
|
2051
2242
|
...(effectiveEffort ? { effort: effectiveEffort } : {}),
|
|
2052
2243
|
toolResultDirectory: options.toolResultDirectory,
|
|
@@ -2119,7 +2310,8 @@ export class ClaudeSubagentExecutor {
|
|
|
2119
2310
|
}
|
|
2120
2311
|
: {}),
|
|
2121
2312
|
...(options.signal ? { signal: options.signal } : {}),
|
|
2122
|
-
}
|
|
2313
|
+
};
|
|
2314
|
+
const result = await runtime.run(runtimeRequest);
|
|
2123
2315
|
contextBudget?.observeUsage(result.usage, observedMessages ?? [], definitions);
|
|
2124
2316
|
this.options.eventSink?.({
|
|
2125
2317
|
type: 'task-progress',
|