@agentxm/workspace-operations 0.28.7 → 0.28.9

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.
@@ -10,8 +10,8 @@
10
10
  * @experimental This API is unstable and may change without notice.
11
11
  * @packageDocumentation
12
12
  */
13
- export { BlockingClassSchema, defaultOperationPresentation, operationPresentation, presentationOf, ArtifactMechanismSchema, OperationPreconditionSchema, PlanPolicyIdSchema, PlanPolicyIds, PlanRiskConditionSchema, } from "./plan/plan.js";
14
- export type { ArtifactMechanism, BlockingClass, CompletedJobStep, ErrorJobStep, ExecutedJob, ExecutedPlan, Job, JobStepArtifact, JobStepArtifactSource, JobStepArtifactTarget, JobStepResult, Operation, OperationPrecondition, OperationPresentation, Plan, PlanPolicyId, PlanRiskCondition, PlannedJobStep, ReadyJobStep, RegistryLifecycleEvidence, UnitBlocking, WarnJobStep, } from "./plan/plan.js";
13
+ 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, RegistryBindingProposal, RegistryLifecycleEvidence, UnitBlocking, WarnJobStep, } from "./plan/plan.js";
15
15
  export { AtomicityClassSchema, OperationOutcomeSchema, OperationPhaseSchema, UnitDispositionSchema, UnitStateSchema, countUnitStates, declaredAtomicity, deriveOperationOutcome, executedUnits, makeOperationResolution, plannedUnits, unitIdOf, unitsByStableIdentity, } from "./plan/operation-resolution.js";
16
16
  export type { AtomicityClass, MakeOperationResolutionArgs, OperationAtomicity, OperationBlock, OperationFootprintEntry, OperationInterruption, OperationOutcome, OperationPhase, OperationRecovery, OperationResolution, ResolvedUnit, UnitDisposition, UnitState, UnitStateCounts, } from "./plan/operation-resolution.js";
17
17
  export { OperationJournal, appendResolvedUnit, appendStartedUnit, recordJournalPhase, getOperationJournal, makeOperationJournal, recordOperationJournal, updateOperationJournal, type OperationJournalService, type OperationJournalState, } from "./plan/operation-journal.js";
@@ -22,7 +22,7 @@ export { previewOrApplyPlan } from "./plan/resolve-plan.js";
22
22
  export { ResolvePlanInteraction, type ApplyConfirmation, type ResolvePlanInteractionService, } from "./plan/resolve-plan-interaction.js";
23
23
  export { InterruptionSignalSource, type InterruptionSignalSourceService, } from "./plan/interruption-signal.js";
24
24
  export { isExecutionCandidateFresh, makeExecutionCandidate, type ExecutionCandidate, } from "./plan/execution-candidate.js";
25
- export { applyPlanExecution, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, preapprovedPlanExecution, promptablePlanExecution, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, unclassifiedRecoveryValue, type ConfirmationRecovery, type ConfirmationRecoveryArgument, type ConfirmationRecoveryValue, type ConfiguredAgentOperation, type PlanExecution, type PlanExecutionRequest, } from "./plan/plan-execution.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";
26
26
  export * from "./plan/job-step-message.js";
27
27
  export { scanPlanReadiness, type PlanReadinessReport } from "./operations/scan-plan-readiness.js";
28
28
  export { augmentPlanWithReconciliation, type AugmentedPlanResult, type DegradedLockfileState, } from "./operations/augment-plan.js";
package/dist/src/index.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * @packageDocumentation
12
12
  */
13
13
  // Plan types
14
- export { BlockingClassSchema, defaultOperationPresentation, operationPresentation, presentationOf, ArtifactMechanismSchema, OperationPreconditionSchema, PlanPolicyIdSchema, PlanPolicyIds, PlanRiskConditionSchema, } from "./plan/plan.js";
14
+ export { BlockingClassSchema, defaultOperationPresentation, operationPresentation, presentationOf, ArtifactMechanismSchema, ConfirmableConsentSchema, OperationPreconditionSchema, PlanPolicyIdSchema, PlanPolicyIds, PlanRiskConditionSchema, } from "./plan/plan.js";
15
15
  // Operation resolution — the single value every plan-family command
16
16
  // terminates with, with its pure outcome derivations.
