@agentxm/workspace-operations 0.28.12 → 0.28.13
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/dist/src/index.d.ts +11 -10
- package/dist/src/index.js +16 -12
- package/dist/src/operations/scan-plan-readiness.d.ts +1 -1
- package/dist/src/operations/scan-plan-readiness.js +1 -1
- package/dist/src/plan/apply-plan.js +8 -8
- package/dist/src/plan/execution-candidate.d.ts +7 -1
- package/dist/src/plan/execution-candidate.js +9 -2
- package/dist/src/plan/interruption-resolution.d.ts +50 -0
- package/dist/src/plan/interruption-resolution.js +158 -0
- package/dist/src/plan/operation-journal.d.ts +9 -9
- package/dist/src/plan/operation-journal.js +5 -20
- package/dist/src/plan/operation-resolution.d.ts +1 -1
- package/dist/src/plan/plan-execution-fixtures.d.ts +2 -2
- package/dist/src/plan/plan-execution-fixtures.js +2 -2
- package/dist/src/plan/plan-execution.d.ts +48 -0
- package/dist/src/plan/plan-execution.js +40 -0
- package/dist/src/plan/plan.d.ts +9 -19
- package/dist/src/plan/resolve-plan-interaction.d.ts +1 -1
- package/dist/src/plan/resolve-plan-interaction.js +1 -1
- package/dist/src/plan/resolve-plan.d.ts +38 -23
- package/dist/src/plan/resolve-plan.js +114 -96
- package/dist/src/plan/step-failure-conversions.d.ts +2 -1
- package/dist/src/testing.d.ts +22 -1
- package/dist/src/testing.js +23 -1
- package/package.json +12 -16
- package/dist/src/live.d.ts +0 -13
- package/dist/src/live.js +0 -12
- package/dist/src/operations/load-workspace.d.ts +0 -44
- package/dist/src/operations/load-workspace.js +0 -80
- package/dist/src/operations/memory-transition-lock.d.ts +0 -20
- package/dist/src/operations/memory-transition-lock.js +0 -57
- package/dist/src/operations/transaction.d.ts +0 -60
- package/dist/src/operations/transaction.js +0 -358
- package/dist/src/operations/transition-lock.d.ts +0 -72
- package/dist/src/operations/transition-lock.js +0 -298
|
@@ -1,4 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one derivation of an approval mode from what a request carries.
|
|
3
|
+
*
|
|
4
|
+
* It lives with plan execution rather than in a transport adapter because the
|
|
5
|
+
* distinction it draws — between a route that offers preapproval and one that
|
|
6
|
+
* does not — is what `resolveExecutionCandidate` uses to decide whether an
|
|
7
|
+
* unattended apply blocks naming interactive approval or names a flag.
|
|
8
|
+
*/
|
|
9
|
+
export const confirmableRiskApproval = (requested) => requested.preapproved === true
|
|
10
|
+
? "preapproved"
|
|
11
|
+
: requested.preapproved === false
|
|
12
|
+
? "prompt-if-interactive"
|
|
13
|
+
: "interactive-only";
|
|
1
14
|
export const previewPlanExecution = { request: { mode: "preview" } };
|
|
15
|
+
/**
|
|
16
|
+
* The one derivation of a plan execution from what a request carries.
|
|
17
|
+
*
|
|
18
|
+
* It lives here for the same reason `confirmableRiskApproval` does: the
|
|
19
|
+
* preview arm of `PlanExecution` has no field an approval could occupy, so
|
|
20
|
+
* advance approval accompanying a preview is dropped by this derivation
|
|
21
|
+
* rather than by a transport adapter's own convention. A preview therefore
|
|
22
|
+
* cannot spend an approval it never receives, and two requests that differ
|
|
23
|
+
* only in approval reach `resolveExecutionCandidate` as the same execution.
|
|
24
|
+
*/
|
|
25
|
+
export const requestedPlanExecution = (options) => options.intent.preview
|
|
26
|
+
? {
|
|
27
|
+
...previewPlanExecution,
|
|
28
|
+
...(options.configuredAgentOperations === undefined
|
|
29
|
+
? {}
|
|
30
|
+
: { configuredAgentOperations: options.configuredAgentOperations }),
|
|
31
|
+
}
|
|
32
|
+
: applyPlanExecution({
|
|
33
|
+
approval: confirmableRiskApproval(options.intent.yes === undefined ? {} : { preapproved: options.intent.yes }),
|
|
34
|
+
...(options.acceptedPolicies === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: { acceptedPolicies: options.acceptedPolicies }),
|
|
37
|
+
recovery: options.recovery,
|
|
38
|
+
...(options.configuredAgentOperations === undefined
|
|
39
|
+
? {}
|
|
40
|
+
: { configuredAgentOperations: options.configuredAgentOperations }),
|
|
41
|
+
});
|
|
2
42
|
export const applyPlanExecution = (options) => ({
|
|
3
43
|
request: {
|
|
4
44
|
mode: "apply",
|
package/dist/src/plan/plan.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { type ExtensionType } from "@agentxm/extension-model/unstable/extensions
|
|
|
23
23
|
import type { ArtifactChange } from "@agentxm/workspace-state";
|
|
24
24
|
import type { ConfiguredAgentOutcome } from "@agentxm/workspace-state";
|
|
25
25
|
import type { DeprecationView } from "@agentxm/extension-model/unstable/extensions/deprecation";
|
|
26
|
-
import type { ReleaseAgeOperationEvidence } from "@agentxm/
|
|
26
|
+
import type { RegistryBindingProposal, ReleaseAgeOperationEvidence } from "@agentxm/extension-resolution";
|
|
27
27
|
import type { SuggestedAction } from "@agentxm/registry-protocol/unstable/suggested-action";
|
|
28
28
|
export declare const PlanPolicyIds: readonly ["ignore-version-constraints", "accept-warnings"];
|
|
29
29
|
export declare const PlanPolicyIdSchema: Schema.Literals<readonly ["ignore-version-constraints", "accept-warnings"]>;
|
|
@@ -111,21 +111,6 @@ export interface JobStepArtifactSource {
|
|
|
111
111
|
readonly directory?: string;
|
|
112
112
|
readonly gitTreeHash?: string;
|
|
113
113
|
}
|
|
114
|
-
/**
|
|
115
|
-
* The Registry identity a step proposes to accept. Trust classification
|
|
116
|
-
* compares it with the accepted resolution for the same configured target,
|
|
117
|
-
* so a change of publisher is identified from structured data rather than
|
|
118
|
-
* from warning text.
|
|
119
|
-
*/
|
|
120
|
-
export interface RegistryBindingProposal {
|
|
121
|
-
readonly extensionType: ExtensionType;
|
|
122
|
-
/** The configured (local) name whose accepted resolution the step replaces. */
|
|
123
|
-
readonly target: string;
|
|
124
|
-
readonly owner: string;
|
|
125
|
-
readonly packageName: string;
|
|
126
|
-
readonly version: string;
|
|
127
|
-
readonly publisherBindingId: string;
|
|
128
|
-
}
|
|
129
114
|
export interface RegistryLifecycleEvidence {
|
|
130
115
|
readonly deprecation: DeprecationView;
|
|
131
116
|
}
|
|
@@ -253,9 +238,14 @@ export interface Plan<Requirements = never, Output = never> {
|
|
|
253
238
|
/** Persisted inputs outside workspace state that materially determine this plan. */
|
|
254
239
|
readonly materialPaths?: ReadonlyArray<string>;
|
|
255
240
|
/** Local plans roll back candidate-wide; remote effects report truthful partial outcomes. */
|
|
256
|
-
readonly executionCapabilities?:
|
|
257
|
-
|
|
258
|
-
|
|
241
|
+
readonly executionCapabilities?: PlanExecutionCapabilities;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* What a plan promises about undoing itself. A producer declares this so the
|
|
245
|
+
* atomicity a result reports is the one the plan's owner decided.
|
|
246
|
+
*/
|
|
247
|
+
export interface PlanExecutionCapabilities {
|
|
248
|
+
readonly rollback: "local-atomic" | "non-rollbackable";
|
|
259
249
|
}
|
|
260
250
|
export interface ExecutedJob<Output = never> {
|
|
261
251
|
readonly steps: ReadonlyArray<CompletedJobStep<Output>>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan-resolution interaction port.
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
4
|
+
* `resolveExecutionCandidate` presents candidates and obtains the apply confirmation
|
|
5
5
|
* exclusively through this service. The CLI runtime provides the renderer- and
|
|
6
6
|
* prompt-backed implementation; wording and verbosity gating belong to that
|
|
7
7
|
* implementation, never to the kernel. Progress is not an interaction: the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan-resolution interaction port.
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
4
|
+
* `resolveExecutionCandidate` presents candidates and obtains the apply confirmation
|
|
5
5
|
* exclusively through this service. The CLI runtime provides the renderer- and
|
|
6
6
|
* prompt-backed implementation; wording and verbosity gating belong to that
|
|
7
7
|
* implementation, never to the kernel. Progress is not an interaction: the
|
|
@@ -1,42 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Candidate preparation and resolution.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* `prepareExecutionCandidate` turns a plan into the immutable execution
|
|
5
|
+
* candidate every later step refers to: it augments the plan with lockfile
|
|
6
|
+
* reconciliation, projects configured-agent outcomes, scans readiness,
|
|
7
|
+
* assembles the semantic risk conditions, and fingerprints the material
|
|
8
|
+
* preimages. `resolveExecutionCandidate` presents that candidate, fails
|
|
9
|
+
* closed on blockers and missing policies, previews or confirms, acquires
|
|
10
|
+
* the workspace transition, revalidates the same candidate under it, applies
|
|
11
|
+
* it closure by closure, and produces one `OperationResolution` at every
|
|
12
|
+
* termination path. Presentation and prompting live behind the
|
|
13
|
+
* `ResolvePlanInteraction` port; per-type outcome refinement behind the
|
|
14
|
+
* `ConfiguredAgentOutcomesProvider`.
|
|
10
15
|
*
|
|
11
|
-
* This is a free function, not a method on WorkspaceMutationsService.
|
|
12
16
|
*
|
|
13
17
|
* @experimental This API is unstable and may change without notice.
|
|
14
18
|
*/
|
|
15
|
-
import * as FileSystem from "effect/FileSystem";
|
|
16
19
|
import * as Path from "effect/Path";
|
|
17
20
|
import * as Effect from "effect/Effect";
|
|
18
21
|
import { ApprovalRecoveryMissing, StepFailure } from "./errors.js";
|
|
19
22
|
import { type ExecutionCandidate } from "./execution-candidate.js";
|
|
20
23
|
import type { Plan } from "./plan.js";
|
|
21
24
|
import { type OperationResolution } from "./operation-resolution.js";
|
|
22
|
-
import {
|
|
25
|
+
import { OperationJournal } from "./operation-journal.js";
|
|
26
|
+
import { ConfiguredAgentOutcomesProvider, WorkspaceMutations } from "@agentxm/workspace-state";
|
|
27
|
+
import { FootprintRecorder } from "@agentxm/workspace-transactions";
|
|
23
28
|
import { ResolvePlanInteraction } from "./resolve-plan-interaction.js";
|
|
24
|
-
import { type PlanExecution } from "./plan-execution.js";
|
|
29
|
+
import { type ConfiguredAgentOperation, type PlanExecution } from "./plan-execution.js";
|
|
30
|
+
export interface PrepareExecutionCandidateOptions {
|
|
31
|
+
/** Operations whose configured-agent outcomes the candidate projects and, after apply, verifies. */
|
|
32
|
+
readonly configuredAgentOperations?: ReadonlyArray<ConfiguredAgentOperation>;
|
|
33
|
+
}
|
|
25
34
|
/**
|
|
26
|
-
*
|
|
35
|
+
* Prepare the immutable execution candidate for a plan: augment with lockfile
|
|
36
|
+
* reconciliation, project configured-agent outcomes, scan readiness, assemble
|
|
37
|
+
* risk conditions, and fingerprint every material preimage. Preview,
|
|
38
|
+
* confirmation, and apply all refer to the candidate this returns.
|
|
39
|
+
*/
|
|
40
|
+
export declare const prepareExecutionCandidate: <Requirements, Output>(plan: Plan<Requirements, Output>, options?: PrepareExecutionCandidateOptions | undefined) => Effect.Effect<ExecutionCandidate<Requirements, Output>, import("./errors.js").CandidateFingerprintFailed | import("@agentxm/workspace-state").LockfileValidationError | import("@agentxm/workspace-state").WorkspaceSettingsReadFailure, Path.Path | import("effect/FileSystem").FileSystem | WorkspaceMutations | ConfiguredAgentOutcomesProvider>;
|
|
41
|
+
export interface ResolveExecutionCandidateOptions<Requirements, Output> {
|
|
42
|
+
/** A typed pre-apply gate that runs under the transition before revalidation. */
|
|
43
|
+
readonly beforeApply?: (candidate: ExecutionCandidate<Requirements, Output>) => Effect.Effect<void, StepFailure, Requirements>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Preview or apply a prepared candidate.
|
|
27
47
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* 5. Preview or approve confirmable semantic risk
|
|
34
|
-
* 6. Revalidate and apply the same candidate
|
|
48
|
+
* 1. Present the immutable candidate.
|
|
49
|
+
* 2. Fail closed on blockers and missing named policies.
|
|
50
|
+
* 3. Preview, or approve confirmable semantic risk.
|
|
51
|
+
* 4. Acquire the workspace transition, revalidate the same candidate under
|
|
52
|
+
* it, and apply it closure by closure.
|
|
35
53
|
*
|
|
36
54
|
* Every termination path resolves to one `OperationResolution`.
|
|
37
55
|
*/
|
|
38
|
-
export declare const
|
|
39
|
-
execution: PlanExecution;
|
|
40
|
-
beforeApply?: (candidate: ExecutionCandidate<Requirements, Output>) => Effect.Effect<void, StepFailure, Requirements>;
|
|
41
|
-
}) => Effect.Effect<OperationResolution<Output>, import("./errors.js").CandidateFingerprintFailed | ApprovalRecoveryMissing | import("./errors.js").PlanInteractionFailed | import("@agentxm/workspace-state").WorkspaceTransitionAcquireFailure | import("@agentxm/workspace-state").LockfileValidationError | import("@agentxm/workspace-state").WorkspaceSettingsReadFailure, Path.Path | FileSystem.FileSystem | ResolvePlanInteraction | WorkspaceMutations | Exclude<Requirements, import("effect/Scope").Scope>>;
|
|
56
|
+
export declare const resolveExecutionCandidate: <Requirements, Output>(candidate: ExecutionCandidate<Requirements, Output>, execution: PlanExecution, options?: ResolveExecutionCandidateOptions<Requirements, Output> | undefined) => Effect.Effect<OperationResolution<Output>, ApprovalRecoveryMissing | import("./errors.js").PlanInteractionFailed | import("@agentxm/workspace-state").WorkspaceSettingsReadFailure | import("@agentxm/workspace-transactions").WorkspaceTransitionAcquireFailure, OperationJournal | Path.Path | import("effect/FileSystem").FileSystem | ResolvePlanInteraction | WorkspaceMutations | ConfiguredAgentOutcomesProvider | import("@agentxm/workspace-transactions").WorkspaceTransactionScope | Exclude<Requirements, import("effect/Scope").Scope> | FootprintRecorder>;
|
|
42
57
|
//# sourceMappingURL=resolve-plan.d.ts.map
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Candidate preparation and resolution.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* `prepareExecutionCandidate` turns a plan into the immutable execution
|
|
5
|
+
* candidate every later step refers to: it augments the plan with lockfile
|
|
6
|
+
* reconciliation, projects configured-agent outcomes, scans readiness,
|
|
7
|
+
* assembles the semantic risk conditions, and fingerprints the material
|
|
8
|
+
* preimages. `resolveExecutionCandidate` presents that candidate, fails
|
|
9
|
+
* closed on blockers and missing policies, previews or confirms, acquires
|
|
10
|
+
* the workspace transition, revalidates the same candidate under it, applies
|
|
11
|
+
* it closure by closure, and produces one `OperationResolution` at every
|
|
12
|
+
* termination path. Presentation and prompting live behind the
|
|
13
|
+
* `ResolvePlanInteraction` port; per-type outcome refinement behind the
|
|
14
|
+
* `ConfiguredAgentOutcomesProvider`.
|
|
10
15
|
*
|
|
11
|
-
* This is a free function, not a method on WorkspaceMutationsService.
|
|
12
16
|
*
|
|
13
17
|
* @experimental This API is unstable and may change without notice.
|
|
14
18
|
*/
|
|
15
19
|
import * as Cause from "effect/Cause";
|
|
16
|
-
import * as FileSystem from "effect/FileSystem";
|
|
17
20
|
import * as Path from "effect/Path";
|
|
18
21
|
import * as Effect from "effect/Effect";
|
|
19
|
-
import * as Layer from "effect/Layer";
|
|
20
22
|
import * as Option from "effect/Option";
|
|
21
23
|
import * as Ref from "effect/Ref";
|
|
22
24
|
import { ApprovalRecoveryMissing, STALE_CANDIDATE_DETAIL, StaleExecutionCandidate, StepFailure, } from "./errors.js";
|
|
@@ -25,17 +27,13 @@ import { isExecutionCandidateFresh, makeExecutionCandidate, } from "./execution-
|
|
|
25
27
|
import { augmentPlanWithReconciliation } from "../operations/augment-plan.js";
|
|
26
28
|
import { scanPlanReadiness } from "../operations/scan-plan-readiness.js";
|
|
27
29
|
import { declaredAtomicity, executedUnits, makeOperationResolution, plannedUnits, unitIdOf, } from "./operation-resolution.js";
|
|
28
|
-
import { appendResolvedUnit, appendStartedUnit, recordJournalPhase, recordOperationJournal, } from "./operation-journal.js";
|
|
30
|
+
import { OperationJournal, appendResolvedUnit, appendStartedUnit, recordJournalPhase, recordOperationJournal, } from "./operation-journal.js";
|
|
29
31
|
import { CurrentOperationUnit, observeUnit, publishOperationEvent, publishPhaseStarted, publishWaitEnded, publishWaiting, } from "./operation-events.js";
|
|
30
|
-
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
31
|
-
import {
|
|
32
|
-
import { rollbackWorkspaceClosure, settleWorkspaceClosure, withWorkspaceClosure, } from "../operations/transaction.js";
|
|
33
|
-
import { readFootprint } from "@agentxm/workspace-state";
|
|
32
|
+
import { ConfiguredAgentOutcomesProvider, WorkspaceMutations, configuredAgentLifecycleOutcomes, } from "@agentxm/workspace-state";
|
|
33
|
+
import { FootprintRecorder, WorkspaceRestorationIncomplete, acquireWorkspaceTransition, pendingClosureRestorations, readFootprint, rollbackWorkspaceClosure, runWorkspaceTransaction, settleWorkspaceClosure, withWorkspaceClosure, } from "@agentxm/workspace-transactions";
|
|
34
34
|
import { InterruptionSignalSource } from "./interruption-signal.js";
|
|
35
35
|
import { ResolvePlanInteraction } from "./resolve-plan-interaction.js";
|
|
36
36
|
import { confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, } from "./plan-execution.js";
|
|
37
|
-
import { ConfiguredAgentOutcomesProvider } from "@agentxm/workspace-state";
|
|
38
|
-
import { configuredAgentLifecycleOutcomes } from "@agentxm/workspace-state";
|
|
39
37
|
import { candidateFingerprintFailedToStepFailure, configuredAgentOutcomesUnavailableToStepFailure, restorationIncompleteToStepFailure, workspaceStateReadFailureToStepFailure, workspaceTransactionFailureToStepFailure, } from "./step-failure-conversions.js";
|
|
40
38
|
/** Publish a phase transition to the lifecycle stream and the journal. */
|
|
41
39
|
const enterPhase = (phase) => publishPhaseStarted(phase).pipe(Effect.andThen(recordJournalPhase(phase)));
|
|
@@ -87,59 +85,30 @@ const withExecutedAgentOutcomes = (plan, outcomes) => ({
|
|
|
87
85
|
})),
|
|
88
86
|
});
|
|
89
87
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* Steps:
|
|
93
|
-
* 1. Augment plan with lockfile reconciliation if needed
|
|
94
|
-
* 2. Scan for errors/warnings
|
|
95
|
-
* 3. Construct and display the exact candidate
|
|
96
|
-
* 4. Fail closed on blockers and missing named policies
|
|
97
|
-
* 5. Preview or approve confirmable semantic risk
|
|
98
|
-
* 6. Revalidate and apply the same candidate
|
|
99
|
-
*
|
|
100
|
-
* Every termination path resolves to one `OperationResolution`.
|
|
88
|
+
* The generic lifecycle outcomes for one operation, refined by the provider's
|
|
89
|
+
* per-type override when the operation enables the extension.
|
|
101
90
|
*/
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
scope: ws.scope,
|
|
124
|
-
state,
|
|
125
|
-
targetState: operation.plannedState,
|
|
126
|
-
installed: state === "projected",
|
|
127
|
-
observedAgentIds: state === "projected" ? configuredAgents : [],
|
|
128
|
-
});
|
|
129
|
-
const override = outcomesOverrideFor(operation.extensionType);
|
|
130
|
-
if (operation.plannedState === "enabled" && override !== undefined) {
|
|
131
|
-
return override(state).pipe(Effect.map((outcomes) => outcomes.filter(({ name }) => name === operation.name)), Effect.map((outcomes) => (outcomes.length === 0 ? generic : outcomes)), Effect.catch(() => Effect.succeed(generic)));
|
|
132
|
-
}
|
|
133
|
-
return Effect.succeed(generic);
|
|
134
|
-
};
|
|
135
|
-
const projectedOutcomes = (yield* Effect.forEach(operations, (operation) => outcomesFor(operation, "projected"))).flat();
|
|
136
|
-
const augmentedPlan = operations.length === 0
|
|
137
|
-
? augmented.plan
|
|
138
|
-
: withPlannedAgentOutcomes(augmented.plan, projectedOutcomes);
|
|
139
|
-
// Step 2: Scan readiness and construct semantic risk conditions.
|
|
140
|
-
const readiness = scanPlanReadiness(augmentedPlan);
|
|
141
|
-
const declaredConditionIds = new Set((augmentedPlan.riskConditions ?? []).map((condition) => condition.id));
|
|
142
|
-
const readinessBlockers = augmentedPlan.jobs.flatMap((job) => job.steps.flatMap((step) => step.readiness === "error"
|
|
91
|
+
const outcomesFor = (ws, provider, configuredAgents, operation, state) => {
|
|
92
|
+
const generic = configuredAgentLifecycleOutcomes({
|
|
93
|
+
type: operation.extensionType,
|
|
94
|
+
name: operation.name,
|
|
95
|
+
agentIds: configuredAgents,
|
|
96
|
+
scope: ws.scope,
|
|
97
|
+
state,
|
|
98
|
+
targetState: operation.plannedState,
|
|
99
|
+
installed: state === "projected",
|
|
100
|
+
observedAgentIds: state === "projected" ? configuredAgents : [],
|
|
101
|
+
});
|
|
102
|
+
const override = provider.byExtensionType[operation.extensionType];
|
|
103
|
+
if (operation.plannedState === "enabled" && override !== undefined) {
|
|
104
|
+
return override(state).pipe(Effect.map((outcomes) => outcomes.filter(({ name }) => name === operation.name)), Effect.map((outcomes) => (outcomes.length === 0 ? generic : outcomes)), Effect.catch(() => Effect.succeed(generic)));
|
|
105
|
+
}
|
|
106
|
+
return Effect.succeed(generic);
|
|
107
|
+
};
|
|
108
|
+
/** The readiness blockers a plan's error steps contribute beyond declared conditions. */
|
|
109
|
+
const readinessBlockersOf = (plan) => {
|
|
110
|
+
const declaredConditionIds = new Set((plan.riskConditions ?? []).map((condition) => condition.id));
|
|
111
|
+
return plan.jobs.flatMap((job) => job.steps.flatMap((step) => step.readiness === "error"
|
|
143
112
|
? (step.blockingConditionIds ?? []).length > 0 &&
|
|
144
113
|
(step.blockingConditionIds ?? []).every((id) => declaredConditionIds.has(id))
|
|
145
114
|
? []
|
|
@@ -152,6 +121,27 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
152
121
|
},
|
|
153
122
|
]
|
|
154
123
|
: []));
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Prepare the immutable execution candidate for a plan: augment with lockfile
|
|
127
|
+
* reconciliation, project configured-agent outcomes, scan readiness, assemble
|
|
128
|
+
* risk conditions, and fingerprint every material preimage. Preview,
|
|
129
|
+
* confirmation, and apply all refer to the candidate this returns.
|
|
130
|
+
*/
|
|
131
|
+
export const prepareExecutionCandidate = Effect.fn("prepareExecutionCandidate")(function* (plan, options) {
|
|
132
|
+
const ws = yield* WorkspaceMutations;
|
|
133
|
+
const provider = yield* ConfiguredAgentOutcomesProvider;
|
|
134
|
+
yield* publishPhaseStarted("planning");
|
|
135
|
+
// Lockfile reconciliation, observed as one planning unit.
|
|
136
|
+
const augmented = yield* observeUnit({ id: "lockfile-reconciliation", label: "lockfile reconciliation" }, augmentPlanWithReconciliation(plan, () => ws.getLockfileState()));
|
|
137
|
+
const operations = options?.configuredAgentOperations ?? [];
|
|
138
|
+
const configuredAgents = operations.length === 0 ? [] : yield* ws.getConfiguredAgents();
|
|
139
|
+
const projectedOutcomes = (yield* Effect.forEach(operations, (operation) => outcomesFor(ws, provider, configuredAgents, operation, "projected"))).flat();
|
|
140
|
+
const augmentedPlan = operations.length === 0
|
|
141
|
+
? augmented.plan
|
|
142
|
+
: withPlannedAgentOutcomes(augmented.plan, projectedOutcomes);
|
|
143
|
+
// Readiness blockers and unmet preconditions join the declared risk
|
|
144
|
+
// conditions, so the candidate carries every reason it could be refused.
|
|
155
145
|
const preconditionBlockers = (augmentedPlan.preconditions ?? []).flatMap((precondition) => precondition.status === "unmet"
|
|
156
146
|
? [
|
|
157
147
|
{
|
|
@@ -166,18 +156,46 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
166
156
|
: []);
|
|
167
157
|
const riskConditions = [
|
|
168
158
|
...(augmentedPlan.riskConditions ?? []),
|
|
169
|
-
...
|
|
159
|
+
...readinessBlockersOf(augmentedPlan),
|
|
170
160
|
...preconditionBlockers,
|
|
171
161
|
];
|
|
172
162
|
const candidatePlan = {
|
|
173
163
|
...augmentedPlan,
|
|
174
164
|
...(riskConditions.length === 0 ? {} : { riskConditions }),
|
|
175
165
|
};
|
|
176
|
-
|
|
166
|
+
return yield* makeExecutionCandidate(candidatePlan, {
|
|
177
167
|
settingsPath: ws.layout.settingsPath,
|
|
178
168
|
lockPath: ws.layout.lockPath,
|
|
179
169
|
baseDir: ws.baseDir,
|
|
180
|
-
})
|
|
170
|
+
}, operations);
|
|
171
|
+
});
|
|
172
|
+
/**
|
|
173
|
+
* Preview or apply a prepared candidate.
|
|
174
|
+
*
|
|
175
|
+
* 1. Present the immutable candidate.
|
|
176
|
+
* 2. Fail closed on blockers and missing named policies.
|
|
177
|
+
* 3. Preview, or approve confirmable semantic risk.
|
|
178
|
+
* 4. Acquire the workspace transition, revalidate the same candidate under
|
|
179
|
+
* it, and apply it closure by closure.
|
|
180
|
+
*
|
|
181
|
+
* Every termination path resolves to one `OperationResolution`.
|
|
182
|
+
*/
|
|
183
|
+
export const resolveExecutionCandidate = Effect.fn("resolveExecutionCandidate")(function* (candidate, execution, options) {
|
|
184
|
+
const ws = yield* WorkspaceMutations;
|
|
185
|
+
const provider = yield* ConfiguredAgentOutcomesProvider;
|
|
186
|
+
const interaction = yield* ResolvePlanInteraction;
|
|
187
|
+
const path = yield* Path.Path;
|
|
188
|
+
// The journal and footprint recorder are requirements of the operation
|
|
189
|
+
// boundary: the settlement observers below record into them.
|
|
190
|
+
const journal = yield* OperationJournal;
|
|
191
|
+
yield* FootprintRecorder;
|
|
192
|
+
const mode = execution.request.mode;
|
|
193
|
+
const candidatePlan = candidate.plan;
|
|
194
|
+
const operations = candidate.configuredAgentOperations;
|
|
195
|
+
const configuredAgents = operations.length === 0 ? [] : yield* ws.getConfiguredAgents();
|
|
196
|
+
const readiness = scanPlanReadiness(candidatePlan);
|
|
197
|
+
const readinessBlockers = readinessBlockersOf(candidatePlan);
|
|
198
|
+
const riskConditions = candidatePlan.riskConditions ?? [];
|
|
181
199
|
const atomicity = declaredAtomicity(candidatePlan);
|
|
182
200
|
const resolutionBase = {
|
|
183
201
|
name: candidatePlan.name,
|
|
@@ -218,13 +236,13 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
218
236
|
failure: over.failure,
|
|
219
237
|
suggestions: candidatePlan.failureSuggestions,
|
|
220
238
|
});
|
|
221
|
-
//
|
|
239
|
+
// Display the immutable candidate before any policy terminal or effect.
|
|
222
240
|
if (mode === "preview") {
|
|
223
241
|
yield* enterPhase("preview");
|
|
224
242
|
}
|
|
225
243
|
const hasConfirmableRisk = riskConditions.some((condition) => condition.level === "confirmable");
|
|
226
244
|
yield* interaction.presentPlan(candidatePlan, { mode });
|
|
227
|
-
//
|
|
245
|
+
// Hard blockers dominate preview, overrides, and confirmation.
|
|
228
246
|
if (readiness.hasErrors) {
|
|
229
247
|
const firstError = readinessBlockers[0];
|
|
230
248
|
return notExecuted({
|
|
@@ -250,18 +268,18 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
250
268
|
},
|
|
251
269
|
});
|
|
252
270
|
}
|
|
253
|
-
//
|
|
254
|
-
if (
|
|
271
|
+
// Preview is speculative and never grants approval to a later invocation.
|
|
272
|
+
if (execution.request.mode === "preview") {
|
|
255
273
|
return makeOperationResolution({
|
|
256
274
|
...resolutionBase,
|
|
257
275
|
atomicity: { declared: atomicity, applied: "closure-atomic" },
|
|
258
276
|
units: plannedUnits(candidatePlan.jobs),
|
|
259
277
|
});
|
|
260
278
|
}
|
|
261
|
-
if (!("approvalRecovery" in
|
|
279
|
+
if (!("approvalRecovery" in execution)) {
|
|
262
280
|
return yield* new ApprovalRecoveryMissing();
|
|
263
281
|
}
|
|
264
|
-
const applyExecution =
|
|
282
|
+
const applyExecution = execution;
|
|
265
283
|
const overrideConditions = riskConditions.filter((condition) => condition.level === "override-required");
|
|
266
284
|
const missingOverrides = overrideConditions.filter((condition) => !applyExecution.request.acceptedPolicies.has(condition.policy));
|
|
267
285
|
if (missingOverrides.length > 0) {
|
|
@@ -281,8 +299,8 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
281
299
|
suggestions: escapes,
|
|
282
300
|
});
|
|
283
301
|
}
|
|
284
|
-
//
|
|
285
|
-
//
|
|
302
|
+
// Confirmation. A condition that consents only at a prompt — a publisher
|
|
303
|
+
// change, or any confirmable condition met by a command with no
|
|
286
304
|
// preapprovable confirmation — is never satisfied by preapproval. A
|
|
287
305
|
// preapprovable condition is satisfied by explicit preapproval; otherwise
|
|
288
306
|
// it is asked when a prompt can open and blocks when one cannot, naming
|
|
@@ -319,7 +337,7 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
319
337
|
}
|
|
320
338
|
}
|
|
321
339
|
}
|
|
322
|
-
//
|
|
340
|
+
// Acquire the workspace transition — planning, network acquisition,
|
|
323
341
|
// preview, and confirmation ran without it — then revalidate every material
|
|
324
342
|
// candidate preimage and apply the exact candidate while holding it.
|
|
325
343
|
const totalUnits = candidate.plan.jobs.reduce((count, job) => count + job.steps.length, 0);
|
|
@@ -329,16 +347,16 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
329
347
|
description: candidatePlan.description,
|
|
330
348
|
jobs: [{ concurrency: 1, steps: [step] }],
|
|
331
349
|
}, { restored: false })[0]?.state ?? "committed";
|
|
332
|
-
|
|
333
|
-
|
|
350
|
+
const startedUnits = yield* Ref.make(0);
|
|
351
|
+
const resolvedUnits = yield* Ref.make(0);
|
|
334
352
|
const applyFreshCandidate = Effect.gen(function* () {
|
|
335
353
|
yield* enterPhase("validation");
|
|
336
|
-
if (!(yield* isExecutionCandidateFresh(candidate)
|
|
354
|
+
if (!(yield* isExecutionCandidateFresh(candidate))) {
|
|
337
355
|
return yield* new StaleExecutionCandidate({ candidate: candidatePlan.name });
|
|
338
356
|
}
|
|
339
|
-
if (options
|
|
357
|
+
if (options?.beforeApply !== undefined) {
|
|
340
358
|
yield* options.beforeApply(candidate);
|
|
341
|
-
if (!(yield* isExecutionCandidateFresh(candidate)
|
|
359
|
+
if (!(yield* isExecutionCandidateFresh(candidate))) {
|
|
342
360
|
return yield* new StaleExecutionCandidate({ candidate: candidatePlan.name });
|
|
343
361
|
}
|
|
344
362
|
}
|
|
@@ -361,29 +379,29 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
361
379
|
return yield* applyPlan(closureScopedPlan, {
|
|
362
380
|
// The started fact is journaled before the run's first effect, so an
|
|
363
381
|
// interruption mid-run reports the unit in flight, never not attempted.
|
|
364
|
-
onStepStarted: (step) => appendStartedUnit(unitIdOf(step)).pipe(Effect.andThen(publishOperationEvent((seq, atMs) => ({
|
|
382
|
+
onStepStarted: (step) => appendStartedUnit(unitIdOf(step)).pipe(Effect.provideService(OperationJournal, journal), Effect.andThen(Ref.getAndUpdate(startedUnits, (index) => index + 1)), Effect.flatMap((index) => publishOperationEvent((seq, atMs) => ({
|
|
365
383
|
_tag: "UnitStarted",
|
|
366
384
|
seq,
|
|
367
385
|
atMs,
|
|
368
386
|
unitId: unitIdOf(step),
|
|
369
387
|
label: step.label,
|
|
370
|
-
index
|
|
388
|
+
index,
|
|
371
389
|
total: totalUnits,
|
|
372
390
|
})))),
|
|
373
391
|
// Settlement runs before the next interruptible boundary: the journal
|
|
374
392
|
// fact and the closure's snapshot disposition are recorded together —
|
|
375
393
|
// a settled closure's commits stand, a failed closure restores only
|
|
376
394
|
// itself, and later ready closures continue.
|
|
377
|
-
onStepCompleted: (step) => appendResolvedUnit(step).pipe(Effect.andThen(step.result.result === "error"
|
|
395
|
+
onStepCompleted: (step) => appendResolvedUnit(step).pipe(Effect.provideService(OperationJournal, journal), Effect.andThen(step.result.result === "error"
|
|
378
396
|
? rollbackWorkspaceClosure(unitIdOf(step))
|
|
379
|
-
: settleWorkspaceClosure(unitIdOf(step))), Effect.andThen(publishOperationEvent((seq, atMs) => ({
|
|
397
|
+
: settleWorkspaceClosure(unitIdOf(step))), Effect.andThen(Ref.getAndUpdate(resolvedUnits, (index) => index + 1)), Effect.flatMap((index) => publishOperationEvent((seq, atMs) => ({
|
|
380
398
|
_tag: "UnitResolved",
|
|
381
399
|
seq,
|
|
382
400
|
atMs,
|
|
383
401
|
unitId: unitIdOf(step),
|
|
384
402
|
label: step.label,
|
|
385
403
|
state: resolvedUnitState(step),
|
|
386
|
-
index
|
|
404
|
+
index,
|
|
387
405
|
total: totalUnits,
|
|
388
406
|
})))),
|
|
389
407
|
});
|
|
@@ -402,7 +420,7 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
402
420
|
// apply-level failure is a closure rollback that did not complete and
|
|
403
421
|
// verify — the typed restoration fact derives the retained truth from
|
|
404
422
|
// the in-memory pending record alone.
|
|
405
|
-
const pendingRestoration = yield*
|
|
423
|
+
const pendingRestoration = yield* pendingClosureRestorations;
|
|
406
424
|
if (Option.isSome(pendingRestoration) && pendingRestoration.value.failures.length > 0) {
|
|
407
425
|
const pending = pendingRestoration.value;
|
|
408
426
|
const first = pending.failures[0];
|
|
@@ -433,7 +451,7 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
433
451
|
return { ...result, candidateId: candidate.id };
|
|
434
452
|
}
|
|
435
453
|
const currentOutcomes = (yield* Effect.forEach(operations, (operation) => {
|
|
436
|
-
const override =
|
|
454
|
+
const override = provider.byExtensionType[operation.extensionType];
|
|
437
455
|
return operation.plannedState === "enabled" && override !== undefined
|
|
438
456
|
? override("current").pipe(Effect.map((outcomes) => outcomes.filter(({ name }) => name === operation.name)), Effect.mapError(configuredAgentOutcomesUnavailableToStepFailure))
|
|
439
457
|
: operation.plannedState === "enabled"
|
|
@@ -482,11 +500,11 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
482
500
|
(value.error._tag === "StepFailure" || value.error._tag === "StaleExecutionCandidate");
|
|
483
501
|
const guardedApply = (candidatePlan.executionCapabilities?.rollback === "non-rollbackable"
|
|
484
502
|
? applyCandidate
|
|
485
|
-
:
|
|
503
|
+
: runWorkspaceTransaction({
|
|
486
504
|
targets: [],
|
|
487
505
|
transition: applyCandidate,
|
|
488
506
|
validate: () => Effect.void,
|
|
489
|
-
onRestorationStarted: enterPhase("restoration"),
|
|
507
|
+
onRestorationStarted: enterPhase("restoration").pipe(Effect.provideService(OperationJournal, journal)),
|
|
490
508
|
// Closures protect the shared settings and lockfile at their own
|
|
491
509
|
// first touch; claiming them here would let a late failure tear an
|
|
492
510
|
// earlier closure's settled commit out of the shared files.
|
|
@@ -514,7 +532,7 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
|
|
|
514
532
|
const transitionSubject = "workspace-transition";
|
|
515
533
|
const applyResult = yield* Effect.scoped(Effect.gen(function* () {
|
|
516
534
|
const waited = yield* Ref.make(false);
|
|
517
|
-
const contention = yield*
|
|
535
|
+
const contention = yield* acquireWorkspaceTransition({
|
|
518
536
|
command: applyExecution.approvalRecovery.command.join(" "),
|
|
519
537
|
candidateId: candidate.id,
|
|
520
538
|
// Contention is a first-class lifecycle fact: observers render the
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @experimental This API is unstable and may change without notice.
|
|
9
9
|
*/
|
|
10
|
-
import type { ConfiguredAgentOutcomesUnavailable,
|
|
10
|
+
import type { ConfiguredAgentOutcomesUnavailable, WorkspaceStateReadFailure } from "@agentxm/workspace-state";
|
|
11
|
+
import type { WorkspaceRestorationIncomplete, WorkspaceTransactionFailure } from "@agentxm/workspace-transactions";
|
|
11
12
|
import { StepFailure, type CandidateFingerprintFailed } from "./errors.js";
|
|
12
13
|
/** Translate a scoped settings- or lockfile-read failure, naming the fix. */
|
|
13
14
|
export declare const workspaceStateReadFailureToStepFailure: (error: WorkspaceStateReadFailure) => StepFailure;
|
package/dist/src/testing.d.ts
CHANGED
|
@@ -7,7 +7,28 @@
|
|
|
7
7
|
* @experimental This API is unstable and may change without notice.
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
+
import * as Layer from "effect/Layer";
|
|
11
|
+
import { ConfiguredAgentOutcomesProvider } from "@agentxm/workspace-state";
|
|
12
|
+
import { FootprintRecorder } from "@agentxm/workspace-transactions";
|
|
13
|
+
import { OperationJournal } from "./plan/operation-journal.js";
|
|
14
|
+
/**
|
|
15
|
+
* An empty journal for one test invocation, standing in for the
|
|
16
|
+
* per-invocation journal the CLI's operation lifecycle creates around every
|
|
17
|
+
* command. Provide it wherever a test drives a plan directly instead of
|
|
18
|
+
* through that lifecycle. A test that asserts on what was recorded should
|
|
19
|
+
* build the service itself with `makeOperationJournal` and keep the ref.
|
|
20
|
+
*/
|
|
21
|
+
export declare const OperationJournalTest: Layer.Layer<OperationJournal>;
|
|
22
|
+
/** A fresh footprint recorder for one test invocation. */
|
|
23
|
+
export declare const FootprintRecorderTest: Layer.Layer<FootprintRecorder>;
|
|
24
|
+
/**
|
|
25
|
+
* Every per-invocation service `resolveExecutionCandidate` acquires that the CLI's
|
|
26
|
+
* operation lifecycle opens around a command: the journal, the footprint
|
|
27
|
+
* recorder, and a configured-agent outcome provider with no per-type
|
|
28
|
+
* refinement. A test that drives a plan directly needs all of them, so this
|
|
29
|
+
* is the layer to reach for rather than assembling the three by hand.
|
|
30
|
+
*/
|
|
31
|
+
export declare const PlanInvocationTest: Layer.Layer<OperationJournal | FootprintRecorder | ConfiguredAgentOutcomesProvider>;
|
|
10
32
|
export { ResolvePlanInteractionTest, type ResolvePlanInteractionTestState, } from "./plan/resolve-plan-interaction.js";
|
|
11
33
|
export { interactiveOnlyPlanExecution, preapprovedPlanExecution, promptablePlanExecution, } from "./plan/plan-execution-fixtures.js";
|
|
12
|
-
export { makeMemoryTransitionLockWorld, type MemoryTransitionLockWorld, } from "./operations/memory-transition-lock.js";
|
|
13
34
|
//# sourceMappingURL=testing.d.ts.map
|