@osovv/vv-opencode 1.4.5 → 1.5.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/CHANGELOG.md +14 -0
- package/README.md +8 -8
- package/dist/commands/patch-provider.d.ts +5 -5
- package/dist/commands/patch-provider.js +10 -6
- package/dist/commands/patch-provider.js.map +1 -1
- package/dist/lib/opencode/agent-registrations.d.ts +33 -0
- package/dist/lib/opencode/agent-registrations.js +469 -0
- package/dist/lib/opencode/agent-registrations.js.map +1 -0
- package/dist/lib/opencode/inspection.d.ts +84 -0
- package/dist/lib/opencode/inspection.js +367 -0
- package/dist/lib/opencode/inspection.js.map +1 -0
- package/dist/lib/opencode/model-overrides.d.ts +19 -0
- package/dist/lib/opencode/model-overrides.js +221 -0
- package/dist/lib/opencode/model-overrides.js.map +1 -0
- package/dist/lib/opencode/paths.d.ts +21 -0
- package/dist/lib/opencode/paths.js +62 -0
- package/dist/lib/opencode/paths.js.map +1 -0
- package/dist/lib/opencode/plugin-registration.d.ts +17 -0
- package/dist/lib/opencode/plugin-registration.js +210 -0
- package/dist/lib/opencode/plugin-registration.js.map +1 -0
- package/dist/lib/opencode/shared-utils.d.ts +30 -0
- package/dist/lib/opencode/shared-utils.js +183 -0
- package/dist/lib/opencode/shared-utils.js.map +1 -0
- package/dist/lib/opencode/vvoc-config-io.d.ts +9 -0
- package/dist/lib/opencode/vvoc-config-io.js +58 -0
- package/dist/lib/opencode/vvoc-config-io.js.map +1 -0
- package/dist/lib/opencode.d.ts +15 -174
- package/dist/lib/opencode.js +19 -1387
- package/dist/lib/opencode.js.map +1 -1
- package/dist/lib/spec-lint/cross-file-rules.d.ts +2 -0
- package/dist/lib/spec-lint/cross-file-rules.js +33 -0
- package/dist/lib/spec-lint/cross-file-rules.js.map +1 -0
- package/dist/lib/spec-lint/delegated-extraction.d.ts +8 -0
- package/dist/lib/spec-lint/delegated-extraction.js +112 -0
- package/dist/lib/spec-lint/delegated-extraction.js.map +1 -0
- package/dist/lib/spec-lint/lint.d.ts +7 -0
- package/dist/lib/spec-lint/lint.js +92 -0
- package/dist/lib/spec-lint/lint.js.map +1 -0
- package/dist/lib/spec-lint/parser.d.ts +101 -0
- package/dist/lib/spec-lint/parser.js +482 -0
- package/dist/lib/spec-lint/parser.js.map +1 -0
- package/dist/lib/spec-lint/plan-rules.d.ts +38 -0
- package/dist/lib/spec-lint/plan-rules.js +745 -0
- package/dist/lib/spec-lint/plan-rules.js.map +1 -0
- package/dist/lib/spec-lint/spec-rules.d.ts +8 -0
- package/dist/lib/spec-lint/spec-rules.js +188 -0
- package/dist/lib/spec-lint/spec-rules.js.map +1 -0
- package/dist/lib/spec-lint.d.ts +4 -63
- package/dist/lib/spec-lint.js +13 -1505
- package/dist/lib/spec-lint.js.map +1 -1
- package/dist/lib/vvoc-preset-registry.d.ts +7 -7
- package/dist/lib/vvoc-preset-registry.js +8 -8
- package/dist/lib/vvoc-preset-registry.js.map +1 -1
- package/dist/plugins/workflow/authorization.d.ts +30 -0
- package/dist/plugins/workflow/authorization.js +139 -0
- package/dist/plugins/workflow/authorization.js.map +1 -0
- package/dist/plugins/workflow/index.js +26 -329
- package/dist/plugins/workflow/index.js.map +1 -1
- package/dist/plugins/workflow/recovery.d.ts +17 -0
- package/dist/plugins/workflow/recovery.js +150 -0
- package/dist/plugins/workflow/recovery.js.map +1 -0
- package/dist/plugins/workflow/schemas.d.ts +208 -0
- package/dist/plugins/workflow/schemas.js +138 -0
- package/dist/plugins/workflow/schemas.js.map +1 -0
- package/dist/plugins/workflow/tooling.d.ts +10 -65
- package/dist/plugins/workflow/tooling.js +5 -5
- package/dist/plugins/workflow/tooling.js.map +1 -1
- package/package.json +1 -1
- package/schemas/vvoc/v3.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// FILE: src/plugins/workflow/index.ts
|
|
2
|
-
// VERSION: 0.
|
|
2
|
+
// VERSION: 0.8.0
|
|
3
3
|
// START_MODULE_CONTRACT
|
|
4
4
|
// PURPOSE: Register workflow tools and enforcement while injecting only startup-profile-compatible vv-controller guidance, including delegated control tools with bounded recovery, host-call-bound attempts, terminal report-rejection settlement, and checkpoint reviewer linkage.
|
|
5
|
-
// SCOPE: work_item_open/list/close registration, delegated-only work_item_decide and work_checkpoint registration with root-session authorization and an SDK-backed read-only authorization-message lookup for user-authorized recovery, tracked launch validation with delegated barriers and overlapping-write gates, live host-call bindings that convert supported foreground vv-implementer task launches into failed delegated attempts on confirmed host-terminal errors, result normalization and bounded same-session continuation with explicit hard-stop suppression, callID-bound delegated attempt results, terminal settlement of protocol-invalid reports as report_rejected attempts through staged persistence, checkpoint reviewer bookkeeping, round aggregation with bounded excerpts, implementation round limits,
|
|
6
|
-
// DEPENDS: [@opencode-ai/plugin, src/lib/config-layers.ts, src/lib/orchestration.ts, src/lib/plugin-toggle-config.ts, src/plugins/workflow/checkpoint-io.ts, src/plugins/workflow/checkpoints.ts, src/plugins/workflow/delegated.ts, src/plugins/workflow/persistence.ts, src/plugins/workflow/protocol.ts, src/plugins/workflow/repair.ts, src/plugins/workflow/state.ts, src/plugins/workflow/tooling.ts, src/plugins/workflow/transitions.ts]
|
|
5
|
+
// SCOPE: work_item_open/list/close registration, delegated-only work_item_decide and work_checkpoint registration with root-session authorization and an SDK-backed read-only authorization-message lookup for user-authorized recovery, tracked launch validation with delegated barriers and overlapping-write gates, live host-call bindings that convert supported foreground vv-implementer task launches into failed delegated attempts on confirmed host-terminal errors, result normalization and bounded same-session continuation with explicit hard-stop suppression, callID-bound delegated attempt results, terminal settlement of protocol-invalid reports as report_rejected attempts through staged persistence, checkpoint reviewer bookkeeping, round aggregation with bounded excerpts, implementation round limits, checked persistence, and profile-selected chat.message guidance. Tool argument schemas come from schemas.ts; the authorization guard and message lookups from authorization.ts; staged transactions and committed recovery from recovery.ts.
|
|
6
|
+
// DEPENDS: [@opencode-ai/plugin, src/lib/config-layers.ts, src/lib/orchestration.ts, src/lib/plugin-toggle-config.ts, src/plugins/workflow/authorization.ts, src/plugins/workflow/checkpoint-io.ts, src/plugins/workflow/checkpoints.ts, src/plugins/workflow/delegated.ts, src/plugins/workflow/persistence.ts, src/plugins/workflow/protocol.ts, src/plugins/workflow/recovery.ts, src/plugins/workflow/repair.ts, src/plugins/workflow/schemas.ts, src/plugins/workflow/state.ts, src/plugins/workflow/tooling.ts, src/plugins/workflow/transitions.ts]
|
|
7
7
|
// LINKS: M-PLUGIN-WORKFLOW, M-ORCHESTRATION-PROFILES, M-WORKFLOW-PROTOCOL, M-WORKFLOW-REPAIR, M-WORKFLOW-STATE, M-WORKFLOW-TRANSITIONS, M-WORKFLOW-TOOLING, M-WORKFLOW-PERSISTENCE, M-WORKFLOW-DELEGATED, M-WORKFLOW-CHECKPOINTS, V-M-PLUGIN-WORKFLOW
|
|
8
8
|
// ROLE: RUNTIME
|
|
9
9
|
// MAP_MODE: EXPORTS
|
|
@@ -14,16 +14,19 @@
|
|
|
14
14
|
// END_MODULE_MAP
|
|
15
15
|
//
|
|
16
16
|
// START_CHANGE_SUMMARY
|
|
17
|
-
// LAST_CHANGE: [C-WORKFLOW-
|
|
17
|
+
// LAST_CHANGE: [C-WORKFLOW-INDEX-REDUCE - Moved the five tool argument schemas into schemas.ts with z.infer types, and extracted the authorization guard, message lookups, staged transactions, and committed recovery into authorization.ts and recovery.ts over explicit context; registered shapes and behavior are unchanged.]
|
|
18
18
|
// END_CHANGE_SUMMARY
|
|
19
19
|
import { tool } from "@opencode-ai/plugin";
|
|
20
20
|
import { attemptTrackedResultRepair, detectExplicitHardStopStatus, isTrackedResultRepairEligible, unwrapResumableTaskResult, } from "./repair.js";
|
|
21
21
|
import { parseResultBlock, parseWorkItemHeader, TRACKED_SUBAGENT_NAMES, } from "./protocol.js";
|
|
22
|
-
import { applyTrackedResult, createRecordLookupKey, createWorkflowResultExcerpt, beginTrackedLaunch, createWorkItemStore,
|
|
22
|
+
import { applyTrackedResult, createRecordLookupKey, createWorkflowResultExcerpt, beginTrackedLaunch, createWorkItemStore, getReviewRound, getWorkItem, revertReviewerLaunch, } from "./state.js";
|
|
23
23
|
import { applyDelegatedLaunchFailure, applyDelegatedReportRejection, applyDelegatedResult, beginDelegatedLaunch, revertInFlightDelegatedLaunches, summarizeDelegatedProgress, } from "./delegated.js";
|
|
24
24
|
import { checkpointBarrierUnsatisfied, findOverlappingInFlightReview, recordCheckpointReviewerLaunch, recordCheckpointReviewerResult, } from "./checkpoints.js";
|
|
25
25
|
import { getAllowedNextAgents, getAttemptedImplementationRound, getReviewerRoleForAgent, shouldBlockRound, } from "./transitions.js";
|
|
26
26
|
import { createWorkItemCloseTool, createWorkItemDecideTool, createWorkItemListTool, createWorkItemOpenTool, createWorkCheckpointTool, } from "./tooling.js";
|
|
27
|
+
import { workCheckpointArgs, workItemCloseArgs, workItemDecideArgs, workItemListArgs, workItemOpenArgs, } from "./schemas.js";
|
|
28
|
+
import { assertWorkflowToolAccess, createWorkflowAuthorization, shouldInjectForAgent, } from "./authorization.js";
|
|
29
|
+
import { createRecoverySupport } from "./recovery.js";
|
|
27
30
|
import workflowSystemInstructionTemplate from "./system-instruction.md?raw";
|
|
28
31
|
import { loadVvocConfig } from "../../lib/config-layers.js";
|
|
29
32
|
import { resolveOrchestrationPolicy, } from "../../lib/orchestration.js";
|
|
@@ -31,12 +34,9 @@ import { isVvocPluginEnabled } from "../../lib/plugin-toggle-config.js";
|
|
|
31
34
|
import { deleteWorkflowSessionDir, hydrateWorkflowStateChecked, snapshotWorkflowStateChecked, } from "./persistence.js";
|
|
32
35
|
import { loadApprovedDelegatedPlan } from "./checkpoint-io.js";
|
|
33
36
|
import { isTaskLaunchableInStore } from "./execution.js";
|
|
34
|
-
import { runWorkflowTransaction, WorkflowTransactionQueue } from "./transactions.js";
|
|
35
|
-
const z = tool.schema;
|
|
36
37
|
const TRACKED_SUBAGENT_SET = new Set(TRACKED_SUBAGENT_NAMES);
|
|
37
38
|
const WORK_ITEM_MISSING_MARKER = "__VVOC" + "_SECRET_BEARER_TOKEN_a6f582092f05__";
|
|
38
39
|
const INVALID_NEXT_AGENT_MARKER = "__VVOC" + "_SECRET_BEARER_TOKEN_513fa2de603d__";
|
|
39
|
-
const WORKFLOW_CONTROLLER_AGENT = "vv-controller";
|
|
40
40
|
const REVIEW_ONLY_WORKFLOW_SYSTEM_INSTRUCTION = `
|
|
41
41
|
<workflow_protocol>
|
|
42
42
|
Tracked review-only mechanics are available when independent evaluation is explicitly requested or
|
|
@@ -180,19 +180,6 @@ function delegatedHostFailurePrefix(childSessionId) {
|
|
|
180
180
|
return `Subagent failed (task_id: ${childSessionId}): `;
|
|
181
181
|
}
|
|
182
182
|
// END_BLOCK_DELEGATED_FAILURE_BINDING_TYPES
|
|
183
|
-
function canUseWorkflowTools(agentName) {
|
|
184
|
-
return agentName === WORKFLOW_CONTROLLER_AGENT;
|
|
185
|
-
}
|
|
186
|
-
function assertWorkflowToolAccess(agentName, toolName) {
|
|
187
|
-
if (canUseWorkflowTools(agentName)) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const resolvedAgent = agentName?.trim() || "unknown-agent";
|
|
191
|
-
throw new Error(`WORKFLOW_TOOL_DENIED: ${toolName} is only available to ${WORKFLOW_CONTROLLER_AGENT} sessions. Current agent: ${resolvedAgent}.`);
|
|
192
|
-
}
|
|
193
|
-
function shouldInjectForAgent(agentName) {
|
|
194
|
-
return canUseWorkflowTools(agentName);
|
|
195
|
-
}
|
|
196
183
|
function appendSystemInstruction(existingSystem, instruction) {
|
|
197
184
|
if (!existingSystem?.trim()) {
|
|
198
185
|
return instruction;
|
|
@@ -625,221 +612,21 @@ export const WorkflowPlugin = async ({ client, directory, worktree }) => {
|
|
|
625
612
|
}
|
|
626
613
|
// END_BLOCK_DELEGATED_FAILURE_BINDING
|
|
627
614
|
// START_BLOCK_DELEGATED_AUTHORIZATION
|
|
628
|
-
// New control mutations require the primary vv-controller session
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
contextWorkspace.worktree !== trustedWorkspaceRoot) {
|
|
639
|
-
throw new Error(`CONTROL_DENIED: ${toolName} workspace ${contextWorkspace.worktree} does not match the trusted plugin workspace.`);
|
|
640
|
-
}
|
|
641
|
-
if (contextWorkspace.worktree === undefined &&
|
|
642
|
-
contextWorkspace.directory !== undefined &&
|
|
643
|
-
contextWorkspace.directory !== directory) {
|
|
644
|
-
throw new Error(`CONTROL_DENIED: ${toolName} directory ${contextWorkspace.directory} does not match the trusted plugin directory.`);
|
|
645
|
-
}
|
|
646
|
-
if (invalidHydrationSessions.has(sessionId)) {
|
|
647
|
-
throw new Error(`CONTROL_DENIED: persisted workflow state for session ${sessionId} is invalid; resolve or remove it before new control mutations.`);
|
|
648
|
-
}
|
|
649
|
-
let parentID;
|
|
650
|
-
try {
|
|
651
|
-
const response = await client.session.get({ path: { id: sessionId } });
|
|
652
|
-
if (!response.data) {
|
|
653
|
-
throw new Error("missing session payload");
|
|
654
|
-
}
|
|
655
|
-
parentID = response.data.parentID;
|
|
656
|
-
}
|
|
657
|
-
catch (error) {
|
|
658
|
-
throw new Error(`CONTROL_DENIED: ${toolName} requires root-session identity for ${sessionId}, which could not be verified: ${error.message}`);
|
|
659
|
-
}
|
|
660
|
-
if (parentID !== undefined && parentID !== null && parentID !== "") {
|
|
661
|
-
throw new Error(`CONTROL_DENIED: ${toolName} may only run in the root session; session ${sessionId} is a child of ${parentID}.`);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
615
|
+
// New control mutations require the primary vv-controller session; the
|
|
616
|
+
// guard, its root-session identity check, and the SDK-backed message
|
|
617
|
+
// lookups live in authorization.js over this explicit plugin context.
|
|
618
|
+
const { assertPrimaryControllerMutation, lookupRecoveryUserMessage, lookupAuthorityMessage } = createWorkflowAuthorization({
|
|
619
|
+
client,
|
|
620
|
+
directory,
|
|
621
|
+
worktree,
|
|
622
|
+
trustedWorkspaceRoot,
|
|
623
|
+
invalidHydrationSessions,
|
|
624
|
+
});
|
|
664
625
|
// END_BLOCK_DELEGATED_AUTHORIZATION
|
|
665
626
|
// START_BLOCK_RECOVERY_SUPPORT
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
// failures throw so the domain reports AUTHORIZATION_LOOKUP_FAILED instead
|
|
670
|
-
// of conflating an unreachable lookup with a nonexistent message.
|
|
671
|
-
const lookupRecoveryUserMessage = async (sessionId, messageId) => {
|
|
672
|
-
const response = await client.session.message({
|
|
673
|
-
path: { id: sessionId, messageID: messageId },
|
|
674
|
-
query: { directory },
|
|
675
|
-
});
|
|
676
|
-
if (response.error || !response.data) {
|
|
677
|
-
const errorName = response.error?.name;
|
|
678
|
-
if (errorName && errorName !== "NotFound") {
|
|
679
|
-
throw new Error(`session message lookup failed: ${errorName}`);
|
|
680
|
-
}
|
|
681
|
-
return undefined;
|
|
682
|
-
}
|
|
683
|
-
const info = response.data.info;
|
|
684
|
-
return {
|
|
685
|
-
role: info.role,
|
|
686
|
-
sessionID: info.sessionID,
|
|
687
|
-
id: info.id,
|
|
688
|
-
timeCreatedMs: typeof info.time?.created === "number" && Number.isFinite(info.time.created)
|
|
689
|
-
? info.time.created
|
|
690
|
-
: undefined,
|
|
691
|
-
};
|
|
692
|
-
};
|
|
693
|
-
// Advance-authority provenance uses the same pinned SDK message response.
|
|
694
|
-
// Only verified identity/timing/eligibility metadata leaves this lookup; raw
|
|
695
|
-
// user text is never persisted or logged.
|
|
696
|
-
const lookupAuthorityMessage = async (input) => {
|
|
697
|
-
const response = await client.session.message({
|
|
698
|
-
path: { id: input.sessionId, messageID: input.messageId },
|
|
699
|
-
query: { directory },
|
|
700
|
-
});
|
|
701
|
-
if (response.error || !response.data) {
|
|
702
|
-
const errorName = response.error?.name;
|
|
703
|
-
if (errorName && errorName !== "NotFound") {
|
|
704
|
-
throw new Error(`authority message lookup failed: ${errorName}`);
|
|
705
|
-
}
|
|
706
|
-
return undefined;
|
|
707
|
-
}
|
|
708
|
-
const info = response.data.info;
|
|
709
|
-
const parts = Array.isArray(response.data.parts) ? response.data.parts : [];
|
|
710
|
-
const textParts = parts
|
|
711
|
-
.map((part) => part)
|
|
712
|
-
.filter((part) => part.type === "text" && typeof part.text === "string")
|
|
713
|
-
.map((part) => part.text);
|
|
714
|
-
return {
|
|
715
|
-
messageId: info.id ?? input.messageId,
|
|
716
|
-
sessionId: info.sessionID ?? input.sessionId,
|
|
717
|
-
role: info.role === "user" ? "user" : "assistant",
|
|
718
|
-
createdMs: typeof info.time?.created === "number" && Number.isFinite(info.time.created)
|
|
719
|
-
? info.time.created
|
|
720
|
-
: 0,
|
|
721
|
-
ignored: info.ignored === true,
|
|
722
|
-
syntheticOnly: false,
|
|
723
|
-
textParts,
|
|
724
|
-
};
|
|
725
|
-
};
|
|
726
|
-
// Per-session serialization for generic mutating tool calls. The staged
|
|
727
|
-
// snapshot persists before the committed state is published, so a failed
|
|
728
|
-
// write never exposes new obligations, authority, or launch permissions.
|
|
729
|
-
const workflowTransactions = new WorkflowTransactionQueue();
|
|
730
|
-
async function commitGenericToolResult(sessionId, run) {
|
|
731
|
-
const liveStore = stores.get(sessionId);
|
|
732
|
-
if (!liveStore) {
|
|
733
|
-
return {
|
|
734
|
-
ok: false,
|
|
735
|
-
errorCode: "SESSION_MISMATCH",
|
|
736
|
-
message: `no live workflow store for session ${sessionId}`,
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
if (invalidHydrationSessions.has(sessionId)) {
|
|
740
|
-
return {
|
|
741
|
-
ok: false,
|
|
742
|
-
errorCode: "INVALID_STATE",
|
|
743
|
-
message: `persisted workflow state for session ${sessionId} is invalid`,
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
const outcome = await runWorkflowTransaction({
|
|
747
|
-
queue: workflowTransactions,
|
|
748
|
-
sessionId,
|
|
749
|
-
getData: () => liveStore.getStoreData(),
|
|
750
|
-
operation: async (staged) => {
|
|
751
|
-
const result = await run(createWorkItemStoreView(staged));
|
|
752
|
-
if (result.ok !== true) {
|
|
753
|
-
// Validation failed: persist nothing and publish nothing.
|
|
754
|
-
return { result, skipPersist: true };
|
|
755
|
-
}
|
|
756
|
-
return { result };
|
|
757
|
-
},
|
|
758
|
-
});
|
|
759
|
-
if (!outcome.ok) {
|
|
760
|
-
void client.app
|
|
761
|
-
.log({
|
|
762
|
-
body: {
|
|
763
|
-
service: "workflow",
|
|
764
|
-
level: "error",
|
|
765
|
-
message: "[workflow][generic][BLOCK_GENERIC_COMMIT] persistence failed",
|
|
766
|
-
extra: { sessionID: sessionId, error: outcome.error.slice(0, 300) },
|
|
767
|
-
},
|
|
768
|
-
})
|
|
769
|
-
.catch(() => undefined);
|
|
770
|
-
return {
|
|
771
|
-
ok: false,
|
|
772
|
-
errorCode: "PERSISTENCE_FAILED",
|
|
773
|
-
message: `generic workflow mutation could not be persisted: ${outcome.error}`,
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
return outcome.result;
|
|
777
|
-
}
|
|
778
|
-
/**
|
|
779
|
-
* Execute one recovery mutation durably on the live store: the domain
|
|
780
|
-
* reducer applies to the live entry (re-prechecking after any authorization
|
|
781
|
-
* await), and the whole live store is then persisted synchronously. If the
|
|
782
|
-
* write fails, the captured prior entry is restored so no unpersisted launch
|
|
783
|
-
* permission is ever exposed — there is no await between mutation, persist,
|
|
784
|
-
* and rollback, so no concurrent actor can observe the intermediate state,
|
|
785
|
-
* and no stale whole-store snapshot can regress concurrent transitions.
|
|
786
|
-
*/
|
|
787
|
-
async function executeCommittedRecovery(sessionId, runRecovery, captureRestore, wasApplied) {
|
|
788
|
-
const liveStore = stores.get(sessionId);
|
|
789
|
-
if (!liveStore || invalidHydrationSessions.has(sessionId)) {
|
|
790
|
-
throw new Error(`CONTROL_DENIED: persisted workflow state for session ${sessionId} is invalid; resolve or remove it before new control mutations.`);
|
|
791
|
-
}
|
|
792
|
-
const restore = captureRestore();
|
|
793
|
-
const result = await runRecovery(liveStore);
|
|
794
|
-
if (!wasApplied(result)) {
|
|
795
|
-
return result;
|
|
796
|
-
}
|
|
797
|
-
const persisted = snapshotWorkflowStateChecked(sessionId, liveStore.getStoreData());
|
|
798
|
-
if (!persisted.ok) {
|
|
799
|
-
restore();
|
|
800
|
-
void client.app
|
|
801
|
-
.log({
|
|
802
|
-
body: {
|
|
803
|
-
service: "workflow",
|
|
804
|
-
level: "error",
|
|
805
|
-
message: "[workflow][recovery][BLOCK_RECOVERY_COMMIT] recovery persistence failed",
|
|
806
|
-
extra: { sessionID: sessionId, error: persisted.error.slice(0, 300) },
|
|
807
|
-
},
|
|
808
|
-
})
|
|
809
|
-
.catch(() => undefined);
|
|
810
|
-
throw new Error(`PERSISTENCE_FAILED: recovery applied in memory but could not be persisted and was rolled back: ${persisted.error}`);
|
|
811
|
-
}
|
|
812
|
-
return result;
|
|
813
|
-
}
|
|
814
|
-
/** Capture and restore one work-item record entry for recovery rollback. */
|
|
815
|
-
function captureRecordRestore(sessionId, workItemId) {
|
|
816
|
-
const liveStore = stores.get(sessionId);
|
|
817
|
-
if (!liveStore)
|
|
818
|
-
return () => undefined;
|
|
819
|
-
const liveData = liveStore.getStoreData();
|
|
820
|
-
const lookupKey = createRecordLookupKey(sessionId, workItemId);
|
|
821
|
-
const prior = liveData.records.get(lookupKey);
|
|
822
|
-
return () => {
|
|
823
|
-
if (prior) {
|
|
824
|
-
liveData.records.set(lookupKey, prior);
|
|
825
|
-
}
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
/** Capture and restore one checkpoint entry for recovery rollback. */
|
|
829
|
-
function captureCheckpointRestore(sessionId, runId, checkpointId) {
|
|
830
|
-
const liveStore = stores.get(sessionId);
|
|
831
|
-
if (!liveStore)
|
|
832
|
-
return () => undefined;
|
|
833
|
-
const run = liveStore.getStoreData().planRuns.get(runId);
|
|
834
|
-
if (!run)
|
|
835
|
-
return () => undefined;
|
|
836
|
-
const priorCheckpoint = run.checkpoints.get(checkpointId);
|
|
837
|
-
return () => {
|
|
838
|
-
if (priorCheckpoint) {
|
|
839
|
-
run.checkpoints.set(checkpointId, priorCheckpoint);
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
}
|
|
627
|
+
// Staged generic transactions and durably committed recovery with rollback
|
|
628
|
+
// live in recovery.js over this explicit persistence seam.
|
|
629
|
+
const { commitGenericToolResult, executeCommittedRecovery, captureRecordRestore, captureCheckpointRestore, } = createRecoverySupport({ client, stores, invalidHydrationSessions });
|
|
843
630
|
// END_BLOCK_RECOVERY_SUPPORT
|
|
844
631
|
// START_BLOCK_CHECKPOINT_LINKAGE
|
|
845
632
|
/** Find the in-flight checkpoint generation whose linked review item matches. */
|
|
@@ -874,38 +661,7 @@ export const WorkflowPlugin = async ({ client, directory, worktree }) => {
|
|
|
874
661
|
tool: {
|
|
875
662
|
work_item_open: tool({
|
|
876
663
|
description: workItemOpenTool.description,
|
|
877
|
-
args:
|
|
878
|
-
items: z.array(z.object({
|
|
879
|
-
key: z.string(),
|
|
880
|
-
title: z.string(),
|
|
881
|
-
mode: z.string(),
|
|
882
|
-
requiredReviewers: z.array(z.string()),
|
|
883
|
-
writeScope: z.array(z.string()).optional(),
|
|
884
|
-
planRunId: z.string().optional(),
|
|
885
|
-
planTaskId: z.string().optional(),
|
|
886
|
-
taskId: z.string().optional(),
|
|
887
|
-
goal: z.string().optional(),
|
|
888
|
-
acceptanceCriteria: z.array(z.string()).optional(),
|
|
889
|
-
verification: z.array(z.string()).optional(),
|
|
890
|
-
dependsOn: z.array(z.string()).optional(),
|
|
891
|
-
blockedBy: z.array(z.string()).optional(),
|
|
892
|
-
})),
|
|
893
|
-
execution: z
|
|
894
|
-
.object({
|
|
895
|
-
executionKey: z.string(),
|
|
896
|
-
source: z.record(z.string(), z.unknown()),
|
|
897
|
-
goal: z.string(),
|
|
898
|
-
boundary: z.object({
|
|
899
|
-
files: z.array(z.string()),
|
|
900
|
-
directories: z.array(z.string()),
|
|
901
|
-
}),
|
|
902
|
-
checkpoints: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
903
|
-
})
|
|
904
|
-
.optional(),
|
|
905
|
-
runId: z.string().optional(),
|
|
906
|
-
amendmentId: z.string().optional(),
|
|
907
|
-
rationale: z.string().optional(),
|
|
908
|
-
},
|
|
664
|
+
args: workItemOpenArgs,
|
|
909
665
|
async execute(args, context) {
|
|
910
666
|
assertWorkflowToolAccess(context.agent, "work_item_open");
|
|
911
667
|
const isGeneric = args.execution !== undefined || args.runId !== undefined;
|
|
@@ -927,9 +683,7 @@ export const WorkflowPlugin = async ({ client, directory, worktree }) => {
|
|
|
927
683
|
}),
|
|
928
684
|
work_item_list: tool({
|
|
929
685
|
description: workItemListTool.description,
|
|
930
|
-
args:
|
|
931
|
-
includeClosed: z.boolean().optional(),
|
|
932
|
-
},
|
|
686
|
+
args: workItemListArgs,
|
|
933
687
|
async execute(args, context) {
|
|
934
688
|
assertWorkflowToolAccess(context.agent, "work_item_list");
|
|
935
689
|
const sessionStore = getOrCreateStore(context.sessionID);
|
|
@@ -938,9 +692,7 @@ export const WorkflowPlugin = async ({ client, directory, worktree }) => {
|
|
|
938
692
|
}),
|
|
939
693
|
work_item_close: tool({
|
|
940
694
|
description: workItemCloseTool.description,
|
|
941
|
-
args:
|
|
942
|
-
workItemId: z.string(),
|
|
943
|
-
},
|
|
695
|
+
args: workItemCloseArgs,
|
|
944
696
|
async execute(args, context) {
|
|
945
697
|
assertWorkflowToolAccess(context.agent, "work_item_close");
|
|
946
698
|
const sessionStore = getOrCreateStore(context.sessionID);
|
|
@@ -955,22 +707,7 @@ export const WorkflowPlugin = async ({ client, directory, worktree }) => {
|
|
|
955
707
|
...{
|
|
956
708
|
work_item_decide: tool({
|
|
957
709
|
description: workItemDecideTool.description,
|
|
958
|
-
args:
|
|
959
|
-
workItemId: z.string(),
|
|
960
|
-
attempt: z.number().int().min(1),
|
|
961
|
-
decision: z.enum(["accept", "request_changes", "rework", "recover"]),
|
|
962
|
-
rationale: z.string().optional(),
|
|
963
|
-
evidence: z.array(z.string()).optional(),
|
|
964
|
-
concernsDisposition: z.string().optional(),
|
|
965
|
-
runId: z.string().optional(),
|
|
966
|
-
checkpointId: z.string().optional(),
|
|
967
|
-
diagnosis: z.string().optional(),
|
|
968
|
-
changedCondition: z.string().optional(),
|
|
969
|
-
verification: z.array(z.string()).optional(),
|
|
970
|
-
recoveryId: z.string().optional(),
|
|
971
|
-
userMessageId: z.string().optional(),
|
|
972
|
-
authorityId: z.string().optional(),
|
|
973
|
-
},
|
|
710
|
+
args: workItemDecideArgs,
|
|
974
711
|
async execute(args, context) {
|
|
975
712
|
// Resolve the store first so invalid persisted state is detected
|
|
976
713
|
// before the authorization check reports it as a control denial.
|
|
@@ -1001,47 +738,7 @@ export const WorkflowPlugin = async ({ client, directory, worktree }) => {
|
|
|
1001
738
|
}),
|
|
1002
739
|
work_checkpoint: tool({
|
|
1003
740
|
description: workCheckpointTool.description,
|
|
1004
|
-
args:
|
|
1005
|
-
action: z.enum([
|
|
1006
|
-
"register",
|
|
1007
|
-
"start",
|
|
1008
|
-
"verify",
|
|
1009
|
-
"recover",
|
|
1010
|
-
"review",
|
|
1011
|
-
"bind",
|
|
1012
|
-
"complete",
|
|
1013
|
-
"amend",
|
|
1014
|
-
"authorize",
|
|
1015
|
-
"record_approval",
|
|
1016
|
-
"revoke_authority",
|
|
1017
|
-
]),
|
|
1018
|
-
planPath: z.string().optional(),
|
|
1019
|
-
runId: z.string().optional(),
|
|
1020
|
-
checkpointId: z.string().optional(),
|
|
1021
|
-
complete: z.boolean().optional(),
|
|
1022
|
-
diagnosis: z.string().optional(),
|
|
1023
|
-
changedCondition: z.string().optional(),
|
|
1024
|
-
verification: z.array(z.string()).optional(),
|
|
1025
|
-
recoveryId: z.string().optional(),
|
|
1026
|
-
userMessageId: z.string().optional(),
|
|
1027
|
-
checkpoints: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
1028
|
-
tasks: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
1029
|
-
amendmentId: z.string().optional(),
|
|
1030
|
-
rationale: z.string().optional(),
|
|
1031
|
-
startFingerprint: z.string().optional(),
|
|
1032
|
-
reviewer: z.string().optional(),
|
|
1033
|
-
authorityId: z.string().optional(),
|
|
1034
|
-
messageId: z.string().optional(),
|
|
1035
|
-
approvalId: z.string().optional(),
|
|
1036
|
-
stage: z.string().optional(),
|
|
1037
|
-
stages: z.array(z.string()).optional(),
|
|
1038
|
-
decisionScope: z.string().optional(),
|
|
1039
|
-
fileBoundary: z.array(z.string()).optional(),
|
|
1040
|
-
reservedStops: z.array(z.string()).optional(),
|
|
1041
|
-
artifactPath: z.string().optional(),
|
|
1042
|
-
artifactSha256: z.string().optional(),
|
|
1043
|
-
revocationId: z.string().optional(),
|
|
1044
|
-
},
|
|
741
|
+
args: workCheckpointArgs,
|
|
1045
742
|
async execute(args, context) {
|
|
1046
743
|
// Resolve the store first so invalid persisted state is detected
|
|
1047
744
|
// before the authorization check reports it as a control denial.
|