@miadi/connector 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,7 +56,11 @@ Requests made before this, one new request per revision, are joined with `supers
56
56
 
57
57
  A mission is found by the issue it implements (jgwill/Miadi#913). `request_authorized_run` and `propose_work` take `primary_issue`; older requests are read by their first issue ref, as an inference. An issue held as primary by several requests answers `ambiguous_mission` with the candidates, an issue only cited answers `related_only`, and only a record id or a declared primary that no older request holds by inference (`contested`) authorizes a steer, release or closure (`identity.authorizes_actions`). A release receipt names artifact, target, production check and check time; record it as `completion_receipt.release` on a handoff that cites the mission. `released` is `verified` only when the connector confirms it itself (the target is `miadi-connector` and the running version is the one released); any other complete receipt within seven days is `receipt_recorded`, with who recorded it and whether that was the approver. Old checks are `stale`; a version string, a future date or an incomplete receipt is `claimed_unverified`. `closed` needs an approved `propose_session_completion` ending (`exit` or `complete_then_exit`) that names the mission in its `mission` field and ends one of the mission's sessions; a completed request is `request_completed`. A GitHub reference the connector cannot read is `unverified`, with the reason (`no_github_token`, `not_visible_to_token`…), never `not_found`.
58
58
 
59
- `request_session_steer` sends one authorized follow-up to one existing live agent, within the steering grant an approved run carries (`steering: { max_attempts, expires_at, pane }` on `request_authorized_run`). It peeks at the bound pane — the grant's pane or the session's first pane, never whichever pane is active — refuses a busy agent or a pane that is not an agent, types the exact instruction literally, and re-peeks to verify the agent accepted it (`accepted`) or only holds it at the prompt (`submitted`). Every attempt leaves a durable artifact with the instruction's sha256, its grant, its target and both captures. A steer the grant does not cover waits for one explicit scoped human decision on its `approval_url`, which runs exactly that instruction once.
59
+ **The factory binding (jgwill/Miadi#909).** The factory runner records every brief it hands to a seat in `delivered.jsonl`: the record, the tmux session, the pane id and the Claude Code session id that took the line. `mission_status` reads that receipt (write tier) and checks it against Miadi's session registry: `binding` is `verified` when both agree on session, tmux session and pane, `receipt_only` when the registry cannot confirm it, `unverified_association` when the registry places the session elsewhere or the pane is gone. Busy or idle comes from the registry when the binding is verified, else from a read-only peek of the bound pane, and is never read as completion. `mission_status` with `session:<uuid>` finds the one mission the factory delivered to that session (`factory_bound`); two are `ambiguous_mission`. `provider` names the provider requested, resolved and actually run, and `match: false` when they differ; nothing is re-run because of it. `get_session` lists the missions the factory delivered to the session.
60
+
61
+ **The agent's result (jgwill/Miadi#921).** An agent returns its work with `propose_session_completion` naming the mission in `mission`, from its bound session, with its evidence refs and, optionally, a structured `result`: `summary`, `outputs` (`ref`, `label`, `git_sha`), `tests` (`command`, `passed`, `failed`, `skipped`) and `blockers`. `mission_status` and `get_run_result` read it back by the mission id as `result`: the standing submission (the newest `verified` one: it names the mission, ends a bound session and was submitted with Miadi's write tier or the factory's writer token, which the connector records on the ending as `submitted_with_tier`; anything else naming the mission is `unverified_association` with an `association_code`, a mere citation `cited_only` and never a result), its summary, tests and blockers as the agent's claim, and each output typed and checked when read. A commit or file on GitHub is read through GitHub (`GITHUB_TOKEN` when the host has one): `verified` with its sha, `changed_since_submission` when its declared `git_sha` no longer matches or the file changed on its branch after the submission, `unverified` with the reason when GitHub cannot be read, never `not_found`. A connector record id the ledger does not hold is `not_found`. Bound sessions are taken from the factory's receipts for every reader, so the verdict is the same at every tier; below the write tier the session ids are left out. The completion decision stays the person's, and only a verified submission sets it: `stage_truth.completion` is `pending_approval`, `accepted`, `declined` or `revision_requested` (`none` for an unverified one, whose stage is `result_submitted_unverified`; an approved exit submitted below the write tier closes nothing), `pending_human_gates` lists what waits on a person with its approval page, and `mission_stage` names the furthest stage with evidence (`result_submitted_awaiting_completion_approval`, `completion_accepted`, `merged`, `released`, `closed`…). A duplicate submission (same session, ending and mission, still pending) is handed back with `replayed: true`, and `not_recorded` names what the second call carried that was not kept.
62
+
63
+ `request_session_steer` sends one authorized follow-up to one existing live agent, within the steering grant an approved run carries (`steering: { max_attempts, expires_at, pane }` on `request_authorized_run`). It peeks at the bound pane — the grant's pane or the session's first pane, never whichever pane is active — refuses a busy agent or a pane that is not an agent, types the exact instruction literally, and re-peeks to verify the agent accepted it (`accepted`) or only holds it at the prompt (`submitted`). Every attempt leaves a durable artifact with the instruction's sha256, its grant, its target and both captures. When the factory recorded the mission's pane, the steer goes to that pane id and nowhere else: another session is `stale_binding`, another pane `wrong_pane`, a replaced pane `stale_binding`, and text already at the prompt `composing` (nothing typed, Enter not pressed). A pane whose prompt box cannot be read (any TUI but Claude Code's today) is `blocked`. Every tmux address is exact (`=name:w.p`), because tmux otherwise matches a session by the start of its name. Only a steer that typed its instruction uses one of the grant's attempts (`typed`, and `attempts` in the answer). The connector's service steers through the tmux server of the user it runs as; `get_capabilities.steering` says whether that transport answers now, and while it does not, `request_session_steer` records nothing and uses no attempt. A steer the grant does not cover waits for one explicit scoped human decision on its `approval_url`, which runs exactly that instruction once.
60
64
 
61
65
  `request_authorized_run` may name a `provider` (`codex`, `pi`, `copilot`, `claude`). The connector verifies it is installed on the host before recording anything; an unknown or unavailable provider answers `provider_unavailable` and the mission stays unstarted. With no provider named, the first installed provider in the preference order is resolved and named in the brief; Claude Code is never chosen silently — it runs only when named. `provider_capabilities` (write tier) reports what the host has installed, the sign-in evidence, the preference order, and the read-only Jev check: `unconfigured` without `JEV_AI_API_KEY` or `TYPESAFE_API_KEY` in the connector's environment, else the models Jev lists for the key (no charge). A provider name inside a task's text selects nothing.
62
66
 
package/dist/approve.js CHANGED
@@ -19,6 +19,7 @@ import { MiadiUnavailable } from "./people.js";
19
19
  import { current, newRecordId } from "./records.js";
20
20
  import { APPROVABLE, projectRequest } from "./request-state.js";
21
21
  import { factoryDir, runBriefFile, readQueueState } from "./queue-state.js";
22
+ import { factoryLedgerPath, readFactoryReceipts, receiptsForRecord } from "./factory-receipt.js";
22
23
  import { steerAttempts, steerSession, tmuxAdapter } from "./steer.js";
23
24
  import { contentHash, versionOf } from "./revisions.js";
24
25
  export { factoryDir, runBriefFile, readQueueState } from "./queue-state.js";
@@ -108,6 +109,8 @@ export function runBrief(record, approver, sessions, instructions) {
108
109
  : []),
109
110
  "",
110
111
  "When it is done, reply with what was achieved, the evidence (commits, paths, URLs), what is still open, and whether this session should end. Then record the receipt through the connector's update_handoff if a handoff names this record, and end the session per its lifecycle.",
112
+ // The return path a requester reads by the mission id (jgwill/Miadi#921).
113
+ `Return the result to the requester with the connector's propose_session_completion: your Claude Code session id as session_id, mission "${text(record.id)}", the ending, the reason, evidence_refs, and result { summary, outputs (ref, label, git_sha), tests (command, passed, failed), blockers }. mission_status and get_run_result read it back by the mission id; a person decides the ending.`,
111
114
  "",
