harnery 0.42.0 → 0.44.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 +3 -3
- package/dist/commander.d.ts +16 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +18 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +95 -52
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +38 -11
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +10 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +22 -7
- package/dist/commands/governor.js +22 -10
- package/dist/commands/inbox.d.ts +1 -1
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +55 -2
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +28 -4
- package/dist/commands/servers.d.ts.map +1 -1
- package/dist/commands/servers.js +16 -1
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +7 -2
- package/dist/commands/toc.d.ts +14 -0
- package/dist/commands/toc.d.ts.map +1 -1
- package/dist/commands/toc.js +7 -4
- package/dist/commands/tunnel.d.ts +65 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +290 -101
- package/dist/commands/web-fetch.d.ts +3 -2
- package/dist/commands/web-fetch.d.ts.map +1 -1
- package/dist/commands/web-fetch.js +25 -19
- package/dist/commands/web.d.ts +7 -1
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +9 -3
- package/dist/commands/workflow.js +24 -30
- package/dist/core/agents/live-authority-v3.d.ts +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -0
- package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/live-lifecycle-v3.js +28 -7
- package/dist/core/agents/mailbox.d.ts +9 -8
- package/dist/core/agents/mailbox.d.ts.map +1 -1
- package/dist/core/agents/mailbox.js +24 -17
- package/dist/core/agents/render/prompt-context.d.ts +2 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +19 -17
- package/dist/core/agents/render/session-context.d.ts +2 -8
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +64 -43
- package/dist/core/agents/rules/stop-hook.js +3 -3
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +25 -14
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/agents/state/names.d.ts +43 -3
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +87 -3
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +5 -0
- package/dist/core/agents/state/proc-start.d.ts +19 -0
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +180 -70
- package/dist/core/agents/state/unchanged-file.d.ts +44 -0
- package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
- package/dist/core/agents/state/unchanged-file.js +390 -0
- package/dist/core/context/index.d.ts +22 -10
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +381 -119
- package/dist/core/context/observation.d.ts +26 -0
- package/dist/core/context/observation.d.ts.map +1 -0
- package/dist/core/context/observation.js +11 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +25 -14
- package/dist/core/diagnostics/path-identity.d.ts +3 -0
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
- package/dist/core/diagnostics/path-identity.js +12 -0
- package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
- package/dist/core/diagnostics/sanitize.js +4 -3
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +29 -0
- package/dist/core/events/v3/generation-custody.d.ts +5 -0
- package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
- package/dist/core/events/v3/generation-custody.js +50 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +77 -64
- package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +14 -1
- package/dist/core/governor/service-read.d.ts +10 -2
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +105 -22
- package/dist/core/governor/service.d.ts +20 -2
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +72 -47
- package/dist/core/hooks/cli.js +120 -23
- package/dist/core/hooks/fork-lineage.d.ts +40 -0
- package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
- package/dist/core/hooks/fork-lineage.js +62 -0
- package/dist/core/hooks/resolve/transcript.d.ts +16 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +84 -0
- package/dist/core/hooks/shell-rm.d.ts +2 -0
- package/dist/core/hooks/shell-rm.d.ts.map +1 -0
- package/dist/core/hooks/shell-rm.js +105 -0
- package/dist/core/hooks/shell-tokens.d.ts +12 -0
- package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
- package/dist/core/hooks/shell-tokens.js +97 -0
- package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
- package/dist/core/hooks/shell-waiter.js +3 -93
- package/dist/core/inbox/contract.d.ts +2 -1
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/index.d.ts +2 -0
- package/dist/core/inbox/index.d.ts.map +1 -1
- package/dist/core/inbox/index.js +2 -0
- package/dist/core/inbox/peer-messages.d.ts +37 -0
- package/dist/core/inbox/peer-messages.d.ts.map +1 -0
- package/dist/core/inbox/peer-messages.js +72 -0
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +67 -21
- package/dist/core/inbox/targets.d.ts +22 -0
- package/dist/core/inbox/targets.d.ts.map +1 -0
- package/dist/core/inbox/targets.js +108 -0
- package/dist/core/inbox/watcher.js +7 -3
- package/dist/core/journal/index.d.ts.map +1 -1
- package/dist/core/journal/index.js +17 -2
- package/dist/core/resources/contract.d.ts +3 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/service-status.d.ts +14 -1
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +23 -13
- package/dist/core/resources/service.d.ts +9 -1
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +47 -27
- package/dist/core/resources/status.d.ts +2 -5
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +36 -7
- package/dist/core/semantic/contract.d.ts +5 -5
- package/dist/core/servers/index.d.ts +24 -13
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +132 -92
- package/dist/core/servers/tunnels.d.ts +3 -5
- package/dist/core/servers/tunnels.d.ts.map +1 -1
- package/dist/core/servers/tunnels.js +3 -9
- package/dist/core/storage/process-log.d.ts +12 -0
- package/dist/core/storage/process-log.d.ts.map +1 -1
- package/dist/core/storage/process-log.js +10 -2
- package/dist/core/supervisor/contract.d.ts +12 -0
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.js +2 -1
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +54 -9
- package/dist/core/supervisor/service.d.ts +9 -1
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +59 -29
- package/dist/core/supervisor/services.d.ts.map +1 -1
- package/dist/core/supervisor/services.js +22 -1
- package/dist/core/supervisor/status.d.ts +17 -2
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +70 -12
- package/dist/core/supervisor/timeline.d.ts.map +1 -1
- package/dist/core/supervisor/timeline.js +2 -1
- package/dist/core/work/state.js +7 -7
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +105 -32
- package/dist/core/workflow/failure-evidence.d.ts +16 -0
- package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
- package/dist/core/workflow/failure-evidence.js +114 -0
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +1 -1
- package/dist/core/workflow/live-session-v3.d.ts +6 -0
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +126 -5
- package/dist/core/workflow/proof.d.ts +5 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +115 -20
- package/dist/core/workflow/run-state.d.ts +3 -1
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +31 -27
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +19 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +13 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +19 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +14 -0
- package/dist/core/workflow/types.d.ts +64 -17
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.d.ts +0 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +0 -1
- package/dist/core/workflow/workspaces/leases.d.ts +3 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +30 -16
- package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +270 -6
- package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
- package/dist/core/workflow/workspaces/ownership.js +194 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +14 -0
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +21 -13
- package/dist/core/workflow/workspaces/state.d.ts +1 -0
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +21 -1
- package/dist/lib/browser/windows-chrome.d.ts +100 -12
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +262 -73
- package/dist/lib/docs-links.d.ts.map +1 -1
- package/dist/lib/docs-links.js +45 -2
- package/dist/lib/guarded-remove.d.ts +7 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -1
- package/dist/lib/guarded-remove.js +106 -12
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -0
- package/dist/lib/tunnel/allowlist.d.ts +44 -0
- package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
- package/dist/lib/tunnel/allowlist.js +204 -0
- package/dist/lib/tunnel/current-address.d.ts +41 -0
- package/dist/lib/tunnel/current-address.d.ts.map +1 -0
- package/dist/lib/tunnel/current-address.js +123 -0
- package/dist/lib/tunnel/gate.js +13 -6
- package/dist/lib/tunnel/processes.d.ts +39 -0
- package/dist/lib/tunnel/processes.d.ts.map +1 -0
- package/dist/lib/tunnel/processes.js +121 -0
- package/dist/lib/tunnel/state.d.ts +28 -3
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +98 -27
- package/dist/lib/zip/index.d.ts +41 -0
- package/dist/lib/zip/index.d.ts.map +1 -0
- package/dist/lib/zip/index.js +398 -0
- package/package.json +8 -1
- package/src/commander.ts +32 -0
- package/src/commands/agents.ts +151 -74
- package/src/commands/browse.ts +50 -14
- package/src/commands/diagnostics.ts +10 -0
- package/src/commands/doctor.ts +26 -7
- package/src/commands/governor.ts +23 -10
- package/src/commands/inbox.ts +52 -2
- package/src/commands/rm.ts +43 -4
- package/src/commands/servers.ts +16 -4
- package/src/commands/supervisor.ts +7 -2
- package/src/commands/toc.ts +14 -6
- package/src/commands/tunnel.ts +394 -116
- package/src/commands/web-fetch.ts +30 -19
- package/src/commands/web.ts +9 -3
- package/src/commands/workflow.ts +30 -30
- package/src/core/agents/live-authority-v3.ts +17 -0
- package/src/core/agents/live-lifecycle-v3.ts +52 -14
- package/src/core/agents/mailbox.ts +26 -17
- package/src/core/agents/render/prompt-context.ts +35 -20
- package/src/core/agents/render/session-context.ts +92 -48
- package/src/core/agents/rules/stop-hook.ts +3 -3
- package/src/core/agents/state/heartbeat-writer.ts +29 -12
- package/src/core/agents/state/live-coordination-writer.ts +29 -0
- package/src/core/agents/state/names.ts +117 -6
- package/src/core/agents/state/pidmap.ts +13 -0
- package/src/core/agents/state/proc-start.ts +57 -0
- package/src/core/agents/state/stale-sweep.ts +247 -82
- package/src/core/agents/state/unchanged-file.ts +456 -0
- package/src/core/context/index.ts +446 -136
- package/src/core/context/observation.ts +29 -0
- package/src/core/diagnostics/bundle.ts +23 -18
- package/src/core/diagnostics/path-identity.ts +15 -0
- package/src/core/diagnostics/sanitize.ts +4 -3
- package/src/core/events/v3/authority-outbox.ts +56 -0
- package/src/core/events/v3/generation-custody.ts +59 -0
- package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
- package/src/core/events/v3/producers/recorder.ts +94 -68
- package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
- package/src/core/governor/planning.ts +20 -8
- package/src/core/governor/service-read.ts +128 -20
- package/src/core/governor/service.ts +119 -42
- package/src/core/hooks/cli.ts +139 -22
- package/src/core/hooks/fork-lineage.ts +82 -0
- package/src/core/hooks/resolve/transcript.ts +88 -0
- package/src/core/hooks/shell-rm.ts +103 -0
- package/src/core/hooks/shell-tokens.ts +96 -0
- package/src/core/hooks/shell-waiter.ts +4 -91
- package/src/core/inbox/contract.ts +2 -1
- package/src/core/inbox/index.ts +2 -0
- package/src/core/inbox/peer-messages.ts +94 -0
- package/src/core/inbox/service.ts +88 -22
- package/src/core/inbox/targets.ts +148 -0
- package/src/core/inbox/watcher.ts +8 -9
- package/src/core/journal/index.ts +19 -2
- package/src/core/resources/contract.ts +4 -0
- package/src/core/resources/service-status.ts +35 -12
- package/src/core/resources/service.ts +71 -24
- package/src/core/resources/status.ts +57 -8
- package/src/core/servers/index.ts +177 -91
- package/src/core/servers/tunnels.ts +3 -11
- package/src/core/storage/process-log.ts +17 -2
- package/src/core/supervisor/contract.ts +13 -0
- package/src/core/supervisor/explanations.ts +4 -1
- package/src/core/supervisor/findings.ts +68 -10
- package/src/core/supervisor/service.ts +88 -26
- package/src/core/supervisor/services.ts +26 -8
- package/src/core/supervisor/status.ts +89 -12
- package/src/core/supervisor/timeline.ts +2 -1
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +131 -49
- package/src/core/workflow/failure-evidence.ts +141 -0
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/live-session-v3.ts +146 -5
- package/src/core/workflow/proof.ts +134 -26
- package/src/core/workflow/run-state.ts +31 -29
- package/src/core/workflow/spawn-claude.ts +18 -0
- package/src/core/workflow/spawn-codex.ts +13 -0
- package/src/core/workflow/spawn-cursor.ts +18 -0
- package/src/core/workflow/spawn-opencode.ts +14 -0
- package/src/core/workflow/types.ts +51 -17
- package/src/core/workflow/workspaces/index.ts +0 -1
- package/src/core/workflow/workspaces/leases.ts +48 -17
- package/src/core/workflow/workspaces/local-git.ts +315 -5
- package/src/core/workflow/workspaces/ownership.ts +232 -0
- package/src/core/workflow/workspaces/reclaim.ts +58 -0
- package/src/core/workflow/workspaces/recovery.ts +25 -13
- package/src/core/workflow/workspaces/state.ts +20 -0
- package/src/lib/browser/windows-chrome.ts +346 -86
- package/src/lib/docs-links.ts +49 -2
- package/src/lib/guarded-remove.ts +132 -14
- package/src/lib/identities/assume.ts +4 -0
- package/src/lib/tunnel/allowlist.ts +208 -0
- package/src/lib/tunnel/current-address.ts +158 -0
- package/src/lib/tunnel/gate.ts +16 -8
- package/src/lib/tunnel/processes.ts +149 -0
- package/src/lib/tunnel/state.ts +123 -27
- package/src/lib/zip/index.ts +483 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Shared, deliberately small shell lexer for PreToolUse command guards. */
|
|
2
|
+
/** Keep quoted arguments opaque, including examples and heredoc bodies. */
|
|
3
|
+
export function tokenize(source) {
|
|
4
|
+
const out = [];
|
|
5
|
+
const heredocs = [];
|
|
6
|
+
for (let i = 0; i < source.length;) {
|
|
7
|
+
const ch = source[i];
|
|
8
|
+
if (ch === "\\" && source[i + 1] === "\n") {
|
|
9
|
+
i += 2;
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (ch === "\n") {
|
|
13
|
+
out.push({ value: ";", quoted: false, operator: true });
|
|
14
|
+
i++;
|
|
15
|
+
for (const doc of heredocs.splice(0)) {
|
|
16
|
+
while (i < source.length) {
|
|
17
|
+
const end = source.indexOf("\n", i);
|
|
18
|
+
const stop = end === -1 ? source.length : end;
|
|
19
|
+
const line = source.slice(i, stop);
|
|
20
|
+
i = end === -1 ? source.length : end + 1;
|
|
21
|
+
if ((doc.tabs ? line.replace(/^\t+/, "") : line) === doc.end)
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (/\s/.test(ch)) {
|
|
28
|
+
i++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === "#") {
|
|
32
|
+
const end = source.indexOf("\n", i);
|
|
33
|
+
i = end === -1 ? source.length : end;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (";|&(){}<>".includes(ch)) {
|
|
37
|
+
const op = source.slice(i).match(/^(?:<<-?|>>|&&|\|\||[;|&(){}<>])/)[0];
|
|
38
|
+
out.push({ value: op, quoted: false, operator: true });
|
|
39
|
+
i += op.length;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
let value = "";
|
|
43
|
+
let quoted = false;
|
|
44
|
+
while (i < source.length && !/[\s;|&(){}<>]/.test(source[i])) {
|
|
45
|
+
const next = source[i++];
|
|
46
|
+
if (next === "'" || next === '"' || next === "`") {
|
|
47
|
+
quoted = true;
|
|
48
|
+
while (i < source.length && source[i] !== next) {
|
|
49
|
+
if (source[i] === "\\" && next !== "'" && i + 1 < source.length)
|
|
50
|
+
i++;
|
|
51
|
+
value += source[i++];
|
|
52
|
+
}
|
|
53
|
+
if (source[i] === next)
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
else if (next === "\\" && i < source.length) {
|
|
57
|
+
const escaped = source[i++];
|
|
58
|
+
if (escaped !== "\n")
|
|
59
|
+
value += escaped;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
value += next;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const previous = out.at(-1)?.value;
|
|
66
|
+
if (previous === "<<" || previous === "<<-") {
|
|
67
|
+
heredocs.push({ end: value, tabs: previous === "<<-" });
|
|
68
|
+
}
|
|
69
|
+
out.push({ value, quoted });
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
export function basename(value) {
|
|
74
|
+
return value.split(/[\\/]/).at(-1) ?? value;
|
|
75
|
+
}
|
|
76
|
+
/** The command string of a Bash-style tool call, or null for any other tool. */
|
|
77
|
+
export function shellCommandFromTool(toolName, toolInput) {
|
|
78
|
+
if (typeof toolName !== "string")
|
|
79
|
+
return null;
|
|
80
|
+
const name = toolName.toLowerCase().replace(/^functions\./, "");
|
|
81
|
+
if (!["bash", "shell", "shell_command", "exec_command"].includes(name))
|
|
82
|
+
return null;
|
|
83
|
+
let input = toolInput;
|
|
84
|
+
if (typeof input === "string") {
|
|
85
|
+
try {
|
|
86
|
+
input = JSON.parse(input);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!input || typeof input !== "object")
|
|
93
|
+
return null;
|
|
94
|
+
const args = input;
|
|
95
|
+
const command = args.command ?? args.cmd;
|
|
96
|
+
return typeof command === "string" ? command : null;
|
|
97
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-waiter.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/shell-waiter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shell-waiter.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/shell-waiter.ts"],"names":[],"mappings":"AA2EA,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAGtF"}
|
|
@@ -1,83 +1,10 @@
|
|
|
1
|
+
import { basename, shellCommandFromTool, tokenize } from "./shell-tokens.js";
|
|
1
2
|
/** Recognize inline waiters; this is not a proof of arbitrary shell termination. */
|
|
2
3
|
const REASON = "Inline shell waiter blocked: polling loops and delayed log-only checks can outlive the job " +
|
|
3
4
|
"and accumulate as background tasks. Read the existing task output now, or wait on its " +
|
|
4
5
|
"existing task handle. If polling is necessary, use a reviewed script with a hard deadline, " +
|
|
5
6
|
"nonzero timeout exit, and producer-exit/failure detection. Do not create another waiter " +
|
|
6
7
|
"to check progress or infer completion from a broad pgrep -f pattern.";
|
|
7
|
-
/** Keep quoted arguments opaque, including examples and heredoc bodies. */
|
|
8
|
-
function tokenize(source) {
|
|
9
|
-
const out = [];
|
|
10
|
-
const heredocs = [];
|
|
11
|
-
for (let i = 0; i < source.length;) {
|
|
12
|
-
const ch = source[i];
|
|
13
|
-
if (ch === "\\" && source[i + 1] === "\n") {
|
|
14
|
-
i += 2;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
if (ch === "\n") {
|
|
18
|
-
out.push({ value: ";", quoted: false, operator: true });
|
|
19
|
-
i++;
|
|
20
|
-
for (const doc of heredocs.splice(0)) {
|
|
21
|
-
while (i < source.length) {
|
|
22
|
-
const end = source.indexOf("\n", i);
|
|
23
|
-
const stop = end === -1 ? source.length : end;
|
|
24
|
-
const line = source.slice(i, stop);
|
|
25
|
-
i = end === -1 ? source.length : end + 1;
|
|
26
|
-
if ((doc.tabs ? line.replace(/^\t+/, "") : line) === doc.end)
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (/\s/.test(ch)) {
|
|
33
|
-
i++;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (ch === "#") {
|
|
37
|
-
const end = source.indexOf("\n", i);
|
|
38
|
-
i = end === -1 ? source.length : end;
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (";|&(){}<>".includes(ch)) {
|
|
42
|
-
const op = source.slice(i).match(/^(?:<<-?|>>|&&|\|\||[;|&(){}<>])/)[0];
|
|
43
|
-
out.push({ value: op, quoted: false, operator: true });
|
|
44
|
-
i += op.length;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
let value = "";
|
|
48
|
-
let quoted = false;
|
|
49
|
-
while (i < source.length && !/[\s;|&(){}<>]/.test(source[i])) {
|
|
50
|
-
const next = source[i++];
|
|
51
|
-
if (next === "'" || next === '"' || next === "`") {
|
|
52
|
-
quoted = true;
|
|
53
|
-
while (i < source.length && source[i] !== next) {
|
|
54
|
-
if (source[i] === "\\" && next !== "'" && i + 1 < source.length)
|
|
55
|
-
i++;
|
|
56
|
-
value += source[i++];
|
|
57
|
-
}
|
|
58
|
-
if (source[i] === next)
|
|
59
|
-
i++;
|
|
60
|
-
}
|
|
61
|
-
else if (next === "\\" && i < source.length) {
|
|
62
|
-
const escaped = source[i++];
|
|
63
|
-
if (escaped !== "\n")
|
|
64
|
-
value += escaped;
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
value += next;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const previous = out.at(-1)?.value;
|
|
71
|
-
if (previous === "<<" || previous === "<<-") {
|
|
72
|
-
heredocs.push({ end: value, tabs: previous === "<<-" });
|
|
73
|
-
}
|
|
74
|
-
out.push({ value, quoted });
|
|
75
|
-
}
|
|
76
|
-
return out;
|
|
77
|
-
}
|
|
78
|
-
function basename(value) {
|
|
79
|
-
return value.split(/[\\/]/).at(-1) ?? value;
|
|
80
|
-
}
|
|
81
8
|
function isWaiter(source, depth = 0) {
|
|
82
9
|
if (depth > 8)
|
|
83
10
|
return false;
|
|
@@ -151,23 +78,6 @@ function isWaiter(source, depth = 0) {
|
|
|
151
78
|
commands.every((name) => harmless.has(name)));
|
|
152
79
|
}
|
|
153
80
|
export function shellWaiterReason(toolName, toolInput) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const name = toolName.toLowerCase().replace(/^functions\./, "");
|
|
157
|
-
if (!["bash", "shell", "shell_command", "exec_command"].includes(name))
|
|
158
|
-
return null;
|
|
159
|
-
let input = toolInput;
|
|
160
|
-
if (typeof input === "string") {
|
|
161
|
-
try {
|
|
162
|
-
input = JSON.parse(input);
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (!input || typeof input !== "object")
|
|
169
|
-
return null;
|
|
170
|
-
const args = input;
|
|
171
|
-
const command = args.command ?? args.cmd;
|
|
172
|
-
return typeof command === "string" && isWaiter(command) ? REASON : null;
|
|
81
|
+
const command = shellCommandFromTool(toolName, toolInput);
|
|
82
|
+
return command !== null && isWaiter(command) ? REASON : null;
|
|
173
83
|
}
|
|
@@ -17,7 +17,8 @@ export interface HarneryInboxMessageRecordV1 {
|
|
|
17
17
|
schema: typeof HARNERY_INBOX_RECORD_SCHEMA;
|
|
18
18
|
kind: "message";
|
|
19
19
|
message_id: string;
|
|
20
|
-
|
|
20
|
+
/** A required null identifies an operator; strings identify real agent instances. */
|
|
21
|
+
sender_instance_id: string | null;
|
|
21
22
|
sender_display_name: string;
|
|
22
23
|
recipient_instance_id: string;
|
|
23
24
|
recipient_display_name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/contract.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B,EAAG,yBAAkC,CAAC;AAC9E,eAAO,MAAM,2BAA2B,EAAG,yBAAkC,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/contract.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,2BAA2B,EAAG,yBAAkC,CAAC;AAC9E,eAAO,MAAM,2BAA2B,EAAG,yBAAkC,CAAC;AAE9E,MAAM,WAAW,kBAAkB;IACjC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG,4BAA4B,CAAC;AAE9F,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,2BAA2B,CAAC;IAC3C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,0BAA0B,GAClC,oBAAoB,GACpB,qBAAqB,GACrB,qBAAqB,GACrB,qBAAqB,CAAC;AAE1B,qBAAa,yBAA0B,SAAQ,KAAK;IAEhD,QAAQ,CAAC,WAAW,EAAE,0BAA0B;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAFb,WAAW,EAAE,0BAA0B,EACvC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM;CAMzB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAC;IAChD,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;IAChC,6BAA6B,EAAE,IAAI,CAAC;CACrC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC"}
|
package/dist/core/inbox/index.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HarneryInboxLimits, HarneryInboxMessageRecordV1 } from "./contract.js";
|
|
2
|
+
import { HarneryInboxService } from "./service.js";
|
|
3
|
+
import { type MessageTargetInput } from "./targets.js";
|
|
4
|
+
/** Preserve the existing ping body/count bounds. Pending messages have no TTL;
|
|
5
|
+
* the lifecycle planner protects them through the idle finalization window. */
|
|
6
|
+
export declare const PEER_INBOX_LIMITS: HarneryInboxLimits;
|
|
7
|
+
export declare function peerInbox(coordRoot: string): HarneryInboxService;
|
|
8
|
+
/** CLI and web use this one path. A receipt proves a synced append, never that
|
|
9
|
+
* a process is awake or that the recipient has noticed the message. */
|
|
10
|
+
export declare function sendPeerMessage(input: {
|
|
11
|
+
coordRoot: string;
|
|
12
|
+
target: MessageTargetInput;
|
|
13
|
+
senderInstanceId: string | null;
|
|
14
|
+
senderName: string;
|
|
15
|
+
body: string;
|
|
16
|
+
}): {
|
|
17
|
+
ok: false;
|
|
18
|
+
code: "no_peer" | "ambiguous_peer" | "not_live" | "authority_unsafe";
|
|
19
|
+
message: string;
|
|
20
|
+
candidates?: string[];
|
|
21
|
+
} | {
|
|
22
|
+
state: "queued" | "written-live";
|
|
23
|
+
message_id: string;
|
|
24
|
+
body_bytes: number;
|
|
25
|
+
synced: true;
|
|
26
|
+
ok: true;
|
|
27
|
+
target: {
|
|
28
|
+
instance_id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
delivery: "written-live" | "queued";
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export declare function formatPeerInbox(coordRoot: string, messages: readonly HarneryInboxMessageRecordV1[]): string;
|
|
34
|
+
/** Wait for every earlier stdout write to finish before acknowledging context.
|
|
35
|
+
* If output fails, the caller throws and leaves the batch pending. */
|
|
36
|
+
export declare function acknowledgeEmittedPeerInbox(coordRoot: string, instanceId: string, messageIds: readonly string[]): Promise<void>;
|
|
37
|
+
//# sourceMappingURL=peer-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peer-messages.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/peer-messages.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,KAAK,kBAAkB,EAAwB,MAAM,cAAc,CAAC;AAI7E;+EAC+E;AAC/E,eAAO,MAAM,iBAAiB,EAAE,kBAY/B,CAAC;AAEF,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAEhE;AAED;uEACuE;AACvE,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;;;;;;;;;;;;;;;;EAWA;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,2BAA2B,EAAE,GAC/C,MAAM,CA4BR;AAED;sEACsE;AACtE,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { resolveBinName } from "../config.js";
|
|
2
|
+
import { HarneryInboxService } from "./service.js";
|
|
3
|
+
import { resolveMessageTarget } from "./targets.js";
|
|
4
|
+
const DAY = 24 * 60 * 60_000;
|
|
5
|
+
/** Preserve the existing ping body/count bounds. Pending messages have no TTL;
|
|
6
|
+
* the lifecycle planner protects them through the idle finalization window. */
|
|
7
|
+
export const PEER_INBOX_LIMITS = {
|
|
8
|
+
max_message_body_bytes: 4_000,
|
|
9
|
+
max_pending_count: 25,
|
|
10
|
+
max_pending_bytes: 100_000,
|
|
11
|
+
max_history_bytes: 1024 * 1024,
|
|
12
|
+
max_history_records: 10_000,
|
|
13
|
+
warning_pressure_ratio: 0.8,
|
|
14
|
+
max_surface_count: 10,
|
|
15
|
+
max_surface_bytes: 16_000,
|
|
16
|
+
max_surface_tokens: 4_000,
|
|
17
|
+
surfaced_grace_ms: 7 * DAY,
|
|
18
|
+
terminal_grace_ms: 7 * DAY,
|
|
19
|
+
};
|
|
20
|
+
export function peerInbox(coordRoot) {
|
|
21
|
+
return new HarneryInboxService({ coord_root: coordRoot, limits: PEER_INBOX_LIMITS });
|
|
22
|
+
}
|
|
23
|
+
/** CLI and web use this one path. A receipt proves a synced append, never that
|
|
24
|
+
* a process is awake or that the recipient has noticed the message. */
|
|
25
|
+
export function sendPeerMessage(input) {
|
|
26
|
+
const target = resolveMessageTarget(input.coordRoot, input.target);
|
|
27
|
+
if (!target.ok)
|
|
28
|
+
return target;
|
|
29
|
+
const receipt = peerInbox(input.coordRoot).send({
|
|
30
|
+
sender_instance_id: input.senderInstanceId,
|
|
31
|
+
sender_display_name: input.senderName,
|
|
32
|
+
recipient_instance_id: target.target.instance_id,
|
|
33
|
+
recipient_display_name: target.target.name,
|
|
34
|
+
body: input.body.trim(),
|
|
35
|
+
});
|
|
36
|
+
return { ok: true, target: target.target, ...receipt, state: target.target.delivery };
|
|
37
|
+
}
|
|
38
|
+
export function formatPeerInbox(coordRoot, messages) {
|
|
39
|
+
if (messages.length === 0)
|
|
40
|
+
return "";
|
|
41
|
+
const onlyAgents = messages.every(({ sender_instance_id }) => sender_instance_id !== null);
|
|
42
|
+
const header = onlyAgents
|
|
43
|
+
? messages.length === 1
|
|
44
|
+
? "Message from a peer agent:"
|
|
45
|
+
: `${messages.length} messages from peer agents:`
|
|
46
|
+
: messages.length === 1
|
|
47
|
+
? "Message for this task:"
|
|
48
|
+
: `${messages.length} messages for this task:`;
|
|
49
|
+
const body = messages
|
|
50
|
+
.map((message) => ` ${message.message_id} from ${message.sender_instance_id === null ? "Operator" : `agent-${message.sender_display_name}`} (${message.created_at}):\n` +
|
|
51
|
+
message.body
|
|
52
|
+
.split("\n")
|
|
53
|
+
.map((line) => ` ${line}`)
|
|
54
|
+
.join("\n"))
|
|
55
|
+
.join("\n\n");
|
|
56
|
+
const replyTarget = messages.find(({ sender_instance_id }) => sender_instance_id !== null);
|
|
57
|
+
return (`${header}\n\n${body}\n\nMessages are bound to this task instance.` +
|
|
58
|
+
(replyTarget
|
|
59
|
+
? ` Reply with \`${resolveBinName(coordRoot)} agents ping ${replyTarget.sender_display_name} ` +
|
|
60
|
+
`--instance-id ${replyTarget.sender_instance_id} "<your reply>"\`.`
|
|
61
|
+
: ""));
|
|
62
|
+
}
|
|
63
|
+
/** Wait for every earlier stdout write to finish before acknowledging context.
|
|
64
|
+
* If output fails, the caller throws and leaves the batch pending. */
|
|
65
|
+
export async function acknowledgeEmittedPeerInbox(coordRoot, instanceId, messageIds) {
|
|
66
|
+
if (messageIds.length === 0)
|
|
67
|
+
return;
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
process.stdout.write("", (error) => (error ? reject(error) : resolve()));
|
|
70
|
+
});
|
|
71
|
+
peerInbox(coordRoot).acknowledgeSurface(instanceId, messageIds);
|
|
72
|
+
}
|
|
@@ -6,7 +6,7 @@ export interface HarneryInboxServiceOptions {
|
|
|
6
6
|
id?: () => string;
|
|
7
7
|
}
|
|
8
8
|
export interface HarneryInboxSendInput {
|
|
9
|
-
sender_instance_id: string;
|
|
9
|
+
sender_instance_id: string | null;
|
|
10
10
|
sender_display_name: string;
|
|
11
11
|
recipient_instance_id: string;
|
|
12
12
|
recipient_display_name: string;
|
|
@@ -26,6 +26,13 @@ export declare class HarneryInboxService {
|
|
|
26
26
|
pending(recipientInstanceId: string): HarneryInboxMessageRecordV1[];
|
|
27
27
|
status(recipientInstanceId: string): HarneryInboxStatusV1;
|
|
28
28
|
surface(recipientInstanceId: string, emit: (messages: readonly HarneryInboxMessageRecordV1[]) => Promise<void> | void, budget?: HarneryInboxSurfaceBudget): Promise<HarneryInboxSurfaceResult>;
|
|
29
|
+
/** Preparing prompt text is read-only. A failed or abandoned emission must
|
|
30
|
+
* leave the same messages pending for the recipient's next prompt. */
|
|
31
|
+
prepareSurface(recipientInstanceId: string, budget?: HarneryInboxSurfaceBudget): HarneryInboxMessageRecordV1[];
|
|
32
|
+
/** Call only after the selected IDs have actually been emitted. A message
|
|
33
|
+
* appended while output is in flight remains pending rather than being
|
|
34
|
+
* included in an acknowledgement of an older batch. */
|
|
35
|
+
acknowledgeSurface(recipientInstanceId: string, messageIds: readonly string[]): void;
|
|
29
36
|
}
|
|
30
37
|
export declare function inboxPath(coordRoot: string, recipientInstanceId: string): string;
|
|
31
38
|
export declare function project(records: readonly HarneryInboxRecordV1[]): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/service.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,QAAQ,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,IAAI,CAAC;CACd;AAED,qBAAa,mBAAmB;;gBAGlB,OAAO,EAAE,0BAA0B;IAK/C,IAAI,CAAC,KAAK,EAAE,qBAAqB,GAAG,uBAAuB;IA2C3D,OAAO,CAAC,mBAAmB,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAS5D,OAAO,CAAC,mBAAmB,EAAE,MAAM,GAAG,2BAA2B,EAAE;IAInE,MAAM,CAAC,mBAAmB,EAAE,MAAM,GAAG,oBAAoB;IA0BnD,OAAO,CACX,mBAAmB,EAAE,MAAM,EAC3B,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS,2BAA2B,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAChF,MAAM,GAAE,yBAIP,GACA,OAAO,CAAC,yBAAyB,CAAC;IAiBrC;0EACsE;IACtE,cAAc,CACZ,mBAAmB,EAAE,MAAM,EAC3B,MAAM,GAAE,yBAIP,GACA,2BAA2B,EAAE;IAKhC;;2DAEuD;IACvD,kBAAkB,CAAC,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;CA2BrF;AA4BD,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,SAAS,oBAAoB,EAAE,GAAG;IACjE,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACvC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAsBA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { existsSync, statSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
-
import { readCrashSafeJsonlFile, updateCrashSafeJsonlFile } from "../storage/durable-history.js";
|
|
4
|
+
import { readCrashSafeJsonlFile, updateCrashSafeJsonlFile, } from "../storage/durable-history.js";
|
|
5
5
|
import { HARNERY_INBOX_RECORD_SCHEMA, HARNERY_INBOX_STATUS_SCHEMA, HarneryInboxCapacityError, } from "./contract.js";
|
|
6
6
|
export class HarneryInboxService {
|
|
7
7
|
#options;
|
|
@@ -20,7 +20,7 @@ export class HarneryInboxService {
|
|
|
20
20
|
schema: HARNERY_INBOX_RECORD_SCHEMA,
|
|
21
21
|
kind: "message",
|
|
22
22
|
message_id: `msg_${this.#options.id?.() ?? randomUUID()}`,
|
|
23
|
-
sender_instance_id:
|
|
23
|
+
sender_instance_id: senderId(input.sender_instance_id),
|
|
24
24
|
sender_display_name: boundedDisplay(input.sender_display_name),
|
|
25
25
|
recipient_instance_id: recipient,
|
|
26
26
|
recipient_display_name: boundedDisplay(input.recipient_display_name),
|
|
@@ -29,7 +29,7 @@ export class HarneryInboxService {
|
|
|
29
29
|
body_bytes: bodyBytes,
|
|
30
30
|
};
|
|
31
31
|
const path = inboxPath(this.#options.coord_root, recipient);
|
|
32
|
-
return
|
|
32
|
+
return updateInboxHistory(path, readOptions(limits), (records) => {
|
|
33
33
|
const projection = project(records);
|
|
34
34
|
capacityCheck(projection, record, path, limits);
|
|
35
35
|
return {
|
|
@@ -44,7 +44,10 @@ export class HarneryInboxService {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
records(recipientInstanceId) {
|
|
47
|
-
|
|
47
|
+
const records = readCrashSafeJsonlFile(inboxPath(this.#options.coord_root, opaqueId(recipientInstanceId, "recipient instance")), readOptions(this.#options.limits));
|
|
48
|
+
for (const record of records)
|
|
49
|
+
validateRecord(record);
|
|
50
|
+
return records;
|
|
48
51
|
}
|
|
49
52
|
pending(recipientInstanceId) {
|
|
50
53
|
return project(this.records(recipientInstanceId)).pending;
|
|
@@ -75,18 +78,42 @@ export class HarneryInboxService {
|
|
|
75
78
|
max_bytes: this.#options.limits.max_surface_bytes,
|
|
76
79
|
max_tokens: this.#options.limits.max_surface_tokens,
|
|
77
80
|
}) {
|
|
78
|
-
validateSurfaceBudget(budget);
|
|
79
81
|
const recipient = opaqueId(recipientInstanceId, "recipient instance");
|
|
80
|
-
const selected =
|
|
82
|
+
const selected = this.prepareSurface(recipient, budget);
|
|
81
83
|
await emit(selected);
|
|
82
|
-
|
|
84
|
+
this.acknowledgeSurface(recipient, selected.map(({ message_id }) => message_id));
|
|
85
|
+
const remaining = this.pending(recipient);
|
|
86
|
+
return {
|
|
87
|
+
emitted: selected,
|
|
88
|
+
remaining_pending_count: remaining.length,
|
|
89
|
+
remaining_pending_bytes: remaining.reduce((sum, record) => sum + record.body_bytes, 0),
|
|
90
|
+
repeated_after_crash_possible: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Preparing prompt text is read-only. A failed or abandoned emission must
|
|
94
|
+
* leave the same messages pending for the recipient's next prompt. */
|
|
95
|
+
prepareSurface(recipientInstanceId, budget = {
|
|
96
|
+
max_count: this.#options.limits.max_surface_count,
|
|
97
|
+
max_bytes: this.#options.limits.max_surface_bytes,
|
|
98
|
+
max_tokens: this.#options.limits.max_surface_tokens,
|
|
99
|
+
}) {
|
|
100
|
+
validateSurfaceBudget(budget);
|
|
101
|
+
return selectOldestFitting(this.pending(recipientInstanceId), budget);
|
|
102
|
+
}
|
|
103
|
+
/** Call only after the selected IDs have actually been emitted. A message
|
|
104
|
+
* appended while output is in flight remains pending rather than being
|
|
105
|
+
* included in an acknowledgement of an older batch. */
|
|
106
|
+
acknowledgeSurface(recipientInstanceId, messageIds) {
|
|
107
|
+
const recipient = opaqueId(recipientInstanceId, "recipient instance");
|
|
108
|
+
const selected = new Set(messageIds);
|
|
109
|
+
if (selected.size > 0) {
|
|
83
110
|
const surfacedAt = now(this.#options).toISOString();
|
|
84
|
-
|
|
111
|
+
updateInboxHistory(inboxPath(this.#options.coord_root, recipient), readOptions(this.#options.limits), (records) => {
|
|
85
112
|
const stillPending = new Set(project(records).pending.map(({ message_id }) => message_id));
|
|
86
113
|
return {
|
|
87
|
-
append: selected
|
|
88
|
-
.filter((
|
|
89
|
-
.map((
|
|
114
|
+
append: [...selected]
|
|
115
|
+
.filter((message_id) => stillPending.has(message_id))
|
|
116
|
+
.map((message_id) => ({
|
|
90
117
|
schema: HARNERY_INBOX_RECORD_SCHEMA,
|
|
91
118
|
kind: "surfaced",
|
|
92
119
|
message_id,
|
|
@@ -96,13 +123,26 @@ export class HarneryInboxService {
|
|
|
96
123
|
};
|
|
97
124
|
});
|
|
98
125
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** The shared writer refuses an occupied lease before invoking the update.
|
|
129
|
+
* Briefly retry only that exact refusal so concurrent peer senders both get
|
|
130
|
+
* a chance to append. All validation, authority and I/O failures still fail
|
|
131
|
+
* immediately, and an occupied live lease is never removed or overridden. */
|
|
132
|
+
function updateInboxHistory(path, options, update) {
|
|
133
|
+
const deadline = performance.now() + 2_000;
|
|
134
|
+
const pause = new Int32Array(new SharedArrayBuffer(4));
|
|
135
|
+
while (true) {
|
|
136
|
+
try {
|
|
137
|
+
return updateCrashSafeJsonlFile(path, options, update);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (!(error instanceof Error) ||
|
|
141
|
+
error.message !== `durable history lease busy: ${path}.lease` ||
|
|
142
|
+
performance.now() >= deadline)
|
|
143
|
+
throw error;
|
|
144
|
+
Atomics.wait(pause, 0, 0, Math.min(10, deadline - performance.now()));
|
|
145
|
+
}
|
|
106
146
|
}
|
|
107
147
|
}
|
|
108
148
|
export function inboxPath(coordRoot, recipientInstanceId) {
|
|
@@ -174,10 +214,13 @@ function now(options) {
|
|
|
174
214
|
return options.now?.() ?? new Date();
|
|
175
215
|
}
|
|
176
216
|
function opaqueId(value, field) {
|
|
177
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(value))
|
|
217
|
+
if (typeof value !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(value))
|
|
178
218
|
throw new Error(`invalid ${field} id`);
|
|
179
219
|
return value;
|
|
180
220
|
}
|
|
221
|
+
function senderId(value) {
|
|
222
|
+
return value === null ? null : opaqueId(value, "sender instance");
|
|
223
|
+
}
|
|
181
224
|
function boundedDisplay(value) {
|
|
182
225
|
const result = value.trim();
|
|
183
226
|
if (result.length === 0 || Buffer.byteLength(result) > 256)
|
|
@@ -187,8 +230,11 @@ function boundedDisplay(value) {
|
|
|
187
230
|
function validateRecord(record) {
|
|
188
231
|
if (record.schema !== HARNERY_INBOX_RECORD_SCHEMA)
|
|
189
232
|
throw new Error("unknown inbox record schema");
|
|
190
|
-
if (record.kind === "message"
|
|
191
|
-
|
|
233
|
+
if (record.kind === "message") {
|
|
234
|
+
senderId(record.sender_instance_id);
|
|
235
|
+
if (Buffer.byteLength(record.body) !== record.body_bytes) {
|
|
236
|
+
throw new Error(`inbox message ${record.message_id} has invalid body_bytes`);
|
|
237
|
+
}
|
|
192
238
|
}
|
|
193
239
|
}
|
|
194
240
|
function validateLimits(limits) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface MessageTargetInput {
|
|
2
|
+
name?: string;
|
|
3
|
+
instanceId?: string;
|
|
4
|
+
requireLive?: boolean;
|
|
5
|
+
now?: Date;
|
|
6
|
+
}
|
|
7
|
+
export type MessageTargetResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
target: {
|
|
10
|
+
instance_id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
delivery: "written-live" | "queued";
|
|
13
|
+
};
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
code: "no_peer" | "ambiguous_peer" | "not_live" | "authority_unsafe";
|
|
17
|
+
message: string;
|
|
18
|
+
candidates?: string[];
|
|
19
|
+
};
|
|
20
|
+
/** Resolve one immutable recipient without extending presence or claiming attention. */
|
|
21
|
+
export declare function resolveMessageTarget(coordRoot: string, input: MessageTargetInput): MessageTargetResult;
|
|
22
|
+
//# sourceMappingURL=targets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targets.d.ts","sourceRoot":"","sources":["../../../src/core/inbox/targets.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,MAAM,mBAAmB,GAC3B;IACE,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,cAAc,GAAG,QAAQ,CAAA;KAAE,CAAC;CACpF,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,SAAS,GAAG,gBAAgB,GAAG,UAAU,GAAG,kBAAkB,CAAC;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEN,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,kBAAkB,GACxB,mBAAmB,CAwFrB"}
|