dsh-ntr 0.3.0 → 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 +614 -25
- 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 +127 -30
- 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 +403 -16
- package/lib/client.js +1267 -70
- 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 +230 -7
- 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 +131 -1
- package/lib/types/impl.d.ts +41 -1
- package/lib/types/protocol.d.ts +130 -0
- package/lib/types/update.d.ts +7 -4
- package/lib/update.js +25 -10
- package/package.json +2 -1
- package/src/bridge.ts +645 -27
- 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 +229 -41
- 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 +463 -20
- package/src/impl.ts +84 -2
- package/src/index.ts +8 -0
- package/src/protocol.ts +129 -0
- package/src/update.ts +21 -8
package/lib/client/index.js
CHANGED
|
@@ -8,16 +8,58 @@
|
|
|
8
8
|
import { AcpStore } from "./store.js";
|
|
9
9
|
import { AcpSettingsSection } from "./panel.js";
|
|
10
10
|
import { AcpComposerMode } from "./composer-mode.js";
|
|
11
|
+
import { AcpContextMeter } from "./context-meter.js";
|
|
11
12
|
import { AcpComposerAgentTag, AcpSessionControls, AcpSessionMenu } from "./session-controls.js";
|
|
12
13
|
import { AcpConversationView } from "./acp-view.js";
|
|
13
14
|
import { AcpSettingsDialog } from "./settings-dialog.js";
|
|
14
15
|
import { PermissionDock } from "./permission-dock.js";
|
|
16
|
+
import { ReleasedBanner } from "./released-banner.js";
|
|
17
|
+
import { AcpBranchAction } from "./branch-action.js";
|
|
15
18
|
import { createModelSeatClaim, createPermissionSeatClaim, MODEL_SEAT_KEY, PERMISSION_SEAT_KEY, } from "./model-seat.js";
|
|
16
|
-
import { acpModeFromPresetEvent, rowAgentPreset, stagedIsAcpMode, stagedSessionOf, } from "./acp-ui.js";
|
|
19
|
+
import { acpModeFromPresetEvent, ACP_PRESET_ID, rowAgentPreset, stagedIsAcpMode, stagedSessionOf, } from "./acp-ui.js";
|
|
17
20
|
import { createSlashIsolation, slashAcpVerdict } from "./slash-isolation.js";
|
|
18
21
|
export const name = 'dsh-ntr-client';
|
|
19
22
|
export const inject = ['slots', 'connection'];
|
|
20
23
|
const browserPrefersZh = typeof navigator !== 'undefined' && /^zh\b/i.test(navigator.language);
|
|
24
|
+
/**
|
|
25
|
+
* The NTR scope marker (`html[data-ntr-acp]`): the ONLY hook the message-action
|
|
26
|
+
* hiding CSS keys on, so the host's like / dislike / branch icons disappear in
|
|
27
|
+
* exactly the conversations this gate has confirmed to run the ACP preset —
|
|
28
|
+
* and reappear the moment the evidence stops answering (every `standDown`
|
|
29
|
+
* clears it; non-ACP sessions never see it). Idempotent per call.
|
|
30
|
+
*/
|
|
31
|
+
const setScopeMarker = (active) => {
|
|
32
|
+
if (typeof document === 'undefined')
|
|
33
|
+
return;
|
|
34
|
+
document.documentElement.toggleAttribute('data-ntr-acp', active);
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* One-shot sanity probe for the aria-label hiding: the host's action buttons
|
|
38
|
+
* carry LOCALIZED labels and no data-* hook, so a copy change (or a third
|
|
39
|
+
* language) silently un-hides them while everything else keeps working. Once
|
|
40
|
+
* the marker is on and a turn tail is actually on screen, check that at least
|
|
41
|
+
* one label the rules key on still exists and leave one console line for
|
|
42
|
+
* whoever debugs "the icons came back" — dev-visible, never user UI.
|
|
43
|
+
*/
|
|
44
|
+
let probedHostLabels = false;
|
|
45
|
+
const probeHostActionLabels = () => {
|
|
46
|
+
if (probedHostLabels || typeof document === 'undefined')
|
|
47
|
+
return;
|
|
48
|
+
// No turn tail rendered yet: the check would answer from nothing, so leave
|
|
49
|
+
// the probe armed for the next activation instead of burning it once.
|
|
50
|
+
if (document.querySelector('[data-turn-tail]') === null)
|
|
51
|
+
return;
|
|
52
|
+
probedHostLabels = true;
|
|
53
|
+
const found = document.querySelector([
|
|
54
|
+
'button:not([data-ntr-branch])[aria-label="在新对话中分支"]',
|
|
55
|
+
'button:not([data-ntr-branch])[aria-label="Branch into a new conversation"]',
|
|
56
|
+
'button[aria-label="好的回答"]',
|
|
57
|
+
'button[aria-label="Good response"]',
|
|
58
|
+
].join(', '));
|
|
59
|
+
if (found === null) {
|
|
60
|
+
console.warn('[dsh-ntr] none of the host message-action labels matched; the like/dislike/branch icons may not be hidden in NTR conversations');
|
|
61
|
+
}
|
|
62
|
+
};
|
|
21
63
|
/**
|
|
22
64
|
* How long a host verdict may stand as the gate's ONLY fact source before the
|
|
23
65
|
* gate asks the host again.
|
|
@@ -97,11 +139,37 @@ export function apply(ctx) {
|
|
|
97
139
|
return undefined;
|
|
98
140
|
}
|
|
99
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* The runtime's `sessions.fork`, resolved per call: `undefined` renders the
|
|
144
|
+
* branch button disabled (`hostUnavailable`) instead of hiding it, so a
|
|
145
|
+
* host that gains the primitive later is picked up on the next render.
|
|
146
|
+
*/
|
|
147
|
+
const hostFork = () => {
|
|
148
|
+
try {
|
|
149
|
+
const fork = service('sessions')?.fork;
|
|
150
|
+
return typeof fork === 'function' ? { fork } : undefined;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/** Focus a session in the workspace (the branch click's last step). */
|
|
157
|
+
const openSession = (sessionId) => {
|
|
158
|
+
try {
|
|
159
|
+
service('uiWorkspace')?.openSession?.(sessionId);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Opening is presentation; a host without the workspace service leaves
|
|
163
|
+
// the branched session in the sidebar list, reachable by hand.
|
|
164
|
+
}
|
|
165
|
+
};
|
|
100
166
|
/** The injected face every ACP conversation entry receives. */
|
|
101
167
|
const chatFace = (sessionId) => ({
|
|
102
168
|
acp: store,
|
|
103
169
|
sessionId: String(sessionId),
|
|
104
170
|
faceFor,
|
|
171
|
+
hostFork,
|
|
172
|
+
openSession,
|
|
105
173
|
});
|
|
106
174
|
ctx.effect(() => ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
107
175
|
name: 'settings.section',
|
|
@@ -140,6 +208,12 @@ export function apply(ctx) {
|
|
|
140
208
|
// composer-mode.tsx, so the composed-props constraint cannot be met by type
|
|
141
209
|
// (see the note there). Runtime passes the real shares either way.
|
|
142
210
|
}, AcpComposerMode)), 'dsh-ntr: composer agent-mode control');
|
|
211
|
+
// The agent's OWN context occupancy in the composer's bottom info row
|
|
212
|
+
// (`conversation.composer.dock` — the dock below the input card where the
|
|
213
|
+
// host renders its native ContextMeter) is installed by the ACP-mode gate
|
|
214
|
+
// below, with the same lifetime as the NTR view tab and the seats. It is
|
|
215
|
+
// NOT registered unconditionally: in an ordinary conversation this row must
|
|
216
|
+
// hold the host's native meter and nothing of ours (see `holdAuxEntries`).
|
|
143
217
|
// Its picker rides the composer's overlay seat — the platform's home for
|
|
144
218
|
// menus, so the popup floats above the card instead of being clipped by it.
|
|
145
219
|
ctx.effect(() => ctx.slots.inject('conversation.input.overlay', () => ctx.slots.register({
|
|
@@ -178,6 +252,32 @@ export function apply(ctx) {
|
|
|
178
252
|
// session id this dock belongs to (same contract as every other entry).
|
|
179
253
|
inject: (sessionId) => chatFace(sessionId),
|
|
180
254
|
}, PermissionDock)), 'dsh-ntr: conversation permission dock');
|
|
255
|
+
// The released-session banner in the same dock seat: when this
|
|
256
|
+
// conversation's agent process is parked (idle release or crash), one row
|
|
257
|
+
// says so and offers the explicit resume — the gesture form of the
|
|
258
|
+
// "next message resumes it" promise. Gated inside the component on the
|
|
259
|
+
// chat-list rows (released banner renders iff nothing live is left).
|
|
260
|
+
ctx.effect(() => ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
261
|
+
name: 'conversation.input.dock',
|
|
262
|
+
id: 'acp-released-banner',
|
|
263
|
+
order: 20,
|
|
264
|
+
inject: (sessionId) => chatFace(sessionId),
|
|
265
|
+
}, ReleasedBanner)), 'dsh-ntr: released-session banner');
|
|
266
|
+
// The NTR branch entry riding the host's own assistant-actions row
|
|
267
|
+
// (`conversation.chat.assistant-actions` — list, session scope; the
|
|
268
|
+
// positional inject argument is the chat session id, like every other
|
|
269
|
+
// session slot). Registered for EVERY conversation; the component gates
|
|
270
|
+
// itself on the confirmed ACP verdict (non-ACP renders nothing), and the
|
|
271
|
+
// `data-ntr-branch` attribute it stamps is the CSS pass that keeps the
|
|
272
|
+
// hiding rules from swallowing the plugin's own button (same aria-label as
|
|
273
|
+
// the host's). Order 20 puts it after the (hidden) feedback entry's 10 —
|
|
274
|
+
// visually the spot the host's own branch button occupied.
|
|
275
|
+
ctx.effect(() => ctx.slots.inject('conversation.chat.assistant-actions', () => ctx.slots.register({
|
|
276
|
+
name: 'conversation.chat.assistant-actions',
|
|
277
|
+
id: 'ntr-branch',
|
|
278
|
+
order: 20,
|
|
279
|
+
inject: (sessionId) => chatFace(sessionId),
|
|
280
|
+
}, AcpBranchAction)), 'dsh-ntr: message branch action');
|
|
181
281
|
/**
|
|
182
282
|
* The client runtime's session list store, read structurally. Used to find
|
|
183
283
|
* the session the composer is staged on and whether it runs the ACP preset —
|
|
@@ -189,6 +289,30 @@ export function apply(ctx) {
|
|
|
189
289
|
return undefined;
|
|
190
290
|
return list;
|
|
191
291
|
};
|
|
292
|
+
/**
|
|
293
|
+
* The renderer's current Session scope observable, when this host exposes it.
|
|
294
|
+
* This is the source the host's own main-view picker prefers; the list's
|
|
295
|
+
* retained rows are only the fallback for hosts without the adapter.
|
|
296
|
+
*/
|
|
297
|
+
const currentScopeSource = () => {
|
|
298
|
+
try {
|
|
299
|
+
const current = service('uiSession')?.adapter?.current;
|
|
300
|
+
return current !== undefined && typeof current.getSnapshot === 'function' ? current : undefined;
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
/** The session the composer is actually staged on, if the runtime can say. */
|
|
307
|
+
const currentScopeSessionId = () => {
|
|
308
|
+
try {
|
|
309
|
+
const key = currentScopeSource()?.getSnapshot?.()?.key;
|
|
310
|
+
return typeof key === 'string' && key !== '' ? key : undefined;
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
192
316
|
/**
|
|
193
317
|
* Chat session ids the ACP-mode tracker has already asked the host about (the
|
|
194
318
|
* `session/chat-list` fallback), mapped to WHEN the last ask went out. The
|
|
@@ -214,6 +338,8 @@ export function apply(ctx) {
|
|
|
214
338
|
const acpModeVerdict = (sessionId) => store.getSnapshot().acpMode[sessionId];
|
|
215
339
|
ctx.effect(() => {
|
|
216
340
|
let stopWatch;
|
|
341
|
+
/** Subscription to the renderer's current Session binding (see `currentScopeSource`). */
|
|
342
|
+
let stopCurrent;
|
|
217
343
|
/**
|
|
218
344
|
* The claim on the host's model seat, held while the staged conversation
|
|
219
345
|
* runs the ACP preset. That cell is where the host's own base sits (the
|
|
@@ -249,6 +375,60 @@ export function apply(ctx) {
|
|
|
249
375
|
* available yet.
|
|
250
376
|
*/
|
|
251
377
|
let permissionClaim;
|
|
378
|
+
/**
|
|
379
|
+
* ACP-only auxiliary entries: the NTR view tab and the composer's context
|
|
380
|
+
* ring. Unlike the two seats these are `list` slots with no visibility
|
|
381
|
+
* predicate of their own, so "self-hide in the component" cannot remove a
|
|
382
|
+
* tab from the header or free the native bottom row. They are installed by
|
|
383
|
+
* this gate for exactly the same lifetime as the claims and disposed on
|
|
384
|
+
* every stand-down — a standard conversation then has no NTR tab at all,
|
|
385
|
+
* and the host's native context meter sits alone in its dock row.
|
|
386
|
+
*/
|
|
387
|
+
let viewEntry;
|
|
388
|
+
let meterEntry;
|
|
389
|
+
const releaseAuxEntries = () => {
|
|
390
|
+
try {
|
|
391
|
+
viewEntry?.();
|
|
392
|
+
}
|
|
393
|
+
catch { }
|
|
394
|
+
viewEntry = undefined;
|
|
395
|
+
try {
|
|
396
|
+
meterEntry?.();
|
|
397
|
+
}
|
|
398
|
+
catch { }
|
|
399
|
+
meterEntry = undefined;
|
|
400
|
+
};
|
|
401
|
+
const holdAuxEntries = () => {
|
|
402
|
+
// A refused registration leaves the native surface in place (fail-open);
|
|
403
|
+
// the next evaluation retries when the slot is declared.
|
|
404
|
+
if (viewEntry === undefined) {
|
|
405
|
+
try {
|
|
406
|
+
viewEntry = ctx.slots.inject('conversation.view', () => ctx.slots.register({
|
|
407
|
+
name: 'conversation.view',
|
|
408
|
+
id: 'acp',
|
|
409
|
+
order: 20,
|
|
410
|
+
label: () => label('NTR', 'NTR'),
|
|
411
|
+
inject: (sessionId) => chatFace(sessionId),
|
|
412
|
+
}, AcpConversationView));
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
viewEntry = undefined;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (meterEntry === undefined) {
|
|
419
|
+
try {
|
|
420
|
+
meterEntry = ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
|
|
421
|
+
name: 'conversation.composer.dock',
|
|
422
|
+
id: 'acp-context-meter',
|
|
423
|
+
order: 10,
|
|
424
|
+
inject: (sessionId) => chatFace(sessionId),
|
|
425
|
+
}, AcpContextMeter));
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
meterEntry = undefined;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
};
|
|
252
432
|
/**
|
|
253
433
|
* Last diagnostic line from {@link syncAcpMode}. Every way this gate can
|
|
254
434
|
* end with the native seat still on screen used to be silent — an inner
|
|
@@ -261,7 +441,8 @@ export function apply(ctx) {
|
|
|
261
441
|
let seatDiag = 'idle';
|
|
262
442
|
let permissionSeatDiag = 'idle';
|
|
263
443
|
/**
|
|
264
|
-
* Hand
|
|
444
|
+
* Hand every ACP-only surface back: both composer cells, the NTR view tab
|
|
445
|
+
* and the context ring.
|
|
265
446
|
*
|
|
266
447
|
* Every path that is not "this staged conversation is CONFIRMED to run the
|
|
267
448
|
* ACP preset" ends here — including the paths that carry no verdict at all
|
|
@@ -283,6 +464,8 @@ export function apply(ctx) {
|
|
|
283
464
|
// read identically in the one place either is observable.
|
|
284
465
|
permissionSeatDiag = `${reason} (released)`;
|
|
285
466
|
disarmVerdictReask();
|
|
467
|
+
setScopeMarker(false);
|
|
468
|
+
releaseAuxEntries();
|
|
286
469
|
if (claim !== undefined) {
|
|
287
470
|
claim.release();
|
|
288
471
|
claim = undefined;
|
|
@@ -324,7 +507,13 @@ export function apply(ctx) {
|
|
|
324
507
|
standDown('no session list service');
|
|
325
508
|
return;
|
|
326
509
|
}
|
|
327
|
-
|
|
510
|
+
// Prefer the renderer's own current selection; the list's retained rows
|
|
511
|
+
// are the fallback for hosts without the scope adapter. A multi-view
|
|
512
|
+
// layout can leave several rows retained, and judging the first one (or
|
|
513
|
+
// the one the previous switch left behind) would hold the ACP chrome
|
|
514
|
+
// while an ordinary conversation is on screen — the reported drift.
|
|
515
|
+
const current = currentScopeSessionId();
|
|
516
|
+
const staged = stagedSessionOf(snapshot, current);
|
|
328
517
|
if (staged === undefined) {
|
|
329
518
|
standDown('no staged session (nothing retains the main view)');
|
|
330
519
|
return;
|
|
@@ -381,14 +570,24 @@ export function apply(ctx) {
|
|
|
381
570
|
// nothing. Both cells follow the same evidence — the staged
|
|
382
571
|
// conversation's preset — through one {@link standDown}, so neither can
|
|
383
572
|
// stay claimed after the evidence for it stops answering.
|
|
384
|
-
if (!stagedIsAcpMode(snapshot, acpModeVerdict)) {
|
|
573
|
+
if (!stagedIsAcpMode(snapshot, acpModeVerdict, current)) {
|
|
574
|
+
// The row's own verdict is authoritative even when it says no: fold it
|
|
575
|
+
// into the store so every per-conversation surface (the branch
|
|
576
|
+
// button's ACP gate included) stops rendering without waiting for a
|
|
577
|
+
// host read. A silent row folds nothing — that case is the ask path's.
|
|
578
|
+
if (rowAgentPreset(staged.row) !== undefined)
|
|
579
|
+
store.noteAcpMode(staged.sessionId, false);
|
|
385
580
|
standDown(`staged ${staged.sessionId} is not in ACP mode`);
|
|
386
581
|
return;
|
|
387
582
|
}
|
|
388
583
|
// The row speaks, so the host verdict is not this gate's only fact
|
|
389
|
-
// source and no re-ask timer is needed.
|
|
390
|
-
|
|
584
|
+
// source and no re-ask timer is needed. The same fold in the affirmative
|
|
585
|
+
// direction: the runtime row IS the preset truth, and the branch
|
|
586
|
+
// button's gate reads the folded map, not the snapshot.
|
|
587
|
+
if (rowAgentPreset(staged.row) !== undefined) {
|
|
588
|
+
store.noteAcpMode(staged.sessionId, rowAgentPreset(staged.row) === ACP_PRESET_ID);
|
|
391
589
|
disarmVerdictReask();
|
|
590
|
+
}
|
|
392
591
|
// Stays re-runnable on purpose: the seat is not declared before the
|
|
393
592
|
// composer bar mounts, a registration can be refused, and the cell can be
|
|
394
593
|
// taken from us later. The claim reports each of those through the
|
|
@@ -396,6 +595,8 @@ export function apply(ctx) {
|
|
|
396
595
|
// next evaluation (store mutation, list notification, preset frame, or a
|
|
397
596
|
// change to the cell itself) simply tries again.
|
|
398
597
|
seatDiag = `staged ${staged.sessionId} is ACP mode`;
|
|
598
|
+
setScopeMarker(true);
|
|
599
|
+
probeHostActionLabels();
|
|
399
600
|
claim ??= createModelSeatClaim({
|
|
400
601
|
slots: ctx.slots,
|
|
401
602
|
occupant: () => AcpSessionControls,
|
|
@@ -415,6 +616,11 @@ export function apply(ctx) {
|
|
|
415
616
|
onDiag: (line) => { permissionSeatDiag = `staged ${staged.sessionId} · ${line}`; },
|
|
416
617
|
});
|
|
417
618
|
permissionClaim.sync();
|
|
619
|
+
// The tab and the occupancy ring have the same lifetime as the claims:
|
|
620
|
+
// they exist only while this staged conversation is an ACP one, and a
|
|
621
|
+
// stand-down disposes them (a standard conversation must not see an NTR
|
|
622
|
+
// tab, and the host's native bottom row must stand alone there).
|
|
623
|
+
holdAuxEntries();
|
|
418
624
|
// Confirmed ACP on a silent row: the verdict is all the evidence there
|
|
419
625
|
// is, so it gets a re-ask deadline (see {@link ACP_MODE_VERDICT_TTL_MS}).
|
|
420
626
|
if (rowAgentPreset(staged.row) === undefined)
|
|
@@ -472,6 +678,19 @@ export function apply(ctx) {
|
|
|
472
678
|
const list = sessionsList();
|
|
473
679
|
if (list !== undefined && stopWatch === undefined)
|
|
474
680
|
stopWatch = list.subscribe(runGate);
|
|
681
|
+
// The selection can move without the list changing (a tab or sidebar
|
|
682
|
+
// click), and `uiSession.current` is the host's own answer to "which
|
|
683
|
+
// conversation is on screen". Re-run the gate on that transition so the
|
|
684
|
+
// ACP chrome leaves before the newly staged ordinary conversation paints.
|
|
685
|
+
const current = currentScopeSource();
|
|
686
|
+
if (current?.subscribe !== undefined && stopCurrent === undefined) {
|
|
687
|
+
try {
|
|
688
|
+
stopCurrent = current.subscribe(runGate);
|
|
689
|
+
}
|
|
690
|
+
catch {
|
|
691
|
+
stopCurrent = undefined;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
475
694
|
attachPresetFrames();
|
|
476
695
|
runGate();
|
|
477
696
|
};
|
|
@@ -538,7 +757,12 @@ export function apply(ctx) {
|
|
|
538
757
|
stopCell();
|
|
539
758
|
stopPreset?.();
|
|
540
759
|
stopWatch?.();
|
|
760
|
+
stopCurrent?.();
|
|
541
761
|
disarmVerdictReask();
|
|
762
|
+
// The scope marker dies with the gate: a reload (HMR) must not leave the
|
|
763
|
+
// hiding CSS pointed at conversations nothing confirms anymore.
|
|
764
|
+
setScopeMarker(false);
|
|
765
|
+
releaseAuxEntries();
|
|
542
766
|
if (claim !== undefined) {
|
|
543
767
|
claim.release();
|
|
544
768
|
claim = undefined;
|
|
@@ -582,9 +806,9 @@ export function apply(ctx) {
|
|
|
582
806
|
const sources = triggers.live?.sources;
|
|
583
807
|
return Array.isArray(sources) ? sources : undefined;
|
|
584
808
|
},
|
|
585
|
-
verdict: (sessionId) => slashAcpVerdict(sessionsList()?.getSnapshot(), sessionId, id => store.getSnapshot().acpMode[id]),
|
|
809
|
+
verdict: (sessionId) => slashAcpVerdict(sessionsList()?.getSnapshot(), sessionId, id => store.getSnapshot().acpMode[id], currentScopeSessionId()),
|
|
586
810
|
ensureVerdict: async (sessionId) => {
|
|
587
|
-
const held = () => slashAcpVerdict(sessionsList()?.getSnapshot(), sessionId, id => store.getSnapshot().acpMode[id]);
|
|
811
|
+
const held = () => slashAcpVerdict(sessionsList()?.getSnapshot(), sessionId, id => store.getSnapshot().acpMode[id], currentScopeSessionId());
|
|
588
812
|
const known = held();
|
|
589
813
|
if (known !== undefined)
|
|
590
814
|
return known;
|
|
@@ -674,11 +898,10 @@ export function apply(ctx) {
|
|
|
674
898
|
// diffs). Session-scope list entry; the view ring renders it one tab at a
|
|
675
899
|
// time. Meanwhile the agent's prose and thinking ride the NATIVE chat plane
|
|
676
900
|
// through the LLM adapter — see docs/acp-convergence.md §2.1.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}, AcpConversationView)), 'dsh-ntr: ACP view tab');
|
|
901
|
+
//
|
|
902
|
+
// It is installed by the ACP-mode gate above (`holdAuxEntries`) and NOT at
|
|
903
|
+
// plugin load: the host projects every registered `conversation.view` entry
|
|
904
|
+
// into EVERY session's tab strip, so an unconditional registration showed
|
|
905
|
+
// the NTR tab on ordinary conversations too. The gate's lifetime is the
|
|
906
|
+
// only per-session seam the host offers.
|
|
684
907
|
}
|