17
17
  export { AtomicityClassSchema, OperationOutcomeSchema, OperationPhaseSchema, UnitDispositionSchema, UnitStateSchema, countUnitStates, declaredAtomicity, deriveOperationOutcome, executedUnits, makeOperationResolution, plannedUnits, unitIdOf, unitsByStableIdentity, } from "./plan/operation-resolution.js";
@@ -30,7 +30,7 @@ export { previewOrApplyPlan } from "./plan/resolve-plan.js";
30
30
  export { ResolvePlanInteraction, } from "./plan/resolve-plan-interaction.js";
31
31
  export { InterruptionSignalSource, } from "./plan/interruption-signal.js";
32
32
  export { isExecutionCandidateFresh, makeExecutionCandidate, } from "./plan/execution-candidate.js";
33
- export { applyPlanExecution, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, preapprovedPlanExecution, promptablePlanExecution, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, unclassifiedRecoveryValue, } from "./plan/plan-execution.js";
33
+ export { applyPlanExecution, confirmationRecoverySuggestions, namedPolicyRecoverySuggestions, credentialFreeLocatorRecoveryValue, previewPlanExecution, protectedRecoveryValue, publicRecoveryValue, recoveryOption, recoveryPositional, recoverySwitch, renderConfirmationRecoveryCommand, unclassifiedRecoveryValue, } from "./plan/plan-execution.js";
34
34
  // Job step messaging
35
35
  export * from "./plan/job-step-message.js";
36
36
  // Plan readiness and reconciliation gating
