@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 +5 -1
- package/dist/approve.js +24 -4
- package/dist/bin.js +12 -1
- package/dist/factory-receipt.d.ts +28 -0
- package/dist/factory-receipt.js +69 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +5 -3
- package/dist/mission-result.d.ts +142 -0
- package/dist/mission-result.js +273 -0
- package/dist/mission-status.d.ts +92 -12
- package/dist/mission-status.js +214 -13
- package/dist/steer.d.ts +34 -2
- package/dist/steer.js +111 -15
- package/dist/tools.d.ts +9 -0
- package/dist/tools.js +176 -19
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -23,6 +23,9 @@ import { discoverProviders, jevConfigOf, resolveProvider, routerPreflight, PROVI
|
|
|
23
23
|
import { authorizesActions, missionStatus, primaryIssueOf, resolveMissionSubject } from "./mission-status.js";
|
|
24
24
|
import { candidatesFor, CRITICALITIES, jevAdapter, modelPreflight, PREFLIGHT_CONTRACT, PREFLIGHT_VERSION, TASK_TYPES, } from "./model-preflight.js";
|
|
25
25
|
import { checkGrant, isBusy, steerAttempts, steeringGrantOf, steerSession } from "./steer.js";
|
|
26
|
+
import { factoryLedgerPath, readFactoryReceipts, receiptsForRecord, recordsForSession } from "./factory-receipt.js";
|
|
27
|
+
import { missionResult, redactResult } from "./mission-result.js";
|
|
28
|
+
import { factoryDir as defaultFactoryDir } from "./queue-state.js";
|
|
26
29
|
import { join } from "node:path";
|
|
27
30
|
import { artefactVisibility, readArtefactWeave, saveResearchArtefact } from "@miadi/inquiry-weave";
|
|
28
31
|
import { ResearchUploads, UploadRefusal } from "./uploads.js";
|
|
@@ -109,6 +112,46 @@ export function githubAccess(status, hasToken) {
|
|
|
109
112
|
return "forbidden_or_rate_limited";
|
|
110
113
|
return `github_${status}`;
|
|
111
114
|
}
|
|
115
|
+
/** GitHub answers kept briefly, so one status read does not spend the unauthenticated rate limit twice. */
|
|
116
|
+
const githubCache = new Map();
|
|
117
|
+
const GITHUB_CACHE_MS = 5 * 60_000;
|
|
118
|
+
/** A GitHub REST read as the connector can make it: GITHUB_TOKEN when the host has one, else anonymous. */
|
|
119
|
+
export function githubGetter(fetchImpl = fetch, token = process.env.GITHUB_TOKEN) {
|
|
120
|
+
return async (path) => {
|
|
121
|
+
// Keyed by whether a token was sent: an anonymous 404 is not a token's answer (#922 R9).
|
|
122
|
+
const key = `${token ? "token" : "anonymous"}:${path}`;
|
|
123
|
+
const hit = githubCache.get(key);
|
|
124
|
+
if (hit && Date.now() - hit.at < GITHUB_CACHE_MS)
|
|
125
|
+
return hit.answer;
|
|
126
|
+
const headers = { accept: "application/vnd.github+json", ...(token ? { authorization: "Bearer " + token } : {}) };
|
|
127
|
+
let answer;
|
|
128
|
+
try {
|
|
129
|
+
const res = await fetchImpl(`https://api.github.com${path}`, { headers, signal: AbortSignal.timeout(15_000) });
|
|
130
|
+
answer = res.ok ? { ok: true, json: await res.json() } : { ok: false, status: res.status, access: githubAccess(res.status, Boolean(token)) };
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
answer = { ok: false, status: 0, access: "unreachable" };
|
|
134
|
+
}
|
|
135
|
+
// Rate limits and outages are not kept: the next read asks again.
|
|
136
|
+
if (answer.ok || answer.status === 404) {
|
|
137
|
+
if (githubCache.size >= 500)
|
|
138
|
+
for (const [k, v] of githubCache)
|
|
139
|
+
if (Date.now() - v.at >= GITHUB_CACHE_MS)
|
|
140
|
+
githubCache.delete(k);
|
|
141
|
+
if (githubCache.size < 1000)
|
|
142
|
+
githubCache.set(key, { at: Date.now(), answer });
|
|
143
|
+
}
|
|
144
|
+
return answer;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** Whether the steering transport works now: registered is not operational (jgwill/Miadi#909). */
|
|
148
|
+
export function steeringTransport(tmux) {
|
|
149
|
+
if (!tmux)
|
|
150
|
+
return { tool: "registered", transport: "unavailable", reason: "The connector host was started without a steering surface." };
|
|
151
|
+
if (tmux.available && !tmux.available())
|
|
152
|
+
return { tool: "registered", transport: "unavailable", reason: "The host's tmux server does not answer." };
|
|
153
|
+
return { tool: "registered", transport: "operational" };
|
|
154
|
+
}
|
|
112
155
|
function episodeRef(input) {
|
|
113
156
|
// miadi-chronicle://316, miadi-chronicle:316, chronicle:<folder>, a folder, or a number
|
|
114
157
|
return encodeURIComponent(input.replace(/^miadi-chronicle:(\/\/)?/, "").replace(/^chronicle:/, "").trim());
|
|
@@ -137,15 +180,19 @@ export function registerTools(server, ctx) {
|
|
|
137
180
|
if (!viewer || viewer.api_tier === "write" || r.actor.person_id === viewer.id)
|
|
138
181
|
return r;
|
|
139
182
|
const body = { ...r.body };
|
|
140
|
-
|
|
183
|
+
// A steer's binding, pane, target and artifact path name the seat as the session does (#922 R2).
|
|
184
|
+
for (const k of ["session", "cwd", "session_id", "binding", "pane"])
|
|
141
185
|
delete body[k];
|
|
142
186
|
for (const k of ["markdown", "transcript"])
|
|
143
187
|
if (typeof body[k] === "string")
|
|
144
188
|
body[k] = `${body[k].length} chars`;
|
|
145
189
|
const receipt = r.completion_receipt ? { ...r.completion_receipt } : undefined;
|
|
146
190
|
if (receipt)
|
|
147
|
-
for (const k of ["to", "file", "session_id"])
|
|
191
|
+
for (const k of ["to", "file", "session_id", "target", "artifact"])
|
|
148
192
|
delete receipt[k];
|
|
193
|
+
// A steer's message names its target pane.
|
|
194
|
+
if (receipt && r.kind === "session_steer_request")
|
|
195
|
+
delete receipt.message;
|
|
149
196
|
return { ...r, body, ...(receipt ? { completion_receipt: receipt } : {}) };
|
|
150
197
|
};
|
|
151
198
|
const actorOf = (person, auth) => {
|
|
@@ -178,6 +225,9 @@ export function registerTools(server, ctx) {
|
|
|
178
225
|
const records = () => all().map(redact);
|
|
179
226
|
const uploads = new ResearchUploads(join(config.stateDir, "uploads"));
|
|
180
227
|
const touching = (ref) => records().filter((r) => r.refs.includes(ref) || r.evidence_refs.includes(ref) || r.id === ref);
|
|
228
|
+
/** The factory runner's delivery receipts, read fresh: operator data, for the write tier only. */
|
|
229
|
+
const receipts = () => readFactoryReceipts(factoryLedgerPath(ctx.factoryDir ?? defaultFactoryDir()));
|
|
230
|
+
const github = githubGetter(ctx.approval?.fetchImpl ?? fetch);
|
|
181
231
|
// ── reads ──────────────────────────────────────────────────────────────
|
|
182
232
|
server.registerTool("get_capabilities", {
|
|
183
233
|
title: "What this connector can do for you",
|
|
@@ -207,6 +257,8 @@ export function registerTools(server, ctx) {
|
|
|
207
257
|
ledger: "connector ledger (append-only, /srv/miadi/connector)",
|
|
208
258
|
},
|
|
209
259
|
tools: TOOL_STATUS,
|
|
260
|
+
// Registered is not operational: whether a steer can reach a pane on this host now (jgwill/Miadi#909).
|
|
261
|
+
steering: steeringTransport(ctx.tmux),
|
|
210
262
|
foundation_read: {
|
|
211
263
|
permission: FOUNDATIONS_PERMISSION,
|
|
212
264
|
held: foundations,
|
|
@@ -354,7 +406,13 @@ export function registerTools(server, ctx) {
|
|
|
354
406
|
requireTier(person, "write");
|
|
355
407
|
const id = session_id.replace(/^session:/, "");
|
|
356
408
|
const session = await miadi.get(person.token, `/api/session/${encodeURIComponent(id)}`);
|
|
357
|
-
|
|
409
|
+
// The missions the factory delivered to this session, by the runner's receipt (jgwill/Miadi#909).
|
|
410
|
+
const all = receipts();
|
|
411
|
+
const missions = recordsForSession(all, id).map((record) => {
|
|
412
|
+
const rc = all.filter((x) => x.record === record && x.session_id === id).sort((a, b) => b.at.localeCompare(a.at))[0];
|
|
413
|
+
return { record, binding: "factory_receipt", delivered_at: rc?.at, tmux_session: rc?.tmux_session, pane_id: rc?.pane_id, read: `mission_status with ${record}` };
|
|
414
|
+
});
|
|
415
|
+
return { ...session, missions, connector_records: touching(`session:${id}`) };
|
|
358
416
|
}));
|
|
359
417
|
server.registerTool("get_evidence", {
|
|
360
418
|
title: "What an episode holds as evidence",
|
|
@@ -629,14 +687,23 @@ export function registerTools(server, ctx) {
|
|
|
629
687
|
}));
|
|
630
688
|
server.registerTool("get_run_result", {
|
|
631
689
|
title: "Where a requested run stands",
|
|
632
|
-
description: "A request's decision (approved, approved with changes, sent back for revision, declined), the approver's instructions, the brief queued for the factory intake once approved, whether the intake delivered it, and its
|
|
690
|
+
description: "A request's decision (approved, approved with changes, sent back for revision, declined), the approver's instructions, the brief queued for the factory intake once approved, whether the intake delivered it, its receipt, and for a run or work proposal the result its agent submitted (summary, outputs with their checks, tests, blockers, the pending or decided completion). mission_status joins the live session and every stage.",
|
|
633
691
|
inputSchema: { record_id: z.string().min(1) },
|
|
634
692
|
annotations: READ,
|
|
635
693
|
}, wrap(async ({ record_id }, extra) => {
|
|
636
|
-
who(extra);
|
|
694
|
+
const { person } = who(extra);
|
|
637
695
|
const r = records().find((x) => x.id === record_id);
|
|
638
696
|
if (!r)
|
|
639
697
|
return { state: "not_found", record_id };
|
|
698
|
+
const isMission = r.kind === "run_request" || r.kind === "work_proposal";
|
|
699
|
+
// The sessions a result must come from: the runner's receipts, for every tier, else the record's session refs (#922 R5).
|
|
700
|
+
const bound = new Set(receiptsForRecord(receipts(), r.id).flatMap((x) => (x.session_id ? [x.session_id] : [])));
|
|
701
|
+
if (!bound.size)
|
|
702
|
+
for (const ref of r.refs)
|
|
703
|
+
if (/^session:[0-9a-f-]{36}$/.test(ref))
|
|
704
|
+
bound.add(ref.slice(8));
|
|
705
|
+
const checked = isMission ? await missionResult(r, all(), bound, { githubGet: github, approvalUrl }) : undefined;
|
|
706
|
+
const result = checked && person.api_tier !== "write" ? redactResult(checked) : checked;
|
|
640
707
|
return {
|
|
641
708
|
record: r,
|
|
642
709
|
version: versionOf(r),
|
|
@@ -648,11 +715,13 @@ export function registerTools(server, ctx) {
|
|
|
648
715
|
...(r.approval_state === "revision_requested"
|
|
649
716
|
? { revision: { instructions: r.completion_receipt?.instructions, next: `Revise it in place: revise_pending_request with request_id ${r.id} and expected_version ${versionOf(r)}.` } }
|
|
650
717
|
: {}),
|
|
718
|
+
// What the agent returned (jgwill/Miadi#921). The projection above is the request; this is its result.
|
|
719
|
+
...(result ? { result, mission_status: `mission_status with ${r.id} joins the live session, the stages and the human gates.` } : {}),
|
|
651
720
|
};
|
|
652
721
|
}));
|
|
653
722
|
server.registerTool("mission_status", {
|
|
654
723
|
title: "Where a mission stands",
|
|
655
|
-
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.",
|
|
724
|
+
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. A session the factory delivered a mission to resolves through the runner's receipt (factory_bound), cross-checked with Miadi's session registry (binding verified, receipt_only or unverified_association). result returns what the agent submitted through propose_session_completion naming the mission: summary, outputs with their checks, tests, blockers and the completion decision; mission_stage and pending_human_gates say where it stands and what waits on a person. Operator details need Miadi's write tier. jgwill/Miadi#893, #913, #909, #921.",
|
|
656
725
|
inputSchema: {
|
|
657
726
|
ref: z
|
|
658
727
|
.string()
|
|
@@ -666,11 +735,13 @@ export function registerTools(server, ctx) {
|
|
|
666
735
|
return missionStatus(ref.trim(), records(), {
|
|
667
736
|
projectOf: (r) => requestProjection(r, { reviewBase: ctx.approval?.config.reviewUrl, factoryDir: ctx.factoryDir }),
|
|
668
737
|
queueOf: (r) => (r.kind === "run_request" || r.kind === "work_proposal" ? readQueueState(r, ctx.factoryDir) : undefined),
|
|
669
|
-
|
|
738
|
+
// Without a lister the host cannot say which sessions live: unknown, never "absent" (jgwill/Miadi#909).
|
|
739
|
+
tmuxSessions: writeTier ? (ctx.tmuxSessions ?? (ctx.tmux?.listSessions ? () => ctx.tmux.listSessions() : undefined)) : undefined,
|
|
670
740
|
paneBusy: writeTier && ctx.tmux
|
|
671
|
-
? (session) => {
|
|
741
|
+
? (session, paneId) => {
|
|
672
742
|
try {
|
|
673
|
-
const
|
|
743
|
+
const panes = ctx.tmux.listPanes(session);
|
|
744
|
+
const pane = paneId ? panes.find((p) => p.id === paneId) : panes[0];
|
|
674
745
|
if (!pane)
|
|
675
746
|
return "unknown";
|
|
676
747
|
return isBusy(ctx.tmux.capture(`${session}:${pane.window}.${pane.pane}`)) ? "busy" : "idle";
|
|
@@ -680,6 +751,24 @@ export function registerTools(server, ctx) {
|
|
|
680
751
|
}
|
|
681
752
|
}
|
|
682
753
|
: undefined,
|
|
754
|
+
panePresent: writeTier && ctx.tmux
|
|
755
|
+
? (session, paneId) => {
|
|
756
|
+
try {
|
|
757
|
+
return ctx.tmux.listPanes(session).some((p) => p.id === paneId);
|
|
758
|
+
}
|
|
759
|
+
catch {
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
: undefined,
|
|
764
|
+
factoryReceiptsOf: writeTier ? (id) => receiptsForRecord(receipts(), id) : undefined,
|
|
765
|
+
recordsBoundTo: writeTier ? (sessionId) => recordsForSession(receipts(), sessionId) : undefined,
|
|
766
|
+
// What a result and a closure are checked against does not depend on who reads (#922 R5); only what is shown does.
|
|
767
|
+
boundSessionIdsOf: (id) => receiptsForRecord(receipts(), id).flatMap((x) => (x.session_id ? [x.session_id] : [])),
|
|
768
|
+
trustedRecords: all(),
|
|
769
|
+
operator: writeTier,
|
|
770
|
+
githubGet: github,
|
|
771
|
+
approvalUrl,
|
|
683
772
|
sessionOf: writeTier ? (id) => miadi.get(person.token, `/api/session/${encodeURIComponent(id)}`) : undefined,
|
|
684
773
|
issueOf: async (owner, repo, n) => {
|
|
685
774
|
const f = ctx.approval?.fetchImpl ?? fetch;
|
|
@@ -1119,21 +1208,77 @@ export function registerTools(server, ctx) {
|
|
|
1119
1208
|
.optional()
|
|
1120
1209
|
.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)."),
|
|
1121
1210
|
evidence_refs: refs,
|
|
1211
|
+
result: z
|
|
1212
|
+
.object({
|
|
1213
|
+
summary: z.string().max(4000).optional().describe("What was achieved, in the agent's words. Default: the reason."),
|
|
1214
|
+
outputs: z
|
|
1215
|
+
.array(z.object({
|
|
1216
|
+
ref: z.string().min(1).max(500).describe("A commit or file URL on GitHub, owner/repo#n, miadi-review:<uuid>, miadi-chronicle://<n>/<path>, a record id or a URL."),
|
|
1217
|
+
label: z.string().max(200).optional(),
|
|
1218
|
+
git_sha: z.string().regex(/^[0-9a-f]{7,40}$/).optional().describe("The commit sha, or the file's git blob sha, as submitted: a later change shows as changed_since_submission."),
|
|
1219
|
+
}))
|
|
1220
|
+
.max(30)
|
|
1221
|
+
.optional(),
|
|
1222
|
+
tests: z
|
|
1223
|
+
.object({
|
|
1224
|
+
command: z.string().max(300).optional(),
|
|
1225
|
+
passed: z.number().int().min(0).optional(),
|
|
1226
|
+
failed: z.number().int().min(0).optional(),
|
|
1227
|
+
skipped: z.number().int().min(0).optional(),
|
|
1228
|
+
note: z.string().max(1000).optional(),
|
|
1229
|
+
})
|
|
1230
|
+
.optional(),
|
|
1231
|
+
blockers: z.array(z.string().min(1).max(500)).max(20).optional(),
|
|
1232
|
+
})
|
|
1233
|
+
.optional()
|
|
1234
|
+
.describe("The mission's result as its agent returns it (jgwill/Miadi#921): mission_status and get_run_result read it back by the mission id. It is the agent's claim; outputs are checked when read."),
|
|
1122
1235
|
},
|
|
1123
1236
|
annotations: WRITE,
|
|
1124
1237
|
}, wrap(async (a, extra) => {
|
|
1125
1238
|
const { person, auth } = who(extra);
|
|
1126
1239
|
requireContribute(auth);
|
|
1127
1240
|
const id = a.session_id.replace(/^session:/, "");
|
|
1241
|
+
const startedAt = new Date().toISOString();
|
|
1128
1242
|
const r = append("session_completion_proposal", person, auth, {
|
|
1129
1243
|
circle: "continuity",
|
|
1130
1244
|
refs: a.mission ? [`session:${id}`, a.mission] : [`session:${id}`],
|
|
1131
1245
|
evidence_refs: a.evidence_refs,
|
|
1132
1246
|
approval_state: "awaiting_approval",
|
|
1133
1247
|
idempotency_key: a.mission ? `${id}:${a.ending}:${a.mission}` : `${id}:${a.ending}`,
|
|
1134
|
-
body: {
|
|
1248
|
+
body: {
|
|
1249
|
+
session_id: id,
|
|
1250
|
+
ending: a.ending,
|
|
1251
|
+
reason: a.reason,
|
|
1252
|
+
...(a.mission ? { mission: a.mission } : {}),
|
|
1253
|
+
...(a.result ? { result: a.result } : {}),
|
|
1254
|
+
// Set by the connector, never by the caller: a result is verified only when the write tier submits it (#922 R3).
|
|
1255
|
+
submitted_with_tier: person.api_tier ?? null,
|
|
1256
|
+
},
|
|
1135
1257
|
});
|
|
1136
|
-
|
|
1258
|
+
// The same session, ending and mission already awaiting a decision: it is handed back unchanged.
|
|
1259
|
+
const replayed = r.created_at < startedAt;
|
|
1260
|
+
const prior = r.body;
|
|
1261
|
+
const dropped = replayed
|
|
1262
|
+
? [
|
|
1263
|
+
...(prior.reason !== a.reason ? ["reason"] : []),
|
|
1264
|
+
...(JSON.stringify(prior.result ?? null) !== JSON.stringify(a.result ?? null) ? ["result"] : []),
|
|
1265
|
+
...(JSON.stringify(r.evidence_refs) !== JSON.stringify(a.evidence_refs ?? []) ? ["evidence_refs"] : []),
|
|
1266
|
+
]
|
|
1267
|
+
: [];
|
|
1268
|
+
return {
|
|
1269
|
+
...r,
|
|
1270
|
+
approval_url: approvalUrl(r.id),
|
|
1271
|
+
...(replayed
|
|
1272
|
+
? {
|
|
1273
|
+
replayed: true,
|
|
1274
|
+
...(dropped.length ? { not_recorded: dropped } : {}),
|
|
1275
|
+
note: dropped.length
|
|
1276
|
+
? `An ending for this session, ending and mission already awaits a decision; it stands as first submitted, and this call's ${dropped.join(", ")} were not recorded. Ask the person to decline it, then submit again.`
|
|
1277
|
+
: "An ending for this session, ending and mission already awaits a decision; it is returned unchanged and nothing new was recorded.",
|
|
1278
|
+
}
|
|
1279
|
+
: {}),
|
|
1280
|
+
...(a.mission ? { read_back: `mission_status with ${a.mission}` } : {}),
|
|
1281
|
+
};
|
|
1137
1282
|
}));
|
|
1138
1283
|
// ── requests: recorded, approved by a person on the approval page, then run ──
|
|
1139
1284
|
server.registerTool("request_authorized_run", {
|
|
@@ -1383,8 +1528,10 @@ export function registerTools(server, ctx) {
|
|
|
1383
1528
|
const { person, auth } = who(extra);
|
|
1384
1529
|
requireContribute(auth);
|
|
1385
1530
|
requireTier(person, "write");
|
|
1386
|
-
|
|
1387
|
-
|
|
1531
|
+
// No transport, no record: a steer that cannot reach a pane uses none of the grant (jgwill/Miadi#909).
|
|
1532
|
+
const transport = steeringTransport(ctx.tmux);
|
|
1533
|
+
if (transport.transport !== "operational")
|
|
1534
|
+
throw new ToolRefusal("unavailable", `${transport.reason} The steer is unstarted; no attempt was used.`);
|
|
1388
1535
|
const grantRecord = all().find((r) => r.id === a.run_request);
|
|
1389
1536
|
if (!grantRecord || (grantRecord.kind !== "run_request" && grantRecord.kind !== "work_proposal")) {
|
|
1390
1537
|
throw new ToolRefusal("invalid_ref", "Give the id of the approved run_request or work_proposal this steer belongs to.");
|
|
@@ -1395,10 +1542,14 @@ export function registerTools(server, ctx) {
|
|
|
1395
1542
|
const grant = steeringGrantOf(grantRecord);
|
|
1396
1543
|
const priorAttempts = steerAttempts(all(), grantRecord.id);
|
|
1397
1544
|
const covered = checkGrant(grant, priorAttempts) === "ok";
|
|
1545
|
+
// The pane the factory delivered this mission to: the steer goes there and nowhere else (jgwill/Miadi#909).
|
|
1546
|
+
const bound = receiptsForRecord(receipts(), grantRecord.id)[0];
|
|
1547
|
+
const binding = bound ? { session: bound.tmux_session, ...(bound.pane_id ? { pane_id: bound.pane_id } : {}) } : undefined;
|
|
1398
1548
|
const body = {
|
|
1399
1549
|
instruction: a.instruction,
|
|
1400
|
-
session: a.session ?? grantRecord.body.session,
|
|
1550
|
+
session: a.session ?? bound?.tmux_session ?? grantRecord.body.session,
|
|
1401
1551
|
pane: a.pane ?? grant?.pane,
|
|
1552
|
+
...(binding ? { binding: { ...binding, by: "factory_receipt", delivered_at: bound.at, ...(bound.session_id ? { session_id: bound.session_id } : {}) } } : {}),
|
|
1402
1553
|
};
|
|
1403
1554
|
const fields = {
|
|
1404
1555
|
circle: grantRecord.circle,
|
|
@@ -1421,8 +1572,9 @@ export function registerTools(server, ctx) {
|
|
|
1421
1572
|
};
|
|
1422
1573
|
}
|
|
1423
1574
|
const r = append("session_steer_request", person, auth, { ...fields, approval_state: "not_required" });
|
|
1424
|
-
const result = await steerSession({ tmux: ctx.tmux, stateDir: config.stateDir, ...(ctx.steerSettleMs !== undefined ? { settleMs: ctx.steerSettleMs } : {}) }, { record: r, grantRecord, priorAttempts });
|
|
1425
|
-
|
|
1575
|
+
const result = await steerSession({ tmux: ctx.tmux, stateDir: config.stateDir, ...(ctx.steerSettleMs !== undefined ? { settleMs: ctx.steerSettleMs } : {}) }, { record: r, grantRecord, priorAttempts, ...(binding ? { binding } : {}) });
|
|
1576
|
+
// A typed steer is never "failed": failed is re-runnable, and that would type it twice (#922 R8).
|
|
1577
|
+
const delivered = result.typed;
|
|
1426
1578
|
ledger.append({
|
|
1427
1579
|
id: newRecordId(r.kind),
|
|
1428
1580
|
kind: r.kind,
|
|
@@ -1439,13 +1591,18 @@ export function registerTools(server, ctx) {
|
|
|
1439
1591
|
decided_at: new Date().toISOString(),
|
|
1440
1592
|
outcome: delivered ? result.status : "failed",
|
|
1441
1593
|
steer_status: result.status,
|
|
1594
|
+
typed: result.typed,
|
|
1442
1595
|
message: result.message,
|
|
1443
1596
|
...(result.target ? { target: result.target } : {}),
|
|
1444
1597
|
...(result.artifact ? { artifact: result.artifact } : {}),
|
|
1445
1598
|
},
|
|
1446
1599
|
body: r.body,
|
|
1447
1600
|
});
|
|
1448
|
-
|
|
1601
|
+
const after = steerAttempts(all(), grantRecord.id);
|
|
1602
|
+
return {
|
|
1603
|
+
...(records().find((x) => x.id === r.id) ?? r),
|
|
1604
|
+
attempts: { used: after, ...(grant?.max_attempts !== undefined ? { max: grant.max_attempts } : {}), counted: result.typed },
|
|
1605
|
+
};
|
|
1449
1606
|
}));
|
|
1450
1607
|
server.registerTool("request_review_operation", {
|
|
1451
1608
|
title: "Request a review operation",
|
|
@@ -1885,7 +2042,7 @@ export const TOOL_STATUS = {
|
|
|
1885
2042
|
handoff_status: "live",
|
|
1886
2043
|
list_records: "live",
|
|
1887
2044
|
get_run_result: "live",
|
|
1888
|
-
mission_status: "live (operator details need the Miadi write tier)",
|
|
2045
|
+
mission_status: "live (operator details need the Miadi write tier; returns the agent's submitted result)",
|
|
1889
2046
|
mission_model_preflight: "live, Miadi write tier (static policy; Jev only with consult_jev and a Jev key on the host, otherwise router_unconfigured)",
|
|
1890
2047
|
provider_capabilities: "live, Miadi write tier",
|
|
1891
2048
|
search: "live",
|
|
@@ -1905,7 +2062,7 @@ export const TOOL_STATUS = {
|
|
|
1905
2062
|
revise_pending_request: "live, contribute (a waiting or sent-back run or proposal gets its next version; same id, same card)",
|
|
1906
2063
|
get_request_history: "live (every version of a request, oldest first; whoever asked, or the write tier)",
|
|
1907
2064
|
supersede_pending_requests: "live, contribute (retires named earlier requests still waiting and joins them as earlier versions of the one that stands)",
|
|
1908
|
-
request_session_steer: "live, contribute, write tier (runs inside an approved run's steering grant, else one scoped human decision)",
|
|
2065
|
+
request_session_steer: "live, contribute, write tier (runs inside an approved run's steering grant, else one scoped human decision; get_capabilities.steering says whether the transport works now)",
|
|
1909
2066
|
request_review_operation: "live, contribute, needs_approval",
|
|
1910
2067
|
request_deep_diver: "live, contribute, needs_approval",
|
|
1911
2068
|
link_review_to_episode: "live, contribute, needs_approval",
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.16";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.1.
|
|
1
|
+
export const VERSION = "0.1.16";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miadi/connector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
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",
|