@miadi/connector 0.1.24 → 0.1.25
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 +2 -0
- package/dist/approve.js +19 -10
- package/dist/decisions.d.ts +6 -0
- package/dist/decisions.js +6 -0
- package/dist/server.js +7 -1
- package/dist/tools.js +13 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,6 +126,8 @@ The drafter gets two rounds: a report and a revision, twice. The third report's
|
|
|
126
126
|
|
|
127
127
|
In a client that renders MCP Apps (ChatGPT among them), `show_decisions` draws one card per request awaiting a decision: what is asked, by whom, and what approving does. The person decides on the card with their own click: Approve, Approve with my changes, Send back for revision, or Decline. The card then shows the receipt.
|
|
128
128
|
|
|
129
|
+
A new card address needs a new URI, and the earlier ones stay readable (`DECISIONS_CARD_EARLIER_URIS`). A host keeps the tool metadata it read until its connection is refreshed by hand, and draws an empty card for a template it cannot read. The journal holds one line for each `show_decisions` call and each template read (`card_template_read`), so a blank card can be told from a card nobody asked for.
|
|
130
|
+
|
|
129
131
|
A decision is recorded once. An approved run that calls another service (a review operation, Deep Diver, an episode door, a steer) is written to the ledger as approved and running before the call, so a restart can never offer the request for a second approval; a run the connector was recording when it stopped reads as failed and may be run again. A decision whose run takes longer than `MIADI_CONNECTOR_DECISION_WAIT_MS` is answered as recorded and running, and the card reads `get_run_result` every 3 seconds until the outcome is in. The same decision sent again (a host retrying a call, a second click, the page form sent twice) answers with the first one's receipt, marked `replayed`, and runs nothing. A different decision is refused as `decision_conflict`, with where the request stands. When the host gives up on a call, the card reads where the request stands before it offers the buttons again.
|
|
130
132
|
|
|
131
133
|
**A created review gets its transcript (jgwill/Miadi#925).** An approved `create` calls the review service, then reads the video's original-language YouTube captions with a local `uvx yt-dlp` and stores them on the review as `[mm:ss] text`, so an agent reads them through `fetch` and the factory's kind briefs find them with `miadi_review.py transcript`. No model is called for the transcript. The service answers an empty `POST /api/reviews/:id/transcript` by asking Gemini, so an empty one is never sent. `completion_receipt.transcript` says where it stands, and the card and the approval page say the same:
|
package/dist/approve.js
CHANGED
|
@@ -403,7 +403,7 @@ blockquote{margin:.2rem 0 .6rem;padding-left:.8rem;border-left:2px solid var(--l
|
|
|
403
403
|
@media (prefers-color-scheme:dark){.err{color:#ff8a80}}
|
|
404
404
|
</style></head><body><main>${body}</main></body></html>`;
|
|
405
405
|
}
|
|
406
|
-
function summary(r) {
|
|
406
|
+
function summary(r, o = {}) {
|
|
407
407
|
const shown = { ...r.body };
|
|
408
408
|
for (const k of ["markdown", "transcript"])
|
|
409
409
|
if (typeof shown[k] === "string")
|
|
@@ -414,7 +414,7 @@ function summary(r) {
|
|
|
414
414
|
<dt>By</dt><dd>${escapeHtml(r.actor.name)}${r.actor.client_name ? ` through ${escapeHtml(r.actor.client_name)}` : ""}</dd>
|
|
415
415
|
<dt>When</dt><dd>${escapeHtml(r.created_at)}</dd>
|
|
416
416
|
${r.circle ? `<dt>Circle</dt><dd>${escapeHtml(r.circle)}</dd>` : ""}
|
|
417
|
-
${r.intention ? `<dt>Intention</dt><dd>${escapeHtml(r.intention)}</dd>` : ""}
|
|
417
|
+
${r.intention && o.intention !== false ? `<dt>Intention</dt><dd>${escapeHtml(r.intention)}</dd>` : ""}
|
|
418
418
|
${r.refs.length ? `<dt>References</dt><dd>${r.refs.map(escapeHtml).join("<br>")}</dd>` : ""}
|
|
419
419
|
<dt>State</dt><dd>${escapeHtml(r.approval_state ?? "—")}</dd>
|
|
420
420
|
${versionOf(r) > 1 ? `<dt>Version</dt><dd>${versionOf(r)}, the one a decision here decides. Earlier versions stay in the ledger.</dd>` : ""}
|
|
@@ -984,14 +984,14 @@ function signInToRead(r, error) {
|
|
|
984
984
|
// The label names no one and no instruction: this page is shown before sign-in.
|
|
985
985
|
const open = r.approval_state === "awaiting_approval";
|
|
986
986
|
const label = projectRequest(r, { recording: isBeingDecided(r.id) })?.label ?? r.approval_state ?? "";
|
|
987
|
-
return page(open ? "A Miadi request awaits a decision" : "A Miadi request was decided", `<h1>${open ? "
|
|
987
|
+
return page(open ? "A Miadi request awaits a decision" : "A Miadi request was decided", `<h1>${open ? "Sign in to read this request" : "A request was decided"}</h1>
|
|
988
988
|
<p>${escapeHtml(KIND_LABELS[r.kind] ?? r.kind)}, asked ${escapeHtml(r.created_at.slice(0, 16).replace("T", " "))} UTC. ${escapeHtml(label)}.</p>
|
|
989
|
-
<p>${open ? "
|
|
989
|
+
<p>${open ? "Signing in approves nothing. The next page shows the request, and a person with Miadi's write tier decides there. Whoever asked can read it." : "Sign in to read what was decided and what came of it. Whoever asked, and anyone with Miadi's write tier, can read it."}</p>
|
|
990
990
|
${error ? `<p class="err" role="alert">${escapeHtml(error)}</p>` : ""}
|
|
991
991
|
<form method="post">
|
|
992
992
|
<label for="t">Your Miadi person token</label>
|
|
993
993
|
<input id="t" name="person_token" type="password" autocomplete="off" required>
|
|
994
|
-
<div class="row"><button class="primary" type="submit">
|
|
994
|
+
<div class="row"><button class="primary" type="submit">Sign in and read the request</button></div>
|
|
995
995
|
</form>`);
|
|
996
996
|
}
|
|
997
997
|
/** The request, and the decision form for a write-tier person (their token carried in the form they just signed into). */
|
|
@@ -1081,12 +1081,20 @@ function approvePage(deps, r, o = {}) {
|
|
|
1081
1081
|
// Freshness binds a decision still to come; a decided run shows the authorization it holds (#926 review R1).
|
|
1082
1082
|
const authorization = r.kind === "run_request" ? authorizeProvider(r, deps.ledger.all(), { requireFresh: open }) : undefined;
|
|
1083
1083
|
const blocked = Boolean(authorization && !authorization.authorized);
|
|
1084
|
-
|
|
1084
|
+
// Signed in and able to decide: the decision comes before the record's details, and no line says it waits for someone else.
|
|
1085
|
+
const canDecideHere = Boolean(actionable && o.token);
|
|
1086
|
+
const heading = canDecideHere ? "Not approved yet: decide here" : open ? "A request awaiting a decision" : (projectionOf(r, deps)?.label ?? "A decided request");
|
|
1087
|
+
const details = canDecideHere
|
|
1088
|
+
? `<details><summary>What exactly is asked</summary>${summary(r, { intention: false })}</details>`
|
|
1089
|
+
: `${summary(r)}
|
|
1090
|
+
${open ? draftReviewSection(r, deps.draftReview?.stored(r), false) : ""}
|
|
1091
|
+
${projectionBlock(projectionOf(r, deps), open ? undefined : r.completion_receipt)}`;
|
|
1092
|
+
return page("Approve a Miadi request", `<h1>${escapeHtml(heading)}</h1>
|
|
1093
|
+
${canDecideHere ? `<p>You are signed in. Nothing is approved until you press a button below.</p>` : ""}
|
|
1085
1094
|
${actionable ? `<p>${escapeHtml(consequenceOf(r, authorization, current(deps.ledger.all())))} Declining records your refusal. Either is kept in the ledger with your name.</p>` : ""}
|
|
1095
|
+
${canDecideHere && r.intention ? `<p><strong>Intention:</strong> ${escapeHtml(r.intention)}</p>` : ""}
|
|
1086
1096
|
${authorization ? providerSection(r, authorization, actionable ? o.chooserToken : undefined) : ""}
|
|
1087
|
-
${
|
|
1088
|
-
${open ? draftReviewSection(r, deps.draftReview?.stored(r), Boolean(actionable && o.token && !blocked)) : ""}
|
|
1089
|
-
${projectionBlock(projectionOf(r, deps), open ? undefined : r.completion_receipt)}
|
|
1097
|
+
${canDecideHere ? draftReviewSection(r, deps.draftReview?.stored(r), !blocked) : ""}
|
|
1090
1098
|
${o.error ? `<p class="err" role="alert">${escapeHtml(o.error)}</p>` : ""}
|
|
1091
1099
|
${actionable && o.token
|
|
1092
1100
|
? `<form method="post">
|
|
@@ -1111,7 +1119,8 @@ ${blocked
|
|
|
1111
1119
|
<form method="post">
|
|
1112
1120
|
<input type="hidden" name="person_token" value="${escapeHtml(o.token)}">
|
|
1113
1121
|
<div class="row"><button class="primary" type="submit" name="decision" value="rerun">Run it again</button></div></form>`
|
|
1114
|
-
: ""}
|
|
1122
|
+
: ""}
|
|
1123
|
+
${details}`);
|
|
1115
1124
|
}
|
|
1116
1125
|
/**
|
|
1117
1126
|
* Which provider a run launches with, who chose it, and, when the requester's own choice is
|
package/dist/decisions.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export declare const DECISIONS_CARD_URI = "ui://miadi/decisions-v2.html";
|
|
2
|
+
/**
|
|
3
|
+
* Addresses the card had before. Every one stays readable, because a host keeps the tool metadata it read
|
|
4
|
+
* until the connection is refreshed by hand, and it renders nothing for a template it cannot read: removing
|
|
5
|
+
* v1 in 0.1.22 left ChatGPT drawing an empty card (jgwill/Miadi#939).
|
|
6
|
+
*/
|
|
7
|
+
export declare const DECISIONS_CARD_EARLIER_URIS: string[];
|
|
2
8
|
/** The default lifetime of a card's decision token, in seconds; MIADI_CONNECTOR_DECISION_TOKEN_TTL changes it. */
|
|
3
9
|
export declare const DECISION_TOKEN_TTL: number;
|
|
4
10
|
/** Where the tokens travel in a result's `_meta`, keyed by record id. */
|
package/dist/decisions.js
CHANGED
|
@@ -25,6 +25,12 @@ import { join } from "node:path";
|
|
|
25
25
|
import { newSecret } from "./store.js";
|
|
26
26
|
import { DEFAULT_DECISION_TOKEN_TTL } from "./config.js";
|
|
27
27
|
export const DECISIONS_CARD_URI = "ui://miadi/decisions-v2.html";
|
|
28
|
+
/**
|
|
29
|
+
* Addresses the card had before. Every one stays readable, because a host keeps the tool metadata it read
|
|
30
|
+
* until the connection is refreshed by hand, and it renders nothing for a template it cannot read: removing
|
|
31
|
+
* v1 in 0.1.22 left ChatGPT drawing an empty card (jgwill/Miadi#939).
|
|
32
|
+
*/
|
|
33
|
+
export const DECISIONS_CARD_EARLIER_URIS = ["ui://miadi/decisions-v1.html"];
|
|
28
34
|
/** The default lifetime of a card's decision token, in seconds; MIADI_CONNECTOR_DECISION_TOKEN_TTL changes it. */
|
|
29
35
|
export const DECISION_TOKEN_TTL = DEFAULT_DECISION_TOKEN_TTL;
|
|
30
36
|
/** Where the tokens travel in a result's `_meta`, keyed by record id. */
|
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
5
5
|
import { mcpAuthRouter, createOAuthMetadata, getOAuthProtectedResourceMetadataUrl } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
6
6
|
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
|
|
7
7
|
import { MiadiOAuthProvider, SCOPES, SCOPE_CONTRIBUTE, SCOPE_READ, emptyOAuthState } from "./oauth.js";
|
|
8
|
-
import { reconcileInterrupted, registerApprovalRoutes, registerProviderPolicyRoutes, startTranscriptSettling } from "./approve.js";
|
|
8
|
+
import { logDecision, reconcileInterrupted, registerApprovalRoutes, registerProviderPolicyRoutes, startTranscriptSettling } from "./approve.js";
|
|
9
9
|
import { DecisionTokens } from "./decisions.js";
|
|
10
10
|
import { JsonFile, Ledger, ensureStateDir, statePaths } from "./store.js";
|
|
11
11
|
import { registerTools } from "./tools.js";
|
|
@@ -107,6 +107,12 @@ export function createConnectorApp(config, deps) {
|
|
|
107
107
|
bearer(req, res, next);
|
|
108
108
|
};
|
|
109
109
|
const handle = async (req, res) => {
|
|
110
|
+
// What a host asks for to draw the card, for the journal: a blank card is otherwise invisible from here (jgwill/Miadi#939).
|
|
111
|
+
const rpc = (req.body ?? {});
|
|
112
|
+
if (rpc.method === "resources/read")
|
|
113
|
+
logDecision(approvalDeps, { phase: "card_template_read", uri: String(rpc.params?.uri ?? "").slice(0, 120) });
|
|
114
|
+
if (rpc.method === "tools/call" && rpc.params?.name === "show_decisions")
|
|
115
|
+
logDecision(approvalDeps, { phase: "show_decisions" });
|
|
110
116
|
const ctx = {
|
|
111
117
|
config,
|
|
112
118
|
miadi,
|
package/dist/tools.js
CHANGED
|
@@ -18,7 +18,7 @@ import { CIRCLES, CIRCLE_IDS, CLAIM_CATEGORIES, DOMAINS, HANDOFF_STATES, current
|
|
|
18
18
|
import { VERSION } from "./version.js";
|
|
19
19
|
import { AlreadyDecided, DECISIONS, DecisionConflict, KIND_LABELS, ProviderChoiceRequired, STILL_RUNNING, StaleVersion, approvalStateOf, consequenceOf, decide, decisionWaitOf, isApprovable, isBeingDecided, logDecision, readQueueState, requestProjection, settleWithin, } from "./approve.js";
|
|
20
20
|
import { RevisionRefused, requestHistory, revisionRecord, supersedeRecords, versionOf } from "./revisions.js";
|
|
21
|
-
import { DECISIONS_CARD_HTML, DECISIONS_CARD_URI, DECISION_TOKENS_META } from "./decisions.js";
|
|
21
|
+
import { DECISIONS_CARD_EARLIER_URIS, DECISIONS_CARD_HTML, DECISIONS_CARD_URI, DECISION_TOKENS_META } from "./decisions.js";
|
|
22
22
|
import { discoverProviders, jevConfigOf, resolveProvider, routerPreflight, PROVIDERS } from "./providers.js";
|
|
23
23
|
import { authorizesActions, missionStatus, primaryIssueOf, resolveMissionSubject } from "./mission-status.js";
|
|
24
24
|
import { authorizationReceipt, authorizeProvider, CHOICE_PROVIDERS, isPersonId, needsPersonChoice, providerPolicyOf } from "./provider-policy.js";
|
|
@@ -2256,16 +2256,18 @@ export function registerTools(server, ctx) {
|
|
|
2256
2256
|
shown[k] = `${shown[k].length} chars`;
|
|
2257
2257
|
return JSON.stringify({ ...shown, refs: r.refs.length ? r.refs : undefined }, null, 2);
|
|
2258
2258
|
};
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2259
|
+
for (const uri of [DECISIONS_CARD_URI, ...DECISIONS_CARD_EARLIER_URIS]) {
|
|
2260
|
+
server.registerResource(uri === DECISIONS_CARD_URI ? "miadi-decisions-card" : `miadi-decisions-card-${uri.replace(/\W+/g, "-")}`, uri, { title: "Miadi decisions", description: "Requests awaiting a person's decision, decided by the person's own click.", mimeType: "text/html;profile=mcp-app" }, async () => ({
|
|
2261
|
+
contents: [
|
|
2262
|
+
{
|
|
2263
|
+
uri,
|
|
2264
|
+
mimeType: "text/html;profile=mcp-app",
|
|
2265
|
+
text: DECISIONS_CARD_HTML,
|
|
2266
|
+
_meta: { ui: { prefersBorder: true, csp: { connectDomains: [], resourceDomains: [] } }, "openai/widgetDescription": "Cards for the requests awaiting the person's decision." },
|
|
2267
|
+
},
|
|
2268
|
+
],
|
|
2269
|
+
}));
|
|
2270
|
+
}
|
|
2269
2271
|
server.registerTool("show_decisions", {
|
|
2270
2272
|
title: "Show the requests awaiting a decision",
|
|
2271
2273
|
description: "Show the requests that await a person's decision (runs, work proposals, review operations, Deep Diver, episode links, session endings) as cards in the conversation. The person approves, approves with changes, sends back or declines each one with their own click; you cannot decide for them. Call it after making a request, or when the person asks what waits for them. Optionally pass record_id to show one request.",
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.25";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.1.
|
|
1
|
+
export const VERSION = "0.1.25";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miadi/connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
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",
|