@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 +4 -2
- package/dist/index.js +72 -1
- package/package.json +1 -1
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
|
-
|
|
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";
|