@agentxm/workspace-operations 0.28.7-preview.1788522540.63ba4b89a → 0.28.8

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,19 +10,19 @@
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";
18
18
  export { applyPlan, type ApplyPlanOptions, type OperationHandler } from "./plan/apply-plan.js";
19
- export { CurrentOperationUnit, OperationEventSchema, OperationLifecycle, OperationModeSchema, ProgressUnitSchema, SettledOutcomeSchema, awaitDrained, lifecycleEvents, makeOperationLifecycle, makeThrottledUnitProgress, 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";
19
+ 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
20
  export { ApprovalRecoveryMissing, CandidateFingerprintFailed, OPERATION_ERROR_CATEGORIES, OperationErrorCategorySchema, PlanInteractionFailed, STALE_CANDIDATE_DETAIL, StaleExecutionCandidate, StepFailure, type OperationErrorCategory, } from "./plan/errors.js";
21
21
  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";
@@ -20,7 +20,7 @@ export { OperationJournal, appendResolvedUnit, appendStartedUnit, recordJournalP
20
20
  // Apply plan + operation handler registry
21
21
  export { applyPlan } from "./plan/apply-plan.js";
22
22
  // Operation lifecycle events — the live contract every observer subscribes to.
23
- export { CurrentOperationUnit, OperationEventSchema, OperationLifecycle, OperationModeSchema, ProgressUnitSchema, SettledOutcomeSchema, awaitDrained, lifecycleEvents, makeOperationLifecycle, makeThrottledUnitProgress, observeUnit, publishOperationEvent, publishPhaseStarted, publishUnitProgress, publishWaitEnded, publishWaiting, settleOperation, subscribeLossless, } from "./plan/operation-events.js";
23
+ 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
24
  // Serialized error vocabulary and the plan-family tagged errors.
25
25
  export { ApprovalRecoveryMissing, CandidateFingerprintFailed, OPERATION_ERROR_CATEGORIES, OperationErrorCategorySchema, PlanInteractionFailed, STALE_CANDIDATE_DETAIL, StaleExecutionCandidate, StepFailure, } from "./plan/errors.js";
26
26
  // Interactive preview/apply orchestration over the workspace read model.
@@ -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
@@ -233,21 +233,37 @@ export declare const makeThrottledUnitProgress: (options: {
233
233
  readonly unit: ProgressUnit;
234
234
  readonly intervalMs?: number | undefined;
235
235
  }) => Effect.Effect<(done: number, total?: number | undefined) => Effect.Effect<void>>;
236
- export interface ObservedUnit {
236
+ export interface ObservedUnit<A = never> {
237
237
  readonly id: string;
238
238
  readonly label: string;
239
239
  /** Planned position; assigned by the service when the caller has none. */
240
240
  readonly index?: number | undefined;
241
241
  readonly total?: number | undefined;
242
242
  readonly parentUnitId?: string | undefined;
243
+ /**
244
+ * The label the unit settles with, read from the value it produced. A unit
245
+ * that exists to resolve a fact reports that fact here, so its settlement
246
+ * names the answer instead of repeating the question. Only a successful
247
+ * exit has a value to read; a failed or interrupted unit settles with the
248
+ * label it started with.
249
+ */
250
+ readonly resolvedLabel?: (value: A) => string;
243
251
  }
244
252
  /**
245
253
  * Run one unit of work under the lifecycle: `UnitStarted` before, then
246
254
  * `UnitResolved` with the state the exit proves (`committed`, `failed`, or
247
- * `interrupted`). The unit identity is provided to the run so nested producers
248
- * can publish progress. No-op wrapper without a broadcast.
255
+ * `interrupted`) and the label the unit settles with. The unit identity is
256
+ * provided to the run so nested producers can publish progress. No-op wrapper
257
+ * without a broadcast.
258
+ */
259
+ export declare const observeUnit: <A, E, R>(unit: ObservedUnit<A>, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
260
+ /**
261
+ * Run a unit as a child of the unit currently in progress, so work a step
262
+ * delegates nests under the step that delegated it. Identical to
263
+ * `observeUnit` when no unit is in progress, and when the caller states its
264
+ * own parent that statement wins.
249
265
  */
250
- export declare const observeUnit: <A, E, R>(unit: ObservedUnit, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
266
+ export declare const observeChildUnit: <A, E, R>(unit: ObservedUnit<A>, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
251
267
  /** Publish the terminal event once. No-op without a broadcast. */
252
268
  export declare const settleOperation: (outcome: SettledOutcome) => Effect.Effect<void>;
253
269
  /**
@@ -234,8 +234,9 @@ const unitStateForExit = (exit) => Exit.isSuccess(exit)
234
234
  /**
235
235
  * Run one unit of work under the lifecycle: `UnitStarted` before, then
236
236
  * `UnitResolved` with the state the exit proves (`committed`, `failed`, or
237
- * `interrupted`). The unit identity is provided to the run so nested producers
238
- * can publish progress. No-op wrapper without a broadcast.
237
+ * `interrupted`) and the label the unit settles with. The unit identity is
238
+ * provided to the run so nested producers can publish progress. No-op wrapper
239
+ * without a broadcast.
239
240
  */
240
241
  export const observeUnit = (unit, effect) => Effect.flatMap(Effect.serviceOption(OperationLifecycle), (service) => {
241
242
  if (Option.isNone(service))
@@ -259,13 +260,24 @@ export const observeUnit = (unit, effect) => Effect.flatMap(Effect.serviceOption
259
260
  seq,
260
261
  atMs,
261
262
  unitId: unit.id,
262
- label: unit.label,
263
+ label: Exit.isSuccess(exit) && unit.resolvedLabel !== undefined
264
+ ? unit.resolvedLabel(exit.value)
265
+ : unit.label,
263
266
  state: unitStateForExit(exit),
264
267
  index,
265
268
  ...total,
266
269
  }))));
267
270
  });
268
271
  });
272
+ /**
273
+ * Run a unit as a child of the unit currently in progress, so work a step
274
+ * delegates nests under the step that delegated it. Identical to
275
+ * `observeUnit` when no unit is in progress, and when the caller states its
276
+ * own parent that statement wins.
277
+ */
278
+ export const observeChildUnit = (unit, effect) => Effect.flatMap(Effect.serviceOption(CurrentOperationUnit), (parent) => observeUnit(unit.parentUnitId !== undefined || Option.isNone(parent)
279
+ ? unit
280
+ : { ...unit, parentUnitId: parent.value.unitId }, effect));
269
281
  /** Publish the terminal event once. No-op without a broadcast. */
270
282
  export const settleOperation = (outcome) => withLifecycle((service) => service.settle(outcome), () => Effect.void);
271
283
  /**
@@ -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-preview.1788522540.63ba4b89a",
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.8",
8
+ "@agentxm/registry-protocol": "^0.28.8",
9
+ "@agentxm/workspace-state": "^0.28.8",
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/extension-model": "^0.28.7-preview.1788522540.63ba4b89a",
52
- "@agentxm/registry-protocol": "^0.28.7-preview.1788522540.63ba4b89a",
53
- "@agentxm/workspace-state": "^0.28.7-preview.1788522540.63ba4b89a"
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.8"
64
+ }