dsh-ntr 0.1.6 → 0.2.1
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 +6 -3
- package/README.md +6 -3
- package/cordis.patch.yml +18 -18
- package/lib/bridge.js +134 -314
- package/lib/client/acp-ui.js +60 -32
- package/lib/client/index.js +17 -0
- package/lib/client/panel.js +127 -114
- package/lib/client/permission-dock.js +72 -0
- package/lib/client/session-controls.js +4 -4
- package/lib/client/store.js +58 -12
- package/lib/client.js +696 -393
- package/lib/client.js.map +1 -1
- package/lib/impl.js +30 -19
- package/lib/index.js +6 -51
- package/lib/types/bridge.d.ts +63 -157
- package/lib/types/client/panel.d.ts +12 -20
- package/lib/types/client/permission-dock.d.ts +31 -0
- package/lib/types/client/store.d.ts +42 -12
- package/lib/types/impl.d.ts +31 -17
- package/lib/types/protocol.d.ts +27 -12
- package/package.json +5 -1
- package/src/bridge.ts +163 -379
- package/src/client/acp-ui.tsx +60 -32
- package/src/client/index.tsx +18 -0
- package/src/client/panel.tsx +243 -258
- package/src/client/permission-dock.tsx +133 -0
- package/src/client/session-controls.tsx +4 -4
- package/src/client/store.ts +81 -25
- package/src/impl.ts +49 -29
- package/src/index.ts +6 -65
- package/src/protocol.ts +32 -12
|
@@ -84,16 +84,49 @@ export type Block = {
|
|
|
84
84
|
tone: 'info' | 'error';
|
|
85
85
|
text: string;
|
|
86
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* One permission ask waiting for a human. Every field is copied VERBATIM from
|
|
89
|
+
* the host's `permission/request` frame: the host owns what the ask is called
|
|
90
|
+
* (`toolName` / `reason`), which options the agent offered (order included),
|
|
91
|
+
* and whether the ask carries a deadline. The browser does not derive a title
|
|
92
|
+
* from `toolCall` and does not second-guess the agent's option labels.
|
|
93
|
+
*/
|
|
87
94
|
export interface PendingPermission {
|
|
88
95
|
requestId: string;
|
|
96
|
+
/** ACP session id. */
|
|
89
97
|
sessionId: string;
|
|
90
|
-
|
|
98
|
+
/** DSH chat session id; absent for `/acp` RPC-only sessions (settings-only ask). */
|
|
99
|
+
dshSessionId?: string;
|
|
100
|
+
toolName: string;
|
|
101
|
+
reason?: string;
|
|
91
102
|
options: Array<{
|
|
92
103
|
optionId: string;
|
|
93
104
|
name: string;
|
|
94
105
|
kind: string;
|
|
95
106
|
}>;
|
|
107
|
+
timeoutMode: PermissionTimeoutMode;
|
|
108
|
+
/** Epoch ms; absent in `wait` mode. */
|
|
109
|
+
deadlineAt?: number;
|
|
96
110
|
}
|
|
111
|
+
/** Returned by {@link deadlineRemainingLabel} once the deadline has passed. */
|
|
112
|
+
export declare const PERMISSION_DEADLINE_EXPIRED = "expired";
|
|
113
|
+
/**
|
|
114
|
+
* The asks that belong to one DSH chat conversation. An ask without a
|
|
115
|
+
* `dshSessionId` belongs to no conversation (an `/acp` RPC-only session), so it
|
|
116
|
+
* is deliberately absent from every conversation's dock and stays in the
|
|
117
|
+
* settings-panel ledger.
|
|
118
|
+
*/
|
|
119
|
+
export declare function permissionsForSession(permissions: readonly PendingPermission[], dshSessionId: string): PendingPermission[];
|
|
120
|
+
/**
|
|
121
|
+
* Compact countdown for one ask, or `undefined` when there is nothing to count
|
|
122
|
+
* down (`wait` mode, or a frame without a deadline). Pure so the formatting is
|
|
123
|
+
* testable without a clock: the dock passes `Date.now()` on its tick.
|
|
124
|
+
*
|
|
125
|
+
* Past the deadline the answer is {@link PERMISSION_DEADLINE_EXPIRED} rather
|
|
126
|
+
* than a negative number — the host has already armed the expiry, and a card
|
|
127
|
+
* counting backwards would claim a deadline that no longer exists.
|
|
128
|
+
*/
|
|
129
|
+
export declare function deadlineRemainingLabel(deadlineAt: number | undefined, now: number, mode: PermissionTimeoutMode): string | undefined;
|
|
97
130
|
/**
|
|
98
131
|
* One observed ACP transcript, keyed by ACP session id. This is the ACP view
|
|
99
132
|
* tab's data source: the native chat plane can only render text/reasoning
|
|
@@ -164,17 +197,6 @@ export interface UiState {
|
|
|
164
197
|
* that never answers, …) reads as a dead button.
|
|
165
198
|
*/
|
|
166
199
|
probeErrors: Record<string, string>;
|
|
167
|
-
/**
|
|
168
|
-
* Host reports that it HAS the native approval chain (permission asks are
|
|
169
|
-
* answered in the chat composer). This is a capability report, not a
|
|
170
|
-
* per-ask promise: an individual ask still falls back to the settings-panel
|
|
171
|
-
* ledger when the service is unavailable this time, or when the session has
|
|
172
|
-
* no chat identity behind it (`AgentBridge.askNativeApproval`). The panel
|
|
173
|
-
* therefore answers ANY pending ask, whatever this flag says — see
|
|
174
|
-
* `permissionLedgerCard` in panel.tsx. False on an older host or when the
|
|
175
|
-
* approval service is missing, in which case the ledger is the only path.
|
|
176
|
-
*/
|
|
177
|
-
approvalRouted: boolean;
|
|
178
200
|
/**
|
|
179
201
|
* ACP agent sessions backing the chat session the composer control sits in.
|
|
180
202
|
* Only meaningful together with {@link chatSessionsFor}: the control reads it
|
|
@@ -556,6 +578,14 @@ export declare class AcpStore {
|
|
|
556
578
|
/** Mirror the running flag onto a transcript the ACP view is showing. */
|
|
557
579
|
private markWatchedRunning;
|
|
558
580
|
setConfigOption(configId: string, value: string): Promise<void>;
|
|
581
|
+
/**
|
|
582
|
+
* Answer one ask (`optionId` absent = cancel it). A failure is reported
|
|
583
|
+
* through the store's notice — the settings panel renders that — AND
|
|
584
|
+
* rethrown, so the conversation dock can pin the message next to the card
|
|
585
|
+
* that failed instead of leaving it on a settings page the user may not
|
|
586
|
+
* have open. Callers that cannot show it (the settings panel) swallow it
|
|
587
|
+
* after the notice.
|
|
588
|
+
*/
|
|
559
589
|
respondPermission(requestId: string, optionId?: string): Promise<void>;
|
|
560
590
|
stopAgent(agentId: string): Promise<void>;
|
|
561
591
|
/**
|
package/lib/types/impl.d.ts
CHANGED
|
@@ -561,6 +561,21 @@ export declare class EventHub {
|
|
|
561
561
|
subscribe(sink: (event: AcpEvent) => void): () => void;
|
|
562
562
|
broadcast(event: AcpEvent): void;
|
|
563
563
|
}
|
|
564
|
+
/** Everything a `permission/request` frame carries besides the ids the ledger owns. */
|
|
565
|
+
export interface PendingPermissionFields {
|
|
566
|
+
/** DSH chat session id; absent for `/acp` RPC-only sessions. */
|
|
567
|
+
dshSessionId?: string;
|
|
568
|
+
toolCall: unknown;
|
|
569
|
+
/** Host-derived human name (`permissionToolName`). */
|
|
570
|
+
toolName: string;
|
|
571
|
+
/** One-line detail from the agent's declared input, when it has any. */
|
|
572
|
+
reason?: string;
|
|
573
|
+
options: PermissionOptionView[];
|
|
574
|
+
/** What an unanswered ask does when its deadline fires. */
|
|
575
|
+
timeoutMode: PermissionTimeoutMode;
|
|
576
|
+
/** Epoch ms of the deadline; absent in `wait` mode (no deadline). */
|
|
577
|
+
deadlineAt?: number;
|
|
578
|
+
}
|
|
564
579
|
export interface PendingPermission {
|
|
565
580
|
requestId: string;
|
|
566
581
|
sessionId: string;
|
|
@@ -570,17 +585,12 @@ export interface PendingPermission {
|
|
|
570
585
|
* client receives (see `permissionReplayFrames`). Absent on entries created
|
|
571
586
|
* without a view — those still answer, they just cannot be re-shown.
|
|
572
587
|
*/
|
|
573
|
-
view?:
|
|
574
|
-
toolCall: unknown;
|
|
575
|
-
options: PermissionOptionView[];
|
|
576
|
-
};
|
|
588
|
+
view?: PendingPermissionFields;
|
|
577
589
|
}
|
|
578
590
|
/** One outstanding ask as a reconnecting client must be re-shown it. */
|
|
579
|
-
export interface PendingPermissionView {
|
|
591
|
+
export interface PendingPermissionView extends PendingPermissionFields {
|
|
580
592
|
requestId: string;
|
|
581
593
|
sessionId: string;
|
|
582
|
-
toolCall: unknown;
|
|
583
|
-
options: PermissionOptionView[];
|
|
584
594
|
}
|
|
585
595
|
export declare class PermissionLedger {
|
|
586
596
|
private readonly pending;
|
|
@@ -603,6 +613,16 @@ export declare class PermissionLedger {
|
|
|
603
613
|
*/
|
|
604
614
|
pendingViews(): PendingPermissionView[];
|
|
605
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* The `permission/request` frame for one ask.
|
|
618
|
+
*
|
|
619
|
+
* ONE builder for both the live broadcast and the reconnect replay, so the two
|
|
620
|
+
* can never drift: a client that reconnects must receive exactly the frame the
|
|
621
|
+
* live one carried (its conversation card filters on `dshSessionId` and shows
|
|
622
|
+
* `toolName` / `deadlineAt`, so a replay missing them would silently drop the
|
|
623
|
+
* ask).
|
|
624
|
+
*/
|
|
625
|
+
export declare function permissionRequestFrame(view: PendingPermissionView): AcpEvent;
|
|
606
626
|
/**
|
|
607
627
|
* Frames that re-establish a freshly connected client's view of the pending
|
|
608
628
|
* permission asks: every outstanding ask re-sent as its original
|
|
@@ -610,10 +630,10 @@ export declare class PermissionLedger {
|
|
|
610
630
|
*
|
|
611
631
|
* Why a replay at all: a `permission/request` is broadcast ONCE, when the ask is
|
|
612
632
|
* created. A client that connects later — page reload, second tab, stream
|
|
613
|
-
* outage — never receives it, and
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
633
|
+
* outage — never receives it, and that card (conversation or settings ledger)
|
|
634
|
+
* is the ONLY answer entry for the ask, so the ask is invisible to exactly the
|
|
635
|
+
* client that needs it. OmniTerm closes the same hole with
|
|
636
|
+
* `PermissionManager::pending_events()` + a `PermissionsSynced` marker on
|
|
617
637
|
* every WS connect.
|
|
618
638
|
*
|
|
619
639
|
* Why the terminator is mandatory: the replay is a snapshot, so an ask resolved
|
|
@@ -706,12 +726,6 @@ export interface AcpManager {
|
|
|
706
726
|
* cache (newly published models appear, withdrawn ones drop out).
|
|
707
727
|
*/
|
|
708
728
|
cachedAgentModels?(agentId: string): AgentProbeModelsResult | undefined;
|
|
709
|
-
/**
|
|
710
|
-
* True when ACP permission asks are routed to the host's native approval
|
|
711
|
-
* service (answered in the chat composer) instead of the settings-panel
|
|
712
|
-
* ledger. Read-only capability report for `agents/list`.
|
|
713
|
-
*/
|
|
714
|
-
approvalRouting?(): boolean;
|
|
715
729
|
/**
|
|
716
730
|
* ACP agent sessions backing one DSH chat session (looked up by chat session
|
|
717
731
|
* id), so the composer can offer the agent's OWN mode list. Empty when no
|
package/lib/types/protocol.d.ts
CHANGED
|
@@ -349,12 +349,35 @@ export type AcpEvent = {
|
|
|
349
349
|
agentId: string;
|
|
350
350
|
exposedModels: string[];
|
|
351
351
|
autoDiscoverModels: boolean;
|
|
352
|
-
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* One outstanding ask, broadcast ONCE when it is created and replayed as-is
|
|
355
|
+
* to every freshly connected client (see `permission/synced`).
|
|
356
|
+
*
|
|
357
|
+
* Every ACP permission ask carries everything the conversation card needs:
|
|
358
|
+
* who owns it (`dshSessionId`), what it is about (`toolCall` + the derived
|
|
359
|
+
* `toolName` / `reason`), the options to render verbatim, and the deadline in
|
|
360
|
+
* force. The host never answers on its own outside an expired `abort` /
|
|
361
|
+
* `auto` deadline, so this frame (and its replay) is the answer entry.
|
|
362
|
+
*/
|
|
363
|
+
| {
|
|
353
364
|
type: 'permission/request';
|
|
354
365
|
requestId: string;
|
|
366
|
+
/** ACP session id the ask belongs to. */
|
|
355
367
|
sessionId: string;
|
|
368
|
+
/** DSH chat session id; absent for `/acp` RPC-only sessions. */
|
|
369
|
+
dshSessionId?: string;
|
|
370
|
+
/** Raw ACP `ToolCallUpdate` the agent sent — never summarized away. */
|
|
356
371
|
toolCall: unknown;
|
|
372
|
+
/** Host-derived human name (`permissionToolName`, `_meta` fallback included). */
|
|
373
|
+
toolName: string;
|
|
374
|
+
/** One-line detail from the agent's declared input, when it has any. */
|
|
375
|
+
reason?: string;
|
|
357
376
|
options: PermissionOptionView[];
|
|
377
|
+
/** What an unanswered ask does when its deadline fires. */
|
|
378
|
+
timeoutMode: PermissionTimeoutMode;
|
|
379
|
+
/** Epoch ms of the deadline; absent in `wait` mode (no deadline). */
|
|
380
|
+
deadlineAt?: number;
|
|
358
381
|
} | {
|
|
359
382
|
type: 'permission/resolved';
|
|
360
383
|
requestId: string;
|
|
@@ -366,11 +389,9 @@ export type AcpEvent = {
|
|
|
366
389
|
* (`abort`), or answered with the option the agent's own kinds imply
|
|
367
390
|
* (`auto`).
|
|
368
391
|
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* ledger path does not need it — its card sits on the settings panel and the
|
|
373
|
-
* chat line already pointed there.
|
|
392
|
+
* This is the conversation's record of the expiry: the card it was answering
|
|
393
|
+
* is gone, so without this frame the user would see it vanish with nothing
|
|
394
|
+
* said about it.
|
|
374
395
|
*/
|
|
375
396
|
| {
|
|
376
397
|
type: 'permission/expired';
|
|
@@ -495,12 +516,6 @@ export type RpcResult<T> = {
|
|
|
495
516
|
};
|
|
496
517
|
export interface AgentsListResult {
|
|
497
518
|
agents: AgentView[];
|
|
498
|
-
/**
|
|
499
|
-
* True when permission asks are routed to the host approval chain and the
|
|
500
|
-
* browser must stop rendering the settings-panel permission card. False (or
|
|
501
|
-
* absent on an older host) keeps the degraded ledger path visible.
|
|
502
|
-
*/
|
|
503
|
-
approvalRouted?: boolean;
|
|
504
519
|
}
|
|
505
520
|
export type SessionPromptResult = {
|
|
506
521
|
accepted: true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ntr",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Interactive Agent Client Protocol (ACP) client for the DSH web UI: attach any ACP-capable agent and stream its session.",
|
|
5
5
|
"repository": "https://github.com/GDWhisper/dsh-ntr.git",
|
|
6
6
|
"type": "module",
|
|
@@ -48,9 +48,13 @@
|
|
|
48
48
|
"@deepseek-ai/dsh-client-ui-sidebar": "0.1.5-rc.1",
|
|
49
49
|
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.1",
|
|
50
50
|
"@deepseek-ai/dsh-host-webserver": "0.1.5-rc.1",
|
|
51
|
+
"@testing-library/react": "^16.3.3",
|
|
51
52
|
"@types/node": "^22.19.0",
|
|
52
53
|
"@types/react": "^19.2.0",
|
|
54
|
+
"@types/react-dom": "^19.3.0",
|
|
55
|
+
"jsdom": "^27.4.0",
|
|
53
56
|
"react": "^19.2.0",
|
|
57
|
+
"react-dom": "^19.3.1",
|
|
54
58
|
"tsdown": "^0.23.0",
|
|
55
59
|
"typescript": "^5.9.0",
|
|
56
60
|
"vitest": "^3.2.0"
|