@tt-a1i/openpi 0.1.0 → 0.1.1
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/LICENSE +21 -0
- package/README.md +281 -390
- package/SETUP.md +20 -20
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/assets/readme-hero-mobile.svg +2 -2
- package/assets/readme-hero.svg +10 -10
- package/extensions/setup/index.ts +149 -39
- package/extensions/setup/intercom-fs-helper.cjs +130 -0
- package/extensions/setup/intercom.ts +603 -0
- package/extensions/shared/child-session.ts +2 -1
- package/extensions/shared/setup-config.ts +5 -1
- package/extensions/subagents/index.ts +1 -1
- package/extensions/workflows/artifacts.ts +6 -1
- package/extensions/workflows/dashboard.ts +138 -27
- package/extensions/workflows/graph-projection.ts +238 -0
- package/extensions/workflows/handoff.ts +194 -0
- package/extensions/workflows/index.ts +242 -55
- package/extensions/workflows/invocation-ledger.ts +362 -0
- package/extensions/workflows/model.ts +52 -0
- package/extensions/workflows/operator.ts +131 -0
- package/extensions/workflows/prompt.ts +8 -6
- package/extensions/workflows/runner.ts +10 -2
- package/extensions/workflows/sandbox.ts +5 -0
- package/package.json +6 -6
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
getMarkdownTheme,
|
|
39
39
|
keyHint,
|
|
40
40
|
type ExtensionAPI,
|
|
41
|
+
type SessionManager,
|
|
41
42
|
type ExtensionContext,
|
|
42
43
|
} from "@earendil-works/pi-coding-agent";
|
|
43
44
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
@@ -62,6 +63,17 @@ import {
|
|
|
62
63
|
loadJournal,
|
|
63
64
|
persistWorkflowJson,
|
|
64
65
|
} from "./artifacts.ts";
|
|
66
|
+
import { createWorkflowHandoffRegistry } from "./handoff.ts";
|
|
67
|
+
import {
|
|
68
|
+
classifyInterruptedInvocation,
|
|
69
|
+
createInvocationIdentity,
|
|
70
|
+
requestInvocation,
|
|
71
|
+
transitionInvocation,
|
|
72
|
+
} from "./invocation-ledger.ts";
|
|
73
|
+
import {
|
|
74
|
+
normalizeWorkflowOperatorKey,
|
|
75
|
+
WorkflowOperatorRegistry,
|
|
76
|
+
} from "./operator.ts";
|
|
65
77
|
import {
|
|
66
78
|
agentCallKey,
|
|
67
79
|
createReplayCache,
|
|
@@ -71,6 +83,7 @@ import {
|
|
|
71
83
|
import { RunController } from "./controller.ts";
|
|
72
84
|
import {
|
|
73
85
|
normalizePersistedWorkflowDetails,
|
|
86
|
+
recoverStaleWorkflowDetails,
|
|
74
87
|
sessionWorkflowRunIds,
|
|
75
88
|
showWorkflowDashboard,
|
|
76
89
|
} from "./dashboard.ts";
|
|
@@ -98,6 +111,7 @@ import {
|
|
|
98
111
|
statusColor,
|
|
99
112
|
statusWord,
|
|
100
113
|
createUsageReader,
|
|
114
|
+
refreshWorkflowGraph,
|
|
101
115
|
SQUARE,
|
|
102
116
|
type AgentRecord,
|
|
103
117
|
type WorkflowDetails,
|
|
@@ -166,6 +180,8 @@ interface ScriptAgentResult {
|
|
|
166
180
|
ok: boolean;
|
|
167
181
|
output: string;
|
|
168
182
|
structured?: unknown;
|
|
183
|
+
/** Opaque same-run handle for bounded downstream handoff. */
|
|
184
|
+
ref?: string;
|
|
169
185
|
acceptance?: AgentRecord["acceptance"];
|
|
170
186
|
error?: string;
|
|
171
187
|
}
|
|
@@ -180,6 +196,10 @@ interface AgentCallOptions {
|
|
|
180
196
|
provider?: unknown;
|
|
181
197
|
effort?: unknown;
|
|
182
198
|
isolation?: unknown;
|
|
199
|
+
/** Reuse one in-memory child Session within this workflow run. */
|
|
200
|
+
operator?: unknown;
|
|
201
|
+
/** Same-run result refs to hydrate as bounded untrusted input data. */
|
|
202
|
+
inputs?: unknown;
|
|
183
203
|
}
|
|
184
204
|
|
|
185
205
|
const WorkflowParams = Type.Object({
|
|
@@ -745,6 +765,8 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
745
765
|
workflowConfig.concurrency,
|
|
746
766
|
workflowConfig.maxAgentCalls,
|
|
747
767
|
);
|
|
768
|
+
const handoffs = createWorkflowHandoffRegistry();
|
|
769
|
+
const operators = new WorkflowOperatorRegistry();
|
|
748
770
|
|
|
749
771
|
// Each concurrent child gets its own extension runtime. Children use the
|
|
750
772
|
// parent's live trust decision; an isolated child gets its own cwd (its
|
|
@@ -779,6 +801,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
779
801
|
};
|
|
780
802
|
const emit = (checkpoint = true) => {
|
|
781
803
|
if (runSettled) return;
|
|
804
|
+
refreshWorkflowGraph(details);
|
|
782
805
|
if (checkpoint) persistence.checkpoint();
|
|
783
806
|
if (emitTimer) return;
|
|
784
807
|
emitTimer = setTimeout(
|
|
@@ -809,6 +832,16 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
809
832
|
);
|
|
810
833
|
for (const record of details.agents) {
|
|
811
834
|
if (record.state !== "running") continue;
|
|
835
|
+
if (
|
|
836
|
+
record.invocation &&
|
|
837
|
+
record.invocation.executionState !== "settled" &&
|
|
838
|
+
record.invocation.executionState !== "uncertain"
|
|
839
|
+
) {
|
|
840
|
+
record.invocation = classifyInterruptedInvocation(
|
|
841
|
+
record.invocation,
|
|
842
|
+
Date.now(),
|
|
843
|
+
);
|
|
844
|
+
}
|
|
812
845
|
record.state = "error";
|
|
813
846
|
record.error =
|
|
814
847
|
record.error ?? "Agent did not settle before run cleanup";
|
|
@@ -816,6 +849,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
816
849
|
}
|
|
817
850
|
details.status = status;
|
|
818
851
|
details.finishedAt = Date.now();
|
|
852
|
+
refreshWorkflowGraph(details);
|
|
819
853
|
if (error) details.error = sanitizeWorkflowDisplayLine(error);
|
|
820
854
|
return true;
|
|
821
855
|
};
|
|
@@ -874,8 +908,15 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
874
908
|
error: "Workflow was aborted before this agent started",
|
|
875
909
|
};
|
|
876
910
|
}
|
|
911
|
+
const startedAt = Date.now();
|
|
912
|
+
const callId = `${details.runId}:call:${index}`;
|
|
877
913
|
const record: AgentRecord = {
|
|
878
914
|
index,
|
|
915
|
+
callId,
|
|
916
|
+
invocation: requestInvocation(
|
|
917
|
+
createInvocationIdentity(details.runId, index),
|
|
918
|
+
startedAt,
|
|
919
|
+
),
|
|
879
920
|
label,
|
|
880
921
|
phase:
|
|
881
922
|
typeof opts.phase === "string"
|
|
@@ -884,20 +925,49 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
884
925
|
state: "running",
|
|
885
926
|
model: ctx.model?.id,
|
|
886
927
|
contextWindow: ctx.model?.contextWindow,
|
|
887
|
-
startedAt
|
|
928
|
+
startedAt,
|
|
888
929
|
preview: "",
|
|
889
930
|
usage: emptyUsage(),
|
|
890
931
|
transcript: [],
|
|
891
932
|
};
|
|
892
933
|
details.agents.push(record);
|
|
934
|
+
refreshWorkflowGraph(details);
|
|
893
935
|
persistence.checkpoint({ immediate: true });
|
|
894
936
|
emit(false);
|
|
895
937
|
|
|
896
938
|
const fail = (error: string): ScriptAgentResult => {
|
|
897
939
|
if (record.state === "running" && !runSettled) {
|
|
940
|
+
const at = Date.now();
|
|
941
|
+
if (
|
|
942
|
+
record.invocation?.admissionState === "pending" &&
|
|
943
|
+
record.invocation.executionState === "pending"
|
|
944
|
+
) {
|
|
945
|
+
record.invocation = transitionInvocation(record.invocation, {
|
|
946
|
+
status: "rejected",
|
|
947
|
+
at,
|
|
948
|
+
});
|
|
949
|
+
} else if (
|
|
950
|
+
record.invocation?.admissionState === "claimed" &&
|
|
951
|
+
record.invocation.executionState === "running"
|
|
952
|
+
) {
|
|
953
|
+
record.invocation = transitionInvocation(record.invocation, {
|
|
954
|
+
status: "settled",
|
|
955
|
+
outcome: "error",
|
|
956
|
+
at,
|
|
957
|
+
});
|
|
958
|
+
} else if (
|
|
959
|
+
record.invocation &&
|
|
960
|
+
record.invocation.executionState !== "settled" &&
|
|
961
|
+
record.invocation.executionState !== "uncertain"
|
|
962
|
+
) {
|
|
963
|
+
record.invocation = classifyInterruptedInvocation(
|
|
964
|
+
record.invocation,
|
|
965
|
+
at,
|
|
966
|
+
);
|
|
967
|
+
}
|
|
898
968
|
record.state = "error";
|
|
899
969
|
record.error = sanitizeWorkflowDisplayLine(error);
|
|
900
|
-
record.finishedAt =
|
|
970
|
+
record.finishedAt = at;
|
|
901
971
|
emit();
|
|
902
972
|
}
|
|
903
973
|
return { ok: false, output: "", error };
|
|
@@ -909,6 +979,45 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
909
979
|
: String(promptValue ?? "");
|
|
910
980
|
if (!basePrompt.trim())
|
|
911
981
|
return fail("agent() requires a non-empty prompt string");
|
|
982
|
+
|
|
983
|
+
let operatorKey: string | undefined;
|
|
984
|
+
try {
|
|
985
|
+
if (opts.operator !== undefined) {
|
|
986
|
+
operatorKey = normalizeWorkflowOperatorKey(String(opts.operator));
|
|
987
|
+
if (opts.isolation !== undefined) {
|
|
988
|
+
throw new Error(
|
|
989
|
+
"workflow operators cannot use per-call worktree isolation",
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
record.operatorKey = operatorKey;
|
|
993
|
+
}
|
|
994
|
+
} catch (error) {
|
|
995
|
+
return fail(`agent "${label}": ${errorText(error)}`);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
let inputRefs: string[] = [];
|
|
999
|
+
let promptWithHandoffs = basePrompt;
|
|
1000
|
+
try {
|
|
1001
|
+
if (opts.inputs !== undefined) {
|
|
1002
|
+
if (
|
|
1003
|
+
!Array.isArray(opts.inputs) ||
|
|
1004
|
+
!opts.inputs.every((value) => typeof value === "string")
|
|
1005
|
+
) {
|
|
1006
|
+
throw new Error(
|
|
1007
|
+
"inputs must be an array of workflow result refs",
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
inputRefs = [...opts.inputs];
|
|
1011
|
+
}
|
|
1012
|
+
const entries = handoffs.resolveEntries(inputRefs);
|
|
1013
|
+
record.inputCallIds = entries.flatMap((entry) =>
|
|
1014
|
+
entry.callId ? [entry.callId] : [],
|
|
1015
|
+
);
|
|
1016
|
+
promptWithHandoffs = handoffs.appendToPrompt(basePrompt, inputRefs);
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
return fail(`agent "${label}": ${errorText(error)}`);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
912
1021
|
let acceptanceContract: ReturnType<typeof parseAcceptanceContract>;
|
|
913
1022
|
let effectiveSchema: unknown;
|
|
914
1023
|
try {
|
|
@@ -921,8 +1030,8 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
921
1030
|
}
|
|
922
1031
|
const prompt = buildWorkflowAgentPrompt(
|
|
923
1032
|
acceptanceContract
|
|
924
|
-
? `${
|
|
925
|
-
:
|
|
1033
|
+
? `${promptWithHandoffs}\n\n${acceptanceInstruction(acceptanceContract)}`
|
|
1034
|
+
: promptWithHandoffs,
|
|
926
1035
|
);
|
|
927
1036
|
if (controller.signal.aborted)
|
|
928
1037
|
return fail("Workflow was aborted before this agent started");
|
|
@@ -1012,16 +1121,36 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1012
1121
|
}
|
|
1013
1122
|
record.model = model?.id;
|
|
1014
1123
|
record.contextWindow = model?.contextWindow;
|
|
1124
|
+
const operatorFingerprint = operatorKey
|
|
1125
|
+
? agentCallKey("workflow-operator", {
|
|
1126
|
+
execution: {
|
|
1127
|
+
agentType: agentType
|
|
1128
|
+
? {
|
|
1129
|
+
name: agentType.name,
|
|
1130
|
+
body: agentType.body,
|
|
1131
|
+
tools: agentType.tools,
|
|
1132
|
+
}
|
|
1133
|
+
: undefined,
|
|
1134
|
+
model: model ? `${model.provider}/${model.id}` : undefined,
|
|
1135
|
+
effort: thinkingLevel,
|
|
1136
|
+
structured: effectiveSchema !== undefined,
|
|
1137
|
+
},
|
|
1138
|
+
})
|
|
1139
|
+
: undefined;
|
|
1015
1140
|
|
|
1016
1141
|
// Replay is deliberately narrower than execution: only a named type
|
|
1017
1142
|
// whose effective tool allowlist is entirely known read-only can be
|
|
1018
1143
|
// cached. General-purpose children inherit bash/edit/write, custom
|
|
1019
1144
|
// tools have unknown effects, and worktrees have state a string result
|
|
1020
1145
|
// cannot restore.
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1146
|
+
// A reused operator's later activation depends on prior in-memory
|
|
1147
|
+
// conversation state, which a cached string cannot reconstruct.
|
|
1148
|
+
const replaySafe =
|
|
1149
|
+
operatorKey === undefined &&
|
|
1150
|
+
isReplaySafeAgentCall({
|
|
1151
|
+
tools: agentType?.tools,
|
|
1152
|
+
isolation: opts.isolation,
|
|
1153
|
+
});
|
|
1025
1154
|
const replayLease = beginProcessReplayWorkspaceLease(replaySafe);
|
|
1026
1155
|
let replayResources:
|
|
1027
1156
|
Awaited<ReturnType<typeof getResources>> | undefined;
|
|
@@ -1070,9 +1199,14 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1070
1199
|
const cached =
|
|
1071
1200
|
callKey && replayLease.canReplay ? replay?.take(callKey) : undefined;
|
|
1072
1201
|
if (cached) {
|
|
1202
|
+
const finishedAt = Date.now();
|
|
1203
|
+
record.invocation = transitionInvocation(record.invocation!, {
|
|
1204
|
+
status: "replayed",
|
|
1205
|
+
at: finishedAt,
|
|
1206
|
+
});
|
|
1073
1207
|
record.state = "done";
|
|
1074
1208
|
record.replayed = true;
|
|
1075
|
-
record.finishedAt =
|
|
1209
|
+
record.finishedAt = finishedAt;
|
|
1076
1210
|
record.preview = sanitizeWorkflowDisplayText(
|
|
1077
1211
|
cached.output,
|
|
1078
1212
|
PREVIEW_LENGTH,
|
|
@@ -1083,6 +1217,16 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1083
1217
|
cached.structured,
|
|
1084
1218
|
);
|
|
1085
1219
|
}
|
|
1220
|
+
const ref = handoffs.register({
|
|
1221
|
+
callId,
|
|
1222
|
+
settled: true,
|
|
1223
|
+
ok: true,
|
|
1224
|
+
output: cached.output,
|
|
1225
|
+
...(cached.structured !== undefined
|
|
1226
|
+
? { structured: cached.structured }
|
|
1227
|
+
: {}),
|
|
1228
|
+
});
|
|
1229
|
+
if (ref) record.resultRef = ref;
|
|
1086
1230
|
emit();
|
|
1087
1231
|
// Re-journal so a chain of resumes keeps working: run C resuming from
|
|
1088
1232
|
// B still finds what B replayed from A.
|
|
@@ -1094,12 +1238,24 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1094
1238
|
...(cached.structured !== undefined
|
|
1095
1239
|
? { structured: cached.structured }
|
|
1096
1240
|
: {}),
|
|
1241
|
+
...(ref ? { ref } : {}),
|
|
1097
1242
|
...(record.acceptance ? { acceptance: record.acceptance } : {}),
|
|
1098
1243
|
};
|
|
1099
1244
|
}
|
|
1100
1245
|
|
|
1101
1246
|
return controller
|
|
1102
1247
|
.schedule(async (runSignal) => {
|
|
1248
|
+
const claimedAt = Date.now();
|
|
1249
|
+
record.invocation = transitionInvocation(record.invocation!, {
|
|
1250
|
+
status: "claimed",
|
|
1251
|
+
at: claimedAt,
|
|
1252
|
+
});
|
|
1253
|
+
refreshWorkflowGraph(details);
|
|
1254
|
+
persistence.checkpoint({ immediate: true });
|
|
1255
|
+
record.invocation = transitionInvocation(record.invocation, {
|
|
1256
|
+
status: "running",
|
|
1257
|
+
at: Date.now(),
|
|
1258
|
+
});
|
|
1103
1259
|
record.model = model?.id;
|
|
1104
1260
|
record.contextWindow = model?.contextWindow;
|
|
1105
1261
|
emit();
|
|
@@ -1172,45 +1328,58 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1172
1328
|
if (runSettled) {
|
|
1173
1329
|
throw new Error("Workflow was settled before agent creation");
|
|
1174
1330
|
}
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
? {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1331
|
+
const runChild = (sessionManager?: SessionManager) =>
|
|
1332
|
+
runAgent({
|
|
1333
|
+
prompt,
|
|
1334
|
+
schema: effectiveSchema,
|
|
1335
|
+
model,
|
|
1336
|
+
thinkingLevel,
|
|
1337
|
+
// Replay-safe calls use the same canonical cwd as the
|
|
1338
|
+
// identity and filesystem boundary, so a symlink spelling of
|
|
1339
|
+
// the checkout cannot retarget relative tool paths.
|
|
1340
|
+
cwd: replayIdentity?.cwd ?? agentCwd,
|
|
1341
|
+
loader: resources.loader,
|
|
1342
|
+
settingsManager: resources.settingsManager,
|
|
1343
|
+
...(sessionManager ? { sessionManager } : {}),
|
|
1344
|
+
modelRegistry: ctx.modelRegistry,
|
|
1345
|
+
...(agentType?.tools ? { tools: agentType.tools } : {}),
|
|
1346
|
+
...(replayIdentity
|
|
1347
|
+
? {
|
|
1348
|
+
replayFilesystemBoundary: {
|
|
1349
|
+
repositoryRoot: replayIdentity.repositoryRoot,
|
|
1350
|
+
cwd: replayIdentity.cwd,
|
|
1351
|
+
onViolation: () => {
|
|
1352
|
+
replayBoundaryViolated = true;
|
|
1353
|
+
},
|
|
1195
1354
|
},
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
:
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1355
|
+
}
|
|
1356
|
+
: {}),
|
|
1357
|
+
signal: runSignal,
|
|
1358
|
+
onProgress: (progress) => {
|
|
1359
|
+
if (runSettled || record.state !== "running") return;
|
|
1360
|
+
record.preview = sanitizeWorkflowDisplayText(
|
|
1361
|
+
progress.preview,
|
|
1362
|
+
PREVIEW_LENGTH,
|
|
1363
|
+
);
|
|
1364
|
+
record.usage = progress.usage;
|
|
1365
|
+
record.model = progress.model ?? record.model;
|
|
1366
|
+
record.contextWindow =
|
|
1367
|
+
progress.contextWindow ?? record.contextWindow;
|
|
1368
|
+
record.transcript = progress.transcript;
|
|
1369
|
+
emit();
|
|
1370
|
+
},
|
|
1371
|
+
});
|
|
1372
|
+
const outcome = operatorKey
|
|
1373
|
+
? await operators.activate(
|
|
1374
|
+
{
|
|
1375
|
+
key: operatorKey,
|
|
1376
|
+
fingerprint: operatorFingerprint!,
|
|
1377
|
+
cwd: agentCwd,
|
|
1378
|
+
signal: runSignal,
|
|
1379
|
+
},
|
|
1380
|
+
runChild,
|
|
1381
|
+
)
|
|
1382
|
+
: await runChild();
|
|
1214
1383
|
|
|
1215
1384
|
if (runSettled || record.state !== "running") {
|
|
1216
1385
|
return {
|
|
@@ -1228,7 +1397,8 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1228
1397
|
outcome.output || record.preview,
|
|
1229
1398
|
PREVIEW_LENGTH,
|
|
1230
1399
|
);
|
|
1231
|
-
|
|
1400
|
+
const finishedAt = Date.now();
|
|
1401
|
+
record.finishedAt = finishedAt;
|
|
1232
1402
|
const judged = applyAcceptance({
|
|
1233
1403
|
contract: acceptanceContract,
|
|
1234
1404
|
structured: outcome.structured,
|
|
@@ -1238,12 +1408,27 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1238
1408
|
const acceptance = judged.ledger;
|
|
1239
1409
|
if (acceptance) record.acceptance = acceptance;
|
|
1240
1410
|
const outcomeOk = judged.ok;
|
|
1411
|
+
record.invocation = transitionInvocation(record.invocation!, {
|
|
1412
|
+
status: "settled",
|
|
1413
|
+
outcome: outcomeOk ? "success" : "error",
|
|
1414
|
+
at: finishedAt,
|
|
1415
|
+
});
|
|
1241
1416
|
record.state = outcomeOk ? "done" : "error";
|
|
1242
1417
|
if (outcomeOk) delete record.error;
|
|
1243
1418
|
else
|
|
1244
1419
|
record.error = judged.error
|
|
1245
1420
|
? sanitizeWorkflowDisplayLine(judged.error)
|
|
1246
1421
|
: undefined;
|
|
1422
|
+
const ref = handoffs.register({
|
|
1423
|
+
callId,
|
|
1424
|
+
settled: true,
|
|
1425
|
+
ok: outcomeOk,
|
|
1426
|
+
output: outcome.output,
|
|
1427
|
+
...(outcome.structured !== undefined
|
|
1428
|
+
? { structured: outcome.structured }
|
|
1429
|
+
: {}),
|
|
1430
|
+
});
|
|
1431
|
+
if (ref) record.resultRef = ref;
|
|
1247
1432
|
emit();
|
|
1248
1433
|
|
|
1249
1434
|
// Only provably read-only successes with a complete, stable
|
|
@@ -1285,6 +1470,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1285
1470
|
...(outcome.structured !== undefined
|
|
1286
1471
|
? { structured: outcome.structured }
|
|
1287
1472
|
: {}),
|
|
1473
|
+
...(ref ? { ref } : {}),
|
|
1288
1474
|
...(acceptance ? { acceptance } : {}),
|
|
1289
1475
|
...(record.error !== undefined ? { error: record.error } : {}),
|
|
1290
1476
|
};
|
|
@@ -1379,12 +1565,16 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1379
1565
|
const settled = await controller.settle({
|
|
1380
1566
|
abort: status !== "completed",
|
|
1381
1567
|
});
|
|
1568
|
+
const operatorsSettled = await waitBounded(operators.close(), 1_000);
|
|
1382
1569
|
if (runSettled) return;
|
|
1383
|
-
if (!settled) {
|
|
1570
|
+
if (!settled || !operatorsSettled) {
|
|
1384
1571
|
status = "failed";
|
|
1572
|
+
const cleanupError = !settled
|
|
1573
|
+
? "agent shutdown deadline exceeded"
|
|
1574
|
+
: "workflow operator cleanup deadline exceeded";
|
|
1385
1575
|
details.error = details.error
|
|
1386
|
-
? `${details.error};
|
|
1387
|
-
:
|
|
1576
|
+
? `${details.error}; ${cleanupError}`
|
|
1577
|
+
: cleanupError[0]!.toUpperCase() + cleanupError.slice(1);
|
|
1388
1578
|
}
|
|
1389
1579
|
if (runSettled) return;
|
|
1390
1580
|
terminalize(status, details.error);
|
|
@@ -1744,10 +1934,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1744
1934
|
// shutdown settle deadline.
|
|
1745
1935
|
return {
|
|
1746
1936
|
ok: true,
|
|
1747
|
-
details:
|
|
1748
|
-
details.status === "running"
|
|
1749
|
-
? { ...details, status: "aborted" as const }
|
|
1750
|
-
: details,
|
|
1937
|
+
details: recoverStaleWorkflowDetails(details),
|
|
1751
1938
|
} as const;
|
|
1752
1939
|
} catch {
|
|
1753
1940
|
return {
|