@davidorex/pi-context 0.29.0 → 0.30.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.
@@ -1,5 +1,7 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { type TSchema } from "typebox";
3
+ import type { DispatchContext } from "./dispatch-context.js";
4
+ import { type ReadStructured } from "./read-element.js";
3
5
  /**
4
6
  * One substrate operation. `run(cwd, params)` returns the text payload that
5
7
  * registerAll places at `content[0].text`; everything else (param coercion,
@@ -12,13 +14,51 @@ import { type TSchema } from "typebox";
12
14
  * for that consumer and is not the enforcement point (the pi-agent-dispatch
13
15
  * auth-gate remains canonical).
14
16
  */
17
+ /**
18
+ * The discriminated result of an op's `run` (TASK-012 / FGAP-013). An op returns
19
+ * one of three shapes, each carrying its OWN text-rendering rule so the CLI
20
+ * `--json` envelope can emit a real JSON value (no double-encode) while the
21
+ * default text surface + the in-pi Pi-tool surface stay byte-identical:
22
+ * - `string` — a prose op's human message; rendered as itself.
23
+ * - `{ json: … }` — a data op that previously returned `JSON.stringify(x, null, 2)`;
24
+ * rendered via JSON.stringify, structured value is `json`.
25
+ * - `{ read: … }` — a read op that previously returned `serializeForRead(x).content`;
26
+ * rendered via renderReadText, structured value is the
27
+ * {@link ReadStructured} (data + paging/cap metadata).
28
+ * {@link renderOpResultText} collapses all three back to the text both boundaries
29
+ * emit; the CLI `--json` branch unwraps the structured value instead.
30
+ */
31
+ export type OpResult = string | {
32
+ json: unknown;
33
+ } | {
34
+ read: ReadStructured;
35
+ };
36
+ /**
37
+ * Collapse an {@link OpResult} to the text the default CLI surface + the in-pi
38
+ * Pi-tool surface emit, NOW BOUNDED at the 50KB read cap (TASK-013 / FGAP-015).
39
+ * `{read}` → renderReadText (already capped); prose `string` → itself when under
40
+ * cap, else the REFUSAL prose; `{json}` → `JSON.stringify(x, null, 2)` when under
41
+ * cap, else the REFUSAL prose (no partial body).
42
+ */
43
+ export declare function renderOpResultText(r: OpResult): string;
44
+ /**
45
+ * The JSON VALUE for the CLI `--json` envelope `output` field, NOW BOUNDED at the
46
+ * 50KB read cap (TASK-013 / FGAP-015). Prose `string` → itself when under cap,
47
+ * else the REFUSAL string; `{read}` → its ReadStructured (already fail-closed —
48
+ * serializes tiny on over-cap); `{json}` → the raw value when under cap, else a
49
+ * fail-closed envelope that MIRRORS {@link ReadStructured}'s over-cap shape
50
+ * (`{ data: null, truncated: true, totalBytes, complete: false }`) so `--json`
51
+ * consumers see one uniform fail-closed envelope across `{read}` and bounded
52
+ * `{json}`. No partial payload is ever emitted past the cap.
53
+ */
54
+ export declare function boundedJsonOutput(r: OpResult): unknown;
15
55
  export interface OpDefinition<P = any> {
16
56
  name: string;
17
57
  label: string;
18
58
  description: string;
19
59
  promptSnippet?: string;
20
60
  parameters: TSchema;
21
- run(cwd: string, params: P): string | Promise<string>;
61
+ run(cwd: string, params: P, ctx?: DispatchContext): OpResult | Promise<OpResult>;
22
62
  authGated?: boolean;
23
63
  surface: "use" | "process";
24
64
  }
@@ -34,11 +74,147 @@ export declare const ops: OpDefinition[];
34
74
  * point; this list is the source of pi-context's contribution to it.
35
75
  */
36
76
  export declare const gatedTools: string[];
