harnery 0.44.0 → 0.45.0
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 +1 -0
- package/dist/commands/files.d.ts +2 -2
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +7 -4
- package/dist/commands/servers.d.ts.map +1 -1
- package/dist/commands/servers.js +11 -17
- package/dist/commands/tunnel.d.ts +15 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +453 -153
- package/dist/commands/turn-rollback.d.ts +5 -0
- package/dist/commands/turn-rollback.d.ts.map +1 -0
- package/dist/commands/turn-rollback.js +76 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +44 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +18 -0
- package/dist/core/agents/state/proc-start.d.ts +5 -6
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +13 -9
- package/dist/core/agents/state/process-handle.d.ts +26 -0
- package/dist/core/agents/state/process-handle.d.ts.map +1 -0
- package/dist/core/agents/state/process-handle.js +121 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
- package/dist/core/artifacts/delivery-card.js +2 -1
- package/dist/core/resources/service.d.ts +2 -16
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +1 -78
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +2 -9
- package/dist/core/servers/index.d.ts +12 -11
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +194 -81
- package/dist/core/servers/tunnels.d.ts +5 -3
- package/dist/core/servers/tunnels.d.ts.map +1 -1
- package/dist/core/servers/tunnels.js +45 -2
- package/dist/core/supervisor/service.d.ts +2 -4
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/turn-rollback/content.d.ts +53 -0
- package/dist/core/turn-rollback/content.d.ts.map +1 -0
- package/dist/core/turn-rollback/content.js +396 -0
- package/dist/core/turn-rollback/index.d.ts +72 -0
- package/dist/core/turn-rollback/index.d.ts.map +1 -0
- package/dist/core/turn-rollback/index.js +443 -0
- package/dist/core/workflow/index.d.ts +4 -2
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -1
- package/dist/core/workflow/recovery-audit.d.ts +30 -0
- package/dist/core/workflow/recovery-audit.d.ts.map +1 -0
- package/dist/core/workflow/recovery-audit.js +116 -0
- package/dist/core/workflow/run-state.d.ts +19 -2
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +113 -8
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +70 -14
- package/dist/core/workflow/workspaces/reclaim.d.ts +6 -1
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.d.ts +2 -0
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +24 -0
- package/dist/lib/tunnel/state.d.ts +13 -6
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +58 -12
- package/package.json +6 -1
- package/src/commander.ts +10 -0
- package/src/commands/files.ts +13 -5
- package/src/commands/servers.ts +12 -23
- package/src/commands/tunnel.ts +533 -166
- package/src/commands/turn-rollback.ts +111 -0
- package/src/commands/workflow.ts +59 -0
- package/src/core/agents/coord-client.ts +20 -0
- package/src/core/agents/state/proc-start.ts +14 -11
- package/src/core/agents/state/process-handle.ts +165 -0
- package/src/core/artifacts/delivery-card.ts +7 -1
- package/src/core/resources/service.ts +3 -88
- package/src/core/semantic/service.ts +2 -8
- package/src/core/servers/index.ts +234 -85
- package/src/core/servers/tunnels.ts +55 -3
- package/src/core/supervisor/service.ts +2 -3
- package/src/core/turn-rollback/content.ts +499 -0
- package/src/core/turn-rollback/index.ts +580 -0
- package/src/core/workflow/index.ts +12 -1
- package/src/core/workflow/recovery-audit.ts +181 -0
- package/src/core/workflow/run-state.ts +137 -8
- package/src/core/workflow/workspaces/local-git.ts +86 -15
- package/src/core/workflow/workspaces/reclaim.ts +4 -0
- package/src/core/workflow/workspaces/recovery.ts +23 -0
- package/src/lib/tunnel/state.ts +70 -15
package/dist/lib/tunnel/state.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Tunnel config + state persistence + provider helpers. Commands default to
|
|
2
2
|
// <cwd>/.cache/tunnel/; gitignored, so the allowlist is per-machine.
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
|
-
import {
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { accessSync, chmodSync, constants, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
7
|
import { delimiter, join, resolve } from "node:path";
|
|
7
8
|
import { isPathAllowed } from "./path-scope.js";
|
|
@@ -104,7 +105,17 @@ export function readState(name = DEFAULT_INSTANCE, root = process.cwd()) {
|
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
export function writeState(state, root = process.cwd()) {
|
|
107
|
-
|
|
108
|
+
const path = cachePath(stateFile(state.name), root);
|
|
109
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
110
|
+
try {
|
|
111
|
+
writeFileSync(temporary, JSON.stringify(state, null, 2), { flag: "wx", mode: 0o600 });
|
|
112
|
+
renameSync(temporary, path);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (existsSync(temporary))
|
|
116
|
+
unlinkSync(temporary);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
108
119
|
}
|
|
109
120
|
export function clearState(name = DEFAULT_INSTANCE, root = process.cwd()) {
|
|
110
121
|
const p = cachePath(stateFile(name), root);
|
|
@@ -139,14 +150,49 @@ export function isProcessAlive(pid) {
|
|
|
139
150
|
return false;
|
|
140
151
|
}
|
|
141
152
|
}
|
|
142
|
-
/**
|
|
143
|
-
export function
|
|
144
|
-
if (!
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
/** Classify saved original witnesses under the verifier's registration custody. */
|
|
154
|
+
export function tunnelReadiness(state, verifier) {
|
|
155
|
+
if (!state ||
|
|
156
|
+
typeof state !== "object" ||
|
|
157
|
+
Object.hasOwn(state, "provider_pid") ||
|
|
158
|
+
Object.hasOwn(state, "provider_start_token") ||
|
|
159
|
+
typeof state.registration_id !== "string" ||
|
|
160
|
+
!state.registration_id ||
|
|
161
|
+
!Number.isSafeInteger(state.gate_pid) ||
|
|
162
|
+
state.gate_pid < 1 ||
|
|
163
|
+
typeof state.gate_start_token !== "string" ||
|
|
164
|
+
!state.gate_start_token)
|
|
165
|
+
return "unknown";
|
|
166
|
+
const targets = [{ pid: state.gate_pid, token: state.gate_start_token }];
|
|
167
|
+
if (state.provider === "tailscale") {
|
|
168
|
+
if (state.provider_state !== "ready" ||
|
|
169
|
+
state.cloudflared_pid !== undefined ||
|
|
170
|
+
state.cloudflared_start_token !== undefined)
|
|
171
|
+
return "unknown";
|
|
172
|
+
}
|
|
173
|
+
else if (state.provider === "cloudflare") {
|
|
174
|
+
if (!Number.isSafeInteger(state.cloudflared_pid) ||
|
|
175
|
+
state.cloudflared_pid < 1 ||
|
|
176
|
+
typeof state.cloudflared_start_token !== "string" ||
|
|
177
|
+
!state.cloudflared_start_token)
|
|
178
|
+
return "unknown";
|
|
179
|
+
targets.push({ pid: state.cloudflared_pid, token: state.cloudflared_start_token });
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
return "unknown";
|
|
183
|
+
try {
|
|
184
|
+
return verifier.withRegistration(state, () => {
|
|
185
|
+
const observed = targets.map(({ pid, token }) => verifier.observeProcess(pid, token));
|
|
186
|
+
if (observed.every((value) => value === "absent"))
|
|
187
|
+
return "dead";
|
|
188
|
+
if (observed.every((value) => value === "same"))
|
|
189
|
+
return "ready";
|
|
190
|
+
return "unknown";
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return "unknown";
|
|
195
|
+
}
|
|
150
196
|
}
|
|
151
197
|
export function tunnelTargetPort(state) {
|
|
152
198
|
const value = /^[a-z][a-z\d+.-]*:\/\//i.test(state.target)
|
|
@@ -173,10 +219,10 @@ export function tunnelTargetPort(state) {
|
|
|
173
219
|
* link 400s. Matching the vhost too keeps the returned tunnel the one that
|
|
174
220
|
* actually serves the caller's origin.
|
|
175
221
|
*/
|
|
176
|
-
export function findLiveTunnelForOrigin(port, vhost, root
|
|
222
|
+
export function findLiveTunnelForOrigin(port, vhost, root, verifier) {
|
|
177
223
|
return (listStates(root).find((state) => tunnelTargetPort(state) === port &&
|
|
178
224
|
state.vhost === vhost &&
|
|
179
|
-
|
|
225
|
+
tunnelReadiness(state, verifier) === "ready") ?? null);
|
|
180
226
|
}
|
|
181
227
|
/** True when the tunnel's path scope forwards every one of `paths`. */
|
|
182
228
|
export function tunnelServesPaths(state, paths) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harnery",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Multi-agent coordination for AI coding agents - Claude Code, Cursor, and Codex.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryan Kelly",
|
|
@@ -67,6 +67,11 @@
|
|
|
67
67
|
"types": "./dist/core/context/index.d.ts",
|
|
68
68
|
"import": "./dist/core/context/index.js"
|
|
69
69
|
},
|
|
70
|
+
"./core/turn-rollback": {
|
|
71
|
+
"bun": "./src/core/turn-rollback/index.ts",
|
|
72
|
+
"types": "./dist/core/turn-rollback/index.d.ts",
|
|
73
|
+
"import": "./dist/core/turn-rollback/index.js"
|
|
74
|
+
},
|
|
70
75
|
"./core/workflow": {
|
|
71
76
|
"bun": "./src/core/workflow/index.ts",
|
|
72
77
|
"types": "./dist/core/workflow/index.d.ts",
|
package/src/commander.ts
CHANGED
|
@@ -795,6 +795,16 @@ function harneryCommandBundles({
|
|
|
795
795
|
load: async (program) =>
|
|
796
796
|
(await import("./commands/workflow.ts")).registerWorkflowCommand(program, emit),
|
|
797
797
|
},
|
|
798
|
+
lazy(
|
|
799
|
+
"turn-rollback",
|
|
800
|
+
"Capture and review exact turn paths before restoring owned content.",
|
|
801
|
+
async (program) =>
|
|
802
|
+
(await import("./commands/turn-rollback.ts")).registerTurnRollbackCommand(
|
|
803
|
+
program,
|
|
804
|
+
emit,
|
|
805
|
+
context,
|
|
806
|
+
),
|
|
807
|
+
),
|
|
798
808
|
lazy("work", "Track durable objectives across bounded workflow attempts.", async (program) =>
|
|
799
809
|
(await import("./commands/work.ts")).registerWorkCommand(program, emit),
|
|
800
810
|
),
|
package/src/commands/files.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Command } from "commander";
|
|
|
4
4
|
import type { EmitContext, HarneryProgramContext } from "../commander.ts";
|
|
5
5
|
import { resolveCoordRoot } from "../core/agents/coord-client.ts";
|
|
6
6
|
import { DEFAULT_WEB_PORT, resolveWebPort } from "../core/config.ts";
|
|
7
|
+
import { createTunnelReadinessVerifier } from "../core/servers/index.ts";
|
|
7
8
|
import { coordRootId, localCoordRootIdUrl } from "../lib/coord-root-id.ts";
|
|
8
9
|
import {
|
|
9
10
|
encodedRepoPath,
|
|
@@ -14,10 +15,10 @@ import {
|
|
|
14
15
|
} from "../lib/local-file-url.ts";
|
|
15
16
|
import { isPathAllowed } from "../lib/tunnel/path-scope.ts";
|
|
16
17
|
import {
|
|
17
|
-
isTunnelStateLive,
|
|
18
18
|
listStates,
|
|
19
|
-
type
|
|
19
|
+
type TunnelReadinessVerifier,
|
|
20
20
|
type TunnelState,
|
|
21
|
+
tunnelReadiness,
|
|
21
22
|
tunnelTargetPort,
|
|
22
23
|
} from "../lib/tunnel/state.ts";
|
|
23
24
|
|
|
@@ -208,10 +209,10 @@ export function tunnelFileUrl(
|
|
|
208
209
|
local: LocalFileUrl,
|
|
209
210
|
port: number,
|
|
210
211
|
states: TunnelState[],
|
|
211
|
-
|
|
212
|
+
verifier: TunnelReadinessVerifier,
|
|
212
213
|
): string {
|
|
213
214
|
const live = states.filter(
|
|
214
|
-
(state) => tunnelTargetPort(state) === port &&
|
|
215
|
+
(state) => tunnelTargetPort(state) === port && tunnelReadiness(state, verifier) === "ready",
|
|
215
216
|
);
|
|
216
217
|
if (local.mode === "raw") {
|
|
217
218
|
const path = `/${encodedRepoPath(local.relPath)}`;
|
|
@@ -309,7 +310,14 @@ export function registerFilesCommand(
|
|
|
309
310
|
mode: opts.raw ? "raw" : opts.viewer ? "viewer" : "auto",
|
|
310
311
|
});
|
|
311
312
|
if (opts.verify !== false) await verifyDashboardRoot(coordRoot, port);
|
|
312
|
-
const url = opts.tunnel
|
|
313
|
+
const url = opts.tunnel
|
|
314
|
+
? tunnelFileUrl(
|
|
315
|
+
result,
|
|
316
|
+
port,
|
|
317
|
+
listStates(coordRoot),
|
|
318
|
+
createTunnelReadinessVerifier({ coordRoot }),
|
|
319
|
+
)
|
|
320
|
+
: result.url;
|
|
313
321
|
emit.text(`${url}\n`);
|
|
314
322
|
} catch (error) {
|
|
315
323
|
if (error instanceof TunnelLinkError) {
|
package/src/commands/servers.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { hostname } from "node:os";
|
|
3
2
|
import type { Command } from "commander";
|
|
4
3
|
import type { EmitContext, HarneryProgramContext } from "../commander.ts";
|
|
5
4
|
import { resolveCoordRoot } from "../core/agents/coord-client.ts";
|
|
@@ -111,23 +110,13 @@ export function registerServersCommand(
|
|
|
111
110
|
const preview = Boolean(options.type) && !options.yes;
|
|
112
111
|
const results: Array<StopResult | { id: string; would_stop: true }> = [];
|
|
113
112
|
for (const stray of strays) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
label: stray.command,
|
|
122
|
-
pid: stray.pid,
|
|
123
|
-
...(stray.start_token ? { start_token: stray.start_token } : {}),
|
|
124
|
-
host: hostname(),
|
|
125
|
-
started_at: new Date().toISOString(),
|
|
126
|
-
registered_at: new Date().toISOString(),
|
|
127
|
-
},
|
|
128
|
-
{ coordRoot },
|
|
129
|
-
),
|
|
130
|
-
);
|
|
113
|
+
const receipt = adoptServer(stray.pid, {
|
|
114
|
+
coordRoot,
|
|
115
|
+
type: "unregistered",
|
|
116
|
+
owner: "none",
|
|
117
|
+
dependencies: { listServers: () => report },
|
|
118
|
+
});
|
|
119
|
+
results.push(await stopServer(receipt, { coordRoot }));
|
|
131
120
|
}
|
|
132
121
|
for (const view of selected) {
|
|
133
122
|
if (preview && !ids.includes(view.record.id))
|
|
@@ -408,14 +397,14 @@ export function registerServersCommand(
|
|
|
408
397
|
|
|
409
398
|
cmd
|
|
410
399
|
.command("unregister <id>")
|
|
411
|
-
.description("Remove
|
|
400
|
+
.description("Remove the exact observed server registration (optionally require --pid)")
|
|
412
401
|
.option("--pid <pid>", "Only remove when the record names this pid")
|
|
413
402
|
.action((id: string, options: { pid?: string }) => {
|
|
414
403
|
try {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
});
|
|
404
|
+
const coordRoot = root();
|
|
405
|
+
const record = readServer(id, { coordRoot });
|
|
406
|
+
const matchesPid = !options.pid || record?.pid === integer(options.pid, "--pid");
|
|
407
|
+
const removed = !!record && matchesPid && unregisterServer(record, { coordRoot });
|
|
419
408
|
emit.config({ format: "json" });
|
|
420
409
|
emit.data({ id, removed });
|
|
421
410
|
} catch (error) {
|