harnery 0.45.0 → 0.45.2
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/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +6 -45
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +0 -18
- 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 +2 -7
- package/dist/core/agents/state/proc-start.d.ts +20 -31
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +37 -70
- package/dist/core/agents/state/service-process.d.ts +61 -0
- package/dist/core/agents/state/service-process.d.ts.map +1 -0
- package/dist/core/agents/state/service-process.js +143 -0
- 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/context/index.d.ts +6 -4
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +194 -256
- package/dist/core/context/observation.d.ts +9 -9
- package/dist/core/context/observation.d.ts.map +1 -1
- package/dist/core/context/observation.js +37 -6
- 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/events/v3/runtime-build.d.ts +14 -0
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +44 -0
- package/dist/core/governor/index.d.ts +2 -1
- package/dist/core/governor/index.d.ts.map +1 -1
- package/dist/core/governor/index.js +2 -1
- package/dist/core/governor/service-read.d.ts +3 -10
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +10 -94
- package/dist/core/governor/service.d.ts +3 -7
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +17 -182
- package/dist/core/hooks/cli.js +78 -48
- package/dist/core/hooks/resolve/owner.js +2 -2
- package/dist/core/resources/sampler.js +2 -2
- package/dist/core/resources/service-status.d.ts +2 -14
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +3 -23
- package/dist/core/resources/service.d.ts +3 -7
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +12 -101
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +3 -2
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +9 -2
- package/dist/core/servers/index.d.ts +11 -10
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +82 -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/supervisor/service.d.ts +4 -7
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +23 -122
- package/dist/core/supervisor/services.js +2 -2
- package/dist/core/supervisor/status.d.ts +3 -17
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +7 -70
- package/dist/core/work/state.js +7 -7
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +23 -66
- package/dist/core/workflow/index.d.ts +3 -4
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -2
- 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 +34 -86
- package/dist/core/workflow/resume-lock.d.ts +17 -0
- package/dist/core/workflow/resume-lock.d.ts.map +1 -0
- package/dist/core/workflow/resume-lock.js +158 -0
- package/dist/core/workflow/run-state.d.ts +0 -21
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +3 -174
- 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/core/workflow/workspaces/leases.d.ts +4 -0
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +1 -1
- package/dist/core/workflow/workspaces/local-git.js +91 -210
- package/dist/core/workflow/workspaces/ownership.d.ts +0 -7
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/ownership.js +3 -130
- package/dist/core/workflow/workspaces/reclaim.d.ts +4 -7
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +0 -4
- package/dist/core/workflow/workspaces/recovery.d.ts +1 -1
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +1 -4
- 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/commands/workflow.ts +8 -58
- package/src/core/agents/coord-client.ts +0 -20
- 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 +2 -15
- package/src/core/agents/state/proc-start.ts +41 -69
- package/src/core/agents/state/service-process.ts +217 -0
- 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/context/index.ts +207 -272
- package/src/core/context/observation.ts +40 -9
- 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/events/v3/runtime-build.ts +62 -0
- package/src/core/governor/index.ts +1 -2
- package/src/core/governor/service-read.ts +15 -114
- package/src/core/governor/service.ts +36 -220
- package/src/core/hooks/cli.ts +85 -47
- package/src/core/hooks/resolve/owner.ts +2 -2
- package/src/core/resources/sampler.ts +3 -2
- package/src/core/resources/service-status.ts +6 -35
- package/src/core/resources/service.ts +23 -133
- package/src/core/resources/status.ts +3 -2
- package/src/core/semantic/service.ts +8 -2
- package/src/core/servers/index.ts +86 -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/supervisor/service.ts +33 -155
- package/src/core/supervisor/services.ts +2 -2
- package/src/core/supervisor/status.ts +10 -85
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +22 -74
- package/src/core/workflow/index.ts +2 -13
- package/src/core/workflow/live-session-v3.ts +5 -146
- package/src/core/workflow/proof.ts +40 -104
- package/src/core/workflow/resume-lock.ts +179 -0
- package/src/core/workflow/run-state.ts +3 -207
- 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/core/workflow/workspaces/leases.ts +1 -1
- package/src/core/workflow/workspaces/local-git.ts +120 -239
- package/src/core/workflow/workspaces/ownership.ts +3 -158
- package/src/core/workflow/workspaces/reclaim.ts +3 -9
- package/src/core/workflow/workspaces/recovery.ts +1 -1
- package/src/core/workflow/workspaces/state.ts +1 -5
- 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/process-handle.d.ts +0 -26
- package/dist/core/agents/state/process-handle.d.ts.map +0 -1
- package/dist/core/agents/state/process-handle.js +0 -121
- 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/dist/core/workflow/recovery-audit.d.ts +0 -30
- package/dist/core/workflow/recovery-audit.d.ts.map +0 -1
- package/dist/core/workflow/recovery-audit.js +0 -116
- package/src/commands/turn-rollback.ts +0 -111
- package/src/core/agents/state/process-handle.ts +0 -165
- 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
- package/src/core/workflow/recovery-audit.ts +0 -181
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { existsSync } from "node:fs";
|
|
19
|
-
import type { WorkflowRecoveryAuthorization } from "../recovery-audit.ts";
|
|
20
19
|
import { git, gitMaybe } from "./git.ts";
|
|
21
20
|
import type { WorkflowLiveness } from "./recovery.ts";
|
|
22
21
|
import type { WorkspaceCleanupAttempt, WorkspaceCleanupReceipt } from "./types.ts";
|
|
@@ -26,7 +25,6 @@ export type ReclaimMode = "salvage" | "discard";
|
|
|
26
25
|
export type WorkflowReclaimRefusalCode =
|
|
27
26
|
| "workflow_reclaim_run_live"
|
|
28
27
|
| "workflow_reclaim_liveness_unknown"
|
|
29
|
-
| "workflow_reclaim_recovery_mismatch"
|
|
30
28
|
| "workflow_reclaim_ownership_unproven"
|
|
31
29
|
| "workflow_reclaim_lease_held";
|
|
32
30
|
|
|
@@ -43,7 +41,8 @@ export class WorkflowReclaimError extends Error {
|
|
|
43
41
|
export interface WorkspaceReclaimOptions {
|
|
44
42
|
mode: ReclaimMode;
|
|
45
43
|
yes?: boolean;
|
|
46
|
-
|
|
44
|
+
/** Admit missing or corrupt liveness evidence; active evidence still refuses. */
|
|
45
|
+
force?: boolean;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
export interface ReclaimLeaseHolder {
|
|
@@ -59,7 +58,6 @@ export interface WorkspaceReclaimPreview {
|
|
|
59
58
|
run_id: string;
|
|
60
59
|
mode: ReclaimMode;
|
|
61
60
|
liveness: WorkflowLiveness;
|
|
62
|
-
evidence_sha256: string;
|
|
63
61
|
ownership: "ok" | "unproven";
|
|
64
62
|
ownership_detail?: string;
|
|
65
63
|
dirty_paths: string[] | null;
|
|
@@ -84,7 +82,7 @@ export interface PrepareReclaimInput {
|
|
|
84
82
|
worktreePath: string;
|
|
85
83
|
mode: ReclaimMode;
|
|
86
84
|
runId: string;
|
|
87
|
-
/**
|
|
85
|
+
/** Recheck peer authority once, immediately before the first destructive step. */
|
|
88
86
|
beforeEffect?: () => void;
|
|
89
87
|
}
|
|
90
88
|
|
|
@@ -117,7 +115,6 @@ export function prepareReclaim(input: PrepareReclaimInput): ReclaimPreparation {
|
|
|
117
115
|
// cannot see. Running clean first would leave staged deletions behind.
|
|
118
116
|
input.beforeEffect?.();
|
|
119
117
|
git(input.worktreePath, ["reset", "--hard"]);
|
|
120
|
-
input.beforeEffect?.();
|
|
121
118
|
git(input.worktreePath, ["clean", "-fd"]);
|
|
122
119
|
return { action: "discarded", detail: "uncommitted changes were discarded on request" };
|
|
123
120
|
}
|
|
@@ -138,13 +135,10 @@ export function prepareReclaim(input: PrepareReclaimInput): ReclaimPreparation {
|
|
|
138
135
|
const before = git(input.worktreePath, ["rev-parse", "HEAD"]).trim();
|
|
139
136
|
input.beforeEffect?.();
|
|
140
137
|
git(input.worktreePath, ["add", "-A"]);
|
|
141
|
-
input.beforeEffect?.();
|
|
142
138
|
git(input.worktreePath, ["commit", "--no-verify", "-m", salvageMessage(input.runId)]);
|
|
143
139
|
const commit = git(input.worktreePath, ["rev-parse", "HEAD"]).trim();
|
|
144
140
|
const branch = salvageBranch(input.runId);
|
|
145
|
-
input.beforeEffect?.();
|
|
146
141
|
git(input.worktreePath, ["branch", "--force", branch, commit]);
|
|
147
|
-
input.beforeEffect?.();
|
|
148
142
|
git(input.worktreePath, ["reset", "--hard", before]);
|
|
149
143
|
return {
|
|
150
144
|
action: "salvaged",
|
|
@@ -4,7 +4,7 @@ import { readWorkflowProof } from "../proof.ts";
|
|
|
4
4
|
|
|
5
5
|
export type WorkflowLiveness = "active" | "inactive" | "unknown";
|
|
6
6
|
|
|
7
|
-
/** Corruption must not hide a readable active marker from
|
|
7
|
+
/** Corruption must not hide a readable active marker from a forced reclaim. */
|
|
8
8
|
export function hasActiveWorkflowEvidence(coordRoot: string, runId: string): boolean {
|
|
9
9
|
const path = join(resolve(coordRoot), ".harnery", "workflows", runId, "transcript.jsonl");
|
|
10
10
|
if (!existsSync(path)) return false;
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
stableDigest,
|
|
20
20
|
writeImmutableJson,
|
|
21
21
|
} from "../durable-record.ts";
|
|
22
|
-
import { assertClaimAuthority
|
|
22
|
+
import { assertClaimAuthority } from "./ownership.ts";
|
|
23
23
|
|
|
24
24
|
export { appendWorkflowTranscriptEvent } from "../transcript.ts";
|
|
25
25
|
|
|
@@ -143,10 +143,6 @@ export function readWorkspaceRequest(
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
export function writeWorkspaceClaim(coordRoot: string, claim: WorkspaceClaim): boolean {
|
|
146
|
-
return publishWorkspaceClaim(coordRoot, claim, () => writeClaim(coordRoot, claim));
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function writeClaim(coordRoot: string, claim: WorkspaceClaim): boolean {
|
|
150
146
|
const path = workspaceClaimPath(coordRoot, claim.provider_id, claim.binding_id);
|
|
151
147
|
try {
|
|
152
148
|
return writeImmutableJson(path, claim);
|
|
@@ -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,26 +0,0 @@
|
|
|
1
|
-
import type { ProcessGenerationVerdict } from "./proc-start.js";
|
|
2
|
-
export interface ProcessHandleSignalResult {
|
|
3
|
-
sent: boolean;
|
|
4
|
-
verdict: ProcessGenerationVerdict;
|
|
5
|
-
}
|
|
6
|
-
export type NativeProcessHelperRunner = (command: string, args: string[], options: {
|
|
7
|
-
input: string;
|
|
8
|
-
encoding: "utf8";
|
|
9
|
-
timeout: number;
|
|
10
|
-
maxBuffer: number;
|
|
11
|
-
windowsHide: boolean;
|
|
12
|
-
}) => {
|
|
13
|
-
status: number | null;
|
|
14
|
-
stdout: string | Buffer | null;
|
|
15
|
-
error?: Error;
|
|
16
|
-
};
|
|
17
|
-
export declare const LINUX_PROCESS_HANDLE_SCRIPT = "\nimport json, os, pathlib, signal, sys\nresult = {\"sent\": False, \"verdict\": \"unknown\"}\nfd = None\ntry:\n request = json.loads(sys.stdin.readline())\n if not hasattr(os, \"pidfd_open\") or not hasattr(signal, \"pidfd_send_signal\"):\n raise RuntimeError(\"pidfd support unavailable\")\n fd = os.pidfd_open(request[\"pid\"])\n stat = pathlib.Path(\"/proc/%d/stat\" % request[\"pid\"]).read_text()\n ticks = stat[stat.rfind(\") \") + 2:].split()[19]\n boot = pathlib.Path(\"/proc/sys/kernel/random/boot_id\").read_text().strip().replace(\"-\", \"\")[:8]\n token = \"l%s.%s\" % (boot, ticks)\n result[\"token\"] = token\n recorded = request.get(\"token\")\n result[\"verdict\"] = \"same\" if token == recorded else \"different\"\n if request[\"mode\"] == \"signal\" and result[\"verdict\"] == \"same\":\n print(json.dumps({\"phase\": \"verified\"}), flush=True)\n if sys.stdin.readline().strip() == \"signal\":\n signal.pidfd_send_signal(fd, request[\"signal\"])\n result[\"sent\"] = True\nexcept Exception:\n pass\nfinally:\n if fd is not None:\n os.close(fd)\nprint(json.dumps(result), flush=True)\n";
|
|
18
|
-
export declare const WINDOWS_PROCESS_HANDLE_SCRIPT = "\n$ErrorActionPreference = 'Stop'\n$result = @{ sent = $false; verdict = 'unknown' }\n$handle = [IntPtr]::Zero\ntry {\n Add-Type -TypeDefinition @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class HarneryProcessHandle {\n [DllImport(\"kernel32.dll\", SetLastError=true)] public static extern IntPtr OpenProcess(uint access, bool inherit, uint processId);\n [DllImport(\"kernel32.dll\", SetLastError=true)] public static extern bool GetProcessTimes(IntPtr handle, out long creation, out long exit, out long kernel, out long user);\n [DllImport(\"kernel32.dll\", SetLastError=true)] public static extern bool TerminateProcess(IntPtr handle, uint exitCode);\n [DllImport(\"kernel32.dll\")] public static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);\n [DllImport(\"kernel32.dll\")] public static extern bool CloseHandle(IntPtr handle);\n}\n'@\n $request = [Console]::In.ReadLine() | ConvertFrom-Json\n $access = [uint32]0x101000\n if ($request.mode -eq 'signal') { $access = $access -bor 1 }\n $handle = [HarneryProcessHandle]::OpenProcess($access, $false, [uint32]$request.pid)\n if ($handle -eq [IntPtr]::Zero) { throw 'process handle unavailable' }\n [long]$creation = 0; [long]$exitTime = 0; [long]$kernel = 0; [long]$userTime = 0\n if (-not [HarneryProcessHandle]::GetProcessTimes($handle, [ref]$creation, [ref]$exitTime, [ref]$kernel, [ref]$userTime)) { throw 'process time unavailable' }\n $token = 'w' + $creation.ToString([Globalization.CultureInfo]::InvariantCulture)\n $result.token = $token\n $result.verdict = if ($token -ceq $request.token) { 'same' } else { 'different' }\n if ($request.mode -eq 'signal' -and $result.verdict -eq 'same') {\n [Console]::Out.WriteLine('{\"phase\":\"verified\"}')\n [Console]::Out.Flush()\n if ([Console]::In.ReadLine() -eq 'signal' -and [HarneryProcessHandle]::WaitForSingleObject($handle, 0) -eq 258) {\n $result.sent = [HarneryProcessHandle]::TerminateProcess($handle, 1)\n }\n }\n} catch {} finally {\n if ($handle -ne [IntPtr]::Zero) { [void][HarneryProcessHandle]::CloseHandle($handle) }\n}\n$result | ConvertTo-Json -Compress\n";
|
|
19
|
-
/** Windows creation FILETIME, read from an OpenProcess handle. */
|
|
20
|
-
export declare function windowsProcessStartToken(pid: number, run?: NativeProcessHelperRunner): string | null;
|
|
21
|
-
/** No numeric-PID signaling fallback is permitted when an exact handle fails. */
|
|
22
|
-
export declare function signalProcessWithHandle(pid: number, recordedToken: string, signal: NodeJS.Signals | number, options?: {
|
|
23
|
-
platform?: NodeJS.Platform;
|
|
24
|
-
run?: NativeProcessHelperRunner;
|
|
25
|
-
}): ProcessHandleSignalResult;
|
|
26
|
-
//# sourceMappingURL=process-handle.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"process-handle.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/process-handle.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEhE,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAMD,MAAM,MAAM,yBAAyB,GAAG,CACtC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IACP,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;CACtB,KACE;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAI9E,eAAO,MAAM,2BAA2B,+qCA2BvC,CAAC;AAEF,eAAO,MAAM,6BAA6B,qnEAqCzC,CAAC;AAwCF,kEAAkE;AAClE,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,yBAAqC,GACzC,MAAM,GAAG,IAAI,CAGf;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,EAC/B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAAC,GAAG,CAAC,EAAE,yBAAyB,CAAA;CAAO,GAC5E,yBAAyB,CAgB3B"}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { constants } from "node:os";
|
|
3
|
-
// The helper owns the descriptor throughout observation and effect. The second
|
|
4
|
-
// input line authorizes the effect; EOF releases the handle without signaling.
|
|
5
|
-
export const LINUX_PROCESS_HANDLE_SCRIPT = `
|
|
6
|
-
import json, os, pathlib, signal, sys
|
|
7
|
-
result = {"sent": False, "verdict": "unknown"}
|
|
8
|
-
fd = None
|
|
9
|
-
try:
|
|
10
|
-
request = json.loads(sys.stdin.readline())
|
|
11
|
-
if not hasattr(os, "pidfd_open") or not hasattr(signal, "pidfd_send_signal"):
|
|
12
|
-
raise RuntimeError("pidfd support unavailable")
|
|
13
|
-
fd = os.pidfd_open(request["pid"])
|
|
14
|
-
stat = pathlib.Path("/proc/%d/stat" % request["pid"]).read_text()
|
|
15
|
-
ticks = stat[stat.rfind(") ") + 2:].split()[19]
|
|
16
|
-
boot = pathlib.Path("/proc/sys/kernel/random/boot_id").read_text().strip().replace("-", "")[:8]
|
|
17
|
-
token = "l%s.%s" % (boot, ticks)
|
|
18
|
-
result["token"] = token
|
|
19
|
-
recorded = request.get("token")
|
|
20
|
-
result["verdict"] = "same" if token == recorded else "different"
|
|
21
|
-
if request["mode"] == "signal" and result["verdict"] == "same":
|
|
22
|
-
print(json.dumps({"phase": "verified"}), flush=True)
|
|
23
|
-
if sys.stdin.readline().strip() == "signal":
|
|
24
|
-
signal.pidfd_send_signal(fd, request["signal"])
|
|
25
|
-
result["sent"] = True
|
|
26
|
-
except Exception:
|
|
27
|
-
pass
|
|
28
|
-
finally:
|
|
29
|
-
if fd is not None:
|
|
30
|
-
os.close(fd)
|
|
31
|
-
print(json.dumps(result), flush=True)
|
|
32
|
-
`;
|
|
33
|
-
export const WINDOWS_PROCESS_HANDLE_SCRIPT = `
|
|
34
|
-
$ErrorActionPreference = 'Stop'
|
|
35
|
-
$result = @{ sent = $false; verdict = 'unknown' }
|
|
36
|
-
$handle = [IntPtr]::Zero
|
|
37
|
-
try {
|
|
38
|
-
Add-Type -TypeDefinition @'
|
|
39
|
-
using System;
|
|
40
|
-
using System.Runtime.InteropServices;
|
|
41
|
-
public static class HarneryProcessHandle {
|
|
42
|
-
[DllImport("kernel32.dll", SetLastError=true)] public static extern IntPtr OpenProcess(uint access, bool inherit, uint processId);
|
|
43
|
-
[DllImport("kernel32.dll", SetLastError=true)] public static extern bool GetProcessTimes(IntPtr handle, out long creation, out long exit, out long kernel, out long user);
|
|
44
|
-
[DllImport("kernel32.dll", SetLastError=true)] public static extern bool TerminateProcess(IntPtr handle, uint exitCode);
|
|
45
|
-
[DllImport("kernel32.dll")] public static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
|
|
46
|
-
[DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr handle);
|
|
47
|
-
}
|
|
48
|
-
'@
|
|
49
|
-
$request = [Console]::In.ReadLine() | ConvertFrom-Json
|
|
50
|
-
$access = [uint32]0x101000
|
|
51
|
-
if ($request.mode -eq 'signal') { $access = $access -bor 1 }
|
|
52
|
-
$handle = [HarneryProcessHandle]::OpenProcess($access, $false, [uint32]$request.pid)
|
|
53
|
-
if ($handle -eq [IntPtr]::Zero) { throw 'process handle unavailable' }
|
|
54
|
-
[long]$creation = 0; [long]$exitTime = 0; [long]$kernel = 0; [long]$userTime = 0
|
|
55
|
-
if (-not [HarneryProcessHandle]::GetProcessTimes($handle, [ref]$creation, [ref]$exitTime, [ref]$kernel, [ref]$userTime)) { throw 'process time unavailable' }
|
|
56
|
-
$token = 'w' + $creation.ToString([Globalization.CultureInfo]::InvariantCulture)
|
|
57
|
-
$result.token = $token
|
|
58
|
-
$result.verdict = if ($token -ceq $request.token) { 'same' } else { 'different' }
|
|
59
|
-
if ($request.mode -eq 'signal' -and $result.verdict -eq 'same') {
|
|
60
|
-
[Console]::Out.WriteLine('{"phase":"verified"}')
|
|
61
|
-
[Console]::Out.Flush()
|
|
62
|
-
if ([Console]::In.ReadLine() -eq 'signal' -and [HarneryProcessHandle]::WaitForSingleObject($handle, 0) -eq 258) {
|
|
63
|
-
$result.sent = [HarneryProcessHandle]::TerminateProcess($handle, 1)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
} catch {} finally {
|
|
67
|
-
if ($handle -ne [IntPtr]::Zero) { [void][HarneryProcessHandle]::CloseHandle($handle) }
|
|
68
|
-
}
|
|
69
|
-
$result | ConvertTo-Json -Compress
|
|
70
|
-
`;
|
|
71
|
-
function runHelper(request, platform, run) {
|
|
72
|
-
const unknown = { sent: false, verdict: "unknown" };
|
|
73
|
-
if (!Number.isInteger(request.pid) || request.pid <= 0)
|
|
74
|
-
return unknown;
|
|
75
|
-
if (platform !== "linux" && platform !== "win32")
|
|
76
|
-
return unknown;
|
|
77
|
-
try {
|
|
78
|
-
const command = platform === "linux" ? "python3" : "powershell.exe";
|
|
79
|
-
const args = platform === "linux"
|
|
80
|
-
? ["-I", "-c", LINUX_PROCESS_HANDLE_SCRIPT]
|
|
81
|
-
: ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", WINDOWS_PROCESS_HANDLE_SCRIPT];
|
|
82
|
-
const output = run(command, args, {
|
|
83
|
-
input: `${JSON.stringify(request)}\nsignal\n`,
|
|
84
|
-
encoding: "utf8",
|
|
85
|
-
timeout: 10_000,
|
|
86
|
-
maxBuffer: 64 * 1024,
|
|
87
|
-
windowsHide: true,
|
|
88
|
-
});
|
|
89
|
-
if (output.error || output.status !== 0)
|
|
90
|
-
return unknown;
|
|
91
|
-
const lines = String(output.stdout ?? "")
|
|
92
|
-
.trim()
|
|
93
|
-
.split(/\r?\n/);
|
|
94
|
-
const parsed = JSON.parse(lines.at(-1) ?? "");
|
|
95
|
-
if (typeof parsed.sent !== "boolean" ||
|
|
96
|
-
!["same", "different", "unknown"].includes(parsed.verdict) ||
|
|
97
|
-
(parsed.sent && parsed.verdict !== "same"))
|
|
98
|
-
return unknown;
|
|
99
|
-
return parsed;
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return unknown;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/** Windows creation FILETIME, read from an OpenProcess handle. */
|
|
106
|
-
export function windowsProcessStartToken(pid, run = spawnSync) {
|
|
107
|
-
const result = runHelper({ mode: "probe", pid }, "win32", run);
|
|
108
|
-
return typeof result.token === "string" && /^w\d+$/.test(result.token) ? result.token : null;
|
|
109
|
-
}
|
|
110
|
-
/** No numeric-PID signaling fallback is permitted when an exact handle fails. */
|
|
111
|
-
export function signalProcessWithHandle(pid, recordedToken, signal, options = {}) {
|
|
112
|
-
const platform = options.platform ?? process.platform;
|
|
113
|
-
const number = typeof signal === "number" ? signal : constants.signals[signal];
|
|
114
|
-
if (!Number.isInteger(number) ||
|
|
115
|
-
number <= 0 ||
|
|
116
|
-
(platform === "linux" && !/^l[0-9a-f]{8}\.\d+$/.test(recordedToken)) ||
|
|
117
|
-
(platform === "win32" && (!/^w\d+$/.test(recordedToken) || ![9, 15].includes(number))))
|
|
118
|
-
return { sent: false, verdict: "unknown" };
|
|
119
|
-
const result = runHelper({ mode: "signal", pid, token: recordedToken, signal: number }, platform, options.run ?? spawnSync);
|
|
120
|
-
return { sent: result.sent, verdict: result.verdict };
|
|
121
|
-
}
|
|
@@ -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"}
|