@amistio/cli 0.1.57 → 0.1.58

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/README.md CHANGED
@@ -95,7 +95,7 @@ Approved implementation work uses Git as the handoff boundary. During worktree p
95
95
 
96
96
  Failed or stale work can be requeued from the web Tasks panel. Requeue creates a new linked work attempt and preserves the original terminal attempt for audit history; Requeue safe sends one backend batch that recomputes safe candidates, reports already-active and skipped rows, and still uses linked attempts. Requeue is blocked while equivalent work is already active, when the paired runner does not advertise the needed work kind, or when the latest linked attempt repeats the same sanitized blocker fingerprint. Repeated runner setup, handoff, policy, verification, and worktree blockers require root-cause repair before another linked attempt. Completed implementation status is separate from proof: queue `implementationVerification` from Tasks when a plan needs source-aware evidence before cleanup or implementation status decisions.
97
97
 
98
- Patch-context failures are typed recovery states. If a local tool reports a stale `apply_patch` context miss, such as an expected-lines mismatch, the runner marks the work with patch-drift recovery metadata and safe next actions instead of uploading raw patch output or leaving a generic failed row. Retryable API failures during implementation finalization are staged in the user-level Amistio finalization outbox and replayed before the runner claims more work, so a transient `500` does not require rerunning the local AI tool. When completion queues a required implementation Test gate, the source-work finalization remains pending locally, compatible runners claim the gate before unrelated implementation work, and finalization replays after the gate passes.
98
+ Patch-context failures are typed recovery states. If a local tool reports a stale `apply_patch` context miss, such as an expected-lines mismatch, the runner marks the work with patch-drift recovery metadata and safe next actions instead of uploading raw patch output or leaving a generic failed row. Retryable API failures during implementation finalization are staged in the user-level Amistio finalization outbox and replayed before the runner claims more work, so a transient `500` does not require rerunning the local AI tool. When completion queues a required implementation Test gate, the source-work finalization remains pending locally, compatible runners claim the gate before unrelated implementation work, and finalization replays after the gate passes. If finalization replay finds that the stored work claim belongs to a forgotten, missing, stale, offline, or lease-expired runner, the CLI requests abandoned-claim recovery and retries only after the server safely adopts the claim.
99
99
 
100
100
  Runner setup and local-tool execution use bounded failure controls. During Git worktree preflight, `amistio run --watch` repairs safe stale Git registrations when the target worktree directory is missing and Git marks the registration prunable; dirty, present, or ambiguous worktrees are preserved. Other Git worktree preflight failures are retried by releasing the claim for another attempt, then fail the work item after `--max-preflight-attempts` attempts, defaulting to 3. Active local-tool runs renew the work lease, and `--tool-timeout-seconds` caps tool execution, defaulting to 1800 seconds.
101
101
 