@@ -144,9 +144,6 @@ export const acquireWorkspaceTransitionLock = (args) => Effect.gen(function* ()
144
144
  const workspaceExisted = yield* fs
145
145
  .exists(workspaceDir)
146
146
  .pipe(Effect.mapError((cause) => new WorkspaceDirectoryError({ path: workspaceDir, step: "inspect", cause })));
147
- yield* fs
148
- .makeDirectory(scratchDir, { recursive: true })
149
- .pipe(Effect.mapError((cause) => new TransitionLockError({ path: scratchDir, step: "create-scratch", cause })));
150
147
  const removeEmptyScratch = fs.readDirectory(scratchDir).pipe(Effect.flatMap((entries) => entries.length === 0
151
148
  ? fs.remove(scratchDir, { recursive: true, force: false })
152
149
  : Effect.void), Effect.ignore);
@@ -159,6 +156,12 @@ export const acquireWorkspaceTransitionLock = (args) => Effect.gen(function* ()
159
156
  let waitedMillis = 0;
160
157
  let reportedWaiting = false;
161
158
  while (true) {
159
+ // The previous holder removes an empty scratch directory when it
160
+ // releases. Recreate it before every attempt so that a waiter can
161
+ // acquire after that cleanup instead of mistaking ENOENT for a hold.
162
+ yield* fs
163
+ .makeDirectory(scratchDir, { recursive: true })
164
+ .pipe(Effect.mapError((cause) => new TransitionLockError({ path: scratchDir, step: "create-scratch", cause })));
162
165
  // One attempt is atomic with respect to interruption: from the library
163
166
  // granting the hold through finalizer registration there is no
164
167
  // interruptible gap, so an interrupt requested mid-acquisition defers
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Plan-execution fixtures for tests and executable specifications.
3
+ *
4
+ * Production commands convert their parsed intent into a `PlanExecution` at
5
+ * the CLI boundary; these constants let kernel and handler tests drive
6
+ * `previewOrApplyPlan` with an already-decoded approval decision. Production
7
+ * source never imports this module.
8
+ *
9
+ * @experimental This API is unstable and may change without notice.
10
+ */
11
+ import type { PlanPolicyId } from "./plan.js";
12
+ import { type ConfirmationRecovery, type PlanExecution } from "./plan-execution.js";
13
+ /** An apply whose preapprovable confirmations are already approved. */
14
+ export declare const preapprovedPlanExecution: PlanExecution;
15
+ /** An apply from a route that offers preapproval but received none. */
16
+ export declare const promptablePlanExecution: (recovery: ConfirmationRecovery, acceptedPolicies?: ReadonlySet<PlanPolicyId>) => PlanExecution;
17
+ /** An apply from a route with no preapprovable confirmation. */
18
+ export declare const interactiveOnlyPlanExecution: (recovery: ConfirmationRecovery, acceptedPolicies?: ReadonlySet<PlanPolicyId>) => PlanExecution;
19
+ //# sourceMappingURL=plan-execution-fixtures.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Plan-execution fixtures for tests and executable specifications.
3
+ *
4
+ * Production commands convert their parsed intent into a `PlanExecution` at
5
+ * the CLI boundary; these constants let kernel and handler tests drive
6
+ * `previewOrApplyPlan` with an already-decoded approval decision. Production
7
+ * source never imports this module.
8
+ *
9
+ * @experimental This API is unstable and may change without notice.
10
+ */
11
+ import { applyPlanExecution, } from "./plan-execution.js";
12
+ const emptyRecovery = { command: [], arguments: [] };
13
+ /** An apply whose preapprovable confirmations are already approved. */
14
+ export const preapprovedPlanExecution = applyPlanExecution({
15
+ approval: "preapproved",
16
+ recovery: emptyRecovery,
17
+ });
18
+ /** An apply from a route that offers preapproval but received none. */
19
+ export const promptablePlanExecution = (recovery, acceptedPolicies) => applyPlanExecution({
20
+ approval: "prompt-if-interactive",
21
+ ...(acceptedPolicies === undefined ? {} : { acceptedPolicies }),
22
+ recovery,
23
+ });
24
+ /** An apply from a route with no preapprovable confirmation. */
25
+ export const interactiveOnlyPlanExecution = (recovery, acceptedPolicies) => applyPlanExecution({
26
+ approval: "interactive-only",
27
+ ...(acceptedPolicies === undefined ? {} : { acceptedPolicies }),
28
+ recovery,
29
+ });
30
+ //# sourceMappingURL=plan-execution-fixtures.js.map
@@ -30,11 +30,25 @@ export interface ConfirmationRecovery {
30
30
  readonly command: ReadonlyArray<string>;
31
31
  readonly arguments: ReadonlyArray<ConfirmationRecoveryArgument>;
32
32
  }
33
+ /**
34
+ * How an apply may satisfy a confirmable risk condition.
35
+ *
36
+ * - `preapproved`: the person supplied explicit preapproval for this
37
+ * invocation on a command that offers a preapprovable confirmation.
38
+ * - `prompt-if-interactive`: the command offers a preapprovable confirmation
39
+ * and none was given; a prompt opens when one can, and recovery may
40
+ * suggest preapproval.
41
+ * - `interactive-only`: the command has no preapprovable confirmation. A
42
+ * confirmable condition it meets is unexpected for unattended use: it
43
+ * prompts when a prompt can open and otherwise blocks naming interactive
44
+ * approval, never a preapproval flag the command does not accept.
45
+ */
46
+ export type ConfirmableRiskApproval = "preapproved" | "prompt-if-interactive" | "interactive-only";
33
47
  export type PlanExecutionRequest = {
34
48
  readonly mode: "preview";
35
49
  } | {
36
50
  readonly mode: "apply";
37
- readonly confirmableRiskApproval: "prompt-if-interactive" | "preapproved";
51
+ readonly confirmableRiskApproval: ConfirmableRiskApproval;
38
52
  readonly acceptedPolicies: ReadonlySet<PlanPolicyId>;
39
53
  };
40
54
  /** Invocation-scoped policy input plus safe replay metadata for approval recovery. */
@@ -52,13 +66,11 @@ export type PlanExecution = {
52
66
  };
53
67
  export declare const previewPlanExecution: PlanExecution;
54
68
  export declare const applyPlanExecution: (options: {
55
- readonly approval: "prompt-if-interactive" | "preapproved";
69
+ readonly approval: ConfirmableRiskApproval;
56
70
  readonly acceptedPolicies?: ReadonlySet<PlanPolicyId>;
57
71
  readonly recovery: ConfirmationRecovery;
58
72
  readonly configuredAgentOperations?: ReadonlyArray<ConfiguredAgentOperation>;
59
73
  }) => PlanExecution;
60
- export declare const preapprovedPlanExecution: PlanExecution;
61
- export declare const promptablePlanExecution: (recovery: ConfirmationRecovery, acceptedPolicies?: ReadonlySet<PlanPolicyId>) => PlanExecution;
62
74
  export declare const publicRecoveryValue: (value: string) => ConfirmationRecoveryValue;
63
75
  export declare const protectedRecoveryValue: () => ConfirmationRecoveryValue;
64
76
  export declare const unclassifiedRecoveryValue: () => ConfirmationRecoveryValue;
@@ -66,10 +78,25 @@ export declare const credentialFreeLocatorRecoveryValue: (value: string) => Conf
66
78
  export declare const recoveryOption: (flag: string, value: ConfirmationRecoveryValue) => ConfirmationRecoveryArgument;
67
79
  export declare const recoveryPositional: (value: ConfirmationRecoveryValue) => ConfirmationRecoveryArgument;
68
80
  export declare const recoverySwitch: (flag: string, enabled: boolean) => ConfirmationRecoveryArgument;
69
- export declare const renderConfirmationRecoveryCommand: (recovery: ConfirmationRecovery, options?: {
70
- readonly includeYes?: boolean;
81
+ /**
82
+ * How a rendered recovery command obtains approval.
83
+ *
84
+ * - `preapprovable`: the route accepts preapproval, so the replay appends it.
85
+ * - `interactive`: approval can only be given at a prompt, so the replay drops
86
+ * the switches that prohibit one and appends nothing.
87
+ * - `none`: the replay carries neither; used when a named policy override is
88
+ * the missing input.
89
+ */
90
+ export type RecoveryApproval = "preapprovable" | "interactive" | "none";
91
+ export declare const renderConfirmationRecoveryCommand: (recovery: ConfirmationRecovery, options: {
92
+ readonly approval: RecoveryApproval;
71
93
  readonly additionalSwitches?: ReadonlyArray<string>;
72
94
  }) => string | undefined;
73
95
  export declare const namedPolicyRecoverySuggestions: (recovery: ConfirmationRecovery, requiredFlags: ReadonlyArray<string>) => ReadonlyArray<SuggestedAction>;
74
- export declare const confirmationRecoverySuggestions: (recovery: ConfirmationRecovery) => ReadonlyArray<SuggestedAction>;
96
+ /**
97
+ * The recovery a blocked confirmation offers. A preapprovable confirmation
98
+ * replays the invocation with explicit preapproval; an interactive-only one
99
+ * names the need for a prompt and the mode changes that let one open.
100
+ */
101
+ export declare const confirmationRecoverySuggestions: (recovery: ConfirmationRecovery, approval: Exclude<RecoveryApproval, "none">) => ReadonlyArray<SuggestedAction>;
75
102
  //# sourceMappingURL=plan-execution.d.ts.map
@@ -10,16 +10,6 @@ export const applyPlanExecution = (options) => ({
10
10
  ? {}
11
11
  : { configuredAgentOperations: options.configuredAgentOperations }),
12
12
  });
13
- const emptyRecovery = { command: [], arguments: [] };
14
- export const preapprovedPlanExecution = applyPlanExecution({
15
- approval: "preapproved",
16
- recovery: emptyRecovery,
17
- });
18
- export const promptablePlanExecution = (recovery, acceptedPolicies) => applyPlanExecution({
19
- approval: "prompt-if-interactive",
20
- ...(acceptedPolicies === undefined ? {} : { acceptedPolicies }),
21
- recovery,
22
- });
23
13
  export const publicRecoveryValue = (value) => ({
24
14
  _tag: "Public",
25
15
  value,
@@ -52,17 +42,20 @@ const quoteShellToken = (value) => value.length > 0 && safeShellToken.test(value
52
42
  const isReplayable = (argument) => argument._tag === "Switch" ||
53
43
  (argument.value._tag === "Public" && !/[()]/.test(argument.value.value));
54
44
  const renderValue = (value) => value._tag === "Public" && !/[()]/.test(value.value) ? quoteShellToken(value.value) : undefined;
55
- export const renderConfirmationRecoveryCommand = (recovery, options = {}) => {
45
+ const PREAPPROVAL_FLAG = "--yes";
46
+ const PROMPT_PROHIBITING_FLAGS = new Set(["--json", "--non-interactive"]);
47
+ const NON_REPLAYED_FLAGS = new Set(["--preview", PREAPPROVAL_FLAG]);
48
+ export const renderConfirmationRecoveryCommand = (recovery, options) => {
56
49
  if (recovery.command.length === 0 || !recovery.arguments.every(isReplayable))
57
50
  return undefined;
51
+ const dropped = (flag) => NON_REPLAYED_FLAGS.has(flag) ||
52
+ (options.approval === "interactive" && PROMPT_PROHIBITING_FLAGS.has(flag));
58
53
  const optionTokens = recovery.arguments.flatMap((argument) => {
59
54
  switch (argument._tag) {
60
55
  case "Switch":
61
- return argument.enabled && argument.flag !== "--preview" && argument.flag !== "--yes"
62
- ? [argument.flag]
63
- : [];
56
+ return argument.enabled && !dropped(argument.flag) ? [argument.flag] : [];
64
57
  case "Option": {
65
- if (argument.flag === "--preview" || argument.flag === "--yes")
58
+ if (dropped(argument.flag))
66
59
  return [];
67
60
  const value = renderValue(argument.value);
68
61
  return value === undefined ? [] : [argument.flag, value];
@@ -84,7 +77,7 @@ export const renderConfirmationRecoveryCommand = (recovery, options = {}) => {
84
77
  "axm",
85
78
  ...recovery.command,
86
79
  ...optionTokens,
87
- ...(options.includeYes === false ? [] : ["--yes"]),
80
+ ...(options.approval === "preapprovable" ? [PREAPPROVAL_FLAG] : []),
88
81
  ...(options.additionalSwitches ?? []),
89
82
  ...(needsOptionTerminator ? ["--"] : []),
90
83
  ...positionalValues,
@@ -93,7 +86,7 @@ export const renderConfirmationRecoveryCommand = (recovery, options = {}) => {
93
86
  };
94
87
  export const namedPolicyRecoverySuggestions = (recovery, requiredFlags) => {
95
88
  const command = renderConfirmationRecoveryCommand(recovery, {
96
- includeYes: false,
89
+ approval: "none",
97
90
  additionalSwitches: requiredFlags,
98
91
  });
99
92
  return command === undefined
@@ -104,8 +97,19 @@ export const namedPolicyRecoverySuggestions = (recovery, requiredFlags) => {
104
97
  ]
105
98
  : [{ description: "Retry with the required policy override", cmd: command }];
106
99
  };
107
- export const confirmationRecoverySuggestions = (recovery) => {
108
- const command = renderConfirmationRecoveryCommand(recovery);
100
+ const INTERACTIVE_APPROVAL_DESCRIPTION = "Approve interactively: rerun in a terminal without --json or --non-interactive and confirm the plan";
101
+ /**
102
+ * The recovery a blocked confirmation offers. A preapprovable confirmation
103
+ * replays the invocation with explicit preapproval; an interactive-only one
104
+ * names the need for a prompt and the mode changes that let one open.
105
+ */
106
+ export const confirmationRecoverySuggestions = (recovery, approval) => {
107
+ const command = renderConfirmationRecoveryCommand(recovery, { approval });
108
+ if (approval === "interactive") {
109
+ return command === undefined
110
+ ? [{ description: INTERACTIVE_APPROVAL_DESCRIPTION }]
111
+ : [{ description: INTERACTIVE_APPROVAL_DESCRIPTION, cmd: command }];
112
+ }
109
113
  return command === undefined
110
114
  ? [
111
115
  {
@@ -40,10 +40,19 @@ export interface UnitBlocking {
40
40
  /** Machine-readable reference to the blocking unit or condition. */
41
41
  readonly reference?: string;
42
42
  }
43
+ /**
44
+ * How a confirmable condition may be approved. `preapprovable` (the default
45
+ * when absent) accepts explicit preapproval from a command that offers it;
46
+ * `interactive-only` accepts only an affirmative answer at a prompt, so no
47
+ * flag and no unattended mode can satisfy it.
48
+ */
49
+ export declare const ConfirmableConsentSchema: Schema.Literals<readonly ["preapprovable", "interactive-only"]>;
50
+ export type ConfirmableConsent = typeof ConfirmableConsentSchema.Type;
43
51
  export declare const PlanRiskConditionSchema: Schema.Union<readonly [Schema.Struct<{
44
52
  readonly level: Schema.Literal<"confirmable">;
45
53
  readonly id: Schema.String;
46
54
  readonly detail: Schema.String;
55
+ readonly consent: Schema.optional<Schema.Literals<readonly ["preapprovable", "interactive-only"]>>;
47
56
  }>, Schema.Struct<{
48
57
  readonly level: Schema.Literal<"override-required">;
49
58
  readonly id: Schema.String;
@@ -102,6 +111,21 @@ export interface JobStepArtifactSource {
102
111
  readonly directory?: string;
103
112
  readonly gitTreeHash?: string;
104
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
+ }
105
129
  export interface RegistryLifecycleEvidence {
106
130
  readonly deprecation: DeprecationView;
107
131
  }
@@ -132,6 +156,7 @@ export interface ReadyJobStep<Requirements = never, Output = never> {
132
156
  readonly artifact?: JobStepArtifact;
133
157
  readonly agentOutcomes?: ReadonlyArray<ConfiguredAgentOutcome>;
134
158
  readonly registryLifecycle?: RegistryLifecycleEvidence;
159
+ readonly registryBinding?: RegistryBindingProposal;
135
160
  readonly run: Effect.Effect<JobStepResult<Output>, StepFailure, Requirements>;
136
161
  }
137
162
  export interface WarnJobStep<Requirements = never, Output = never> {
@@ -143,6 +168,7 @@ export interface WarnJobStep<Requirements = never, Output = never> {
143
168
  readonly artifact?: JobStepArtifact;
144
169
  readonly agentOutcomes?: ReadonlyArray<ConfiguredAgentOutcome>;
145
170
  readonly registryLifecycle?: RegistryLifecycleEvidence;
171
+ readonly registryBinding?: RegistryBindingProposal;
146
172
  readonly run: Effect.Effect<JobStepResult<Output>, StepFailure, Requirements>;
147
173
  }
148
174
  export interface ErrorJobStep {
@@ -154,6 +180,7 @@ export interface ErrorJobStep {
154
180
  readonly artifact?: JobStepArtifact;
155
181
  readonly agentOutcomes?: ReadonlyArray<ConfiguredAgentOutcome>;
156
182
  readonly registryLifecycle?: RegistryLifecycleEvidence;
183
+ readonly registryBinding?: RegistryBindingProposal;
157
184
  /** Semantic blockers already represented in Plan.riskConditions. */
158
185
  readonly blockingConditionIds?: ReadonlyArray<string>;
159
186
  }
@@ -40,11 +40,19 @@ export const BlockingClassSchema = Schema.Literals([
40
40
  title: "Blocking Class",
41
41
  description: "Bounded reason class for work that did not proceed.",
42
42
  });
43
+ /**
44
+ * How a confirmable condition may be approved. `preapprovable` (the default
45
+ * when absent) accepts explicit preapproval from a command that offers it;
46
+ * `interactive-only` accepts only an affirmative answer at a prompt, so no
47
+ * flag and no unattended mode can satisfy it.
48
+ */
49
+ export const ConfirmableConsentSchema = Schema.Literals(["preapprovable", "interactive-only"]);
43
50
  export const PlanRiskConditionSchema = Schema.Union([
44
51
  Schema.Struct({
45
52
  level: Schema.Literal("confirmable"),
46
53
  id: Schema.String,
47
54
  detail: Schema.String,
55
+ consent: Schema.optional(ConfirmableConsentSchema),
48
56
  }),
49
57
  Schema.Struct({
50
58
  level: Schema.Literal("override-required"),
@@ -281,31 +281,42 @@ export const previewOrApplyPlan = Effect.fn("previewOrApplyPlan")(function* (pla
281
281
  suggestions: escapes,
282
282
  });
283
283
  }
284
+ // Step 5b: Confirmation. A condition that consents only at a prompt — a
285
+ // publisher change, or any confirmable condition met by a command with no
286
+ // preapprovable confirmation — is never satisfied by preapproval. A
287
+ // preapprovable condition is satisfied by explicit preapproval; otherwise
288
+ // it is asked when a prompt can open and blocks when one cannot, naming
289
+ // the recovery its route actually supports.
284
290
  const hasSteps = candidatePlan.jobs.some((job) => job.steps.length > 0);
285
- if (hasSteps &&
286
- hasConfirmableRisk &&
287
- applyExecution.request.confirmableRiskApproval === "prompt-if-interactive") {
288
- if (!(yield* interaction.isConfirmationAvailable)) {
289
- const confirmable = riskConditions.find((condition) => condition.level === "confirmable");
290
- const escapes = confirmationRecoverySuggestions(applyExecution.approvalRecovery);
291
- return makeOperationResolution({
292
- ...resolutionBase,
293
- atomicity: { declared: atomicity, applied: "closure-atomic" },
294
- units: plannedUnits(candidatePlan.jobs),
295
- blocking: {
296
- class: "approval-required",
297
- subject: confirmable?.id ?? candidatePlan.name,
298
- phase: "confirmation",
299
- detail: confirmable?.detail ?? "This plan requires confirmation before it can apply.",
300
- ...(escapes[0] === undefined ? {} : { escape: escapes[0] }),
301
- },
302
- suggestions: escapes,
303
- });
304
- }
305
- yield* enterPhase("confirmation");
306
- const confirmation = yield* interaction.confirmApplyChanges(applyExecution.approvalRecovery);
307
- if (confirmation !== "approved") {
308
- return notExecuted({ declined: true });
291
+ if (hasSteps && hasConfirmableRisk) {
292
+ const approval = applyExecution.request.confirmableRiskApproval;
293
+ const interactiveOnly = riskConditions.find((condition) => condition.level === "confirmable" && condition.consent === "interactive-only");
294
+ const requiresPrompt = interactiveOnly !== undefined || approval === "interactive-only";
295
+ if (requiresPrompt || approval === "prompt-if-interactive") {
296
+ if (!(yield* interaction.isConfirmationAvailable)) {
297
+ const confirmable = interactiveOnly ?? riskConditions.find((condition) => condition.level === "confirmable");
298
+ const escapes = confirmationRecoverySuggestions(applyExecution.approvalRecovery, requiresPrompt ? "interactive" : "preapprovable");
299
+ return makeOperationResolution({
300
+ ...resolutionBase,
301
+ atomicity: { declared: atomicity, applied: "closure-atomic" },
302
+ units: plannedUnits(candidatePlan.jobs),
303
+ blocking: {
304
+ class: "approval-required",
305
+ subject: confirmable?.id ?? candidatePlan.name,
306
+ phase: "confirmation",
307
+ detail: requiresPrompt
308
+ ? `${confirmable?.detail ?? "This plan requires confirmation before it can apply."} Interactive approval is required; preapproval does not apply.`
309
+ : (confirmable?.detail ?? "This plan requires confirmation before it can apply."),
310
+ ...(escapes[0] === undefined ? {} : { escape: escapes[0] }),
311
+ },
312
+ suggestions: escapes,
313
+ });
314
+ }
315
+ yield* enterPhase("confirmation");
316
+ const confirmation = yield* interaction.confirmApplyChanges(applyExecution.approvalRecovery);
317
+ if (confirmation !== "approved") {
318
+ return notExecuted({ declined: true });
319
+ }
309
320
  }
310
321
  }
311
322
  // Step 6: Acquire the workspace transition — planning, network acquisition,
@@ -8,4 +8,5 @@
8
8
  * @packageDocumentation
9
9
  */
10
10
  export { ResolvePlanInteractionTest, type ResolvePlanInteractionTestState, } from "./plan/resolve-plan-interaction.js";
11
+ export { interactiveOnlyPlanExecution, preapprovedPlanExecution, promptablePlanExecution, } from "./plan/plan-execution-fixtures.js";
11
12
  //# sourceMappingURL=testing.d.ts.map
@@ -8,4 +8,5 @@
8
8
  * @packageDocumentation
9
9
  */
10
10
  export { ResolvePlanInteractionTest, } from "./plan/resolve-plan-interaction.js";
11
+ export { interactiveOnlyPlanExecution, preapprovedPlanExecution, promptablePlanExecution, } from "./plan/plan-execution-fixtures.js";
11
12
  //# sourceMappingURL=testing.js.map
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
1
  {
2
- "name": "@agentxm/workspace-operations",
3
- "version": "0.28.7",
4
- "description": "AXM workspace operations kernel: plans, execution candidates, operation resolutions, and workspace transactions for the axm CLI. Unstable and unsupported — use the axm.sh CLI.",
5
- "type": "module",
6
- "license": "FSL-1.1-MIT",
7
- "homepage": "https://axm.sh",
2
+ "author": "AgentXM <hello@agentxm.ai> (https://agentxm.ai)",
8
3
  "bugs": {
9
4
  "url": "https://github.com/agentxm/axm/issues"
10
5
  },
11
- "author": "AgentXM <hello@agentxm.ai> (https://agentxm.ai)",
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/agentxm/axm.git",
15
- "directory": "packages/workspace-operations"
6
+ "dependencies": {
7
+ "@agentxm/extension-model": "^0.28.9",
8
+ "@agentxm/registry-protocol": "^0.28.9",
9
+ "@agentxm/workspace-state": "^0.28.9",
10
+ "effect": "4.0.0-rc.112",
11
+ "proper-lockfile": "^4.1.2"
12
+ },
13
+ "description": "AXM workspace operations kernel: plans, execution candidates, operation resolutions, and workspace transactions for the axm CLI. Unstable and unsupported — use the axm.sh CLI.",
14
+ "devDependencies": {
15
+ "@effect/platform-node": "4.0.0-rc.112",
16
+ "@effect/vitest": "4.0.0-rc.112",
17
+ "@types/proper-lockfile": "^4.1.4",
18
+ "@typescript/native": "npm:typescript@^7.0.2",
19
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
20
+ "vitest": "^4.1.10",
21
+ "yaml": "^2.9.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=22.19.0"
16
25
  },
17
- "sideEffects": false,
18
26
  "exports": {
19
27
  ".": {
20
- "types": "./dist/src/index.d.ts",
21
28
  "axm-source": "./src/index.ts",
22
- "default": "./dist/src/index.js"
29
+ "default": "./dist/src/index.js",
30
+ "types": "./dist/src/index.d.ts"
23
31
  },
24
32
  "./live": {
25
- "types": "./dist/src/live.d.ts",
26
33
  "axm-source": "./src/live.ts",
27
- "default": "./dist/src/live.js"
34
+ "default": "./dist/src/live.js",
35
+ "types": "./dist/src/live.d.ts"
28
36
  },
29
37
  "./testing": {
30
- "types": "./dist/src/testing.d.ts",
31
38
  "axm-source": "./src/testing.ts",
32
- "default": "./dist/src/testing.js"
39
+ "default": "./dist/src/testing.js",
40
+ "types": "./dist/src/testing.d.ts"
33
41
  }
34
42
  },
35
43
  "files": [
36
44
  "dist/src/",
37
45
  "!**/*.map"
38
46
  ],
39
- "publishConfig": {
40
- "access": "public"
41
- },
42
- "engines": {
43
- "node": ">=22.19.0"
44
- },
47
+ "homepage": "https://axm.sh",
48
+ "license": "FSL-1.1-MIT",
49
+ "name": "@agentxm/workspace-operations",
45
50
  "nx": {
46
51
  "includedScripts": []
47
52
  },
48
- "dependencies": {
49
- "effect": "4.0.0-rc.112",
50
- "proper-lockfile": "^4.1.2",
51
- "@agentxm/registry-protocol": "^0.28.7",
52
- "@agentxm/workspace-state": "^0.28.7",
53
- "@agentxm/extension-model": "^0.28.7"
53
+ "publishConfig": {
54
+ "access": "public"
54
55
  },
55
- "devDependencies": {
56
- "@effect/platform-node": "4.0.0-rc.112",
57
- "@effect/vitest": "4.0.0-rc.112",
58
- "@types/proper-lockfile": "^4.1.4",
59
- "@typescript/native": "npm:typescript@^7.0.2",
60
- "typescript": "npm:@typescript/typescript6@^6.0.2",
61
- "vitest": "^4.1.10",
62
- "yaml": "^2.9.0"
63
- }
64
- }
56
+ "repository": {
57
+ "directory": "packages/workspace-operations",
58
+ "type": "git",
59
+ "url": "https://github.com/agentxm/axm.git"
60
+ },
61
+ "sideEffects": false,
62
+ "type": "module",
63
+ "version": "0.28.9"
64
+ }