77
+ /**
78
+ * One entry on the {@link INTENTIONALLY_UNEXPOSED_WRITERS} allowlist. `libraryFn`
79
+ * names a library write function deliberately NOT given its own op; exactly one
80
+ * of `safeOp` / `reason` carries the justification (`safeOp` when a safe op
81
+ * supersedes the raw writer; `reason` for internal/foreign-only writers with no
82
+ * op surface at all).
83
+ */
84
+ export interface UnexposedWriter {
85
+ libraryFn: string;
86
+ reason?: string;
87
+ safeOp?: string;
88
+ }
89
+ /**
90
+ * The FGAP-009 non-exposure allowlist: every library write function that is
91
+ * deliberately NOT op-backed, with the reason it is withheld. This is the
92
+ * closure contract γ (TASK-008) WILL consume: γ's parity test — not yet written
93
+ * (no executable parity test exists in β; β defines the contract, γ implements
94
+ * the test against it) — WILL assert that EVERY library writer is either op-backed
95
+ * (appears in {@link ops}, directly or transitively) OR named here, so a
96
+ * newly-added library writer with neither an op nor an allowlist entry will fail
97
+ * that test, keeping the op surface and the library write surface in lockstep.
98
+ *
99
+ * The `*ForDir` twins of op-backed writers (e.g. `appendRelationForDir`,
100
+ * `writeRelationsForDir`, `upsertItemInBlockForDir`, `appendToBlockForDir`, …)
101
+ * are NOT enumerated individually: each is the dir-targeted internal twin of a
102
+ * cwd-form writer that IS op-backed, and is covered by that cwd-form op (the op
103
+ * resolves the active substrate dir then delegates to the same shared
104
+ * typed-file primitive the `*ForDir` twin calls). The contract is that γ's
105
+ * parity test (TASK-008, not yet written) WILL treat a `*ForDir` writer as
106
+ * covered when its cwd-form sibling is covered.
107
+ */
108
+ export declare const INTENTIONALLY_UNEXPOSED_WRITERS: UnexposedWriter[];
109
+ /**
110
+ * The five mutually-exhaustive ways a library write function is COVERED by the
111
+ * FGAP-009 op-surface ↔ library-write-surface parity contract. A writer that
112
+ * matches NONE of these is a silent gap that γ's (TASK-008) parity test — once
113
+ * written — MUST fail on. Coverage is the DISJUNCTION over these classes — a
114
+ * writer needs ANY one, not all.
115
+ */
116
+ export declare enum CoverageClass {
117
+ /** An op's `run` calls the writer directly (e.g. `append-block-item` → `appendToBlock`). */
118
+ OpBackedDirect = "op-backed-direct",
119
+ /**
120
+ * An op's `run` reaches the writer TRANSITIVELY — through any helper / wrapper
121
+ * chain, not just a direct call. Two sub-shapes both land here:
122
+ * - `*ByRef` / SDK relation porcelain: the `remove-relation` / `replace-relation`
123
+ * / `append-relations` ops call `removeRelationByRef` / `replaceRelationByRef`
124
+ * / `appendRelationsByRef`, which call `writeRelations`.
125
+ * - init / switch → internal-helper chains: `context-init` → `initProject` →
126
+ * `writeSkeletonConfig`; `context-switch` → `switchToExisting` /
127
+ * `switchAndCreate` → `reconcileActiveSubstrateRegistration`. The writer is
128
+ * not a `*ByRef` porcelain and is not allowlisted, but it IS reachable from an
129
+ * op's `run` via a helper the op calls.
130
+ * Coverage condition: reachable from some op's `run` via any helper/wrapper chain.
131
+ */
132
+ OpBackedTransitive = "op-backed-transitive",
133
+ /**
134
+ * A `*ForDir` dir-targeted twin of a covered cwd-form writer (e.g.
135
+ * `appendToBlockForDir` is the twin of the op-backed `appendToBlock`). Covered
136
+ * by its cwd-form sibling — both delegate to the same shared typed-file
137
+ * primitive; the cwd-form op resolves the active dir then calls it.
138
+ */
139
+ ForDirTwin = "for-dir-twin",
140
+ /**
141
+ * On {@link INTENTIONALLY_UNEXPOSED_WRITERS}: a raw write deliberately given NO
142
+ * direct op (a scoped op supersedes it, or it is internal/foreign-only).
143
+ */
144
+ IntentionallyUnexposed = "intentionally-unexposed",
145
+ /**
146
+ * A block-api internal primitive below the op layer — the `*TypedFile` read/
147
+ * write layer (`readTypedFile` / `writeTypedFile`), `prepareItemIdentityForWrite`,
148
+ * and the identity / content-hash helpers. Never op-backed by design; the ops
149
+ * compose over these.
150
+ */
151
+ InternalPrimitive = "internal-primitive"
152
+ }
153
+ /**
154
+ * One clause of {@link OP_COVERAGE_RULE}: a coverage class plus the human-readable
155
+ * test γ applies to decide whether a writer falls in it.
156
+ */
157
+ export interface CoverageClause {
158
+ coverageClass: CoverageClass;
159
+ test: string;
160
+ }
161
+ /**
162
+ * The FGAP-009 coverage RULE, made explicit so γ (TASK-008) will import the
163
+ * contract rather than re-derive it. A library write function is COVERED iff it
164
+ * matches ANY clause below (the disjunction); a writer matching none is a silent
165
+ * gap that γ's parity test — when written — MUST fail on. β fixes the contract
166
+ * here; no executable parity test exists yet (that is γ).
167
+ *
168
+ * Why `writeConfig` is allowlisted but `writeRelations` is NOT — the distinction
169
+ * a strict name-parity reading mis-saw as inconsistent:
170
+ * - `writeConfig` has NO direct wholesale-config op. The scoped surface is
171
+ * `amend-config` (one-entry-in-one-registry add/replace/remove, AJV-validated),
172
+ * which deliberately does NOT expose a raw whole-config overwrite. So
173
+ * `writeConfig` is `intentionally-unexposed` (the scoped op supersedes the raw
174
+ * writer; a raw wholesale overwrite is withheld by design).
175
+ * - `writeRelations` IS reached — transitively — by the relation ops:
176
+ * `remove-relation` / `replace-relation` / `append-relations` call
177
+ * `removeRelationByRef` / `replaceRelationByRef` / `appendRelationsByRef`, each
178
+ * of which calls `writeRelations`. It is therefore `op-backed-transitive`
179
+ * and needs NO allowlist entry. The asymmetry is real and correct: one writer
180
+ * has an op path (via a helper/wrapper chain), the other does not.
181
+ *
182
+ * The `op-backed-transitive` clause covers BOTH the `*ByRef` relation porcelain
183
+ * AND the init/switch → internal-helper chains: `writeSkeletonConfig` (reached
184
+ * via `context-init` → `initProject`) and `reconcileActiveSubstrateRegistration`
185
+ * (reached via `context-switch` → `switchToExisting` / `switchAndCreate`) are
186
+ * neither `*ByRef` porcelain nor allowlisted, yet each is reachable from an op's
187
+ * `run` through a helper that op calls — so each classifies cleanly as
188
+ * `op-backed-transitive`, not as a gap.
189
+ */
190
+ export declare const OP_COVERAGE_RULE: CoverageClause[];
191
+ /**
192
+ * Build the DispatchContext threaded into an op's `run` from the in-pi tool
193
+ * execute boundary (registerAll). Two derivation branches:
194
+ *
195
+ * - When `params.writer.user` is a non-empty string — the shape the
196
+ * pi-agent-dispatch auth-gate stamps onto authGated op params on operator
197
+ * confirm — the writer is a human identity. (The smuggle-ops promote-item /
198
+ * write-schema-migration / context-switch carry a `writer` schema field
199
+ * precisely so the gate has somewhere to stamp; this converts that field
200
+ * into the contract ctx the op now consumes via its 3rd `run` arg.)
201
+ * - Otherwise the writer is the running agent, identified by the active
202
+ * model's id (`ExtensionContext.model.id`); falls back to "pi-agent" when
203
+ * no model (or no id) is resolvable.
204
+ *
205
+ * Exported for unit testing — the two branches are asserted directly against
206
+ * synthetic params + a minimal ExtensionContext-shaped object.
207
+ */
208
+ export declare function buildDispatchContextFromExecute(params: unknown, extCtx: {
209
+ model?: {
210
+ id?: string;
211
+ } | undefined;
212
+ }): DispatchContext;
37
213
  /**
38
214
  * Register every op in `ops` as a pi tool. Each tool's execute body is the
39
- * uniform wrapper around the op's run(): coerce params, await run, place the
40
- * returned string at content[0].text. Behavior-identical to the prior inline
41
- * registrations.
215
+ * uniform wrapper around the op's run(): coerce params, build the attestation
216
+ * DispatchContext from the auth-gate-stamped writer (human) or the running
217
+ * model (agent), await run, place the returned string at content[0].text.
42
218
  */
