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
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
import { spawnSync } from "node:child_process";
|
|
23
23
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
24
24
|
import { join } from "node:path";
|
|
25
|
-
import {
|
|
26
|
-
|
|
25
|
+
import { coordFreshnessSeconds } from "../../config.js";
|
|
26
|
+
import { instanceHasLivePid } from "../state/pidmap.js";
|
|
27
27
|
export function evaluateCommit(coordRoot, req) {
|
|
28
28
|
if (req.staged_paths.length === 0) {
|
|
29
29
|
return {
|
|
@@ -36,7 +36,7 @@ export function evaluateCommit(coordRoot, req) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
const peers = readActivePeers(coordRoot);
|
|
39
|
-
const cutoffMs = Date.now() -
|
|
39
|
+
const cutoffMs = Date.now() - coordFreshnessSeconds(coordRoot) * 1000;
|
|
40
40
|
const stagedSet = new Set(req.staged_paths);
|
|
41
41
|
const gitlinkSet = new Set(req.staged_gitlinks ?? []);
|
|
42
42
|
const conflicts = [];
|
|
@@ -166,33 +166,7 @@ function isHolderSelfAttributed(coordRoot, holderId, stagedSet, peers) {
|
|
|
166
166
|
return true;
|
|
167
167
|
}
|
|
168
168
|
function holderHasLiveForeignPid(coordRoot, holderId) {
|
|
169
|
-
|
|
170
|
-
if (!existsSync(dir))
|
|
171
|
-
return false;
|
|
172
|
-
for (const f of readdirSync(dir)) {
|
|
173
|
-
let row = "";
|
|
174
|
-
try {
|
|
175
|
-
row = readFileSync(join(dir, f), "utf8").trim();
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
const owner = row.split("\t")[0]?.trim() ?? "";
|
|
181
|
-
if (owner !== holderId)
|
|
182
|
-
continue;
|
|
183
|
-
// Process still alive?
|
|
184
|
-
const pid = Number.parseInt(f, 10);
|
|
185
|
-
if (!Number.isFinite(pid))
|
|
186
|
-
continue;
|
|
187
|
-
try {
|
|
188
|
-
process.kill(pid, 0); // signal 0 = liveness probe
|
|
189
|
-
return true; // live foreign pid found
|
|
190
|
-
}
|
|
191
|
-
catch {
|
|
192
|
-
// ESRCH (no such process): pid-map entry is stale, skip
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return false;
|
|
169
|
+
return instanceHasLivePid(coordRoot, holderId);
|
|
196
170
|
}
|
|
197
171
|
function isPathCleanInHead(coordRoot, relPath) {
|
|
198
172
|
// Path is tracked + diff-clean against HEAD = "already committed, holder
|
|
@@ -230,13 +204,6 @@ function readActivePeers(coordRoot) {
|
|
|
230
204
|
}
|
|
231
205
|
return out;
|
|
232
206
|
}
|
|
233
|
-
function freshnessSecs() {
|
|
234
|
-
const raw = coordEnv("AGENT_FRESHNESS");
|
|
235
|
-
if (!raw)
|
|
236
|
-
return DEFAULT_FRESHNESS_SECS;
|
|
237
|
-
const n = Number.parseInt(raw, 10);
|
|
238
|
-
return Number.isFinite(n) && n > 0 ? n : DEFAULT_FRESHNESS_SECS;
|
|
239
|
-
}
|
|
240
207
|
function shortName(peer) {
|
|
241
208
|
if (peer.name && peer.name.length > 0)
|
|
242
209
|
return `agent-${peer.name}`;
|
|
@@ -34,6 +34,14 @@ export interface StopHookRequest {
|
|
|
34
34
|
};
|
|
35
35
|
/** Bypass switch: operator escape hatch identical to HARNERY_AGENT_COORD_BYPASS_STOP. */
|
|
36
36
|
bypass?: boolean;
|
|
37
|
+
/** Headless child spawned by `harn workflow` (HARNERY_WORKFLOW_CHILD=1).
|
|
38
|
+
* The end-of-turn ritual exists to surface status to a HUMAN reader; a
|
|
39
|
+
* workflow child reports to the engine's journal instead, so the ritual is
|
|
40
|
+
* meaningless there — worse, blocking burns the child's turn budget on
|
|
41
|
+
* re-prompts (observed as error_max_turns in the Phase 1 spike). Exempting
|
|
42
|
+
* here, rather than disabling the child's hooks wholesale, keeps heartbeat +
|
|
43
|
+
* event capture on: the child stays visible to peers and the coord layer. */
|
|
44
|
+
workflow_child?: boolean;
|
|
37
45
|
}
|
|
38
46
|
/**
|
|
39
47
|
* Evaluate the Stop-hook verdict. Returns the first failing rule (or allow
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stop-hook.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/rules/stop-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAcF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;4EACwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,yFAAyF;IACzF,MAAM,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"stop-hook.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/rules/stop-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAcF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;4EACwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,yFAAyF;IACzF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;iFAM6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AA+BD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa,CAoGvF"}
|
|
@@ -33,6 +33,14 @@ export function evaluateStopHook(coordRoot, req) {
|
|
|
33
33
|
reason: "HARNERY_AGENT_COORD_BYPASS_STOP=1",
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
if (req.workflow_child) {
|
|
37
|
+
return {
|
|
38
|
+
allow: true,
|
|
39
|
+
exit_code: 0,
|
|
40
|
+
rule: "stop-hook.workflow_child",
|
|
41
|
+
reason: "HARNERY_WORKFLOW_CHILD=1: headless workflow child; ritual not applicable",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
36
44
|
let events;
|
|
37
45
|
try {
|
|
38
46
|
events = readRecentEvents(coordRoot, RECENT_EVENT_WINDOW_LINES);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../../../src/core/agents/session-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;iFAEiF;AACjF,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,QAAQ,GACR,aAAa,GACb,aAAa,GACb,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,cAAc,GACd,aAAa,GACb,WAAW,CAAC;AAEhB;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../../../src/core/agents/session-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;iFAEiF;AACjF,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,QAAQ,GACR,aAAa,GACb,aAAa,GACb,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,cAAc,GACd,aAAa,GACb,WAAW,CAAC;AAEhB;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAa1C;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjE;AAsGD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,gBAAgB,EACtB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,IAAI,CAEN;AAED,wDAAwD;AACxD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,SAAO,GAAG,MAAM,CAGxD"}
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* middleware callers need no edits.
|
|
9
9
|
*/
|
|
10
10
|
import { randomBytes } from "node:crypto";
|
|
11
|
-
import { existsSync, readFileSync
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { dirname, resolve } from "node:path";
|
|
13
13
|
// Kept dependency-light: vendored verbatim into a downstream consumer, so no coordEnv import.
|
|
14
|
-
import { normalizeHarness } from "./canonical-emit.js";
|
|
14
|
+
import { normalizeHarness, resolveEmitRoot } from "./canonical-emit.js";
|
|
15
15
|
import { emit } from "./events/emit.js";
|
|
16
16
|
/**
|
|
17
17
|
* Resolved path of the ndjson sidecar file. Lives inside `.harnery/` so a
|
|
@@ -22,27 +22,15 @@ export function sessionEventsPath() {
|
|
|
22
22
|
const explicit = process.env.HARNERY_OUTPUT_SESSION_EVENTS;
|
|
23
23
|
if (explicit)
|
|
24
24
|
return explicit;
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
25
|
+
// Superproject-aware root resolution (git first, cwd walk fallback) via
|
|
26
|
+
// resolveEmitRoot. A plain cwd walk here mis-anchored to a NESTED
|
|
27
|
+
// `.harnery/` when the shell sat inside a submodule carrying its own
|
|
28
|
+
// (e.g. an embedded harnery checkout), silently splitting the event
|
|
29
|
+
// stream. Don't reintroduce CLAUDE.md / .git as anchors, which creates
|
|
29
30
|
// stray .harnery/ dirs under submodule subtrees.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
35
|
-
return resolve(candidate, "session-events.ndjson");
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
/* keep walking */
|
|
40
|
-
}
|
|
41
|
-
const parent = dirname(dir);
|
|
42
|
-
if (parent === dir)
|
|
43
|
-
break;
|
|
44
|
-
dir = parent;
|
|
45
|
-
}
|
|
31
|
+
const root = resolveEmitRoot();
|
|
32
|
+
if (root)
|
|
33
|
+
return resolve(root, ".harnery", "session-events.ndjson");
|
|
46
34
|
return resolve(process.env.HOME || "/tmp", ".harnery", "session-events.ndjson");
|
|
47
35
|
}
|
|
48
36
|
/** Random 8-char hex id for grouping output lines under a single command. */
|
|
@@ -60,28 +48,13 @@ export function newCmdId() {
|
|
|
60
48
|
export function readLastIntent(instanceId) {
|
|
61
49
|
if (!instanceId)
|
|
62
50
|
return null;
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const candidate = resolve(dir, ".harnery");
|
|
69
|
-
try {
|
|
70
|
-
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
71
|
-
agentsDir = candidate;
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
/* keep walking */
|
|
77
|
-
}
|
|
78
|
-
const parent = dirname(dir);
|
|
79
|
-
if (parent === dir)
|
|
80
|
-
break;
|
|
81
|
-
dir = parent;
|
|
82
|
-
}
|
|
83
|
-
if (!agentsDir)
|
|
51
|
+
// Same superproject-aware root resolution as sessionEventsPath(): the
|
|
52
|
+
// intent stamp is written by the PreToolUse hook into the SUPERPROJECT's
|
|
53
|
+
// .harnery/, so a nested-`.harnery/` cwd must not redirect the read.
|
|
54
|
+
const root = resolveEmitRoot();
|
|
55
|
+
if (!root)
|
|
84
56
|
return null;
|
|
57
|
+
const agentsDir = resolve(root, ".harnery");
|
|
85
58
|
const intentPath = resolve(agentsDir, `.last-intent.${instanceId}`);
|
|
86
59
|
if (!existsSync(intentPath))
|
|
87
60
|
return null;
|
|
@@ -20,6 +20,8 @@ export interface V2Heartbeat {
|
|
|
20
20
|
platform?: string;
|
|
21
21
|
subagent_call_id?: string;
|
|
22
22
|
parent_session_id?: string;
|
|
23
|
+
/** Set iff this owner is a `workflow run` child (joins to the run journal). */
|
|
24
|
+
workflow_run_id?: string;
|
|
23
25
|
started_at?: string;
|
|
24
26
|
last_heartbeat: string;
|
|
25
27
|
last_tool?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat-projector.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-projector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,OAAO,EAAE;QACP,cAAc,EAAE,CAAC,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAoD9D;
|
|
1
|
+
{"version":3,"file":"heartbeat-projector.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-projector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,OAAO,EAAE;QACP,cAAc,EAAE,CAAC,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAoD9D;AAyXD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3E"}
|
|
@@ -92,8 +92,8 @@ function seed(ev, coordRoot) {
|
|
|
92
92
|
if (resolved) {
|
|
93
93
|
hb.name = resolved.name;
|
|
94
94
|
hb.kind = resolved.kind;
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
hb.agent_id =
|
|
96
|
+
resolved.agent_id ?? (resolved.kind === "subagent" ? ev.instance_id : undefined);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
catch {
|
|
@@ -138,6 +138,9 @@ function apply(hb, ev, coordRoot) {
|
|
|
138
138
|
const parentSession = pickStr(d, "parent_session_id");
|
|
139
139
|
if (parentSession)
|
|
140
140
|
hb.parent_session_id = parentSession;
|
|
141
|
+
const workflowRunId = pickStr(d, "workflow_run_id");
|
|
142
|
+
if (workflowRunId)
|
|
143
|
+
hb.workflow_run_id = workflowRunId;
|
|
141
144
|
if (!hb.files_touched)
|
|
142
145
|
hb.files_touched = [];
|
|
143
146
|
}
|
|
@@ -202,10 +205,17 @@ function apply(hb, ev, coordRoot) {
|
|
|
202
205
|
if (toolName === "Edit" || toolName === "Write" || toolName === "NotebookEdit") {
|
|
203
206
|
const target = extractFilePath(d);
|
|
204
207
|
if (target) {
|
|
208
|
+
// Canonicalize to repo-relative before storing: the claim guard
|
|
209
|
+
// writes canonical paths directly, so an absolute entry here would
|
|
210
|
+
// double-count the same file (inflated "N files" display) and
|
|
211
|
+
// defeat exact-match pruning on commit.
|
|
212
|
+
const canonical = target.startsWith(`${coordRoot}/`)
|
|
213
|
+
? target.slice(coordRoot.length + 1)
|
|
214
|
+
: target;
|
|
205
215
|
if (!hb.files_touched)
|
|
206
216
|
hb.files_touched = [];
|
|
207
|
-
if (!hb.files_touched.includes(
|
|
208
|
-
hb.files_touched.push(
|
|
217
|
+
if (!hb.files_touched.includes(canonical))
|
|
218
|
+
hb.files_touched.push(canonical);
|
|
209
219
|
}
|
|
210
220
|
}
|
|
211
221
|
break;
|
|
@@ -229,6 +239,15 @@ function apply(hb, ev, coordRoot) {
|
|
|
229
239
|
case "state.status_checked":
|
|
230
240
|
hb.last_status_at = ev.ts;
|
|
231
241
|
break;
|
|
242
|
+
case "identity.assumed": {
|
|
243
|
+
const name = pickStr(d, "name");
|
|
244
|
+
const agentId = pickStr(d, "agent_id");
|
|
245
|
+
if (name)
|
|
246
|
+
hb.name = name;
|
|
247
|
+
if (agentId)
|
|
248
|
+
hb.agent_id = agentId;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
232
251
|
case "state.presence_change": {
|
|
233
252
|
const to = pickStr(d, "to");
|
|
234
253
|
if (to === "mobile" || to === "office")
|
|
@@ -391,6 +410,7 @@ function writeHeartbeat(coordRoot, instanceId, hb) {
|
|
|
391
410
|
setIfDefined(merged, "kind", hb.kind);
|
|
392
411
|
setIfDefined(merged, "agent_id", hb.agent_id);
|
|
393
412
|
setIfDefined(merged, "subagent_call_id", hb.subagent_call_id);
|
|
413
|
+
setIfDefined(merged, "workflow_run_id", hb.workflow_run_id);
|
|
394
414
|
setIfDefined(merged, "model", hb.model);
|
|
395
415
|
setIfDefined(merged, "platform", hb.platform);
|
|
396
416
|
setIfDefined(merged, "started_at", hb.started_at);
|
|
@@ -42,17 +42,31 @@ export declare function setTask(coordRoot: string, instanceId: string, task: str
|
|
|
42
42
|
export declare function stampStatusCheck(coordRoot: string, instanceId: string): Heartbeat | null;
|
|
43
43
|
export declare function setTurnSummary(coordRoot: string, instanceId: string, summary: string): Heartbeat | null;
|
|
44
44
|
export declare function releaseClaim(coordRoot: string, instanceId: string, path: string): Heartbeat | null;
|
|
45
|
+
/** A heartbeat that actually dropped a path during a group unclaim. */
|
|
46
|
+
export interface GroupUnclaimHit {
|
|
47
|
+
instance_id: string;
|
|
48
|
+
session_id?: string;
|
|
49
|
+
platform?: string;
|
|
50
|
+
}
|
|
45
51
|
/**
|
|
46
52
|
* Session-group-wide unclaim. Walks every heartbeat sharing `groupId`
|
|
47
53
|
* (parent's session_id == group_id;
|
|
48
54
|
* subagents inherit it) and removes the path from each one's files_touched.
|
|
49
|
-
* Idempotent: heartbeats that don't hold the path are untouched.
|
|
55
|
+
* Idempotent: heartbeats that don't hold the path are untouched. Returns the
|
|
56
|
+
* heartbeats that actually dropped the path so the caller can emit the
|
|
57
|
+
* durable `claim.release` events — a file-only prune is silently reverted by
|
|
58
|
+
* the next projector replay.
|
|
59
|
+
*
|
|
60
|
+
* files_touched can hold either absolute-under-coordRoot or canonical
|
|
61
|
+
* repo-relative entries (legacy projections stored the raw tool_input path),
|
|
62
|
+
* so both sides are normalized before comparing — an exact-string match
|
|
63
|
+
* silently no-ops on the mixed-form case and the claim never releases.
|
|
50
64
|
*
|
|
51
65
|
* This is the Option B fix for post-commit's pid-map attribution hole: a
|
|
52
66
|
* subagent-held claim that doesn't live on the parent's heartbeat still gets
|
|
53
67
|
* pruned because the walk covers the whole group.
|
|
54
68
|
*/
|
|
55
|
-
export declare function groupUnclaim(coordRoot: string, groupId: string, path: string):
|
|
69
|
+
export declare function groupUnclaim(coordRoot: string, groupId: string, path: string): GroupUnclaimHit[];
|
|
56
70
|
export declare function killHeartbeat(coordRoot: string, instanceId: string): boolean;
|
|
57
71
|
export declare function healPidmap(coordRoot: string, instanceId: string, pid: number): void;
|
|
58
72
|
export declare function healHeartbeat(coordRoot: string, instanceId: string, sessionId?: string, model?: string, harness?: string): Heartbeat | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA6DH,MAAM,WAAW,SAAS;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAiBD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQrF;AAeD,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAS7F;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAKxF;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,IAAI,CAMlB;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,IAAI,CAYlB;AAED
|
|
1
|
+
{"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA6DH,MAAM,WAAW,SAAS;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAiBD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQrF;AAeD,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAS7F;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAKxF;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,SAAS,GAAG,IAAI,CAMlB;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,SAAS,GAAG,IAAI,CAYlB;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CAsChG;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAS5E;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAuBnF;AAED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf,SAAS,GAAG,IAAI,CAsDlB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,SAAS,GAAG,IAAI,CAOlB"}
|
|
@@ -129,18 +129,29 @@ export function releaseClaim(coordRoot, instanceId, path) {
|
|
|
129
129
|
* Session-group-wide unclaim. Walks every heartbeat sharing `groupId`
|
|
130
130
|
* (parent's session_id == group_id;
|
|
131
131
|
* subagents inherit it) and removes the path from each one's files_touched.
|
|
132
|
-
* Idempotent: heartbeats that don't hold the path are untouched.
|
|
132
|
+
* Idempotent: heartbeats that don't hold the path are untouched. Returns the
|
|
133
|
+
* heartbeats that actually dropped the path so the caller can emit the
|
|
134
|
+
* durable `claim.release` events — a file-only prune is silently reverted by
|
|
135
|
+
* the next projector replay.
|
|
136
|
+
*
|
|
137
|
+
* files_touched can hold either absolute-under-coordRoot or canonical
|
|
138
|
+
* repo-relative entries (legacy projections stored the raw tool_input path),
|
|
139
|
+
* so both sides are normalized before comparing — an exact-string match
|
|
140
|
+
* silently no-ops on the mixed-form case and the claim never releases.
|
|
133
141
|
*
|
|
134
142
|
* This is the Option B fix for post-commit's pid-map attribution hole: a
|
|
135
143
|
* subagent-held claim that doesn't live on the parent's heartbeat still gets
|
|
136
144
|
* pruned because the walk covers the whole group.
|
|
137
145
|
*/
|
|
138
146
|
export function groupUnclaim(coordRoot, groupId, path) {
|
|
147
|
+
const hits = [];
|
|
139
148
|
if (!groupId || !path)
|
|
140
|
-
return;
|
|
149
|
+
return hits;
|
|
141
150
|
const activeDir = join(coordRoot, ".harnery", "active");
|
|
142
151
|
if (!existsSync(activeDir))
|
|
143
|
-
return;
|
|
152
|
+
return hits;
|
|
153
|
+
const norm = (p) => p.startsWith(`${coordRoot}/`) ? p.slice(coordRoot.length + 1) : p;
|
|
154
|
+
const target = norm(path);
|
|
144
155
|
for (const f of readdirSync(activeDir)) {
|
|
145
156
|
if (!f.endsWith(".json"))
|
|
146
157
|
continue;
|
|
@@ -156,7 +167,7 @@ export function groupUnclaim(coordRoot, groupId, path) {
|
|
|
156
167
|
if (peerSession !== groupId)
|
|
157
168
|
continue;
|
|
158
169
|
const files = body.files_touched ?? [];
|
|
159
|
-
const next = files.filter((p) => p !==
|
|
170
|
+
const next = files.filter((p) => norm(p) !== target);
|
|
160
171
|
if (next.length === files.length)
|
|
161
172
|
continue;
|
|
162
173
|
body.files_touched = next;
|
|
@@ -164,11 +175,17 @@ export function groupUnclaim(coordRoot, groupId, path) {
|
|
|
164
175
|
const tmp = `${hbPath}.tmp.${process.pid}`;
|
|
165
176
|
writeFileSync(tmp, JSON.stringify(body, null, 2), "utf8");
|
|
166
177
|
renameSync(tmp, hbPath);
|
|
178
|
+
hits.push({
|
|
179
|
+
instance_id: body.instance_id ?? f.replace(/\.json$/, ""),
|
|
180
|
+
session_id: body.session_id,
|
|
181
|
+
platform: body.platform,
|
|
182
|
+
});
|
|
167
183
|
}
|
|
168
184
|
catch {
|
|
169
185
|
/* silent */
|
|
170
186
|
}
|
|
171
187
|
}
|
|
188
|
+
return hits;
|
|
172
189
|
}
|
|
173
190
|
export function killHeartbeat(coordRoot, instanceId) {
|
|
174
191
|
const path = heartbeatPath(coordRoot, instanceId);
|
|
@@ -228,9 +245,9 @@ export function healHeartbeat(coordRoot, instanceId, sessionId, model, harness)
|
|
|
228
245
|
if (resolved) {
|
|
229
246
|
name = resolved.name;
|
|
230
247
|
kind = resolved.kind;
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
248
|
+
// An explicitly assumed session carries its durable persona UUID in
|
|
249
|
+
// name-history. Native subagents continue to use instance_id.
|
|
250
|
+
agentId = resolved.agent_id ?? (resolved.kind === "subagent" ? instanceId : "");
|
|
234
251
|
}
|
|
235
252
|
}
|
|
236
253
|
catch {
|
|
@@ -8,14 +8,30 @@
|
|
|
8
8
|
* Durable persistence: `.harnery/.name-history` (JSONL, one row per assignment)
|
|
9
9
|
* + `.harnery/.name-counter` (current counter, atomic temp+rename).
|
|
10
10
|
*
|
|
11
|
-
* Recreation rule
|
|
12
|
-
* 1. Own instance_id in name-history → (
|
|
13
|
-
* 2. session_id in name-history (owner != session) →
|
|
11
|
+
* Recreation rule:
|
|
12
|
+
* 1. Own instance_id in name-history → latest (name, kind, agent_id)
|
|
13
|
+
* 2. session_id in name-history (owner != session) → latest parent's
|
|
14
|
+
* (name, agent_id) with kind="transient"
|
|
15
|
+
*
|
|
16
|
+
* Multiple rows for one instance are intentional. `identity assume` appends a
|
|
17
|
+
* new binding instead of rewriting history; readers therefore resolve from the
|
|
18
|
+
* end of the file. Older one-row histories retain their original behavior.
|
|
14
19
|
* 3. Else: new assignment, consume a counter slot.
|
|
15
20
|
*/
|
|
16
21
|
/** All 260 names. */
|
|
17
22
|
export declare const COORD_NAMES: readonly ["Anna", "Bob", "Carlos", "Dalia", "Erika", "Fred", "Gemma", "Hank", "Imelda", "Jerry", "Kirk", "Lee", "Milton", "Nate", "Oscar", "Patty", "Quentin", "Rafael", "Sara", "Tony", "Ulrik", "Valerie", "Whitney", "Xander", "Yara", "Zara", "Adelaide", "Bertha", "Carmen", "Dorian", "Edna", "Francine", "Greta", "Helene", "Ines", "Joyce", "Karen", "Lorenzo", "Mindy", "Nadine", "Odette", "Paulette", "Quinn", "Rebekah", "Sean", "Tammy", "Ursula", "Vinson", "Wallace", "Xanthe", "Yusuf", "Zoe", "Alex", "Beatrice", "Cyrus", "Delia", "Ernesto", "Florence", "Gordon", "Hester", "Isaias", "Jenna", "Klaus", "Larry", "Maxine", "Nicholas", "Otto", "Peter", "Quill", "Renee", "Stella", "Theresa", "Umberto", "Virgil", "Willow", "Xavier", "Yolanda", "Zane", "Anita", "Bruno", "Cora", "Damon", "Edith", "Felix", "Gloria", "Holly", "Iris", "Jasper", "Kaia", "Linda", "Marco", "Nora", "Olive", "Petra", "Querida", "Roman", "Sebastian", "Tobias", "Una", "Vera", "Walter", "Xena", "Yusra", "Zelda", "Aaron", "Bonnie", "Caleb", "Daphne", "Elias", "Fiona", "Galileo", "Hugo", "Ian", "Jude", "Knox", "Leo", "Maya", "Nash", "Owen", "Paxton", "Quetzal", "Rosa", "Sage", "Talia", "Uri", "Vincent", "Wesley", "Ximena", "Yvette", "Zephyr", "Astrid", "Beau", "Celeste", "Dexter", "Ezra", "Fitz", "Genevieve", "Hadley", "Imogen", "Joaquin", "Kira", "Lila", "Mason", "Nila", "Olga", "Pearl", "Quincy", "Royce", "Sterling", "Trent", "Ulysses", "Violet", "Wynne", "Xerxes", "Yael", "Zinnia", "Amelia", "Bianca", "Cody", "Dahlia", "Esme", "Fern", "Greer", "Harriet", "Ivan", "Juno", "Kendrick", "Luther", "Margot", "Nigel", "Orion", "Phillip", "Quenby", "Reagan", "Sienna", "Tessa", "Uma", "Vesper", "Watson", "Xiomara", "Yuri", "Zoltan", "Andre", "Boris", "Cassidy", "Davis", "Evander", "Forrest", "Gibson", "Henry", "Irene", "Justus", "Kasper", "Lainey", "Miles", "Naomi", "Ophelia", "Phoebe", "Quark", "Rylie", "Saul", "Truman", "Ulrich", "Voss", "Winifred", "Xuxa", "Yvonne", "Zia", "Aria", "Brenda", "Crispin", "Dolores", "Estelle", "Felicity", "Goldie", "Hazel", "Ira", "Jovi", "Kestrel", "Lyric", "Mavis", "Nico", "Otis", "Percy", "Querubin", "Rhett", "Scout", "Tatum", "Unity", "Vivian", "Wyatt", "Xan", "Yancy", "Zev", "Atticus", "Barnaby", "Calliope", "Drake", "Eustace", "Foster", "Granger", "Hollis", "Indira", "Jericho", "Kaleb", "Logan", "Magnus", "Noor", "Oakley", "Posy", "Quito", "Rosalind", "Sloane", "Theron", "Upton", "Vance", "Wendell", "Xola", "Yann", "Zora"];
|
|
18
23
|
export type NameKind = "session" | "subagent" | "transient";
|
|
24
|
+
export interface NameHistoryRow {
|
|
25
|
+
instance_id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
kind: NameKind;
|
|
28
|
+
ts: string;
|
|
29
|
+
/** Durable persona UUID. Present after `agents identity assume`. */
|
|
30
|
+
agent_id?: string;
|
|
31
|
+
/** Audit marker distinguishing an explicit role adoption from pool assignment. */
|
|
32
|
+
source?: "pool" | "identity.assume";
|
|
33
|
+
previous_name?: string;
|
|
34
|
+
}
|
|
19
35
|
/**
|
|
20
36
|
* Resolve (name, kind) for an existing owner without consuming a counter
|
|
21
37
|
* slot. Returns null if no history match found.
|
|
@@ -26,7 +42,26 @@ export type NameKind = "session" | "subagent" | "transient";
|
|
|
26
42
|
export declare function resolveName(coordRoot: string, instanceId: string, sessionId?: string): {
|
|
27
43
|
name: string;
|
|
28
44
|
kind: NameKind;
|
|
45
|
+
agent_id?: string;
|
|
29
46
|
} | null;
|
|
47
|
+
export interface NameAssumptionResult {
|
|
48
|
+
changed: boolean;
|
|
49
|
+
previous: {
|
|
50
|
+
name: string;
|
|
51
|
+
kind: NameKind;
|
|
52
|
+
agent_id?: string;
|
|
53
|
+
} | null;
|
|
54
|
+
current: {
|
|
55
|
+
name: string;
|
|
56
|
+
kind: NameKind;
|
|
57
|
+
agent_id: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Append an explicit instance → durable-persona binding. Latest-row-wins makes
|
|
62
|
+
* this auditable and retry-safe without mutating prior assignment history.
|
|
63
|
+
*/
|
|
64
|
+
export declare function recordNameAssumption(coordRoot: string, instanceId: string, name: string, agentId: string, kind?: NameKind): NameAssumptionResult;
|
|
30
65
|
/**
|
|
31
66
|
* Assign a name to <instanceId> with the given <kind>. Counter-consuming when
|
|
32
67
|
* the owner is new. Idempotent: returns existing name on resume.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/names.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,qBAAqB;AACrB,eAAO,MAAM,WAAW,y5EA+Qd,CAAC;AAMX,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAsCD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4B5D;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,QAAoB,GACzB,oBAAoB,CAoBtB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAsBxF"}
|
|
@@ -8,9 +8,14 @@
|
|
|
8
8
|
* Durable persistence: `.harnery/.name-history` (JSONL, one row per assignment)
|
|
9
9
|
* + `.harnery/.name-counter` (current counter, atomic temp+rename).
|
|
10
10
|
*
|
|
11
|
-
* Recreation rule
|
|
12
|
-
* 1. Own instance_id in name-history → (
|
|
13
|
-
* 2. session_id in name-history (owner != session) →
|
|
11
|
+
* Recreation rule:
|
|
12
|
+
* 1. Own instance_id in name-history → latest (name, kind, agent_id)
|
|
13
|
+
* 2. session_id in name-history (owner != session) → latest parent's
|
|
14
|
+
* (name, agent_id) with kind="transient"
|
|
15
|
+
*
|
|
16
|
+
* Multiple rows for one instance are intentional. `identity assume` appends a
|
|
17
|
+
* new binding instead of rewriting history; readers therefore resolve from the
|
|
18
|
+
* end of the file. Older one-row histories retain their original behavior.
|
|
14
19
|
* 3. Else: new assignment, consume a counter slot.
|
|
15
20
|
*/
|
|
16
21
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
@@ -333,20 +338,53 @@ function appendHistory(coordRoot, row) {
|
|
|
333
338
|
*/
|
|
334
339
|
export function resolveName(coordRoot, instanceId, sessionId) {
|
|
335
340
|
const history = readHistory(coordRoot);
|
|
336
|
-
for (
|
|
341
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
342
|
+
const row = history[i];
|
|
337
343
|
if (row.instance_id === instanceId) {
|
|
338
|
-
return {
|
|
344
|
+
return {
|
|
345
|
+
name: row.name,
|
|
346
|
+
kind: row.kind,
|
|
347
|
+
...(row.agent_id ? { agent_id: row.agent_id } : {}),
|
|
348
|
+
};
|
|
339
349
|
}
|
|
340
350
|
}
|
|
341
351
|
if (sessionId && sessionId !== instanceId) {
|
|
342
|
-
for (
|
|
352
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
353
|
+
const row = history[i];
|
|
343
354
|
if (row.instance_id === sessionId) {
|
|
344
|
-
return {
|
|
355
|
+
return {
|
|
356
|
+
name: row.name,
|
|
357
|
+
kind: "transient",
|
|
358
|
+
...(row.agent_id ? { agent_id: row.agent_id } : {}),
|
|
359
|
+
};
|
|
345
360
|
}
|
|
346
361
|
}
|
|
347
362
|
}
|
|
348
363
|
return null;
|
|
349
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Append an explicit instance → durable-persona binding. Latest-row-wins makes
|
|
367
|
+
* this auditable and retry-safe without mutating prior assignment history.
|
|
368
|
+
*/
|
|
369
|
+
export function recordNameAssumption(coordRoot, instanceId, name, agentId, kind = "session") {
|
|
370
|
+
const previous = resolveName(coordRoot, instanceId);
|
|
371
|
+
const current = { name, kind, agent_id: agentId };
|
|
372
|
+
if (previous?.name === current.name &&
|
|
373
|
+
previous.kind === current.kind &&
|
|
374
|
+
previous.agent_id === current.agent_id) {
|
|
375
|
+
return { changed: false, previous, current };
|
|
376
|
+
}
|
|
377
|
+
appendHistory(coordRoot, {
|
|
378
|
+
instance_id: instanceId,
|
|
379
|
+
name,
|
|
380
|
+
kind,
|
|
381
|
+
agent_id: agentId,
|
|
382
|
+
source: "identity.assume",
|
|
383
|
+
previous_name: previous?.name,
|
|
384
|
+
ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
385
|
+
});
|
|
386
|
+
return { changed: true, previous, current };
|
|
387
|
+
}
|
|
350
388
|
/**
|
|
351
389
|
* Assign a name to <instanceId> with the given <kind>. Counter-consuming when
|
|
352
390
|
* the owner is new. Idempotent: returns existing name on resume.
|
|
@@ -370,6 +408,7 @@ export function assignName(coordRoot, instanceId, kind) {
|
|
|
370
408
|
instance_id: instanceId,
|
|
371
409
|
name,
|
|
372
410
|
kind,
|
|
411
|
+
source: "pool",
|
|
373
412
|
ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
374
413
|
});
|
|
375
414
|
return name;
|
|
@@ -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
|
|
@@ -8,4 +8,8 @@
|
|
|
8
8
|
* Atomic temp+rename. Idempotent: re-writing the same row is a no-op.
|
|
9
9
|
*/
|
|
10
10
|
export declare function writePidmapRow(coordRoot: string, pid: number, instanceId: string, platform: string): void;
|
|
11
|
+
/** True when any pid-map row for `instanceId` still belongs to a live process. */
|
|
12
|
+
export declare function instanceHasLivePid(coordRoot: string, instanceId: string): boolean;
|
|
13
|
+
/** Drop every pid-map row owned by `instanceId`. Returns how many files were removed. */
|
|
14
|
+
export declare function removePidmapRowsForInstance(coordRoot: string, instanceId: string): number;
|
|
11
15
|
//# sourceMappingURL=pidmap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pidmap.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/pidmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"pidmap.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/pidmap.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,IAAI,CAYN;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAsBjF;AAED,yFAAyF;AACzF,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAqBzF"}
|