@miadi/connector 0.1.11 → 0.1.13

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/dist/tools.js CHANGED
@@ -18,13 +18,14 @@ import { CIRCLES, CIRCLE_IDS, CLAIM_CATEGORIES, DOMAINS, HANDOFF_STATES, current
18
18
  import { VERSION } from "./version.js";
19
19
  import { AlreadyDecided, DECISIONS, KIND_LABELS, consequenceOf, decide, isApprovable, readQueueState, requestProjection } from "./approve.js";
20
20
  import { DECISIONS_CARD_HTML, DECISIONS_CARD_URI, DECISION_TOKENS_META } from "./decisions.js";
21
- import { discoverProviders, resolveProvider, routerPreflight, PROVIDERS } from "./providers.js";
22
- import { missionStatus } from "./mission-status.js";
21
+ import { discoverProviders, jevConfigOf, resolveProvider, routerPreflight, PROVIDERS } from "./providers.js";
22
+ import { authorizesActions, missionStatus, primaryIssueOf, resolveMissionSubject } from "./mission-status.js";
23
+ import { candidatesFor, CRITICALITIES, jevAdapter, modelPreflight, PREFLIGHT_CONTRACT, PREFLIGHT_VERSION, TASK_TYPES, } from "./model-preflight.js";
23
24
  import { checkGrant, isBusy, steerAttempts, steeringGrantOf, steerSession } from "./steer.js";
24
25
  import { join } from "node:path";
25
26
  import { artefactVisibility, readArtefactWeave, saveResearchArtefact } from "@miadi/inquiry-weave";
26
27
  import { ResearchUploads, UploadRefusal } from "./uploads.js";
27
- import { DEFAULT_PAGE_BYTES, FileRefusal, MAX_TEXT_BYTES, MIN_PAGE_BYTES, TEXT_TYPES, checkRelativePath, episodeNumberOf, extensionOf, fileRef, pageOf, parseFileRef, } from "./episode-files.js";
28
+ import { DEFAULT_PAGE_BYTES, FOUNDATIONS_PERMISSION, FileRefusal, MAX_TEXT_BYTES, MIN_PAGE_BYTES, TEXT_TYPES, checkRelativePath, episodeNumberOf, extensionOf, fileRef, holdsFoundationRead, isFoundationDoc, pageOf, parseFileRef, } from "./episode-files.js";
28
29
  class ToolRefusal extends Error {
29
30
  code;
30
31
  detail;
@@ -84,6 +85,29 @@ const WRITE = { readOnlyHint: false, destructiveHint: false, openWorldHint: fals
84
85
  const refs = z.array(z.string()).max(50).default([]).describe("References: miadi-review:<uuid>, miadi-chronicle://<n>, owner/repo#n, session:<uuid>, a record id, or a URL.");
85
86
  const circle = z.enum(CIRCLE_IDS);
86
87
  const claim = z.enum(CLAIM_CATEGORIES).describe("observed, hypothesis, desired_future, fiction or verified_implementation.");
88
+ const primaryIssue = z
89
+ .string()
90
+ .regex(/^[\w.-]+\/[\w.-]+#\d+$/)
91
+ .optional()
92
+ .describe("owner/repo#n: the one issue this mission implements. Other refs stay related. Without it, mission_status can only infer the mission from the first issue in refs (jgwill/Miadi#913).");
93
+ /** Refs with the declared primary issue first, never duplicated. */
94
+ function withPrimary(refs, primary) {
95
+ return primary ? [primary, ...refs.filter((r) => r !== primary)] : refs;
96
+ }
97
+ /**
98
+ * Why GitHub did not answer a reference (jgwill/Miadi#913 B). A 404 without a
99
+ * token is a private repository as often as a missing issue, so nothing here is
100
+ * ever reported as not found.
101
+ */
102
+ export function githubAccess(status, hasToken) {
103
+ if (status === 404)
104
+ return hasToken ? "not_visible_to_token" : "no_github_token";
105
+ if (status === 401)
106
+ return "token_rejected";
107
+ if (status === 403 || status === 429)
108
+ return "forbidden_or_rate_limited";
109
+ return `github_${status}`;
110
+ }
87
111
  function episodeRef(input) {
88
112
  // miadi-chronicle://316, miadi-chronicle:316, chronicle:<folder>, a folder, or a number
89
113
  return encodeURIComponent(input.replace(/^miadi-chronicle:(\/\/)?/, "").replace(/^chronicle:/, "").trim());
@@ -161,7 +185,14 @@ export function registerTools(server, ctx) {
161
185
  annotations: READ,
162
186
  }, wrap(async (_args, extra) => {
163
187
  const { person, auth } = who(extra, "none");
164
- const miadiUp = await miadi.get(person.token, "/api/identity/me").then(() => "reachable", (e) => (e instanceof MiadiError && e.status === 0 ? "unavailable" : "reachable"));
188
+ let fresh;
189
+ const miadiUp = await miadi.get(person.token, "/api/identity/me").then((me) => {
190
+ if (Array.isArray(me?.permissions))
191
+ fresh = me.permissions.map(String);
192
+ return "reachable";
193
+ }, (e) => (e instanceof MiadiError && e.status === 0 ? "unavailable" : "reachable"));
194
+ // Miadi's answer now, not the sign-in's last one (cached up to five minutes).
195
+ const foundations = holdsFoundationRead(fresh ?? person.permissions);
165
196
  return {
166
197
  service: "@miadi/connector",
167
198
  version: VERSION,
@@ -175,6 +206,15 @@ export function registerTools(server, ctx) {
175
206
  ledger: "connector ledger (append-only, /srv/miadi/connector)",
176
207
  },
177
208
  tools: TOOL_STATUS,
209
+ foundation_read: {
210
+ permission: FOUNDATIONS_PERMISSION,
211
+ held: foundations,
212
+ reads: foundations
213
+ ? "read_episode_file reads foundations/<topic>/*.md in episodes you can see; Miadi checks the permission on every read."
214
+ : person.api_tier
215
+ ? "Not held. read_episode_file reads an episode's text files through your Miadi read tier."
216
+ : "Not held: nothing to read. An admin gives it under /admin → People → Additional permissions.",
217
+ },
178
218
  domains: DOMAINS,
179
219
  circles: CIRCLES.map((c) => ({ id: c.id, glyph: c.glyph, name: c.name })),
180
220
  not_built: [
@@ -211,7 +251,7 @@ export function registerTools(server, ctx) {
211
251
  signal: AbortSignal.timeout(15_000),
212
252
  });
213
253
  if (!res.ok)
214
- return { ref: r, kind: "github_issue", state: res.status === 404 ? "not_found_or_private" : `github_${res.status}`, cited_by: cited };
254
+ return { ref: r, kind: "github_issue", state: "unverified", access: githubAccess(res.status, Boolean(process.env.GITHUB_TOKEN)), cited_by: cited };
215
255
  const i = await res.json();
216
256
  return {
217
257
  ref: r,
@@ -341,6 +381,26 @@ export function registerTools(server, ctx) {
341
381
  * door; Miadi applies their audience, the room rule and the vessel's path
342
382
  * guard, and the path is checked here before a URL is built (jgwill/Miadi#815).
343
383
  */
384
+ /** A number, epNNN or a folder name; `..` would be folded by the URL parser into another route. */
385
+ const checkedEpisodeRef = (episode) => {
386
+ const ref = episodeRef(episode);
387
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(decodeURIComponent(ref))) {
388
+ throw new FileRefusal("invalid_path", "Name the episode as miadi-chronicle://<n>, its number or its folder.");
389
+ }
390
+ return ref;
391
+ };
392
+ /** An episode's foundation documents, by path and size: what read_episode_file reads with the permission alone. */
393
+ const foundationListing = async (person, episode) => {
394
+ const listing = await miadi.get(person.token, `/api/chronicle/episodes/${checkedEpisodeRef(episode)}/files`);
395
+ const folder = listing?.episode?.path;
396
+ const number = listing?.episode?.number ?? (folder ? episodeNumberOf(folder) : null);
397
+ const docs = (Array.isArray(listing?.files) ? listing.files : []).filter((f) => f?.previewable && isFoundationDoc(String(f.relativePath).split("/")));
398
+ return {
399
+ episode: { ref: number === null ? `chronicle:${folder}` : `miadi-chronicle://${number}`, number, folder },
400
+ foundations: docs.map((f) => ({ path: f.relativePath, bytes: f.size, modified_at: f.modifiedAt, read_ref: fileRef(number ?? folder, f.relativePath) })),
401
+ count: docs.length,
402
+ };
403
+ };
344
404
  const readEpisodeFile = async (person, a) => {
345
405
  const path = a.path.trim();
346
406
  const segments = checkRelativePath(path);
@@ -349,12 +409,18 @@ export function registerTools(server, ctx) {
349
409
  if (!contentType) {
350
410
  throw new FileRefusal("unsupported", `${path} is not read as text here: only ${Object.keys(TEXT_TYPES).join(", ")} files are. Miadi serves documents (html, pdf, csv, svg) as downloads and images and media by their own routes; get_evidence lists them.`);
351
411
  }
352
- const ref = episodeRef(a.episode);
353
- // A number, epNNN or a folder name; `..` would be folded by the URL parser into another route.
354
- if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(decodeURIComponent(ref))) {
355
- throw new FileRefusal("invalid_path", "Name the episode as miadi-chronicle://<n>, its number or its folder.");
412
+ const ref = checkedEpisodeRef(a.episode);
413
+ // Without a tier, a foundation document is all this tool reads, through
414
+ // Miadi's foundation door, which decides on every call whether the person
415
+ // holds the permission: a grant or a revocation counts from the next call,
416
+ // whatever the sign-in cached. A tier holder reads the room as before.
417
+ const foundation = person.api_tier === null && isFoundationDoc(segments);
418
+ if (!foundation && person.api_tier === null) {
419
+ throw new ToolRefusal("forbidden", `Without Miadi's read tier, this tool reads only foundation documents (foundations/<topic>/<file>.md), with the ${FOUNDATIONS_PERMISSION} permission.`);
356
420
  }
357
- const url = `/api/chronicle/episodes/${ref}/files/${segments.map(encodeURIComponent).join("/")}`;
421
+ const url = foundation
422
+ ? `/api/chronicle/episodes/${ref}/foundations/${segments.slice(1).map(encodeURIComponent).join("/")}`
423
+ : `/api/chronicle/episodes/${ref}/files/${segments.map(encodeURIComponent).join("/")}`;
358
424
  let text;
359
425
  try {
360
426
  text = await miadi.get(person.token, url);
@@ -368,7 +434,12 @@ export function registerTools(server, ctx) {
368
434
  throw new ToolRefusal("unavailable", "Miadi could not be reached.", detail);
369
435
  if (body.error === "not_in_the_room")
370
436
  throw new ToolRefusal("forbidden", body.message ?? "This file is not part of the episode room.", detail);
371
- if (error.status === 401 || error.status === 403)
437
+ if (error.status === 401)
438
+ throw new ToolRefusal("unauthenticated", "Miadi does not recognise this sign-in's token.", detail);
439
+ if (error.status === 403 && foundation) {
440
+ throw new ToolRefusal("forbidden", `Reading foundation documents needs the ${FOUNDATIONS_PERMISSION} permission; an admin gives it under /admin → People → Additional permissions.`, detail);
441
+ }
442
+ if (error.status === 403)
372
443
  throw new ToolRefusal("forbidden", body.message ?? "Miadi does not let this person read that file.", detail);
373
444
  if (error.status === 404)
374
445
  throw new ToolRefusal("not_found", body.message ?? `No file ${path} in ${decodeURIComponent(ref)}.`, detail);
@@ -408,7 +479,7 @@ export function registerTools(server, ctx) {
408
479
  };
409
480
  server.registerTool("read_episode_file", {
410
481
  title: "Read one file an episode holds",
411
- description: "The full UTF-8 text of one text file (md, txt, yaml, yml, json) in a Chronicle episode, read as you through Miadi, with its content type, byte count and sha256. Name the episode and the path get_evidence lists (or pass its read_ref as episode alone). A file longer than max_bytes comes in pages: pass page.next back to read the next one; joined, the pages hash to sha256. Read only: nothing is changed.",
482
+ description: "The full UTF-8 text of one text file (md, txt, yaml, yml, json) in a Chronicle episode, read as you through Miadi, with its content type, byte count and sha256. Name the episode and the path get_evidence lists (or pass its read_ref as episode alone); name the episode alone to list its foundation documents. With Miadi's read tier it reads the episode's text files; with the chronicle_foundations_read permission alone, foundations/<topic>/*.md only. A file longer than max_bytes comes in pages: pass page.next back to read the next one; joined, the pages hash to sha256. Read only: nothing is changed.",
412
483
  inputSchema: {
413
484
  episode: z.string().min(1).describe("miadi-chronicle://<n>, an episode number or its folder; or a read_ref (miadi-chronicle://<n>/<path>) with path left out."),
414
485
  path: z.string().optional().describe("The file's path inside the episode, as get_evidence lists it, e.g. foundations/broker-adapters/README.md."),
@@ -418,13 +489,22 @@ export function registerTools(server, ctx) {
418
489
  },
419
490
  annotations: READ,
420
491
  }, wrap(async (a, extra) => {
421
- const { person } = who(extra);
492
+ // No API tier needed: the permission alone admits foundation documents (jgwill/Miadi#905).
493
+ const { person } = who(extra, "none");
422
494
  let episode = a.episode;
423
495
  let path = a.path;
424
496
  if (!path) {
425
497
  const parsed = parseFileRef(a.episode);
426
- if (!parsed)
427
- throw new FileRefusal("invalid_path", "Name the file: give path, or pass a read_ref (miadi-chronicle://<n>/<path>) as episode.");
498
+ if (!parsed) {
499
+ // Miadi's answer now: the sign-in's copy may be five minutes old, and a
500
+ // person just given the permission would be refused the listing.
501
+ const now = person.api_tier === null ? await miadi.get(person.token, "/api/identity/me").catch(() => null) : null;
502
+ const permissions = Array.isArray(now?.permissions) ? now.permissions.map(String) : person.permissions;
503
+ if (person.api_tier === null && !holdsFoundationRead(permissions)) {
504
+ throw new ToolRefusal("forbidden", `Listing an episode's foundation documents needs Miadi's read tier or the ${FOUNDATIONS_PERMISSION} permission.`);
505
+ }
506
+ return foundationListing(person, a.episode);
507
+ }
428
508
  ({ episode, path } = parsed);
429
509
  }
430
510
  return readEpisodeFile(person, { ...a, episode, path });
@@ -569,7 +649,7 @@ export function registerTools(server, ctx) {
569
649
  }));
570
650
  server.registerTool("mission_status", {
571
651
  title: "Where a mission stands",
572
- description: "One mission, joined and verified: its source request and decision, the sessions bound to it (live or not), its issues and PRs, what is approved, queued, delivered, running, merged, released or closed — each by its evidence, never inferred from an idle pane — and the next authorized action. Operator details need Miadi's write tier. jgwill/Miadi#893.",
652
+ description: "One mission, joined and verified: its source request and decision, the sessions bound to it (live or not), its issues and PRs, what is approved, queued, delivered, running, merged, released or closed — each by its evidence, never inferred from an idle pane — and the next authorized action. An issue or session resolves only through the request that holds it as primary; several are ambiguous_mission, citations alone are related_only, and an inference authorizes nothing (identity.authorizes_actions). Released needs a release receipt with artifact, target, production check and a fresh time; closed needs an approved session ending that cites the mission. Operator details need Miadi's write tier. jgwill/Miadi#893, #913.",
573
653
  inputSchema: {
574
654
  ref: z
575
655
  .string()
@@ -611,10 +691,11 @@ export function registerTools(server, ctx) {
611
691
  }
612
692
  const res = await f(`https://api.github.com/repos/${owner}/${repo}/issues/${n}`, { headers, signal: AbortSignal.timeout(15_000) });
613
693
  if (!res.ok)
614
- return { state: res.status === 404 ? "not_found_or_private" : `github_${res.status}` };
694
+ return { state: "unverified", access: githubAccess(res.status, Boolean(process.env.GITHUB_TOKEN)) };
615
695
  const i = await res.json();
616
696
  return { state: i.state, title: i.title };
617
697
  },
698
+ liveVersionOf: (pkg) => (pkg === "@miadi/connector" ? VERSION : undefined),
618
699
  });
619
700
  }));
620
701
  server.registerTool("provider_capabilities", {
@@ -633,6 +714,63 @@ export function registerTools(server, ctx) {
633
714
  note: "A provider name in a task's text selects nothing; request_authorized_run's provider field is verified against this list, and an unavailable provider leaves the mission unstarted.",
634
715
  };
635
716
  }));
717
+ server.registerTool("mission_model_preflight", {
718
+ title: "Which provider and model fits a mission, and how that is known",
719
+ description: "A typed, versioned, read-only preflight for one mission (jgwill/Miadi#913): the candidate providers with their concrete model when the host shows one, sign-in evidence (config directory only), quota unknown, policy eligibility (the approved provider is kept; Claude Code only when named), a review floor, and a recommendation or an abstention with its reasons. It says where the choice came from: static_fallback, jev or a synthetic fixture. Jev is asked only with consult_jev and a Jev key in the connector's environment; that call is charged, and it carries no task text, mission id or repository name. A reported launch on another provider than the approved one fails closed as provider_mismatch. It authorizes nothing. Needs Miadi's write tier.",
720
+ inputSchema: {
721
+ ref: z.string().min(1).describe("The mission: run_request:<id> or work_proposal:<id> (an issue or session ref resolves only through its primary relation)."),
722
+ task_type: z.enum(TASK_TYPES),
723
+ criticality: z.enum(CRITICALITIES).default("normal"),
724
+ consult_jev: z.boolean().default(false).describe("Ask Jev to choose among the eligible options. Charged by Jev; off by default."),
725
+ launch: z
726
+ .object({
727
+ provider: z.enum(["codex", "pi", "copilot", "claude"]),
728
+ model: z.string().max(120).optional(),
729
+ evidence: z.string().min(1).max(500).describe("What shows it: a process line, a pane header."),
730
+ })
731
+ .optional()
732
+ .describe("What actually launched, as observed. A provider other than the approved one fails closed."),
733
+ },
734
+ annotations: READ,
735
+ }, wrap(async (a, extra) => {
736
+ const { person } = who(extra);
737
+ requireTier(person, "write");
738
+ const found = resolveMissionSubject(String(a.ref).trim(), records());
739
+ if (!found?.record) {
740
+ return {
741
+ contract: PREFLIGHT_CONTRACT,
742
+ version: PREFLIGHT_VERSION,
743
+ state: "mission_unresolved",
744
+ ref: a.ref,
745
+ identity: found ? { resolution: found.resolution, candidates: found.candidates } : { resolution: "not_found", candidates: [] },
746
+ note: "Name one mission by its record id; no model is chosen for an unidentified mission.",
747
+ };
748
+ }
749
+ const record = found.record;
750
+ const body = record.body;
751
+ const provider = (typeof body.resolved_provider === "string" ? body.resolved_provider : typeof body.provider === "string" ? body.provider : null);
752
+ const basis = record.approval_state === "approved" ? "approved" : "requested";
753
+ const candidates = candidatesFor(discoverProviders(ctx.providersProbe), provider, ctx.modelOf, basis);
754
+ const jevConfig = jevConfigOf(process.env);
755
+ const jev = a.consult_jev ? (ctx.jevAdapter ?? (jevConfig ? jevAdapter(jevConfig, ctx.approval?.fetchImpl ?? fetch) : undefined)) : undefined;
756
+ const configured = Boolean(jevConfig);
757
+ const primary = primaryIssueOf(record);
758
+ return modelPreflight({
759
+ mission: {
760
+ id: record.id,
761
+ resolution: found.resolution,
762
+ authorizes_actions: authorizesActions(found),
763
+ ...(primary ? { primary_issue: primary.ref } : {}),
764
+ },
765
+ approved_provider: provider,
766
+ provider_basis: basis,
767
+ task: a.task_type,
768
+ criticality: a.criticality,
769
+ candidates,
770
+ ...(a.launch ? { launch: a.launch } : {}),
771
+ ...(jev ? { adapter: jev } : { routerWhenAbsent: configured ? "not_requested" : "router_unconfigured" }),
772
+ });
773
+ }));
636
774
  // search / fetch: the pair ChatGPT uses for connectors in search and deep research.
637
775
  server.registerTool("search", {
638
776
  title: "Search Miadi",
@@ -919,6 +1057,7 @@ export function registerTools(server, ctx) {
919
1057
  desired_outcome: z.string().min(1).max(6000),
920
1058
  circle,
921
1059
  refs,
1060
+ primary_issue: primaryIssue,
922
1061
  repo: z.string().max(120).optional(),
923
1062
  ...base,
924
1063
  },
@@ -928,13 +1067,13 @@ export function registerTools(server, ctx) {
928
1067
  requireContribute(auth);
929
1068
  const r = append("work_proposal", person, auth, {
930
1069
  circle: a.circle,
931
- refs: a.refs,
1070
+ refs: withPrimary(a.refs, a.primary_issue),
932
1071
  intention: a.intention,
933
1072
  role: a.role,
934
1073
  idempotency_key: a.idempotency_key,
935
1074
  state: "pending",
936
1075
  approval_state: "awaiting_approval",
937
- body: { title: a.title, desired_outcome: a.desired_outcome, repo: a.repo },
1076
+ body: { title: a.title, desired_outcome: a.desired_outcome, repo: a.repo, ...(a.primary_issue ? { primary_issue: a.primary_issue } : {}) },
938
1077
  });
939
1078
  return { ...r, approval_url: approvalUrl(r.id) };
940
1079
  }));
@@ -971,6 +1110,11 @@ export function registerTools(server, ctx) {
971
1110
  session_id: z.string().min(8),
972
1111
  ending: z.enum(["exit", "complete_then_exit", "record_only"]),
973
1112
  reason: z.string().min(1).max(4000),
1113
+ mission: z
1114
+ .string()
1115
+ .regex(/^(run_request|work_proposal):[\w-]+$/)
1116
+ .optional()
1117
+ .describe("The mission this ending closes, by record id. Only an approved exit or complete_then_exit that names the mission here closes it in mission_status (jgwill/Miadi#913)."),
974
1118
  evidence_refs: refs,
975
1119
  },
976
1120
  annotations: WRITE,
@@ -980,11 +1124,11 @@ export function registerTools(server, ctx) {
980
1124
  const id = a.session_id.replace(/^session:/, "");
981
1125
  const r = append("session_completion_proposal", person, auth, {
982
1126
  circle: "continuity",
983
- refs: [`session:${id}`],
1127
+ refs: a.mission ? [`session:${id}`, a.mission] : [`session:${id}`],
984
1128
  evidence_refs: a.evidence_refs,
985
1129
  approval_state: "awaiting_approval",
986
- idempotency_key: `${id}:${a.ending}`,
987
- body: { session_id: id, ending: a.ending, reason: a.reason },
1130
+ idempotency_key: a.mission ? `${id}:${a.ending}:${a.mission}` : `${id}:${a.ending}`,
1131
+ body: { session_id: id, ending: a.ending, reason: a.reason, ...(a.mission ? { mission: a.mission } : {}) },
988
1132
  });
989
1133
  return { ...r, approval_url: approvalUrl(r.id) };
990
1134
  }));
@@ -996,6 +1140,7 @@ export function registerTools(server, ctx) {
996
1140
  task: z.string().min(1).max(12000).describe("What the session does and what it returns."),
997
1141
  circle,
998
1142
  refs,
1143
+ primary_issue: primaryIssue,
999
1144
  session: z.string().regex(/^[A-Za-z0-9._-]{3,80}$/).optional().describe("An existing tmux session to hand it to, or a name for a new one."),
1000
1145
  cwd: z.string().max(300).optional().describe("Working folder for a new session (default the Miadi checkout)."),
1001
1146
  provider: z
@@ -1025,7 +1170,7 @@ export function registerTools(server, ctx) {
1025
1170
  }
1026
1171
  const r = append("run_request", person, auth, {
1027
1172
  circle: a.circle,
1028
- refs: a.refs,
1173
+ refs: withPrimary(a.refs, a.primary_issue),
1029
1174
  intention: a.intention,
1030
1175
  role: a.role,
1031
1176
  idempotency_key: a.idempotency_key,
@@ -1037,6 +1182,7 @@ export function registerTools(server, ctx) {
1037
1182
  cwd: a.cwd,
1038
1183
  provider: a.provider,
1039
1184
  resolved_provider: resolution.provider.id,
1185
+ ...(a.primary_issue ? { primary_issue: a.primary_issue } : {}),
1040
1186
  ...(a.steering ? { steering: a.steering } : {}),
1041
1187
  },
1042
1188
  });
@@ -1548,7 +1694,7 @@ export const TOOL_STATUS = {
1548
1694
  list_sessions: "live, Miadi write tier",
1549
1695
  get_session: "live, Miadi write tier",
1550
1696
  get_evidence: "live",
1551
- read_episode_file: "live (text files an episode holds: md, txt, yaml, yml, json, up to 512 KiB, paged; read as you, nothing changed)",
1697
+ read_episode_file: "live (Miadi read tier: an episode's text files; chronicle_foundations_read alone: foundations/<topic>/*.md; up to 512 KiB, paged; read as you, nothing changed)",
1552
1698
  get_evaluations: "live (tracing records and recorded assessments; no provider scores)",
1553
1699
  get_narrative_beats: "live (NCP project beats and candidate beats)",
1554
1700
  review_lookup: "live (video search needs the review token on the connector host)",
@@ -1557,6 +1703,7 @@ export const TOOL_STATUS = {
1557
1703
  list_records: "live",
1558
1704
  get_run_result: "live",
1559
1705
  mission_status: "live (operator details need the Miadi write tier)",
1706
+ mission_model_preflight: "live, Miadi write tier (static policy; Jev only with consult_jev and a Jev key on the host, otherwise router_unconfigured)",
1560
1707
  provider_capabilities: "live, Miadi write tier",
1561
1708
  search: "live",
1562
1709
  fetch: "live",
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.1.11";
1
+ export declare const VERSION = "0.1.13";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.1.11";
1
+ export const VERSION = "0.1.13";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miadi/connector",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "The Miadi connector: one remote MCP service through which a person signed in with their Miadi person token, from ChatGPT or any MCP client, reads sessions, reviews, episodes and circles and records observations, inquiries, handoffs and proposals with their name on each record.",
5
5
  "license": "MIT",
6
6
  "author": "Miadi",