43
219
  export declare function registerAll(pi: ExtensionAPI): void;
44
220
  //# sourceMappingURL=ops-registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ops-registry.d.ts","sourceRoot":"","sources":["../src/ops-registry.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAGX,YAAY,EAEZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,SAAS,CAAC;AA4D7C;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,KAAK,GAAG,SAAS,CAAC;CAC3B;AA6BD,eAAO,MAAM,GAAG,EAAE,YAAY,EAuzC7B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,EAAsD,CAAC;AAYtF;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAuBlD"}
1
+ {"version":3,"file":"ops-registry.d.ts","sourceRoot":"","sources":["../src/ops-registry.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAGX,YAAY,EAEZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,SAAS,CAAC;AAkC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAwB7D,OAAO,EAAe,KAAK,cAAc,EAAoC,MAAM,mBAAmB,CAAC;AAQvG;;;;;;;;;;;GAWG;AACH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC;AA4C7E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAStD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,GAAG,OAAO,CAStD;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,KAAK,GAAG,SAAS,CAAC;CAC3B;AA6BD,eAAO,MAAM,GAAG,EAAE,YAAY,EAmjD7B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,EAAsD,CAAC;AAEtF;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,+BAA+B,EAAE,eAAe,EAgB5D,CAAC;AAEF;;;;;;GAMG;AACH,oBAAY,aAAa;IACxB,4FAA4F;IAC5F,cAAc,qBAAqB;IACnC;;;;;;;;;;;;OAYG;IACH,kBAAkB,yBAAyB;IAC3C;;;;;OAKG;IACH,UAAU,iBAAiB;IAC3B;;;OAGG;IACH,sBAAsB,4BAA4B;IAClD;;;;;OAKG;IACH,iBAAiB,uBAAuB;CACxC;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,aAAa,EAAE,aAAa,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAqB5C,CAAC;AAYF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,+BAA+B,CAC9C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;CAAE,GAC7C,eAAe,CAOjB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAwBlD"}