harnery 0.45.0 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commander.js +0 -1
- package/dist/commands/files.d.ts +2 -2
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +4 -7
- package/dist/commands/servers.d.ts.map +1 -1
- package/dist/commands/servers.js +17 -11
- package/dist/commands/tunnel.d.ts +0 -15
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +153 -453
- package/dist/core/agents/live-authority-v3.d.ts +0 -1
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +0 -11
- package/dist/core/agents/live-lifecycle-v3.d.ts +2 -4
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/live-lifecycle-v3.js +7 -28
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +14 -25
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-writer.js +0 -5
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +0 -5
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +70 -180
- package/dist/core/artifacts/activity.d.ts +4 -1
- package/dist/core/artifacts/activity.d.ts.map +1 -1
- package/dist/core/artifacts/activity.js +8 -2
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
- package/dist/core/artifacts/delivery-card.js +1 -2
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +38 -12
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +0 -29
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +0 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +0 -7
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +64 -77
- package/dist/core/hooks/cli.js +78 -48
- package/dist/core/servers/index.d.ts +13 -10
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +97 -192
- package/dist/core/servers/tunnels.d.ts +3 -5
- package/dist/core/servers/tunnels.d.ts.map +1 -1
- package/dist/core/servers/tunnels.js +2 -45
- package/dist/core/storage/durable-history.js +56 -22
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +73 -17
- package/dist/core/work/state.js +7 -7
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +21 -65
- package/dist/core/workflow/live-session-v3.d.ts +0 -6
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +5 -126
- package/dist/core/workflow/proof.d.ts +4 -4
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +4 -12
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +0 -19
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +0 -13
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +0 -19
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +0 -14
- package/dist/core/workflow/types.d.ts +11 -54
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +0 -4
- package/dist/lib/tunnel/state.d.ts +6 -13
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +12 -58
- package/package.json +1 -6
- package/src/commander.ts +0 -10
- package/src/commands/files.ts +5 -13
- package/src/commands/servers.ts +23 -12
- package/src/commands/tunnel.ts +166 -533
- package/src/core/agents/live-authority-v3.ts +0 -17
- package/src/core/agents/live-lifecycle-v3.ts +14 -52
- package/src/core/agents/state/heartbeat-writer.ts +12 -29
- package/src/core/agents/state/live-coordination-writer.ts +0 -29
- package/src/core/agents/state/pidmap.ts +0 -13
- package/src/core/agents/state/stale-sweep.ts +82 -247
- package/src/core/artifacts/activity.ts +9 -2
- package/src/core/artifacts/delivery-card.ts +1 -7
- package/src/core/artifacts/index.ts +45 -12
- package/src/core/events/v3/authority-outbox.ts +0 -56
- package/src/core/events/v3/producers/coordination-recorder.ts +0 -15
- package/src/core/events/v3/producers/recorder.ts +68 -94
- package/src/core/hooks/cli.ts +85 -47
- package/src/core/servers/index.ts +106 -228
- package/src/core/servers/tunnels.ts +3 -55
- package/src/core/storage/durable-history.ts +52 -20
- package/src/core/storage/segments.ts +79 -17
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +21 -73
- package/src/core/workflow/live-session-v3.ts +5 -146
- package/src/core/workflow/proof.ts +5 -21
- package/src/core/workflow/spawn-claude.ts +0 -18
- package/src/core/workflow/spawn-codex.ts +0 -13
- package/src/core/workflow/spawn-cursor.ts +0 -18
- package/src/core/workflow/spawn-opencode.ts +0 -14
- package/src/core/workflow/types.ts +11 -41
- package/src/lib/identities/assume.ts +0 -4
- package/src/lib/tunnel/state.ts +15 -70
- package/dist/commands/turn-rollback.d.ts +0 -5
- package/dist/commands/turn-rollback.d.ts.map +0 -1
- package/dist/commands/turn-rollback.js +0 -76
- package/dist/core/agents/state/unchanged-file.d.ts +0 -44
- package/dist/core/agents/state/unchanged-file.d.ts.map +0 -1
- package/dist/core/agents/state/unchanged-file.js +0 -390
- package/dist/core/events/v3/generation-custody.d.ts +0 -5
- package/dist/core/events/v3/generation-custody.d.ts.map +0 -1
- package/dist/core/events/v3/generation-custody.js +0 -50
- package/dist/core/turn-rollback/content.d.ts +0 -53
- package/dist/core/turn-rollback/content.d.ts.map +0 -1
- package/dist/core/turn-rollback/content.js +0 -396
- package/dist/core/turn-rollback/index.d.ts +0 -72
- package/dist/core/turn-rollback/index.d.ts.map +0 -1
- package/dist/core/turn-rollback/index.js +0 -443
- package/dist/core/workflow/failure-evidence.d.ts +0 -16
- package/dist/core/workflow/failure-evidence.d.ts.map +0 -1
- package/dist/core/workflow/failure-evidence.js +0 -114
- package/src/commands/turn-rollback.ts +0 -111
- package/src/core/agents/state/unchanged-file.ts +0 -456
- package/src/core/events/v3/generation-custody.ts +0 -59
- package/src/core/turn-rollback/content.ts +0 -499
- package/src/core/turn-rollback/index.ts +0 -580
- package/src/core/workflow/failure-evidence.ts +0 -141
|
@@ -22,7 +22,6 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
|
|
|
22
22
|
import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
|
|
23
23
|
import { notFoundError } from "./adapters.ts";
|
|
24
24
|
import { buildChildEnv } from "./child-env.ts";
|
|
25
|
-
import { observeSpawnFailure } from "./failure-evidence.ts";
|
|
26
25
|
import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
|
|
27
26
|
import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
|
|
28
27
|
import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
|
|
@@ -89,19 +88,6 @@ export function buildOpenCodeInvocation(req: SpawnRequest): AdapterInvocation {
|
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
export function normalizeOpenCodeResult(raw: AdapterRawResult): SpawnResult {
|
|
92
|
-
const result = normalizeOpenCodeTransportResult(raw);
|
|
93
|
-
if (result.ok) return result;
|
|
94
|
-
const parsed = parseOpenCodeStream(raw.stdout);
|
|
95
|
-
const text = parsed.text || raw.stdout.trim();
|
|
96
|
-
return {
|
|
97
|
-
...result,
|
|
98
|
-
text,
|
|
99
|
-
sessionId: parsed.sessionId,
|
|
100
|
-
failureEvidence: observeSpawnFailure(raw, text, parsed.text ? "vendor_envelope" : "stdout"),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function normalizeOpenCodeTransportResult(raw: AdapterRawResult): SpawnResult {
|
|
105
91
|
if (raw.timedOut) {
|
|
106
92
|
return {
|
|
107
93
|
ok: false,
|
|
@@ -137,12 +137,11 @@ export interface WorkflowAgentProof {
|
|
|
137
137
|
session_id?: string;
|
|
138
138
|
result?: ResultDigest;
|
|
139
139
|
error?: string;
|
|
140
|
-
/**
|
|
141
|
-
*
|
|
140
|
+
/** Set on a failed agent whose spawn was classified uninformative about the
|
|
141
|
+
* work (ADR 0046): environment (binary absent) or upstream (vendor refused).
|
|
142
|
+
* Absent ⇒ a work failure. Recorded even when a script's parallel() swallows
|
|
143
|
+
* the rejection, so the run-level class can still be derived from proof. */
|
|
142
144
|
class?: SpawnFailureClass;
|
|
143
|
-
/** Failure observations carried from the adapter. Missing observations are
|
|
144
|
-
* unknown and cannot authorize an uncharged attempt. */
|
|
145
|
-
failure_evidence?: SpawnFailureEvidence;
|
|
146
145
|
/** Which vendor sandbox mode this agent launched with and why (ADR 0192).
|
|
147
146
|
* Absent on a cached agent, which launched nothing. */
|
|
148
147
|
access?: WorkerAccessDecision;
|
|
@@ -363,16 +362,15 @@ export interface WorkflowSpecialistProfile {
|
|
|
363
362
|
}
|
|
364
363
|
|
|
365
364
|
/**
|
|
366
|
-
*
|
|
367
|
-
*
|
|
365
|
+
* Why a failed run was uninformative about the work (ADR 0046). Absent means
|
|
366
|
+
* the attempt produced information about the work (or succeeded), which is the
|
|
367
|
+
* default and is charged against the attempt budget exactly as before.
|
|
368
368
|
*
|
|
369
369
|
* - `environment`: the run never started — a precondition was missing (the
|
|
370
|
-
* vendor binary was absent).
|
|
371
|
-
* uncharged and not retried: retrying an unchanged
|
|
370
|
+
* vendor binary was absent). Uncharged AND not retried: retrying an unchanged
|
|
372
371
|
* environment cannot help, so the work item stops and names the precondition.
|
|
373
372
|
* - `upstream`: the vendor was reached and refused (5xx, 429, circuit open).
|
|
374
|
-
*
|
|
375
|
-
* observed absent output, and no refusal. Unsupported evidence stays unknown.
|
|
373
|
+
* Uncharged, but retry stays available (bounded by max_uncharged_attempts).
|
|
376
374
|
*/
|
|
377
375
|
export type SpawnFailureClass = "environment" | "upstream";
|
|
378
376
|
|
|
@@ -394,30 +392,6 @@ export type SpawnFailureClass = "environment" | "upstream";
|
|
|
394
392
|
*/
|
|
395
393
|
export type RunFailureClass = SpawnFailureClass | "decision";
|
|
396
394
|
|
|
397
|
-
/** Positive claims name the observation that supports them. A transport error
|
|
398
|
-
* or an empty reply is never evidence that no effects occurred. */
|
|
399
|
-
export type SpawnEffectEvidence =
|
|
400
|
-
| { state: "unknown" }
|
|
401
|
-
| { state: "none"; source: "spawn_errno"; reference: "ENOENT" }
|
|
402
|
-
| { state: "none" | "observed"; source: "adapter_evidence"; reference: string };
|
|
403
|
-
|
|
404
|
-
export type SpawnRefusalEvidence =
|
|
405
|
-
| { state: "unknown" }
|
|
406
|
-
| { state: "refused"; source: "vendor_envelope"; reference: string };
|
|
407
|
-
|
|
408
|
-
export type SpawnOutputSource = "final_message" | "vendor_envelope" | "stdout";
|
|
409
|
-
|
|
410
|
-
export type SpawnPartialOutputEvidence =
|
|
411
|
-
| { state: "unknown" }
|
|
412
|
-
| { state: "absent"; source: SpawnOutputSource }
|
|
413
|
-
| { state: "present"; source: SpawnOutputSource; bytes: number; sha256: string };
|
|
414
|
-
|
|
415
|
-
export interface SpawnFailureEvidence {
|
|
416
|
-
effect: SpawnEffectEvidence;
|
|
417
|
-
refusal: SpawnRefusalEvidence;
|
|
418
|
-
partial_output: SpawnPartialOutputEvidence;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
395
|
/** What a spawn adapter returns for one subagent run. */
|
|
422
396
|
export interface SpawnResult {
|
|
423
397
|
ok: boolean;
|
|
@@ -429,13 +403,9 @@ export interface SpawnResult {
|
|
|
429
403
|
durationMs: number;
|
|
430
404
|
/** Populated when ok=false. */
|
|
431
405
|
error?: string;
|
|
432
|
-
/**
|
|
433
|
-
*
|
|
406
|
+
/** Set when ok=false and the failure was positively identified as
|
|
407
|
+
* uninformative about the work. Absent ⇒ a work failure (charged). */
|
|
434
408
|
class?: SpawnFailureClass;
|
|
435
|
-
/** Transport classification alone grants neither a retry nor free work.
|
|
436
|
-
* Absent evidence is explicitly unknown. Positive adapter_evidence claims
|
|
437
|
-
* require a documented producer observation, never an HTTP/text heuristic. */
|
|
438
|
-
failureEvidence?: SpawnFailureEvidence;
|
|
439
409
|
}
|
|
440
410
|
|
|
441
411
|
export interface SpawnRequest {
|
|
@@ -140,7 +140,6 @@ export function reclaimAbandonedLocalConflict(
|
|
|
140
140
|
let sessionId = conflict.instance_id;
|
|
141
141
|
let ageSecs: number | undefined;
|
|
142
142
|
const liveRow = readLiveCoordinationRow(coordRoot, conflict.instance_id);
|
|
143
|
-
let expectedGenerationId = liveRow?.v3_generation_id ?? null;
|
|
144
143
|
if (liveRow) {
|
|
145
144
|
adapter = adapterOf(liveRow.platform);
|
|
146
145
|
sessionId = liveRow.session_id || conflict.instance_id;
|
|
@@ -154,9 +153,7 @@ export function reclaimAbandonedLocalConflict(
|
|
|
154
153
|
// It is required to join the exact producer state when recording the sweep.
|
|
155
154
|
adapter = adapterOf(hb.platform);
|
|
156
155
|
sessionId = hb.session_id || conflict.instance_id;
|
|
157
|
-
expectedGenerationId = hb.v3_generation_id ?? null;
|
|
158
156
|
} catch {
|
|
159
|
-
expectedGenerationId = null;
|
|
160
157
|
/* content unreadable — still reclaim the file */
|
|
161
158
|
}
|
|
162
159
|
try {
|
|
@@ -171,7 +168,6 @@ export function reclaimAbandonedLocalConflict(
|
|
|
171
168
|
owner: conflict.instance_id,
|
|
172
169
|
nativeSessionId: sessionId,
|
|
173
170
|
adapter,
|
|
174
|
-
expectedGenerationId,
|
|
175
171
|
observation: "stale_heartbeat",
|
|
176
172
|
ageMs: Math.max(0, (ageSecs ?? 0) * 1_000),
|
|
177
173
|
});
|
package/src/lib/tunnel/state.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// <cwd>/.cache/tunnel/; gitignored, so the allowlist is per-machine.
|
|
3
3
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
6
5
|
import {
|
|
7
6
|
accessSync,
|
|
8
7
|
chmodSync,
|
|
@@ -11,7 +10,6 @@ import {
|
|
|
11
10
|
mkdirSync,
|
|
12
11
|
readdirSync,
|
|
13
12
|
readFileSync,
|
|
14
|
-
renameSync,
|
|
15
13
|
statSync,
|
|
16
14
|
unlinkSync,
|
|
17
15
|
writeFileSync,
|
|
@@ -106,12 +104,10 @@ export interface TunnelState {
|
|
|
106
104
|
provider: TunnelProvider;
|
|
107
105
|
url: string;
|
|
108
106
|
gate_pid: number;
|
|
109
|
-
gate_start_token?: string;
|
|
110
|
-
cloudflared_start_token?: string;
|
|
111
|
-
registration_id?: string;
|
|
112
|
-
provider_state?: "pending" | "ready";
|
|
113
107
|
/** Present for Cloudflare quick tunnels; absent for Tailscale Serve/Funnel. */
|
|
114
108
|
cloudflared_pid?: number;
|
|
109
|
+
/** Optional provider-side process when a provider owns one. */
|
|
110
|
+
provider_pid?: number;
|
|
115
111
|
started_at: string;
|
|
116
112
|
target: string;
|
|
117
113
|
vhost: string;
|
|
@@ -172,15 +168,7 @@ export function readState(
|
|
|
172
168
|
}
|
|
173
169
|
|
|
174
170
|
export function writeState(state: TunnelState, root: string = process.cwd()): void {
|
|
175
|
-
|
|
176
|
-
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
177
|
-
try {
|
|
178
|
-
writeFileSync(temporary, JSON.stringify(state, null, 2), { flag: "wx", mode: 0o600 });
|
|
179
|
-
renameSync(temporary, path);
|
|
180
|
-
} catch (error) {
|
|
181
|
-
if (existsSync(temporary)) unlinkSync(temporary);
|
|
182
|
-
throw error;
|
|
183
|
-
}
|
|
171
|
+
writeFileSync(cachePath(stateFile(state.name), root), JSON.stringify(state, null, 2));
|
|
184
172
|
}
|
|
185
173
|
|
|
186
174
|
export function clearState(name: string = DEFAULT_INSTANCE, root: string = process.cwd()): void {
|
|
@@ -214,60 +202,17 @@ export function isProcessAlive(pid: number): boolean {
|
|
|
214
202
|
}
|
|
215
203
|
}
|
|
216
204
|
|
|
217
|
-
export type
|
|
218
|
-
export type TunnelProcessObservation = "same" | "absent" | "different" | "unknown";
|
|
219
|
-
|
|
220
|
-
export interface TunnelReadinessVerifier {
|
|
221
|
-
observeProcess(pid: number, originalToken: string): TunnelProcessObservation;
|
|
222
|
-
withRegistration(state: TunnelState, inspect: () => TunnelReadiness): TunnelReadiness;
|
|
223
|
-
}
|
|
205
|
+
export type ProcessAliveCheck = (pid: number) => boolean;
|
|
224
206
|
|
|
225
|
-
/**
|
|
226
|
-
export function
|
|
207
|
+
/** True when both the local gate and its provider-side process are live. */
|
|
208
|
+
export function isTunnelStateLive(
|
|
227
209
|
state: TunnelState,
|
|
228
|
-
|
|
229
|
-
):
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
Object.hasOwn(state, "provider_start_token") ||
|
|
235
|
-
typeof state.registration_id !== "string" ||
|
|
236
|
-
!state.registration_id ||
|
|
237
|
-
!Number.isSafeInteger(state.gate_pid) ||
|
|
238
|
-
state.gate_pid < 1 ||
|
|
239
|
-
typeof state.gate_start_token !== "string" ||
|
|
240
|
-
!state.gate_start_token
|
|
241
|
-
)
|
|
242
|
-
return "unknown";
|
|
243
|
-
const targets = [{ pid: state.gate_pid, token: state.gate_start_token }];
|
|
244
|
-
if (state.provider === "tailscale") {
|
|
245
|
-
if (
|
|
246
|
-
state.provider_state !== "ready" ||
|
|
247
|
-
state.cloudflared_pid !== undefined ||
|
|
248
|
-
state.cloudflared_start_token !== undefined
|
|
249
|
-
)
|
|
250
|
-
return "unknown";
|
|
251
|
-
} else if (state.provider === "cloudflare") {
|
|
252
|
-
if (
|
|
253
|
-
!Number.isSafeInteger(state.cloudflared_pid) ||
|
|
254
|
-
state.cloudflared_pid! < 1 ||
|
|
255
|
-
typeof state.cloudflared_start_token !== "string" ||
|
|
256
|
-
!state.cloudflared_start_token
|
|
257
|
-
)
|
|
258
|
-
return "unknown";
|
|
259
|
-
targets.push({ pid: state.cloudflared_pid!, token: state.cloudflared_start_token });
|
|
260
|
-
} else return "unknown";
|
|
261
|
-
try {
|
|
262
|
-
return verifier.withRegistration(state, () => {
|
|
263
|
-
const observed = targets.map(({ pid, token }) => verifier.observeProcess(pid, token));
|
|
264
|
-
if (observed.every((value) => value === "absent")) return "dead";
|
|
265
|
-
if (observed.every((value) => value === "same")) return "ready";
|
|
266
|
-
return "unknown";
|
|
267
|
-
});
|
|
268
|
-
} catch {
|
|
269
|
-
return "unknown";
|
|
270
|
-
}
|
|
210
|
+
processAlive: ProcessAliveCheck = isProcessAlive,
|
|
211
|
+
): boolean {
|
|
212
|
+
if (!processAlive(state.gate_pid)) return false;
|
|
213
|
+
if (state.provider === "tailscale") return true;
|
|
214
|
+
const providerPid = state.cloudflared_pid ?? state.provider_pid;
|
|
215
|
+
return typeof providerPid === "number" && processAlive(providerPid);
|
|
271
216
|
}
|
|
272
217
|
|
|
273
218
|
export function tunnelTargetPort(state: TunnelState): number | null {
|
|
@@ -297,15 +242,15 @@ export function tunnelTargetPort(state: TunnelState): number | null {
|
|
|
297
242
|
export function findLiveTunnelForOrigin(
|
|
298
243
|
port: number,
|
|
299
244
|
vhost: string,
|
|
300
|
-
root: string,
|
|
301
|
-
|
|
245
|
+
root: string = process.cwd(),
|
|
246
|
+
processAlive: ProcessAliveCheck = isProcessAlive,
|
|
302
247
|
): TunnelState | null {
|
|
303
248
|
return (
|
|
304
249
|
listStates(root).find(
|
|
305
250
|
(state) =>
|
|
306
251
|
tunnelTargetPort(state) === port &&
|
|
307
252
|
state.vhost === vhost &&
|
|
308
|
-
|
|
253
|
+
isTunnelStateLive(state, processAlive),
|
|
309
254
|
) ?? null
|
|
310
255
|
);
|
|
311
256
|
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import type { EmitContext, HarneryProgramContext } from "../commander.js";
|
|
3
|
-
/** Explicit turn boundaries; hooks cannot infer provider ownership or path scope. */
|
|
4
|
-
export declare function registerTurnRollbackCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
|
|
5
|
-
//# sourceMappingURL=turn-rollback.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"turn-rollback.d.ts","sourceRoot":"","sources":["../../src/commands/turn-rollback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAa1E,qFAAqF;AACrF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA2FN"}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { monorepoRoot } from "../core/agents/index.js";
|
|
2
|
-
import { applyTurnRollback, beginTurnRollback, inspectTurnRollback, prepareTurnRollback, sealTurnRollback, } from "../core/turn-rollback/index.js";
|
|
3
|
-
import { readJsonRecord } from "../core/workflow/durable-record.js";
|
|
4
|
-
import { readWorkspaceBinding } from "../core/workflow/workspaces/state.js";
|
|
5
|
-
/** Explicit turn boundaries; hooks cannot infer provider ownership or path scope. */
|
|
6
|
-
export function registerTurnRollbackCommand(program, emit, context) {
|
|
7
|
-
const command = program
|
|
8
|
-
.command("turn-rollback")
|
|
9
|
-
.description("Capture and review exact-path content rollback in an owned workspace");
|
|
10
|
-
const run = (operation) => async (options) => {
|
|
11
|
-
try {
|
|
12
|
-
const coordRoot = context?.resolveCoordRoot?.() ?? context?.repoRoot ?? monorepoRoot();
|
|
13
|
-
if (!coordRoot)
|
|
14
|
-
throw new Error("project coordination root is unavailable");
|
|
15
|
-
emit.config({ format: "json" });
|
|
16
|
-
const result = await operation(coordRoot, options);
|
|
17
|
-
emit.data(result);
|
|
18
|
-
if (result &&
|
|
19
|
-
typeof result === "object" &&
|
|
20
|
-
"status" in result &&
|
|
21
|
-
result.status === "unknown")
|
|
22
|
-
emit.setExitCode(1);
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
emit.error({ code: "turn_rollback_refused", message: String(error) });
|
|
26
|
-
emit.setExitCode(1);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
const binding = (root, options) => {
|
|
30
|
-
const value = readWorkspaceBinding(root, "local-git-worktree", options.binding);
|
|
31
|
-
if (!value)
|
|
32
|
-
throw new Error("immutable workspace binding was not found");
|
|
33
|
-
return value;
|
|
34
|
-
};
|
|
35
|
-
command
|
|
36
|
-
.command("begin")
|
|
37
|
-
.description("Capture exact literal paths before a turn")
|
|
38
|
-
.requiredOption("--binding <id>", "Owned workspace binding ID")
|
|
39
|
-
.requiredOption("--turn <id>", "Caller turn identity")
|
|
40
|
-
.requiredOption("--generation <number>", "Increasing turn generation")
|
|
41
|
-
.requiredOption("--path <file...>", "Exact relative file paths, including planned new files")
|
|
42
|
-
.action(run((root, options) => beginTurnRollback(root, {
|
|
43
|
-
binding: binding(root, options),
|
|
44
|
-
turn: { turn_id: options.turn, generation: Number(options.generation) },
|
|
45
|
-
paths: options.path,
|
|
46
|
-
})));
|
|
47
|
-
for (const operation of ["seal", "prepare", "show"]) {
|
|
48
|
-
command
|
|
49
|
-
.command(`${operation} <capture-id>`)
|
|
50
|
-
.description(`${operation} an exact turn capture`)
|
|
51
|
-
.requiredOption("--binding <id>", "Owned workspace binding ID")
|
|
52
|
-
.action((id, options) => run((root, opts) => {
|
|
53
|
-
const input = { binding: binding(root, opts), capture_id: id };
|
|
54
|
-
return operation === "seal"
|
|
55
|
-
? sealTurnRollback(root, input)
|
|
56
|
-
: operation === "prepare"
|
|
57
|
-
? prepareTurnRollback(root, input)
|
|
58
|
-
: inspectTurnRollback(root, input);
|
|
59
|
-
})(options));
|
|
60
|
-
}
|
|
61
|
-
command
|
|
62
|
-
.command("apply")
|
|
63
|
-
.description("Preview a saved proposal, or apply it after exact confirmation")
|
|
64
|
-
.requiredOption("--binding <id>", "Owned workspace binding ID")
|
|
65
|
-
.requiredOption("--plan <file>", "Saved JSON proposal returned by prepare")
|
|
66
|
-
.option("--yes", "Apply this exact proposal; preview is the default")
|
|
67
|
-
.option("--actor <name>", "Who authorized the restore")
|
|
68
|
-
.option("--reason <text>", "Why this turn is being restored")
|
|
69
|
-
.action(run((root, options) => applyTurnRollback(root, {
|
|
70
|
-
binding: binding(root, options),
|
|
71
|
-
plan: readJsonRecord(options.plan, "rollback proposal"),
|
|
72
|
-
yes: options.yes,
|
|
73
|
-
actor: options.actor,
|
|
74
|
-
reason: options.reason,
|
|
75
|
-
})));
|
|
76
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export interface FileRevision {
|
|
2
|
-
readonly bytes: Buffer;
|
|
3
|
-
readonly dev: bigint;
|
|
4
|
-
readonly ino: bigint;
|
|
5
|
-
readonly size: bigint;
|
|
6
|
-
readonly mtimeNs: bigint;
|
|
7
|
-
readonly ctimeNs: bigint;
|
|
8
|
-
}
|
|
9
|
-
export type DeleteFileResult = {
|
|
10
|
-
state: "deleted" | "changed" | "missing" | "rejected";
|
|
11
|
-
} | {
|
|
12
|
-
state: "recovery-required";
|
|
13
|
-
recoveryPath: string;
|
|
14
|
-
error?: string;
|
|
15
|
-
} | {
|
|
16
|
-
state: "error";
|
|
17
|
-
recoveryPath?: string;
|
|
18
|
-
error: string;
|
|
19
|
-
};
|
|
20
|
-
export interface FileRecoveryResult {
|
|
21
|
-
state: "restored" | "retained" | "error";
|
|
22
|
-
recoveryPath: string;
|
|
23
|
-
sourcePath?: string;
|
|
24
|
-
error?: string;
|
|
25
|
-
}
|
|
26
|
-
/** A stable read of one regular file, including its exact bytes and revision. */
|
|
27
|
-
export declare function captureFileRevision(filePath: string): FileRevision | null;
|
|
28
|
-
export declare function fileRevisionMatches(filePath: string, expected: FileRevision): boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Writers must publish by atomic rename, never by an open descriptor to the old
|
|
31
|
-
* inode. The private directory is outside their publication namespace. Moving
|
|
32
|
-
* the row gives deletion custody over that inode without owning its live name.
|
|
33
|
-
* The caller owns the state parent and keeps its topology stable. Its platform
|
|
34
|
-
* permissions define which principals can modify it; this protocol does not
|
|
35
|
-
* exclude principals already authorized to change that parent.
|
|
36
|
-
*/
|
|
37
|
-
export declare function deleteFileIfUnchanged(filePath: string, expected: FileRevision, validate: (pinned: FileRevision) => boolean): DeleteFileResult;
|
|
38
|
-
/**
|
|
39
|
-
* Recover same-host/platform/PID-namespace pins after process termination. File fsync
|
|
40
|
-
* protects manifest contents; directory entries are not fsynced, so this does
|
|
41
|
-
* not promise recovery after an OS crash or power loss.
|
|
42
|
-
*/
|
|
43
|
-
export declare function recoverInterruptedFileDeletions(directoryPath: string, validate: (sourcePath: string) => boolean): FileRecoveryResult[];
|
|
44
|
-
//# sourceMappingURL=unchanged-file.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"unchanged-file.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/unchanged-file.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAA;CAAE,GACzD;IAAE,KAAK,EAAE,mBAAmB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoCzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,OAAO,CAGrF;AAsGD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,GAC1C,gBAAgB,CAmFlB;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,GACxC,kBAAkB,EAAE,CA+FtB"}
|