maka-agent 0.2.0-dev.76.20261009 → 0.2.0-dev.77.20261009
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/runtime/dist/agent-graph-supervisor-wake.js +115 -17
- package/node_modules/@maka/runtime/dist/agent-run.js +11 -0
- package/node_modules/@maka/runtime/dist/computer-use-observation-text.js +22 -0
- package/node_modules/@maka/runtime/dist/conversation-copy-agent-output.js +227 -0
- package/node_modules/@maka/runtime/dist/conversation-copy.js +176 -46
- package/node_modules/@maka/runtime/dist/filesystem-worker/resource-resolver.js +3 -1
- package/node_modules/@maka/runtime/dist/owned-child-process.js +209 -0
- package/node_modules/@maka/runtime/dist/owned-process-main.js +183 -0
- package/node_modules/@maka/runtime/dist/pipe-process-driver.js +19 -25
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +124 -3
- package/node_modules/@maka/runtime/dist/session-manager.js +125 -12
- package/node_modules/@maka/runtime/dist/shell-exec.js +18 -20
- package/node_modules/@maka/runtime/dist/shell-run-manager.js +2 -1
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +279 -8
- package/node_modules/@maka/runtime/dist/stream-graph-schedule-reconcile.js +209 -37
- package/node_modules/@maka/runtime/dist/tool-availability.js +37 -6
- package/node_modules/@maka/runtime/dist/tool-runtime.js +2 -1
- package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +44 -13
- package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -0
- package/node_modules/@maka/runtime-host/dist/client/wsl-environment.js +4 -0
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +4 -1
- package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +46 -9
- package/node_modules/@maka/runtime-host/dist/server/candidate.js +47 -40
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +70 -24
- package/node_modules/@maka/runtime-host/dist/server/execution-service.js +5 -1
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +41 -5
- package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +20 -3
- package/node_modules/@maka/runtime-host/dist/server/session-background-activity.js +71 -0
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +8 -4
- package/node_modules/@maka/runtime-host/dist/server/session-interaction-activity.js +78 -0
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +56 -0
- package/node_modules/@maka/runtime-host/dist/server/session-revision-graph-references.js +16 -2
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +3 -1
- package/node_modules/@maka/storage/dist/context-offload-store.js +8 -3
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +54 -19
- package/package.json +2 -2
- /package/node_modules/@maka/runtime/dist/workers/{filesystem-worker.js → filesystem-worker.mjs} +0 -0
|
@@ -40,6 +40,8 @@ import { baseToolResultProjection, decodeLedgerTransition, reduceEffectiveModelP
|
|
|
40
40
|
import { archivedToolResultProjection } from './tool-result-archive-transition.js';
|
|
41
41
|
import { serializeToolResultProjectionV1 } from './tool-result-archive-encoding.js';
|
|
42
42
|
import { createHash } from 'node:crypto';
|
|
43
|
+
import { encodeCanonicalRuntimeEvent } from '@maka/core/canonical-runtime-event';
|
|
44
|
+
import { conversationCopyAgentOutput, isConversationCopyAgentOutputSnapshot, } from './conversation-copy-agent-output.js';
|
|
43
45
|
import { readToolResultPage, readableToolResult, resolveReadInput, READ_PAGE_MAX_BYTES, } from './read-page.js';
|
|
44
46
|
/** Walks every typed StorageRef site reached by conversation-copy rewriting. */
|
|
45
47
|
function collectConversationCopyStorageRefs(input) {
|
|
@@ -52,7 +54,7 @@ function collectConversationCopyStorageRefs(input) {
|
|
|
52
54
|
if (isArchivedToolResultPlaceholder(value))
|
|
53
55
|
return;
|
|
54
56
|
try {
|
|
55
|
-
addContent(
|
|
57
|
+
addContent(decodeConversationCopyToolResult(value));
|
|
56
58
|
}
|
|
57
59
|
catch {
|
|
58
60
|
// Opaque tool results carry no typed StorageRef.
|
|
@@ -141,7 +143,7 @@ export function rewriteConversationCopyMessage(message, references) {
|
|
|
141
143
|
if (message.type === 'tool_result') {
|
|
142
144
|
return {
|
|
143
145
|
...message,
|
|
144
|
-
content: rewriteToolResultContent(message.content, references),
|
|
146
|
+
content: rewriteToolResultContent(message.content, references, references.toolNamesByCall?.get(conversationCopyToolCallKey(message.turnId, message.toolUseId))),
|
|
145
147
|
};
|
|
146
148
|
}
|
|
147
149
|
if (message.type === 'token_usage' && message.providerRequestTraceId) {
|
|
@@ -153,7 +155,8 @@ export function rewriteConversationCopyMessage(message, references) {
|
|
|
153
155
|
return message;
|
|
154
156
|
}
|
|
155
157
|
function rewriteAttachmentResourceRefs(text, artifactIds) {
|
|
156
|
-
|
|
158
|
+
// Snapshot tool evidence can contain JSON-escaped quotes and newlines.
|
|
159
|
+
return text.replace(/maka:\/\/runtime\/attachments\/[^\\\s)\]}>`'",;:!]+/g, (candidate) => {
|
|
157
160
|
const parsed = parseAttachmentResourceRef(candidate);
|
|
158
161
|
const artifactId = parsed ? artifactIds.get(parsed.artifactId) : undefined;
|
|
159
162
|
return artifactId ? `maka://runtime/attachments/${artifactId}` : candidate;
|
|
@@ -320,6 +323,10 @@ export async function cloneConversationRuntimeLedger(input) {
|
|
|
320
323
|
const operationIds = toolOperationIdMap(flattenedPlans, targetInvocationIds);
|
|
321
324
|
const references = {
|
|
322
325
|
...input.referenceMap,
|
|
326
|
+
toolNamesByCall: conversationCopyToolNames({
|
|
327
|
+
messages: input.copiedMessages,
|
|
328
|
+
runtimeEvents: flattenedPlans.flatMap((plan) => plan.events),
|
|
329
|
+
}),
|
|
323
330
|
runIds,
|
|
324
331
|
invocationIds,
|
|
325
332
|
operationIds,
|
|
@@ -589,6 +596,14 @@ function rewriteReadInput(input, references, clonedEvents) {
|
|
|
589
596
|
: rewriteLedgerArchiveText(rewriteAttachmentResourceRefs(original, references.artifactIds), references);
|
|
590
597
|
if (!input.path.startsWith('maka://read/'))
|
|
591
598
|
return { ...input, path };
|
|
599
|
+
if (path !== original &&
|
|
600
|
+
!eventId &&
|
|
601
|
+
references.ledgerArchives?.has(original) &&
|
|
602
|
+
parseToolResultArchiveResourceRef(original)?.storage !== 'ledger') {
|
|
603
|
+
// A retired v1 body has become a rewritten ledger snapshot. Its previous
|
|
604
|
+
// byte offset and digest cannot address the new resource.
|
|
605
|
+
return { path };
|
|
606
|
+
}
|
|
592
607
|
const url = new URL(input.path);
|
|
593
608
|
if (eventId && path !== original) {
|
|
594
609
|
const copied = clonedEvents.get(eventId);
|
|
@@ -674,7 +689,21 @@ async function loadConversationCopyRunEvents(sourceRuns, sourceEvents, copyTurnI
|
|
|
674
689
|
];
|
|
675
690
|
}));
|
|
676
691
|
}
|
|
677
|
-
|
|
692
|
+
// Legacy archive bodies and provider projections can store the raw result;
|
|
693
|
+
// RuntimeEvents and transcript messages normally carry the JSON wrapper.
|
|
694
|
+
function decodeConversationCopyToolResult(value, toolName) {
|
|
695
|
+
if (toolName === 'agent_output' &&
|
|
696
|
+
(conversationCopyAgentOutput(value) || isConversationCopyAgentOutputSnapshot(value)))
|
|
697
|
+
return { kind: 'json', value };
|
|
698
|
+
return decodePersistedToolResultContent(markPersisted(value));
|
|
699
|
+
}
|
|
700
|
+
export function conversationCopyLinkedChildReferences(content, toolName) {
|
|
701
|
+
if (content.kind === 'json') {
|
|
702
|
+
if (toolName !== 'agent_output')
|
|
703
|
+
return [];
|
|
704
|
+
const output = conversationCopyAgentOutput(content.value);
|
|
705
|
+
return output ? [output.reference] : [];
|
|
706
|
+
}
|
|
678
707
|
if (content.kind === 'subagent') {
|
|
679
708
|
if (!content.childSessionId)
|
|
680
709
|
return [];
|
|
@@ -706,27 +735,56 @@ export function conversationCopyLinkedChildReferences(content) {
|
|
|
706
735
|
: []);
|
|
707
736
|
}
|
|
708
737
|
export function collectConversationCopyLinkedChildReferences(input) {
|
|
709
|
-
|
|
710
|
-
|
|
738
|
+
return conversationCopyToolResults(input).flatMap(({ content, toolName }) => conversationCopyLinkedChildReferences(content, toolName));
|
|
739
|
+
}
|
|
740
|
+
/** Pair transcript results with call metadata, including historical ledger-only calls. */
|
|
741
|
+
function conversationCopyToolNames(input) {
|
|
742
|
+
const names = new Map();
|
|
743
|
+
const add = (turnId, callId, name) => {
|
|
744
|
+
const key = conversationCopyToolCallKey(turnId, callId);
|
|
745
|
+
names.set(key, names.has(key) && names.get(key) !== name ? undefined : name);
|
|
746
|
+
};
|
|
747
|
+
for (const message of input.messages) {
|
|
748
|
+
if (message.type === 'tool_call')
|
|
749
|
+
add(message.turnId, message.id, message.toolName);
|
|
750
|
+
}
|
|
751
|
+
for (const event of input.runtimeEvents) {
|
|
752
|
+
if (event.content?.kind === 'function_call' || event.content?.kind === 'function_response') {
|
|
753
|
+
add(event.turnId, event.content.id, event.content.name);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return names;
|
|
757
|
+
}
|
|
758
|
+
function conversationCopyToolCallKey(turnId, callId) {
|
|
759
|
+
return JSON.stringify([turnId, callId]);
|
|
760
|
+
}
|
|
761
|
+
function conversationCopyToolResults(input) {
|
|
762
|
+
const results = [];
|
|
763
|
+
const toolNames = conversationCopyToolNames(input);
|
|
764
|
+
const add = (value, toolName) => {
|
|
711
765
|
if (isArchivedToolResultPlaceholder(value))
|
|
712
766
|
return;
|
|
713
767
|
try {
|
|
714
|
-
|
|
768
|
+
results.push({ content: decodeConversationCopyToolResult(value, toolName), toolName });
|
|
715
769
|
}
|
|
716
770
|
catch {
|
|
717
|
-
// Opaque tool results have no typed
|
|
771
|
+
// Opaque tool results have no typed conversation-copy references.
|
|
718
772
|
}
|
|
719
773
|
};
|
|
720
774
|
for (const message of input.messages) {
|
|
721
775
|
if (message.type === 'tool_result') {
|
|
722
|
-
|
|
776
|
+
results.push({
|
|
777
|
+
content: message.content,
|
|
778
|
+
toolName: toolNames.get(conversationCopyToolCallKey(message.turnId, message.toolUseId)),
|
|
779
|
+
});
|
|
723
780
|
}
|
|
724
781
|
}
|
|
725
782
|
for (const event of input.runtimeEvents) {
|
|
726
|
-
if (event.content?.kind === 'function_response')
|
|
727
|
-
add(event.content.result);
|
|
783
|
+
if (event.content?.kind === 'function_response') {
|
|
784
|
+
add(event.content.result, toolNames.get(conversationCopyToolCallKey(event.turnId, event.content.id)));
|
|
785
|
+
}
|
|
728
786
|
}
|
|
729
|
-
return
|
|
787
|
+
return results;
|
|
730
788
|
}
|
|
731
789
|
/**
|
|
732
790
|
* Collects the `relativePath` of every `session_file` StorageRef that the copied
|
|
@@ -737,6 +795,8 @@ export function collectConversationCopyLinkedChildReferences(input) {
|
|
|
737
795
|
* their refs resolve in `rewriteStorageRef`. Walks exactly the ref sites reached
|
|
738
796
|
* by `rewriteStorageRef`: user-message attachments, tool_result image refs, text
|
|
739
797
|
* runtime-event attachments, and function_response / archived tool-result images.
|
|
798
|
+
* Static agent_output snapshots also own their Artifacts; those can retain a
|
|
799
|
+
* child Turn id outside the copied parent's turn closure and need explicit inclusion.
|
|
740
800
|
*/
|
|
741
801
|
export function collectConversationCopySessionFileRefs(input) {
|
|
742
802
|
const refs = new Set();
|
|
@@ -745,6 +805,14 @@ export function collectConversationCopySessionFileRefs(input) {
|
|
|
745
805
|
refs.add(ref.relativePath);
|
|
746
806
|
}
|
|
747
807
|
}
|
|
808
|
+
for (const { content, toolName } of conversationCopyToolResults(input)) {
|
|
809
|
+
if (toolName === 'agent_output' &&
|
|
810
|
+
content.kind === 'json' &&
|
|
811
|
+
isConversationCopyAgentOutputSnapshot(content.value)) {
|
|
812
|
+
for (const artifactId of content.value.artifactIds)
|
|
813
|
+
refs.add(artifactId);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
748
816
|
return refs;
|
|
749
817
|
}
|
|
750
818
|
function cloneAgentRunEvent(event, ids, references, sourceCompactableEvents, clonedRuntimeEvents, checkpointIds, providerTraceIds, logicalCallIds, clonedTransitions) {
|
|
@@ -830,10 +898,9 @@ function cloneAgentRunEvent(event, ids, references, sourceCompactableEvents, clo
|
|
|
830
898
|
}
|
|
831
899
|
else if (event.type === MODEL_PROJECTION_TRANSITION_EVENT_TYPE) {
|
|
832
900
|
const cloned = clonedTransitions.get(event);
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
// cannot bring replaced content back.
|
|
901
|
+
// A transition is omitted only when its target was excluded or its archive
|
|
902
|
+
// wrapper was collapsed into an already copied archive. Neither case can
|
|
903
|
+
// bring replaced content back into the model-visible history.
|
|
837
904
|
if (!cloned)
|
|
838
905
|
return null;
|
|
839
906
|
data = { ...event.data, transition: cloned, runtimeEventId: cloned.target.runtimeEventId };
|
|
@@ -873,37 +940,57 @@ function cloneModelProjectionTransition(event, references, clonedRuntimeEvents,
|
|
|
873
940
|
}
|
|
874
941
|
const placeholder = rawPlaceholder;
|
|
875
942
|
const existing = transitionState.get(clonedTarget.id);
|
|
876
|
-
|
|
943
|
+
// RuntimeEvent storage canonicalizes JSON key order. Hash the bytes a later
|
|
944
|
+
// archive read will reconstruct, including newly built snapshot projections.
|
|
945
|
+
const sourceProjection = existing?.projection ??
|
|
946
|
+
baseToolResultProjection(encodeCanonicalRuntimeEvent(clonedTarget).event);
|
|
877
947
|
if (!sourceProjection) {
|
|
878
948
|
throw new Error(`Cannot copy model projection transition ${event.id} onto its target`);
|
|
879
949
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
950
|
+
const previousTransitionId = source.previousTransitionId
|
|
951
|
+
? requiredMappedId(transitionIds, source.previousTransitionId, 'model projection transition')
|
|
952
|
+
: undefined;
|
|
953
|
+
// Persisted placeholders can use a public event address or an exact legacy
|
|
954
|
+
// ledger identity. Both must resolve to the same rebuilt archive on recopy.
|
|
955
|
+
const sourceRefs = new Set([
|
|
956
|
+
buildToolResultArchiveResourceRef(placeholder),
|
|
957
|
+
...(placeholder.resourceRef ? [placeholder.resourceRef] : []),
|
|
958
|
+
]);
|
|
959
|
+
const previousPlaceholder = existing?.projection.kind === 'json' ? existing.projection.value : undefined;
|
|
960
|
+
if (existing &&
|
|
961
|
+
isArchivedToolResultPlaceholder(previousPlaceholder) &&
|
|
962
|
+
previousPlaceholder.rewriteVersion === 2) {
|
|
963
|
+
if (previousTransitionId !== existing.transitionId)
|
|
964
|
+
throw new Error(`Cannot collapse disconnected archive transition ${event.id}`);
|
|
965
|
+
// Public Read addresses identify an event, not a transition. Archiving
|
|
966
|
+
// this placeholder again would make that address return itself. Keep the
|
|
967
|
+
// first archive of the copied body and alias every later wrapper to it.
|
|
968
|
+
for (const sourceRef of sourceRefs)
|
|
969
|
+
references.ledgerArchives?.set(sourceRef, previousPlaceholder);
|
|
970
|
+
transitionIds.set(source.transitionId, existing.transitionId);
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
if (clonedTarget.content?.kind === 'function_response' && !clonedTarget.content.modelProjection)
|
|
974
|
+
clonedTarget.content.modelProjection = sourceProjection;
|
|
975
|
+
// Rebuild every archive over the copied ledger, including legacy v1 bodies.
|
|
883
976
|
const serialized = serializeToolResultProjectionV1(sourceProjection);
|
|
884
977
|
const rewritten = buildLedgerArchivedToolResultPlaceholder({
|
|
885
978
|
storage: 'ledger',
|
|
886
979
|
runtimeEventId: clonedTarget.id,
|
|
887
980
|
toolCallId: placeholder.toolCallId,
|
|
888
981
|
toolName: placeholder.toolName,
|
|
982
|
+
originalEstimatedTokens: placeholder.originalEstimatedTokens,
|
|
983
|
+
reason: placeholder.reason,
|
|
889
984
|
sourceProjectionDigest: durableToolResultProjectionDigest(sourceProjection),
|
|
890
985
|
bodySha256: createHash('sha256').update(serialized).digest('hex'),
|
|
891
986
|
originalBytes: Buffer.byteLength(serialized),
|
|
892
|
-
|
|
893
|
-
reason: placeholder.reason,
|
|
894
|
-
...(source.previousTransitionId
|
|
895
|
-
? {
|
|
896
|
-
previousTransitionId: requiredMappedId(transitionIds, source.previousTransitionId, 'model projection transition'),
|
|
897
|
-
}
|
|
898
|
-
: {}),
|
|
987
|
+
...(previousTransitionId ? { previousTransitionId } : {}),
|
|
899
988
|
});
|
|
900
989
|
if (placeholder.page) {
|
|
901
990
|
rewritten.page = readToolResultPage(serialized, { path: rewritten.resourceRef }, READ_PAGE_MAX_BYTES - Buffer.byteLength(JSON.stringify(rewritten)) - 32);
|
|
902
991
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
references.ledgerArchives?.set(placeholder.resourceRef ?? buildToolResultArchiveResourceRef(placeholder), rewritten);
|
|
906
|
-
}
|
|
992
|
+
for (const sourceRef of sourceRefs)
|
|
993
|
+
references.ledgerArchives?.set(sourceRef, rewritten);
|
|
907
994
|
const transition = buildModelProjectionTransition({
|
|
908
995
|
sessionId: references.targetSessionId,
|
|
909
996
|
target: {
|
|
@@ -917,11 +1004,7 @@ function cloneModelProjectionTransition(event, references, clonedRuntimeEvents,
|
|
|
917
1004
|
// The applied chain is copied in fold order, so a predecessor is always
|
|
918
1005
|
// rebuilt before its successor. An unmapped one means the chain broke, and
|
|
919
1006
|
// rooting the successor instead would change what the fold decides.
|
|
920
|
-
...(
|
|
921
|
-
? {
|
|
922
|
-
previousTransitionId: requiredMappedId(transitionIds, source.previousTransitionId, 'model projection transition'),
|
|
923
|
-
}
|
|
924
|
-
: {}),
|
|
1007
|
+
...(previousTransitionId ? { previousTransitionId } : {}),
|
|
925
1008
|
now: source.createdAt,
|
|
926
1009
|
});
|
|
927
1010
|
transitionIds.set(source.transitionId, transition.transitionId);
|
|
@@ -1135,6 +1218,9 @@ function rewriteInvocationOpening(opening, references) {
|
|
|
1135
1218
|
};
|
|
1136
1219
|
}
|
|
1137
1220
|
function rewriteRuntimeEventReferences(event, references) {
|
|
1221
|
+
const toolName = event.content?.kind === 'function_response'
|
|
1222
|
+
? references.toolNamesByCall?.get(conversationCopyToolCallKey(event.turnId, event.content.id))
|
|
1223
|
+
: undefined;
|
|
1138
1224
|
const content = event.content?.kind === 'text'
|
|
1139
1225
|
? {
|
|
1140
1226
|
...event.content,
|
|
@@ -1151,10 +1237,10 @@ function rewriteRuntimeEventReferences(event, references) {
|
|
|
1151
1237
|
: event.content?.kind === 'function_response'
|
|
1152
1238
|
? {
|
|
1153
1239
|
...event.content,
|
|
1154
|
-
result: rewriteRuntimeToolResult(event.content.result, references),
|
|
1240
|
+
result: rewriteRuntimeToolResult(event.content.result, references, toolName),
|
|
1155
1241
|
...(event.content.modelProjection
|
|
1156
1242
|
? {
|
|
1157
|
-
modelProjection:
|
|
1243
|
+
modelProjection: rewriteConversationCopyModelProjection(event.content.modelProjection, references, toolName),
|
|
1158
1244
|
}
|
|
1159
1245
|
: {}),
|
|
1160
1246
|
}
|
|
@@ -1249,7 +1335,24 @@ function rewriteToolRecoveryFact(recovery, operationId, references) {
|
|
|
1249
1335
|
},
|
|
1250
1336
|
};
|
|
1251
1337
|
}
|
|
1252
|
-
function rewriteToolResultContent(content, references) {
|
|
1338
|
+
function rewriteToolResultContent(content, references, toolName) {
|
|
1339
|
+
if (toolName === 'agent_output' && content.kind === 'json') {
|
|
1340
|
+
if (isConversationCopyAgentOutputSnapshot(content.value)) {
|
|
1341
|
+
return rewriteAgentOutputSnapshot(content.value, references);
|
|
1342
|
+
}
|
|
1343
|
+
const output = conversationCopyAgentOutput(content.value);
|
|
1344
|
+
if (output) {
|
|
1345
|
+
const shared = sharedChild(output.reference.childSessionId, references);
|
|
1346
|
+
if (!shared) {
|
|
1347
|
+
return rewriteAgentOutputSnapshot(output.snapshot, references);
|
|
1348
|
+
}
|
|
1349
|
+
// Revision children remain owned by their original physical Session.
|
|
1350
|
+
// Validate those external references without rewriting their identities.
|
|
1351
|
+
rewriteLinkedRunId(output.reference.runId, shared, references, 'AgentRun');
|
|
1352
|
+
rewriteLinkedArtifactIds(output.reference.artifactIds, shared, references);
|
|
1353
|
+
return content;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1253
1356
|
if (content.kind === 'image') {
|
|
1254
1357
|
return { ...content, ref: rewriteStorageRef(content.ref, references) };
|
|
1255
1358
|
}
|
|
@@ -1320,17 +1423,44 @@ function rewriteToolResultContent(content, references) {
|
|
|
1320
1423
|
}
|
|
1321
1424
|
return content;
|
|
1322
1425
|
}
|
|
1323
|
-
function
|
|
1426
|
+
function rewriteAgentOutputSnapshot(snapshot, references) {
|
|
1427
|
+
return {
|
|
1428
|
+
kind: 'json',
|
|
1429
|
+
value: {
|
|
1430
|
+
...snapshot,
|
|
1431
|
+
...(snapshot.text !== undefined
|
|
1432
|
+
? { text: rewriteAttachmentResourceRefs(snapshot.text, references.artifactIds) }
|
|
1433
|
+
: {}),
|
|
1434
|
+
artifactIds: rewriteArtifactIds(snapshot.artifactIds, references),
|
|
1435
|
+
},
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
function rewriteConversationCopyModelProjection(projection, references, toolName) {
|
|
1439
|
+
if (toolName === 'agent_output' &&
|
|
1440
|
+
projection.kind === 'json' &&
|
|
1441
|
+
(conversationCopyAgentOutput(projection.value) ||
|
|
1442
|
+
isConversationCopyAgentOutputSnapshot(projection.value))) {
|
|
1443
|
+
const content = rewriteToolResultContent({ kind: 'json', value: projection.value }, references, toolName);
|
|
1444
|
+
if (content.kind === 'json')
|
|
1445
|
+
return { ...projection, value: content.value };
|
|
1446
|
+
}
|
|
1447
|
+
return rewriteDurableToolResultProjectionArtifactRefs(projection, (ref) => rewriteProjectionArtifactRef(ref, references));
|
|
1448
|
+
}
|
|
1449
|
+
function rewriteRuntimeToolResult(value, references, toolName) {
|
|
1324
1450
|
if (isArchivedToolResultPlaceholder(value))
|
|
1325
1451
|
return rewriteArchivedToolResult(value, references);
|
|
1326
1452
|
let content;
|
|
1327
1453
|
try {
|
|
1328
|
-
content =
|
|
1454
|
+
content = decodeConversationCopyToolResult(value, toolName);
|
|
1329
1455
|
}
|
|
1330
1456
|
catch {
|
|
1331
1457
|
return value;
|
|
1332
1458
|
}
|
|
1333
|
-
|
|
1459
|
+
const rewritten = rewriteToolResultContent(content, references, toolName);
|
|
1460
|
+
return (conversationCopyAgentOutput(value) || isConversationCopyAgentOutputSnapshot(value)) &&
|
|
1461
|
+
rewritten.kind === 'json'
|
|
1462
|
+
? rewritten.value
|
|
1463
|
+
: rewritten;
|
|
1334
1464
|
}
|
|
1335
1465
|
function rewriteArtifactIds(artifactIds, references) {
|
|
1336
1466
|
return artifactIds.flatMap((artifactId) => {
|
|
@@ -1401,11 +1531,11 @@ function rewriteProjectionArtifactRef(ref, references) {
|
|
|
1401
1531
|
return rewritten;
|
|
1402
1532
|
}
|
|
1403
1533
|
function rewriteArchivedToolResult(value, references) {
|
|
1534
|
+
const migrated = references.ledgerArchives?.get(buildToolResultArchiveResourceRef(value));
|
|
1535
|
+
if (migrated)
|
|
1536
|
+
return migrated;
|
|
1404
1537
|
if (value.rewriteVersion === 2) {
|
|
1405
|
-
|
|
1406
|
-
if (!rewritten)
|
|
1407
|
-
throw new Error('Cannot copy ledger archive without its committed transition');
|
|
1408
|
-
return rewritten;
|
|
1538
|
+
throw new Error('Cannot copy ledger archive without its committed transition');
|
|
1409
1539
|
}
|
|
1410
1540
|
const artifactId = rewriteOwnedArtifactId(value.artifactId, references);
|
|
1411
1541
|
const resource = value.resourceRef
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
import { realpath, stat } from 'node:fs/promises';
|
|
20
20
|
import { dirname, join, resolve } from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
|
-
|
|
22
|
+
// `.mjs` so Node never looks for a package.json to decide the module type:
|
|
23
|
+
// the Windows AppContainer sandbox denies that read and the worker crashes.
|
|
24
|
+
export const FILESYSTEM_WORKER_BUNDLE_NAME = 'filesystem-worker.mjs';
|
|
23
25
|
export function filesystemWorkerBundleCandidate(location) {
|
|
24
26
|
if (location.kind === 'desktop-packaged') {
|
|
25
27
|
return join(location.resourcesPath, 'workers', FILESYSTEM_WORKER_BUNDLE_NAME);
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { buildSpawnStdio } from './child-fd-input.js';
|
|
22
|
+
import { terminateProcessTree } from './process-tree-terminator.js';
|
|
23
|
+
/** The returned PID owns the command group. Its private IPC channel is a
|
|
24
|
+
* lifetime lease: only the supervisor can spawn the command, and losing the
|
|
25
|
+
* owner tears down the group even when no Host cleanup callback can run.
|
|
26
|
+
* Output and descriptor payloads flow directly through inherited OS handles.
|
|
27
|
+
* `ready` resolves with the command's own PID once the supervisor admits it.
|
|
28
|
+
*/
|
|
29
|
+
export function spawnOwnedProcess(input) {
|
|
30
|
+
const stdio = buildSpawnStdio(input.fdInputs, input.stdin);
|
|
31
|
+
const child = spawn(process.execPath, [fileURLToPath(new URL('./owned-process-main.js', import.meta.url))], {
|
|
32
|
+
cwd: input.cwd,
|
|
33
|
+
// Caller-supplied environment belongs to the command, not this trusted supervisor.
|
|
34
|
+
env: {
|
|
35
|
+
...process.env,
|
|
36
|
+
NODE_OPTIONS: '',
|
|
37
|
+
...(process.versions.electron ? { ELECTRON_RUN_AS_NODE: '1' } : {}),
|
|
38
|
+
},
|
|
39
|
+
stdio: [...stdio, 'ipc'],
|
|
40
|
+
// POSIX: the supervisor leads a new process group that the command joins.
|
|
41
|
+
// Termination signals the group and removes descendants visible outside
|
|
42
|
+
// it at each process-table snapshot. Windows has no process groups;
|
|
43
|
+
// taskkill /T owns the equivalent cleanup. There, detaching also keeps
|
|
44
|
+
// the supervisor out of the Host's libuv job, which would kill it with
|
|
45
|
+
// the Host before it could stop the command's descendants; the command
|
|
46
|
+
// itself stays in the supervisor's job.
|
|
47
|
+
detached: true,
|
|
48
|
+
windowsHide: true,
|
|
49
|
+
});
|
|
50
|
+
let started = false;
|
|
51
|
+
let completed = false;
|
|
52
|
+
let failureReported = false;
|
|
53
|
+
let resolveReady;
|
|
54
|
+
let rejectReady;
|
|
55
|
+
const ready = new Promise((resolve, reject) => {
|
|
56
|
+
resolveReady = resolve;
|
|
57
|
+
rejectReady = reject;
|
|
58
|
+
});
|
|
59
|
+
// Some callers observe ChildProcess errors instead of awaiting admission.
|
|
60
|
+
void ready.catch(() => { });
|
|
61
|
+
const timer = setTimeout(() => fail(new Error('Command supervisor startup timed out')), 10_000);
|
|
62
|
+
timer.unref();
|
|
63
|
+
const terminate = () => {
|
|
64
|
+
if (child.pid)
|
|
65
|
+
void terminateProcessTree({ pid: child.pid, signal: 'SIGKILL' }).catch(() => { });
|
|
66
|
+
};
|
|
67
|
+
// Once the supervisor is reaped its PID can be reused, so a tree walk from it
|
|
68
|
+
// could reach unrelated processes. Its POSIX process group cannot be
|
|
69
|
+
// reallocated while members remain, so it alone still names the command.
|
|
70
|
+
// Windows has no such handle; taskkill /T on a reaped PID is skipped.
|
|
71
|
+
const terminateOrphanedGroup = () => {
|
|
72
|
+
if (process.platform === 'win32' || !child.pid)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// The group is already empty.
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
// Command output reaches these pipes directly, racing the IPC admission
|
|
82
|
+
// message. Hold it until callers have observed `ready`, so output never
|
|
83
|
+
// precedes admission, as with a direct spawn. Callers attach 'data'
|
|
84
|
+
// listeners synchronously; an explicit pause keeps those from resuming.
|
|
85
|
+
const outputs = [child.stdout, child.stderr].filter((stream) => stream !== null);
|
|
86
|
+
for (const stream of outputs)
|
|
87
|
+
stream.pause();
|
|
88
|
+
let outputReleased = false;
|
|
89
|
+
const releaseOutput = () => {
|
|
90
|
+
if (outputReleased)
|
|
91
|
+
return;
|
|
92
|
+
outputReleased = true;
|
|
93
|
+
for (const stream of outputs)
|
|
94
|
+
stream.resume();
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* `stop` says who stops the tree: `tree` walks it from the live supervisor;
|
|
98
|
+
* `group` signals only its process group, because the supervisor is exiting
|
|
99
|
+
* on its own and its PID may be reaped before a walk could start; `none`
|
|
100
|
+
* leaves the stop to the supervisor, which walks its own tree.
|
|
101
|
+
*/
|
|
102
|
+
function fail(error, stop = 'tree') {
|
|
103
|
+
if (failureReported)
|
|
104
|
+
return;
|
|
105
|
+
failureReported = true;
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
rejectReady(error);
|
|
108
|
+
releaseOutput();
|
|
109
|
+
if (stop === 'tree')
|
|
110
|
+
terminate();
|
|
111
|
+
else if (stop === 'group')
|
|
112
|
+
terminateOrphanedGroup();
|
|
113
|
+
child.emit('error', error);
|
|
114
|
+
}
|
|
115
|
+
child.once('error', (error) => {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
rejectReady(error);
|
|
118
|
+
releaseOutput();
|
|
119
|
+
});
|
|
120
|
+
child.once('spawn', () => {
|
|
121
|
+
const request = {
|
|
122
|
+
kind: 'launch',
|
|
123
|
+
program: input.program,
|
|
124
|
+
args: input.args,
|
|
125
|
+
cwd: input.cwd,
|
|
126
|
+
env: input.env ?? process.env,
|
|
127
|
+
shell: input.shell,
|
|
128
|
+
inheritedFds: (input.fdInputs ?? []).map(({ fd }) => fd),
|
|
129
|
+
};
|
|
130
|
+
child.send(request, (error) => {
|
|
131
|
+
if (error && child.exitCode === null && child.signalCode === null)
|
|
132
|
+
fail(error);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
child.on('message', (value) => {
|
|
136
|
+
const message = value;
|
|
137
|
+
if (message.kind === 'started') {
|
|
138
|
+
started = true;
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
resolveReady(message.pid);
|
|
141
|
+
// Promise continuations of `ready` run before the check phase; a
|
|
142
|
+
// nextTick resume would deliver output ahead of them.
|
|
143
|
+
setImmediate(releaseOutput);
|
|
144
|
+
}
|
|
145
|
+
else if (message.kind === 'completed') {
|
|
146
|
+
completed = true;
|
|
147
|
+
}
|
|
148
|
+
else if (message.kind === 'failed') {
|
|
149
|
+
// A result already arrived; a later report cannot replace it.
|
|
150
|
+
if (completed)
|
|
151
|
+
return;
|
|
152
|
+
const error = Object.assign(new Error(message.message), { code: message.code });
|
|
153
|
+
if (!started) {
|
|
154
|
+
// The command never spawned; the supervisor is exiting on its own.
|
|
155
|
+
completed = true;
|
|
156
|
+
fail(error, 'group');
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// A supervisor fault after admission: it stops its own tree, including
|
|
160
|
+
// descendants that left its group, before exiting. Signalling the group
|
|
161
|
+
// now would kill it mid-walk. The group kill on its exit stays the
|
|
162
|
+
// backstop, so `completed` remains unset.
|
|
163
|
+
fail(error, 'none');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
child.once('exit', (code) => {
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
releaseOutput();
|
|
170
|
+
// `exit` is not ordered after IPC messages still being read; the channel's
|
|
171
|
+
// close is. Decide a missing result only once the channel has drained.
|
|
172
|
+
if (completed || !child.connected)
|
|
173
|
+
settleExit(code);
|
|
174
|
+
else
|
|
175
|
+
child.once('disconnect', () => settleExit(code));
|
|
176
|
+
});
|
|
177
|
+
function settleExit(code) {
|
|
178
|
+
if (!started)
|
|
179
|
+
rejectReady(new Error('Command supervisor exited before admission'));
|
|
180
|
+
if (completed)
|
|
181
|
+
return;
|
|
182
|
+
// The group can outlive its leader. An unexpected supervisor exit must
|
|
183
|
+
// not leave an admitted command running while the Host is still alive.
|
|
184
|
+
terminateOrphanedGroup();
|
|
185
|
+
// A clean exit without `completed` lost the command's result. On POSIX the
|
|
186
|
+
// Host's own stops end the supervisor by signal, so any exit code there is
|
|
187
|
+
// a supervisor fault too. On Windows taskkill, the Host's forced stop, also
|
|
188
|
+
// ends with an ordinary code.
|
|
189
|
+
const lost = code === 0 || (code !== null && process.platform !== 'win32');
|
|
190
|
+
if (lost && !failureReported) {
|
|
191
|
+
child.emit('error', new Error('Command supervisor lost its result'));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { child, ready };
|
|
195
|
+
}
|
|
196
|
+
/** A supervisor PID denotes the whole command group, including during startup. */
|
|
197
|
+
export function signalOwnedProcess(child, signal) {
|
|
198
|
+
if (process.platform === 'win32' || !child.pid)
|
|
199
|
+
return child.kill(signal);
|
|
200
|
+
try {
|
|
201
|
+
process.kill(-child.pid, signal);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
if (error.code === 'ESRCH')
|
|
206
|
+
return false;
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|