@@ -103,7 +103,9 @@ The environment doctor blocks work before local AI/tool execution when `git`, `n
103
103
 
104
104
  Runner watch mode defaults to bounded parallel claim lanes, capped at 8. `--max-concurrent-work <count>` lowers or explicitly sets the advertised lane count; use `--max-concurrent-work 1` only when you intentionally want serial execution. The server enforces one active lease per runner lane, honors the advertised capacity, and keeps equivalent implementation scopes serialized through Git worktree locks; use separate lanes for independent work, not multiple attempts at the same ADR scope. Before local tool execution, the runner records a bounded user-level active claim with work item, lane, lease, implementation scope, and worktree key, then releases it on completion or failure; if another local lane already owns the same work or worktree, the runner skips execution and releases the server claim when possible.
105
105
 
106
- Watch mode prints a completed-work success once per work item, keeps fresh completion visible briefly, and returns old completed work to the ready state when no queued, running, blocked, failed, review, or runner-readiness action needs attention.
106
+ Current runners recover abandoned server claims without impersonating the old owner. If a work item is still claimed by a forgotten, missing, offline, stale, or lease-expired runner, a compatible runner asks the API to release or adopt that claim through an audited recovery path before normal claiming or pending finalization replay continues. Fresh live claims still reject non-owner renewals, releases, and finalization attempts. If a project has no compatible runner, work waits for setup until one is paired and heartbeating.
107
+
108
+ Watch mode prints a completed-work success once per work item, keeps fresh completion visible briefly, and returns old completed work to the ready state when no queued, running, blocked, failed, review, or runner-readiness action needs attention. Unchanged idle-ready status is printed once and then stays quiet until the next action changes.
107
109
 
108
110
  Known validation failures such as `unsafe_context_path` are printed with attention-needed next steps. For project-context refresh path-safety failures, deploy the latest web/API fix, update and restart the runner when applicable, retry the refresh, and capture only bounded non-secret output if it repeats.
109
111
 
package/dist/index.js CHANGED
@@ -602,6 +602,14 @@ var runnerResourceUsageSchema = z.object({
602
602
  systemLoadAverage15m: z.number().nonnegative().optional()
603
603
  });
604
604
  var runnerClaimLaneIdSchema = z.string().trim().min(1).max(80);
605
+ var abandonedRunnerClaimRecoveryReasonSchema = z.enum([
606
+ "claimOwnerRemoved",
607
+ "claimOwnerMissing",
608
+ "claimOwnerOffline",
609
+ "claimOwnerStale",
610
+ "leaseExpired",
611
+ "pendingFinalizationOwnershipLost"
612
+ ]);
605
613
  var workIsolationModeSchema = z.enum(["none", "primaryCheckout", "branch", "gitWorktree"]);
606
614
  var runnerExecutionEnvironmentProfileSchema = z.enum(["hostWorktree", "hostWorktreeWithSetup", "dockerWorkspace", "cloudSandbox"]);
607
615
  var runnerEnvironmentBlockerReasonSchema = z.enum([
@@ -901,6 +909,10 @@ var workItemSchema = baseItemSchema.extend({
901
909
  claimLeaseId: z.string().min(1).optional(),
902
910
  claimAttempt: z.number().int().nonnegative().optional(),
903
911
  leaseExpiresAt: isoDateTimeSchema.optional(),
912
+ previousClaimedByRunnerId: z.string().min(1).optional(),
913
+ claimRecoveryReason: abandonedRunnerClaimRecoveryReasonSchema.optional(),
914
+ claimRecoveredByRunnerId: z.string().min(1).optional(),
915
+ claimRecoveredAt: isoDateTimeSchema.optional(),
904
916
  controllingAdrId: z.string().min(1).optional(),
905
917
  implementationScopeId: z.string().min(1).optional(),
906
918
  executionBranch: z.string().min(1).optional(),
@@ -1903,11 +1915,20 @@ var implementationTestGateItemSchema = baseItemSchema.extend({
1903
1915
  source: implementationTestGateSourceSchema.default("system"),
1904
1916
  sourceWorkItemId: z.string().min(1),
1905
1917
  gateWorkItemId: z.string().min(1).optional(),
1918
+ generatedDraftId: z.string().min(1).optional(),
1919
+ changeId: z.string().min(1).optional(),
1906
1920
  repositoryLinkId: z.string().min(1).optional(),
1921
+ implementationScopeId: z.string().min(1).optional(),
1907
1922
  runnerId: z.string().min(1).optional(),
1908
1923
  planDocumentId: z.string().min(1).optional(),
1909
1924
  planDocumentRevision: z.number().int().nonnegative().optional(),
1910
1925
  testProfileId: z.string().min(1).optional(),
1926
+ autopilotAuthorization: autopilotAuthorizationSchema.optional(),
1927
+ autopilotAuthorizationId: z.string().min(1).optional(),
1928
+ autopilotCandidateId: z.string().min(1).optional(),
1929
+ autopilotCandidateType: autopilotCandidateTypeSchema.optional(),
1930
+ autopilotClassificationOutcome: autopilotClassificationOutcomeSchema.optional(),
1931
+ autopilotPolicyVersion: z.string().trim().min(1).max(80).optional(),
1911
1932
  outcome: implementationTestGateOutcomeSchema.optional(),
1912
1933
  result: implementationTestGateResultSchema.optional(),
1913
1934
  summary: z.string().trim().min(1).max(2e3).optional(),
@@ -2891,6 +2912,9 @@ var AmistioApiError = class extends Error {
2891
2912
  function isForgottenRunnerApiError(error) {
2892
2913
  return error instanceof AmistioApiError && error.status === 403 && error.detail.includes("This runner was forgotten");
2893
2914
  }
2915
+ function isWorkItemNotClaimedByRunnerApiError(error) {
2916
+ return error instanceof AmistioApiError && error.status === 403 && error.detail.includes("Work item is not claimed by this runner.");
2917
+ }
2894
2918
  function isRetryableApiError(error) {
2895
2919
  if (error instanceof AmistioApiError) {
2896
2920
  return error.status === 408 || error.status === 429 || error.status >= 500;
@@ -3147,6 +3171,21 @@ var ApiClient = class {
3147
3171
  }
3148
3172
  );
3149
3173
  }
3174
+ async recoverAbandonedWorkClaim(projectId, workItemId, input) {
3175
+ return this.request(
3176
+ `/projects/${projectId}/work-items/${workItemId}/claim-recovery`,
3177
+ z3.object({
3178
+ status: z3.enum(["recovered", "notNeeded"]),
3179
+ workItem: workItemSchema,
3180
+ message: z3.string(),
3181
+ recoveryReason: abandonedRunnerClaimRecoveryReasonSchema.optional()
3182
+ }),
3183
+ {
3184
+ method: "POST",
3185
+ body: JSON.stringify(input)
3186
+ }
3187
+ );
3188
+ }
3150
3189
  async submitBrainGenerationResult(projectId, workItemId, result) {
3151
3190
  return this.request(
3152
3191
  `/projects/${projectId}/work-items/${workItemId}/generation-result`,
@@ -5823,7 +5862,7 @@ function shouldPrintWatchState(action, previous, nowMs, reminderMs = watchStateR
5823
5862
  if (!previous || previous.key !== key) {
5824
5863
  return true;
5825
5864
  }
5826
- if (action.kind === "workCompleted") {
5865
+ if (action.kind === "workCompleted" || action.kind === "idle") {
5827
5866
  return false;
5828
5867
  }
5829
5868
  return nowMs - previous.printedAtMs >= reminderMs;
@@ -13521,6 +13560,16 @@ async function submitImplementationFinalizationEntry(apiClient, entry, options =
13521
13560
  console.log(message2);
13522
13561
  return { status: "deferred", message: message2, retryable: true };
13523
13562
  }
13563
+ if (isWorkItemNotClaimedByRunnerApiError(error)) {
13564
+ const recovered = await recoverPendingImplementationFinalizationClaim(apiClient, entry);
13565
+ if (recovered) {
13566
+ return submitImplementationFinalizationEntry(apiClient, entry, options);
13567
+ }
13568
+ await markImplementationFinalizationRetry(entry, "pending_finalization_ownership_lost");
13569
+ const message2 = `Pending implementation finalization ${entry.workItemId} is waiting for abandoned claim recovery before replay can continue.`;
13570
+ console.log(message2);
13571
+ return { status: "deferred", message: message2, retryable: true };
13572
+ }
13524
13573
  const detail = truncateLogExcerpt(errorMessage7(error));
13525
13574
  if (isRetryableApiError(error)) {
13526
13575
  const updated = await markImplementationFinalizationRetry(entry, detail);
@@ -13550,6 +13599,28 @@ async function submitImplementationFinalizationEntry(apiClient, entry, options =
13550
13599
  }
13551
13600
  return { status: "completed", workItem: response.workItem };
13552
13601
  }
13602
+ async function recoverPendingImplementationFinalizationClaim(apiClient, entry) {
13603
+ console.log(`Work claim owner is gone for ${entry.workItemId}; requesting abandoned-claim recovery before retrying finalization.`);
13604
+ try {
13605
+ const recovery = await apiClient.recoverAbandonedWorkClaim(entry.projectId, entry.workItemId, {
13606
+ runnerId: entry.runnerId,
13607
+ repositoryLinkId: entry.repositoryLinkId,
13608
+ reason: "pendingFinalizationOwnershipLost",
13609
+ mode: "adopt",
13610
+ ...entry.telemetry.claimLaneId ? { claimLaneId: entry.telemetry.claimLaneId } : {},
13611
+ ...entry.telemetry.machineId ? { machineId: entry.telemetry.machineId } : {},
13612
+ leaseSeconds: RUNNER_WORK_LEASE_SECONDS,
13613
+ supportedWorkKinds: runnerSupportedWorkKinds,
13614
+ supportsBranchIsolation: true,
13615
+ supportsGitWorktreeIsolation: true,
13616
+ maxConcurrentWork: 1
13617
+ });
13618
+ return recovery.workItem.claimedByRunnerId === entry.runnerId;
13619
+ } catch (error) {
13620
+ console.error(`Could not recover abandoned claim for ${entry.workItemId}: ${truncateLogExcerpt(errorMessage7(error))}`);
13621
+ return false;
13622
+ }
13623
+ }
13553
13624
  function runnerMilestoneStatusFromWorkStatus(status) {
13554
13625
  if (status === "completed" || status === "failed" || status === "running" || status === "blocked") return status;
13555
13626
  if (status === "approved" || status === "drafted") return "queued";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amistio/cli",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",