dsh-ntr 0.3.1 → 0.3.2
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.en.md +1 -1
- package/README.md +1 -1
- package/cordis.patch.yml +17 -1
- package/lib/bridge.js +607 -18
- package/lib/client/acp-ui.js +103 -3
- package/lib/client/acp-view.js +24 -4
- package/lib/client/branch-action.js +133 -0
- package/lib/client/composer-mode.js +48 -13
- package/lib/client/context-meter.js +86 -0
- package/lib/client/index.js +238 -15
- package/lib/client/panel.js +96 -29
- package/lib/client/released-banner.js +66 -0
- package/lib/client/session-controls.js +38 -13
- package/lib/client/slash-isolation.js +2 -2
- package/lib/client/store.js +348 -13
- package/lib/client.js +1138 -67
- package/lib/client.js.map +1 -1
- package/lib/impl.js +54 -1
- package/lib/index.js +7 -0
- package/lib/protocol.js +19 -0
- package/lib/types/bridge.d.ts +229 -6
- package/lib/types/client/acp-ui.d.ts +34 -2
- package/lib/types/client/acp-view.d.ts +13 -2
- package/lib/types/client/branch-action.d.ts +40 -0
- package/lib/types/client/composer-mode.d.ts +28 -0
- package/lib/types/client/context-meter.d.ts +51 -0
- package/lib/types/client/released-banner.d.ts +34 -0
- package/lib/types/client/slash-isolation.d.ts +1 -1
- package/lib/types/client/store.d.ts +98 -1
- package/lib/types/impl.d.ts +41 -1
- package/lib/types/protocol.d.ts +123 -0
- package/package.json +2 -1
- package/src/bridge.ts +638 -20
- package/src/client/acp-ui.tsx +123 -2
- package/src/client/acp-view.tsx +30 -5
- package/src/client/branch-action.tsx +160 -0
- package/src/client/composer-mode.tsx +59 -13
- package/src/client/context-meter.tsx +128 -0
- package/src/client/index.tsx +265 -14
- package/src/client/panel.tsx +157 -40
- package/src/client/primitives.d.ts +27 -0
- package/src/client/released-banner.tsx +97 -0
- package/src/client/session-controls.tsx +34 -11
- package/src/client/slash-isolation.ts +2 -1
- package/src/client/store.ts +375 -14
- package/src/impl.ts +84 -2
- package/src/index.ts +8 -0
- package/src/protocol.ts +121 -0
package/lib/impl.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* fakes.
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from "./ids.js";
|
|
8
|
-
import { DEFAULT_TAG_HALO } from "./protocol.js";
|
|
8
|
+
import { DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_TAG_HALO, IDLE_RELEASE_MS } from "./protocol.js";
|
|
9
9
|
export { randomUUID };
|
|
10
10
|
/** Structured failure the RPC layer converts to `{ok:false, error}` and `apply()` surfaces at boot. */
|
|
11
11
|
export class AcpError extends Error {
|
|
@@ -158,6 +158,20 @@ export function validateApprovalDeadlines(value) {
|
|
|
158
158
|
}
|
|
159
159
|
return out;
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Validate the idle-release window the settings page writes.
|
|
163
|
+
*
|
|
164
|
+
* The window is expressed in MILLISECONDS on the shared ladder
|
|
165
|
+
* ({@link IDLE_RELEASE_MS}), so a value the slider can produce is always a
|
|
166
|
+
* value the host runs — the same one-ladder rule the approval deadline card
|
|
167
|
+
* follows.
|
|
168
|
+
*/
|
|
169
|
+
export function validateIdleReleaseMs(raw, code) {
|
|
170
|
+
if (typeof raw !== 'number' || !Number.isInteger(raw) || !IDLE_RELEASE_MS.includes(raw)) {
|
|
171
|
+
throw new AcpError(code, `acp: idleTimeoutMs must be one of ${IDLE_RELEASE_MS.join(', ')}`);
|
|
172
|
+
}
|
|
173
|
+
return raw;
|
|
174
|
+
}
|
|
161
175
|
/**
|
|
162
176
|
* Validate the mode a write asks for. Three values, and the list is the whole
|
|
163
177
|
* vocabulary: an unanswered ask is either answered for you, kept waiting, or
|
|
@@ -502,6 +516,14 @@ export function validateConfig(raw) {
|
|
|
502
516
|
// `abort` a zero window would refuse every ask on arrival — the agent stops
|
|
503
517
|
// at every gate — so it becomes the shortest real wait.
|
|
504
518
|
const effectivePermissionTimeoutMs = normalizeApprovalWindow(permissionTimeoutMode, permissionTimeoutMs);
|
|
519
|
+
// The chat-process idle-release window (the settings page edits it).
|
|
520
|
+
// Same shape as the per-agent field: 0 = never release, otherwise an
|
|
521
|
+
// integer number of milliseconds.
|
|
522
|
+
const idleTimeoutMs = value.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
523
|
+
if (typeof idleTimeoutMs !== 'number' || !Number.isInteger(idleTimeoutMs)
|
|
524
|
+
|| idleTimeoutMs > 86_400_000 || (idleTimeoutMs !== 0 && idleTimeoutMs < 1_000)) {
|
|
525
|
+
throw new AcpError('bad_config', 'acp: idleTimeoutMs must be 0 (never release) or an integer in [1000, 86400000]');
|
|
526
|
+
}
|
|
505
527
|
const cancelFallbackMs = value.cancelFallbackMs ?? 15_000;
|
|
506
528
|
if (typeof cancelFallbackMs !== 'number' || !Number.isInteger(cancelFallbackMs) || cancelFallbackMs < 0 || cancelFallbackMs > 600_000) {
|
|
507
529
|
throw new AcpError('bad_config', 'acp: cancelFallbackMs must be an integer in [0, 600000] (0 = disable the fallback)');
|
|
@@ -543,6 +565,7 @@ export function validateConfig(raw) {
|
|
|
543
565
|
turnStallMs,
|
|
544
566
|
turnStallToolMs,
|
|
545
567
|
turnStallTickMs,
|
|
568
|
+
idleTimeoutMs,
|
|
546
569
|
};
|
|
547
570
|
}
|
|
548
571
|
// ---- Workspace guard ----
|
|
@@ -1733,6 +1756,26 @@ export async function dispatchRpc(manager, endpoint, payload) {
|
|
|
1733
1756
|
await manager.setChatSessionMode?.(dshSessionId, modeId, optionalStr(body, 'agentId', 64));
|
|
1734
1757
|
return ok({ done: true });
|
|
1735
1758
|
}
|
|
1759
|
+
case 'session/chat-resume': {
|
|
1760
|
+
// The released-session banner's explicit resume: the recovery the next
|
|
1761
|
+
// prompt performs lazily, on the user's gesture instead. Addressed by
|
|
1762
|
+
// DSH chat session id, like the rest of the conversation surfaces.
|
|
1763
|
+
const dshSessionId = str(body, 'dshSessionId', 128);
|
|
1764
|
+
await manager.resumeChatSession?.(dshSessionId);
|
|
1765
|
+
return ok({ done: true });
|
|
1766
|
+
}
|
|
1767
|
+
case 'session/chat-fork-adopt': {
|
|
1768
|
+
// The second half of a branched conversation: bind the DSH session the
|
|
1769
|
+
// host's `sessions.fork` just created to the agent's own `session/fork`
|
|
1770
|
+
// of the parent's live ACP session. Both ids are validated before the
|
|
1771
|
+
// optional call — the same shape rule as `session/chat-list` above.
|
|
1772
|
+
// The adopted ids ride the answer (the protocol's ChatForkAdoptResult);
|
|
1773
|
+
// a manager without the method keeps the plain `done` shape.
|
|
1774
|
+
const dshSessionId = str(body, 'dshSessionId', 128);
|
|
1775
|
+
const parentDshSessionId = str(body, 'parentDshSessionId', 128);
|
|
1776
|
+
const adopted = await manager.adoptForkedSession?.(dshSessionId, parentDshSessionId);
|
|
1777
|
+
return ok(adopted ?? { done: true });
|
|
1778
|
+
}
|
|
1736
1779
|
case 'session/chat-bind': {
|
|
1737
1780
|
// Point one chat conversation at an ACP agent. The plugin's own
|
|
1738
1781
|
// conversation↔agent mapping — NOT a DSH model route (see
|
|
@@ -1820,6 +1863,16 @@ export async function dispatchRpc(manager, endpoint, payload) {
|
|
|
1820
1863
|
await manager.setApprovalDeadlines?.(patch, body.reset === true);
|
|
1821
1864
|
return ok({ deadlines: manager.approvalDeadlines?.() });
|
|
1822
1865
|
}
|
|
1866
|
+
case 'ui/idle-release':
|
|
1867
|
+
// Absent on a host half from before the tab existed: the browser then
|
|
1868
|
+
// renders nothing rather than a number it cannot trust.
|
|
1869
|
+
return ok({ idleRelease: manager.idleRelease?.() });
|
|
1870
|
+
case 'ui/idle-release-set': {
|
|
1871
|
+
const reset = body.reset === true;
|
|
1872
|
+
const idleTimeoutMs = reset ? undefined : validateIdleReleaseMs(body.idleTimeoutMs, 'bad_payload');
|
|
1873
|
+
await manager.setIdleRelease?.(idleTimeoutMs, reset);
|
|
1874
|
+
return ok({ idleRelease: manager.idleRelease?.() });
|
|
1875
|
+
}
|
|
1823
1876
|
case 'agent/set-exposed-models': {
|
|
1824
1877
|
const agentId = str(body, 'agentId', 64);
|
|
1825
1878
|
const raw = body.exposedModels;
|
package/lib/index.js
CHANGED
|
@@ -32,6 +32,7 @@ export function apply(ctx, config) {
|
|
|
32
32
|
turnStallMs: resolved.turnStallMs,
|
|
33
33
|
turnStallToolMs: resolved.turnStallToolMs,
|
|
34
34
|
turnStallTickMs: resolved.turnStallTickMs,
|
|
35
|
+
idleTimeoutMs: resolved.idleTimeoutMs,
|
|
35
36
|
});
|
|
36
37
|
const adapter = new AcpLlmAdapter(manager);
|
|
37
38
|
// Built-in update: the plugin never downloads or swaps files itself. The
|
|
@@ -66,6 +67,12 @@ export function apply(ctx, config) {
|
|
|
66
67
|
? rawConfig.permissionTimeoutMode
|
|
67
68
|
: undefined;
|
|
68
69
|
manager.setConfiguredApprovalDeadlines(configuredDeadlines, configuredMode);
|
|
70
|
+
// The idle-release baseline the settings page's pick outranks. Only the
|
|
71
|
+
// raw value counts: `validateConfig` fills a default in, so the
|
|
72
|
+
// validated number cannot tell "the file says 10 minutes" from
|
|
73
|
+
// "the file says nothing, so 30 minutes applies".
|
|
74
|
+
const configuredIdleTimeoutMs = typeof rawConfig.idleTimeoutMs === 'number' ? rawConfig.idleTimeoutMs : undefined;
|
|
75
|
+
manager.setConfiguredIdleRelease(configuredIdleTimeoutMs);
|
|
69
76
|
// Boot hook kept only so this entry point's shape does not change:
|
|
70
77
|
// `bootstrapCatalogs` is intentionally empty — boot never spawns an agent,
|
|
71
78
|
// in any environment and for any `probeModelsOnBoot` value. Catalogs fill
|
package/lib/protocol.js
CHANGED
|
@@ -17,3 +17,22 @@
|
|
|
17
17
|
*/
|
|
18
18
|
/** Default when nothing was ever saved: the breathing halo at normal speed. */
|
|
19
19
|
export const DEFAULT_TAG_HALO = { mode: 'breath', speed: 'normal' };
|
|
20
|
+
/**
|
|
21
|
+
* The idle-release windows the settings page offers, in MILLISECONDS.
|
|
22
|
+
*
|
|
23
|
+
* ONE ladder, shared by both halves: the browser snaps its slider to these
|
|
24
|
+
* values and the host accepts only these values, so a window the slider can
|
|
25
|
+
* produce is always a window the host runs. The first rung is 1 minute (an
|
|
26
|
+
* aggressive release), the rest are 5-minute steps up to an hour.
|
|
27
|
+
*/
|
|
28
|
+
export const IDLE_RELEASE_MS = [
|
|
29
|
+
60_000, 300_000, 600_000, 900_000, 1_200_000, 1_500_000, 1_800_000, 2_100_000, 2_400_000, 2_700_000, 3_000_000, 3_300_000, 3_600_000,
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* The built-in idle-release window for chat processes: 30 minutes.
|
|
33
|
+
*
|
|
34
|
+
* Lives beside the ladder it is a rung of, so the two cannot drift: a
|
|
35
|
+
* deployment that names no window of its own runs this, and the settings
|
|
36
|
+
* page's reset returns to it.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_IDLE_TIMEOUT_MS = 1_800_000;
|
package/lib/types/bridge.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { type InitializeResponse } from '@agentclientprotocol/sdk';
|
|
10
10
|
import { EventHub, PermissionLedger, Transcript } from './impl.ts';
|
|
11
11
|
import type { AcpToolCallSnapshot, PendingPermissionView, RosterPresetRow } from './impl.ts';
|
|
12
|
-
import type { AcpAgentCatalog, AcpAgentDef, AcpApprovalDeadlines, AcpConversationChoice, AcpPresetStatus, AcpReleaseCause, AcpTagHalo, AgentCatalogResult, AgentProbeModelsResult, AgentTestResult, AgentView, ChatSessionView, ChatTranscriptView, DetectedAgentInfo, GenerateOptions, LlmAdapterLike, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, ApprovalDeadlineKey, ApprovalDeadlinePatch, PermissionTimeoutMode, PluginUpdateState, PreparedAdapterCall, SessionView, StreamChunk } from './protocol.ts';
|
|
12
|
+
import type { AcpAgentCatalog, AcpAgentDef, AcpApprovalDeadlines, AcpConversationChoice, AcpPresetStatus, AcpReleaseCause, AcpTagHalo, AgentCatalogResult, AgentProbeModelsResult, AgentTestResult, AgentView, ChatSessionView, ChatTranscriptView, DetectedAgentInfo, GenerateOptions, LlmAdapterLike, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, AcpIdleRelease, ApprovalDeadlineKey, ApprovalDeadlinePatch, PermissionTimeoutMode, PluginUpdateState, PreparedAdapterCall, SessionView, StreamChunk } from './protocol.ts';
|
|
13
13
|
import { UpdateService } from './update.ts';
|
|
14
14
|
/**
|
|
15
15
|
* Version of this installed package, read from the shipped `package.json` —
|
|
@@ -45,6 +45,15 @@ interface LiveSession {
|
|
|
45
45
|
closed: boolean;
|
|
46
46
|
/** The agent process died; the session awaits a `session/load` resume. */
|
|
47
47
|
released: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The agent-side session has history while the LOCAL buffer is empty: this
|
|
50
|
+
* record was restored from disk after a host restart (the replay has not
|
|
51
|
+
* landed yet), or a resume cleared the buffer ahead of the replay. The ACP
|
|
52
|
+
* view then says history replays on restore instead of "nothing yet". Set
|
|
53
|
+
* by `doResumeSession` (captured before the reset) and the restore path; the
|
|
54
|
+
* serializer ORs it with the live buffer length.
|
|
55
|
+
*/
|
|
56
|
+
hasHistory?: boolean;
|
|
48
57
|
/**
|
|
49
58
|
* When the agent last showed ANY sign of life on this session: an update, a
|
|
50
59
|
* permission ask (arrival or answer), or a turn start. The stall watchdog
|
|
@@ -279,6 +288,20 @@ export declare class AgentBridge {
|
|
|
279
288
|
* this bridge was built.
|
|
280
289
|
*/
|
|
281
290
|
private readonly sessionWorkspace?;
|
|
291
|
+
/**
|
|
292
|
+
* Told whenever the SESSION RECORDS change shape (a release parks them, a
|
|
293
|
+
* resume revives them): the manager persists `chatSessions` through it, so
|
|
294
|
+
* a parked conversation stays resumable across a host restart (see
|
|
295
|
+
* `BridgeManager.loadPersistedChatSessions`).
|
|
296
|
+
*/
|
|
297
|
+
private readonly onSessionsDirty?;
|
|
298
|
+
/**
|
|
299
|
+
* Told whenever a fresh `initialize` reports the agent's
|
|
300
|
+
* `sessionCapabilities.fork` bit — the LAST KNOWN fork capability the
|
|
301
|
+
* manager persists and serves to the browser (the bridge's own
|
|
302
|
+
* `initResult` dies with the process; the manager's copy survives it).
|
|
303
|
+
*/
|
|
304
|
+
private readonly onForkCapability?;
|
|
282
305
|
state: BridgeState;
|
|
283
306
|
initResult?: InitializeResponse;
|
|
284
307
|
lastError?: string;
|
|
@@ -309,7 +332,7 @@ export declare class AgentBridge {
|
|
|
309
332
|
private readonly staleCancelRetryDelayMs;
|
|
310
333
|
private readonly staleCancelMaxRetries;
|
|
311
334
|
private readonly turnSettleMs;
|
|
312
|
-
private
|
|
335
|
+
private defaultIdleTimeoutMs;
|
|
313
336
|
private readonly probeIdleTimeoutMs;
|
|
314
337
|
/** Armed while READY; fires the idle-release check. */
|
|
315
338
|
private idleTimer?;
|
|
@@ -330,6 +353,13 @@ export declare class AgentBridge {
|
|
|
330
353
|
* that a caller is waiting on the child it is about to kill.
|
|
331
354
|
*/
|
|
332
355
|
private pendingCreates;
|
|
356
|
+
/**
|
|
357
|
+
* `session/fork` requests in flight (the adopt path of a branched
|
|
358
|
+
* conversation). Same teardown hazard as {@link pendingCreates}: until the
|
|
359
|
+
* forked session is registered, nothing else would stop an idle release
|
|
360
|
+
* from killing the child out from under the caller.
|
|
361
|
+
*/
|
|
362
|
+
private pendingForks;
|
|
333
363
|
/**
|
|
334
364
|
* Whether a REAL session has ever been registered on this bridge. Probes
|
|
335
365
|
* bypass session bookkeeping, so a bridge that has never hosted anything was
|
|
@@ -366,7 +396,21 @@ export declare class AgentBridge {
|
|
|
366
396
|
* Resolved per turn: the host entry may wire the workspace reader after
|
|
367
397
|
* this bridge was built.
|
|
368
398
|
*/
|
|
369
|
-
sessionWorkspace?: (() => SessionWorkspaceResolver | undefined) | undefined
|
|
399
|
+
sessionWorkspace?: (() => SessionWorkspaceResolver | undefined) | undefined,
|
|
400
|
+
/**
|
|
401
|
+
* Told whenever the SESSION RECORDS change shape (a release parks them, a
|
|
402
|
+
* resume revives them): the manager persists `chatSessions` through it, so
|
|
403
|
+
* a parked conversation stays resumable across a host restart (see
|
|
404
|
+
* `BridgeManager.loadPersistedChatSessions`).
|
|
405
|
+
*/
|
|
406
|
+
onSessionsDirty?: (() => void) | undefined,
|
|
407
|
+
/**
|
|
408
|
+
* Told whenever a fresh `initialize` reports the agent's
|
|
409
|
+
* `sessionCapabilities.fork` bit — the LAST KNOWN fork capability the
|
|
410
|
+
* manager persists and serves to the browser (the bridge's own
|
|
411
|
+
* `initResult` dies with the process; the manager's copy survives it).
|
|
412
|
+
*/
|
|
413
|
+
onForkCapability?: ((agentId: string, supported: boolean) => void) | undefined);
|
|
370
414
|
/**
|
|
371
415
|
* Adopt a rewritten approval deadline or mode (the settings page's write).
|
|
372
416
|
* Only the window and the mode live here; every other tuning value is
|
|
@@ -381,6 +425,18 @@ export declare class AgentBridge {
|
|
|
381
425
|
permissionTimeoutMs?: number;
|
|
382
426
|
mode?: PermissionTimeoutMode;
|
|
383
427
|
}): void;
|
|
428
|
+
/**
|
|
429
|
+
* Adopt a rewritten host-default idle window (the settings page's write).
|
|
430
|
+
*
|
|
431
|
+
* Only the DEFAULT moves: an agent that names its own `idleTimeoutMs` keeps
|
|
432
|
+
* it (that promise is about the agent, not about the deployment), and a
|
|
433
|
+
* probe-only child keeps the short probe window.
|
|
434
|
+
*
|
|
435
|
+
* The timer is re-armed so the new window governs from NOW: an already
|
|
436
|
+
* armed timer still holds the old window, so a shortened window would not
|
|
437
|
+
* take effect until the old one elapsed.
|
|
438
|
+
*/
|
|
439
|
+
applyIdleRelease(defaultIdleTimeoutMs: number): void;
|
|
384
440
|
/**
|
|
385
441
|
* The idle-release window this agent honors for its CHAT process: its own
|
|
386
442
|
* field, else the host default. This is the number the panel prefills and
|
|
@@ -559,6 +615,25 @@ export declare class AgentBridge {
|
|
|
559
615
|
* only shape that keeps every session of that process consistent.
|
|
560
616
|
*/
|
|
561
617
|
probeConfigOptions(): Promise<unknown[]>;
|
|
618
|
+
/**
|
|
619
|
+
* Fork one live session through the agent's own `session/fork` (the
|
|
620
|
+
* UNSTABLE fourth session-creation point): the agent copies its internal
|
|
621
|
+
* state into a NEW ACP session that remembers the parent's context — the
|
|
622
|
+
* second half of a branched conversation, after the host's `sessions.fork`
|
|
623
|
+
* has already built the DSH session tree. The child is addressed by the
|
|
624
|
+
* CHILD DSH conversation id, so its first message reaches an agent that
|
|
625
|
+
* actually remembers the parent.
|
|
626
|
+
*
|
|
627
|
+
* The parent must be live on this connection — a released parent has no
|
|
628
|
+
* process to fork from (the browser blocks that click; this is the hard
|
|
629
|
+
* stop behind it). The ACP request carries the SAME `mcpServers` list as
|
|
630
|
+
* `session/new` / `session/load` (one list per process; an empty list would
|
|
631
|
+
* evict the live sessions' MCP scope) and is bounded by the startup timeout
|
|
632
|
+
* like every session-creation step. Counted in {@link pendingForks} while
|
|
633
|
+
* in flight so an idle release cannot kill the child out from under the
|
|
634
|
+
* caller.
|
|
635
|
+
*/
|
|
636
|
+
forkSession(parent: LiveSession, dshSessionId: string): Promise<LiveSession>;
|
|
562
637
|
/**
|
|
563
638
|
* Fire one prompt turn. Returns after the agent settles the turn; the caller
|
|
564
639
|
* (manager) keeps the UI informed through broadcast events, so the RPC that
|
|
@@ -617,6 +692,12 @@ export declare class AgentBridge {
|
|
|
617
692
|
takeReleased(sessionId: string): LiveSession | undefined;
|
|
618
693
|
/** Drop a released session record for good (user closed it or the agent was stopped). */
|
|
619
694
|
dropReleased(sessionId: string): boolean;
|
|
695
|
+
/**
|
|
696
|
+
* Register a session record restored from disk as already parked: a host
|
|
697
|
+
* restart killed every process, so it comes back exactly as `releaseAll`
|
|
698
|
+
* would have left it — resumable via `session/load`, never live.
|
|
699
|
+
*/
|
|
700
|
+
parkReleased(session: LiveSession): void;
|
|
620
701
|
get releasedList(): LiveSession[];
|
|
621
702
|
hasReleased(sessionId: string): LiveSession | undefined;
|
|
622
703
|
inflightResume(sessionId: string): Promise<LiveSession> | undefined;
|
|
@@ -1131,6 +1212,29 @@ export declare class BridgeManager {
|
|
|
1131
1212
|
* write would fight with no way to tell which one is live.
|
|
1132
1213
|
*/
|
|
1133
1214
|
private deadlineOverride;
|
|
1215
|
+
/**
|
|
1216
|
+
* The idle-release window the settings page picked, in ms, persisted
|
|
1217
|
+
* with the agent registry. `undefined` means the deployment baseline runs.
|
|
1218
|
+
*
|
|
1219
|
+
* Merged OVER {@link baselineIdleTimeoutMs} exactly like
|
|
1220
|
+
* {@link deadlineOverride} over {@link baselineDeadline}: a UI pick is what
|
|
1221
|
+
* runs, and `cordis.patch.yml` becomes the value the reset button returns
|
|
1222
|
+
* to. Without this split the two sources would fight with no way to tell
|
|
1223
|
+
* which one is live.
|
|
1224
|
+
*/
|
|
1225
|
+
private idleReleaseOverride?;
|
|
1226
|
+
/**
|
|
1227
|
+
* The window `cordis.patch.yml` names, when it names one. Kept apart
|
|
1228
|
+
* from the override for the same reason the deadline keeps its baseline
|
|
1229
|
+
* apart: the settings card has to be able to say "the file asks for
|
|
1230
|
+
* 10 minutes, the value set here wins".
|
|
1231
|
+
*/
|
|
1232
|
+
private configuredIdleTimeoutMs?;
|
|
1233
|
+
/**
|
|
1234
|
+
* The idle-release window before any override — the tuning this manager was
|
|
1235
|
+
* constructed with (the deployment baseline, or the built-in 30 minutes).
|
|
1236
|
+
*/
|
|
1237
|
+
private baselineIdleTimeoutMs;
|
|
1134
1238
|
/**
|
|
1135
1239
|
* Tuning in force, defaults applied. The approval deadline is rewritten
|
|
1136
1240
|
* IN PLACE by {@link applyDeadlineTuning}, which is why this is a live object
|
|
@@ -1238,6 +1342,29 @@ export declare class BridgeManager {
|
|
|
1238
1342
|
* keeps the two controls from reading as duplicates.
|
|
1239
1343
|
*/
|
|
1240
1344
|
chatSessions(dshSessionId: string): ChatSessionView[];
|
|
1345
|
+
/**
|
|
1346
|
+
* Adopt the ACP side of a branched conversation: the browser has already
|
|
1347
|
+
* asked the HOST to fork the DSH session tree (`sessions.fork`), and this
|
|
1348
|
+
* runs the agent's own `session/fork` of the parent conversation's live ACP
|
|
1349
|
+
* session so the child actually remembers the parent context — then binds
|
|
1350
|
+
* the child to it (the fourth session-creation point, with every discipline
|
|
1351
|
+
* the other three carry).
|
|
1352
|
+
*
|
|
1353
|
+
* Failure is deliberately all-or-nothing: nothing is written for the child
|
|
1354
|
+
* until the agent has answered, so a failed adopt leaves the child an
|
|
1355
|
+
* ordinary copied-history conversation whose first message starts a fresh
|
|
1356
|
+
* `session/new` (today's behavior) — never a half-bound record.
|
|
1357
|
+
*
|
|
1358
|
+
* @param childDshSessionId - the DSH session the host fork created.
|
|
1359
|
+
* @param parentDshSessionId - the conversation being branched; its ACP
|
|
1360
|
+
* session must be LIVE on this connection (a released parent has no
|
|
1361
|
+
* process to fork from — the browser blocks the click, this is the hard
|
|
1362
|
+
* stop behind it).
|
|
1363
|
+
*/
|
|
1364
|
+
adoptForkedSession(childDshSessionId: string, parentDshSessionId: string): Promise<{
|
|
1365
|
+
sessionId: string;
|
|
1366
|
+
acpSessionId: string;
|
|
1367
|
+
}>;
|
|
1241
1368
|
/**
|
|
1242
1369
|
* Bind one DSH chat session to an ACP agent.
|
|
1243
1370
|
*
|
|
@@ -1391,11 +1518,26 @@ export declare class BridgeManager {
|
|
|
1391
1518
|
*/
|
|
1392
1519
|
chatTranscripts(dshSessionId: string, afterSeq?: number): ChatTranscriptView[];
|
|
1393
1520
|
/**
|
|
1394
|
-
* Switch one chat-bound session's ACP mode.
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1521
|
+
* Switch one chat-bound session's ACP mode.
|
|
1522
|
+
*
|
|
1523
|
+
* A released session is deliberately NOT resumed for it (user decision,
|
|
1524
|
+
* 2026-10-30 — the composer's axes are "what the next turn should use", and
|
|
1525
|
+
* the process comes back only when the user sends a message or hits the
|
|
1526
|
+
* released banner's resume button). The pick is recorded and
|
|
1527
|
+
* `applyChatChoice` applies it on resume, exactly like `setChatSessionModel`
|
|
1528
|
+
* / `setChatSessionEffort` / `setChatSessionConfig`; only a LIVE session is
|
|
1529
|
+
* told immediately (`session/set_mode`).
|
|
1397
1530
|
*/
|
|
1398
1531
|
setChatSessionMode(dshSessionId: string, modeId: string, agentId?: string): Promise<void>;
|
|
1532
|
+
/**
|
|
1533
|
+
* Bring one chat conversation's released session back on a fresh process on
|
|
1534
|
+
* the USER's explicit gesture (the "恢复进程" banner) — the same work the
|
|
1535
|
+
* next prompt performs lazily (`getOrCreateDshSession`'s released branch),
|
|
1536
|
+
* minus the create path: a banner button must never materialize a session
|
|
1537
|
+
* the conversation has not had yet. A live target passes through untouched,
|
|
1538
|
+
* so a double click (or a click racing the next message) spawns nothing.
|
|
1539
|
+
*/
|
|
1540
|
+
resumeChatSession(dshSessionId: string): Promise<void>;
|
|
1399
1541
|
/**
|
|
1400
1542
|
* Wire the harness's preset roster (`ctx.agentPresets`) into `presetStatus`.
|
|
1401
1543
|
*
|
|
@@ -1451,7 +1593,30 @@ export declare class BridgeManager {
|
|
|
1451
1593
|
*/
|
|
1452
1594
|
private loadPersistedChoices;
|
|
1453
1595
|
private persistAgents;
|
|
1596
|
+
/**
|
|
1597
|
+
* Restore chat session records from disk as PARKED sessions.
|
|
1598
|
+
*
|
|
1599
|
+
* Every process died with the host, so nothing comes back live — but the
|
|
1600
|
+
* agent-side sessions did not. Restoring each record as `released` is what
|
|
1601
|
+
* makes the released banner, its resume button and the "next message
|
|
1602
|
+
* resumes it" promise survive a restart (the alternative is the next
|
|
1603
|
+
* message quietly starting a NEW, context-less session — constraint 10's
|
|
1604
|
+
* "released ≠ destroyed" lost at the process boundary). Tolerant, one row
|
|
1605
|
+
* at a time: a single corrupt entry must not cost every other conversation
|
|
1606
|
+
* its context. Records whose agent is no longer configured are dropped —
|
|
1607
|
+
* there is nothing to resume ON, and the next write omits them.
|
|
1608
|
+
*/
|
|
1609
|
+
private loadPersistedChatSessions;
|
|
1454
1610
|
private readonly catalogs;
|
|
1611
|
+
/**
|
|
1612
|
+
* Each agent's LAST KNOWN `sessionCapabilities.fork` bit, persisted with the
|
|
1613
|
+
* model catalog. A live `initialize` result outranks it whenever one exists
|
|
1614
|
+
* (see {@link canForkNow}) — this copy answers when the process is gone
|
|
1615
|
+
* (a released session's branch button must not flash into "supported" just
|
|
1616
|
+
* because `initResult` died with it, nor read `view().agentCapabilities`,
|
|
1617
|
+
* which a release clears).
|
|
1618
|
+
*/
|
|
1619
|
+
private readonly forkCaps;
|
|
1455
1620
|
/**
|
|
1456
1621
|
* The slash commands each agent declared in its most recent session, cached
|
|
1457
1622
|
* per agent and persisted alongside the model catalog.
|
|
@@ -1520,6 +1685,21 @@ export declare class BridgeManager {
|
|
|
1520
1685
|
* restatement would make every open composer re-read `agent/catalog`.
|
|
1521
1686
|
*/
|
|
1522
1687
|
private absorbCommands;
|
|
1688
|
+
/**
|
|
1689
|
+
* Record a fresh `initialize`'s `sessionCapabilities.fork` verdict (the
|
|
1690
|
+
* bridge reports it right after the handshake). Persisted with the model
|
|
1691
|
+
* catalog; no broadcast — the browser learns it through `session/chat-list`
|
|
1692
|
+
* rows, and its branch button deliberately runs on a last-known basis (the
|
|
1693
|
+
* agent's own next `initialize` is the refresh).
|
|
1694
|
+
*/
|
|
1695
|
+
private noteForkCapability;
|
|
1696
|
+
/**
|
|
1697
|
+
* Whether an agent's fork can be attempted RIGHT NOW: a live `initialize`
|
|
1698
|
+
* answer wins (the process is up, its handshake is current); only when the
|
|
1699
|
+
* process is gone does the LAST KNOWN persisted bit answer. `false` covers
|
|
1700
|
+
* "never seen" — an unknown agent simply never gets a fork attempt.
|
|
1701
|
+
*/
|
|
1702
|
+
private canForkNow;
|
|
1523
1703
|
/**
|
|
1524
1704
|
* Append models this observation newly introduced to the agent's selection.
|
|
1525
1705
|
*
|
|
@@ -1736,6 +1916,18 @@ export declare class BridgeManager {
|
|
|
1736
1916
|
/** The agent tag's halo preference (composer display, plugin-private). */
|
|
1737
1917
|
tagHalo(): AcpTagHalo;
|
|
1738
1918
|
setTagHalo(halo: AcpTagHalo): Promise<void>;
|
|
1919
|
+
/**
|
|
1920
|
+
* Declare the deployment baseline for the idle-release window — the value
|
|
1921
|
+
* `cordis.patch.yml` names, passed RAW (only when it names one).
|
|
1922
|
+
*
|
|
1923
|
+
* Kept separate from the validated tuning for the same reason the
|
|
1924
|
+
* deadline keeps its baseline apart: "the file says 10 minutes" and
|
|
1925
|
+
* "the file says nothing, so 30 minutes applies" are different facts.
|
|
1926
|
+
* The first is the reset target and shows as the fact being overridden;
|
|
1927
|
+
* the second is a built-in default. Called once by the host entry,
|
|
1928
|
+
* before any bridge exists.
|
|
1929
|
+
*/
|
|
1930
|
+
setConfiguredIdleRelease(idleTimeoutMs: number | undefined): void;
|
|
1739
1931
|
/**
|
|
1740
1932
|
* Declare the deployment baseline for the approval deadline — the values
|
|
1741
1933
|
* `cordis.patch.yml` names, passed RAW (only the keys it actually contains).
|
|
@@ -1758,6 +1950,37 @@ export declare class BridgeManager {
|
|
|
1758
1950
|
* stay silent about a plain default.
|
|
1759
1951
|
*/
|
|
1760
1952
|
approvalDeadlines(): AcpApprovalDeadlines;
|
|
1953
|
+
/**
|
|
1954
|
+
* The idle-release window in force, plus the facts that explain a value the
|
|
1955
|
+
* user did not pick: the deployment baseline an override is outranking.
|
|
1956
|
+
*
|
|
1957
|
+
* `defaultIdleTimeoutMs` is the window a RESET returns to — the deployment
|
|
1958
|
+
* baseline when the config file names one, else the built-in default. It is
|
|
1959
|
+
* NOT a hardcoded constant: a deployment that names 10 minutes must reset
|
|
1960
|
+
* to 10 minutes, not to the built-in 30.
|
|
1961
|
+
*
|
|
1962
|
+
* The config file's own number is reported only when the file names one, so
|
|
1963
|
+
* the settings card can stay silent about a plain default.
|
|
1964
|
+
*/
|
|
1965
|
+
idleRelease(): AcpIdleRelease;
|
|
1966
|
+
/**
|
|
1967
|
+
* Write the idle-release window, or drop the persisted override with
|
|
1968
|
+
* `reset` so the deployment baseline applies again.
|
|
1969
|
+
*
|
|
1970
|
+
* HOT: the rewrite reaches every live bridge, so a user changing the window
|
|
1971
|
+
* never has to kill a running session to see it take effect.
|
|
1972
|
+
*/
|
|
1973
|
+
setIdleRelease(idleTimeoutMs: number | undefined, reset?: boolean): Promise<void>;
|
|
1974
|
+
/**
|
|
1975
|
+
* Recompute the idle-release window from override → deployment baseline, and
|
|
1976
|
+
* push the result to every live bridge.
|
|
1977
|
+
*/
|
|
1978
|
+
private applyIdleReleaseTuning;
|
|
1979
|
+
/**
|
|
1980
|
+
* The idle-release window that runs, in ms: the settings page's pick, else
|
|
1981
|
+
* the deployment baseline.
|
|
1982
|
+
*/
|
|
1983
|
+
private idleTimeoutMs;
|
|
1761
1984
|
/**
|
|
1762
1985
|
* Write the approval deadline, or the mode (omitted keys keep their value),
|
|
1763
1986
|
* or drop the persisted override with `reset` so the config-file baseline —
|
|
@@ -81,6 +81,26 @@ export interface AcpChatInjected {
|
|
|
81
81
|
acp: AcpStore;
|
|
82
82
|
/** Resolve the session's command/projection face; `undefined` before the session materializes. */
|
|
83
83
|
faceFor(sessionId: string): AcpSessionFace | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* The runtime's `sessions.fork` face (the host's own branching primitive,
|
|
86
|
+
* resolved structurally per call); `undefined` when the host half lacks it —
|
|
87
|
+
* the branch button then renders disabled (`hostUnavailable`), never hidden.
|
|
88
|
+
*/
|
|
89
|
+
hostFork?: () => AcpHostForkFace | undefined;
|
|
90
|
+
/** The workspace's `openSession` face (the branch click's final step). */
|
|
91
|
+
openSession?: (sessionId: string) => void;
|
|
92
|
+
}
|
|
93
|
+
/** The slice of the client runtime's session service the branch button needs. */
|
|
94
|
+
export interface AcpHostForkFace {
|
|
95
|
+
/**
|
|
96
|
+
* Fork the DSH session tree at the latest completed turn. Resolves the CHILD
|
|
97
|
+
* session id; the host owns the cut-point snapping, history copy, `(1)`
|
|
98
|
+
* title and workspace attribution — the plugin rewrites none of it.
|
|
99
|
+
*/
|
|
100
|
+
fork(options: {
|
|
101
|
+
sessionId: string;
|
|
102
|
+
increaseTitle?: boolean;
|
|
103
|
+
}): Promise<unknown>;
|
|
84
104
|
}
|
|
85
105
|
/**
|
|
86
106
|
* The slice of the client runtime's `ConversationSnapshot` our composer entries
|
|
@@ -233,6 +253,13 @@ export interface AcpStagedSession {
|
|
|
233
253
|
/**
|
|
234
254
|
* Which session the composer is currently staged on.
|
|
235
255
|
*
|
|
256
|
+
* The renderer's own current scope binding is the first answer when the caller
|
|
257
|
+
* passes it (`currentSessionId`, read from `uiSession.adapter.current` in the
|
|
258
|
+
* browser entry): that is the exact session the host has selected, while the
|
|
259
|
+
* list can retain more than one row in a multi-view layout. Falling back to
|
|
260
|
+
* the list is still required — a host without that scope adapter, and the old
|
|
261
|
+
* snapshot shape below, have no better source.
|
|
262
|
+
*
|
|
236
263
|
* Old runtimes answer from `snapshot.current` (with `currentAddress` as the
|
|
237
264
|
* subagent marker). rc.2 has no `current`: the staged session is the row the
|
|
238
265
|
* main view retains (`retainedBy.mainView > 0`) — the exact rule
|
|
@@ -240,10 +267,15 @@ export interface AcpStagedSession {
|
|
|
240
267
|
* row with `origin === "subagent"` is an addressed subagent route. `undefined`
|
|
241
268
|
* when nothing is staged yet (the list has not loaded, or no row holds the main
|
|
242
269
|
* view): callers must stand down rather than guess.
|
|
270
|
+
*
|
|
271
|
+
* A current id the list has no row for is ALSO `undefined`: the selection may
|
|
272
|
+
* be a subagent address, and guessing "not a subagent" there would let the
|
|
273
|
+
* gate install ACP chrome on a route the plugin must not drive. The next list
|
|
274
|
+
* notification retries.
|
|
243
275
|
*/
|
|
244
|
-
export declare function stagedSessionOf(snapshot: AcpSessionsSnapshot | undefined): AcpStagedSession | undefined;
|
|
276
|
+
export declare function stagedSessionOf(snapshot: AcpSessionsSnapshot | undefined, currentSessionId?: string): AcpStagedSession | undefined;
|
|
245
277
|
/** Whether the staged session runs the ACP agent preset. */
|
|
246
|
-
export declare function stagedIsAcpMode(snapshot: AcpSessionsSnapshot | undefined, acpMode?: (sessionId: string) => boolean | undefined): boolean;
|
|
278
|
+
export declare function stagedIsAcpMode(snapshot: AcpSessionsSnapshot | undefined, acpMode?: (sessionId: string) => boolean | undefined, currentSessionId?: string): boolean;
|
|
247
279
|
/**
|
|
248
280
|
* The ACP-mode verdict a committed `agent-preset/selected` frame carries. The
|
|
249
281
|
* frame is `[sessionId, agentPreset]` and the runtime broadcasts it the moment
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* spawns. Everything it shows is host state.
|
|
13
13
|
*/
|
|
14
14
|
import type { AcpChatInjected } from './acp-ui.tsx';
|
|
15
|
-
import type { AgentView, ChatSessionView } from '../protocol.ts';
|
|
15
|
+
import type { AgentView, ChatSessionView, SessionView } from '../protocol.ts';
|
|
16
16
|
/** Props of the ACP view entry (injected face + the frame's session id). */
|
|
17
17
|
export interface AcpViewProps extends AcpChatInjected {
|
|
18
18
|
sessionId: string;
|
|
@@ -43,6 +43,17 @@ export declare function diffRows(oldText: string | null, newText: string): DiffR
|
|
|
43
43
|
* the new content — the degenerate case where there is no edit at all.
|
|
44
44
|
*/
|
|
45
45
|
export declare function isNewFileDiff(oldText: string | null, newText: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether an empty stream means "history exists but is not shown yet": the
|
|
48
|
+
* record was restored from disk after a host restart (the agent's replay has
|
|
49
|
+
* not landed), or a resume cleared the buffer ahead of the replay. The view
|
|
50
|
+
* then promises the replay instead of claiming the conversation never
|
|
51
|
+
* happened — "还没有消息" to someone whose chat plane still shows yesterday's
|
|
52
|
+
* turns is exactly the kind of contradiction this file's copy rules exist to
|
|
53
|
+
* prevent. In-memory released sessions keep their buffer, so this only fires
|
|
54
|
+
* when the buffer really is absent.
|
|
55
|
+
*/
|
|
56
|
+
export declare function historyAwaitsReplay(session: Pick<SessionView, 'released' | 'hasHistory'>): boolean;
|
|
46
57
|
/**
|
|
47
58
|
* Everything the view can read that says "this conversation is set up on an
|
|
48
59
|
* agent": a live or parked ACP session, the host's binding (`session/chat-list`'s
|
|
@@ -101,5 +112,5 @@ export declare function acpEmptyCopy(facts: AcpEmptyFacts, t: Translate): AcpEmp
|
|
|
101
112
|
* The ACP view tab body: who serves this conversation and how it is doing,
|
|
102
113
|
* then the agent's own stream.
|
|
103
114
|
*/
|
|
104
|
-
export declare function AcpConversationView(props: AcpViewProps): import("react").JSX.Element;
|
|
115
|
+
export declare function AcpConversationView(props: AcpViewProps): import("react").JSX.Element | null;
|
|
105
116
|
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The NTR branch button (slot `conversation.chat.assistant-actions`): in an
|
|
3
|
+
* ACP conversation this is the ONLY branch entry — the host's like / dislike /
|
|
4
|
+
|
|
5
|
+
* branch icons are hidden under `html[data-ntr-acp]` (see acp-ui.tsx), and
|
|
6
|
+
* this button takes the host branch's place with the same icon, size and
|
|
7
|
+
* row position (order 20, after the hidden feedback entry's order 10).
|
|
8
|
+
*
|
|
9
|
+
* Clicking runs the fixed two-stage branch: the HOST forks the DSH session
|
|
10
|
+
* tree first (`sessions.fork`, structurally resolved), then the plugin adopts
|
|
11
|
+
* the ACP side (`session/chat-fork-adopt` → the agent's own `session/fork`),
|
|
12
|
+
* so the child conversation's agent actually remembers the parent context —
|
|
13
|
+
* see the NTR message-actions plan, §D5/§D7.
|
|
14
|
+
*
|
|
15
|
+
* Availability is the store's `forkStateOf` six-state mapping over facts the
|
|
16
|
+
* button never derives itself: the host's per-row `fork` verdict
|
|
17
|
+
* (`session/chat-list`), the SSE running flag, the host-fork structural probe,
|
|
18
|
+
* and the DOM tail verdict. The tail check reads the host's OWN
|
|
19
|
+
* `[data-turn-tail]` marker (`data-actions-reveal === "always"` = the latest
|
|
20
|
+
* completed turn) and fails CLOSED: a missing marker disables the button
|
|
21
|
+
* rather than risking a fork cut at a point the user is not looking at.
|
|
22
|
+
*/
|
|
23
|
+
import type { ComponentType } from 'react';
|
|
24
|
+
import type { AcpChatInjected } from './acp-ui.tsx';
|
|
25
|
+
/**
|
|
26
|
+
* The host's own branch-button icon, picked per host generation. The icon
|
|
27
|
+
* exports MOVED between shipped hosts — desktop 0.2.1-rc.2 has
|
|
28
|
+
* `IconBranchOutlineRegular` / `…Medium` but NOT `…16`, newer packages export
|
|
29
|
+
* `…16` — and a hard import of one name compiles to a plain property read
|
|
30
|
+
* (`primitives.IconBranchOutline16`), so the missing export is `undefined`
|
|
31
|
+
* and `<undefined />` throws "Element type is invalid". The slot's error
|
|
32
|
+
* boundary then abdicates the whole entry: no button, no tooltip, nothing in
|
|
33
|
+
* any log — the exact silent disappearance this picker exists to prevent.
|
|
34
|
+
* Rendering the button never depends on the icon: with all three absent the
|
|
35
|
+
* click still works, only the 28px glyph is missing.
|
|
36
|
+
*/
|
|
37
|
+
export declare function branchIconOf(source: Record<string, unknown>): ComponentType<Record<string, never>> | undefined;
|
|
38
|
+
export declare function AcpBranchAction(props: AcpChatInjected & {
|
|
39
|
+
sessionId: string;
|
|
40
|
+
}): import("react").JSX.Element | null;
|
|
@@ -50,4 +50,32 @@ export interface ComposerModeProps {
|
|
|
50
50
|
* gone and this select is the mode switch the user actually has.
|
|
51
51
|
*/
|
|
52
52
|
export declare function agentModeTitle(agentId: string | undefined, acp: AcpStore): string;
|
|
53
|
+
/**
|
|
54
|
+
* Whether this conversation is one the agent-mode control may serve.
|
|
55
|
+
*
|
|
56
|
+
* An EXPLICIT non-ACP verdict outranks a lingering session row: switching a
|
|
57
|
+
* conversation off the NTR preset does not close its ACP session, so "a row
|
|
58
|
+
* exists" alone would keep this control on an ordinary conversation — the same
|
|
59
|
+
* precedence the agent tag, the context ring and the released banner already
|
|
60
|
+
* apply. Only while the host has not answered (`undefined`) does a bound
|
|
61
|
+
* session count as proof, which is what keeps the control available before the
|
|
62
|
+
* host's first `session/chat-list` answer lands.
|
|
63
|
+
*/
|
|
64
|
+
export declare function agentModeVisible(acpMode: boolean | undefined, hasBoundSession: boolean): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The mode the select shows as in force.
|
|
67
|
+
*
|
|
68
|
+
* A LIVE session is the authority (it is what the agent runs right now). A
|
|
69
|
+
* RELEASED row only describes the mode the agent was parked ON, so a newer
|
|
70
|
+
* pick — which `applyChatChoice` will apply on the next resume — outranks it
|
|
71
|
+
* (and the parked row still beats the catalog default). Same precedence as
|
|
72
|
+
* the model and thought level in `describeRoute`: a pre-switch on a parked
|
|
73
|
+
* session must be visible or the control reads as dead.
|
|
74
|
+
*/
|
|
75
|
+
export declare function modeInForce(target: {
|
|
76
|
+
released?: boolean;
|
|
77
|
+
modes?: {
|
|
78
|
+
currentModeId?: string;
|
|
79
|
+
};
|
|
80
|
+
} | undefined, persisted: string | undefined, fallback?: string): string;
|
|
53
81
|
export declare function AcpComposerMode({ acp, sessionId, useSession }: ComposerModeProps): import("react").JSX.Element | null;
|