112
115
  "Queued by the Miadi connector's approval door, delivered by the factory runner (@miadi/factory-runner).",
113
116
  "</input>",
@@ -150,7 +153,15 @@ export async function executeApproved(record, approver, deps, instructions) {
150
153
  const queueBrief = () => {
151
154
  const queue = join(deps.factoryDir ?? factoryDir(), "queue");
152
155
  mkdirSync(queue, { recursive: true });
153
- const { to, brief } = runBrief(record, approver, (deps.tmuxSessions ?? liveTmux)(), instructions);
156
+ // A lister that cannot answer means no session is known live: the brief opens a new one (#922 R16).
157
+ let sessions;
158
+ try {
159
+ sessions = (deps.tmuxSessions ?? liveTmux)();
160
+ }
161
+ catch {
162
+ sessions = [];
163
+ }
164
+ const { to, brief } = runBrief(record, approver, sessions, instructions);
154
165
  const file = runBriefFile(record);
155
166
  writeFileSync(join(queue, file), brief, { flag: "wx", mode: 0o640 });
156
167
  return { outcome: "queued", to, file, note: "The factory intake delivers queued briefs every 5 minutes." };
@@ -221,11 +232,20 @@ export async function executeApproved(record, approver, deps, instructions) {
221
232
  const grantRecord = grantId ? current(deps.ledger.all()).find((x) => x.id === grantId) : undefined;
222
233
  if (!grantRecord)
223
234
  return { outcome: "failed", message: "The run whose grant this steer cites is gone." };
224
- const result = await steerSession({ tmux: deps.tmux ?? tmuxAdapter(), stateDir: deps.config.stateDir, ...(deps.steerSettleMs !== undefined ? { settleMs: deps.steerSettleMs } : {}) }, { record, grantRecord, priorAttempts: steerAttempts(current(deps.ledger.all()), grantRecord.id), authorized: true });
225
- const delivered = result.status === "accepted" || result.status === "submitted";
235
+ // The pane the factory delivered the mission to, when it recorded one (jgwill/Miadi#909).
236
+ const bound = receiptsForRecord(readFactoryReceipts(factoryLedgerPath(deps.factoryDir ?? factoryDir())), grantRecord.id)[0];
237
+ const result = await steerSession({ tmux: deps.tmux ?? tmuxAdapter(), stateDir: deps.config.stateDir, ...(deps.steerSettleMs !== undefined ? { settleMs: deps.steerSettleMs } : {}) }, {
238
+ record,
239
+ grantRecord,
240
+ priorAttempts: steerAttempts(current(deps.ledger.all()), grantRecord.id),
241
+ authorized: true,
242
+ ...(bound ? { binding: { session: bound.tmux_session, ...(bound.pane_id ? { pane_id: bound.pane_id } : {}) } } : {}),
243
+ });
244
+ // A typed steer is never "failed": failed is re-runnable, and that would type it twice (#922 R8).
226
245
  return {
227
- outcome: delivered ? result.status : "failed",
246
+ outcome: result.typed ? result.status : "failed",
228
247
  steer_status: result.status,
248
+ typed: result.typed,
229
249
  message: result.message,
230
250
  ...(result.target ? { target: result.target } : {}),
231
251
  ...(result.artifact ? { artifact: result.artifact } : {}),
package/dist/bin.js CHANGED
@@ -2,13 +2,24 @@
2
2
  import { configFromEnv } from "./config.js";
3
3
  import { createConnectorApp } from "./server.js";
4
4
  import { identifyThroughMiadi } from "./people.js";
5
+ import { steeringTransport } from "./tools.js";
6
+ import { tmuxAdapter } from "./steer.js";
5
7
  import { VERSION } from "./version.js";
6
8
  const config = configFromEnv();
7
- const { app } = createConnectorApp(config, { identify: identifyThroughMiadi(config.miadiUrl) });
9
+ // The host's steering surface (jgwill/Miadi#909): the tmux server of the user the service runs as,
10
+ // reached only through request_session_steer inside an approved grant, at the mission's bound pane.
11
+ const tmux = tmuxAdapter();
12
+ const { app } = createConnectorApp(config, {
13
+ identify: identifyThroughMiadi(config.miadiUrl),
14
+ tmux,
15
+ tmuxSessions: () => tmux.listSessions(),
16
+ });
8
17
  app.listen(config.port, config.host, () => {
9
18
  console.log(`@miadi/connector ${VERSION} on http://${config.host}:${config.port}`);
10
19
  console.log(` public: ${config.publicUrl}/mcp`);
11
20
  console.log(` miadi: ${config.miadiUrl}`);
12
21
  console.log(` reviews: ${config.reviewUrl} (search ${config.reviewToken ? "enabled" : "disabled: no MIADI_REVIEW_TOKEN"})`);
13
22
  console.log(` state: ${config.stateDir}`);
23
+ const steering = steeringTransport(tmux);
24
+ console.log(` steering: ${steering.transport}${steering.reason ? ` (${steering.reason})` : ""}`);
14
25
  });
@@ -0,0 +1,28 @@
1
+ export interface FactoryReceipt {
2
+ /** The runner's dedupe key for the delivered block. */
3
+ key: string;
4
+ /** When the runner recorded the delivery. */
5
+ at: string;
6
+ /** The connector record the brief carried, e.g. run_request:<uuid>. */
7
+ record: string;
8
+ /** The tmux session the brief went to (without the runner's `new:` prefix). */
9
+ tmux_session: string;
10
+ /** Whether the runner opened that session for this brief. */
11
+ created: boolean;
12
+ /** tmux pane id the line was typed into, e.g. %140. */
13
+ pane_id?: string;
14
+ /** Claude Code session id the runner saw take the line. */
15
+ session_id?: string;
16
+ provider?: string;
17
+ launcher?: string;
18
+ /** The brief file the line pointed the seat at. */
19
+ brief?: string;
20
+ }
21
+ /** The runner's ledger file: MIADI_FACTORY_LEDGER, else `<factory>/delivered.jsonl`, as the runner resolves it. */
22
+ export declare function factoryLedgerPath(dir?: string, env?: NodeJS.ProcessEnv): string;
23
+ /** Every delivery the runner recorded as sent, oldest first. Unreadable or malformed lines are skipped, never guessed. */
24
+ export declare function readFactoryReceipts(file: string): FactoryReceipt[];
25
+ /** The receipts for one connector record, newest first. */
26
+ export declare function receiptsForRecord(receipts: FactoryReceipt[], recordId: string): FactoryReceipt[];
27
+ /** The distinct records delivered to one Claude Code session id. */
28
+ export declare function recordsForSession(receipts: FactoryReceipt[], sessionId: string): string[];
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The factory runner's delivery receipts (jgwill/Miadi#911, read here for #909 and #921).
3
+ *
4
+ * `@miadi/factory-runner` appends one line to `<factory>/delivered.jsonl` for every
5
+ * brief it hands to a seat: the connector record it carried, the tmux session it
6
+ * went to, the pane, and the Claude Code session id the runner saw take the line
7
+ * (from Claude's own session registry and transcript, deliver.js `submitLine`).
8
+ * That line is the binding between an approved run and the agent that runs it.
9
+ * The connector only reads it: a receipt never dispatches, steers or closes.
10
+ */
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { factoryDir } from "./queue-state.js";
14
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
15
+ const str = (v) => (typeof v === "string" && v.trim() ? v.trim() : undefined);
16
+ /** The runner's ledger file: MIADI_FACTORY_LEDGER, else `<factory>/delivered.jsonl`, as the runner resolves it. */
17
+ export function factoryLedgerPath(dir = factoryDir(), env = process.env) {
18
+ return env.MIADI_FACTORY_LEDGER || join(dir, "delivered.jsonl");
19
+ }
20
+ /** Every delivery the runner recorded as sent, oldest first. Unreadable or malformed lines are skipped, never guessed. */
21
+ export function readFactoryReceipts(file) {
22
+ if (!existsSync(file))
23
+ return [];
24
+ let text;
25
+ try {
26
+ text = readFileSync(file, "utf8");
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ const out = [];
32
+ for (const line of text.split("\n")) {
33
+ if (!line.trim())
34
+ continue;
35
+ let r;
36
+ try {
37
+ r = JSON.parse(line);
38
+ }
39
+ catch {
40
+ continue;
41
+ }
42
+ const record = str(r.record);
43
+ const to = str(r.to)?.replace(/^new:/, "");
44
+ if (r.sent !== true || !record || !to)
45
+ continue;
46
+ const sessionId = str(r.session_id);
47
+ out.push({
48
+ key: str(r.key) ?? "",
49
+ at: str(r.at) ?? "",
50
+ record,
51
+ tmux_session: to,
52
+ created: r.created === true,
53
+ ...(str(r.pane) ? { pane_id: str(r.pane) } : {}),
54
+ ...(sessionId && UUID.test(sessionId) ? { session_id: sessionId } : {}),
55
+ ...(str(r.provider) ? { provider: str(r.provider) } : {}),
56
+ ...(str(r.launcher) ? { launcher: str(r.launcher) } : {}),
57
+ ...(str(r.brief) ? { brief: str(r.brief) } : {}),
58
+ });
59
+ }
60
+ return out;
61
+ }
62
+ /** The receipts for one connector record, newest first. */
63
+ export function receiptsForRecord(receipts, recordId) {
64
+ return receipts.filter((r) => r.record === recordId).sort((a, b) => b.at.localeCompare(a.at));
65
+ }
66
+ /** The distinct records delivered to one Claude Code session id. */
67
+ export function recordsForSession(receipts, sessionId) {
68
+ return [...new Set(receipts.filter((r) => r.session_id === sessionId).map((r) => r.record))];
69
+ }
package/dist/index.d.ts CHANGED
@@ -3,10 +3,12 @@ export { configFromEnv, type ConnectorConfig } from "./config.js";
3
3
  export { identifyThroughMiadi, MiadiUnavailable, type MiadiPerson, type ApiTier } from "./people.js";
4
4
  export { MiadiOAuthProvider, SCOPES, SCOPE_READ, SCOPE_CONTRIBUTE, type Person } from "./oauth.js";
5
5
  export { CIRCLES, CIRCLE_IDS, CLAIM_CATEGORIES, DOMAINS, HANDOFF_STATES, RECORD_KINDS, current, type ConnectorRecord, type CircleId, type ClaimCategory, type RecordKind, } from "./records.js";
6
- export { TOOL_STATUS } from "./tools.js";
6
+ export { TOOL_STATUS, githubGetter, steeringTransport } from "./tools.js";
7
+ export { factoryLedgerPath, readFactoryReceipts, receiptsForRecord, recordsForSession, type FactoryReceipt } from "./factory-receipt.js";
8
+ export { classifyOutput, missionResult, type GithubAnswer, type GithubGet, type MissionOutput, type MissionResult, type OutputCheck, type ResultSubmission, } from "./mission-result.js";
7
9
  export { runBrief, readQueueState } from "./approve.js";
8
10
  export { PROVIDERS, discoverProviders, jevConfigOf, resolveProvider, routerPreflight, type JevConfig, type ProviderAvailability, type ProviderId, type ProviderResolution, type RouterPreflight, } from "./providers.js";
9
- export { missionStatus, primaryIssueOf, resolveMissionSubject, type MissionStatus, type MissionStatusDeps, type MissionIdentity } from "./mission-status.js";
11
+ export { missionStage, missionStatus, primaryIssueOf, resolveMissionSubject, type MissionIdentity, type MissionProvider, type MissionSession, type MissionStatus, type MissionStatusDeps, } from "./mission-status.js";
10
12
  export { modelPreflight, candidatesFor, buildJevRequest, decisionState, jevAdapter, reviewFloor, PREFLIGHT_CONTRACT, PREFLIGHT_VERSION, JEV_CONFIDENCE_FLOOR, type ModelPreflight, type ModelCandidate, type JevAdapter, type JevRequest, } from "./model-preflight.js";
11
- export { AGENT_COMMANDS, BUSY_PATTERNS, STEER_STATUSES, checkGrant, isBusy, readSteerArtifact, steerAttempts, steerSession, steeringGrantOf, tmuxAdapter, type SteerDeps, type SteerResult, type SteerStatus, type SteeringGrant, type TmuxLike, } from "./steer.js";
13
+ export { AGENT_COMMANDS, BUSY_PATTERNS, STEER_STATUSES, checkGrant, composingText, isBusy, readSteerArtifact, steerAttempts, steerSession, steeringGrantOf, tmuxAdapter, type SteerDeps, type SteerResult, type SteerStatus, type SteeringGrant, type TmuxLike, } from "./steer.js";
12
14
  export { VERSION } from "./version.js";
package/dist/index.js CHANGED
@@ -3,10 +3,12 @@ export { configFromEnv } from "./config.js";
3
3
  export { identifyThroughMiadi, MiadiUnavailable } from "./people.js";
4
4
  export { MiadiOAuthProvider, SCOPES, SCOPE_READ, SCOPE_CONTRIBUTE } from "./oauth.js";
5
5
  export { CIRCLES, CIRCLE_IDS, CLAIM_CATEGORIES, DOMAINS, HANDOFF_STATES, RECORD_KINDS, current, } from "./records.js";
6
- export { TOOL_STATUS } from "./tools.js";
6
+ export { TOOL_STATUS, githubGetter, steeringTransport } from "./tools.js";
7
+ export { factoryLedgerPath, readFactoryReceipts, receiptsForRecord, recordsForSession } from "./factory-receipt.js";
8
+ export { classifyOutput, missionResult, } from "./mission-result.js";
7
9
  export { runBrief, readQueueState } from "./approve.js";
8
10
  export { PROVIDERS, discoverProviders, jevConfigOf, resolveProvider, routerPreflight, } from "./providers.js";
9
- export { missionStatus, primaryIssueOf, resolveMissionSubject } from "./mission-status.js";
11
+ export { missionStage, missionStatus, primaryIssueOf, resolveMissionSubject, } from "./mission-status.js";
10
12
  export { modelPreflight, candidatesFor, buildJevRequest, decisionState, jevAdapter, reviewFloor, PREFLIGHT_CONTRACT, PREFLIGHT_VERSION, JEV_CONFIDENCE_FLOOR, } from "./model-preflight.js";
11
- export { AGENT_COMMANDS, BUSY_PATTERNS, STEER_STATUSES, checkGrant, isBusy, readSteerArtifact, steerAttempts, steerSession, steeringGrantOf, tmuxAdapter, } from "./steer.js";
13
+ export { AGENT_COMMANDS, BUSY_PATTERNS, STEER_STATUSES, checkGrant, composingText, isBusy, readSteerArtifact, steerAttempts, steerSession, steeringGrantOf, tmuxAdapter, } from "./steer.js";
12
14
  export { VERSION } from "./version.js";
@@ -0,0 +1,142 @@
1
+ /**
2
+ * A mission's result, as its agent submitted it (jgwill/Miadi#921).
3
+ *
4
+ * The agent returns its work through the record it already makes at the end:
5
+ * `propose_session_completion` naming the mission in `mission`, with its reason,
6
+ * its evidence refs and, since 0.1.16, an optional structured `result` (summary,
7
+ * outputs with hashes, tests, blockers). This file joins those submissions to the
8
+ * mission by exact id and checks them:
9
+ *
10
+ * - A submission is `verified` when it names the mission, ends one of the sessions
11
+ * the mission is bound to (the factory receipt's session id or a signed
12
+ * `session:` ref) and was made with Miadi's write tier (the factory's writer
13
+ * token included): a session id is a field anyone can type (#922 R3). Anything
14
+ * else naming the mission is `unverified_association`; citing it only in refs is
15
+ * `cited_only`. Only a verified submission sets the completion decision (#922 R4).
16
+ * - What the agent wrote (summary, tests, blockers) is the agent's claim. Outputs
17
+ * on GitHub are checked through GitHub as the connector can read it; a reference
18
+ * it cannot read is `unverified` with the reason, never `not_found`.
19
+ * - The completion decision stays the person's: pending, accepted, declined or
20
+ * sent back, read from the proposal's own decision. An idle pane is none of these.
21
+ */
22
+ import type { ConnectorRecord } from "./records.js";
23
+ export type GithubAnswer = {
24
+ ok: true;
25
+ json: any;
26
+ } | {
27
+ ok: false;
28
+ status: number;
29
+ access: string;
30
+ };
31
+ /** GET a GitHub REST path (e.g. /repos/o/r/commits/sha) as the connector can read it. */
32
+ export type GithubGet = (path: string) => Promise<GithubAnswer>;
33
+ export type OutputType = "commit" | "file" | "issue_or_pr" | "review" | "episode_file" | "record" | "link" | "unrecognized";
34
+ export interface OutputCheck {
35
+ /**
36
+ * verified: the connector read it now. changed_since_submission: it reads now, but its content
37
+ * changed after the agent submitted it. unverified: it could not be read (see access).
38
+ * not_found: a connector record id the ledger does not hold. unrecognized: not a reference the
39
+ * connector can resolve. not_checked: a kind the connector does not read here.
40
+ */
41
+ state: "verified" | "changed_since_submission" | "unverified" | "not_found" | "unrecognized" | "not_checked";
42
+ access?: string;
43
+ /** The commit sha, or the file's git blob sha, as read now. */
44
+ sha?: string;
45
+ size?: number;
46
+ committed_at?: string;
47
+ /** The file's latest commit on its ref, when its ref is a branch. */
48
+ last_changed_at?: string;
49
+ /** The ref is a branch, so the content behind the URL can move. */
50
+ ref_moves?: boolean;
51
+ checked_at: string;
52
+ note?: string;
53
+ }
54
+ export interface MissionOutput {
55
+ ref: string;
56
+ type: OutputType;
57
+ label?: string;
58
+ repository?: string;
59
+ commit?: string;
60
+ path?: string;
61
+ git_ref?: string;
62
+ /** The git sha the agent declared for it, when it declared one. */
63
+ declared_sha?: string;
64
+ check: OutputCheck;
65
+ /** How to read the whole of it, with the reader's own access. */
66
+ read?: string;
67
+ }
68
+ export type CompletionDecision = "pending_approval" | "accepted" | "declined" | "revision_requested";
69
+ /** Why a submission is not verified, as a code a viewer of any tier may read. */
70
+ export type AssociationReason = "cited_only" | "no_binding" | "not_bound_session" | "submitter_below_write_tier";
71
+ export interface ResultSubmission {
72
+ record: string;
73
+ submitted_at: string;
74
+ submitted_by: {
75
+ name: string;
76
+ person_id: string;
77
+ };
78
+ /** verified: names this mission and ends one of its bound sessions. unverified_association: names it, from a session the mission is not bound to (or none to compare). cited_only: cites it in refs only. */
79
+ association: "verified" | "unverified_association" | "cited_only";
80
+ association_code?: AssociationReason;
81
+ association_reason?: string;
82
+ session_id?: string;
83
+ ending?: string;
84
+ decision: CompletionDecision;
85
+ decided_by?: string;
86
+ decided_at?: string;
87
+ }
88
+ export interface ResultTests {
89
+ /** agent_claim: the agent's own count. not_structured: no test receipt beyond the summary's words. */
90
+ state: "agent_claim" | "not_structured";
91
+ command?: string;
92
+ passed?: number;
93
+ failed?: number;
94
+ skipped?: number;
95
+ note?: string;
96
+ }
97
+ export interface MissionResult {
98
+ state: "no_result_submitted" | "submitted";
99
+ /** The standing submission is verified; only then does it set the completion decision. */
100
+ verified: boolean;
101
+ /** The submission that stands: the newest verified one, else the newest naming the mission. */
102
+ current?: ResultSubmission & {
103
+ /** The agent's own account: a claim, not a verification. */
104
+ summary: string;
105
+ summary_claim: "agent_claim";
106
+ outputs: MissionOutput[];
107
+ tests: ResultTests;
108
+ blockers: string[];
109
+ /** Where a write-tier person decides the ending, while it is pending. */
110
+ approval_url?: string;
111
+ };
112
+ /** Every record that submits or cites a result for this mission, newest first. */
113
+ submissions: ResultSubmission[];
114
+ /** The completion decision of the standing submission when it is verified; "none" otherwise. */
115
+ completion: CompletionDecision | "none";
116
+ notes: string[];
117
+ }
118
+ export interface MissionResultDeps {
119
+ githubGet?: GithubGet;
120
+ /** Whether the submitter of a record held Miadi's write tier when it was made. Default: the record says so. */
121
+ submittedWithWriteTier?: (r: ConnectorRecord) => boolean;
122
+ approvalUrl?: (id: string) => string;
123
+ now?: () => Date;
124
+ }
125
+ /** What one reference is, before anything is read. */
126
+ export declare function classifyOutput(ref: string): Omit<MissionOutput, "check">;
127
+ /**
128
+ * The result a mission's agent submitted, checked. `boundSessions` are the Claude
129
+ * Code session ids the mission is bound to (empty when the viewer may not see them).
130
+ */
131
+ export declare function missionResult(mission: ConnectorRecord, records: ConnectorRecord[], boundSessions: Set<string>, deps?: MissionResultDeps): Promise<MissionResult>;
132
+ /** Endings made before 0.1.16 carry no recorded tier; from this time on every ending carries one (#922 R18). */
133
+ export declare const TIER_RECORDED_SINCE = "2026-11-05T00:00:00.000Z";
134
+ /**
135
+ * Whether a submission was made with Miadi's write tier. The factory's writer token is write tier
136
+ * by construction. Since 0.1.16 the connector records the submitter's tier on every ending. An
137
+ * ending without it is trusted only when it is older than TIER_RECORDED_SINCE: nobody can add a
138
+ * tier to it now, and a newer one without a tier was not written by this connector.
139
+ */
140
+ export declare function submittedWithWriteTier(r: ConnectorRecord): boolean;
141
+ /** The result as a viewer below the write tier reads it: no session ids, which are operator data. */
142
+ export declare function redactResult(result: MissionResult): MissionResult;