@aiwayds/dsh-tui-pi 2.23.1 → 2.25.0
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/lib/attention-jev.d.ts +80 -0
- package/lib/attention-jev.js +161 -0
- package/lib/attention-jev.js.map +1 -0
- package/lib/attention.d.ts +122 -0
- package/lib/attention.js +220 -0
- package/lib/attention.js.map +1 -0
- package/lib/dsh-events.d.ts +7 -4
- package/lib/index.js +16 -2
- package/lib/index.js.map +1 -1
- package/lib/live-widgets.js +4 -3
- package/lib/live-widgets.js.map +1 -1
- package/lib/model-list.d.ts +22 -7
- package/lib/model-list.js +34 -10
- package/lib/model-list.js.map +1 -1
- package/lib/session.d.ts +78 -1
- package/lib/session.js +151 -42
- package/lib/session.js.map +1 -1
- package/lib/source-kind.d.ts +2 -2
- package/lib/subagent-policy.d.ts +43 -3
- package/lib/subagent-policy.js +85 -6
- package/lib/subagent-policy.js.map +1 -1
- package/lib/subagent-status-tool.d.ts +8 -0
- package/lib/subagent-status-tool.js +16 -0
- package/lib/subagent-status-tool.js.map +1 -1
- package/lib/subagent-viewer.d.ts +6 -2
- package/lib/subagent-viewer.js +34 -10
- package/lib/subagent-viewer.js.map +1 -1
- package/lib/theme-settings.d.ts +10 -0
- package/lib/theme-settings.js +19 -0
- package/lib/theme-settings.js.map +1 -1
- package/locales/en.json +5 -2
- package/locales/ja.json +5 -2
- package/locales/ko.json +5 -2
- package/locales/zh-CN.json +5 -2
- package/package.json +2 -1
package/lib/model-list.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure model-list assembly for the /model picker — favorites pinned on top,
|
|
3
|
-
* hidden models in a dim section at the bottom, everything between.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* hidden models in a dim section at the bottom, everything between. Every
|
|
4
|
+
* section lists its models aggregated under their provider (providers
|
|
5
|
+
* alphabetical, models alphabetical within a provider). No TUI imports:
|
|
6
|
+
* every function here is plain data-in/data-out, so the ordering, filtering
|
|
7
|
+
* and toggle semantics stay unit-testable without a terminal
|
|
6
8
|
* (test/model-list.test.mjs).
|
|
7
9
|
*/
|
|
8
10
|
/** Composite settings key of a model: `${provider}/${id}`. */
|
|
@@ -18,6 +20,22 @@ export function matchesModelFilter(model, query) {
|
|
|
18
20
|
|| model.id.toLowerCase().includes(needle)
|
|
19
21
|
|| model.provider.toLowerCase().includes(needle));
|
|
20
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Display order of every picker section: providers aggregate alphabetically,
|
|
25
|
+
* and within a provider models sort by the label the MODEL column shows
|
|
26
|
+
* (name, falling back to id) with the id as the tiebreak. The listing
|
|
27
|
+
* arrives from pickModel as a Promise.all race across providers, so the
|
|
28
|
+
* input order interleaves providers by resolve speed — this comparator is
|
|
29
|
+
* what makes the rows deterministic.
|
|
30
|
+
*/
|
|
31
|
+
export function compareListedModels(a, b) {
|
|
32
|
+
const byProvider = a.provider.localeCompare(b.provider);
|
|
33
|
+
if (byProvider !== 0)
|
|
34
|
+
return byProvider;
|
|
35
|
+
const labelA = a.name === '' ? a.id : a.name;
|
|
36
|
+
const labelB = b.name === '' ? b.id : b.name;
|
|
37
|
+
return labelA.localeCompare(labelB) || a.id.localeCompare(b.id);
|
|
38
|
+
}
|
|
21
39
|
/**
|
|
22
40
|
* Toggle `key` in `list`: remove when present, append when absent (join
|
|
23
41
|
* order = favorite order). Returns a new array; the input is never mutated.
|
|
@@ -44,33 +62,39 @@ export function narrowStringList(value) {
|
|
|
44
62
|
/**
|
|
45
63
|
* Assemble the picker rows, top to bottom:
|
|
46
64
|
*
|
|
47
|
-
* 1. Favorites (`section: 'favorite'`, ★ prefix applied by renderCell)
|
|
48
|
-
*
|
|
65
|
+
* 1. Favorites (`section: 'favorite'`, ★ prefix applied by renderCell) —
|
|
66
|
+
* display order is the shared provider-grouped alphabetical order, not
|
|
67
|
+
* the join order of the persisted `favoriteModels` list (persistence
|
|
68
|
+
* keeps join order; only the rendering sorts). A favorited model is
|
|
69
|
+
* removed from every other section;
|
|
49
70
|
* 2. one full-width divider row — only when at least one favorite is visible
|
|
50
71
|
* AND something follows it;
|
|
51
|
-
* 3. the remaining models (`section: 'normal'`) in
|
|
72
|
+
* 3. the remaining models (`section: 'normal'`) in the same order;
|
|
52
73
|
* 4. the Hidden section — only without an active filter and when non-empty:
|
|
53
74
|
* a `hiddenHeader` row carrying the count, then the hidden models
|
|
54
|
-
* (`section: 'hidden'`, rendered dim) that are not also favorited
|
|
75
|
+
* (`section: 'hidden'`, rendered dim) that are not also favorited,
|
|
76
|
+
* likewise in the shared order.
|
|
55
77
|
*
|
|
56
78
|
* An active `filter` keeps the pinned structure but leaves only matching
|
|
57
79
|
* models per section; stale keys in `favorites`/`hidden` (models no longer
|
|
58
80
|
* listed by any provider) are skipped silently.
|
|
59
81
|
*/
|
|
60
82
|
export function buildModelRows(models, favorites, hidden, filter = '') {
|
|
61
|
-
const
|
|
83
|
+
const sorted = [...models].sort(compareListedModels);
|
|
84
|
+
const byKey = new Map(sorted.map(model => [modelKey(model), model]));
|
|
62
85
|
const favKeys = new Set(favorites);
|
|
63
86
|
const hiddenSet = new Set(hidden);
|
|
64
87
|
const favRows = favorites
|
|
65
88
|
.map(key => byKey.get(key))
|
|
66
89
|
.filter((model) => model !== undefined && matchesModelFilter(model, filter))
|
|
90
|
+
.sort(compareListedModels)
|
|
67
91
|
.map(model => ({ kind: 'model', section: 'favorite', model }));
|
|
68
|
-
const normalRows =
|
|
92
|
+
const normalRows = sorted
|
|
69
93
|
.filter(model => !favKeys.has(modelKey(model))
|
|
70
94
|
&& !hiddenSet.has(modelKey(model))
|
|
71
95
|
&& matchesModelFilter(model, filter))
|
|
72
96
|
.map(model => ({ kind: 'model', section: 'normal', model }));
|
|
73
|
-
const hiddenModels =
|
|
97
|
+
const hiddenModels = sorted.filter(model => hiddenSet.has(modelKey(model)) && !favKeys.has(modelKey(model)));
|
|
74
98
|
const showHidden = filter.trim() === '' && hiddenModels.length > 0;
|
|
75
99
|
const rows = [...favRows];
|
|
76
100
|
if (favRows.length > 0 && (normalRows.length > 0 || showHidden)) {
|
package/lib/model-list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-list.js","sourceRoot":"","sources":["../src/model-list.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"model-list.js","sourceRoot":"","sources":["../src/model-list.ts"],"names":[],"mappings":"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"}
|
package/lib/session.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
13
13
|
import type { Agent, AgentHandle, ModelSelection } from '@deepseek-ai/dsh-agent';
|
|
14
14
|
import { type StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
15
15
|
import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session';
|
|
16
|
+
import { type AttentionInfo, type AttentionRow } from './attention.ts';
|
|
17
|
+
import type { AttentionJevSource } from './attention-jev.ts';
|
|
16
18
|
import type { AgentView } from './dsh-events.ts';
|
|
17
19
|
import type { PendingPromptView } from './steer-flow.ts';
|
|
18
20
|
export interface BridgeCallbacks {
|
|
@@ -215,6 +217,48 @@ export declare class DshSessionBridge {
|
|
|
215
217
|
});
|
|
216
218
|
/** Whether a `reconcileLiveWithProjection` pass is already in flight. */
|
|
217
219
|
private projectionReconcileInFlight;
|
|
220
|
+
/**
|
|
221
|
+
* Shared child-adoption gate for the two discovery paths: the firehose
|
|
222
|
+
* `session/event` fold (constructor) and the reconcile tick's live-store
|
|
223
|
+
* sweep (`reconcileLiveFromStore`). A session whose parent is a tracked
|
|
224
|
+
* session AND is marked as delegated is a child; the descriptor event
|
|
225
|
+
* later refines the label/provider.
|
|
226
|
+
*
|
|
227
|
+
* Guard: a child is `origin: 'subagent'` OR a delegation budget
|
|
228
|
+
* `delegationDepth > 0`. Both markers are written together by dsh's
|
|
229
|
+
* childSessionMeta (spawn AND in-process fork children alike), so in
|
|
230
|
+
* practice the origin alone carries the decision; the budget clause
|
|
231
|
+
* only future-proofs the gate against a child that carries the budget
|
|
232
|
+
* without the origin. The budget test MUST be a VALUE test, not a
|
|
233
|
+
* field-presence test: the jsonl persistence backend materialises
|
|
234
|
+
* `delegationDepth: 0` on every restored header (write `?? 0`, read
|
|
235
|
+
* unconditionally), so a presence test would pull user-facing
|
|
236
|
+
* `Session.fork` conversations and other non-child restored sessions
|
|
237
|
+
* onto the live board / Ctrl+G.
|
|
238
|
+
*
|
|
239
|
+
* @returns whether a new view was adopted (caller emits).
|
|
240
|
+
*/
|
|
241
|
+
private adoptChildSession;
|
|
242
|
+
/**
|
|
243
|
+
* Live-store sweep — the corpus-level companion to
|
|
244
|
+
* `reconcileLiveWithProjection` (dsh 0.1.7-rc.2). `listDescendants` walked
|
|
245
|
+
* the complete Session corpus on rc.1 but recurses parent CATALOGS on
|
|
246
|
+
* rc.2, so a child whose catalog branch is corrupt/unreadable — or whose
|
|
247
|
+
* catalog entry is missing entirely — is omitted from the authoritative
|
|
248
|
+
* listing with no diagnostic row of its own. Every still-working child
|
|
249
|
+
* necessarily resides in the process, and the store's live list is the one
|
|
250
|
+
* enumeration that depends on no catalog: this sweep re-runs the shared
|
|
251
|
+
* adoption gate over `sessions.list()` each tick and picks up what both
|
|
252
|
+
* the firehose (burst-lost creation events) and the catalog walk missed.
|
|
253
|
+
* Merge discipline is the bridge-wide one — only ADD (the
|
|
254
|
+
* `agentViews.has` guard makes it idempotent and never resurrects a
|
|
255
|
+
* settled view); rounds/settlement continue through the ordinary fold
|
|
256
|
+
* paths once the child is in `childSessions`/`trackedSessions`. Cost is
|
|
257
|
+
* one header check per process-resident session per tick — no persistence
|
|
258
|
+
* read, and children of previous runs are not resident after a restart,
|
|
259
|
+
* so they cannot be resurrected onto the board.
|
|
260
|
+
*/
|
|
261
|
+
private reconcileLiveFromStore;
|
|
218
262
|
/**
|
|
219
263
|
* Correct the live view against the host's authoritative descendant
|
|
220
264
|
* listing (`SubagentRuntime.listDescendants`, projection-backed — no
|
|
@@ -235,6 +279,13 @@ export declare class DshSessionBridge {
|
|
|
235
279
|
* resurrect a settled continuable (its listing row is `inactive` while it
|
|
236
280
|
* idles between turns). Runs best-effort: an absent/unshaped subagents
|
|
237
281
|
* service or a throwing listing is silently skipped.
|
|
282
|
+
*
|
|
283
|
+
* dsh 0.1.7-rc.2: the listing recurses parent CATALOGS instead of walking
|
|
284
|
+
* the complete Session corpus — a child behind a corrupt/unreadable
|
|
285
|
+
* catalog branch is reported only as a branch diagnostic, and a truly
|
|
286
|
+
* catalog-less orphan is invisible (no row at all). Diagnostic rows are
|
|
287
|
+
* skipped here; the {@link reconcileLiveFromStore} sweep is the
|
|
288
|
+
* corpus-level companion that keeps such children off the blind spot.
|
|
238
289
|
*/
|
|
239
290
|
private reconcileLiveWithProjection;
|
|
240
291
|
/**
|
|
@@ -269,6 +320,28 @@ export declare class DshSessionBridge {
|
|
|
269
320
|
* policy's hard-stop records composed in (they live off-view — see
|
|
270
321
|
* markChildHardStopped). */
|
|
271
322
|
getAgentViews(): readonly AgentView[];
|
|
323
|
+
/**
|
|
324
|
+
* Attach the attention ranking's jev upgrade source (heuristic-only when
|
|
325
|
+
* undefined) and the live maxRounds reader for the ranking's state table.
|
|
326
|
+
* Wired once by the host; the source self-debounces and self-guards.
|
|
327
|
+
*/
|
|
328
|
+
attachAttentionSource(source: AttentionJevSource | undefined, capReader: () => number): void;
|
|
329
|
+
/** Re-observe the attention ranking; driven by the reconcile tick. */
|
|
330
|
+
private updateAttention;
|
|
331
|
+
/** One child's current attention info (heuristic base; jev once it has judged). */
|
|
332
|
+
getAttention(childId: string): AttentionInfo | undefined;
|
|
333
|
+
/** The spin-strike state for one child (the early-stop ladder's input). */
|
|
334
|
+
getSpinState(childId: string): {
|
|
335
|
+
streak: number;
|
|
336
|
+
lastStrikeAt: number;
|
|
337
|
+
};
|
|
338
|
+
/** All attention rows, highest score first (picker sort, status board). */
|
|
339
|
+
getAttentionRows(): readonly AttentionRow[];
|
|
340
|
+
/** The guard deny-copy hint: the top attention-worthy live child, when one ranks above ok. */
|
|
341
|
+
getAttentionHint(): {
|
|
342
|
+
label: string;
|
|
343
|
+
detail: string;
|
|
344
|
+
} | undefined;
|
|
272
345
|
/** Children still running (`outcome` unset) — the live count the guard caps. */
|
|
273
346
|
getLiveChildren(): readonly AgentView[];
|
|
274
347
|
/** The buffered transcript of one child, in log order (capped, maybe truncated). */
|
|
@@ -369,9 +442,13 @@ export declare class DshSessionBridge {
|
|
|
369
442
|
* after a successful `cancelChild`; a stop that found nothing live is
|
|
370
443
|
* deliberately not recorded (the child settled on its own in time).
|
|
371
444
|
*/
|
|
372
|
-
markChildHardStopped(childId: string, round: number, cap: number): void;
|
|
445
|
+
markChildHardStopped(childId: string, round: number, cap: number, early?: true): void;
|
|
373
446
|
/** Hard stops the maxRounds policy executed, keyed by child session id. */
|
|
374
447
|
private readonly hardStopRecords;
|
|
448
|
+
/** Attention ranking (heuristic base + optional jev layer) — see src/attention.ts. */
|
|
449
|
+
private readonly attentionBoard;
|
|
450
|
+
private attentionSource;
|
|
451
|
+
private attentionCapReader;
|
|
375
452
|
/**
|
|
376
453
|
* Whether ANY LLM work is in flight: the bridge's own mid-turn status OR
|
|
377
454
|
* at least one live child. The keymap's running gate reads this — with
|
package/lib/session.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
|
14
14
|
import { SettingsConflictError } from '@deepseek-ai/dsh-settings';
|
|
15
15
|
import { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
16
16
|
import { readAppendSystem } from "./append-system.js";
|
|
17
|
+
import { AttentionBoard } from "./attention.js";
|
|
17
18
|
import { statSync } from 'node:fs';
|
|
18
19
|
import { join } from 'node:path';
|
|
19
20
|
import { descriptorModelRoute, isAgentEnd, isAgentStart, isDcpCompactionNotice, isLlmRetry, isSubagentDescriptor, isTuiPluginInjection } from "./dsh-events.js";
|
|
@@ -355,45 +356,10 @@ export class DshSessionBridge {
|
|
|
355
356
|
const sessionKey = String(session.id);
|
|
356
357
|
// Discover subagent children by session header — the deployment may
|
|
357
358
|
// never emit tool-workflow events (the firehose is not scope-filtered,
|
|
358
|
-
// so child sessions arrive here too).
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
//
|
|
362
|
-
// Guard: a child is `origin: 'subagent'` OR a delegation budget
|
|
363
|
-
// `delegationDepth > 0`. Both markers are written together by dsh's
|
|
364
|
-
// childSessionMeta (spawn AND in-process fork children alike), so in
|
|
365
|
-
// practice the origin alone carries the decision; the budget clause
|
|
366
|
-
// only future-proofs the gate against a child that carries the budget
|
|
367
|
-
// without the origin. The budget test MUST be a VALUE test, not a
|
|
368
|
-
// field-presence test: the jsonl persistence backend materialises
|
|
369
|
-
// `delegationDepth: 0` on every restored header (write `?? 0`, read
|
|
370
|
-
// unconditionally), so a presence test would pull user-facing
|
|
371
|
-
// `Session.fork` conversations and other non-child restored sessions
|
|
372
|
-
// onto the live board / Ctrl+G.
|
|
373
|
-
const header = session.header;
|
|
374
|
-
if (header?.parentSession !== undefined
|
|
375
|
-
&& (header.origin === 'subagent' || (header.delegationDepth ?? 0) > 0)
|
|
376
|
-
&& this.trackedSessions.has(String(header.parentSession))
|
|
377
|
-
&& !this.agentViews.has(sessionKey)) {
|
|
378
|
-
this.agentViews.set(sessionKey, {
|
|
379
|
-
childId: sessionKey,
|
|
380
|
-
parentSession: String(header.parentSession),
|
|
381
|
-
// Current dsh children always carry the origin, so in practice the
|
|
382
|
-
// label is `subagent <id8>`; the `fork` label only fires for the
|
|
383
|
-
// defensive budget-without-origin shape above.
|
|
384
|
-
label: header.origin === undefined
|
|
385
|
-
? `fork ${sessionKey.slice(0, 8)}`
|
|
386
|
-
: `subagent ${sessionKey.slice(0, 8)}`,
|
|
387
|
-
startedAt: event.time,
|
|
388
|
-
tokens: 0,
|
|
389
|
-
rounds: 0,
|
|
390
|
-
retries: 0,
|
|
391
|
-
contextTokens: 0,
|
|
392
|
-
});
|
|
393
|
-
this.childSessions.add(sessionKey);
|
|
394
|
-
this.trackedSessions.add(sessionKey);
|
|
359
|
+
// so child sessions arrive here too). The gate is shared with the
|
|
360
|
+
// reconcile tick's live-store sweep (`reconcileLiveFromStore`).
|
|
361
|
+
if (this.adoptChildSession(session, event.time))
|
|
395
362
|
this.emitLive();
|
|
396
|
-
}
|
|
397
363
|
// Fold workflow + child events for every tracked session.
|
|
398
364
|
if (this.trackedSessions.has(sessionKey)) {
|
|
399
365
|
this.foldTracked(sessionKey, event);
|
|
@@ -436,10 +402,17 @@ export class DshSessionBridge {
|
|
|
436
402
|
// cap must still fire while the child works in the background.
|
|
437
403
|
const reconcile = setInterval(() => {
|
|
438
404
|
try {
|
|
405
|
+
// Live-store sweep first (dsh 0.1.7-rc.2 companion): a child adopted
|
|
406
|
+
// here is in childSessions before the round reconcile scans it, so a
|
|
407
|
+
// recovered child gets its rounds the same tick.
|
|
408
|
+
this.reconcileLiveFromStore();
|
|
439
409
|
this.reconcileChildRounds();
|
|
440
410
|
// The projection-backed reconciliation rides the same cadence but is
|
|
441
411
|
// async and self-guarded (never overlaps itself).
|
|
442
412
|
void this.reconcileLiveWithProjection();
|
|
413
|
+
// Attention ranking re-observe (heuristic recompute is a map walk;
|
|
414
|
+
// the jev layer self-debounces inside its source).
|
|
415
|
+
this.updateAttention();
|
|
443
416
|
}
|
|
444
417
|
catch {
|
|
445
418
|
// A throwing reconcile must never take the process down.
|
|
@@ -450,6 +423,85 @@ export class DshSessionBridge {
|
|
|
450
423
|
}
|
|
451
424
|
/** Whether a `reconcileLiveWithProjection` pass is already in flight. */
|
|
452
425
|
projectionReconcileInFlight = false;
|
|
426
|
+
/**
|
|
427
|
+
* Shared child-adoption gate for the two discovery paths: the firehose
|
|
428
|
+
* `session/event` fold (constructor) and the reconcile tick's live-store
|
|
429
|
+
* sweep (`reconcileLiveFromStore`). A session whose parent is a tracked
|
|
430
|
+
* session AND is marked as delegated is a child; the descriptor event
|
|
431
|
+
* later refines the label/provider.
|
|
432
|
+
*
|
|
433
|
+
* Guard: a child is `origin: 'subagent'` OR a delegation budget
|
|
434
|
+
* `delegationDepth > 0`. Both markers are written together by dsh's
|
|
435
|
+
* childSessionMeta (spawn AND in-process fork children alike), so in
|
|
436
|
+
* practice the origin alone carries the decision; the budget clause
|
|
437
|
+
* only future-proofs the gate against a child that carries the budget
|
|
438
|
+
* without the origin. The budget test MUST be a VALUE test, not a
|
|
439
|
+
* field-presence test: the jsonl persistence backend materialises
|
|
440
|
+
* `delegationDepth: 0` on every restored header (write `?? 0`, read
|
|
441
|
+
* unconditionally), so a presence test would pull user-facing
|
|
442
|
+
* `Session.fork` conversations and other non-child restored sessions
|
|
443
|
+
* onto the live board / Ctrl+G.
|
|
444
|
+
*
|
|
445
|
+
* @returns whether a new view was adopted (caller emits).
|
|
446
|
+
*/
|
|
447
|
+
adoptChildSession(session, startedAt) {
|
|
448
|
+
const sessionKey = String(session.id);
|
|
449
|
+
const header = session.header;
|
|
450
|
+
if (header?.parentSession === undefined
|
|
451
|
+
|| (header.origin !== 'subagent' && (header.delegationDepth ?? 0) <= 0)
|
|
452
|
+
|| !this.trackedSessions.has(String(header.parentSession))
|
|
453
|
+
|| this.agentViews.has(sessionKey))
|
|
454
|
+
return false;
|
|
455
|
+
this.agentViews.set(sessionKey, {
|
|
456
|
+
childId: sessionKey,
|
|
457
|
+
parentSession: String(header.parentSession),
|
|
458
|
+
// Current dsh children always carry the origin, so in practice the
|
|
459
|
+
// label is `subagent <id8>`; the `fork` label only fires for the
|
|
460
|
+
// defensive budget-without-origin shape above.
|
|
461
|
+
label: header.origin === undefined
|
|
462
|
+
? `fork ${sessionKey.slice(0, 8)}`
|
|
463
|
+
: `subagent ${sessionKey.slice(0, 8)}`,
|
|
464
|
+
startedAt,
|
|
465
|
+
tokens: 0,
|
|
466
|
+
rounds: 0,
|
|
467
|
+
retries: 0,
|
|
468
|
+
contextTokens: 0,
|
|
469
|
+
});
|
|
470
|
+
this.childSessions.add(sessionKey);
|
|
471
|
+
this.trackedSessions.add(sessionKey);
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Live-store sweep — the corpus-level companion to
|
|
476
|
+
* `reconcileLiveWithProjection` (dsh 0.1.7-rc.2). `listDescendants` walked
|
|
477
|
+
* the complete Session corpus on rc.1 but recurses parent CATALOGS on
|
|
478
|
+
* rc.2, so a child whose catalog branch is corrupt/unreadable — or whose
|
|
479
|
+
* catalog entry is missing entirely — is omitted from the authoritative
|
|
480
|
+
* listing with no diagnostic row of its own. Every still-working child
|
|
481
|
+
* necessarily resides in the process, and the store's live list is the one
|
|
482
|
+
* enumeration that depends on no catalog: this sweep re-runs the shared
|
|
483
|
+
* adoption gate over `sessions.list()` each tick and picks up what both
|
|
484
|
+
* the firehose (burst-lost creation events) and the catalog walk missed.
|
|
485
|
+
* Merge discipline is the bridge-wide one — only ADD (the
|
|
486
|
+
* `agentViews.has` guard makes it idempotent and never resurrects a
|
|
487
|
+
* settled view); rounds/settlement continue through the ordinary fold
|
|
488
|
+
* paths once the child is in `childSessions`/`trackedSessions`. Cost is
|
|
489
|
+
* one header check per process-resident session per tick — no persistence
|
|
490
|
+
* read, and children of previous runs are not resident after a restart,
|
|
491
|
+
* so they cannot be resurrected onto the board.
|
|
492
|
+
*/
|
|
493
|
+
reconcileLiveFromStore() {
|
|
494
|
+
const sessions = this.ctx.get('sessions');
|
|
495
|
+
if (sessions?.list === undefined)
|
|
496
|
+
return;
|
|
497
|
+
let changed = false;
|
|
498
|
+
for (const session of sessions.list()) {
|
|
499
|
+
if (this.adoptChildSession(session, Date.now()))
|
|
500
|
+
changed = true;
|
|
501
|
+
}
|
|
502
|
+
if (changed)
|
|
503
|
+
this.emitLive();
|
|
504
|
+
}
|
|
453
505
|
/**
|
|
454
506
|
* Correct the live view against the host's authoritative descendant
|
|
455
507
|
* listing (`SubagentRuntime.listDescendants`, projection-backed — no
|
|
@@ -470,6 +522,13 @@ export class DshSessionBridge {
|
|
|
470
522
|
* resurrect a settled continuable (its listing row is `inactive` while it
|
|
471
523
|
* idles between turns). Runs best-effort: an absent/unshaped subagents
|
|
472
524
|
* service or a throwing listing is silently skipped.
|
|
525
|
+
*
|
|
526
|
+
* dsh 0.1.7-rc.2: the listing recurses parent CATALOGS instead of walking
|
|
527
|
+
* the complete Session corpus — a child behind a corrupt/unreadable
|
|
528
|
+
* catalog branch is reported only as a branch diagnostic, and a truly
|
|
529
|
+
* catalog-less orphan is invisible (no row at all). Diagnostic rows are
|
|
530
|
+
* skipped here; the {@link reconcileLiveFromStore} sweep is the
|
|
531
|
+
* corpus-level companion that keeps such children off the blind spot.
|
|
473
532
|
*/
|
|
474
533
|
async reconcileLiveWithProjection() {
|
|
475
534
|
if (this.projectionReconcileInFlight)
|
|
@@ -478,6 +537,9 @@ export class DshSessionBridge {
|
|
|
478
537
|
if (root === undefined)
|
|
479
538
|
return;
|
|
480
539
|
// ctx.get (NOT property access) — see the sessions accessor note above.
|
|
540
|
+
// Inline rc.2 `SubagentDescendantListEntry` union: child rows (catalog
|
|
541
|
+
// walk) and branch diagnostics share the list; `kind` has been on every
|
|
542
|
+
// row since rc.1, so the discriminator is floor-safe.
|
|
481
543
|
const subagents = this.ctx.get('subagents');
|
|
482
544
|
if (subagents?.listDescendants === undefined)
|
|
483
545
|
return;
|
|
@@ -487,13 +549,24 @@ export class DshSessionBridge {
|
|
|
487
549
|
const rootKey = String(root);
|
|
488
550
|
let changed = false;
|
|
489
551
|
for (const entry of entries) {
|
|
552
|
+
// A branch diagnostic (corrupt/unavailable/unsupported) names no
|
|
553
|
+
// child row — skipping it here is what the sweep above covers for.
|
|
554
|
+
if (entry.kind === 'diagnostic')
|
|
555
|
+
continue;
|
|
490
556
|
const childKey = String(entry.id);
|
|
491
|
-
if (childKey === rootKey
|
|
557
|
+
if (childKey === rootKey)
|
|
492
558
|
continue;
|
|
493
559
|
// A running child this bridge never discovered (burst, or its events
|
|
494
560
|
// never bubbled). Register it header-discovery-shaped; descriptor
|
|
495
|
-
// refinement (label/mode/route) follows from the listing itself.
|
|
561
|
+
// refinement (label/mode/route) follows from the listing itself. A
|
|
562
|
+
// child already on the board needs no listing re-add — its own events
|
|
563
|
+
// drive the fold. (The existence guard lives HERE, not on the loop
|
|
564
|
+
// head: the settle arm below must still reach existing views — a
|
|
565
|
+
// loop-head guard made it dead code and stale one-shots lingered on
|
|
566
|
+
// the board holding maxAgents slots.)
|
|
496
567
|
if (entry.activity === 'running') {
|
|
568
|
+
if (this.agentViews.has(childKey))
|
|
569
|
+
continue;
|
|
497
570
|
this.agentViews.set(childKey, {
|
|
498
571
|
childId: childKey,
|
|
499
572
|
parentSession: entry.parentId !== undefined ? String(entry.parentId) : undefined,
|
|
@@ -655,6 +728,38 @@ export class DshSessionBridge {
|
|
|
655
728
|
return hs === undefined || view.hardStop !== undefined ? view : { ...view, hardStop: hs };
|
|
656
729
|
});
|
|
657
730
|
}
|
|
731
|
+
/**
|
|
732
|
+
* Attach the attention ranking's jev upgrade source (heuristic-only when
|
|
733
|
+
* undefined) and the live maxRounds reader for the ranking's state table.
|
|
734
|
+
* Wired once by the host; the source self-debounces and self-guards.
|
|
735
|
+
*/
|
|
736
|
+
attachAttentionSource(source, capReader) {
|
|
737
|
+
this.attentionSource = source;
|
|
738
|
+
this.attentionCapReader = capReader;
|
|
739
|
+
}
|
|
740
|
+
/** Re-observe the attention ranking; driven by the reconcile tick. */
|
|
741
|
+
updateAttention() {
|
|
742
|
+
const views = this.getLiveChildren();
|
|
743
|
+
const cap = this.attentionCapReader?.() ?? 0;
|
|
744
|
+
this.attentionBoard.update(views, cap);
|
|
745
|
+
this.attentionSource?.tick(this.attentionBoard, views, cap);
|
|
746
|
+
}
|
|
747
|
+
/** One child's current attention info (heuristic base; jev once it has judged). */
|
|
748
|
+
getAttention(childId) {
|
|
749
|
+
return this.attentionBoard.get(childId);
|
|
750
|
+
}
|
|
751
|
+
/** The spin-strike state for one child (the early-stop ladder's input). */
|
|
752
|
+
getSpinState(childId) {
|
|
753
|
+
return this.attentionBoard.spinState(childId);
|
|
754
|
+
}
|
|
755
|
+
/** All attention rows, highest score first (picker sort, status board). */
|
|
756
|
+
getAttentionRows() {
|
|
757
|
+
return this.attentionBoard.rows();
|
|
758
|
+
}
|
|
759
|
+
/** The guard deny-copy hint: the top attention-worthy live child, when one ranks above ok. */
|
|
760
|
+
getAttentionHint() {
|
|
761
|
+
return this.attentionBoard.topHint();
|
|
762
|
+
}
|
|
658
763
|
/** Children still running (`outcome` unset) — the live count the guard caps. */
|
|
659
764
|
getLiveChildren() {
|
|
660
765
|
return this.getAgentViews().filter(view => view.outcome === undefined);
|
|
@@ -967,17 +1072,21 @@ export class DshSessionBridge {
|
|
|
967
1072
|
* after a successful `cancelChild`; a stop that found nothing live is
|
|
968
1073
|
* deliberately not recorded (the child settled on its own in time).
|
|
969
1074
|
*/
|
|
970
|
-
markChildHardStopped(childId, round, cap) {
|
|
1075
|
+
markChildHardStopped(childId, round, cap, early) {
|
|
971
1076
|
// The record lives in its OWN map, not on the view: fold updates spread
|
|
972
1077
|
// the view snapshot captured at their own event-dispatch time, and the
|
|
973
1078
|
// cancel races those events — a hardStop folded onto the view object was
|
|
974
1079
|
// observably overwritten by the in-flight `turn/end` spread. The map is
|
|
975
1080
|
// authoritative; getAgentViews composes it onto the returned views.
|
|
976
|
-
this.hardStopRecords.set(childId, { round, cap });
|
|
1081
|
+
this.hardStopRecords.set(childId, { round, cap, ...(early !== undefined ? { early } : {}) });
|
|
977
1082
|
this.emitLive();
|
|
978
1083
|
}
|
|
979
1084
|
/** Hard stops the maxRounds policy executed, keyed by child session id. */
|
|
980
1085
|
hardStopRecords = new Map();
|
|
1086
|
+
/** Attention ranking (heuristic base + optional jev layer) — see src/attention.ts. */
|
|
1087
|
+
attentionBoard = new AttentionBoard();
|
|
1088
|
+
attentionSource;
|
|
1089
|
+
attentionCapReader;
|
|
981
1090
|
/**
|
|
982
1091
|
* Whether ANY LLM work is in flight: the bridge's own mid-turn status OR
|
|
983
1092
|
* at least one live child. The keymap's running gate reads this — with
|