dsh-ntr 0.2.0 → 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 +5 -2
- package/README.md +5 -2
- package/lib/bridge.js +14 -23
- package/lib/client/panel.js +96 -78
- package/lib/client.js +403 -301
- package/lib/client.js.map +1 -1
- package/lib/types/bridge.d.ts +3 -3
- package/lib/types/client/panel.d.ts +0 -1
- package/package.json +5 -1
- package/src/bridge.ts +16 -22
- package/src/client/panel.tsx +193 -204
package/lib/types/bridge.d.ts
CHANGED
|
@@ -805,6 +805,8 @@ interface SegmentState {
|
|
|
805
805
|
index: number;
|
|
806
806
|
activeType: 'reasoning' | 'text' | 'tool-call' | null;
|
|
807
807
|
activeText: string;
|
|
808
|
+
/** UTF-16 units of this segment's text and reasoning chunks (the usage estimate). */
|
|
809
|
+
outputChars: number;
|
|
808
810
|
/** The open tool-call block's snapshot, while `activeType === 'tool-call'`. */
|
|
809
811
|
activeToolCall: AcpToolCallSnapshot | undefined;
|
|
810
812
|
/** Whether this segment opened at least one tool-call block. */
|
|
@@ -845,7 +847,7 @@ type TurnSettle = {
|
|
|
845
847
|
* `transcript.since(cursor)` — events that arrive while no segment is open are
|
|
846
848
|
* recorded, never lost), the seen-call registry (a call id gets exactly one
|
|
847
849
|
* card per turn, wherever its restatements land), and the turn-level facts
|
|
848
|
-
* (`sawVisibleBlock
|
|
850
|
+
* (`sawVisibleBlock`) that outlive any single segment.
|
|
849
851
|
*/
|
|
850
852
|
declare class TurnStreamController {
|
|
851
853
|
private readonly manager;
|
|
@@ -860,7 +862,6 @@ declare class TurnStreamController {
|
|
|
860
862
|
private disposed;
|
|
861
863
|
private readonly seenCalls;
|
|
862
864
|
private readonly announcedKinds;
|
|
863
|
-
private latestUsage;
|
|
864
865
|
/** Narrative lines (permission asks) that arrived while no segment was open. */
|
|
865
866
|
private readonly pendingProse;
|
|
866
867
|
sawVisibleBlock: boolean;
|
|
@@ -908,7 +909,6 @@ declare class TurnStreamController {
|
|
|
908
909
|
private pushProse;
|
|
909
910
|
/** Events that arrive between segments: recorded, replayed into the next one. */
|
|
910
911
|
private recordWithoutSegment;
|
|
911
|
-
private noteUsage;
|
|
912
912
|
/** Record a tool call's output (idempotent) and wake any waiter. */
|
|
913
913
|
private noteToolRecord;
|
|
914
914
|
/**
|
|
@@ -86,7 +86,6 @@ export declare function approvalDeadlineFacts(deadlines: AcpApprovalDeadlines):
|
|
|
86
86
|
/** A reset is only honest when something is actually being outranked. */
|
|
87
87
|
overridden: boolean;
|
|
88
88
|
};
|
|
89
|
-
/** Settings configuration section for ACP agents. */
|
|
90
89
|
export declare function AcpSettingsSection(props: WithStore): import("react").JSX.Element;
|
|
91
90
|
/** Everything the connection form edits, in one object (kept in the row). */
|
|
92
91
|
interface AgentForm {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-ntr",
|
|
3
|
-
"version": "0.2.
|
|
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"
|
package/src/bridge.ts
CHANGED
|
@@ -2400,6 +2400,8 @@ interface SegmentState {
|
|
|
2400
2400
|
index: number
|
|
2401
2401
|
activeType: 'reasoning' | 'text' | 'tool-call' | null
|
|
2402
2402
|
activeText: string
|
|
2403
|
+
/** UTF-16 units of this segment's text and reasoning chunks (the usage estimate). */
|
|
2404
|
+
outputChars: number
|
|
2403
2405
|
/** The open tool-call block's snapshot, while `activeType === 'tool-call'`. */
|
|
2404
2406
|
activeToolCall: AcpToolCallSnapshot | undefined
|
|
2405
2407
|
/** Whether this segment opened at least one tool-call block. */
|
|
@@ -2407,6 +2409,9 @@ interface SegmentState {
|
|
|
2407
2409
|
finished: boolean
|
|
2408
2410
|
}
|
|
2409
2411
|
|
|
2412
|
+
/** ACP `usage_update` is context occupancy, not request tokens; host usage is a 4-chars/token OUTPUT estimate (tool args excluded). */
|
|
2413
|
+
const OUTPUT_CHARS_PER_TOKEN = 4
|
|
2414
|
+
|
|
2410
2415
|
/** How one segment's stream ends, in `StreamChunk.finish` terms. */
|
|
2411
2416
|
type SegmentFinishReason =
|
|
2412
2417
|
| { kind: 'stop' }
|
|
@@ -2427,7 +2432,7 @@ type TurnSettle =
|
|
|
2427
2432
|
* `transcript.since(cursor)` — events that arrive while no segment is open are
|
|
2428
2433
|
* recorded, never lost), the seen-call registry (a call id gets exactly one
|
|
2429
2434
|
* card per turn, wherever its restatements land), and the turn-level facts
|
|
2430
|
-
* (`sawVisibleBlock
|
|
2435
|
+
* (`sawVisibleBlock`) that outlive any single segment.
|
|
2431
2436
|
*/
|
|
2432
2437
|
class TurnStreamController {
|
|
2433
2438
|
private segment: SegmentState | undefined
|
|
@@ -2435,7 +2440,6 @@ class TurnStreamController {
|
|
|
2435
2440
|
private disposed = false
|
|
2436
2441
|
private readonly seenCalls = new Set<string>()
|
|
2437
2442
|
private readonly announcedKinds = new Map<string, string>()
|
|
2438
|
-
private latestUsage: { inputTokens: number; outputTokens: number } | null = null
|
|
2439
2443
|
/** Narrative lines (permission asks) that arrived while no segment was open. */
|
|
2440
2444
|
private readonly pendingProse: string[] = []
|
|
2441
2445
|
sawVisibleBlock = false
|
|
@@ -2487,6 +2491,7 @@ class TurnStreamController {
|
|
|
2487
2491
|
index: -1,
|
|
2488
2492
|
activeType: null,
|
|
2489
2493
|
activeText: '',
|
|
2494
|
+
outputChars: 0,
|
|
2490
2495
|
activeToolCall: undefined,
|
|
2491
2496
|
hasCard: false,
|
|
2492
2497
|
finished: false,
|
|
@@ -2605,7 +2610,9 @@ class TurnStreamController {
|
|
|
2605
2610
|
seg.activeType = null
|
|
2606
2611
|
seg.activeToolCall = undefined
|
|
2607
2612
|
}
|
|
2608
|
-
|
|
2613
|
+
const outputTokens = Math.round(seg.outputChars / OUTPUT_CHARS_PER_TOKEN)
|
|
2614
|
+
// Zero input keeps the host's cache-hit pill hidden instead of faking 0%.
|
|
2615
|
+
if (outputTokens > 0) seg.queue.push({ type: 'usage', usage: { inputTokens: 0, outputTokens } })
|
|
2609
2616
|
seg.queue.push({ type: 'finish', reason })
|
|
2610
2617
|
seg.queue.close()
|
|
2611
2618
|
if (this.segment === seg) this.segment = undefined
|
|
@@ -2643,11 +2650,10 @@ class TurnStreamController {
|
|
|
2643
2650
|
const seg = this.segment
|
|
2644
2651
|
if (seg === undefined || seg.finished) {
|
|
2645
2652
|
// Between segments: tool outputs still reach the shared registry (the
|
|
2646
|
-
// guard's virtual tool may be parked on this very record)
|
|
2647
|
-
//
|
|
2648
|
-
//
|
|
2649
|
-
//
|
|
2650
|
-
// executed on the host.
|
|
2653
|
+
// guard's virtual tool may be parked on this very record), but the
|
|
2654
|
+
// cursor does NOT advance — the next segment replays everything since
|
|
2655
|
+
// it, which re-records those idempotently and folds the prose the agent
|
|
2656
|
+
// streamed while its cards executed on the host.
|
|
2651
2657
|
this.recordWithoutSegment(update)
|
|
2652
2658
|
return
|
|
2653
2659
|
}
|
|
@@ -2689,17 +2695,7 @@ class TurnStreamController {
|
|
|
2689
2695
|
/** Events that arrive between segments: recorded, replayed into the next one. */
|
|
2690
2696
|
private recordWithoutSegment(update: Record<string, unknown>): void {
|
|
2691
2697
|
const kind = String(update.sessionUpdate ?? '')
|
|
2692
|
-
if (kind === '
|
|
2693
|
-
this.noteUsage(update)
|
|
2694
|
-
} else if (kind === 'tool_call' || kind === 'tool_call_update') {
|
|
2695
|
-
this.noteToolRecord(update)
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
|
|
2699
|
-
private noteUsage(update: Record<string, unknown>): void {
|
|
2700
|
-
const used = typeof update.used === 'number' ? update.used : 0
|
|
2701
|
-
const size = typeof update.size === 'number' ? update.size : 0
|
|
2702
|
-
this.latestUsage = { inputTokens: size, outputTokens: used }
|
|
2698
|
+
if (kind === 'tool_call' || kind === 'tool_call_update') this.noteToolRecord(update)
|
|
2703
2699
|
}
|
|
2704
2700
|
|
|
2705
2701
|
/** Record a tool call's output (idempotent) and wake any waiter. */
|
|
@@ -2725,6 +2721,7 @@ class TurnStreamController {
|
|
|
2725
2721
|
this.finishSegment(seg, { kind: 'stop' })
|
|
2726
2722
|
return false
|
|
2727
2723
|
}
|
|
2724
|
+
seg.outputChars += text.length
|
|
2728
2725
|
this.emitProse(seg, kind === 'agent_thought_chunk' ? 'reasoning' : 'text', text)
|
|
2729
2726
|
return true
|
|
2730
2727
|
} else if (kind === 'tool_call' || kind === 'tool_call_update') {
|
|
@@ -2743,9 +2740,6 @@ class TurnStreamController {
|
|
|
2743
2740
|
this.seenCalls.add(snapshot.callId)
|
|
2744
2741
|
this.openToolCallBlock(seg, snapshot)
|
|
2745
2742
|
return true
|
|
2746
|
-
} else if (kind === 'usage_update') {
|
|
2747
|
-
this.noteUsage(update)
|
|
2748
|
-
return true
|
|
2749
2743
|
} else if (!BOOKKEEPING_UPDATES.has(kind)) {
|
|
2750
2744
|
const text = describeUnhandledUpdate(kind, update)
|
|
2751
2745
|
if (this.announcedKinds.get(kind) === text) return true
|