@rynx-ai/runtime 0.1.11-beta.3 → 0.1.11-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude/executor.d.ts +19 -5
- package/dist/claude/executor.js +56 -12
- package/dist/claude/models.d.ts +0 -5
- package/dist/claude/models.js +1 -7
- package/dist/claude/native-bridge.d.ts +2 -0
- package/dist/claude/native-bridge.js +23 -0
- package/dist/claude/native-hook-main.js +62 -0
- package/dist/claude/native-integration.d.ts +50 -10
- package/dist/claude/native-integration.js +262 -37
- package/dist/claude/session-status.d.ts +39 -0
- package/dist/claude/session-status.js +163 -0
- package/dist/claude/transcript.js +27 -17
- package/dist/codex-app-server/client.d.ts +10 -6
- package/dist/codex-app-server/client.js +67 -15
- package/dist/codex-app-server/forwarder.d.ts +92 -3
- package/dist/codex-app-server/forwarder.js +509 -56
- package/dist/codex-app-server/mapping.d.ts +3 -6
- package/dist/codex-app-server/mapping.js +174 -28
- package/dist/codex-app-server/mcp-startup.d.ts +13 -0
- package/dist/codex-app-server/mcp-startup.js +63 -0
- package/dist/codex-app-server/protocol.d.ts +64 -7
- package/dist/codex-app-server/ws-channel.js +19 -19
- package/dist/codex-home.js +2 -4
- package/dist/host.d.ts +64 -21
- package/dist/host.js +1333 -468
- package/dist/index.d.ts +1 -1
- package/dist/input-resources.d.ts +4 -0
- package/dist/input-resources.js +21 -5
- package/dist/models-catalog.d.ts +2 -1
- package/dist/models-catalog.js +94 -6
- package/dist/runner/child.d.ts +49 -21
- package/dist/runner/child.js +629 -48
- package/dist/runner/manager.d.ts +54 -13
- package/dist/runner/manager.js +479 -114
- package/dist/runner/protocol.d.ts +62 -19
- package/dist/runner/protocol.js +5 -0
- package/dist/runner/startup-policy.d.ts +7 -0
- package/dist/runner/startup-policy.js +10 -0
- package/dist/terminal/claude-tui.d.ts +3 -1
- package/dist/terminal/claude-tui.js +3 -1
- package/dist/terminal/registry.js +3 -2
- package/dist/terminal/tmux.d.ts +50 -7
- package/dist/terminal/tmux.js +168 -47
- package/package.json +4 -3
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import type { SkillMeta } from "@rynx-ai/core";
|
|
2
|
+
export interface MaterializedClaudePlugin {
|
|
3
|
+
pluginDir: string;
|
|
4
|
+
}
|
|
5
|
+
/** Stable Claude `--plugin-dir` owned by one Rynx Session. */
|
|
6
|
+
export declare function claudePluginDir(sessionId: string): string;
|
|
2
7
|
/**
|
|
3
|
-
*
|
|
8
|
+
* Return the existing Session-owned plugin directory as-is. It is the durable
|
|
9
|
+
* Provider input for this Session, not an immutable shared cache: runtime files
|
|
10
|
+
* created inside it (for example Python `__pycache__`) remain part of that
|
|
11
|
+
* Session and do not trigger SkillRef replay.
|
|
12
|
+
*/
|
|
13
|
+
export declare function reuseClaudePlugin(sessionId: string): Promise<MaterializedClaudePlugin | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Materialize a selected catalog skill subset into a Session-owned Claude
|
|
4
16
|
* `--plugin-dir` (a dir with `skills/<name>/SKILL.md` + a `.claude-plugin/plugin.json`
|
|
5
17
|
* manifest — claude's mechanism for exposing skills outside its host dirs, since
|
|
6
18
|
* it doesn't scan `~/.rynx/skills`). Consumed by the claude-native live launch.
|
|
7
19
|
*
|
|
20
|
+
* The committed path is deterministic and survives Provider/runner restarts.
|
|
21
|
+
* An existing directory is returned as-is. The first materialization is built
|
|
22
|
+
* in a sibling staging directory and renamed into the stable path so a crash
|
|
23
|
+
* cannot expose a partially copied plugin.
|
|
24
|
+
*
|
|
8
25
|
* Returns `null` when there's nothing to add. Rynx-managed Claude sessions load
|
|
9
26
|
* no host/project/local setting sources, so this explicit plugin directory is
|
|
10
27
|
* the only native skill channel; the selected subset is enforced exactly.
|
|
11
28
|
*/
|
|
12
|
-
export declare function
|
|
13
|
-
pluginDir?: string;
|
|
14
|
-
cleanup?: () => Promise<void>;
|
|
15
|
-
} | null>;
|
|
29
|
+
export declare function materializeClaudePlugin(sessionId: string, selected: SkillMeta[] | null | undefined): Promise<MaterializedClaudePlugin | null>;
|
package/dist/claude/executor.js
CHANGED
|
@@ -1,26 +1,70 @@
|
|
|
1
|
-
import { cp, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
1
|
+
import { cp, mkdir, mkdtemp, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { runtimeSessionStateDir } from "../runtime-state-paths.js";
|
|
4
|
+
/** Stable Claude `--plugin-dir` owned by one Rynx Session. */
|
|
5
|
+
export function claudePluginDir(sessionId) {
|
|
6
|
+
return path.join(runtimeSessionStateDir(sessionId), "claude-plugin");
|
|
7
|
+
}
|
|
4
8
|
/**
|
|
5
|
-
*
|
|
9
|
+
* Return the existing Session-owned plugin directory as-is. It is the durable
|
|
10
|
+
* Provider input for this Session, not an immutable shared cache: runtime files
|
|
11
|
+
* created inside it (for example Python `__pycache__`) remain part of that
|
|
12
|
+
* Session and do not trigger SkillRef replay.
|
|
13
|
+
*/
|
|
14
|
+
export async function reuseClaudePlugin(sessionId) {
|
|
15
|
+
const pluginDir = claudePluginDir(sessionId);
|
|
16
|
+
try {
|
|
17
|
+
return (await stat(pluginDir)).isDirectory() ? { pluginDir } : null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Materialize a selected catalog skill subset into a Session-owned Claude
|
|
6
25
|
* `--plugin-dir` (a dir with `skills/<name>/SKILL.md` + a `.claude-plugin/plugin.json`
|
|
7
26
|
* manifest — claude's mechanism for exposing skills outside its host dirs, since
|
|
8
27
|
* it doesn't scan `~/.rynx/skills`). Consumed by the claude-native live launch.
|
|
9
28
|
*
|
|
29
|
+
* The committed path is deterministic and survives Provider/runner restarts.
|
|
30
|
+
* An existing directory is returned as-is. The first materialization is built
|
|
31
|
+
* in a sibling staging directory and renamed into the stable path so a crash
|
|
32
|
+
* cannot expose a partially copied plugin.
|
|
33
|
+
*
|
|
10
34
|
* Returns `null` when there's nothing to add. Rynx-managed Claude sessions load
|
|
11
35
|
* no host/project/local setting sources, so this explicit plugin directory is
|
|
12
36
|
* the only native skill channel; the selected subset is enforced exactly.
|
|
13
37
|
*/
|
|
14
|
-
export async function
|
|
38
|
+
export async function materializeClaudePlugin(sessionId, selected) {
|
|
15
39
|
if (selected == null || selected.length === 0)
|
|
16
40
|
return null;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
const reused = await reuseClaudePlugin(sessionId);
|
|
42
|
+
if (reused)
|
|
43
|
+
return reused;
|
|
44
|
+
const pluginDir = claudePluginDir(sessionId);
|
|
45
|
+
const stateDir = runtimeSessionStateDir(sessionId);
|
|
46
|
+
await mkdir(stateDir, { recursive: true, mode: 0o700 });
|
|
47
|
+
const stagingDir = await mkdtemp(path.join(stateDir, ".claude-plugin-stage-"));
|
|
48
|
+
try {
|
|
49
|
+
const skillsRoot = path.join(stagingDir, "skills");
|
|
50
|
+
await mkdir(skillsRoot, { recursive: true });
|
|
51
|
+
for (const skill of selected) {
|
|
52
|
+
await cp(skill.dir, path.join(skillsRoot, skill.name), { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
await mkdir(path.join(stagingDir, ".claude-plugin"), { recursive: true });
|
|
55
|
+
await writeFile(path.join(stagingDir, ".claude-plugin", "plugin.json"), `${JSON.stringify({ name: "rynx-agent-skills", description: "Per-agent skill subset" }, null, 2)}\n`);
|
|
56
|
+
try {
|
|
57
|
+
await rename(stagingDir, pluginDir);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const concurrent = await reuseClaudePlugin(sessionId);
|
|
61
|
+
if (!concurrent)
|
|
62
|
+
throw error;
|
|
63
|
+
return concurrent;
|
|
64
|
+
}
|
|
65
|
+
return { pluginDir };
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
22
69
|
}
|
|
23
|
-
await mkdir(path.join(dir, ".claude-plugin"), { recursive: true });
|
|
24
|
-
await writeFile(path.join(dir, ".claude-plugin", "plugin.json"), `${JSON.stringify({ name: "rynx-agent-skills", description: "Per-agent skill subset" }, null, 2)}\n`);
|
|
25
|
-
return { pluginDir: dir, cleanup: () => rm(dir, { recursive: true, force: true }) };
|
|
26
70
|
}
|
package/dist/claude/models.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { ModelInfo, ModelListResponse } from "../codex-app-server/protocol.js";
|
|
2
|
-
/**
|
|
3
|
-
* Default model for the claude runtime when `CLAUDE_MODEL` is unset. Kept in
|
|
4
|
-
* sync with the `CLAUDE_MODEL` default in {@link import("../config.js")}.
|
|
5
|
-
*/
|
|
6
|
-
export declare const CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
7
2
|
/** Build the `ModelInfo[]` for the claude runtime. */
|
|
8
3
|
export declare function claudeModelInfos(): ModelInfo[];
|
|
9
4
|
/** {@link ModelListResponse} for the claude runtime (broker `listModels`). */
|
package/dist/claude/models.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default model for the claude runtime when `CLAUDE_MODEL` is unset. Kept in
|
|
3
|
-
* sync with the `CLAUDE_MODEL` default in {@link import("../config.js")}.
|
|
4
|
-
*/
|
|
5
|
-
export const CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
6
1
|
/**
|
|
7
2
|
* Static catalogue of Claude models the `/model` card and `/models` reply offer
|
|
8
3
|
* for the claude runtime. Unlike codex/traex (which query a live app-server),
|
|
@@ -12,7 +7,7 @@ export const CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
|
12
7
|
*/
|
|
13
8
|
const CLAUDE_MODEL_IDS = [
|
|
14
9
|
{ id: "claude-opus-4-8", displayName: "Claude Opus 4.8 · 最强" },
|
|
15
|
-
{ id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6 ·
|
|
10
|
+
{ id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6 · 均衡" },
|
|
16
11
|
{ id: "claude-haiku-4-5", displayName: "Claude Haiku 4.5 · 最快" },
|
|
17
12
|
{ id: "claude-opus-4-7", displayName: "Claude Opus 4.7" },
|
|
18
13
|
{ id: "claude-opus-4-6", displayName: "Claude Opus 4.6" },
|
|
@@ -24,7 +19,6 @@ export function claudeModelInfos() {
|
|
|
24
19
|
id,
|
|
25
20
|
model: id,
|
|
26
21
|
displayName,
|
|
27
|
-
isDefault: id === CLAUDE_DEFAULT_MODEL,
|
|
28
22
|
}));
|
|
29
23
|
}
|
|
30
24
|
/** {@link ModelListResponse} for the claude runtime (broker `listModels`). */
|
|
@@ -109,6 +109,8 @@ export interface HookEvent {
|
|
|
109
109
|
/** Claude's session uuid — subagent hooks report a `subagents/…` transcript path. */
|
|
110
110
|
sessionId?: string;
|
|
111
111
|
source?: string;
|
|
112
|
+
/** Authoritative live background-shell count carried by a Stop hook. */
|
|
113
|
+
backgroundTaskCount?: number;
|
|
112
114
|
payload: Record<string, unknown>;
|
|
113
115
|
}
|
|
114
116
|
/** Tail `hooks.jsonl` from a byte offset into parsed {@link HookEvent}s. */
|
|
@@ -267,6 +267,25 @@ function readJsonFile(path) {
|
|
|
267
267
|
function asString(value) {
|
|
268
268
|
return typeof value === "string" && value ? value : undefined;
|
|
269
269
|
}
|
|
270
|
+
const TERMINAL_BACKGROUND_TASK_STATUSES = new Set([
|
|
271
|
+
"completed",
|
|
272
|
+
"failed",
|
|
273
|
+
"stopped",
|
|
274
|
+
"killed",
|
|
275
|
+
]);
|
|
276
|
+
function backgroundTaskCount(payload) {
|
|
277
|
+
if (payload.hook_event_name !== "Stop")
|
|
278
|
+
return undefined;
|
|
279
|
+
const tasks = payload.background_tasks;
|
|
280
|
+
if (!Array.isArray(tasks))
|
|
281
|
+
return 0;
|
|
282
|
+
return tasks.filter((task) => {
|
|
283
|
+
if (!task || typeof task !== "object" || Array.isArray(task))
|
|
284
|
+
return true;
|
|
285
|
+
const status = task.status;
|
|
286
|
+
return typeof status !== "string" || !TERMINAL_BACKGROUND_TASK_STATUSES.has(status);
|
|
287
|
+
}).length;
|
|
288
|
+
}
|
|
270
289
|
export function readClaudeState(bridgeDir) {
|
|
271
290
|
const raw = readJsonFile(join(bridgeDir, STATE_FILE));
|
|
272
291
|
if (!raw || typeof raw !== "object")
|
|
@@ -348,11 +367,15 @@ export function readHookEventsFrom(bridgeDir, byteOffset) {
|
|
|
348
367
|
const payload = rec.payload;
|
|
349
368
|
if (!payload || typeof payload !== "object")
|
|
350
369
|
continue;
|
|
370
|
+
const liveBackgroundTasks = backgroundTaskCount(payload);
|
|
351
371
|
events.push({
|
|
352
372
|
eventName: asString(payload.hook_event_name),
|
|
353
373
|
transcriptPath: asString(payload.transcript_path),
|
|
354
374
|
sessionId: asString(payload.session_id),
|
|
355
375
|
source: asString(payload.source),
|
|
376
|
+
...(liveBackgroundTasks === undefined
|
|
377
|
+
? {}
|
|
378
|
+
: { backgroundTaskCount: liveBackgroundTasks }),
|
|
356
379
|
payload,
|
|
357
380
|
});
|
|
358
381
|
}
|
|
@@ -176,6 +176,43 @@ function permissionRequest(id, payload, toolInput, suggestions) {
|
|
|
176
176
|
const toolName = asString(payload.tool_name) ?? "tool";
|
|
177
177
|
const command = asString(toolInput.command) ?? asString(toolInput.file_path);
|
|
178
178
|
const cwd = asString(payload.cwd);
|
|
179
|
+
if (toolName === "ExitPlanMode") {
|
|
180
|
+
const plan = asString(toolInput.plan) ?? "Plan details were not provided by Claude.";
|
|
181
|
+
return {
|
|
182
|
+
interactionId: id,
|
|
183
|
+
kind: "permission",
|
|
184
|
+
title: "Plan review",
|
|
185
|
+
fields: [{
|
|
186
|
+
id: "feedback",
|
|
187
|
+
type: "text",
|
|
188
|
+
label: "What should change about the plan?",
|
|
189
|
+
required: false,
|
|
190
|
+
multiline: true,
|
|
191
|
+
placeholder: "Revision feedback (optional)",
|
|
192
|
+
}],
|
|
193
|
+
actions: [
|
|
194
|
+
{
|
|
195
|
+
id: "allow_auto",
|
|
196
|
+
label: "Yes, and use auto mode",
|
|
197
|
+
style: "primary",
|
|
198
|
+
requiresAnswers: false,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: "allow_manual",
|
|
202
|
+
label: "Yes, manually approve edits",
|
|
203
|
+
requiresAnswers: false,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "deny_feedback",
|
|
207
|
+
label: "Reject with feedback",
|
|
208
|
+
style: "danger",
|
|
209
|
+
requiresAnswers: false,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
context: { toolName, summary: plan },
|
|
213
|
+
createdAt: Date.now(),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
179
216
|
return {
|
|
180
217
|
interactionId: id,
|
|
181
218
|
kind: "permission",
|
|
@@ -261,6 +298,31 @@ function nativeVerdict(hookKind, payload, result, suggestions) {
|
|
|
261
298
|
},
|
|
262
299
|
};
|
|
263
300
|
}
|
|
301
|
+
if (toolName === "ExitPlanMode") {
|
|
302
|
+
const feedback = answerText(resolution.answers?.feedback).trim();
|
|
303
|
+
const behavior = resolution.actionId === "allow_auto" ||
|
|
304
|
+
resolution.actionId === "allow_manual"
|
|
305
|
+
? "allow"
|
|
306
|
+
: "deny";
|
|
307
|
+
return {
|
|
308
|
+
hookSpecificOutput: {
|
|
309
|
+
hookEventName: "PermissionRequest",
|
|
310
|
+
decision: {
|
|
311
|
+
behavior,
|
|
312
|
+
...(behavior === "deny" && feedback ? { message: feedback } : {}),
|
|
313
|
+
...(behavior === "allow"
|
|
314
|
+
? {
|
|
315
|
+
updatedPermissions: [{
|
|
316
|
+
type: "setMode",
|
|
317
|
+
mode: resolution.actionId === "allow_auto" ? "auto" : "default",
|
|
318
|
+
destination: "session",
|
|
319
|
+
}],
|
|
320
|
+
}
|
|
321
|
+
: {}),
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
264
326
|
const suggestionMatch = /^allow_suggestion_(\d+)$/.exec(resolution.actionId);
|
|
265
327
|
const suggestionIndex = suggestionMatch ? Number(suggestionMatch[1]) : -1;
|
|
266
328
|
const selectedSuggestion = Number.isSafeInteger(suggestionIndex)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentEvent, SessionInteractionResolution, TerminalCommandData, TodoItem } from "@rynx-ai/core";
|
|
2
2
|
import type { ResolveInteractionResult, RuntimeInteractionEvent } from "../interactions.js";
|
|
3
|
+
import { type ClaudeRunnerStatus } from "./session-status.js";
|
|
3
4
|
/** Mirror sink — the same shape as `CodexForwarderSink` so the host reuses one
|
|
4
5
|
* per-turn normalizer wiring for both runtimes. */
|
|
5
6
|
export interface ClaudeForwarderSink {
|
|
@@ -20,14 +21,22 @@ export interface ClaudeForwarderSink {
|
|
|
20
21
|
onInteraction(event: RuntimeInteractionEvent): void;
|
|
21
22
|
/** The current turn finished (a new user prompt, or the inactivity backstop).
|
|
22
23
|
* `usage` carries the latest statusLine context/cost snapshot, when captured. */
|
|
23
|
-
onTurnEnd(usage?: Record<string, unknown
|
|
24
|
-
/** The
|
|
24
|
+
onTurnEnd(usage?: Record<string, unknown>, backgroundTaskCount?: number): void;
|
|
25
|
+
/** The current turn ended because the user explicitly interrupted it. */
|
|
26
|
+
onTurnInterrupted?(usage?: Record<string, unknown>): void;
|
|
27
|
+
/** Escape was sent for the open Turn. Publish cancelled UI state immediately;
|
|
28
|
+
* the final close remains delayed so a late transcript record can join it. */
|
|
29
|
+
onTurnInterruptRequested?(): void;
|
|
30
|
+
/** The runtime status changed according to Claude's session metadata. */
|
|
31
|
+
onStatus?(status: ClaudeRunnerStatus, blockedOn?: string): void;
|
|
32
|
+
/** The runtime went idle on the legacy hook fallback — surface idle WITHOUT finalizing
|
|
25
33
|
* the turn. claude fires Stop around the same time it flushes the final
|
|
26
34
|
* assistant record and the two orderings race; finalizing here would split a
|
|
27
35
|
* late assistant record into its own turn. The turn is finalized by the next
|
|
28
36
|
* user prompt or the inactivity backstop, so a late record still joins it. */
|
|
29
|
-
onIdle(): void;
|
|
30
|
-
/**
|
|
37
|
+
onIdle(backgroundTaskCount?: number): void;
|
|
38
|
+
/** Claude's current Turn or native runtime failed (StopFailure/pane exit).
|
|
39
|
+
* May fire between Turns so Session-level failure can retire stale liveness. */
|
|
31
40
|
onTurnError(error: Error): void;
|
|
32
41
|
/** Fired once SessionStart reveals claude's session id + transcript path, so
|
|
33
42
|
* the host can persist the id and release its readiness gate. */
|
|
@@ -99,12 +108,26 @@ export declare class ClaudeLiveSession {
|
|
|
99
108
|
private readonly seenClaudeSessionIds;
|
|
100
109
|
private turnOpen;
|
|
101
110
|
private currentTurnId?;
|
|
111
|
+
/** The open turn received an explicit Escape/Stop and must close cancelled. */
|
|
112
|
+
private turnInterrupted;
|
|
102
113
|
/** The Turn opened from a pre-transcript interaction. Its unique provisional
|
|
103
114
|
* id keeps responses distinct until the real prompt uuid can be adopted. */
|
|
104
115
|
private syntheticTurn;
|
|
105
116
|
private lastActivityAt;
|
|
106
117
|
/** When the Stop hook fired (null = not yet) — drives the grace-period close. */
|
|
107
118
|
private stopPendingAt;
|
|
119
|
+
/** Authoritative live background-shell count from the pending Stop hook. */
|
|
120
|
+
private stopBackgroundTaskCount;
|
|
121
|
+
/** Whether the pending Stop count already rode an immediate idle edge. */
|
|
122
|
+
private stopBackgroundTaskCountDelivered;
|
|
123
|
+
/** An idle status from Claude's session metadata. The short delay keeps a
|
|
124
|
+
* final transcript record in the response without letting stale tool state
|
|
125
|
+
* override the provider's terminal status. */
|
|
126
|
+
private providerIdleAt;
|
|
127
|
+
private statusPoller?;
|
|
128
|
+
/** A failed hook is authoritative until the next running edge. Claude writes
|
|
129
|
+
* idle after failures too, so that trailing file update must not erase it. */
|
|
130
|
+
private providerFailureSticky;
|
|
108
131
|
/** Open tool-call ids (start seen, end not yet) — suppresses the idle backstop. */
|
|
109
132
|
private readonly openToolIds;
|
|
110
133
|
/** Transcript-backed acknowledgements for web→TUI input. A direct idle
|
|
@@ -186,6 +209,11 @@ export declare class ClaudeLiveSession {
|
|
|
186
209
|
/** One poll cycle (hooks → transcript → idle backstop). Exposed for tests to
|
|
187
210
|
* drive deterministically; the async {@link loop} just calls it on an interval. */
|
|
188
211
|
tick(): void;
|
|
212
|
+
/** Bind Claude's per-process session metadata after the terminal exists. */
|
|
213
|
+
attachStatusSource({ panePid, configDir, }: {
|
|
214
|
+
panePid: () => number | undefined;
|
|
215
|
+
configDir?: string;
|
|
216
|
+
}): void;
|
|
189
217
|
private loop;
|
|
190
218
|
private pollHooks;
|
|
191
219
|
private handleHook;
|
|
@@ -255,6 +283,8 @@ export declare class ClaudeLiveSession {
|
|
|
255
283
|
/** Refresh the latest statusLine context/cost snapshot (a last-writer-wins file
|
|
256
284
|
* the statusLine hook overwrites on every TUI render). */
|
|
257
285
|
private pollStatus;
|
|
286
|
+
private pollSessionStatus;
|
|
287
|
+
private handleSessionStatus;
|
|
258
288
|
/** A usage record from the latest statusLine snapshot (snake_case, the keys the
|
|
259
289
|
* normalizer's `turn_completed` + the web `readUsageTokens` read), or undefined
|
|
260
290
|
* if the statusLine hook has not fired yet. */
|
|
@@ -283,6 +313,10 @@ export declare class ClaudeLiveSession {
|
|
|
283
313
|
* sent an Escape — an Escape into idle claude submits an empty turn, which
|
|
284
314
|
* claude answers with a stray "No response requested." bubble. */
|
|
285
315
|
isTurnOpen(): boolean;
|
|
316
|
+
/** Fail an open turn exactly once when its native terminal/runner disappears. */
|
|
317
|
+
failOpenTurn(error: Error): boolean;
|
|
318
|
+
/** Retire process-scoped metadata before classifying a terminal exit. */
|
|
319
|
+
noteTerminalExit(error: Error): boolean;
|
|
286
320
|
private closeTurn;
|
|
287
321
|
private closeTurnError;
|
|
288
322
|
private resetMessageCorrelation;
|
|
@@ -292,6 +326,8 @@ export declare class ClaudeLiveSession {
|
|
|
292
326
|
* {@link import("../terminal/tmux.js").TmuxTerminal}). The runner-child hands
|
|
293
327
|
* this to the host after launching the pane, since the host doesn't own tmux. */
|
|
294
328
|
export interface TerminalInjector {
|
|
329
|
+
/** PID of the process owning the tmux pane, when available. */
|
|
330
|
+
panePid?(): number | undefined;
|
|
295
331
|
capturePane(): string;
|
|
296
332
|
clearInputLine(): void;
|
|
297
333
|
paste(text: string): void;
|
|
@@ -303,12 +339,16 @@ export interface InjectViaTerminalOptions {
|
|
|
303
339
|
promptGlyph?: string;
|
|
304
340
|
promptTimeoutMs?: number;
|
|
305
341
|
settleMs?: number;
|
|
342
|
+
/** Wait for the pasted draft to become visible before the first Enter. */
|
|
343
|
+
pasteCommitMs?: number;
|
|
306
344
|
/** Transcript-backed proof that Claude accepted this exact input. When
|
|
307
|
-
*
|
|
345
|
+
* present it takes precedence over pane heuristics. */
|
|
308
346
|
submissionObserved?: () => boolean;
|
|
309
|
-
/**
|
|
347
|
+
/** Total post-Enter verification budget. */
|
|
310
348
|
submitConfirmMs?: number;
|
|
311
|
-
/**
|
|
349
|
+
/** Minimum delay between Enter retries while the draft remains visible. */
|
|
350
|
+
submitRetryMs?: number;
|
|
351
|
+
/** Optional safety cap for tests/callers; the time budget remains authoritative. */
|
|
312
352
|
maxSubmitAttempts?: number;
|
|
313
353
|
pollMs?: number;
|
|
314
354
|
now?: () => number;
|
|
@@ -324,8 +364,8 @@ export interface InjectViaTerminalOptions {
|
|
|
324
364
|
*
|
|
325
365
|
* THROWS if the prompt never appears within the ready-gate window (reference implementation
|
|
326
366
|
* `_wait_for_claude_prompt_ready` RAISE) — a not-ready pane is a hard error the
|
|
327
|
-
* caller reports, NOT a signal to fall through to a second output path.
|
|
328
|
-
*
|
|
329
|
-
* neither a direct user prompt nor a type-ahead enqueue.
|
|
367
|
+
* caller reports, NOT a signal to fall through to a second output path. Enter
|
|
368
|
+
* is retried only while the exact draft remains visible and Claude has durably
|
|
369
|
+
* recorded neither a direct user prompt nor a type-ahead enqueue.
|
|
330
370
|
*/
|
|
331
371
|
export declare function injectViaTerminal(injector: TerminalInjector, text: string, opts?: InjectViaTerminalOptions): Promise<boolean>;
|