@miadi/connector 0.1.19 → 0.1.21
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 +40 -5
- package/dist/approve.d.ts +16 -2
- package/dist/approve.js +185 -16
- package/dist/bin.js +3 -0
- package/dist/mission-status.d.ts +13 -1
- package/dist/mission-status.js +13 -5
- package/dist/model-preflight.d.ts +9 -2
- package/dist/model-preflight.js +23 -1
- package/dist/provider-policy.d.ts +94 -0
- package/dist/provider-policy.js +150 -0
- package/dist/providers.d.ts +7 -3
- package/dist/providers.js +13 -7
- package/dist/queue-state.js +12 -1
- package/dist/reading/adapters.d.ts +18 -0
- package/dist/reading/adapters.js +90 -0
- package/dist/reading/contract.d.ts +153 -0
- package/dist/reading/contract.js +79 -0
- package/dist/reading/markdown.d.ts +47 -0
- package/dist/reading/markdown.js +52 -0
- package/dist/reading/privacy.d.ts +7 -0
- package/dist/reading/privacy.js +14 -0
- package/dist/reading/workflow.d.ts +93 -0
- package/dist/reading/workflow.js +325 -0
- package/dist/records.d.ts +1 -1
- package/dist/records.js +6 -0
- package/dist/request-state.js +9 -1
- package/dist/research-digest.d.ts +30 -0
- package/dist/research-digest.js +104 -0
- package/dist/research-shelf.d.ts +173 -0
- package/dist/research-shelf.js +297 -0
- package/dist/runtime-steer.d.ts +182 -0
- package/dist/runtime-steer.js +543 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +4 -12
- package/dist/steer.d.ts +21 -7
- package/dist/steer.js +115 -10
- package/dist/store.d.ts +2 -0
- package/dist/store.js +18 -1
- package/dist/tools.d.ts +29 -1
- package/dist/tools.js +435 -50
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -1
package/dist/steer.js
CHANGED
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
* - The target is the pane the mission is bound to — the pane the grant names,
|
|
7
7
|
* else the session's first pane (lowest window, lowest pane, jgwill/Miadi#725).
|
|
8
8
|
* The active pane plays no part.
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - A working agent is the normal case (jgwill/Miadi#930). With a runtime route, the correction
|
|
10
|
+
* goes to that session's own inbox and the runtime decides when its model reads it
|
|
11
|
+
* (runtime-steer.ts). Without one, a busy seat answers unsupported with the reason; nothing
|
|
12
|
+
* is typed into a working agent's pane.
|
|
13
|
+
* - An idle seat has no continuation coming, so it is the one case still typed, into its bound
|
|
14
|
+
* pane: peek → scoped act → re-peek (jgwill/Miadi#345). A pane echo is not acceptance; what
|
|
15
|
+
* reached the model is read from the transcript (readSteerDelivery).
|
|
12
16
|
* - Every attempt leaves a durable artifact: the instruction, its sha256, the
|
|
13
17
|
* grant it ran under, the target, the timestamps and both captures.
|
|
14
18
|
* - Pane content is evidence, never instruction: nothing an agent printed is
|
|
@@ -16,8 +20,9 @@
|
|
|
16
20
|
*/
|
|
17
21
|
import { createHash } from "node:crypto";
|
|
18
22
|
import { execFileSync } from "node:child_process";
|
|
19
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
20
24
|
import { join } from "node:path";
|
|
25
|
+
import { enqueueSteer, resolveRuntime, sameInstructionInFlight, transcriptPathOf } from "./runtime-steer.js";
|
|
21
26
|
export const STEER_STATUSES = [
|
|
22
27
|
"prepared",
|
|
23
28
|
"submitted",
|
|
@@ -29,6 +34,9 @@ export const STEER_STATUSES = [
|
|
|
29
34
|
"expired_grant",
|
|
30
35
|
"duplicate",
|
|
31
36
|
"composing",
|
|
37
|
+
// jgwill/Miadi#930. "busy" stays readable on old records; a new steer never answers it.
|
|
38
|
+
"queued_at_runtime",
|
|
39
|
+
"unsupported",
|
|
32
40
|
];
|
|
33
41
|
/** Commands that mark a pane as an agent seat a steer may land in. */
|
|
34
42
|
export const AGENT_COMMANDS = ["claude", "codex", "copilot", "pi", "gemini", "node"];
|
|
@@ -121,20 +129,26 @@ export function steeringGrantOf(run) {
|
|
|
121
129
|
};
|
|
122
130
|
}
|
|
123
131
|
/** Whether a grant covers one more steer. `priorAttempts` counts the steers already run under it. */
|
|
124
|
-
export function checkGrant(grant, priorAttempts, now = new Date()) {
|
|
132
|
+
export function checkGrant(grant, priorAttempts, now = new Date(), revoked = false) {
|
|
125
133
|
if (!grant)
|
|
126
134
|
return "no_grant";
|
|
135
|
+
if (revoked)
|
|
136
|
+
return "revoked";
|
|
127
137
|
if (grant.expires_at && !Number.isNaN(Date.parse(grant.expires_at)) && Date.parse(grant.expires_at) <= now.getTime())
|
|
128
138
|
return "expired_grant";
|
|
129
139
|
if ((grant.max_attempts ?? 0) <= priorAttempts)
|
|
130
140
|
return "duplicate";
|
|
131
141
|
return "ok";
|
|
132
142
|
}
|
|
143
|
+
/** Whether a person revoked this run's steering, or just this one steer when `steer` is given (jgwill/Miadi#930). */
|
|
144
|
+
export function steeringRevoked(records, grantId) {
|
|
145
|
+
return records.some((r) => r.kind === "steering_revocation" && r.refs.includes(grantId) && !r.body.steer);
|
|
146
|
+
}
|
|
133
147
|
/**
|
|
134
148
|
* Steers already run under a grant: decided (or grant-covered) steer records that cite it and
|
|
135
|
-
* typed their instruction (jgwill/Miadi#909). A steer stopped before
|
|
136
|
-
* wrong or stale pane, transport down) uses none of the grant's attempts.
|
|
137
|
-
* `typed` was recorded count when their outcome says the text was delivered.
|
|
149
|
+
* typed or forwarded their instruction (jgwill/Miadi#909, #930). A steer stopped before that
|
|
150
|
+
* (composing, unsupported, wrong or stale pane, transport down) uses none of the grant's attempts.
|
|
151
|
+
* Receipts made before `typed` was recorded count when their outcome says the text was delivered.
|
|
138
152
|
*/
|
|
139
153
|
export function steerAttempts(records, grantId) {
|
|
140
154
|
return records.filter((r) => {
|
|
@@ -145,6 +159,8 @@ export function steerAttempts(records, grantId) {
|
|
|
145
159
|
const receipt = r.completion_receipt;
|
|
146
160
|
if (!receipt)
|
|
147
161
|
return false;
|
|
162
|
+
if (receipt.forwarded === true)
|
|
163
|
+
return true;
|
|
148
164
|
if (typeof receipt.typed === "boolean")
|
|
149
165
|
return receipt.typed;
|
|
150
166
|
const outcome = String(receipt.steer_status ?? receipt.outcome ?? "");
|
|
@@ -179,6 +195,15 @@ export function readSteerArtifact(stateDir, recordId) {
|
|
|
179
195
|
}
|
|
180
196
|
}
|
|
181
197
|
const CAPTURE_LINES = 40;
|
|
198
|
+
const str = (v) => (typeof v === "string" && v.trim() ? v.trim() : undefined);
|
|
199
|
+
const fileSize = (path) => {
|
|
200
|
+
try {
|
|
201
|
+
return statSync(path).size;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
182
207
|
/**
|
|
183
208
|
* Run one steer under its grant. The record is the session_steer_request; the
|
|
184
209
|
* grant record is the approved run the steer belongs to. A returned status
|
|
@@ -195,6 +220,9 @@ export async function steerSession(deps, args) {
|
|
|
195
220
|
const grantSession = String(grantRecord.body.session ?? "") || undefined;
|
|
196
221
|
const session = body.session ?? args.binding?.session ?? grantSession;
|
|
197
222
|
let typed = false;
|
|
223
|
+
let forwarded = false;
|
|
224
|
+
let route;
|
|
225
|
+
let transcript = {};
|
|
198
226
|
const finish = (status, message, extra = {}, target) => {
|
|
199
227
|
const artifact = persistArtifact(deps.stateDir, record.id, {
|
|
200
228
|
record: record.id,
|
|
@@ -204,14 +232,33 @@ export async function steerSession(deps, args) {
|
|
|
204
232
|
status,
|
|
205
233
|
message,
|
|
206
234
|
typed,
|
|
235
|
+
...(forwarded ? { forwarded } : {}),
|
|
236
|
+
...(route ? { route } : {}),
|
|
237
|
+
...transcript,
|
|
238
|
+
...(target ? { target } : {}),
|
|
207
239
|
...(args.binding ? { binding: args.binding } : {}),
|
|
208
240
|
at: now().toISOString(),
|
|
209
241
|
...extra,
|
|
210
242
|
});
|
|
211
|
-
return { status, message, typed, ...(target ? { target } : {}), artifact };
|
|
243
|
+
return { status, message, typed, ...(forwarded ? { forwarded } : {}), ...(route ? { route } : {}), ...transcript, ...(target ? { target } : {}), artifact };
|
|
212
244
|
};
|
|
213
245
|
if (!instruction.trim())
|
|
214
246
|
return { status: "blocked", message: "The steer carries no instruction.", typed: false };
|
|
247
|
+
// Sent before, or about to be when the connector stopped: this answers from that, and nothing is sent again.
|
|
248
|
+
const sent = readSteerArtifact(deps.stateDir, record.id);
|
|
249
|
+
if (sent?.typed === true || sent?.forwarded === true) {
|
|
250
|
+
const earlierRoute = sent.route;
|
|
251
|
+
return {
|
|
252
|
+
status: sent.status,
|
|
253
|
+
message: String(sent.message ?? ""),
|
|
254
|
+
typed: sent.typed === true,
|
|
255
|
+
...(sent.forwarded === true ? { forwarded: true } : {}),
|
|
256
|
+
...(earlierRoute ? { route: earlierRoute } : {}),
|
|
257
|
+
...(typeof sent.transcript_offset === "number" ? { transcript_offset: sent.transcript_offset } : {}),
|
|
258
|
+
...(typeof sent.target === "string" ? { target: sent.target } : {}),
|
|
259
|
+
artifact: { file: artifactPath(deps.stateDir, record.id), sha256: String(sent.instruction_sha256 ?? "") },
|
|
260
|
+
};
|
|
261
|
+
}
|
|
215
262
|
if (!session)
|
|
216
263
|
return finish("stale_binding", "Neither the steer nor its grant names a session; a steer never targets whichever pane is active.");
|
|
217
264
|
if (body.session && grantSession && body.session !== grantSession) {
|
|
@@ -263,6 +310,48 @@ export async function steerSession(deps, args) {
|
|
|
263
310
|
if (!agentCommands.includes(pane.command)) {
|
|
264
311
|
return finish("wrong_pane", `Pane ${target} runs "${pane.command}", not an agent; nothing was typed.`, { pane: { ...pane } }, target);
|
|
265
312
|
}
|
|
313
|
+
if (args.sameInstructionSent && !args.authorized) {
|
|
314
|
+
return finish("duplicate", `These same words were already sent under this grant as ${args.sameInstructionSent}; they are not sent twice. Reword it if you mean something new.`, { earlier: args.sameInstructionSent }, target);
|
|
315
|
+
}
|
|
316
|
+
// A mission bound to a session is answered about that session: the mission's own binding, Claude
|
|
317
|
+
// Code's registry and the adapter's file, never a reading of the pane (jgwill/Miadi#930).
|
|
318
|
+
const provider = args.binding?.provider ?? str(grantRecord.body.resolved_provider) ?? str(grantRecord.body.provider);
|
|
319
|
+
const runtime = deps.runtime
|
|
320
|
+
? resolveRuntime(deps.runtime, { session_id: args.binding?.session_id, pane_id: pane.id, provider })
|
|
321
|
+
: { route: "none", code: "unsupported", reason: "This host has no runtime steering route configured." };
|
|
322
|
+
if (runtime.route === "none" && runtime.code !== "unsupported") {
|
|
323
|
+
return finish(runtime.code, `${runtime.reason} Nothing was sent.`, { pane: { ...pane } }, target);
|
|
324
|
+
}
|
|
325
|
+
// The runtime owns when its model reads this. The connector hands it over and is done.
|
|
326
|
+
const forward = (rt) => {
|
|
327
|
+
const earlier = sameInstructionInFlight(deps.runtime, rt.session_id, grantRecord.id, instruction);
|
|
328
|
+
if (earlier && earlier !== record.id) {
|
|
329
|
+
return finish("duplicate", `These same words were already forwarded to this session as ${earlier}; they are not sent twice.`, { earlier }, target);
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const actor = record.actor;
|
|
333
|
+
const { file, already, envelope } = enqueueSteer(deps.runtime, {
|
|
334
|
+
steer: record.id,
|
|
335
|
+
grant: grantRecord.id,
|
|
336
|
+
session_id: rt.session_id,
|
|
337
|
+
instruction,
|
|
338
|
+
from: { person_id: actor.person_id, name: actor.name, ...(actor.client_name ? { via: actor.client_name } : {}) },
|
|
339
|
+
// A person's decision on this exact steer stands apart from the grant it cites: the grant's expiry and revocation do not stop it.
|
|
340
|
+
...(args.authorized ? { decided: true } : grant?.expires_at ? { grant_expires_at: grant.expires_at } : {}),
|
|
341
|
+
...(rt.transcript_path ? { transcript_path: rt.transcript_path } : {}),
|
|
342
|
+
now: now(),
|
|
343
|
+
});
|
|
344
|
+
forwarded = true;
|
|
345
|
+
route = "claude_hook_inbox";
|
|
346
|
+
transcript = envelope.transcript_offset !== undefined ? { transcript_offset: envelope.transcript_offset } : {};
|
|
347
|
+
return finish("queued_at_runtime", `Handed to the inbox of Claude Code session ${rt.session_id}. Its own hooks present it to its model at its next continuation, in the same turn. This is not yet proof the model read it.`, { inbox_file: file, already_queued: already, queued_at: envelope.queued_at, expires_at: envelope.expires_at }, target);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
return finish("blocked", `The inbox of session ${rt.session_id} refused the steer (${error instanceof Error ? error.message : String(error)}); nothing was forwarded.`, {}, target);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
if (runtime.route === "claude_hook_inbox" && runtime.seat !== "idle")
|
|
354
|
+
return forward(runtime);
|
|
266
355
|
let before;
|
|
267
356
|
try {
|
|
268
357
|
before = deps.tmux.capture(target);
|
|
@@ -271,22 +360,38 @@ export async function steerSession(deps, args) {
|
|
|
271
360
|
return finish("blocked", `The pane could not be read (${error instanceof Error ? error.message : String(error)}); nothing was typed.`, {}, target);
|
|
272
361
|
}
|
|
273
362
|
if (isBusy(before)) {
|
|
274
|
-
|
|
363
|
+
if (runtime.route === "claude_hook_inbox")
|
|
364
|
+
return forward(runtime);
|
|
365
|
+
return finish("unsupported", `The agent in ${target} is working and has no runtime steering route: ${runtime.route === "none" ? runtime.reason : ""} Nothing was typed.`, { peek: before.split("\n").slice(-CAPTURE_LINES) }, target);
|
|
275
366
|
}
|
|
276
367
|
const composing = composingText(before);
|
|
277
368
|
if (composing === null) {
|
|
369
|
+
if (runtime.route === "claude_hook_inbox")
|
|
370
|
+
return forward(runtime);
|
|
278
371
|
return finish("blocked", `The prompt box of ${target} (${pane.command}) cannot be read, so whether someone is writing there cannot be told. Nothing was typed.`, { peek: before.split("\n").slice(-CAPTURE_LINES) }, target);
|
|
279
372
|
}
|
|
280
373
|
if (composing) {
|
|
374
|
+
// Someone's words are at the prompt: the inbox waits for their prompt and never touches them.
|
|
375
|
+
if (runtime.route === "claude_hook_inbox")
|
|
376
|
+
return forward(runtime);
|
|
281
377
|
return finish("composing", `Text already sits at the prompt in ${target}; someone may be writing. Nothing was typed and Enter was not pressed.`, { peek: before.split("\n").slice(-CAPTURE_LINES) }, target);
|
|
282
378
|
}
|
|
379
|
+
if (deps.runtime && args.binding?.session_id) {
|
|
380
|
+
const path = transcriptPathOf(deps.runtime, args.binding.session_id);
|
|
381
|
+
if (path)
|
|
382
|
+
transcript = { transcript_offset: fileSize(path) };
|
|
383
|
+
}
|
|
384
|
+
// Written before the keys: a connector stopped between here and the receipt never types it twice.
|
|
385
|
+
persistArtifact(deps.stateDir, record.id, { record: record.id, grant: grantRecord.id, instruction, session, status: "submitted", message: `Typed into ${target}; the connector stopped before it could say more.`, typed: true, route: "tmux_exact_pane", ...transcript, target, at: now().toISOString() });
|
|
283
386
|
try {
|
|
284
387
|
deps.tmux.sendLiteral(target, instruction);
|
|
285
388
|
}
|
|
286
389
|
catch (error) {
|
|
390
|
+
// finish() overwrites the artifact written above: nothing was typed, so a later call may try again.
|
|
287
391
|
return finish("blocked", `tmux refused the text for ${target} (${error instanceof Error ? error.message : String(error)}); nothing was typed.`, { peek: before.split("\n").slice(-CAPTURE_LINES) }, target);
|
|
288
392
|
}
|
|
289
393
|
typed = true;
|
|
394
|
+
route = "tmux_exact_pane";
|
|
290
395
|
try {
|
|
291
396
|
deps.tmux.sendEnter(target);
|
|
292
397
|
}
|
package/dist/store.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare class Ledger<R extends {
|
|
|
17
17
|
private cache;
|
|
18
18
|
constructor(path: string);
|
|
19
19
|
append(record: R): R;
|
|
20
|
+
/** A processing receipt is committed to disk before its dependent stage starts. */
|
|
21
|
+
appendDurable(record: R): R;
|
|
20
22
|
all(): R[];
|
|
21
23
|
}
|
|
22
24
|
export declare function statePaths(dir: string): {
|
package/dist/store.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* (observations, handoffs, proposals). Append-only: a record
|
|
11
11
|
* is never edited, a later record supersedes it.
|
|
12
12
|
*/
|
|
13
|
-
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
13
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, statSync, writeSync } from "node:fs";
|
|
14
14
|
import { createHash, randomBytes } from "node:crypto";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { readJsonSafe, writeJsonAtomic } from "@miadi/node-service-kit/json";
|
|
@@ -66,6 +66,23 @@ export class Ledger {
|
|
|
66
66
|
appendFileSync(this.path, JSON.stringify(record) + "\n", { mode: 0o600 });
|
|
67
67
|
return record;
|
|
68
68
|
}
|
|
69
|
+
/** A processing receipt is committed to disk before its dependent stage starts. */
|
|
70
|
+
appendDurable(record) {
|
|
71
|
+
const fd = openSync(this.path, "a", 0o600);
|
|
72
|
+
try {
|
|
73
|
+
// Keep a torn crash tail separate from this record, as all() skips invalid lines.
|
|
74
|
+
const old = readFileSync(this.path);
|
|
75
|
+
const line = Buffer.from((old.length && old[old.length - 1] !== 10 ? "\n" : "") + JSON.stringify(record) + "\n");
|
|
76
|
+
let written = 0;
|
|
77
|
+
while (written < line.length)
|
|
78
|
+
written += writeSync(fd, line, written, line.length - written);
|
|
79
|
+
fsyncSync(fd);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
closeSync(fd);
|
|
83
|
+
}
|
|
84
|
+
return record;
|
|
85
|
+
}
|
|
69
86
|
all() {
|
|
70
87
|
if (!existsSync(this.path))
|
|
71
88
|
return [];
|
package/dist/tools.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { type ApprovalDeps } from "./approve.js";
|
|
|
9
9
|
import { type DecisionTokens } from "./decisions.js";
|
|
10
10
|
import { type DiscoveryProbe, type ProviderId } from "./providers.js";
|
|
11
11
|
import { type JevAdapter } from "./model-preflight.js";
|
|
12
|
+
import { type RuntimeDeps } from "./runtime-steer.js";
|
|
12
13
|
import { type TmuxLike } from "./steer.js";
|
|
13
14
|
import { type GithubGet } from "./mission-result.js";
|
|
14
15
|
export interface ToolContext {
|
|
@@ -24,6 +25,8 @@ export interface ToolContext {
|
|
|
24
25
|
decisionTokens?: DecisionTokens;
|
|
25
26
|
/** The steering surface (jgwill/Miadi#893); absent, steering answers unavailable. */
|
|
26
27
|
tmux?: TmuxLike;
|
|
28
|
+
/** Where sessions and their steering inboxes live (jgwill/Miadi#930). Absent, no runtime route is known. */
|
|
29
|
+
steeringRuntime?: RuntimeDeps;
|
|
27
30
|
tmuxSessions?: () => string[];
|
|
28
31
|
/** Provider discovery probes; tests inject fakes (jgwill/Miadi#893). */
|
|
29
32
|
providersProbe?: DiscoveryProbe;
|
|
@@ -42,7 +45,32 @@ export interface ToolContext {
|
|
|
42
45
|
export declare function githubAccess(status: number, hasToken: boolean): string;
|
|
43
46
|
/** A GitHub REST read as the connector can make it: GITHUB_TOKEN when the host has one, else anonymous. */
|
|
44
47
|
export declare function githubGetter(fetchImpl?: typeof fetch, token?: string | undefined): GithubGet;
|
|
45
|
-
|
|
48
|
+
export declare const STEERING_CONTRACT: {
|
|
49
|
+
readonly busy_is_normal: true;
|
|
50
|
+
readonly connector: "Admits the steer, resolves mission → session → runtime, hands it to that session's inbox and reports what the runtime accepted. It does not watch tools or wait for idle.";
|
|
51
|
+
readonly runtime: "The agent's own lifecycle presents it to its model at the next supported continuation in the same turn.";
|
|
52
|
+
readonly delivery_states: readonly ["authorized", "forwarded_to_runtime", "queued_at_runtime", "delivered_to_agent_context", "acknowledged", "behavior_adjusted", "unsupported", "expired", "blocked", "unknown"];
|
|
53
|
+
readonly provider_adapters: {
|
|
54
|
+
readonly claude: {
|
|
55
|
+
readonly route: "claude_hook_inbox";
|
|
56
|
+
readonly continuation: "PostToolUse, PostToolUseFailure, then Stop if no tool follows";
|
|
57
|
+
readonly needs: "miadi-session-observability 0.1.34 or later, loaded when the session started";
|
|
58
|
+
};
|
|
59
|
+
readonly codex: {
|
|
60
|
+
readonly route: "unsupported";
|
|
61
|
+
};
|
|
62
|
+
readonly copilot: {
|
|
63
|
+
readonly route: "unsupported";
|
|
64
|
+
};
|
|
65
|
+
readonly pi: {
|
|
66
|
+
readonly route: "unsupported";
|
|
67
|
+
};
|
|
68
|
+
readonly gemini: {
|
|
69
|
+
readonly route: "unsupported";
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
readonly idle_seat: "An idle seat has no continuation coming, so its steer is typed into its bound pane once the prompt reads empty.";
|
|
73
|
+
};
|
|
46
74
|
export declare function steeringTransport(tmux: TmuxLike | undefined): {
|
|
47
75
|
tool: "registered";
|
|
48
76
|
transport: "operational" | "unavailable";
|