@tt-a1i/openpi 0.1.0 → 0.2.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/LICENSE +21 -0
- package/README.md +295 -389
- package/SETUP.md +24 -22
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/assets/readme-hero-mobile.svg +2 -2
- package/assets/readme-hero.svg +10 -10
- package/extensions/ask-user/handoff.ts +5 -1
- package/extensions/ask-user/index.ts +44 -0
- package/extensions/background-terminals/index.ts +118 -29
- package/extensions/background-terminals/src/domain.ts +5 -1
- package/extensions/background-terminals/src/manager.ts +2 -1
- package/extensions/background-terminals/src/prompt.ts +35 -0
- package/extensions/background-terminals/src/result-delivery.ts +76 -3
- package/extensions/background-terminals/src/ui/tool-result.ts +52 -1
- package/extensions/capabilities/index.ts +198 -0
- package/extensions/context-pivot/index.ts +21 -0
- package/extensions/cron/index.ts +42 -15
- package/extensions/execution-convergence/active-evidence.ts +129 -0
- package/extensions/execution-convergence/index.ts +442 -0
- package/extensions/execution-convergence/workspace-provenance.ts +338 -0
- package/extensions/file-search/index.ts +8 -1
- package/extensions/file-search/src/binaries.ts +2 -1
- package/extensions/git-info/src/runtime.ts +1 -1
- package/extensions/goal/controller.ts +2 -1
- package/extensions/goal/index.ts +20 -1
- package/extensions/plan-mode/index.ts +12 -0
- package/extensions/setup/index.ts +241 -45
- package/extensions/setup/intercom-fs-helper.cjs +130 -0
- package/extensions/setup/intercom.ts +603 -0
- package/extensions/shared/child-session.ts +42 -5
- package/extensions/shared/setup-config.ts +27 -1
- package/extensions/shared/setup-episode-state.ts +7 -0
- package/extensions/shared/tool-surface.ts +435 -0
- package/extensions/subagents/index.ts +16 -1
- package/extensions/subagents/src/manager.ts +13 -11
- package/extensions/subagents/src/prompt.ts +1 -1
- package/extensions/tasks/index.ts +39 -12
- package/extensions/ui-customization/footer.ts +6 -1
- package/extensions/workflows/artifacts.ts +6 -1
- package/extensions/workflows/dashboard.ts +138 -27
- package/extensions/workflows/graph-projection.ts +240 -0
- package/extensions/workflows/handoff.ts +194 -0
- package/extensions/workflows/index.ts +258 -56
- package/extensions/workflows/invocation-ledger.ts +368 -0
- package/extensions/workflows/model.ts +57 -1
- package/extensions/workflows/operator.ts +131 -0
- package/extensions/workflows/prompt.ts +10 -38
- package/extensions/workflows/replay-safety.ts +9 -8
- package/extensions/workflows/runner.ts +10 -2
- package/extensions/workflows/sandbox.ts +5 -0
- package/package.json +15 -15
- package/skills/subagents/SKILL.md +6 -0
- package/skills/workflows/EXAMPLES.md +58 -0
- package/skills/workflows/REFERENCE.md +44 -0
- package/skills/workflows/SKILL.md +39 -0
|
@@ -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";
|
|
@@ -45,6 +46,10 @@ import { Type, type Static } from "typebox";
|
|
|
45
46
|
import { formatActivityStatus } from "../shared/activity-status.ts";
|
|
46
47
|
import { waitBounded } from "../shared/child-session.ts";
|
|
47
48
|
import { loadSetupConfig } from "../shared/setup-config.ts";
|
|
49
|
+
import {
|
|
50
|
+
OPENPI_TOOL_SURFACE,
|
|
51
|
+
patchOwnedTools,
|
|
52
|
+
} from "../shared/tool-surface.ts";
|
|
48
53
|
import {
|
|
49
54
|
loadAgentTypes,
|
|
50
55
|
resolveAgentModel,
|
|
@@ -62,6 +67,17 @@ import {
|
|
|
62
67
|
loadJournal,
|
|
63
68
|
persistWorkflowJson,
|
|
64
69
|
} from "./artifacts.ts";
|
|
70
|
+
import { createWorkflowHandoffRegistry } from "./handoff.ts";
|
|
71
|
+
import {
|
|
72
|
+
classifyInterruptedInvocation,
|
|
73
|
+
createInvocationIdentity,
|
|
74
|
+
requestInvocation,
|
|
75
|
+
transitionInvocation,
|
|
76
|
+
} from "./invocation-ledger.ts";
|
|
77
|
+
import {
|
|
78
|
+
normalizeWorkflowOperatorKey,
|
|
79
|
+
WorkflowOperatorRegistry,
|
|
80
|
+
} from "./operator.ts";
|
|
65
81
|
import {
|
|
66
82
|
agentCallKey,
|
|
67
83
|
createReplayCache,
|
|
@@ -71,6 +87,7 @@ import {
|
|
|
71
87
|
import { RunController } from "./controller.ts";
|
|
72
88
|
import {
|
|
73
89
|
normalizePersistedWorkflowDetails,
|
|
90
|
+
recoverStaleWorkflowDetails,
|
|
74
91
|
sessionWorkflowRunIds,
|
|
75
92
|
showWorkflowDashboard,
|
|
76
93
|
} from "./dashboard.ts";
|
|
@@ -98,6 +115,7 @@ import {
|
|
|
98
115
|
statusColor,
|
|
99
116
|
statusWord,
|
|
100
117
|
createUsageReader,
|
|
118
|
+
refreshWorkflowGraph,
|
|
101
119
|
SQUARE,
|
|
102
120
|
type AgentRecord,
|
|
103
121
|
type WorkflowDetails,
|
|
@@ -166,6 +184,8 @@ interface ScriptAgentResult {
|
|
|
166
184
|
ok: boolean;
|
|
167
185
|
output: string;
|
|
168
186
|
structured?: unknown;
|
|
187
|
+
/** Opaque same-run handle for bounded downstream handoff. */
|
|
188
|
+
ref?: string;
|
|
169
189
|
acceptance?: AgentRecord["acceptance"];
|
|
170
190
|
error?: string;
|
|
171
191
|
}
|
|
@@ -180,6 +200,10 @@ interface AgentCallOptions {
|
|
|
180
200
|
provider?: unknown;
|
|
181
201
|
effort?: unknown;
|
|
182
202
|
isolation?: unknown;
|
|
203
|
+
/** Reuse one in-memory child Session within this workflow run. */
|
|
204
|
+
operator?: unknown;
|
|
205
|
+
/** Same-run result refs to hydrate as bounded untrusted input data. */
|
|
206
|
+
inputs?: unknown;
|
|
183
207
|
}
|
|
184
208
|
|
|
185
209
|
const WorkflowParams = Type.Object({
|
|
@@ -401,6 +425,14 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
401
425
|
[...activeRuns].map(([runId, run]) => [runId, run.details] as const),
|
|
402
426
|
);
|
|
403
427
|
const settledRuns = new Map<string, WorkflowDetails>();
|
|
428
|
+
const hideLifecycleTools = () =>
|
|
429
|
+
patchOwnedTools(pi, "workflows", {
|
|
430
|
+
disable: OPENPI_TOOL_SURFACE.workflows.deferred,
|
|
431
|
+
});
|
|
432
|
+
const showLifecycleTools = () =>
|
|
433
|
+
patchOwnedTools(pi, "workflows", {
|
|
434
|
+
enable: OPENPI_TOOL_SURFACE.workflows.deferred,
|
|
435
|
+
});
|
|
404
436
|
const stripState = new WorkflowStripState();
|
|
405
437
|
const widgetKey = "workflow-navigation";
|
|
406
438
|
|
|
@@ -557,6 +589,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
557
589
|
};
|
|
558
590
|
|
|
559
591
|
pi.on("session_start", (_event, ctx) => {
|
|
592
|
+
hideLifecycleTools();
|
|
560
593
|
if (ctx.hasUI) lastContext = ctx;
|
|
561
594
|
agentTypes = loadAgentTypes({
|
|
562
595
|
agentDir: getAgentDir(),
|
|
@@ -745,6 +778,8 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
745
778
|
workflowConfig.concurrency,
|
|
746
779
|
workflowConfig.maxAgentCalls,
|
|
747
780
|
);
|
|
781
|
+
const handoffs = createWorkflowHandoffRegistry();
|
|
782
|
+
const operators = new WorkflowOperatorRegistry();
|
|
748
783
|
|
|
749
784
|
// Each concurrent child gets its own extension runtime. Children use the
|
|
750
785
|
// parent's live trust decision; an isolated child gets its own cwd (its
|
|
@@ -779,6 +814,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
779
814
|
};
|
|
780
815
|
const emit = (checkpoint = true) => {
|
|
781
816
|
if (runSettled) return;
|
|
817
|
+
refreshWorkflowGraph(details);
|
|
782
818
|
if (checkpoint) persistence.checkpoint();
|
|
783
819
|
if (emitTimer) return;
|
|
784
820
|
emitTimer = setTimeout(
|
|
@@ -809,6 +845,16 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
809
845
|
);
|
|
810
846
|
for (const record of details.agents) {
|
|
811
847
|
if (record.state !== "running") continue;
|
|
848
|
+
if (
|
|
849
|
+
record.invocation &&
|
|
850
|
+
record.invocation.executionState !== "settled" &&
|
|
851
|
+
record.invocation.executionState !== "uncertain"
|
|
852
|
+
) {
|
|
853
|
+
record.invocation = classifyInterruptedInvocation(
|
|
854
|
+
record.invocation,
|
|
855
|
+
Date.now(),
|
|
856
|
+
);
|
|
857
|
+
}
|
|
812
858
|
record.state = "error";
|
|
813
859
|
record.error =
|
|
814
860
|
record.error ?? "Agent did not settle before run cleanup";
|
|
@@ -816,6 +862,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
816
862
|
}
|
|
817
863
|
details.status = status;
|
|
818
864
|
details.finishedAt = Date.now();
|
|
865
|
+
refreshWorkflowGraph(details);
|
|
819
866
|
if (error) details.error = sanitizeWorkflowDisplayLine(error);
|
|
820
867
|
return true;
|
|
821
868
|
};
|
|
@@ -874,8 +921,15 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
874
921
|
error: "Workflow was aborted before this agent started",
|
|
875
922
|
};
|
|
876
923
|
}
|
|
924
|
+
const startedAt = Date.now();
|
|
925
|
+
const callId = `${details.runId}:call:${index}`;
|
|
877
926
|
const record: AgentRecord = {
|
|
878
927
|
index,
|
|
928
|
+
callId,
|
|
929
|
+
invocation: requestInvocation(
|
|
930
|
+
createInvocationIdentity(details.runId, index),
|
|
931
|
+
startedAt,
|
|
932
|
+
),
|
|
879
933
|
label,
|
|
880
934
|
phase:
|
|
881
935
|
typeof opts.phase === "string"
|
|
@@ -884,20 +938,49 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
884
938
|
state: "running",
|
|
885
939
|
model: ctx.model?.id,
|
|
886
940
|
contextWindow: ctx.model?.contextWindow,
|
|
887
|
-
startedAt
|
|
941
|
+
startedAt,
|
|
888
942
|
preview: "",
|
|
889
943
|
usage: emptyUsage(),
|
|
890
944
|
transcript: [],
|
|
891
945
|
};
|
|
892
946
|
details.agents.push(record);
|
|
947
|
+
refreshWorkflowGraph(details);
|
|
893
948
|
persistence.checkpoint({ immediate: true });
|
|
894
949
|
emit(false);
|
|
895
950
|
|
|
896
951
|
const fail = (error: string): ScriptAgentResult => {
|
|
897
952
|
if (record.state === "running" && !runSettled) {
|
|
953
|
+
const at = Date.now();
|
|
954
|
+
if (
|
|
955
|
+
record.invocation?.admissionState === "pending" &&
|
|
956
|
+
record.invocation.executionState === "pending"
|
|
957
|
+
) {
|
|
958
|
+
record.invocation = transitionInvocation(record.invocation, {
|
|
959
|
+
status: "rejected",
|
|
960
|
+
at,
|
|
961
|
+
});
|
|
962
|
+
} else if (
|
|
963
|
+
record.invocation?.admissionState === "claimed" &&
|
|
964
|
+
record.invocation.executionState === "running"
|
|
965
|
+
) {
|
|
966
|
+
record.invocation = transitionInvocation(record.invocation, {
|
|
967
|
+
status: "settled",
|
|
968
|
+
outcome: "error",
|
|
969
|
+
at,
|
|
970
|
+
});
|
|
971
|
+
} else if (
|
|
972
|
+
record.invocation &&
|
|
973
|
+
record.invocation.executionState !== "settled" &&
|
|
974
|
+
record.invocation.executionState !== "uncertain"
|
|
975
|
+
) {
|
|
976
|
+
record.invocation = classifyInterruptedInvocation(
|
|
977
|
+
record.invocation,
|
|
978
|
+
at,
|
|
979
|
+
);
|
|
980
|
+
}
|
|
898
981
|
record.state = "error";
|
|
899
982
|
record.error = sanitizeWorkflowDisplayLine(error);
|
|
900
|
-
record.finishedAt =
|
|
983
|
+
record.finishedAt = at;
|
|
901
984
|
emit();
|
|
902
985
|
}
|
|
903
986
|
return { ok: false, output: "", error };
|
|
@@ -909,6 +992,45 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
909
992
|
: String(promptValue ?? "");
|
|
910
993
|
if (!basePrompt.trim())
|
|
911
994
|
return fail("agent() requires a non-empty prompt string");
|
|
995
|
+
|
|
996
|
+
let operatorKey: string | undefined;
|
|
997
|
+
try {
|
|
998
|
+
if (opts.operator !== undefined) {
|
|
999
|
+
operatorKey = normalizeWorkflowOperatorKey(String(opts.operator));
|
|
1000
|
+
if (opts.isolation !== undefined) {
|
|
1001
|
+
throw new Error(
|
|
1002
|
+
"workflow operators cannot use per-call worktree isolation",
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
record.operatorKey = operatorKey;
|
|
1006
|
+
}
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
return fail(`agent "${label}": ${errorText(error)}`);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
let inputRefs: string[] = [];
|
|
1012
|
+
let promptWithHandoffs = basePrompt;
|
|
1013
|
+
try {
|
|
1014
|
+
if (opts.inputs !== undefined) {
|
|
1015
|
+
if (
|
|
1016
|
+
!Array.isArray(opts.inputs) ||
|
|
1017
|
+
!opts.inputs.every((value) => typeof value === "string")
|
|
1018
|
+
) {
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
"inputs must be an array of workflow result refs",
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
inputRefs = [...opts.inputs];
|
|
1024
|
+
}
|
|
1025
|
+
const entries = handoffs.resolveEntries(inputRefs);
|
|
1026
|
+
record.inputCallIds = entries.flatMap((entry) =>
|
|
1027
|
+
entry.callId ? [entry.callId] : [],
|
|
1028
|
+
);
|
|
1029
|
+
promptWithHandoffs = handoffs.appendToPrompt(basePrompt, inputRefs);
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
return fail(`agent "${label}": ${errorText(error)}`);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
912
1034
|
let acceptanceContract: ReturnType<typeof parseAcceptanceContract>;
|
|
913
1035
|
let effectiveSchema: unknown;
|
|
914
1036
|
try {
|
|
@@ -921,8 +1043,8 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
921
1043
|
}
|
|
922
1044
|
const prompt = buildWorkflowAgentPrompt(
|
|
923
1045
|
acceptanceContract
|
|
924
|
-
? `${
|
|
925
|
-
:
|
|
1046
|
+
? `${promptWithHandoffs}\n\n${acceptanceInstruction(acceptanceContract)}`
|
|
1047
|
+
: promptWithHandoffs,
|
|
926
1048
|
);
|
|
927
1049
|
if (controller.signal.aborted)
|
|
928
1050
|
return fail("Workflow was aborted before this agent started");
|
|
@@ -1012,19 +1134,40 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1012
1134
|
}
|
|
1013
1135
|
record.model = model?.id;
|
|
1014
1136
|
record.contextWindow = model?.contextWindow;
|
|
1137
|
+
const operatorFingerprint = operatorKey
|
|
1138
|
+
? agentCallKey("workflow-operator", {
|
|
1139
|
+
execution: {
|
|
1140
|
+
agentType: agentType
|
|
1141
|
+
? {
|
|
1142
|
+
name: agentType.name,
|
|
1143
|
+
body: agentType.body,
|
|
1144
|
+
tools: agentType.tools,
|
|
1145
|
+
}
|
|
1146
|
+
: undefined,
|
|
1147
|
+
model: model ? `${model.provider}/${model.id}` : undefined,
|
|
1148
|
+
effort: thinkingLevel,
|
|
1149
|
+
structured: effectiveSchema !== undefined,
|
|
1150
|
+
},
|
|
1151
|
+
})
|
|
1152
|
+
: undefined;
|
|
1015
1153
|
|
|
1016
1154
|
// Replay is deliberately narrower than execution: only a named type
|
|
1017
1155
|
// whose effective tool allowlist is entirely known read-only can be
|
|
1018
1156
|
// cached. General-purpose children inherit bash/edit/write, custom
|
|
1019
1157
|
// tools have unknown effects, and worktrees have state a string result
|
|
1020
1158
|
// cannot restore.
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1159
|
+
// A reused operator's later activation depends on prior in-memory
|
|
1160
|
+
// conversation state, which a cached string cannot reconstruct.
|
|
1161
|
+
const replaySafe =
|
|
1162
|
+
operatorKey === undefined &&
|
|
1163
|
+
isReplaySafeAgentCall({
|
|
1164
|
+
tools: agentType?.tools,
|
|
1165
|
+
isolation: opts.isolation,
|
|
1166
|
+
});
|
|
1025
1167
|
const replayLease = beginProcessReplayWorkspaceLease(replaySafe);
|
|
1026
1168
|
let replayResources:
|
|
1027
|
-
Awaited<ReturnType<typeof getResources>>
|
|
1169
|
+
| Awaited<ReturnType<typeof getResources>>
|
|
1170
|
+
| undefined;
|
|
1028
1171
|
let replayIdentity: ReturnType<typeof createReplayIdentity> | undefined;
|
|
1029
1172
|
if (replaySafe) {
|
|
1030
1173
|
try {
|
|
@@ -1070,9 +1213,14 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1070
1213
|
const cached =
|
|
1071
1214
|
callKey && replayLease.canReplay ? replay?.take(callKey) : undefined;
|
|
1072
1215
|
if (cached) {
|
|
1216
|
+
const finishedAt = Date.now();
|
|
1217
|
+
record.invocation = transitionInvocation(record.invocation!, {
|
|
1218
|
+
status: "replayed",
|
|
1219
|
+
at: finishedAt,
|
|
1220
|
+
});
|
|
1073
1221
|
record.state = "done";
|
|
1074
1222
|
record.replayed = true;
|
|
1075
|
-
record.finishedAt =
|
|
1223
|
+
record.finishedAt = finishedAt;
|
|
1076
1224
|
record.preview = sanitizeWorkflowDisplayText(
|
|
1077
1225
|
cached.output,
|
|
1078
1226
|
PREVIEW_LENGTH,
|
|
@@ -1083,6 +1231,16 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1083
1231
|
cached.structured,
|
|
1084
1232
|
);
|
|
1085
1233
|
}
|
|
1234
|
+
const ref = handoffs.register({
|
|
1235
|
+
callId,
|
|
1236
|
+
settled: true,
|
|
1237
|
+
ok: true,
|
|
1238
|
+
output: cached.output,
|
|
1239
|
+
...(cached.structured !== undefined
|
|
1240
|
+
? { structured: cached.structured }
|
|
1241
|
+
: {}),
|
|
1242
|
+
});
|
|
1243
|
+
if (ref) record.resultRef = ref;
|
|
1086
1244
|
emit();
|
|
1087
1245
|
// Re-journal so a chain of resumes keeps working: run C resuming from
|
|
1088
1246
|
// B still finds what B replayed from A.
|
|
@@ -1094,12 +1252,24 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1094
1252
|
...(cached.structured !== undefined
|
|
1095
1253
|
? { structured: cached.structured }
|
|
1096
1254
|
: {}),
|
|
1255
|
+
...(ref ? { ref } : {}),
|
|
1097
1256
|
...(record.acceptance ? { acceptance: record.acceptance } : {}),
|
|
1098
1257
|
};
|
|
1099
1258
|
}
|
|
1100
1259
|
|
|
1101
1260
|
return controller
|
|
1102
1261
|
.schedule(async (runSignal) => {
|
|
1262
|
+
const claimedAt = Date.now();
|
|
1263
|
+
record.invocation = transitionInvocation(record.invocation!, {
|
|
1264
|
+
status: "claimed",
|
|
1265
|
+
at: claimedAt,
|
|
1266
|
+
});
|
|
1267
|
+
refreshWorkflowGraph(details);
|
|
1268
|
+
persistence.checkpoint({ immediate: true });
|
|
1269
|
+
record.invocation = transitionInvocation(record.invocation, {
|
|
1270
|
+
status: "running",
|
|
1271
|
+
at: Date.now(),
|
|
1272
|
+
});
|
|
1103
1273
|
record.model = model?.id;
|
|
1104
1274
|
record.contextWindow = model?.contextWindow;
|
|
1105
1275
|
emit();
|
|
@@ -1172,45 +1342,58 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1172
1342
|
if (runSettled) {
|
|
1173
1343
|
throw new Error("Workflow was settled before agent creation");
|
|
1174
1344
|
}
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
? {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1345
|
+
const runChild = (sessionManager?: SessionManager) =>
|
|
1346
|
+
runAgent({
|
|
1347
|
+
prompt,
|
|
1348
|
+
schema: effectiveSchema,
|
|
1349
|
+
model,
|
|
1350
|
+
thinkingLevel,
|
|
1351
|
+
// Replay-safe calls use the same canonical cwd as the
|
|
1352
|
+
// identity and filesystem boundary, so a symlink spelling of
|
|
1353
|
+
// the checkout cannot retarget relative tool paths.
|
|
1354
|
+
cwd: replayIdentity?.cwd ?? agentCwd,
|
|
1355
|
+
loader: resources.loader,
|
|
1356
|
+
settingsManager: resources.settingsManager,
|
|
1357
|
+
...(sessionManager ? { sessionManager } : {}),
|
|
1358
|
+
modelRegistry: ctx.modelRegistry,
|
|
1359
|
+
...(agentType?.tools ? { tools: agentType.tools } : {}),
|
|
1360
|
+
...(replayIdentity
|
|
1361
|
+
? {
|
|
1362
|
+
replayFilesystemBoundary: {
|
|
1363
|
+
repositoryRoot: replayIdentity.repositoryRoot,
|
|
1364
|
+
cwd: replayIdentity.cwd,
|
|
1365
|
+
onViolation: () => {
|
|
1366
|
+
replayBoundaryViolated = true;
|
|
1367
|
+
},
|
|
1195
1368
|
},
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
:
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1369
|
+
}
|
|
1370
|
+
: {}),
|
|
1371
|
+
signal: runSignal,
|
|
1372
|
+
onProgress: (progress) => {
|
|
1373
|
+
if (runSettled || record.state !== "running") return;
|
|
1374
|
+
record.preview = sanitizeWorkflowDisplayText(
|
|
1375
|
+
progress.preview,
|
|
1376
|
+
PREVIEW_LENGTH,
|
|
1377
|
+
);
|
|
1378
|
+
record.usage = progress.usage;
|
|
1379
|
+
record.model = progress.model ?? record.model;
|
|
1380
|
+
record.contextWindow =
|
|
1381
|
+
progress.contextWindow ?? record.contextWindow;
|
|
1382
|
+
record.transcript = progress.transcript;
|
|
1383
|
+
emit();
|
|
1384
|
+
},
|
|
1385
|
+
});
|
|
1386
|
+
const outcome = operatorKey
|
|
1387
|
+
? await operators.activate(
|
|
1388
|
+
{
|
|
1389
|
+
key: operatorKey,
|
|
1390
|
+
fingerprint: operatorFingerprint!,
|
|
1391
|
+
cwd: agentCwd,
|
|
1392
|
+
signal: runSignal,
|
|
1393
|
+
},
|
|
1394
|
+
runChild,
|
|
1395
|
+
)
|
|
1396
|
+
: await runChild();
|
|
1214
1397
|
|
|
1215
1398
|
if (runSettled || record.state !== "running") {
|
|
1216
1399
|
return {
|
|
@@ -1228,7 +1411,8 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1228
1411
|
outcome.output || record.preview,
|
|
1229
1412
|
PREVIEW_LENGTH,
|
|
1230
1413
|
);
|
|
1231
|
-
|
|
1414
|
+
const finishedAt = Date.now();
|
|
1415
|
+
record.finishedAt = finishedAt;
|
|
1232
1416
|
const judged = applyAcceptance({
|
|
1233
1417
|
contract: acceptanceContract,
|
|
1234
1418
|
structured: outcome.structured,
|
|
@@ -1238,12 +1422,27 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1238
1422
|
const acceptance = judged.ledger;
|
|
1239
1423
|
if (acceptance) record.acceptance = acceptance;
|
|
1240
1424
|
const outcomeOk = judged.ok;
|
|
1425
|
+
record.invocation = transitionInvocation(record.invocation!, {
|
|
1426
|
+
status: "settled",
|
|
1427
|
+
outcome: outcomeOk ? "success" : "error",
|
|
1428
|
+
at: finishedAt,
|
|
1429
|
+
});
|
|
1241
1430
|
record.state = outcomeOk ? "done" : "error";
|
|
1242
1431
|
if (outcomeOk) delete record.error;
|
|
1243
1432
|
else
|
|
1244
1433
|
record.error = judged.error
|
|
1245
1434
|
? sanitizeWorkflowDisplayLine(judged.error)
|
|
1246
1435
|
: undefined;
|
|
1436
|
+
const ref = handoffs.register({
|
|
1437
|
+
callId,
|
|
1438
|
+
settled: true,
|
|
1439
|
+
ok: outcomeOk,
|
|
1440
|
+
output: outcome.output,
|
|
1441
|
+
...(outcome.structured !== undefined
|
|
1442
|
+
? { structured: outcome.structured }
|
|
1443
|
+
: {}),
|
|
1444
|
+
});
|
|
1445
|
+
if (ref) record.resultRef = ref;
|
|
1247
1446
|
emit();
|
|
1248
1447
|
|
|
1249
1448
|
// Only provably read-only successes with a complete, stable
|
|
@@ -1285,6 +1484,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1285
1484
|
...(outcome.structured !== undefined
|
|
1286
1485
|
? { structured: outcome.structured }
|
|
1287
1486
|
: {}),
|
|
1487
|
+
...(ref ? { ref } : {}),
|
|
1288
1488
|
...(acceptance ? { acceptance } : {}),
|
|
1289
1489
|
...(record.error !== undefined ? { error: record.error } : {}),
|
|
1290
1490
|
};
|
|
@@ -1379,12 +1579,16 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1379
1579
|
const settled = await controller.settle({
|
|
1380
1580
|
abort: status !== "completed",
|
|
1381
1581
|
});
|
|
1582
|
+
const operatorsSettled = await waitBounded(operators.close(), 1_000);
|
|
1382
1583
|
if (runSettled) return;
|
|
1383
|
-
if (!settled) {
|
|
1584
|
+
if (!settled || !operatorsSettled) {
|
|
1384
1585
|
status = "failed";
|
|
1586
|
+
const cleanupError = !settled
|
|
1587
|
+
? "agent shutdown deadline exceeded"
|
|
1588
|
+
: "workflow operator cleanup deadline exceeded";
|
|
1385
1589
|
details.error = details.error
|
|
1386
|
-
? `${details.error};
|
|
1387
|
-
:
|
|
1590
|
+
? `${details.error}; ${cleanupError}`
|
|
1591
|
+
: cleanupError[0]!.toUpperCase() + cleanupError.slice(1);
|
|
1388
1592
|
}
|
|
1389
1593
|
if (runSettled) return;
|
|
1390
1594
|
terminalize(status, details.error);
|
|
@@ -1453,6 +1657,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1453
1657
|
// Session may be shutting down.
|
|
1454
1658
|
}
|
|
1455
1659
|
});
|
|
1660
|
+
showLifecycleTools();
|
|
1456
1661
|
return {
|
|
1457
1662
|
content: [
|
|
1458
1663
|
{
|
|
@@ -1744,10 +1949,7 @@ export default function workflows(pi: ExtensionAPI) {
|
|
|
1744
1949
|
// shutdown settle deadline.
|
|
1745
1950
|
return {
|
|
1746
1951
|
ok: true,
|
|
1747
|
-
details:
|
|
1748
|
-
details.status === "running"
|
|
1749
|
-
? { ...details, status: "aborted" as const }
|
|
1750
|
-
: details,
|
|
1952
|
+
details: recoverStaleWorkflowDetails(details),
|
|
1751
1953
|
} as const;
|
|
1752
1954
|
} catch {
|
|
1753
1955
|
return {
|