@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/README.md +7 -2
- package/dist/episode-files.d.ts +9 -0
- package/dist/episode-files.js +13 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/mission-status.d.ts +121 -15
- package/dist/mission-status.js +198 -27
- package/dist/model-preflight.d.ts +144 -0
- package/dist/model-preflight.js +323 -0
- package/dist/providers.d.ts +29 -3
- package/dist/providers.js +32 -11
- package/dist/server.d.ts +5 -1
- package/dist/server.js +2 -0
- package/dist/tools.d.ts +12 -1
- package/dist/tools.js +171 -24
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
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
|
-
|
|
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:
|
|
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 =
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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.
|
|
1
|
+
export declare const VERSION = "0.1.13";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.1.
|
|
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.
|
|
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",
|