@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
package/dist/src/index.d.ts
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @agentxm/workspace-operations public API.
|
|
3
3
|
*
|
|
4
|
-
* The workspace-operations
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The workspace-operations capability: plan vocabulary and execution, the
|
|
5
|
+
* two-phase candidate orchestration (`prepareExecutionCandidate` then
|
|
6
|
+
* `resolveExecutionCandidate`), operation resolutions and journals, and plan
|
|
7
|
+
* readiness and reconciliation gating. Transactions and the transition lock
|
|
8
|
+
* live in `@agentxm/workspace-transactions`; the composed workspace layer in
|
|
9
|
+
* `@agentxm/workspace-state/live`.
|
|
9
10
|
*
|
|
10
11
|
* @experimental This API is unstable and may change without notice.
|
|
11
12
|
* @packageDocumentation
|
|
12
13
|
*/
|
|
13
14
|
export { BlockingClassSchema, defaultOperationPresentation, operationPresentation, presentationOf, ArtifactMechanismSchema, ConfirmableConsentSchema, OperationPreconditionSchema, PlanPolicyIdSchema, PlanPolicyIds, PlanRiskConditionSchema, } from "./plan/plan.js";
|
|
14
|
-
export type { ArtifactMechanism, BlockingClass, CompletedJobStep, ConfirmableConsent, ErrorJobStep, ExecutedJob, ExecutedPlan, Job, JobStepArtifact, JobStepArtifactSource, JobStepArtifactTarget, JobStepResult, Operation, OperationPrecondition, OperationPresentation, Plan, PlanPolicyId, PlanRiskCondition, PlannedJobStep, ReadyJobStep,
|
|
15
|
+
export type { ArtifactMechanism, BlockingClass, CompletedJobStep, ConfirmableConsent, ErrorJobStep, ExecutedJob, ExecutedPlan, Job, JobStepArtifact, JobStepArtifactSource, JobStepArtifactTarget, JobStepResult, Operation, OperationPrecondition, OperationPresentation, Plan, PlanExecutionCapabilities, PlanPolicyId, PlanRiskCondition, PlannedJobStep, ReadyJobStep, RegistryLifecycleEvidence, UnitBlocking, WarnJobStep, } from "./plan/plan.js";
|
|
15
16
|
export { AtomicityClassSchema, OperationOutcomeSchema, OperationPhaseSchema, UnitDispositionSchema, UnitStateSchema, countUnitStates, declaredAtomicity, deriveOperationOutcome, executedUnits, makeOperationResolution, plannedUnits, unitIdOf, unitsByStableIdentity, } from "./plan/operation-resolution.js";
|
|
16
17
|
export type { AtomicityClass, MakeOperationResolutionArgs, OperationAtomicity, OperationBlock, OperationFootprintEntry, OperationInterruption, OperationOutcome, OperationPhase, OperationRecovery, OperationResolution, ResolvedUnit, UnitDisposition, UnitState, UnitStateCounts, } from "./plan/operation-resolution.js";
|
|
17
18
|
export { OperationJournal, appendResolvedUnit, appendStartedUnit, recordJournalPhase, getOperationJournal, makeOperationJournal, recordOperationJournal, updateOperationJournal, type OperationJournalService, type OperationJournalState, } from "./plan/operation-journal.js";
|
|
18
19
|
export { applyPlan, type ApplyPlanOptions, type OperationHandler } from "./plan/apply-plan.js";
|
|
19
20
|
export { CurrentOperationUnit, OperationEventSchema, OperationLifecycle, OperationModeSchema, ProgressUnitSchema, SettledOutcomeSchema, awaitDrained, lifecycleEvents, makeOperationLifecycle, makeThrottledUnitProgress, observeChildUnit, observeUnit, publishOperationEvent, publishPhaseStarted, publishUnitProgress, publishWaitEnded, publishWaiting, settleOperation, subscribeLossless, type ObservedUnit, type OperationEvent, type OperationEventEncoded, type OperationEventInput, type OperationLifecycleService, type OperationMode, type ProgressUnit, type SettledOutcome, } from "./plan/operation-events.js";
|
|
20
21
|
export { ApprovalRecoveryMissing, CandidateFingerprintFailed, OPERATION_ERROR_CATEGORIES, OperationErrorCategorySchema, PlanInteractionFailed, STALE_CANDIDATE_DETAIL, StaleExecutionCandidate, StepFailure, type OperationErrorCategory, } from "./plan/errors.js";
|
|
21
|
-
export {
|
|
22
|
+
export { prepareExecutionCandidate, resolveExecutionCandidate, type PrepareExecutionCandidateOptions, type ResolveExecutionCandidateOptions, } from "./plan/resolve-plan.js";
|
|
23
|
+
export { candidateFingerprintFailedToStepFailure, configuredAgentOutcomesUnavailableToStepFailure, restorationIncompleteToStepFailure, workspaceStateReadFailureToStepFailure, workspaceTransactionFailureToStepFailure, } from "./plan/step-failure-conversions.js";
|
|
22
24
|
export { ResolvePlanInteraction, type ApplyConfirmation, type ResolvePlanInteractionService, } from "./plan/resolve-plan-interaction.js";
|
|
23
25
|
export { InterruptionSignalSource, type InterruptionSignalSourceService, } from "./plan/interruption-signal.js";
|
|
26
|
+
export { resolveInterruption, type InterruptedInvocation, type ObservedFootprintEntry, } from "./plan/interruption-resolution.js";
|
|
24
27
|
export { isExecutionCandidateFresh, makeExecutionCandidate, type ExecutionCandidate, } from "./plan/execution-candidate.js";
|
|
25
|
-
export { applyPlanExecution, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, unclassifiedRecoveryValue, type ConfirmableRiskApproval, type ConfirmationRecovery, type ConfirmationRecoveryArgument, type ConfirmationRecoveryValue, type ConfiguredAgentOperation, type PlanExecution, type PlanExecutionRequest, type RecoveryApproval, } from "./plan/plan-execution.js";
|
|
28
|
+
export { applyPlanExecution, confirmableRiskApproval, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, requestedPlanExecution, unclassifiedRecoveryValue, type ConfirmableRiskApproval, type ConfirmationRecovery, type ConfirmationRecoveryArgument, type ConfirmationRecoveryValue, type ConfiguredAgentOperation, type PlanExecution, type PlanExecutionRequest, type RecoveryApproval, type RequestedApproval, type RequestedPlanIntent, } from "./plan/plan-execution.js";
|
|
26
29
|
export * from "./plan/job-step-message.js";
|
|
27
30
|
export { scanPlanReadiness, type PlanReadinessReport } from "./operations/scan-plan-readiness.js";
|
|
28
31
|
export { augmentPlanWithReconciliation, type AugmentedPlanResult, type DegradedLockfileState, } from "./operations/augment-plan.js";
|
|
29
|
-
export { rollbackWorkspaceClosure, runWorkspaceTransaction, settleWorkspaceClosure, withWorkspaceClosure, type WorkspaceTransactionArgs, } from "./operations/transaction.js";
|
|
30
|
-
export { liveWorkspaceTransitionLock, type WorkspaceTransitionLock, TRANSITION_WAIT_BOUND_MILLIS, acquireWorkspaceTransitionLock, heldWorkspaceTransition, isWorkspaceTransitionHeldByThisInvocation, transitionLockPath, type HeldWorkspaceTransition, } from "./operations/transition-lock.js";
|
|
31
32
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @agentxm/workspace-operations public API.
|
|
3
3
|
*
|
|
4
|
-
* The workspace-operations
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The workspace-operations capability: plan vocabulary and execution, the
|
|
5
|
+
* two-phase candidate orchestration (`prepareExecutionCandidate` then
|
|
6
|
+
* `resolveExecutionCandidate`), operation resolutions and journals, and plan
|
|
7
|
+
* readiness and reconciliation gating. Transactions and the transition lock
|
|
8
|
+
* live in `@agentxm/workspace-transactions`; the composed workspace layer in
|
|
9
|
+
* `@agentxm/workspace-state/live`.
|
|
9
10
|
*
|
|
10
11
|
* @experimental This API is unstable and may change without notice.
|
|
11
12
|
* @packageDocumentation
|
|
@@ -23,21 +24,24 @@ export { applyPlan } from "./plan/apply-plan.js";
|
|
|
23
24
|
export { CurrentOperationUnit, OperationEventSchema, OperationLifecycle, OperationModeSchema, ProgressUnitSchema, SettledOutcomeSchema, awaitDrained, lifecycleEvents, makeOperationLifecycle, makeThrottledUnitProgress, observeChildUnit, observeUnit, publishOperationEvent, publishPhaseStarted, publishUnitProgress, publishWaitEnded, publishWaiting, settleOperation, subscribeLossless, } from "./plan/operation-events.js";
|
|
24
25
|
// Serialized error vocabulary and the plan-family tagged errors.
|
|
25
26
|
export { ApprovalRecoveryMissing, CandidateFingerprintFailed, OPERATION_ERROR_CATEGORIES, OperationErrorCategorySchema, PlanInteractionFailed, STALE_CANDIDATE_DETAIL, StaleExecutionCandidate, StepFailure, } from "./plan/errors.js";
|
|
26
|
-
//
|
|
27
|
-
|
|
27
|
+
// Candidate preparation and interactive preview/apply resolution over the
|
|
28
|
+
// workspace read model: preview, confirmation and apply share one candidate,
|
|
29
|
+
// so what a person reads and what is applied are the same decision.
|
|
30
|
+
export { prepareExecutionCandidate, resolveExecutionCandidate, } from "./plan/resolve-plan.js";
|
|
31
|
+
// Conversions from the kernel families a step can surface into the
|
|
32
|
+
// serialized step vocabulary, so a producer serializes them identically
|
|
33
|
+
// wherever the step was assembled.
|
|
34
|
+
export { candidateFingerprintFailedToStepFailure, configuredAgentOutcomesUnavailableToStepFailure, restorationIncompleteToStepFailure, workspaceStateReadFailureToStepFailure, workspaceTransactionFailureToStepFailure, } from "./plan/step-failure-conversions.js";
|
|
28
35
|
// Interaction port for preview/apply presentation, progress, and confirmation.
|
|
29
36
|
// The CLI runtime provides the Live implementation.
|
|
30
37
|
export { ResolvePlanInteraction, } from "./plan/resolve-plan-interaction.js";
|
|
31
38
|
export { InterruptionSignalSource, } from "./plan/interruption-signal.js";
|
|
39
|
+
export { resolveInterruption, } from "./plan/interruption-resolution.js";
|
|
32
40
|
export { isExecutionCandidateFresh, makeExecutionCandidate, } from "./plan/execution-candidate.js";
|
|
33
|
-
export { applyPlanExecution, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, unclassifiedRecoveryValue, } from "./plan/plan-execution.js";
|
|
41
|
+
export { applyPlanExecution, confirmableRiskApproval, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, requestedPlanExecution, unclassifiedRecoveryValue, } from "./plan/plan-execution.js";
|
|
34
42
|
// Job step messaging
|
|
35
43
|
export * from "./plan/job-step-message.js";
|
|
36
44
|
// Plan readiness and reconciliation gating
|
|
37
45
|
export { scanPlanReadiness } from "./operations/scan-plan-readiness.js";
|
|
38
46
|
export { augmentPlanWithReconciliation, } from "./operations/augment-plan.js";
|
|
39
|
-
// Workspace transaction runner and closure lifecycle
|
|
40
|
-
export { rollbackWorkspaceClosure, runWorkspaceTransaction, settleWorkspaceClosure, withWorkspaceClosure, } from "./operations/transaction.js";
|
|
41
|
-
// Transition lock runtime
|
|
42
|
-
export { liveWorkspaceTransitionLock, TRANSITION_WAIT_BOUND_MILLIS, acquireWorkspaceTransitionLock, heldWorkspaceTransition, isWorkspaceTransitionHeldByThisInvocation, transitionLockPath, } from "./operations/transition-lock.js";
|
|
43
47
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure function to scan a plan's steps and collect readiness statistics.
|
|
3
3
|
*
|
|
4
|
-
* Extracts from the inline logic in `
|
|
4
|
+
* Extracts from the inline logic in `resolveExecutionCandidate` so it can be tested
|
|
5
5
|
* independently and reused from core without CLI dependencies.
|
|
6
6
|
*
|
|
7
7
|
* @experimental This API is unstable and may change without notice.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure function to scan a plan's steps and collect readiness statistics.
|
|
3
3
|
*
|
|
4
|
-
* Extracts from the inline logic in `
|
|
4
|
+
* Extracts from the inline logic in `resolveExecutionCandidate` so it can be tested
|
|
5
5
|
* independently and reused from core without CLI dependencies.
|
|
6
6
|
*
|
|
7
7
|
* @experimental This API is unstable and may change without notice.
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import * as Array from "effect/Array";
|
|
16
16
|
import * as Effect from "effect/Effect";
|
|
17
|
+
import * as Ref from "effect/Ref";
|
|
17
18
|
import { StepFailure } from "./errors.js";
|
|
18
19
|
// -----------------------------------------------------------------------------
|
|
19
20
|
// Implementation
|
|
@@ -202,10 +203,12 @@ export const applyPlan = (plan, options) => Effect.gen(function* () {
|
|
|
202
203
|
const observeStep = options?.onStepCompleted ?? (() => Effect.void);
|
|
203
204
|
const observeStart = options?.onStepStarted ?? (() => Effect.void);
|
|
204
205
|
const hasReadinessError = plan.jobs.some((job) => job.steps.some((step) => step.readiness === "error"));
|
|
205
|
-
|
|
206
|
+
// Any job with an error blocks every later job; the fact travels in a
|
|
207
|
+
// `Ref` because it crosses the job traversal's iteration boundary.
|
|
208
|
+
const blocked = yield* Ref.make(false);
|
|
206
209
|
const jobResults = hasReadinessError
|
|
207
210
|
? applyReadinessGate(plan)
|
|
208
|
-
: yield* Effect.forEach(plan.jobs, (job) => blocked
|
|
211
|
+
: yield* Effect.forEach(plan.jobs, (job) => Effect.flatMap(Ref.get(blocked), (isBlocked) => isBlocked
|
|
209
212
|
? Effect.succeed(job.steps.map((step) => blockStep(step, "blocked by earlier job failure", {
|
|
210
213
|
class: "operation-aborted",
|
|
211
214
|
})))
|
|
@@ -213,12 +216,9 @@ export const applyPlan = (plan, options) => Effect.gen(function* () {
|
|
|
213
216
|
? executeDependencyAwareJob(job, observeStart, observeStep)
|
|
214
217
|
: job.executionPolicy === "best-effort"
|
|
215
218
|
? executeBestEffortJob(job, observeStart, observeStep)
|
|
216
|
-
: executeFailFastJob(job, observeStart, observeStep)).pipe(Effect.tap((steps) =>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
return Effect.void;
|
|
221
|
-
})), { concurrency: 1 });
|
|
219
|
+
: executeFailFastJob(job, observeStart, observeStep)).pipe(Effect.tap((steps) => steps.some((step) => step.result.result === "error")
|
|
220
|
+
? Ref.set(blocked, true)
|
|
221
|
+
: Effect.void))), { concurrency: 1 });
|
|
222
222
|
return {
|
|
223
223
|
_tag: "ExecutedPlan",
|
|
224
224
|
name: plan.name,
|
|
@@ -3,6 +3,7 @@ import * as Path from "effect/Path";
|
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import { CandidateFingerprintFailed } from "./errors.js";
|
|
5
5
|
import type { Plan } from "./plan.js";
|
|
6
|
+
import type { ConfiguredAgentOperation } from "./plan-execution.js";
|
|
6
7
|
export interface ExecutionCandidate<Requirements = never, Output = never> {
|
|
7
8
|
readonly id: string;
|
|
8
9
|
readonly plan: Plan<Requirements, Output>;
|
|
@@ -10,11 +11,16 @@ export interface ExecutionCandidate<Requirements = never, Output = never> {
|
|
|
10
11
|
readonly materialFingerprint: string;
|
|
11
12
|
/** Base the material fingerprint is relative to; freshness recomputes against it. */
|
|
12
13
|
readonly baseDir: string;
|
|
14
|
+
/**
|
|
15
|
+
* Configured-agent operations whose outcomes the candidate projected into
|
|
16
|
+
* its steps and verifies after apply.
|
|
17
|
+
*/
|
|
18
|
+
readonly configuredAgentOperations: ReadonlyArray<ConfiguredAgentOperation>;
|
|
13
19
|
}
|
|
14
20
|
export declare const makeExecutionCandidate: <Requirements, Output>(plan: Plan<Requirements, Output>, paths: {
|
|
15
21
|
readonly settingsPath: string;
|
|
16
22
|
readonly lockPath: string;
|
|
17
23
|
readonly baseDir: string;
|
|
18
|
-
}) => Effect.Effect<ExecutionCandidate<Requirements, Output>, CandidateFingerprintFailed, FileSystem.FileSystem | Path.Path>;
|
|
24
|
+
}, configuredAgentOperations?: ReadonlyArray<ConfiguredAgentOperation>) => Effect.Effect<ExecutionCandidate<Requirements, Output>, CandidateFingerprintFailed, FileSystem.FileSystem | Path.Path>;
|
|
19
25
|
export declare const isExecutionCandidateFresh: (candidate: ExecutionCandidate<unknown, unknown>) => Effect.Effect<boolean, CandidateFingerprintFailed, FileSystem.FileSystem | Path.Path>;
|
|
20
26
|
//# sourceMappingURL=execution-candidate.d.ts.map
|
|
@@ -73,7 +73,7 @@ const planIdentity = (plan, baseDir, path) => JSON.stringify({
|
|
|
73
73
|
? path.relative(baseDir, value)
|
|
74
74
|
: value;
|
|
75
75
|
});
|
|
76
|
-
export const makeExecutionCandidate = (plan, paths) => Effect.gen(function* () {
|
|
76
|
+
export const makeExecutionCandidate = (plan, paths, configuredAgentOperations = []) => Effect.gen(function* () {
|
|
77
77
|
const fs = yield* FileSystem.FileSystem;
|
|
78
78
|
const path = yield* Path.Path;
|
|
79
79
|
const materialPaths = resolveMaterialPaths(plan, paths.settingsPath, paths.lockPath, paths.baseDir, path);
|
|
@@ -84,7 +84,14 @@ export const makeExecutionCandidate = (plan, paths) => Effect.gen(function* () {
|
|
|
84
84
|
.update("\0")
|
|
85
85
|
.update(materialFingerprint)
|
|
86
86
|
.digest("hex");
|
|
87
|
-
return {
|
|
87
|
+
return {
|
|
88
|
+
id,
|
|
89
|
+
plan,
|
|
90
|
+
materialPaths,
|
|
91
|
+
materialFingerprint,
|
|
92
|
+
baseDir: paths.baseDir,
|
|
93
|
+
configuredAgentOperations,
|
|
94
|
+
};
|
|
88
95
|
});
|
|
89
96
|
export const isExecutionCandidateFresh = (candidate) => Effect.gen(function* () {
|
|
90
97
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal resolution of an interrupted operation.
|
|
3
|
+
*
|
|
4
|
+
* Interruption is the one lifecycle fact the operation body cannot settle for
|
|
5
|
+
* itself: the signal arrives from outside it. Turning the operation journal
|
|
6
|
+
* into a contract-valid resolution — which closures settled, which were in
|
|
7
|
+
* flight, what durable state is retained, and which atomicity class actually
|
|
8
|
+
* held — is execution semantics, so it lives beside closure execution rather
|
|
9
|
+
* than in a transport adapter. The caller supplies only the resume command it
|
|
10
|
+
* would print, because how an operator re-runs a request is a property of the
|
|
11
|
+
* surface that accepted it.
|
|
12
|
+
*
|
|
13
|
+
* @experimental This API is unstable and may change without notice.
|
|
14
|
+
*/
|
|
15
|
+
import type * as Option from "effect/Option";
|
|
16
|
+
import { type AtomicityClass, type OperationResolution } from "./operation-resolution.js";
|
|
17
|
+
import type { OperationPresentation } from "./plan.js";
|
|
18
|
+
import type { OperationJournalState } from "./operation-journal.js";
|
|
19
|
+
/** What the invoking surface knows about a request the signal cut short. */
|
|
20
|
+
export interface InterruptedInvocation {
|
|
21
|
+
/** Operation name for a resolution produced before planning completed. */
|
|
22
|
+
readonly planName: string;
|
|
23
|
+
readonly mode: "preview" | "apply";
|
|
24
|
+
/**
|
|
25
|
+
* The command family's statically declared atomicity, for a resolution
|
|
26
|
+
* produced before the journal exists. Defaults to `closure-atomic`.
|
|
27
|
+
*/
|
|
28
|
+
readonly declaredAtomicity?: AtomicityClass;
|
|
29
|
+
readonly presentation?: OperationPresentation;
|
|
30
|
+
/** The command an operator re-runs to continue; the surface renders it. */
|
|
31
|
+
readonly replayCommand: string;
|
|
32
|
+
}
|
|
33
|
+
/** One durable path the footprint recorder observed before the signal landed. */
|
|
34
|
+
export interface ObservedFootprintEntry {
|
|
35
|
+
readonly path: string;
|
|
36
|
+
readonly change: "created" | "modified" | "removed" | "restored";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Derive the terminal resolution for an externally interrupted invocation.
|
|
40
|
+
*
|
|
41
|
+
* Before the journal exists nothing was planned or attempted: the resolution
|
|
42
|
+
* carries the requested mode and the command family's declared atomicity,
|
|
43
|
+
* never a hardcoded apply/closure-atomic claim. Once apply has begun, settled
|
|
44
|
+
* closures keep their settlement — a commit stands as retained durable state,
|
|
45
|
+
* and a failure of a restoring apply had already rolled back only its own
|
|
46
|
+
* closure — while a unit that started without a settlement fact is reported
|
|
47
|
+
* `interrupted`, never as not attempted.
|
|
48
|
+
*/
|
|
49
|
+
export declare const resolveInterruption: (invocation: InterruptedInvocation, journal: Option.Option<OperationJournalState>, signal: "SIGINT" | "SIGTERM", footprint: ReadonlyArray<ObservedFootprintEntry>) => OperationResolution<unknown>;
|
|
50
|
+
//# sourceMappingURL=interruption-resolution.d.ts.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal resolution of an interrupted operation.
|
|
3
|
+
*
|
|
4
|
+
* Interruption is the one lifecycle fact the operation body cannot settle for
|
|
5
|
+
* itself: the signal arrives from outside it. Turning the operation journal
|
|
6
|
+
* into a contract-valid resolution — which closures settled, which were in
|
|
7
|
+
* flight, what durable state is retained, and which atomicity class actually
|
|
8
|
+
* held — is execution semantics, so it lives beside closure execution rather
|
|
9
|
+
* than in a transport adapter. The caller supplies only the resume command it
|
|
10
|
+
* would print, because how an operator re-runs a request is a property of the
|
|
11
|
+
* surface that accepted it.
|
|
12
|
+
*
|
|
13
|
+
* @experimental This API is unstable and may change without notice.
|
|
14
|
+
*/
|
|
15
|
+
import * as OptionModule from "effect/Option";
|
|
16
|
+
import { executedUnits, makeOperationResolution, unitIdOf, } from "./operation-resolution.js";
|
|
17
|
+
/**
|
|
18
|
+
* Derive the terminal resolution for an externally interrupted invocation.
|
|
19
|
+
*
|
|
20
|
+
* Before the journal exists nothing was planned or attempted: the resolution
|
|
21
|
+
* carries the requested mode and the command family's declared atomicity,
|
|
22
|
+
* never a hardcoded apply/closure-atomic claim. Once apply has begun, settled
|
|
23
|
+
* closures keep their settlement — a commit stands as retained durable state,
|
|
24
|
+
* and a failure of a restoring apply had already rolled back only its own
|
|
25
|
+
* closure — while a unit that started without a settlement fact is reported
|
|
26
|
+
* `interrupted`, never as not attempted.
|
|
27
|
+
*/
|
|
28
|
+
export const resolveInterruption = (invocation, journal, signal, footprint) => {
|
|
29
|
+
const observedFootprint = footprint.length === 0 ? {} : { footprint };
|
|
30
|
+
if (OptionModule.isNone(journal)) {
|
|
31
|
+
return makeOperationResolution({
|
|
32
|
+
name: invocation.planName,
|
|
33
|
+
description: OptionModule.none(),
|
|
34
|
+
mode: invocation.mode,
|
|
35
|
+
atomicity: {
|
|
36
|
+
declared: invocation.declaredAtomicity ?? "closure-atomic",
|
|
37
|
+
// Nothing was attempted, so no durable effect was made or retained.
|
|
38
|
+
applied: "closure-atomic",
|
|
39
|
+
},
|
|
40
|
+
units: [],
|
|
41
|
+
presentation: invocation.presentation,
|
|
42
|
+
interruption: { signal, disposition: "none" },
|
|
43
|
+
...observedFootprint,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const state = journal.value;
|
|
47
|
+
const applying = state.phase === "apply" || state.phase === "restoration";
|
|
48
|
+
if (!applying) {
|
|
49
|
+
// Planning, preview, confirmation, or validation: nothing was attempted
|
|
50
|
+
// and the planned units stand as planned.
|
|
51
|
+
return makeOperationResolution({
|
|
52
|
+
name: state.name,
|
|
53
|
+
description: state.description,
|
|
54
|
+
mode: state.mode,
|
|
55
|
+
candidateId: state.candidateId,
|
|
56
|
+
atomicity: { declared: state.atomicity.declared, applied: "closure-atomic" },
|
|
57
|
+
units: state.plannedUnits,
|
|
58
|
+
presentation: state.presentation ?? invocation.presentation,
|
|
59
|
+
releaseAge: state.releaseAge,
|
|
60
|
+
preconditions: state.preconditions,
|
|
61
|
+
riskConditions: state.riskConditions,
|
|
62
|
+
interruption: { signal, disposition: "none" },
|
|
63
|
+
...observedFootprint,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const resolvedUnits = executedUnits({
|
|
67
|
+
_tag: "ExecutedPlan",
|
|
68
|
+
name: state.name,
|
|
69
|
+
description: state.description,
|
|
70
|
+
jobs: [{ concurrency: 1, steps: state.resolved }],
|
|
71
|
+
});
|
|
72
|
+
// Closures settle independently: a settled commit stands as retained
|
|
73
|
+
// durable state regardless of the interruption, and a settled failure of a
|
|
74
|
+
// restoring apply had already rolled back only its own closure.
|
|
75
|
+
const settledUnits = resolvedUnits.map((unit) => unit.state === "committed"
|
|
76
|
+
? { ...unit, disposition: "retained" }
|
|
77
|
+
: unit.state === "failed" && state.restoresOnFailure
|
|
78
|
+
? { ...unit, disposition: "restored" }
|
|
79
|
+
: unit);
|
|
80
|
+
const resolvedIds = new Set(state.resolved.map((step) => unitIdOf(step)));
|
|
81
|
+
const startedIds = new Set(state.startedUnitIds);
|
|
82
|
+
// A started unit missing a settlement fact was in flight at the stopping
|
|
83
|
+
// point: its effects were restored by the closure's rollback, or their
|
|
84
|
+
// settlement was simply not observed — never "not attempted".
|
|
85
|
+
const inFlight = state.plannedUnits
|
|
86
|
+
.filter((unit) => startedIds.has(unit.id) && !resolvedIds.has(unit.id))
|
|
87
|
+
.map((unit) => ({
|
|
88
|
+
...unit,
|
|
89
|
+
state: "interrupted",
|
|
90
|
+
disposition: state.restoresOnFailure ? "restored" : "unknown",
|
|
91
|
+
message: state.restoresOnFailure
|
|
92
|
+
? "interrupted while in flight; effects were restored"
|
|
93
|
+
: "interrupted while in flight; settlement was not observed",
|
|
94
|
+
}));
|
|
95
|
+
const notStarted = state.plannedUnits
|
|
96
|
+
.filter((unit) => !startedIds.has(unit.id) && !resolvedIds.has(unit.id))
|
|
97
|
+
.map((unit) => ({
|
|
98
|
+
...unit,
|
|
99
|
+
state: "blocked",
|
|
100
|
+
message: "not attempted: the operation was interrupted",
|
|
101
|
+
blocking: {
|
|
102
|
+
class: "operation-aborted",
|
|
103
|
+
subject: unit.id,
|
|
104
|
+
phase: "apply",
|
|
105
|
+
detail: "not attempted: the operation was interrupted",
|
|
106
|
+
reference: "interruption",
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
const units = [...settledUnits, ...inFlight, ...notStarted];
|
|
110
|
+
const committed = settledUnits.filter((unit) => unit.state === "committed");
|
|
111
|
+
// Unknown dominates — durable state may exist beyond what settled. With
|
|
112
|
+
// everything settled or restored, retained commits are the headline;
|
|
113
|
+
// restored in-flight work without commits reports restored.
|
|
114
|
+
const disposition = inFlight.length > 0 && !state.restoresOnFailure
|
|
115
|
+
? "unknown"
|
|
116
|
+
: committed.length > 0
|
|
117
|
+
? "retained"
|
|
118
|
+
: inFlight.length > 0 || (state.resolved.length > 0 && state.restoresOnFailure)
|
|
119
|
+
? "restored"
|
|
120
|
+
: "none";
|
|
121
|
+
const recovery = disposition === "retained" || disposition === "unknown"
|
|
122
|
+
? {
|
|
123
|
+
retained: committed.flatMap((unit) => unit.artifact === undefined ? [unit.id] : [unit.artifact.path]),
|
|
124
|
+
actions: [
|
|
125
|
+
{
|
|
126
|
+
description: "Re-run the command to continue the remaining units.",
|
|
127
|
+
cmd: invocation.replayCommand,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
}
|
|
131
|
+
: undefined;
|
|
132
|
+
return makeOperationResolution({
|
|
133
|
+
name: state.name,
|
|
134
|
+
description: state.description,
|
|
135
|
+
mode: state.mode,
|
|
136
|
+
candidateId: state.candidateId,
|
|
137
|
+
atomicity: {
|
|
138
|
+
declared: state.atomicity.declared,
|
|
139
|
+
// A restoring apply keeps its closure-atomic promise: settled commits
|
|
140
|
+
// stand and everything else was restored. Only unobserved settlement
|
|
141
|
+
// or non-rollbackable retention downgrade the applied class.
|
|
142
|
+
applied: state.restoresOnFailure
|
|
143
|
+
? "closure-atomic"
|
|
144
|
+
: disposition === "retained" || disposition === "unknown"
|
|
145
|
+
? "non-rollbackable"
|
|
146
|
+
: "closure-atomic",
|
|
147
|
+
},
|
|
148
|
+
units,
|
|
149
|
+
presentation: state.presentation ?? invocation.presentation,
|
|
150
|
+
releaseAge: state.releaseAge,
|
|
151
|
+
preconditions: state.preconditions,
|
|
152
|
+
riskConditions: state.riskConditions,
|
|
153
|
+
interruption: { signal, disposition },
|
|
154
|
+
recovery,
|
|
155
|
+
...observedFootprint,
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=interruption-resolution.js.map
|
|
@@ -18,7 +18,7 @@ import * as Option from "effect/Option";
|
|
|
18
18
|
import * as Ref from "effect/Ref";
|
|
19
19
|
import * as ServiceMap from "effect/Context";
|
|
20
20
|
import type { CompletedJobStep, OperationPresentation, PlanRiskCondition } from "./plan.js";
|
|
21
|
-
import type { ReleaseAgeOperationEvidence } from "@agentxm/
|
|
21
|
+
import type { ReleaseAgeOperationEvidence } from "@agentxm/extension-resolution";
|
|
22
22
|
import type { OperationPrecondition } from "./plan.js";
|
|
23
23
|
import type { OperationAtomicity, OperationPhase, ResolvedUnit } from "./operation-resolution.js";
|
|
24
24
|
export interface OperationJournalState {
|
|
@@ -53,17 +53,17 @@ export interface OperationJournalService {
|
|
|
53
53
|
declare const OperationJournal_base: ServiceMap.ServiceClass<OperationJournal, "@agentxm/workspace-operations/plan/operation-journal/OperationJournal", OperationJournalService>;
|
|
54
54
|
export declare class OperationJournal extends OperationJournal_base {
|
|
55
55
|
}
|
|
56
|
-
/** Record the operation's frozen facts.
|
|
57
|
-
export declare const recordOperationJournal: (state: OperationJournalState) => Effect.Effect<void>;
|
|
58
|
-
/** Merge updates onto the recorded state. No-op when nothing was recorded. */
|
|
59
|
-
export declare const updateOperationJournal: (update: (state: OperationJournalState) => OperationJournalState) => Effect.Effect<void>;
|
|
56
|
+
/** Record the operation's frozen facts. */
|
|
57
|
+
export declare const recordOperationJournal: (state: OperationJournalState) => Effect.Effect<void, never, OperationJournal>;
|
|
58
|
+
/** Merge updates onto the recorded state. No-op when nothing was recorded yet. */
|
|
59
|
+
export declare const updateOperationJournal: (update: (state: OperationJournalState) => OperationJournalState) => Effect.Effect<void, never, OperationJournal>;
|
|
60
60
|
/** Record the lifecycle phase the invocation has entered. */
|
|
61
|
-
export declare const recordJournalPhase: (phase: OperationPhase) => Effect.Effect<void>;
|
|
61
|
+
export declare const recordJournalPhase: (phase: OperationPhase) => Effect.Effect<void, never, OperationJournal>;
|
|
62
62
|
/** Record that a unit's run began. */
|
|
63
|
-
export declare const appendStartedUnit: (unitId: string) => Effect.Effect<void>;
|
|
63
|
+
export declare const appendStartedUnit: (unitId: string) => Effect.Effect<void, never, OperationJournal>;
|
|
64
64
|
/** Record one unit's settlement fact. */
|
|
65
|
-
export declare const appendResolvedUnit: (step: CompletedJobStep<unknown>) => Effect.Effect<void>;
|
|
66
|
-
export declare const getOperationJournal: Effect.Effect<Option.Option<OperationJournalState
|
|
65
|
+
export declare const appendResolvedUnit: (step: CompletedJobStep<unknown>) => Effect.Effect<void, never, OperationJournal>;
|
|
66
|
+
export declare const getOperationJournal: Effect.Effect<Option.Option<OperationJournalState>, never, OperationJournal>;
|
|
67
67
|
export declare const makeOperationJournal: Effect.Effect<OperationJournalService>;
|
|
68
68
|
export {};
|
|
69
69
|
//# sourceMappingURL=operation-journal.d.ts.map
|
|
@@ -19,20 +19,10 @@ import * as Ref from "effect/Ref";
|
|
|
19
19
|
import * as ServiceMap from "effect/Context";
|
|
20
20
|
export class OperationJournal extends ServiceMap.Service()("@agentxm/workspace-operations/plan/operation-journal/OperationJournal") {
|
|
21
21
|
}
|
|
22
|
-
/** Record the operation's frozen facts.
|
|
23
|
-
export const recordOperationJournal = (state) => Effect.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return;
|
|
27
|
-
yield* Ref.set(service.value.ref, Option.some(state));
|
|
28
|
-
});
|
|
29
|
-
/** Merge updates onto the recorded state. No-op when nothing was recorded. */
|
|
30
|
-
export const updateOperationJournal = (update) => Effect.gen(function* () {
|
|
31
|
-
const service = yield* Effect.serviceOption(OperationJournal);
|
|
32
|
-
if (Option.isNone(service))
|
|
33
|
-
return;
|
|
34
|
-
yield* Ref.update(service.value.ref, Option.map(update));
|
|
35
|
-
});
|
|
22
|
+
/** Record the operation's frozen facts. */
|
|
23
|
+
export const recordOperationJournal = (state) => Effect.flatMap(OperationJournal, (service) => Ref.set(service.ref, Option.some(state)));
|
|
24
|
+
/** Merge updates onto the recorded state. No-op when nothing was recorded yet. */
|
|
25
|
+
export const updateOperationJournal = (update) => Effect.flatMap(OperationJournal, (service) => Ref.update(service.ref, Option.map(update)));
|
|
36
26
|
/** Record the lifecycle phase the invocation has entered. */
|
|
37
27
|
export const recordJournalPhase = (phase) => updateOperationJournal((state) => ({ ...state, phase }));
|
|
38
28
|
/** Record that a unit's run began. */
|
|
@@ -42,11 +32,6 @@ export const appendStartedUnit = (unitId) => updateOperationJournal((state) => (
|
|
|
42
32
|
}));
|
|
43
33
|
/** Record one unit's settlement fact. */
|
|
44
34
|
export const appendResolvedUnit = (step) => updateOperationJournal((state) => ({ ...state, resolved: [...state.resolved, step] }));
|
|
45
|
-
export const getOperationJournal = Effect.
|
|
46
|
-
const service = yield* Effect.serviceOption(OperationJournal);
|
|
47
|
-
if (Option.isNone(service))
|
|
48
|
-
return Option.none();
|
|
49
|
-
return yield* Ref.get(service.value.ref);
|
|
50
|
-
});
|
|
35
|
+
export const getOperationJournal = Effect.flatMap(OperationJournal, (service) => Ref.get(service.ref));
|
|
51
36
|
export const makeOperationJournal = Ref.make(Option.none()).pipe(Effect.map((ref) => ({ ref })));
|
|
52
37
|
//# sourceMappingURL=operation-journal.js.map
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type * as Option from "effect/Option";
|
|
16
16
|
import * as Schema from "effect/Schema";
|
|
17
17
|
import type { OperationErrorCategory, StepFailure } from "./errors.js";
|
|
18
|
-
import type { ReleaseAgeOperationEvidence } from "@agentxm/
|
|
18
|
+
import type { ReleaseAgeOperationEvidence } from "@agentxm/extension-resolution";
|
|
19
19
|
import type { SuggestedAction } from "@agentxm/registry-protocol/unstable/suggested-action";
|
|
20
20
|
import type { BlockingClass, ExecutedPlan, Job, JobStepArtifact, OperationPrecondition, OperationPresentation, PlanRiskCondition, RegistryLifecycleEvidence } from "./plan.js";
|
|
21
21
|
import type { ConfiguredAgentOutcome } from "@agentxm/workspace-state";
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Plan-execution fixtures for tests and executable specifications.
|
|
3
3
|
*
|
|
4
4
|
* Production commands convert their parsed intent into a `PlanExecution` at
|
|
5
|
-
* the CLI boundary; these constants let kernel and handler tests
|
|
6
|
-
*
|
|
5
|
+
* the CLI boundary; these constants let kernel and handler tests resolve
|
|
6
|
+
* an execution candidate with an already-decoded approval decision. Production
|
|
7
7
|
* source never imports this module.
|
|
8
8
|
*
|
|
9
9
|
* @experimental This API is unstable and may change without notice.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Plan-execution fixtures for tests and executable specifications.
|
|
3
3
|
*
|
|
4
4
|
* Production commands convert their parsed intent into a `PlanExecution` at
|
|
5
|
-
* the CLI boundary; these constants let kernel and handler tests
|
|
6
|
-
*
|
|
5
|
+
* the CLI boundary; these constants let kernel and handler tests resolve
|
|
6
|
+
* an execution candidate with an already-decoded approval decision. Production
|
|
7
7
|
* source never imports this module.
|
|
8
8
|
*
|
|
9
9
|
* @experimental This API is unstable and may change without notice.
|
|
@@ -44,6 +44,27 @@ export interface ConfirmationRecovery {
|
|
|
44
44
|
* approval, never a preapproval flag the command does not accept.
|
|
45
45
|
*/
|
|
46
46
|
export type ConfirmableRiskApproval = "preapproved" | "prompt-if-interactive" | "interactive-only";
|
|
47
|
+
/**
|
|
48
|
+
* What an invoking surface parsed about the approval a request carries.
|
|
49
|
+
*
|
|
50
|
+
* `preapproved` is present only on the routes whose capabilities declare a
|
|
51
|
+
* preapprovable confirmation; every other route omits it. The absence of the
|
|
52
|
+
* field is therefore a fact about the route, not a missing answer, and it is
|
|
53
|
+
* what separates "the person declined to preapprove" from "this request could
|
|
54
|
+
* never have been preapproved".
|
|
55
|
+
*/
|
|
56
|
+
export interface RequestedApproval {
|
|
57
|
+
readonly preapproved?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The one derivation of an approval mode from what a request carries.
|
|
61
|
+
*
|
|
62
|
+
* It lives with plan execution rather than in a transport adapter because the
|
|
63
|
+
* distinction it draws — between a route that offers preapproval and one that
|
|
64
|
+
* does not — is what `resolveExecutionCandidate` uses to decide whether an
|
|
65
|
+
* unattended apply blocks naming interactive approval or names a flag.
|
|
66
|
+
*/
|
|
67
|
+
export declare const confirmableRiskApproval: (requested: RequestedApproval) => ConfirmableRiskApproval;
|
|
47
68
|
export type PlanExecutionRequest = {
|
|
48
69
|
readonly mode: "preview";
|
|
49
70
|
} | {
|
|
@@ -65,6 +86,33 @@ export type PlanExecution = {
|
|
|
65
86
|
readonly configuredAgentOperations?: ReadonlyArray<ConfiguredAgentOperation>;
|
|
66
87
|
};
|
|
67
88
|
export declare const previewPlanExecution: PlanExecution;
|
|
89
|
+
/**
|
|
90
|
+
* What a command parsed about its own invocation before any plan exists.
|
|
91
|
+
*
|
|
92
|
+
* `yes` is present only on the routes whose capabilities declare a
|
|
93
|
+
* preapprovable confirmation; every other route omits it, exactly as
|
|
94
|
+
* `RequestedApproval` describes.
|
|
95
|
+
*/
|
|
96
|
+
export interface RequestedPlanIntent {
|
|
97
|
+
readonly preview: boolean;
|
|
98
|
+
readonly yes?: boolean;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The one derivation of a plan execution from what a request carries.
|
|
102
|
+
*
|
|
103
|
+
* It lives here for the same reason `confirmableRiskApproval` does: the
|
|
104
|
+
* preview arm of `PlanExecution` has no field an approval could occupy, so
|
|
105
|
+
* advance approval accompanying a preview is dropped by this derivation
|
|
106
|
+
* rather than by a transport adapter's own convention. A preview therefore
|
|
107
|
+
* cannot spend an approval it never receives, and two requests that differ
|
|
108
|
+
* only in approval reach `resolveExecutionCandidate` as the same execution.
|
|
109
|
+
*/
|
|
110
|
+
export declare const requestedPlanExecution: (options: {
|
|
111
|
+
readonly intent: RequestedPlanIntent;
|
|
112
|
+
readonly recovery: ConfirmationRecovery;
|
|
113
|
+
readonly acceptedPolicies?: ReadonlySet<PlanPolicyId>;
|
|
114
|
+
readonly configuredAgentOperations?: ReadonlyArray<ConfiguredAgentOperation>;
|
|
115
|
+
}) => PlanExecution;
|
|
68
116
|
export declare const applyPlanExecution: (options: {
|
|
69
117
|
readonly approval: ConfirmableRiskApproval;
|
|
70
118
|
readonly acceptedPolicies?: ReadonlySet<PlanPolicyId>;
|