@miadi/connector 0.1.12 → 0.1.14

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.
@@ -0,0 +1,110 @@
1
+ import type { ConnectorRecord } from "./records.js";
2
+ /** The kinds that open a session when approved, and the only ones revised in place. */
3
+ export declare const REVISABLE_KINDS: ReadonlySet<string>;
4
+ /** A request is revised only before a decision runs it: waiting, or sent back by its approver. */
5
+ export declare const REVISABLE_STATES: ReadonlySet<string>;
6
+ /** What a revision record carries beside the new content. */
7
+ export interface RequestRevision {
8
+ /** The version this record makes current, counted across the lineage (2, 3, …). */
9
+ version: number;
10
+ previous_version: number;
11
+ reason: string;
12
+ /** The fields that differ from the previous version. */
13
+ changed: string[];
14
+ content_hash: string;
15
+ previous_hash: string;
16
+ author: {
17
+ person_id: string;
18
+ name: string;
19
+ };
20
+ at: string;
21
+ }
22
+ /** What a lineage record carries: the requests the standing one adopted as its earlier versions. */
23
+ export interface RequestLineage {
24
+ adopted: string[];
25
+ reason: string;
26
+ author: {
27
+ person_id: string;
28
+ name: string;
29
+ };
30
+ at: string;
31
+ }
32
+ /** The fields that make a request's content: what a version is. */
33
+ export declare const CONTENT_FIELDS: readonly ["intention", "circle", "refs", "body"];
34
+ /** The content of a request version, as the hash and the history show it. */
35
+ export declare function contentOf(r: Pick<ConnectorRecord, "kind" | "intention" | "circle" | "refs" | "body">): Record<string, unknown>;
36
+ /** sha256 of the canonical JSON of a version's content. */
37
+ export declare function contentHash(r: Pick<ConnectorRecord, "kind" | "intention" | "circle" | "refs" | "body">): string;
38
+ /** The version a folded request is at: 1 for a request never revised. */
39
+ export declare function versionOf(r: Pick<ConnectorRecord, "version"> | undefined): number;
40
+ /** The names of the content fields (body fields by name) that differ between two versions. */
41
+ export declare function changedFields(before: Record<string, unknown>, after: Record<string, unknown>): string[];
42
+ /** One version of a request, as the history returns it. */
43
+ export interface RequestVersion {
44
+ version: number;
45
+ /** The record whose content this version is: the request itself, a revision record, or an adopted request. */
46
+ record_id: string;
47
+ /** The request this version belongs to when it is not the standing one (an adopted, retired request). */
48
+ request_id: string;
49
+ at: string;
50
+ author: string;
51
+ reason?: string;
52
+ changed?: string[];
53
+ content_hash: string;
54
+ content: Record<string, unknown>;
55
+ current: boolean;
56
+ }
57
+ /**
58
+ * Every version of a request, oldest first, numbered across its lineage: the requests
59
+ * it adopted (each with its own versions) and then its own. `raw` is the unfolded ledger,
60
+ * `folded` the folded one (`current()`).
61
+ */
62
+ export declare function requestHistory(id: string, raw: ConnectorRecord[], folded?: ConnectorRecord[]): RequestVersion[];
63
+ /** A revision refused, with the code a tool answers. */
64
+ export declare class RevisionRefused extends Error {
65
+ readonly code: "not_found" | "not_revisable" | "forbidden" | "stale_version" | "being_decided" | "no_change" | "invalid_request";
66
+ readonly detail?: Record<string, unknown> | undefined;
67
+ constructor(code: "not_found" | "not_revisable" | "forbidden" | "stale_version" | "being_decided" | "no_change" | "invalid_request", message: string, detail?: Record<string, unknown> | undefined);
68
+ }
69
+ /**
70
+ * The new record that revises a waiting request, or a refusal. `next` holds the new
71
+ * content (kind, intention, circle, refs, body); `live` is the folded request.
72
+ */
73
+ export declare function revisionRecord(live: ConnectorRecord | undefined, next: {
74
+ intention?: string;
75
+ circle?: ConnectorRecord["circle"];
76
+ refs: string[];
77
+ body: Record<string, unknown>;
78
+ }, o: {
79
+ id: string;
80
+ person: {
81
+ id: string;
82
+ name: string;
83
+ writer: boolean;
84
+ };
85
+ actor: ConnectorRecord["actor"];
86
+ expectedVersion: number;
87
+ reason: string;
88
+ beingDecided: boolean;
89
+ idempotency_key?: string;
90
+ now?: string;
91
+ }): ConnectorRecord;
92
+ /**
93
+ * The records that make `standingId` the one request of a lineage: one retirement for
94
+ * each named request still waiting, and one lineage record on the standing request.
95
+ * Every named request is checked before anything is written; one refusal writes none.
96
+ * Only a request named by its exact id is retired, never one that looks similar, and a
97
+ * request already decided (approved, queued, declined) is refused, never cancelled.
98
+ */
99
+ export declare function supersedeRecords(folded: ConnectorRecord[], standingId: string, retire: string[], o: {
100
+ person: {
101
+ id: string;
102
+ name: string;
103
+ writer: boolean;
104
+ };
105
+ actor: ConnectorRecord["actor"];
106
+ reason: string;
107
+ beingDecided: (id: string) => boolean;
108
+ newId: (kind: ConnectorRecord["kind"]) => string;
109
+ now?: string;
110
+ }): ConnectorRecord[];
@@ -0,0 +1,245 @@
1
+ /**
2
+ * One mission, many versions, one decision (jgwill/Miadi#919).
3
+ *
4
+ * A request that still waits for a decision is revised in place: a revision is a
5
+ * record appended to the ledger that supersedes the request and carries the whole
6
+ * new content, so the request keeps its id, its approval page and its card, and
7
+ * every earlier version stays readable verbatim. Requests made separately before
8
+ * this existed (one new request per revision) are joined by `supersedePending`: the
9
+ * request that stands adopts the others as its earlier versions, and each adopted
10
+ * one is retired, so only the standing request can be approved.
11
+ *
12
+ * A decision names the version it decides. The approval page and the card carry it,
13
+ * and a decision on any other version is refused as stale, so no brief is ever
14
+ * queued for a version the person did not read.
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ /** The kinds that open a session when approved, and the only ones revised in place. */
18
+ export const REVISABLE_KINDS = new Set(["run_request", "work_proposal"]);
19
+ /** A request is revised only before a decision runs it: waiting, or sent back by its approver. */
20
+ export const REVISABLE_STATES = new Set(["awaiting_approval", "revision_requested"]);
21
+ /** The fields that make a request's content: what a version is. */
22
+ export const CONTENT_FIELDS = ["intention", "circle", "refs", "body"];
23
+ function canonical(value) {
24
+ if (Array.isArray(value))
25
+ return value.map(canonical);
26
+ if (value && typeof value === "object") {
27
+ return Object.fromEntries(Object.keys(value)
28
+ .filter((k) => value[k] !== undefined)
29
+ .sort()
30
+ .map((k) => [k, canonical(value[k])]));
31
+ }
32
+ return value;
33
+ }
34
+ /** The content of a request version, as the hash and the history show it. */
35
+ export function contentOf(r) {
36
+ return { kind: r.kind, intention: r.intention, circle: r.circle, refs: r.refs ?? [], body: r.body ?? {} };
37
+ }
38
+ /** sha256 of the canonical JSON of a version's content. */
39
+ export function contentHash(r) {
40
+ return `sha256:${createHash("sha256").update(JSON.stringify(canonical(contentOf(r)))).digest("hex")}`;
41
+ }
42
+ /** The version a folded request is at: 1 for a request never revised. */
43
+ export function versionOf(r) {
44
+ return typeof r?.version === "number" && r.version >= 1 ? r.version : 1;
45
+ }
46
+ /** The names of the content fields (body fields by name) that differ between two versions. */
47
+ export function changedFields(before, after) {
48
+ const out = [];
49
+ const same = (a, b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
50
+ for (const k of ["intention", "circle", "refs"])
51
+ if (!same(before[k], after[k]))
52
+ out.push(k);
53
+ const b = (before.body ?? {});
54
+ const a = (after.body ?? {});
55
+ for (const k of [...new Set([...Object.keys(b), ...Object.keys(a)])].sort())
56
+ if (!same(b[k], a[k]))
57
+ out.push(k);
58
+ return out;
59
+ }
60
+ /**
61
+ * Every version of a request, oldest first, numbered across its lineage: the requests
62
+ * it adopted (each with its own versions) and then its own. `raw` is the unfolded ledger,
63
+ * `folded` the folded one (`current()`).
64
+ */
65
+ export function requestHistory(id, raw, folded = []) {
66
+ // `current` marks the version the request holds now: the last one.
67
+ const own = (rid, seen) => {
68
+ if (seen.has(rid))
69
+ return [];
70
+ seen.add(rid);
71
+ const base = raw.find((r) => r.id === rid);
72
+ if (!base)
73
+ return [];
74
+ const out = [];
75
+ const lineage = raw.filter((r) => r.supersedes === rid && r.lineage).flatMap((r) => r.lineage.adopted);
76
+ const adopted = lineage
77
+ .map((a) => raw.find((r) => r.id === a))
78
+ .filter((r) => !!r)
79
+ .sort((x, y) => x.created_at.localeCompare(y.created_at));
80
+ for (const a of adopted)
81
+ out.push(...own(a.id, seen));
82
+ out.push({ record_id: base.id, request_id: rid, at: base.created_at, author: base.actor?.name ?? "someone", content_hash: contentHash(base), content: contentOf(base) });
83
+ for (const rev of raw.filter((r) => r.supersedes === rid && r.revision)) {
84
+ out.push({
85
+ record_id: rev.id,
86
+ request_id: rid,
87
+ at: rev.revision.at,
88
+ author: rev.revision.author.name,
89
+ reason: rev.revision.reason,
90
+ changed: rev.revision.changed,
91
+ content_hash: rev.revision.content_hash,
92
+ content: contentOf(rev),
93
+ });
94
+ }
95
+ return out;
96
+ };
97
+ void folded;
98
+ const versions = own(id, new Set());
99
+ return versions.map((v, i) => ({ ...v, version: i + 1, current: i === versions.length - 1 }));
100
+ }
101
+ /** A revision refused, with the code a tool answers. */
102
+ export class RevisionRefused extends Error {
103
+ code;
104
+ detail;
105
+ constructor(code, message, detail) {
106
+ super(message);
107
+ this.code = code;
108
+ this.detail = detail;
109
+ }
110
+ }
111
+ /**
112
+ * The new record that revises a waiting request, or a refusal. `next` holds the new
113
+ * content (kind, intention, circle, refs, body); `live` is the folded request.
114
+ */
115
+ export function revisionRecord(live, next, o) {
116
+ if (!live)
117
+ throw new RevisionRefused("not_found", "No request with that id.");
118
+ if (!REVISABLE_KINDS.has(live.kind))
119
+ throw new RevisionRefused("invalid_request", `A ${live.kind} is not revised in place; only agent runs and work proposals are.`);
120
+ if (!o.person.writer && live.actor?.person_id !== o.person.id) {
121
+ throw new RevisionRefused("forbidden", "Only whoever asked, or a person with Miadi's write tier, revises a request.");
122
+ }
123
+ if (o.beingDecided)
124
+ throw new RevisionRefused("being_decided", "A decision on this request is being recorded; it cannot be revised now.");
125
+ if (!REVISABLE_STATES.has(live.approval_state ?? "")) {
126
+ throw new RevisionRefused("not_revisable", `This request is already ${live.approval_state ?? "decided"}. A change after a decision is a new request, or a steer of the session it opened (request_session_steer).`);
127
+ }
128
+ const current = versionOf(live);
129
+ if (o.expectedVersion !== current) {
130
+ throw new RevisionRefused("stale_version", `The request is at version ${current}, not ${o.expectedVersion}. Read it with get_request_history and revise version ${current}.`, {
131
+ current_version: current,
132
+ });
133
+ }
134
+ const before = contentOf(live);
135
+ const after = contentOf({ kind: live.kind, intention: next.intention, circle: next.circle, refs: next.refs, body: next.body });
136
+ const changed = changedFields(before, after);
137
+ if (!changed.length)
138
+ throw new RevisionRefused("no_change", `Version ${current} already says this; nothing was recorded.`, { current_version: current });
139
+ const at = o.now ?? new Date().toISOString();
140
+ const sentBack = live.approval_state === "revision_requested";
141
+ return {
142
+ id: o.id,
143
+ kind: live.kind,
144
+ created_at: at,
145
+ actor: o.actor,
146
+ refs: next.refs,
147
+ evidence_refs: [],
148
+ ...(next.intention !== undefined ? { intention: next.intention } : {}),
149
+ ...(next.circle !== undefined ? { circle: next.circle } : {}),
150
+ body: next.body,
151
+ supersedes: live.id,
152
+ approval_state: "awaiting_approval",
153
+ state: "pending",
154
+ next_action: `Awaiting a decision on version ${current + 1}.`,
155
+ ...(o.idempotency_key ? { idempotency_key: o.idempotency_key } : {}),
156
+ // A request sent back keeps the approver's instructions on its receipt, now as what the revision answers.
157
+ ...(sentBack && live.completion_receipt ? { completion_receipt: { ...live.completion_receipt, answered_by_version: current + 1 } } : {}),
158
+ revision: {
159
+ version: current + 1,
160
+ previous_version: current,
161
+ reason: o.reason,
162
+ changed,
163
+ content_hash: contentHash({ kind: live.kind, intention: next.intention, circle: next.circle, refs: next.refs, body: next.body }),
164
+ previous_hash: contentHash(live),
165
+ author: { person_id: o.person.id, name: o.person.name },
166
+ at,
167
+ },
168
+ };
169
+ }
170
+ /**
171
+ * The records that make `standingId` the one request of a lineage: one retirement for
172
+ * each named request still waiting, and one lineage record on the standing request.
173
+ * Every named request is checked before anything is written; one refusal writes none.
174
+ * Only a request named by its exact id is retired, never one that looks similar, and a
175
+ * request already decided (approved, queued, declined) is refused, never cancelled.
176
+ */
177
+ export function supersedeRecords(folded, standingId, retire, o) {
178
+ const standing = folded.find((r) => r.id === standingId);
179
+ if (!standing)
180
+ throw new RevisionRefused("not_found", `No request ${standingId}.`);
181
+ if (!REVISABLE_KINDS.has(standing.kind))
182
+ throw new RevisionRefused("invalid_request", "Only agent runs and work proposals carry versions.");
183
+ if (!REVISABLE_STATES.has(standing.approval_state ?? "")) {
184
+ throw new RevisionRefused("not_revisable", `The request that stands is already ${standing.approval_state}; earlier versions are joined only to a request still waiting.`);
185
+ }
186
+ if (o.beingDecided(standingId))
187
+ throw new RevisionRefused("being_decided", "A decision on the request that stands is being recorded; nothing was retired.");
188
+ // Earlier requests come before the standing one's own versions: joined after an in-place revision,
189
+ // the numbers it was given would shift (#919 review F4).
190
+ if (versionOf(standing) > 1) {
191
+ throw new RevisionRefused("not_revisable", `The request that stands is already at version ${versionOf(standing)}: join earlier requests to it before revising it in place, or revise it instead.`);
192
+ }
193
+ const ids = [...new Set(retire.map((x) => x.trim()).filter(Boolean))];
194
+ if (!ids.length)
195
+ throw new RevisionRefused("invalid_request", "Name at least one earlier request by its exact id.");
196
+ if (ids.includes(standingId))
197
+ throw new RevisionRefused("invalid_request", "The request that stands cannot also be retired.");
198
+ const may = (r) => o.person.writer || r.actor?.person_id === o.person.id;
199
+ if (!may(standing))
200
+ throw new RevisionRefused("forbidden", "Only whoever asked, or a person with Miadi's write tier, joins versions of a request.");
201
+ const olds = [];
202
+ for (const id of ids) {
203
+ const r = folded.find((x) => x.id === id);
204
+ if (!r)
205
+ throw new RevisionRefused("not_found", `No request ${id}; nothing was retired.`);
206
+ if (r.kind !== standing.kind)
207
+ throw new RevisionRefused("invalid_request", `${id} is a ${r.kind}, the request that stands a ${standing.kind}; nothing was retired.`);
208
+ if (!may(r))
209
+ throw new RevisionRefused("forbidden", `${id} was asked by someone else; nothing was retired.`);
210
+ if (o.beingDecided(id))
211
+ throw new RevisionRefused("being_decided", `A decision on ${id} is being recorded; nothing was retired.`);
212
+ if (!REVISABLE_STATES.has(r.approval_state ?? "")) {
213
+ throw new RevisionRefused("not_revisable", `${id} is already ${r.approval_state}: a decided request is never retired. Nothing was retired.`);
214
+ }
215
+ olds.push(r);
216
+ }
217
+ const at = o.now ?? new Date().toISOString();
218
+ const by = { person_id: o.person.id, name: o.person.name };
219
+ const out = olds.map((r) => ({
220
+ id: o.newId(r.kind),
221
+ kind: r.kind,
222
+ created_at: at,
223
+ actor: o.actor,
224
+ refs: r.refs,
225
+ evidence_refs: [],
226
+ supersedes: r.id,
227
+ approval_state: "declined",
228
+ state: "withdrawn",
229
+ next_action: `Superseded by ${standingId}: decide that request instead.`,
230
+ completion_receipt: { decision: "superseded", superseded_by: standingId, decided_by: by, decided_at: at, via: o.actor.client_name ?? o.actor.client_id, reason: o.reason },
231
+ body: { decision: "superseded", superseded_by: standingId },
232
+ }));
233
+ out.push({
234
+ id: o.newId(standing.kind),
235
+ kind: standing.kind,
236
+ created_at: at,
237
+ actor: o.actor,
238
+ refs: standing.refs,
239
+ evidence_refs: [],
240
+ supersedes: standingId,
241
+ body: { decision: "lineage" },
242
+ lineage: { adopted: olds.map((r) => r.id), reason: o.reason, author: by, at },
243
+ });
244
+ return out;
245
+ }
package/dist/server.d.ts CHANGED
@@ -4,17 +4,21 @@ import { Ledger } from "./store.js";
4
4
  import type { ConnectorRecord } from "./records.js";
5
5
  import type { ConnectorConfig } from "./config.js";
6
6
  import type { TmuxLike } from "./steer.js";
7
- import type { DiscoveryProbe } from "./providers.js";
7
+ import type { DiscoveryProbe, ProviderId } from "./providers.js";
8
+ import type { JevAdapter } from "./model-preflight.js";
8
9
  export interface ConnectorDeps {
9
10
  identify: (token: string) => Promise<MiadiPerson | null>;
10
11
  fetchImpl?: typeof fetch;
11
- /** The factory directory holding queue/ (default ~/.miadi/factory). */
12
+ /** The factory directory holding queue/ (default /srv/miadi/factory). */
12
13
  factoryDir?: string;
13
14
  tmuxSessions?: () => string[];
14
15
  /** The steering surface (jgwill/Miadi#893); tests inject a fake, production uses tmux. */
15
16
  tmux?: TmuxLike;
16
17
  /** Provider discovery probes (jgwill/Miadi#893); tests inject fakes. */
17
18
  providersProbe?: DiscoveryProbe;
19
+ /** A provider's configured default model and a Jev decider (jgwill/Miadi#913); tests inject fakes. */
20
+ modelOf?: (provider: ProviderId) => string | null;
21
+ jevAdapter?: JevAdapter;
18
22
  /** Re-peek delay after a steer is typed (default 4000ms; tests pass 0). */
19
23
  steerSettleMs?: number;
20
24
  /** Person-token attempts allowed per address in 15 minutes on each sign-in form (default 30). */
@@ -25,4 +29,4 @@ export declare function createConnectorApp(config: ConnectorConfig, deps: Connec
25
29
  provider: MiadiOAuthProvider;
26
30
  ledger: Ledger<ConnectorRecord>;
27
31
  };
28
- export declare const INSTRUCTIONS = "Miadi is a relational software factory: agent sessions, Miadi reviews of screenwalks, Chronicle episodes, talking circles and the five accountable circles (Story, Stewardship, Engineering, Evaluation, Continuity) across three domains (Creative, Ceremonial, Engineering).\n\nStart with get_capabilities: it says who you are signed in as, which tools are live, and which return not_implemented.\n\nRules every tool keeps:\n- Every record names the person who made it, with a claim category: observed, hypothesis, desired_future, fiction or verified_implementation. Keep fiction and desired futures apart from what was observed.\n- Requests (runs, work proposals, review operations, Deep Diver, review-to-episode links, inquiry-to-episode weaves, session completion) are recorded as awaiting_approval. Nothing runs, publishes or closes until a person decides. After making a request, call show_decisions: it shows the request as a card in the conversation, where the person decides with their own click (or follows its approval page). Never claim a decision the card has not reported. The person may approve, approve with changes (their instructions are carried into what runs), send it back for revision with instructions (approval_state revision_requested; read the instructions with get_run_result and submit a revised request citing the old id in refs), or decline.\n- A research report (a Deep Research report) is delivered with save_research exactly as written, never summarised or shortened, with its sources; a long report goes in parts. Check the bytes and first and last lines the answer returns against what you sent.\n- An unknown id is answered as not found, never invented. A missing backing service is answered as unavailable.\n- Use the miadi-review:<uuid>, miadi-chronicle://<n>, owner/repo#n and session:<uuid> reference forms.";
32
+ export declare const INSTRUCTIONS = "Miadi is a relational software factory: agent sessions, Miadi reviews of screenwalks, Chronicle episodes, talking circles and the five accountable circles (Story, Stewardship, Engineering, Evaluation, Continuity) across three domains (Creative, Ceremonial, Engineering).\n\nStart with get_capabilities: it says who you are signed in as, which tools are live, and which return not_implemented.\n\nRules every tool keeps:\n- Every record names the person who made it, with a claim category: observed, hypothesis, desired_future, fiction or verified_implementation. Keep fiction and desired futures apart from what was observed.\n- Requests (runs, work proposals, review operations, Deep Diver, review-to-episode links, inquiry-to-episode weaves, session completion) are recorded as awaiting_approval. Nothing runs, publishes or closes until a person decides. After making a request, call show_decisions: it shows the request as a card in the conversation, where the person decides with their own click (or follows its approval page). Never claim a decision the card has not reported. The person may approve, approve with changes (their instructions are carried into what runs), send it back for revision with instructions (approval_state revision_requested; read the instructions with get_run_result and revise it in place with revise_pending_request), or decline.\n- A mission is revised, never re-requested: to change a run or work proposal that still awaits a decision, call revise_pending_request with its id and the version you read (expected_version). It keeps one id, one approval page and one card; get_request_history shows every version. A new request_authorized_run or propose_work is for a new mission only. Requests made earlier as separate versions are joined with supersede_pending_requests, naming their exact ids, only when the person names or confirms them.\n- A research report (a Deep Research report) is delivered with save_research exactly as written, never summarised or shortened, with its sources; a long report goes in parts. Check the bytes and first and last lines the answer returns against what you sent.\n- An unknown id is answered as not found, never invented. A missing backing service is answered as unavailable.\n- Use the miadi-review:<uuid>, miadi-chronicle://<n>, owner/repo#n and session:<uuid> reference forms.";
package/dist/server.js CHANGED
@@ -108,6 +108,8 @@ export function createConnectorApp(config, deps) {
108
108
  tmux: deps.tmux,
109
109
  tmuxSessions: deps.tmuxSessions,
110
110
  providersProbe: deps.providersProbe,
111
+ modelOf: deps.modelOf,
112
+ jevAdapter: deps.jevAdapter,
111
113
  steerSettleMs: deps.steerSettleMs,
112
114
  };
113
115
  const server = new McpServer({ name: "miadi", version: VERSION }, { instructions: INSTRUCTIONS });
@@ -143,7 +145,8 @@ Start with get_capabilities: it says who you are signed in as, which tools are l
143
145
 
144
146
  Rules every tool keeps:
145
147
  - Every record names the person who made it, with a claim category: observed, hypothesis, desired_future, fiction or verified_implementation. Keep fiction and desired futures apart from what was observed.
146
- - Requests (runs, work proposals, review operations, Deep Diver, review-to-episode links, inquiry-to-episode weaves, session completion) are recorded as awaiting_approval. Nothing runs, publishes or closes until a person decides. After making a request, call show_decisions: it shows the request as a card in the conversation, where the person decides with their own click (or follows its approval page). Never claim a decision the card has not reported. The person may approve, approve with changes (their instructions are carried into what runs), send it back for revision with instructions (approval_state revision_requested; read the instructions with get_run_result and submit a revised request citing the old id in refs), or decline.
148
+ - Requests (runs, work proposals, review operations, Deep Diver, review-to-episode links, inquiry-to-episode weaves, session completion) are recorded as awaiting_approval. Nothing runs, publishes or closes until a person decides. After making a request, call show_decisions: it shows the request as a card in the conversation, where the person decides with their own click (or follows its approval page). Never claim a decision the card has not reported. The person may approve, approve with changes (their instructions are carried into what runs), send it back for revision with instructions (approval_state revision_requested; read the instructions with get_run_result and revise it in place with revise_pending_request), or decline.
149
+ - A mission is revised, never re-requested: to change a run or work proposal that still awaits a decision, call revise_pending_request with its id and the version you read (expected_version). It keeps one id, one approval page and one card; get_request_history shows every version. A new request_authorized_run or propose_work is for a new mission only. Requests made earlier as separate versions are joined with supersede_pending_requests, naming their exact ids, only when the person names or confirms them.
147
150
  - A research report (a Deep Research report) is delivered with save_research exactly as written, never summarised or shortened, with its sources; a long report goes in parts. Check the bytes and first and last lines the answer returns against what you sent.
148
151
  - An unknown id is answered as not found, never invented. A missing backing service is answered as unavailable.
149
152
  - Use the miadi-review:<uuid>, miadi-chronicle://<n>, owner/repo#n and session:<uuid> reference forms.`;
package/dist/tools.d.ts CHANGED
@@ -7,7 +7,8 @@ import type { OAuthState } from "./oauth.js";
7
7
  import { type ConnectorRecord } from "./records.js";
8
8
  import { type ApprovalDeps } from "./approve.js";
9
9
  import { type DecisionTokens } from "./decisions.js";
10
- import { type DiscoveryProbe } from "./providers.js";
10
+ import { type DiscoveryProbe, type ProviderId } from "./providers.js";
11
+ import { type JevAdapter } from "./model-preflight.js";
11
12
  import { type TmuxLike } from "./steer.js";
12
13
  export interface ToolContext {
13
14
  config: ConnectorConfig;
@@ -15,7 +16,7 @@ export interface ToolContext {
15
16
  reviews: ReviewClient;
16
17
  ledger: Ledger<ConnectorRecord>;
17
18
  oauthState: JsonFile<OAuthState>;
18
- /** The factory directory holding queue/ (default ~/.miadi/factory). */
19
+ /** The factory directory holding queue/ (default /srv/miadi/factory). */
19
20
  factoryDir?: string;
20
21
  /** What the in-chat approval card needs to run a decided request. */
21
22
  approval?: ApprovalDeps;
@@ -25,9 +26,19 @@ export interface ToolContext {
25
26
  tmuxSessions?: () => string[];
26
27
  /** Provider discovery probes; tests inject fakes (jgwill/Miadi#893). */
27
28
  providersProbe?: DiscoveryProbe;
29
+ /** The configured default model of a provider; tests inject fakes (jgwill/Miadi#913). */
30
+ modelOf?: (provider: ProviderId) => string | null;
31
+ /** A Jev decider; tests inject a synthetic fixture. Absent, the connector's env decides (jgwill/Miadi#913). */
32
+ jevAdapter?: JevAdapter;
28
33
  /** Re-peek delay after a steer is typed (default 4000ms; tests pass 0). */
29
34
  steerSettleMs?: number;
30
35
  }
36
+ /**
37
+ * Why GitHub did not answer a reference (jgwill/Miadi#913 B). A 404 without a
38
+ * token is a private repository as often as a missing issue, so nothing here is
39
+ * ever reported as not found.
40
+ */
41
+ export declare function githubAccess(status: number, hasToken: boolean): string;
31
42
  export declare function registerTools(server: McpServer, ctx: ToolContext): void;
32
43
  /** What get_capabilities reports for each tool. */
33
44
  export declare const TOOL_STATUS: Record<string, string>;