@sentropic/h2a 0.96.0 → 0.96.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +74 -27
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/drive/cluster-mesh-outer.d.ts +44 -0
- package/dist/runtime/drive/cluster-mesh-outer.d.ts.map +1 -0
- package/dist/runtime/drive/cluster-mesh-outer.js +70 -0
- package/dist/runtime/drive/cluster-mesh-outer.js.map +1 -0
- package/dist/runtime/drive/pty-actuator.d.ts +54 -0
- package/dist/runtime/drive/pty-actuator.d.ts.map +1 -0
- package/dist/runtime/drive/pty-actuator.js +371 -0
- package/dist/runtime/drive/pty-actuator.js.map +1 -0
- package/dist/runtime/local-files/paths.js +1 -1
- package/dist/runtime/local-files/paths.js.map +1 -1
- package/dist/runtime/local-files/presence.d.ts.map +1 -1
- package/dist/runtime/local-files/presence.js +10 -1
- package/dist/runtime/local-files/presence.js.map +1 -1
- package/dist/runtime/mcp/agent-launch.d.ts +2 -1
- package/dist/runtime/mcp/agent-launch.d.ts.map +1 -1
- package/dist/runtime/mcp/agent-launch.js +48 -8
- package/dist/runtime/mcp/agent-launch.js.map +1 -1
- package/dist/runtime/mcp/tools.d.ts.map +1 -1
- package/dist/runtime/mcp/tools.js +19 -6
- package/dist/runtime/mcp/tools.js.map +1 -1
- package/dist/runtime/memory/admit-client.d.ts +30 -0
- package/dist/runtime/memory/admit-client.d.ts.map +1 -0
- package/dist/runtime/memory/admit-client.js +44 -0
- package/dist/runtime/memory/admit-client.js.map +1 -0
- package/dist/runtime/memory/d11-ceremony.d.ts +731 -0
- package/dist/runtime/memory/d11-ceremony.d.ts.map +1 -0
- package/dist/runtime/memory/d11-ceremony.js +1282 -0
- package/dist/runtime/memory/d11-ceremony.js.map +1 -0
- package/dist/runtime/memory/graphify-contracts-v2.vendored.d.ts +922 -0
- package/dist/runtime/memory/graphify-contracts-v2.vendored.d.ts.map +1 -0
- package/dist/runtime/memory/graphify-contracts-v2.vendored.js +2 -0
- package/dist/runtime/memory/graphify-contracts-v2.vendored.js.map +1 -0
- package/dist/runtime/memory/note-builder.d.ts +83 -0
- package/dist/runtime/memory/note-builder.d.ts.map +1 -0
- package/dist/runtime/memory/note-builder.js +89 -0
- package/dist/runtime/memory/note-builder.js.map +1 -0
- package/dist/runtime/memory/port-v1.d.ts +235 -0
- package/dist/runtime/memory/port-v1.d.ts.map +1 -0
- package/dist/runtime/memory/port-v1.js +129 -0
- package/dist/runtime/memory/port-v1.js.map +1 -0
- package/dist/runtime/memory/port-v2-adapter.d.ts +117 -0
- package/dist/runtime/memory/port-v2-adapter.d.ts.map +1 -0
- package/dist/runtime/memory/port-v2-adapter.js +140 -0
- package/dist/runtime/memory/port-v2-adapter.js.map +1 -0
- package/dist/runtime/memory/port-v2-compat.d.ts +50 -0
- package/dist/runtime/memory/port-v2-compat.d.ts.map +1 -0
- package/dist/runtime/memory/port-v2-compat.js +102 -0
- package/dist/runtime/memory/port-v2-compat.js.map +1 -0
- package/dist/runtime/memory/preflight.d.ts +17 -0
- package/dist/runtime/memory/preflight.d.ts.map +1 -0
- package/dist/runtime/memory/preflight.js +18 -0
- package/dist/runtime/memory/preflight.js.map +1 -0
- package/dist/runtime/memory/promote-client.d.ts +172 -0
- package/dist/runtime/memory/promote-client.d.ts.map +1 -0
- package/dist/runtime/memory/promote-client.js +168 -0
- package/dist/runtime/memory/promote-client.js.map +1 -0
- package/dist/runtime/memory/recall-client.d.ts +74 -0
- package/dist/runtime/memory/recall-client.d.ts.map +1 -0
- package/dist/runtime/memory/recall-client.js +91 -0
- package/dist/runtime/memory/recall-client.js.map +1 -0
- package/package.json +6 -3
- package/skills/h2a-run/SKILL.md +39 -6
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promote client — D11 double-consensus, CLIENT-SIDE orchestration (build brief
|
|
3
|
+
* slice 6, this build's WP11-slice-3). Two independent high-grade legs each
|
|
4
|
+
* write a VERDICT (third-party-readable, never self-report); this module
|
|
5
|
+
* enforces the double-consensus BEFORE `promoteNote` is ever called, and
|
|
6
|
+
* dispatches through an INJECTED `MemoryProducerPort` exactly like
|
|
7
|
+
* `admit-client.ts` does for admission.
|
|
8
|
+
*
|
|
9
|
+
* SCOPE (bounded): this is the client's consensus *enforcement* + evidence
|
|
10
|
+
* *assembly*. Launching the two model legs and interpreting/reading verdict
|
|
11
|
+
* FILES is out of scope here — a "leg" is an opaque `{model, session}` pair
|
|
12
|
+
* and a verdict ref is an opaque locator string, both supplied by the caller
|
|
13
|
+
* (the conductor orchestrating the legs). graphify's gate is the other half
|
|
14
|
+
* of this boundary: it re-checks structurally (refs + attestation shape), it
|
|
15
|
+
* never trusts this client's say-so either — this module's own refusal is a
|
|
16
|
+
* courtesy that saves a round trip, not the authority.
|
|
17
|
+
*
|
|
18
|
+
* REF, NOT INLINE: `independence_attestation` in `PromotionEvidence` is a
|
|
19
|
+
* LOCATOR string, exactly like `leg1_verdict_ref`/`leg2_verdict_ref` (§9.4).
|
|
20
|
+
* graphify's evidence holds the three refs; the attestation ARTIFACT (a file
|
|
21
|
+
* — see `./d11-ceremony.ts`, which writes it) holds the actual
|
|
22
|
+
* `IndependenceAttestation` content that ref points to. This module never
|
|
23
|
+
* serializes the attestation object itself into the evidence — the caller
|
|
24
|
+
* writes the artifact and hands `assemblePromotionEvidence` the ref it got
|
|
25
|
+
* back.
|
|
26
|
+
*
|
|
27
|
+
* I5 — fail-closed, TWO LAYERS:
|
|
28
|
+
* 1. `checkDoubleConsensusPreconditions` is a PURE, port-less function. It
|
|
29
|
+
* cannot call the port even if it wanted to — a refusal here makes
|
|
30
|
+
* "promoteNote never called" true BY CONSTRUCTION, not by a runtime
|
|
31
|
+
* guard that could be bypassed or forgotten.
|
|
32
|
+
* 2. `promoteNote` (the raw dispatch) mirrors `admit-client.ts`: an absent
|
|
33
|
+
* port, or one that throws/rejects, REFUSES — never a silent
|
|
34
|
+
* `promoted: true` a caller might mistake for success.
|
|
35
|
+
* `promoteNoteWithDoubleConsensus` composes both layers for a caller that
|
|
36
|
+
* wants one call: check → assemble → dispatch, short-circuiting before the
|
|
37
|
+
* port is ever touched on any local refusal.
|
|
38
|
+
*
|
|
39
|
+
* STRUCTURAL independence, not merely attested: the guard computes leg
|
|
40
|
+
* distinctness and the author/session collision from the VERDICTS'
|
|
41
|
+
* own `leg` field, never from `attestation.distinctModels` /
|
|
42
|
+
* `attestation.distinctSessions` — those booleans are a self-report from the
|
|
43
|
+
* orchestrator and reach graphify only by reference (the attestation
|
|
44
|
+
* ARTIFACT, not this in-memory object — see the REF note above), but this
|
|
45
|
+
* client does not treat them as authoritative for its own local refusal. A
|
|
46
|
+
* lying attestation cannot paper over two identical verdict legs.
|
|
47
|
+
*
|
|
48
|
+
* I1 — durable identity slot: `leg.model`, `leg.session`, `authorId` and
|
|
49
|
+
* `noteId` are all OPAQUE strings here — compared with `===` only, never
|
|
50
|
+
* parsed, split or derived-from. This module reserves the slot; it does not
|
|
51
|
+
* mint or interpret conversation-scoped identity.
|
|
52
|
+
* I4 — no new capabilities vocabulary opens here; `MemoryVerdict` and
|
|
53
|
+
* `IndependenceAttestation` are D11-specific data shapes, not a second
|
|
54
|
+
* capabilities taxonomy, and `PromotionEvidence`/`PromotionOutcome` are
|
|
55
|
+
* reused unchanged from `./port-v1.ts`.
|
|
56
|
+
*/
|
|
57
|
+
import type { MemoryContext, MemoryProducerPort, PromotionEvidence, PromotionOutcome } from "./port-v1.js";
|
|
58
|
+
/** An opaque leg identity — never parsed, only compared for equality. */
|
|
59
|
+
export interface LegIdentity {
|
|
60
|
+
readonly model: string;
|
|
61
|
+
readonly session: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A single leg's verdict on a note. Written by the leg itself, to a
|
|
65
|
+
* third-party-readable file; the FILE (referenced elsewhere by an opaque
|
|
66
|
+
* `*Ref` string) is the record, not this in-memory shape — this type is how
|
|
67
|
+
* the client reasons about a verdict once the caller has already resolved
|
|
68
|
+
* one, not a re-implementation of reading the file.
|
|
69
|
+
*/
|
|
70
|
+
export interface MemoryVerdict {
|
|
71
|
+
readonly noteId: string;
|
|
72
|
+
readonly verdict: "GO" | "NO-GO";
|
|
73
|
+
readonly leg: LegIdentity;
|
|
74
|
+
readonly at: number;
|
|
75
|
+
readonly reason?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The orchestrator's attestation that the two legs were actually independent.
|
|
79
|
+
* This is the CONTENT of the attestation artifact a ceremony writes (see
|
|
80
|
+
* `./d11-ceremony.ts`); `PromotionEvidence.independence_attestation` carries
|
|
81
|
+
* only that artifact's REF (locator string), never this object inline — see
|
|
82
|
+
* the module doc's REF note. This client does NOT treat
|
|
83
|
+
* `distinctModels`/`distinctSessions` here as authoritative for its own
|
|
84
|
+
* refusal; it recomputes distinctness from the verdicts.
|
|
85
|
+
*/
|
|
86
|
+
export interface IndependenceAttestation {
|
|
87
|
+
readonly leg1: LegIdentity;
|
|
88
|
+
readonly leg2: LegIdentity;
|
|
89
|
+
readonly distinctModels: boolean;
|
|
90
|
+
readonly distinctSessions: boolean;
|
|
91
|
+
readonly verdictsWrittenBeforeCrossVisibility: boolean;
|
|
92
|
+
readonly orchestrator: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Pack the two verdict-file references and the attestation REFERENCE into the
|
|
96
|
+
* port's `PromotionEvidence` shape. Pure: same inputs → same output, no I/O.
|
|
97
|
+
*
|
|
98
|
+
* `attestationRef` is a LOCATOR string, exactly like `leg1Ref`/`leg2Ref` — NOT
|
|
99
|
+
* the `IndependenceAttestation` object serialized inline. graphify's evidence
|
|
100
|
+
* holds the three refs; the attestation ARTIFACT (a file, written by whoever
|
|
101
|
+
* ran the ceremony — see `./d11-ceremony.ts`) holds the actual
|
|
102
|
+
* `{leg1,leg2,distinct*,...}` content that ref points to. Passed through
|
|
103
|
+
* unchanged — no `JSON.stringify` — because there is nothing to serialize
|
|
104
|
+
* here: the caller already resolved the ref by writing the artifact.
|
|
105
|
+
*/
|
|
106
|
+
export declare function assemblePromotionEvidence(leg1Ref: string, leg2Ref: string, attestationRef: string): PromotionEvidence;
|
|
107
|
+
export type DoubleConsensusCheck = {
|
|
108
|
+
ok: true;
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
reason: string;
|
|
112
|
+
};
|
|
113
|
+
export interface DoubleConsensusInput {
|
|
114
|
+
readonly verdicts: readonly MemoryVerdict[];
|
|
115
|
+
readonly attestation: IndependenceAttestation;
|
|
116
|
+
readonly leg1Ref: string;
|
|
117
|
+
readonly leg2Ref: string;
|
|
118
|
+
/** The note's author (opaque id) — a leg's `session` must not equal this. */
|
|
119
|
+
readonly authorId: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* REFUSES to let a caller proceed to `promoteNote` unless ALL hold:
|
|
123
|
+
* - exactly 2 verdicts;
|
|
124
|
+
* - BOTH verdicts are `"GO"` (any `"NO-GO"`, or a split, refuses);
|
|
125
|
+
* - the two verdicts reference the SAME note (a mismatch refuses — two
|
|
126
|
+
* verdicts for different notes cannot jointly promote either one);
|
|
127
|
+
* - the two legs are STRUCTURALLY distinct — computed from `verdict.leg`
|
|
128
|
+
* itself, not from the attestation's own booleans (not the same
|
|
129
|
+
* model+session counted twice);
|
|
130
|
+
* - `leg1Ref !== leg2Ref` (the same verdict-file reference cannot stand in
|
|
131
|
+
* for both legs);
|
|
132
|
+
* - neither leg's `session` equals `authorId` (separation of powers — a
|
|
133
|
+
* leg cannot review its own note).
|
|
134
|
+
* This function takes no port: it cannot call `promoteNote` even if it
|
|
135
|
+
* wanted to, so its refusal holds by construction, not by convention.
|
|
136
|
+
*/
|
|
137
|
+
export declare function checkDoubleConsensusPreconditions(input: DoubleConsensusInput): DoubleConsensusCheck;
|
|
138
|
+
export interface PromoteNoteResult {
|
|
139
|
+
readonly outcome: PromotionOutcome;
|
|
140
|
+
/** True when refused locally (no port, unreachable port, or a precondition) — the port was never called, or its call never resolved to a success. */
|
|
141
|
+
readonly localOnly: boolean;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Dispatch `evidence` through `port.promoteNote(noteId, evidence, ctx)`.
|
|
145
|
+
* `port` is injected and may be `undefined`/`null` (I5: fail-closed default,
|
|
146
|
+
* not a wiring error — mirrors `admit-client.ts`). This function does NOT
|
|
147
|
+
* itself enforce the double-consensus preconditions — that is
|
|
148
|
+
* `checkDoubleConsensusPreconditions`'s job, run BEFORE this is called (see
|
|
149
|
+
* `promoteNoteWithDoubleConsensus` for the composed entry point).
|
|
150
|
+
*/
|
|
151
|
+
export declare function promoteNote(noteId: string, evidence: PromotionEvidence, ctx: MemoryContext, port: MemoryProducerPort | undefined | null): Promise<PromoteNoteResult>;
|
|
152
|
+
export interface PromoteNoteWithDoubleConsensusInput extends DoubleConsensusInput {
|
|
153
|
+
readonly noteId: string;
|
|
154
|
+
readonly ctx: MemoryContext;
|
|
155
|
+
/**
|
|
156
|
+
* The attestation ARTIFACT's locator — distinct from `attestation` (the
|
|
157
|
+
* in-memory object `checkDoubleConsensusPreconditions` uses for its own
|
|
158
|
+
* completeness gate, unchanged by this reconciliation). This is what
|
|
159
|
+
* actually travels to the port via `assemblePromotionEvidence`.
|
|
160
|
+
*/
|
|
161
|
+
readonly attestationRef: string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The client-side D11 promotion entry point: runs
|
|
165
|
+
* `checkDoubleConsensusPreconditions` first, and ONLY on `{ok: true}`
|
|
166
|
+
* assembles the evidence (`assemblePromotionEvidence`) and dispatches
|
|
167
|
+
* (`promoteNote`). A local refusal short-circuits BEFORE the port is ever
|
|
168
|
+
* touched — the composed call, not just the pure guard in isolation, is what
|
|
169
|
+
* a conductor actually invokes once both legs have written their verdicts.
|
|
170
|
+
*/
|
|
171
|
+
export declare function promoteNoteWithDoubleConsensus(input: PromoteNoteWithDoubleConsensusInput, port: MemoryProducerPort | undefined | null): Promise<PromoteNoteResult>;
|
|
172
|
+
//# sourceMappingURL=promote-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promote-client.d.ts","sourceRoot":"","sources":["../../../src/runtime/memory/promote-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAM3G,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,oCAAoC,EAAE,OAAO,CAAC;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GACrB,iBAAiB,CAMnB;AAMD,MAAM,MAAM,oBAAoB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,uBAAuB,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAUD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,oBAAoB,GAAG,oBAAoB,CAwCnG;AAMD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,qJAAqJ;IACrJ,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAMD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,iBAAiB,EAC3B,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,kBAAkB,GAAG,SAAS,GAAG,IAAI,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CAY5B;AAMD,MAAM,WAAW,mCAAoC,SAAQ,oBAAoB;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,mCAAmC,EAC1C,IAAI,EAAE,kBAAkB,GAAG,SAAS,GAAG,IAAI,GAC1C,OAAO,CAAC,iBAAiB,CAAC,CAQ5B"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promote client — D11 double-consensus, CLIENT-SIDE orchestration (build brief
|
|
3
|
+
* slice 6, this build's WP11-slice-3). Two independent high-grade legs each
|
|
4
|
+
* write a VERDICT (third-party-readable, never self-report); this module
|
|
5
|
+
* enforces the double-consensus BEFORE `promoteNote` is ever called, and
|
|
6
|
+
* dispatches through an INJECTED `MemoryProducerPort` exactly like
|
|
7
|
+
* `admit-client.ts` does for admission.
|
|
8
|
+
*
|
|
9
|
+
* SCOPE (bounded): this is the client's consensus *enforcement* + evidence
|
|
10
|
+
* *assembly*. Launching the two model legs and interpreting/reading verdict
|
|
11
|
+
* FILES is out of scope here — a "leg" is an opaque `{model, session}` pair
|
|
12
|
+
* and a verdict ref is an opaque locator string, both supplied by the caller
|
|
13
|
+
* (the conductor orchestrating the legs). graphify's gate is the other half
|
|
14
|
+
* of this boundary: it re-checks structurally (refs + attestation shape), it
|
|
15
|
+
* never trusts this client's say-so either — this module's own refusal is a
|
|
16
|
+
* courtesy that saves a round trip, not the authority.
|
|
17
|
+
*
|
|
18
|
+
* REF, NOT INLINE: `independence_attestation` in `PromotionEvidence` is a
|
|
19
|
+
* LOCATOR string, exactly like `leg1_verdict_ref`/`leg2_verdict_ref` (§9.4).
|
|
20
|
+
* graphify's evidence holds the three refs; the attestation ARTIFACT (a file
|
|
21
|
+
* — see `./d11-ceremony.ts`, which writes it) holds the actual
|
|
22
|
+
* `IndependenceAttestation` content that ref points to. This module never
|
|
23
|
+
* serializes the attestation object itself into the evidence — the caller
|
|
24
|
+
* writes the artifact and hands `assemblePromotionEvidence` the ref it got
|
|
25
|
+
* back.
|
|
26
|
+
*
|
|
27
|
+
* I5 — fail-closed, TWO LAYERS:
|
|
28
|
+
* 1. `checkDoubleConsensusPreconditions` is a PURE, port-less function. It
|
|
29
|
+
* cannot call the port even if it wanted to — a refusal here makes
|
|
30
|
+
* "promoteNote never called" true BY CONSTRUCTION, not by a runtime
|
|
31
|
+
* guard that could be bypassed or forgotten.
|
|
32
|
+
* 2. `promoteNote` (the raw dispatch) mirrors `admit-client.ts`: an absent
|
|
33
|
+
* port, or one that throws/rejects, REFUSES — never a silent
|
|
34
|
+
* `promoted: true` a caller might mistake for success.
|
|
35
|
+
* `promoteNoteWithDoubleConsensus` composes both layers for a caller that
|
|
36
|
+
* wants one call: check → assemble → dispatch, short-circuiting before the
|
|
37
|
+
* port is ever touched on any local refusal.
|
|
38
|
+
*
|
|
39
|
+
* STRUCTURAL independence, not merely attested: the guard computes leg
|
|
40
|
+
* distinctness and the author/session collision from the VERDICTS'
|
|
41
|
+
* own `leg` field, never from `attestation.distinctModels` /
|
|
42
|
+
* `attestation.distinctSessions` — those booleans are a self-report from the
|
|
43
|
+
* orchestrator and reach graphify only by reference (the attestation
|
|
44
|
+
* ARTIFACT, not this in-memory object — see the REF note above), but this
|
|
45
|
+
* client does not treat them as authoritative for its own local refusal. A
|
|
46
|
+
* lying attestation cannot paper over two identical verdict legs.
|
|
47
|
+
*
|
|
48
|
+
* I1 — durable identity slot: `leg.model`, `leg.session`, `authorId` and
|
|
49
|
+
* `noteId` are all OPAQUE strings here — compared with `===` only, never
|
|
50
|
+
* parsed, split or derived-from. This module reserves the slot; it does not
|
|
51
|
+
* mint or interpret conversation-scoped identity.
|
|
52
|
+
* I4 — no new capabilities vocabulary opens here; `MemoryVerdict` and
|
|
53
|
+
* `IndependenceAttestation` are D11-specific data shapes, not a second
|
|
54
|
+
* capabilities taxonomy, and `PromotionEvidence`/`PromotionOutcome` are
|
|
55
|
+
* reused unchanged from `./port-v1.ts`.
|
|
56
|
+
*/
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// assemblePromotionEvidence — pure, packs refs + attestation into the port's shape.
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Pack the two verdict-file references and the attestation REFERENCE into the
|
|
62
|
+
* port's `PromotionEvidence` shape. Pure: same inputs → same output, no I/O.
|
|
63
|
+
*
|
|
64
|
+
* `attestationRef` is a LOCATOR string, exactly like `leg1Ref`/`leg2Ref` — NOT
|
|
65
|
+
* the `IndependenceAttestation` object serialized inline. graphify's evidence
|
|
66
|
+
* holds the three refs; the attestation ARTIFACT (a file, written by whoever
|
|
67
|
+
* ran the ceremony — see `./d11-ceremony.ts`) holds the actual
|
|
68
|
+
* `{leg1,leg2,distinct*,...}` content that ref points to. Passed through
|
|
69
|
+
* unchanged — no `JSON.stringify` — because there is nothing to serialize
|
|
70
|
+
* here: the caller already resolved the ref by writing the artifact.
|
|
71
|
+
*/
|
|
72
|
+
export function assemblePromotionEvidence(leg1Ref, leg2Ref, attestationRef) {
|
|
73
|
+
return {
|
|
74
|
+
leg1_verdict_ref: leg1Ref,
|
|
75
|
+
leg2_verdict_ref: leg2Ref,
|
|
76
|
+
independence_attestation: attestationRef
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function refuseCheck(reason) {
|
|
80
|
+
return { ok: false, reason };
|
|
81
|
+
}
|
|
82
|
+
function sameLeg(a, b) {
|
|
83
|
+
return a.model === b.model && a.session === b.session;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* REFUSES to let a caller proceed to `promoteNote` unless ALL hold:
|
|
87
|
+
* - exactly 2 verdicts;
|
|
88
|
+
* - BOTH verdicts are `"GO"` (any `"NO-GO"`, or a split, refuses);
|
|
89
|
+
* - the two verdicts reference the SAME note (a mismatch refuses — two
|
|
90
|
+
* verdicts for different notes cannot jointly promote either one);
|
|
91
|
+
* - the two legs are STRUCTURALLY distinct — computed from `verdict.leg`
|
|
92
|
+
* itself, not from the attestation's own booleans (not the same
|
|
93
|
+
* model+session counted twice);
|
|
94
|
+
* - `leg1Ref !== leg2Ref` (the same verdict-file reference cannot stand in
|
|
95
|
+
* for both legs);
|
|
96
|
+
* - neither leg's `session` equals `authorId` (separation of powers — a
|
|
97
|
+
* leg cannot review its own note).
|
|
98
|
+
* This function takes no port: it cannot call `promoteNote` even if it
|
|
99
|
+
* wanted to, so its refusal holds by construction, not by convention.
|
|
100
|
+
*/
|
|
101
|
+
export function checkDoubleConsensusPreconditions(input) {
|
|
102
|
+
const { verdicts, attestation, leg1Ref, leg2Ref, authorId } = input;
|
|
103
|
+
if (!attestation) {
|
|
104
|
+
return refuseCheck("an independence attestation is required");
|
|
105
|
+
}
|
|
106
|
+
if (!Array.isArray(verdicts) || verdicts.length !== 2) {
|
|
107
|
+
const got = Array.isArray(verdicts) ? String(verdicts.length) : "a non-array";
|
|
108
|
+
return refuseCheck(`double consensus requires exactly 2 verdicts, got ${got}`);
|
|
109
|
+
}
|
|
110
|
+
const [v1, v2] = verdicts;
|
|
111
|
+
if (v1.verdict !== "GO" || v2.verdict !== "GO") {
|
|
112
|
+
return refuseCheck("double consensus requires BOTH legs to verdict GO — a NO-GO or a split refuses locally");
|
|
113
|
+
}
|
|
114
|
+
if (v1.noteId !== v2.noteId) {
|
|
115
|
+
return refuseCheck("the two verdicts reference different notes — refusing to conflate them");
|
|
116
|
+
}
|
|
117
|
+
if (sameLeg(v1.leg, v2.leg)) {
|
|
118
|
+
return refuseCheck("the two legs are not independent — the same model+session was counted twice (same leg twice)");
|
|
119
|
+
}
|
|
120
|
+
if (leg1Ref === leg2Ref) {
|
|
121
|
+
return refuseCheck("leg1Ref and leg2Ref are identical — refusing to accept one verdict reference twice");
|
|
122
|
+
}
|
|
123
|
+
if (v1.leg.session === authorId || v2.leg.session === authorId) {
|
|
124
|
+
return refuseCheck("a promoting leg's session equals the note's author — separation of powers requires an independent reviewer");
|
|
125
|
+
}
|
|
126
|
+
return { ok: true };
|
|
127
|
+
}
|
|
128
|
+
function refuseResult(reason) {
|
|
129
|
+
return { outcome: { promoted: false, reason }, localOnly: true };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Dispatch `evidence` through `port.promoteNote(noteId, evidence, ctx)`.
|
|
133
|
+
* `port` is injected and may be `undefined`/`null` (I5: fail-closed default,
|
|
134
|
+
* not a wiring error — mirrors `admit-client.ts`). This function does NOT
|
|
135
|
+
* itself enforce the double-consensus preconditions — that is
|
|
136
|
+
* `checkDoubleConsensusPreconditions`'s job, run BEFORE this is called (see
|
|
137
|
+
* `promoteNoteWithDoubleConsensus` for the composed entry point).
|
|
138
|
+
*/
|
|
139
|
+
export async function promoteNote(noteId, evidence, ctx, port) {
|
|
140
|
+
if (!port) {
|
|
141
|
+
return refuseResult("no memory producer port injected — refusing (fail-closed, I5)");
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const outcome = await port.promoteNote(noteId, evidence, ctx);
|
|
145
|
+
return { outcome, localOnly: false };
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
149
|
+
return refuseResult(`memory producer port unreachable: ${reason}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The client-side D11 promotion entry point: runs
|
|
154
|
+
* `checkDoubleConsensusPreconditions` first, and ONLY on `{ok: true}`
|
|
155
|
+
* assembles the evidence (`assemblePromotionEvidence`) and dispatches
|
|
156
|
+
* (`promoteNote`). A local refusal short-circuits BEFORE the port is ever
|
|
157
|
+
* touched — the composed call, not just the pure guard in isolation, is what
|
|
158
|
+
* a conductor actually invokes once both legs have written their verdicts.
|
|
159
|
+
*/
|
|
160
|
+
export async function promoteNoteWithDoubleConsensus(input, port) {
|
|
161
|
+
const check = checkDoubleConsensusPreconditions(input);
|
|
162
|
+
if (!check.ok) {
|
|
163
|
+
return refuseResult(`double-consensus preconditions not met: ${check.reason}`);
|
|
164
|
+
}
|
|
165
|
+
const evidence = assemblePromotionEvidence(input.leg1Ref, input.leg2Ref, input.attestationRef);
|
|
166
|
+
return promoteNote(input.noteId, evidence, input.ctx, port);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=promote-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promote-client.js","sourceRoot":"","sources":["../../../src/runtime/memory/promote-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall client — WP11 slice 5 (build brief), read-side (wake-recall). Consumes
|
|
3
|
+
* an INJECTED `MemoryRecallPort` and returns the notes for the wake path. This
|
|
4
|
+
* slice stubs the port in tests; it never wires to real storage/graphify and is
|
|
5
|
+
* NOT wired into the actual wake/CLI path — the real recall factory awaits
|
|
6
|
+
* graphify's storage-laying Vague B (out of scope here, same boundary as
|
|
7
|
+
* `admit-client.ts`'s producer side).
|
|
8
|
+
*
|
|
9
|
+
* READ-SIDE GUARANTEES ENFORCED HERE, AS A CONSUMER (not just assumed from the
|
|
10
|
+
* port's types — the port is an external boundary, so this module re-checks the
|
|
11
|
+
* shape of what it gets back):
|
|
12
|
+
* - `projection: "notes-only"` — this module exposes the notes as a FLAT LIST
|
|
13
|
+
* and offers no helper that aggregates `subject: "human:<id>"` notes into a
|
|
14
|
+
* profile. `groupAgentWorkNotesBySubject` below is the ONE grouping helper
|
|
15
|
+
* this module offers, and it EXCLUDES human-subject notes from the grouping
|
|
16
|
+
* by construction (counts them, never groups them) — so a UserModel cannot
|
|
17
|
+
* be assembled by composing this consumer's exports. A port that returns a
|
|
18
|
+
* different projection value is refused locally too: a misbehaving or
|
|
19
|
+
* incorrectly-stubbed port must not silently smuggle a different shape past
|
|
20
|
+
* the consumer boundary (the projection prohibition, §3.3.3, holds even if
|
|
21
|
+
* the far side does not enforce it correctly).
|
|
22
|
+
* - `freshness` and each note's `trust`/`review_status`/`provenance` are
|
|
23
|
+
* passed through VERBATIM — never stripped, never re-labeled as verified.
|
|
24
|
+
* - I5 fail-closed: an absent port, or one that throws/rejects, REFUSES —
|
|
25
|
+
* never a silent `notes: []` that reads as "no memories exist". A refusal
|
|
26
|
+
* is a structurally distinct shape (`refused: true` + `reason`, `notes: []`)
|
|
27
|
+
* from a genuine empty recall (`refused: false, notes: []`), so a caller
|
|
28
|
+
* cannot conflate "asked and got nothing" with "could not ask".
|
|
29
|
+
* - I1: `ctx.principal_owner` travels through opaque and unexamined — this
|
|
30
|
+
* module never derives, parses or keys on it (same reservation as
|
|
31
|
+
* `note-builder.ts`).
|
|
32
|
+
* - I4: no new capabilities/taxonomy vocabulary opens here — the
|
|
33
|
+
* human-subject predicate is REUSED from `note-builder.ts`
|
|
34
|
+
* (`isHumanMemorySubject`), never redefined.
|
|
35
|
+
*/
|
|
36
|
+
import type { MemoryContext, MemoryRecallPort, MemoryRecallQuery, MemoryRecallResultView, RecalledMemoryNoteView } from "./port-v1.js";
|
|
37
|
+
export interface RecallMemorySuccess {
|
|
38
|
+
readonly refused: false;
|
|
39
|
+
readonly notes: readonly RecalledMemoryNoteView[];
|
|
40
|
+
readonly freshness: MemoryRecallResultView["freshness"];
|
|
41
|
+
readonly projection: MemoryRecallResultView["projection"];
|
|
42
|
+
readonly requestingPrincipal: string;
|
|
43
|
+
readonly unpaged: true;
|
|
44
|
+
}
|
|
45
|
+
export interface RecallMemoryRefusal {
|
|
46
|
+
readonly refused: true;
|
|
47
|
+
readonly reason: string;
|
|
48
|
+
/** Always empty on a refusal — never conflate with a genuine empty recall. */
|
|
49
|
+
readonly notes: readonly [];
|
|
50
|
+
}
|
|
51
|
+
export type RecallMemoryOutcome = RecallMemorySuccess | RecallMemoryRefusal;
|
|
52
|
+
/**
|
|
53
|
+
* Dispatch `query` through `port.recallMemory(query, ctx)` for the wake path.
|
|
54
|
+
* `port` is injected and may be `undefined`/`null` (I5: fail-closed default,
|
|
55
|
+
* not a wiring error — the real recall factory does not exist yet).
|
|
56
|
+
*/
|
|
57
|
+
export declare function recallMemory(query: MemoryRecallQuery, ctx: MemoryContext, port: MemoryRecallPort | undefined | null): Promise<RecallMemoryOutcome>;
|
|
58
|
+
export interface NotesGroupedBySubject {
|
|
59
|
+
/** Grouped ONLY for non-human subjects (e.g. `"agent-work"`). */
|
|
60
|
+
readonly grouped: Readonly<Record<string, readonly RecalledMemoryNoteView[]>>;
|
|
61
|
+
/** Count of human-subject notes excluded from the grouping — never aggregated. */
|
|
62
|
+
readonly excludedHumanSubjectCount: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The ONE grouping/summary helper this consumer offers, and it deliberately
|
|
66
|
+
* cannot be used to assemble a human-subject profile: any `human:<id>`
|
|
67
|
+
* subject note is EXCLUDED from the grouping (counted, never aggregated).
|
|
68
|
+
* Only non-human subjects (e.g. `"agent-work"`) are grouped. This mirrors the
|
|
69
|
+
* projection prohibition (§3.3.3) at the consumer boundary, so the
|
|
70
|
+
* prohibition holds even if a caller composes only this module's own exports
|
|
71
|
+
* — there is no way to reach a `Record<humanSubject, notes[]>` through it.
|
|
72
|
+
*/
|
|
73
|
+
export declare function groupAgentWorkNotesBySubject(notes: readonly RecalledMemoryNoteView[]): NotesGroupedBySubject;
|
|
74
|
+
//# sourceMappingURL=recall-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-client.d.ts","sourceRoot":"","sources":["../../../src/runtime/memory/recall-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,EACvB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAClD,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;IACxD,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC,YAAY,CAAC,CAAC;IAC1D,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC;CAC7B;AAED,MAAM,MAAM,mBAAmB,GAAG,mBAAmB,GAAG,mBAAmB,CAAC;AAM5E;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,iBAAiB,EACxB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI,GACxC,OAAO,CAAC,mBAAmB,CAAC,CA2B9B;AAED,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,CAAC,CAAC,CAAC;IAC9E,kFAAkF;IAClF,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,SAAS,sBAAsB,EAAE,GACvC,qBAAqB,CAcvB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall client — WP11 slice 5 (build brief), read-side (wake-recall). Consumes
|
|
3
|
+
* an INJECTED `MemoryRecallPort` and returns the notes for the wake path. This
|
|
4
|
+
* slice stubs the port in tests; it never wires to real storage/graphify and is
|
|
5
|
+
* NOT wired into the actual wake/CLI path — the real recall factory awaits
|
|
6
|
+
* graphify's storage-laying Vague B (out of scope here, same boundary as
|
|
7
|
+
* `admit-client.ts`'s producer side).
|
|
8
|
+
*
|
|
9
|
+
* READ-SIDE GUARANTEES ENFORCED HERE, AS A CONSUMER (not just assumed from the
|
|
10
|
+
* port's types — the port is an external boundary, so this module re-checks the
|
|
11
|
+
* shape of what it gets back):
|
|
12
|
+
* - `projection: "notes-only"` — this module exposes the notes as a FLAT LIST
|
|
13
|
+
* and offers no helper that aggregates `subject: "human:<id>"` notes into a
|
|
14
|
+
* profile. `groupAgentWorkNotesBySubject` below is the ONE grouping helper
|
|
15
|
+
* this module offers, and it EXCLUDES human-subject notes from the grouping
|
|
16
|
+
* by construction (counts them, never groups them) — so a UserModel cannot
|
|
17
|
+
* be assembled by composing this consumer's exports. A port that returns a
|
|
18
|
+
* different projection value is refused locally too: a misbehaving or
|
|
19
|
+
* incorrectly-stubbed port must not silently smuggle a different shape past
|
|
20
|
+
* the consumer boundary (the projection prohibition, §3.3.3, holds even if
|
|
21
|
+
* the far side does not enforce it correctly).
|
|
22
|
+
* - `freshness` and each note's `trust`/`review_status`/`provenance` are
|
|
23
|
+
* passed through VERBATIM — never stripped, never re-labeled as verified.
|
|
24
|
+
* - I5 fail-closed: an absent port, or one that throws/rejects, REFUSES —
|
|
25
|
+
* never a silent `notes: []` that reads as "no memories exist". A refusal
|
|
26
|
+
* is a structurally distinct shape (`refused: true` + `reason`, `notes: []`)
|
|
27
|
+
* from a genuine empty recall (`refused: false, notes: []`), so a caller
|
|
28
|
+
* cannot conflate "asked and got nothing" with "could not ask".
|
|
29
|
+
* - I1: `ctx.principal_owner` travels through opaque and unexamined — this
|
|
30
|
+
* module never derives, parses or keys on it (same reservation as
|
|
31
|
+
* `note-builder.ts`).
|
|
32
|
+
* - I4: no new capabilities/taxonomy vocabulary opens here — the
|
|
33
|
+
* human-subject predicate is REUSED from `note-builder.ts`
|
|
34
|
+
* (`isHumanMemorySubject`), never redefined.
|
|
35
|
+
*/
|
|
36
|
+
import { isHumanMemorySubject } from "./note-builder.js";
|
|
37
|
+
function refuse(reason) {
|
|
38
|
+
return { refused: true, reason, notes: [] };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Dispatch `query` through `port.recallMemory(query, ctx)` for the wake path.
|
|
42
|
+
* `port` is injected and may be `undefined`/`null` (I5: fail-closed default,
|
|
43
|
+
* not a wiring error — the real recall factory does not exist yet).
|
|
44
|
+
*/
|
|
45
|
+
export async function recallMemory(query, ctx, port) {
|
|
46
|
+
if (!port) {
|
|
47
|
+
return refuse("no memory recall port injected — refusing (fail-closed, I5)");
|
|
48
|
+
}
|
|
49
|
+
let result;
|
|
50
|
+
try {
|
|
51
|
+
result = await port.recallMemory(query, ctx);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
55
|
+
return refuse(`memory recall port unreachable: ${reason}`);
|
|
56
|
+
}
|
|
57
|
+
if (result.projection !== "notes-only") {
|
|
58
|
+
return refuse(`memory recall port returned projection "${String(result.projection)}", expected "notes-only" — refusing rather than trusting an unverified shape`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
refused: false,
|
|
62
|
+
notes: result.notes,
|
|
63
|
+
freshness: result.freshness,
|
|
64
|
+
projection: result.projection,
|
|
65
|
+
requestingPrincipal: result.requestingPrincipal,
|
|
66
|
+
unpaged: result.unpaged
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The ONE grouping/summary helper this consumer offers, and it deliberately
|
|
71
|
+
* cannot be used to assemble a human-subject profile: any `human:<id>`
|
|
72
|
+
* subject note is EXCLUDED from the grouping (counted, never aggregated).
|
|
73
|
+
* Only non-human subjects (e.g. `"agent-work"`) are grouped. This mirrors the
|
|
74
|
+
* projection prohibition (§3.3.3) at the consumer boundary, so the
|
|
75
|
+
* prohibition holds even if a caller composes only this module's own exports
|
|
76
|
+
* — there is no way to reach a `Record<humanSubject, notes[]>` through it.
|
|
77
|
+
*/
|
|
78
|
+
export function groupAgentWorkNotesBySubject(notes) {
|
|
79
|
+
const grouped = {};
|
|
80
|
+
let excludedHumanSubjectCount = 0;
|
|
81
|
+
for (const note of notes) {
|
|
82
|
+
if (isHumanMemorySubject(note.subject)) {
|
|
83
|
+
excludedHumanSubjectCount += 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const bucket = grouped[note.subject] ?? (grouped[note.subject] = []);
|
|
87
|
+
bucket.push(note);
|
|
88
|
+
}
|
|
89
|
+
return { grouped, excludedHumanSubjectCount };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=recall-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-client.js","sourceRoot":"","sources":["../../../src/runtime/memory/recall-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AA2BzD,SAAS,MAAM,CAAC,MAAc;IAC5B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAwB,EACxB,GAAkB,EAClB,IAAyC;IAEzC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,MAAM,CAAC,6DAA6D,CAAC,CAAC;IAC/E,CAAC;IAED,IAAI,MAA8B,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChE,OAAO,MAAM,CAAC,mCAAmC,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,KAAK,YAAY,EAAE,CAAC;QACvC,OAAO,MAAM,CACX,2CAA2C,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,8EAA8E,CACnJ,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;QAC/C,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC;AACJ,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,UAAU,4BAA4B,CAC1C,KAAwC;IAExC,MAAM,OAAO,GAA6C,EAAE,CAAC;IAC7D,IAAI,yBAAyB,GAAG,CAAC,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,yBAAyB,IAAI,CAAC,CAAC;YAC/B,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC;AAChD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentropic/h2a",
|
|
3
|
-
"version": "0.96.
|
|
3
|
+
"version": "0.96.2",
|
|
4
4
|
"description": "h2a — the unified CLI + core for human-to-agent coordination.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -52,10 +52,13 @@
|
|
|
52
52
|
"@hono/mcp": "^0.3.0",
|
|
53
53
|
"@hono/node-server": "^2.0.12",
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
55
|
-
"@sentropic/
|
|
55
|
+
"@sentropic/cluster-mesh": "0.9.0",
|
|
56
|
+
"@sentropic/contracts": "^0.3.0",
|
|
57
|
+
"@sentropic/events": "^0.2.0",
|
|
58
|
+
"@sentropic/track": "^0.96.1",
|
|
56
59
|
"hono": "^4.12.32"
|
|
57
60
|
},
|
|
58
61
|
"peerDependencies": {
|
|
59
|
-
"@sentropic/h2a-runtime": "^0.96.
|
|
62
|
+
"@sentropic/h2a-runtime": "^0.96.1"
|
|
60
63
|
}
|
|
61
64
|
}
|
package/skills/h2a-run/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ description: Translate a friendly launch intent ("terra 5.6 xhigh", "codex sol h
|
|
|
8
8
|
## When to use this
|
|
9
9
|
|
|
10
10
|
- The user names a launch by nickname + effort ("terra 5.6 xhigh", "sol en max", "relance codex en xhigh") instead of a provider model id.
|
|
11
|
-
- The user wants a background or interactive Claude/Codex session with a specific reasoning effort, with or without the llm-mesh gateway, and needs the exact CLI flags or MCP parameters.
|
|
11
|
+
- The user wants a background or interactive Claude/Codex/AGY session with a specific reasoning effort, with or without the llm-mesh gateway, and needs the exact CLI flags or MCP parameters.
|
|
12
12
|
- **Not** for deciding *which* flavor is best for a task (that is a human/model-delegation call, not this skill's job), and **not** for resolving alias→upstream-provider routing — that table is owned by the llm-mesh gateway, not this skill (see Step 2).
|
|
13
13
|
|
|
14
14
|
## Hard rule: never call the raw `h2a` binary from Bash
|
|
@@ -19,9 +19,9 @@ This plugin's PreToolUse hook (`packages/h2a/hooks/deny-manual-h2a-cli.mjs`) blo
|
|
|
19
19
|
|
|
20
20
|
None of these require knowing a provider model id up front:
|
|
21
21
|
|
|
22
|
-
1. **Profile** — `claude` or `
|
|
22
|
+
1. **Profile** — `claude`, `codex`, or `agy` (which CLI to launch). Default to whichever CLI the user is already in if unstated; ask if genuinely ambiguous.
|
|
23
23
|
2. **Model flavor** — a nickname like *terra*, *sol* (aka *fable*), *luna*, or unstated (→ the CLI's own default model). Resolves to a `--model` / `model` value (Step 2).
|
|
24
|
-
3. **Effort** — `low | medium | high | xhigh`. "max"
|
|
24
|
+
3. **Effort** — `low | medium | high | xhigh`. For Claude/Codex, "max" means `xhigh`; AGY accepts only `low | medium | high` and rejects both `xhigh` and "max".
|
|
25
25
|
|
|
26
26
|
## Step 2 — resolve the model flavor (source-of-truth caveat)
|
|
27
27
|
|
|
@@ -39,11 +39,13 @@ Any non-default flavor needs the llm-mesh gateway to translate the Anthropic-sha
|
|
|
39
39
|
- CLI: `--gw` (alias `--llm-gateway`) forces it on, `--no-gw` (alias `--no-llm-gateway`) forces it off, omit for the CLI's own default.
|
|
40
40
|
- MCP `h2a_run`: `gateway: "required"` forces it on, `"off"` forces it off, `"auto"` (default) decides. **`"required"` is rejected when `profile` is `"codex"`** — codex already talks to llm-mesh over an Anthropic-compatible surface, use `"auto"` there.
|
|
41
41
|
|
|
42
|
+
AGY is the exception to the named-flavor rule: it talks to its provider directly. Use `gateway: "off"` for `profile: "agy"`; `"required"` is rejected. AGY accepts effort `low|medium|high`, not `xhigh`, and supports run-once mode through its verified `--print` stdin contract. Resolve the exact AGY model with `agy models`; for example, the displayed "Gemini 3.7 Flash (High)" id is `gemini-3.7-flash-high`.
|
|
43
|
+
|
|
42
44
|
## Step 4 — compose the call
|
|
43
45
|
|
|
44
46
|
### `h2a_run` MCP tool (what this agent must use)
|
|
45
47
|
|
|
46
|
-
Required: `profile` (`"claude"|"codex"`), `name` (`^[A-Za-z0-9_-]{1,64}$`), `workspace` (absolute path, must exist, must stay inside the MCP server's startup workspace root), `prompt` (1–65536 UTF-8 bytes, sent on stdin — never put it in argv), `background` (must be literal `true`). Optional: `model` (free-text, format-checked only — see Step 2 for the value), `effort` (`"low"|"medium"|"high"|"xhigh"`), `gateway` (`"auto"|"required"|"off"`, default `"auto"`), `headless` (default `false`), `h2aSidecar` (default `!headless`; cannot be `true` together with `headless: true`).
|
|
48
|
+
Required: `profile` (`"claude"|"codex"|"agy"`), `name` (`^[A-Za-z0-9_-]{1,64}$`), `workspace` (absolute path, must exist, must stay inside the MCP server's startup workspace root), `prompt` (1–65536 UTF-8 bytes, sent on stdin — never put it in argv), `background` (must be literal `true`). Optional: `agent` (AGY only; for example `"stp"`), `model` (free-text, format-checked only — see Step 2 for the value), `effort` (`"low"|"medium"|"high"|"xhigh"`; AGY rejects `xhigh`), `gateway` (`"auto"|"required"|"off"`, default `"auto"`; AGY uses `"off"`), `headless` (default `false`; AGY maps `true` to `--print`), `h2aSidecar` (default `!headless`; cannot be `true` together with `headless: true`).
|
|
47
49
|
|
|
48
50
|
Example — "terra, xhigh, headless, on this repo":
|
|
49
51
|
|
|
@@ -63,6 +65,24 @@ Example — "terra, xhigh, headless, on this repo":
|
|
|
63
65
|
|
|
64
66
|
The tool returns an `h2a.run.result` contract with `session.tmuxSession`, `session.pane`, `session.gateway` (`"gateway"|"direct"`). Read those fields back to confirm what actually launched — do not assume the request was honored silently.
|
|
65
67
|
|
|
68
|
+
Example — direct AGY run-once:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"profile": "agy",
|
|
73
|
+
"name": "gemini-review",
|
|
74
|
+
"workspace": "/abs/path/to/repo",
|
|
75
|
+
"prompt": "<initial instructions>",
|
|
76
|
+
"background": true,
|
|
77
|
+
"agent": "stp",
|
|
78
|
+
"model": "gemini-3.7-flash-high",
|
|
79
|
+
"effort": "high",
|
|
80
|
+
"gateway": "off",
|
|
81
|
+
"headless": true,
|
|
82
|
+
"h2aSidecar": false
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
66
86
|
### `h2a run` CLI (reference only — non-Claude-Code hosts / humans at a terminal)
|
|
67
87
|
|
|
68
88
|
```
|
|
@@ -75,10 +95,23 @@ Interactive/attached form (drop the background-launch flags, add nothing else):
|
|
|
75
95
|
h2a run claude . --model gpt-5.6-terra --effort xhigh --gw --name terra-review
|
|
76
96
|
```
|
|
77
97
|
|
|
98
|
+
Direct AGY run-once form (prompt delivered on stdin):
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
h2a run agy . --agent stp --model gemini-3.7-flash-high --effort high --no-gw --name gemini-review --no-attach --background --json --headless --no-h2a --prompt-stdin
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Interactive AGY keeps the TUI and accepts the same prompt-over-stdin contract; resume uses AGY's native `--conversation` argv through h2a's `-r/--resume` option:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
h2a run agy . --agent stp --model gemini-3.7-flash-high --effort high --no-gw --name gemini-review --prompt-stdin
|
|
108
|
+
h2a run agy . -r <conversation-id> --agent stp --model gemini-3.7-flash-high --effort high --no-gw --name gemini-review
|
|
109
|
+
```
|
|
110
|
+
|
|
78
111
|
Other `h2a run` flags worth knowing:
|
|
79
112
|
|
|
80
|
-
- `-r, --resume <convId>` — continue a conversation; combine with `--model`/`--effort` to relaunch the same conversation at a different flavor/effort.
|
|
81
|
-
- `--headless` — run once, record output under `.h2a/runs/<name>`, then exit (cannot combine with `--h2a`).
|
|
113
|
+
- `-r, --resume <convId>` — continue a conversation; combine with `--model`/`--effort` to relaunch the same conversation at a different flavor/effort (AGY maps this to `--conversation <convId>`).
|
|
114
|
+
- `--headless` — run once, record output under `.h2a/runs/<name>`, then exit (cannot combine with `--h2a`; AGY uses `--input-format stream-json --output-format stream-json`, with one escaped `user` event on stdin).
|
|
82
115
|
- `--count <n>` — fan out N fresh sessions; incompatible with `--model`/`--effort`/`--resume`/any structured launch (each fanned session is a fresh conversation).
|
|
83
116
|
- `--h2a` / `--no-h2a` — start (or skip) the side-window h2a MCP server; defaults on unless `--headless`.
|
|
84
117
|
- `--name <label>` — tmux slug + tab label; defaults to the workspace dirname. Pick one deliberately when launching more than one session against the same repo.
|