harnery 0.8.0 → 0.10.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/README.md +2 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +8 -0
- package/dist/commands/agents.d.ts +24 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +244 -13
- package/dist/commands/backup.d.ts +5 -4
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +15 -14
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +27 -1
- package/dist/commands/claude-desktop.d.ts +19 -0
- package/dist/commands/claude-desktop.d.ts.map +1 -0
- package/dist/commands/claude-desktop.js +168 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +149 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +37 -0
- package/dist/commands/eml.d.ts +32 -0
- package/dist/commands/eml.d.ts.map +1 -1
- package/dist/commands/eml.js +16 -3
- package/dist/commands/grep.d.ts +35 -2
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +427 -139
- package/dist/commands/harness.d.ts +11 -0
- package/dist/commands/harness.d.ts.map +1 -0
- package/dist/commands/harness.js +118 -0
- package/dist/commands/init.d.ts +15 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +125 -14
- package/dist/commands/presence.d.ts +9 -4
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +88 -5
- package/dist/commands/relay.d.ts +9 -0
- package/dist/commands/relay.d.ts.map +1 -0
- package/dist/commands/relay.js +143 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +5 -0
- package/dist/commands/workflow.d.ts +4 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +82 -0
- package/dist/core/agents/canonical-emit.d.ts +15 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +41 -3
- package/dist/core/agents/cli.js +14 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +44 -3
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +26 -1
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +26 -1
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +4 -37
- package/dist/core/agents/rules/stop-hook.d.ts +8 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +8 -0
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +16 -43
- package/dist/core/agents/state/heartbeat-projector.d.ts +2 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +24 -4
- package/dist/core/agents/state/heartbeat-writer.d.ts +16 -2
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +24 -7
- package/dist/core/agents/state/names.d.ts +38 -3
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +46 -7
- package/dist/core/agents/state/pidmap.d.ts +5 -1
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +58 -2
- package/dist/core/agents/state/stale-sweep.d.ts +3 -2
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +6 -6
- package/dist/core/config.d.ts +77 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +210 -25
- package/dist/core/context/index.d.ts +144 -0
- package/dist/core/context/index.d.ts.map +1 -0
- package/dist/core/context/index.js +380 -0
- package/dist/core/harnesses/bench.d.ts +29 -0
- package/dist/core/harnesses/bench.d.ts.map +1 -0
- package/dist/core/harnesses/bench.js +151 -0
- package/dist/core/harnesses/index.d.ts +9 -0
- package/dist/core/harnesses/index.d.ts.map +1 -0
- package/dist/core/harnesses/index.js +4 -0
- package/dist/core/harnesses/profiles.d.ts +62 -0
- package/dist/core/harnesses/profiles.d.ts.map +1 -0
- package/dist/core/harnesses/profiles.js +115 -0
- package/dist/core/harnesses/registry.d.ts +15 -0
- package/dist/core/harnesses/registry.d.ts.map +1 -0
- package/dist/core/harnesses/registry.js +113 -0
- package/dist/core/harnesses/types.d.ts +64 -0
- package/dist/core/harnesses/types.d.ts.map +1 -0
- package/dist/core/harnesses/types.js +20 -0
- package/dist/core/hooks/cli.js +309 -66
- package/dist/core/hooks/events/schema.d.ts +42 -1
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.d.ts +7 -0
- package/dist/core/hooks/harness/events.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.js +4 -0
- package/dist/core/hooks/harness/parse.d.ts +1 -1
- package/dist/core/hooks/harness/parse.d.ts.map +1 -1
- package/dist/core/hooks/harness/parse.js +4 -0
- package/dist/core/hooks/resolve/coord-root.d.ts +11 -0
- package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -1
- package/dist/core/hooks/resolve/coord-root.js +28 -6
- package/dist/core/presence/blob.d.ts +40 -0
- package/dist/core/presence/blob.d.ts.map +1 -0
- package/dist/core/presence/blob.js +91 -0
- package/dist/core/presence/git.d.ts +68 -0
- package/dist/core/presence/git.d.ts.map +1 -0
- package/dist/core/presence/git.js +173 -0
- package/dist/core/presence/index.d.ts +74 -0
- package/dist/core/presence/index.d.ts.map +1 -0
- package/dist/core/presence/index.js +224 -0
- package/dist/core/presence/relay-client.d.ts +39 -0
- package/dist/core/presence/relay-client.d.ts.map +1 -0
- package/dist/core/presence/relay-client.js +295 -0
- package/dist/core/presence/relay-protocol.d.ts +94 -0
- package/dist/core/presence/relay-protocol.d.ts.map +1 -0
- package/dist/core/presence/relay-protocol.js +169 -0
- package/dist/core/workflow/billing.d.ts +48 -0
- package/dist/core/workflow/billing.d.ts.map +1 -0
- package/dist/core/workflow/billing.js +108 -0
- package/dist/core/workflow/child-env.d.ts +30 -0
- package/dist/core/workflow/child-env.d.ts.map +1 -0
- package/dist/core/workflow/child-env.js +43 -0
- package/dist/core/workflow/engine.d.ts +21 -0
- package/dist/core/workflow/engine.d.ts.map +1 -0
- package/dist/core/workflow/engine.js +349 -0
- package/dist/core/workflow/harnesses.d.ts +17 -0
- package/dist/core/workflow/harnesses.d.ts.map +1 -0
- package/dist/core/workflow/harnesses.js +21 -0
- package/dist/core/workflow/spawn-claude.d.ts +25 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -0
- package/dist/core/workflow/spawn-claude.js +103 -0
- package/dist/core/workflow/spawn-codex.d.ts +22 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -0
- package/dist/core/workflow/spawn-codex.js +88 -0
- package/dist/core/workflow/spawn-cursor.d.ts +29 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -0
- package/dist/core/workflow/spawn-cursor.js +91 -0
- package/dist/core/workflow/types.d.ts +152 -0
- package/dist/core/workflow/types.d.ts.map +1 -0
- package/dist/core/workflow/types.js +9 -0
- package/dist/core/workflow/validate.d.ts +15 -0
- package/dist/core/workflow/validate.d.ts.map +1 -0
- package/dist/core/workflow/validate.js +70 -0
- package/dist/lib/browser/client.d.ts +16 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +22 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -0
- package/dist/lib/browser/launch-args.d.ts +21 -0
- package/dist/lib/browser/launch-args.d.ts.map +1 -0
- package/dist/lib/browser/launch-args.js +32 -0
- package/dist/lib/claude-desktop.d.ts +105 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -0
- package/dist/lib/claude-desktop.js +217 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +5 -2
- package/dist/lib/identities/assume.d.ts +51 -0
- package/dist/lib/identities/assume.d.ts.map +1 -0
- package/dist/lib/identities/assume.js +274 -0
- package/dist/lib/identities/index.d.ts +7 -7
- package/dist/lib/identities/index.d.ts.map +1 -1
- package/dist/lib/identities/index.js +24 -24
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +15 -2
- package/package.json +12 -1
- package/schemas/config.schema.json +88 -19
- package/src/commander.ts +8 -0
- package/src/commands/agents.ts +280 -14
- package/src/commands/backup.ts +17 -16
- package/src/commands/browse.ts +32 -0
- package/src/commands/claude-desktop.ts +215 -0
- package/src/commands/context.ts +174 -1
- package/src/commands/doctor.ts +44 -0
- package/src/commands/eml.ts +22 -4
- package/src/commands/grep.ts +535 -142
- package/src/commands/harness.ts +147 -0
- package/src/commands/init.ts +138 -17
- package/src/commands/presence.ts +111 -5
- package/src/commands/relay.ts +160 -0
- package/src/commands/sync.ts +4 -0
- package/src/commands/workflow.ts +123 -0
- package/src/core/agents/canonical-emit.ts +41 -3
- package/src/core/agents/cli.ts +15 -6
- package/src/core/agents/render/prompt-context.ts +48 -3
- package/src/core/agents/render/session-context.ts +27 -1
- package/src/core/agents/rules/claim-conflict.ts +26 -1
- package/src/core/agents/rules/commit-conflict.ts +4 -33
- package/src/core/agents/rules/stop-hook.ts +17 -0
- package/src/core/agents/session-events.ts +15 -39
- package/src/core/agents/state/heartbeat-projector.ts +23 -2
- package/src/core/agents/state/heartbeat-writer.ts +33 -7
- package/src/core/agents/state/names.ts +68 -9
- package/src/core/agents/state/pidmap.ts +59 -2
- package/src/core/agents/state/stale-sweep.ts +6 -11
- package/src/core/config.ts +271 -24
- package/src/core/context/index.ts +575 -0
- package/src/core/harnesses/bench.ts +214 -0
- package/src/core/harnesses/index.ts +31 -0
- package/src/core/harnesses/profiles.ts +141 -0
- package/src/core/harnesses/registry.ts +140 -0
- package/src/core/harnesses/types.ts +90 -0
- package/src/core/hooks/cli.ts +361 -65
- package/src/core/hooks/events/schema.ts +72 -0
- package/src/core/hooks/harness/events.ts +11 -0
- package/src/core/hooks/harness/parse.ts +7 -1
- package/src/core/hooks/resolve/coord-root.ts +28 -6
- package/src/core/presence/blob.ts +125 -0
- package/src/core/presence/git.ts +191 -0
- package/src/core/presence/index.ts +274 -0
- package/src/core/presence/relay-client.ts +322 -0
- package/src/core/presence/relay-protocol.ts +246 -0
- package/src/core/workflow/billing.ts +152 -0
- package/src/core/workflow/child-env.ts +47 -0
- package/src/core/workflow/engine.ts +403 -0
- package/src/core/workflow/harnesses.ts +33 -0
- package/src/core/workflow/spawn-claude.ts +118 -0
- package/src/core/workflow/spawn-codex.ts +93 -0
- package/src/core/workflow/spawn-cursor.ts +108 -0
- package/src/core/workflow/types.ts +160 -0
- package/src/core/workflow/validate.ts +75 -0
- package/src/lib/browser/client.ts +37 -0
- package/src/lib/browser/index.ts +1 -0
- package/src/lib/browser/launch-args.ts +33 -0
- package/src/lib/claude-desktop.ts +293 -0
- package/src/lib/docs-lint.ts +5 -2
- package/src/lib/identities/assume.ts +363 -0
- package/src/lib/identities/index.ts +28 -24
- package/src/lib/instructions/templates.ts +16 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pid-map writer.
|
|
2
|
+
* Pid-map writer + liveness helpers.
|
|
3
3
|
*
|
|
4
4
|
* Per-harness pid-map at `.harnery/pid-map/<pid>` containing
|
|
5
5
|
* `<instance_id>\t<platform>`. `harn agents whoami` walks ppid up 20 hops looking
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Atomic temp+rename. Idempotent: re-writing the same row is a no-op.
|
|
9
9
|
*/
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
function atomicWrite(path, content) {
|
|
13
13
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -30,3 +30,59 @@ export function writePidmapRow(coordRoot, pid, instanceId, platform) {
|
|
|
30
30
|
}
|
|
31
31
|
atomicWrite(path, row);
|
|
32
32
|
}
|
|
33
|
+
/** True when any pid-map row for `instanceId` still belongs to a live process. */
|
|
34
|
+
export function instanceHasLivePid(coordRoot, instanceId) {
|
|
35
|
+
const dir = join(coordRoot, ".harnery", "pid-map");
|
|
36
|
+
if (!existsSync(dir))
|
|
37
|
+
return false;
|
|
38
|
+
for (const f of readdirSync(dir)) {
|
|
39
|
+
let row = "";
|
|
40
|
+
try {
|
|
41
|
+
row = readFileSync(join(dir, f), "utf8").trim();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const owner = row.split("\t")[0]?.trim() ?? "";
|
|
47
|
+
if (owner !== instanceId)
|
|
48
|
+
continue;
|
|
49
|
+
const pid = Number.parseInt(f, 10);
|
|
50
|
+
if (!Number.isFinite(pid))
|
|
51
|
+
continue;
|
|
52
|
+
try {
|
|
53
|
+
process.kill(pid, 0); // signal 0 = liveness probe
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// ESRCH (no such process): pid-map entry is stale, keep scanning
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/** Drop every pid-map row owned by `instanceId`. Returns how many files were removed. */
|
|
63
|
+
export function removePidmapRowsForInstance(coordRoot, instanceId) {
|
|
64
|
+
const dir = join(coordRoot, ".harnery", "pid-map");
|
|
65
|
+
if (!existsSync(dir))
|
|
66
|
+
return 0;
|
|
67
|
+
let removed = 0;
|
|
68
|
+
for (const f of readdirSync(dir)) {
|
|
69
|
+
let row = "";
|
|
70
|
+
try {
|
|
71
|
+
row = readFileSync(join(dir, f), "utf8").trim();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const owner = row.split("\t")[0]?.trim() ?? "";
|
|
77
|
+
if (owner !== instanceId)
|
|
78
|
+
continue;
|
|
79
|
+
try {
|
|
80
|
+
unlinkSync(join(dir, f));
|
|
81
|
+
removed += 1;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
/* best-effort cleanup */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return removed;
|
|
88
|
+
}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* Fires at session.start to clean up crashed-peer detritus before the new
|
|
6
6
|
* session's UX layer reads peer state.
|
|
7
7
|
*
|
|
8
|
-
* Freshness threshold defaults to 600s; configurable via
|
|
9
|
-
* HARNERY_AGENT_COORD_FRESHNESS env var.
|
|
8
|
+
* Freshness threshold defaults to 600s; configurable via the
|
|
9
|
+
* HARNERY_AGENT_COORD_FRESHNESS env var or `.harnery/config.jsonc`
|
|
10
|
+
* `coord.freshness_seconds` (see `coordFreshnessSeconds`).
|
|
10
11
|
*/
|
|
11
12
|
export declare function staleSweep(coordRoot: string): {
|
|
12
13
|
heartbeatsRemoved: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stale-sweep.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/stale-sweep.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"stale-sweep.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/stale-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA+CH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG;IAC7C,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CA2HA"}
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* Fires at session.start to clean up crashed-peer detritus before the new
|
|
6
6
|
* session's UX layer reads peer state.
|
|
7
7
|
*
|
|
8
|
-
* Freshness threshold defaults to 600s; configurable via
|
|
9
|
-
* HARNERY_AGENT_COORD_FRESHNESS env var.
|
|
8
|
+
* Freshness threshold defaults to 600s; configurable via the
|
|
9
|
+
* HARNERY_AGENT_COORD_FRESHNESS env var or `.harnery/config.jsonc`
|
|
10
|
+
* `coord.freshness_seconds` (see `coordFreshnessSeconds`).
|
|
10
11
|
*/
|
|
11
12
|
import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
12
13
|
import { join } from "node:path";
|
|
13
|
-
import {
|
|
14
|
+
import { coordFreshnessSeconds } from "../../config.js";
|
|
14
15
|
import { emit } from "../events/emit.js";
|
|
15
|
-
const DEFAULT_FRESHNESS_SECS = 600;
|
|
16
16
|
/** platform → harness, for the swept-event envelope (mirrors heartbeat-writer's harnessOf). */
|
|
17
17
|
function harnessFromPlatform(platform) {
|
|
18
18
|
if (platform === "cursor")
|
|
@@ -51,9 +51,9 @@ export function staleSweep(coordRoot) {
|
|
|
51
51
|
const heartbeatsRemoved = [];
|
|
52
52
|
let pidmapsRemoved = 0;
|
|
53
53
|
let peerHashesRemoved = 0;
|
|
54
|
-
const freshness =
|
|
54
|
+
const freshness = coordFreshnessSeconds(coordRoot);
|
|
55
55
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
56
|
-
const cutoff = nowSec -
|
|
56
|
+
const cutoff = nowSec - freshness;
|
|
57
57
|
// 1. Prune stale heartbeats from the canonical `.harnery/active/` dir.
|
|
58
58
|
//
|
|
59
59
|
// Two deletion regimes, deliberately asymmetric:
|
package/dist/core/config.d.ts
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Harnery config reader: the settings the coord/hook layer consults when it
|
|
3
|
+
* can't see the consumer CLI's own process.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* (`agent-hook`, `agent-coord`) and the web UI run as harnery itself, so they
|
|
9
|
-
* have no other way to learn the consumer's bin name; `harn init` stamps it here
|
|
10
|
-
* for them to read back. The `files` deny/override section is parsed separately
|
|
11
|
-
* by `web/lib/files.ts`.
|
|
5
|
+
* Two layers, project-over-user (project wins field-by-field):
|
|
6
|
+
* 1. `~/.config/harnery/config.jsonc` — user-global base (optional)
|
|
7
|
+
* 2. `<project-root>/.harnery/config.jsonc` — project override (authoritative)
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Fields owned here: `binName` (host CLI name for agent-facing strings),
|
|
10
|
+
* `hooksSetupHint`, `tools`, `workflow`, `skills`, `presence`, plus the tunable
|
|
11
|
+
* `coord` (heartbeat freshness), `backup` (restic repo/password/prune policy),
|
|
12
|
+
* and `sync` (rclone remote/prefix) sections. The `files` deny/override section
|
|
13
|
+
* is parsed separately by `web/lib/files.ts`.
|
|
14
|
+
*
|
|
15
|
+
* Env vars and CLI flags override any config value per invocation (each accessor
|
|
16
|
+
* documents its own precedence). Dependency-free (no jsonc npm dep) so it runs on
|
|
17
|
+
* both Bun and Node, and so the ADR-009 vendored copies stay portable.
|
|
15
18
|
*/
|
|
16
19
|
/** The standalone CLI's bin name: the resolution floor when nothing else is set. */
|
|
17
20
|
export declare const DEFAULT_BIN_NAME = "harn";
|
|
21
|
+
/** Heartbeat-freshness default (seconds): the sweep window when nothing overrides it. */
|
|
22
|
+
export declare const DEFAULT_FRESHNESS_SECS = 600;
|
|
18
23
|
/** Strip `//` and `/* */` comments from JSONC, ignoring comment-like runs inside strings. */
|
|
19
24
|
export declare function stripJsonComments(input: string): string;
|
|
20
25
|
/**
|
|
@@ -52,4 +57,65 @@ export declare function resolveHooksSetupHint(coordRoot?: string | null): string
|
|
|
52
57
|
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
53
58
|
*/
|
|
54
59
|
export declare function ripgrepAutoInstall(coordRoot?: string | null): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Whether this repo pins workflow runs to subscription billing:
|
|
62
|
+
* `.harnery/config.jsonc` `{ "workflow": { "subscriptionOnly": true } }`.
|
|
63
|
+
* The `workflow run --subscription-only` flag turns it on per invocation;
|
|
64
|
+
* `HARNERY_WORKFLOW_SUBSCRIPTION_ONLY=1|0` overrides per process (the `0`
|
|
65
|
+
* escape hatch exists for a key-only CI job inside a pinned repo).
|
|
66
|
+
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
67
|
+
*/
|
|
68
|
+
export declare function workflowSubscriptionOnly(coordRoot?: string | null): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Whether cross-machine presence (ADR 0016) is enabled for this repo.
|
|
71
|
+
* Default ON — the transport itself additionally gates on an origin remote
|
|
72
|
+
* existing and fails silent everywhere. Opt out via
|
|
73
|
+
* `.harnery/config.jsonc` `{ "presence": { "enabled": false } }`;
|
|
74
|
+
* `HARNERY_PRESENCE=1|0` overrides per process.
|
|
75
|
+
*/
|
|
76
|
+
export declare function presenceEnabled(coordRoot?: string | null): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The presence relay URL for this repo, or null when the relay transport is
|
|
79
|
+
* not configured (git-refs only). `HARNERY_PRESENCE_RELAY` overrides per
|
|
80
|
+
* process (empty string or "0" disables). Requires `presenceEnabled()` to be
|
|
81
|
+
* true — a disabled presence section disables the relay too.
|
|
82
|
+
*/
|
|
83
|
+
export declare function presenceRelayUrl(coordRoot?: string | null): string | null;
|
|
84
|
+
/**
|
|
85
|
+
* The heartbeat-freshness window (seconds): the age above which the sweeper
|
|
86
|
+
* prunes an agent, and the cutoff the `agents` surface uses to fold stale peers.
|
|
87
|
+
* Precedence:
|
|
88
|
+
* 1. `HARNERY_AGENT_COORD_FRESHNESS` env (canonical), or `HARNERY_AGENT_FRESHNESS` (legacy alias)
|
|
89
|
+
* 2. `.harnery/config.jsonc` `coord.freshness_seconds`
|
|
90
|
+
* 3. `600` (10 minutes)
|
|
91
|
+
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
92
|
+
*/
|
|
93
|
+
export declare function coordFreshnessSeconds(coordRoot?: string | null): number;
|
|
94
|
+
/** Resolved `harn backup` defaults (restic repo/password + prune policy). */
|
|
95
|
+
export interface BackupConfig {
|
|
96
|
+
repo: string;
|
|
97
|
+
passwordFile: string;
|
|
98
|
+
keepDaily: number;
|
|
99
|
+
keepWeekly: number;
|
|
100
|
+
keepMonthly: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* `harn backup` (restic) defaults. Per field, precedence is env → config → built-in:
|
|
104
|
+
* repo: `HARNERY_RESTIC_REPO` → `backup.repo` → `~/.cache/harnery/restic-repo`
|
|
105
|
+
* passwordFile: `HARNERY_RESTIC_PASSWORD_FILE` → `backup.password_file` → `~/.config/harnery/restic-password`
|
|
106
|
+
* keepDaily/Weekly/Monthly: `backup.keep_*` → 7 / 4 / 6
|
|
107
|
+
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
108
|
+
*/
|
|
109
|
+
export declare function backupConfig(coordRoot?: string | null): BackupConfig;
|
|
110
|
+
/**
|
|
111
|
+
* `harn sync` (rclone) remote/prefix from `.harnery/config.jsonc` `sync`, or null
|
|
112
|
+
* when unset. This is the config-file layer only; `harn sync` consults env
|
|
113
|
+
* (`HARNERY_SYNC_REMOTE`/`_PREFIX`) first and the `~/.config/harnery/sync.json`
|
|
114
|
+
* file (written by `harn sync init`) as a lower-precedence fallback.
|
|
115
|
+
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
116
|
+
*/
|
|
117
|
+
export declare function syncJsoncConfig(coordRoot?: string | null): {
|
|
118
|
+
remote: string;
|
|
119
|
+
prefix: string;
|
|
120
|
+
} | null;
|
|
55
121
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAQH,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAiE1C,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqCvD;AAwFD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAShE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAK9E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAOrE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAO3E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAOlE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAWzE;AAQD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CASvE;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,CAmBpE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAS3C"}
|
package/dist/core/config.js
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Harnery config reader: the settings the coord/hook layer consults when it
|
|
3
|
+
* can't see the consumer CLI's own process.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* (`agent-hook`, `agent-coord`) and the web UI run as harnery itself, so they
|
|
9
|
-
* have no other way to learn the consumer's bin name; `harn init` stamps it here
|
|
10
|
-
* for them to read back. The `files` deny/override section is parsed separately
|
|
11
|
-
* by `web/lib/files.ts`.
|
|
5
|
+
* Two layers, project-over-user (project wins field-by-field):
|
|
6
|
+
* 1. `~/.config/harnery/config.jsonc` — user-global base (optional)
|
|
7
|
+
* 2. `<project-root>/.harnery/config.jsonc` — project override (authoritative)
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Fields owned here: `binName` (host CLI name for agent-facing strings),
|
|
10
|
+
* `hooksSetupHint`, `tools`, `workflow`, `skills`, `presence`, plus the tunable
|
|
11
|
+
* `coord` (heartbeat freshness), `backup` (restic repo/password/prune policy),
|
|
12
|
+
* and `sync` (rclone remote/prefix) sections. The `files` deny/override section
|
|
13
|
+
* is parsed separately by `web/lib/files.ts`.
|
|
14
|
+
*
|
|
15
|
+
* Env vars and CLI flags override any config value per invocation (each accessor
|
|
16
|
+
* documents its own precedence). Dependency-free (no jsonc npm dep) so it runs on
|
|
17
|
+
* both Bun and Node, and so the ADR-009 vendored copies stay portable.
|
|
15
18
|
*/
|
|
16
19
|
import { readFileSync, statSync } from "node:fs";
|
|
20
|
+
import { homedir } from "node:os";
|
|
17
21
|
import { join } from "node:path";
|
|
18
22
|
import { coordEnv } from "../lib/env.js";
|
|
19
23
|
import { findCoordRoot } from "./hooks/resolve/coord-root.js";
|
|
20
24
|
/** The standalone CLI's bin name: the resolution floor when nothing else is set. */
|
|
21
25
|
export const DEFAULT_BIN_NAME = "harn";
|
|
26
|
+
/** Heartbeat-freshness default (seconds): the sweep window when nothing overrides it. */
|
|
27
|
+
export const DEFAULT_FRESHNESS_SECS = 600;
|
|
22
28
|
/** Strip `//` and `/* */` comments from JSONC, ignoring comment-like runs inside strings. */
|
|
23
29
|
export function stripJsonComments(input) {
|
|
24
30
|
let out = "";
|
|
@@ -61,31 +67,85 @@ export function stripJsonComments(input) {
|
|
|
61
67
|
}
|
|
62
68
|
return out;
|
|
63
69
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The user-global config file, the lower-precedence base under the project file.
|
|
72
|
+
* Honors `XDG_CONFIG_HOME` (falling back to `~/.config`) per the XDG base-dir spec.
|
|
73
|
+
*/
|
|
74
|
+
function userConfigPath() {
|
|
75
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
76
|
+
const base = xdg?.trim() ? xdg : join(homedir(), ".config");
|
|
77
|
+
return join(base, "harnery", "config.jsonc");
|
|
78
|
+
}
|
|
79
|
+
/** File mtime in ms, or -1 when the file can't be stat'd (missing). */
|
|
80
|
+
function statMtime(p) {
|
|
69
81
|
try {
|
|
70
|
-
|
|
82
|
+
return statSync(p).mtimeMs;
|
|
71
83
|
}
|
|
72
84
|
catch {
|
|
73
|
-
|
|
85
|
+
return -1;
|
|
74
86
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
}
|
|
88
|
+
/** Parse one JSONC config file to an object; missing/unparseable → `{}`. */
|
|
89
|
+
function parseConfigFile(p) {
|
|
78
90
|
try {
|
|
79
91
|
const parsed = JSON.parse(stripJsonComments(readFileSync(p, "utf8")));
|
|
80
92
|
if (parsed && typeof parsed === "object")
|
|
81
|
-
|
|
93
|
+
return parsed;
|
|
82
94
|
}
|
|
83
95
|
catch {
|
|
84
|
-
/* missing or unparseable → defaults (
|
|
96
|
+
/* missing or unparseable → defaults (files-section resolver fails loud; the rest is non-critical) */
|
|
97
|
+
}
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
function isPlainObject(v) {
|
|
101
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Deep-merge `override` onto `base` (override wins), recursing into plain
|
|
105
|
+
* objects only so a project that sets just `web.port` doesn't wipe a
|
|
106
|
+
* user-global `web.bind`. Arrays + scalars replace wholesale.
|
|
107
|
+
*/
|
|
108
|
+
function mergeConfig(base, override) {
|
|
109
|
+
const out = { ...base };
|
|
110
|
+
for (const [k, v] of Object.entries(override)) {
|
|
111
|
+
const prev = out[k];
|
|
112
|
+
out[k] = isPlainObject(prev) && isPlainObject(v) ? mergeConfig(prev, v) : v;
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
// mtime-keyed per-process cache (both layers): a stat is cheap, a parse on every render isn't.
|
|
117
|
+
let cache = null;
|
|
118
|
+
/**
|
|
119
|
+
* The effective config for `root`: user-global (`~/.config/harnery/config.jsonc`)
|
|
120
|
+
* as the base, the project file (`<root>/.harnery/config.jsonc`) merged on top.
|
|
121
|
+
* Project values win field-by-field.
|
|
122
|
+
*/
|
|
123
|
+
function readConfig(root) {
|
|
124
|
+
const projPath = join(root, ".harnery", "config.jsonc");
|
|
125
|
+
const userPath = userConfigPath();
|
|
126
|
+
const projMtime = statMtime(projPath);
|
|
127
|
+
const userMtime = statMtime(userPath);
|
|
128
|
+
if (cache &&
|
|
129
|
+
cache.root === root &&
|
|
130
|
+
cache.projMtime === projMtime &&
|
|
131
|
+
cache.userMtime === userMtime) {
|
|
132
|
+
return cache.cfg;
|
|
85
133
|
}
|
|
86
|
-
|
|
134
|
+
const user = userMtime === -1 ? {} : parseConfigFile(userPath);
|
|
135
|
+
const project = projMtime === -1 ? {} : parseConfigFile(projPath);
|
|
136
|
+
const cfg = mergeConfig(user, project);
|
|
137
|
+
cache = { root, projMtime, userMtime, cfg };
|
|
87
138
|
return cfg;
|
|
88
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* The PROJECT config file only — no user-global merge. `pinnedBinName` uses this
|
|
142
|
+
* so a user-global `binName` can never masquerade as a deliberate project pin
|
|
143
|
+
* (the pin guards committed, public surfaces; see `pinnedBinName`).
|
|
144
|
+
*/
|
|
145
|
+
function readProjectConfig(root) {
|
|
146
|
+
const p = join(root, ".harnery", "config.jsonc");
|
|
147
|
+
return statMtime(p) === -1 ? {} : parseConfigFile(p);
|
|
148
|
+
}
|
|
89
149
|
/**
|
|
90
150
|
* Resolve the host CLI's bin name for user-facing strings. Precedence:
|
|
91
151
|
* 1. `HARNERY_BIN` env (explicit per-process override)
|
|
@@ -116,7 +176,7 @@ export function resolveBinName(coordRoot) {
|
|
|
116
176
|
* CLI would otherwise stamp the host's name into public, committed surfaces).
|
|
117
177
|
*/
|
|
118
178
|
export function pinnedBinName(projectRoot) {
|
|
119
|
-
const binName =
|
|
179
|
+
const binName = readProjectConfig(projectRoot).binName;
|
|
120
180
|
return typeof binName === "string" && binName.trim() ? binName.trim() : null;
|
|
121
181
|
}
|
|
122
182
|
/**
|
|
@@ -151,3 +211,128 @@ export function ripgrepAutoInstall(coordRoot) {
|
|
|
151
211
|
return false;
|
|
152
212
|
return readConfig(root).tools?.ripgrep?.autoInstall === true;
|
|
153
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Whether this repo pins workflow runs to subscription billing:
|
|
216
|
+
* `.harnery/config.jsonc` `{ "workflow": { "subscriptionOnly": true } }`.
|
|
217
|
+
* The `workflow run --subscription-only` flag turns it on per invocation;
|
|
218
|
+
* `HARNERY_WORKFLOW_SUBSCRIPTION_ONLY=1|0` overrides per process (the `0`
|
|
219
|
+
* escape hatch exists for a key-only CI job inside a pinned repo).
|
|
220
|
+
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
221
|
+
*/
|
|
222
|
+
export function workflowSubscriptionOnly(coordRoot) {
|
|
223
|
+
const env = coordEnv("WORKFLOW_SUBSCRIPTION_ONLY");
|
|
224
|
+
if (env === "1")
|
|
225
|
+
return true;
|
|
226
|
+
if (env === "0")
|
|
227
|
+
return false;
|
|
228
|
+
const root = coordRoot ?? findCoordRoot();
|
|
229
|
+
if (!root)
|
|
230
|
+
return false;
|
|
231
|
+
return readConfig(root).workflow?.subscriptionOnly === true;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Whether cross-machine presence (ADR 0016) is enabled for this repo.
|
|
235
|
+
* Default ON — the transport itself additionally gates on an origin remote
|
|
236
|
+
* existing and fails silent everywhere. Opt out via
|
|
237
|
+
* `.harnery/config.jsonc` `{ "presence": { "enabled": false } }`;
|
|
238
|
+
* `HARNERY_PRESENCE=1|0` overrides per process.
|
|
239
|
+
*/
|
|
240
|
+
export function presenceEnabled(coordRoot) {
|
|
241
|
+
const env = coordEnv("PRESENCE");
|
|
242
|
+
if (env === "1")
|
|
243
|
+
return true;
|
|
244
|
+
if (env === "0")
|
|
245
|
+
return false;
|
|
246
|
+
const root = coordRoot ?? findCoordRoot();
|
|
247
|
+
if (!root)
|
|
248
|
+
return false;
|
|
249
|
+
return readConfig(root).presence?.enabled !== false;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The presence relay URL for this repo, or null when the relay transport is
|
|
253
|
+
* not configured (git-refs only). `HARNERY_PRESENCE_RELAY` overrides per
|
|
254
|
+
* process (empty string or "0" disables). Requires `presenceEnabled()` to be
|
|
255
|
+
* true — a disabled presence section disables the relay too.
|
|
256
|
+
*/
|
|
257
|
+
export function presenceRelayUrl(coordRoot) {
|
|
258
|
+
const root = coordRoot ?? findCoordRoot();
|
|
259
|
+
if (!presenceEnabled(root))
|
|
260
|
+
return null;
|
|
261
|
+
const env = coordEnv("PRESENCE_RELAY");
|
|
262
|
+
if (env !== undefined && env !== null) {
|
|
263
|
+
const t = env.trim();
|
|
264
|
+
return t && t !== "0" ? t : null;
|
|
265
|
+
}
|
|
266
|
+
if (!root)
|
|
267
|
+
return null;
|
|
268
|
+
const relay = readConfig(root).presence?.relay;
|
|
269
|
+
return typeof relay === "string" && relay.trim() ? relay.trim() : null;
|
|
270
|
+
}
|
|
271
|
+
/** A positive integer from `v`, else `fallback`. Floors non-integer numbers. */
|
|
272
|
+
function posIntOr(v, fallback) {
|
|
273
|
+
const n = typeof v === "number" ? v : Number.NaN;
|
|
274
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The heartbeat-freshness window (seconds): the age above which the sweeper
|
|
278
|
+
* prunes an agent, and the cutoff the `agents` surface uses to fold stale peers.
|
|
279
|
+
* Precedence:
|
|
280
|
+
* 1. `HARNERY_AGENT_COORD_FRESHNESS` env (canonical), or `HARNERY_AGENT_FRESHNESS` (legacy alias)
|
|
281
|
+
* 2. `.harnery/config.jsonc` `coord.freshness_seconds`
|
|
282
|
+
* 3. `600` (10 minutes)
|
|
283
|
+
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
284
|
+
*/
|
|
285
|
+
export function coordFreshnessSeconds(coordRoot) {
|
|
286
|
+
const env = coordEnv("AGENT_COORD_FRESHNESS") ?? coordEnv("AGENT_FRESHNESS");
|
|
287
|
+
if (env !== undefined) {
|
|
288
|
+
const n = Number.parseInt(env, 10);
|
|
289
|
+
if (Number.isFinite(n) && n > 0)
|
|
290
|
+
return n;
|
|
291
|
+
}
|
|
292
|
+
const root = coordRoot ?? findCoordRoot();
|
|
293
|
+
if (root)
|
|
294
|
+
return posIntOr(readConfig(root).coord?.freshness_seconds, DEFAULT_FRESHNESS_SECS);
|
|
295
|
+
return DEFAULT_FRESHNESS_SECS;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* `harn backup` (restic) defaults. Per field, precedence is env → config → built-in:
|
|
299
|
+
* repo: `HARNERY_RESTIC_REPO` → `backup.repo` → `~/.cache/harnery/restic-repo`
|
|
300
|
+
* passwordFile: `HARNERY_RESTIC_PASSWORD_FILE` → `backup.password_file` → `~/.config/harnery/restic-password`
|
|
301
|
+
* keepDaily/Weekly/Monthly: `backup.keep_*` → 7 / 4 / 6
|
|
302
|
+
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
303
|
+
*/
|
|
304
|
+
export function backupConfig(coordRoot) {
|
|
305
|
+
const home = homedir();
|
|
306
|
+
const root = coordRoot ?? findCoordRoot();
|
|
307
|
+
const b = root ? (readConfig(root).backup ?? {}) : {};
|
|
308
|
+
const cfgStr = (v) => typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
309
|
+
const repo = coordEnv("RESTIC_REPO") ?? cfgStr(b.repo) ?? join(home, ".cache", "harnery", "restic-repo");
|
|
310
|
+
const passwordFile = coordEnv("RESTIC_PASSWORD_FILE") ??
|
|
311
|
+
cfgStr(b.password_file) ??
|
|
312
|
+
join(home, ".config", "harnery", "restic-password");
|
|
313
|
+
return {
|
|
314
|
+
repo,
|
|
315
|
+
passwordFile,
|
|
316
|
+
keepDaily: posIntOr(b.keep_daily, 7),
|
|
317
|
+
keepWeekly: posIntOr(b.keep_weekly, 4),
|
|
318
|
+
keepMonthly: posIntOr(b.keep_monthly, 6),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* `harn sync` (rclone) remote/prefix from `.harnery/config.jsonc` `sync`, or null
|
|
323
|
+
* when unset. This is the config-file layer only; `harn sync` consults env
|
|
324
|
+
* (`HARNERY_SYNC_REMOTE`/`_PREFIX`) first and the `~/.config/harnery/sync.json`
|
|
325
|
+
* file (written by `harn sync init`) as a lower-precedence fallback.
|
|
326
|
+
* `coordRoot` is resolved via `findCoordRoot()` when not passed.
|
|
327
|
+
*/
|
|
328
|
+
export function syncJsoncConfig(coordRoot) {
|
|
329
|
+
const root = coordRoot ?? findCoordRoot();
|
|
330
|
+
if (!root)
|
|
331
|
+
return null;
|
|
332
|
+
const s = readConfig(root).sync ?? {};
|
|
333
|
+
if (typeof s.remote === "string" && s.remote.trim()) {
|
|
334
|
+
const prefix = typeof s.prefix === "string" && s.prefix.trim() ? s.prefix.trim() : "harnery";
|
|
335
|
+
return { remote: s.remote.trim(), prefix };
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context continuity: durable, structured state around native harness
|
|
3
|
+
* compaction. Harnery does not decide when a harness compacts. It records
|
|
4
|
+
* context telemetry when the harness exposes it, checkpoints external work
|
|
5
|
+
* state before compaction, and renders a verified recovery briefing after it.
|
|
6
|
+
*/
|
|
7
|
+
import type { Harness } from "../hooks/events/schema.js";
|
|
8
|
+
export declare const CONTEXT_SCHEMA_VERSION: 1;
|
|
9
|
+
export declare const MAX_CAPSULE_BYTES: number;
|
|
10
|
+
export type ContextSampleSource = "hook" | "native_event" | "result" | "transcript" | "estimate";
|
|
11
|
+
export type ContextSampleConfidence = "exact" | "reported" | "estimated";
|
|
12
|
+
export type ContextPhase = "observing" | "checkpointed" | "recovered" | "degraded";
|
|
13
|
+
export type CheckpointReason = "manual" | "pressure" | "pre_compact" | "session_end";
|
|
14
|
+
export interface ContextSample {
|
|
15
|
+
session_id: string;
|
|
16
|
+
harness: Harness;
|
|
17
|
+
model?: string;
|
|
18
|
+
used_tokens?: number;
|
|
19
|
+
window_tokens?: number;
|
|
20
|
+
used_percent?: number;
|
|
21
|
+
source: ContextSampleSource;
|
|
22
|
+
confidence: ContextSampleConfidence;
|
|
23
|
+
observed_at: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ContextContinuityState {
|
|
26
|
+
schema_version: typeof CONTEXT_SCHEMA_VERSION;
|
|
27
|
+
session_id: string;
|
|
28
|
+
instance_id: string;
|
|
29
|
+
phase: ContextPhase;
|
|
30
|
+
generation: number;
|
|
31
|
+
latest_capsule?: string;
|
|
32
|
+
latest_context?: ContextSample;
|
|
33
|
+
updated_at: string;
|
|
34
|
+
recovered_at?: string;
|
|
35
|
+
compaction_completed_at?: string;
|
|
36
|
+
degraded_reason?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface RepoSnapshot {
|
|
39
|
+
cwd: string;
|
|
40
|
+
root?: string;
|
|
41
|
+
branch?: string;
|
|
42
|
+
head?: string;
|
|
43
|
+
dirty_paths: string[];
|
|
44
|
+
dirty_paths_truncated?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface ContinuityCapsule {
|
|
47
|
+
schema_version: typeof CONTEXT_SCHEMA_VERSION;
|
|
48
|
+
capsule_id: string;
|
|
49
|
+
generation: number;
|
|
50
|
+
created_at: string;
|
|
51
|
+
reason: CheckpointReason;
|
|
52
|
+
session: {
|
|
53
|
+
session_id: string;
|
|
54
|
+
instance_id: string;
|
|
55
|
+
harness: Harness;
|
|
56
|
+
model?: string;
|
|
57
|
+
};
|
|
58
|
+
context?: ContextSample;
|
|
59
|
+
work: {
|
|
60
|
+
task?: string;
|
|
61
|
+
turn_summary?: string;
|
|
62
|
+
continuation_note?: string;
|
|
63
|
+
files_held: string[];
|
|
64
|
+
files_held_truncated?: boolean;
|
|
65
|
+
last_tool?: string;
|
|
66
|
+
last_tool_target?: string;
|
|
67
|
+
};
|
|
68
|
+
repo: RepoSnapshot;
|
|
69
|
+
}
|
|
70
|
+
export interface ExtractContextSampleOptions {
|
|
71
|
+
sessionId: string;
|
|
72
|
+
harness: Harness;
|
|
73
|
+
model?: string;
|
|
74
|
+
source?: ContextSampleSource;
|
|
75
|
+
confidence?: ContextSampleConfidence;
|
|
76
|
+
observedAt?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface CheckpointContextInput {
|
|
79
|
+
sessionId: string;
|
|
80
|
+
instanceId: string;
|
|
81
|
+
harness: Harness;
|
|
82
|
+
cwd: string;
|
|
83
|
+
reason: CheckpointReason;
|
|
84
|
+
model?: string;
|
|
85
|
+
continuationNote?: string;
|
|
86
|
+
}
|
|
87
|
+
export interface CheckpointContextResult {
|
|
88
|
+
capsule: ContinuityCapsule;
|
|
89
|
+
path: string;
|
|
90
|
+
state: ContextContinuityState;
|
|
91
|
+
reused: boolean;
|
|
92
|
+
}
|
|
93
|
+
export interface RecoverContextResult {
|
|
94
|
+
state: ContextContinuityState;
|
|
95
|
+
capsule: ContinuityCapsule | null;
|
|
96
|
+
briefing: string;
|
|
97
|
+
recovered: boolean;
|
|
98
|
+
}
|
|
99
|
+
export interface PreparedContextRecovery {
|
|
100
|
+
state: ContextContinuityState;
|
|
101
|
+
capsule: ContinuityCapsule;
|
|
102
|
+
briefing: string;
|
|
103
|
+
}
|
|
104
|
+
/** Parse the context-window shapes exposed by current harness hook/status data. */
|
|
105
|
+
export declare function extractContextSample(payload: Record<string, unknown>, options: ExtractContextSampleOptions): ContextSample | null;
|
|
106
|
+
/** Persist a latest context sample. Identical measurements are a no-op. */
|
|
107
|
+
export declare function recordContextSample(coordRoot: string, instanceId: string, sample: ContextSample): {
|
|
108
|
+
state: ContextContinuityState;
|
|
109
|
+
changed: boolean;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Snapshot external work state. Repeated PreCompact events reuse the existing
|
|
113
|
+
* checkpoint until a matching recovery completes; manual checkpoints always
|
|
114
|
+
* create a new generation.
|
|
115
|
+
*/
|
|
116
|
+
export declare function checkpointContext(coordRoot: string, input: CheckpointContextInput): CheckpointContextResult;
|
|
117
|
+
/** Prepare a briefing without claiming that a harness has accepted it yet. */
|
|
118
|
+
export declare function prepareContextRecovery(coordRoot: string, input: {
|
|
119
|
+
sessionId: string;
|
|
120
|
+
instanceId: string;
|
|
121
|
+
cwd: string;
|
|
122
|
+
}): PreparedContextRecovery | null;
|
|
123
|
+
/** Advance continuity only after the harness context channel accepted the briefing. */
|
|
124
|
+
export declare function completeContextRecovery(coordRoot: string, input: {
|
|
125
|
+
sessionId: string;
|
|
126
|
+
instanceId: string;
|
|
127
|
+
}): ContextContinuityState;
|
|
128
|
+
/** Record that the harness, rather than Harnery, reported compaction complete. */
|
|
129
|
+
export declare function markContextCompactionCompleted(coordRoot: string, input: {
|
|
130
|
+
sessionId: string;
|
|
131
|
+
instanceId: string;
|
|
132
|
+
observedAt?: string;
|
|
133
|
+
}): ContextContinuityState;
|
|
134
|
+
/** Convenience API for callers whose injection is synchronous and guaranteed. */
|
|
135
|
+
export declare function recoverContext(coordRoot: string, input: {
|
|
136
|
+
sessionId: string;
|
|
137
|
+
instanceId: string;
|
|
138
|
+
cwd: string;
|
|
139
|
+
}): RecoverContextResult;
|
|
140
|
+
export declare function readContextState(coordRoot: string, sessionId: string): ContextContinuityState | null;
|
|
141
|
+
export declare function readLatestCapsule(coordRoot: string, sessionId: string): ContinuityCapsule | null;
|
|
142
|
+
export declare function renderRecoveryBriefing(capsule: ContinuityCapsule, liveRepo: RepoSnapshot): string;
|
|
143
|
+
export declare function snapshotRepo(cwd: string): RepoSnapshot;
|
|
144
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/context/index.ts"],"names":[],"mappings":"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"}
|