@crazx/dsh-api-session-controller 0.1.5-alpha.1.zw.2 → 0.1.5-alpha.1.zw.3

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/client.js CHANGED
@@ -2179,6 +2179,87 @@ window.__ModuleLoader__.load({
2179
2179
  return refs;
2180
2180
  }
2181
2181
  //#endregion
2182
+ //#region lib/types/client/sessions/selection-history.js
2183
+ function sameSelection(left, right) {
2184
+ return left.sessionId === right.sessionId && left.address?.parentSessionId === right.address?.parentSessionId && left.address?.childSessionId === right.address?.childSessionId;
2185
+ }
2186
+ /**
2187
+ * Bounded LIFO-with-cursor history. Recording a selection made outside
2188
+ * navigation collapses consecutive repeats, truncates the forward tail after
2189
+ * a backtrack, and drops the oldest entry past the bound. Pruning removes
2190
+ * deleted sessions wherever they sit and re-points the cursor onto the same
2191
+ * logical position.
2192
+ */
2193
+ var SelectionHistory = class {
2194
+ limit;
2195
+ /** Oldest-first; the cursor sits ON the current entry (-1 while empty). */
2196
+ #entries = [];
2197
+ #cursor = -1;
2198
+ constructor(limit = 50) {
2199
+ this.limit = limit;
2200
+ }
2201
+ get entries() {
2202
+ return this.#entries;
2203
+ }
2204
+ get cursor() {
2205
+ return this.#cursor;
2206
+ }
2207
+ /** Whether an older entry exists to go back to. */
2208
+ canBack() {
2209
+ return this.#cursor > 0;
2210
+ }
2211
+ /** Whether a newer entry exists to go forward to. */
2212
+ canForward() {
2213
+ return this.#cursor < this.#entries.length - 1;
2214
+ }
2215
+ /**
2216
+ * Record one selection made outside history navigation.
2217
+ * @param entry - the selection (session, addressed subagent, or clear).
2218
+ */
2219
+ record(entry) {
2220
+ const current = this.#entries[this.#cursor];
2221
+ if (current !== void 0 && sameSelection(current, entry)) return;
2222
+ this.#entries.length = this.#cursor + 1;
2223
+ this.#entries.push(entry);
2224
+ if (this.#entries.length > this.limit) this.#entries.shift();
2225
+ this.#cursor = this.#entries.length - 1;
2226
+ }
2227
+ /**
2228
+ * Step one entry back.
2229
+ * @returns the entry to replay, or undefined at the oldest bound.
2230
+ */
2231
+ back() {
2232
+ if (!this.canBack()) return void 0;
2233
+ this.#cursor -= 1;
2234
+ return this.#entries[this.#cursor];
2235
+ }
2236
+ /**
2237
+ * Step one entry forward.
2238
+ * @returns the entry to replay, or undefined at the newest bound.
2239
+ */
2240
+ forward() {
2241
+ if (!this.canForward()) return void 0;
2242
+ this.#cursor += 1;
2243
+ return this.#entries[this.#cursor];
2244
+ }
2245
+ /**
2246
+ * Drop every entry referencing the removed session wherever it sits,
2247
+ * keeping the cursor on the same logical position.
2248
+ * @param sessionId - the removed session.
2249
+ */
2250
+ prune(sessionId) {
2251
+ let removedBeforeCursor = 0;
2252
+ for (let index = this.#entries.length - 1; index >= 0; index -= 1) {
2253
+ const entry = this.#entries[index];
2254
+ if (entry === void 0 || entry.sessionId !== sessionId) continue;
2255
+ this.#entries.splice(index, 1);
2256
+ if (index <= this.#cursor) removedBeforeCursor += 1;
2257
+ }
2258
+ this.#cursor -= removedBeforeCursor;
2259
+ if (this.#cursor > this.#entries.length - 1) this.#cursor = this.#entries.length - 1;
2260
+ }
2261
+ };
2262
+ //#endregion
2182
2263
  //#region lib/types/client/sessions/manager.js
2183
2264
  function sessionSeqCursor(value) {
2184
2265
  return value === -1 ? -1 : SessionSeq(value);
@@ -2229,6 +2310,10 @@ window.__ModuleLoader__.load({
2229
2310
  */
2230
2311
  jobsBySession = /* @__PURE__ */ new Map();
2231
2312
  selected;
2313
+ /** Toolbar back/forward history over selections (session, addressed child, or clear). */
2314
+ selectionHistory = new SelectionHistory();
2315
+ /** While replaying a history entry, the selections it makes must not re-record. */
2316
+ navigatingHistory = false;
2232
2317
  listSnapshotCache;
2233
2318
  /** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
2234
2319
  * object when every field matches — wire refreshes mint all-new summary objects, so identity
@@ -2246,6 +2331,10 @@ window.__ModuleLoader__.load({
2246
2331
  this.remote = remote;
2247
2332
  this.selected = restoredSelection;
2248
2333
  if (restoredAddress !== void 0) this.addresses.set(restoredAddress.childSessionId, restoredAddress);
2334
+ this.selectionHistory.record(restoredSelection === void 0 ? {} : {
2335
+ sessionId: restoredSelection,
2336
+ ...restoredAddress !== void 0 ? { address: restoredAddress } : {}
2337
+ });
2249
2338
  this.listSnapshotCache = this.buildListSnapshot();
2250
2339
  }
2251
2340
  /**
@@ -2259,6 +2348,10 @@ window.__ModuleLoader__.load({
2259
2348
  this.sessions.get(sessionId)?.configureSubagent(address, address === void 0 ? void 0 : this.catalogs.get(address.parentSessionId)?.parentAvailable);
2260
2349
  this.selected = sessionId;
2261
2350
  this.completedNotifications.delete(sessionId);
2351
+ if (!this.navigatingHistory) this.selectionHistory.record(address === void 0 ? { sessionId } : {
2352
+ sessionId,
2353
+ address
2354
+ });
2262
2355
  this.refreshSubagents(sessionId);
2263
2356
  this.notifier.notifyNow();
2264
2357
  }
@@ -2274,14 +2367,69 @@ window.__ModuleLoader__.load({
2274
2367
  this.sessions.get(address.childSessionId)?.configureSubagent(address, catalog?.parentAvailable);
2275
2368
  this.selected = address.childSessionId;
2276
2369
  this.completedNotifications.delete(address.childSessionId);
2370
+ if (!this.navigatingHistory) this.selectionHistory.record({
2371
+ sessionId: address.childSessionId,
2372
+ address
2373
+ });
2277
2374
  this.refreshSubagents(address.childSessionId);
2278
2375
  this.notifier.notifyNow();
2279
2376
  }
2280
2377
  /** Clear the selection (the layout falls to the no-session view state). */
2281
2378
  clearSelection() {
2282
2379
  this.selected = void 0;
2380
+ if (!this.navigatingHistory) this.selectionHistory.record({});
2283
2381
  this.notifier.notifyNow();
2284
2382
  }
2383
+ /** Whether an older selection exists to go back to. */
2384
+ canBack() {
2385
+ return this.selectionHistory.canBack();
2386
+ }
2387
+ /** Whether a newer selection exists to go forward to. */
2388
+ canForward() {
2389
+ return this.selectionHistory.canForward();
2390
+ }
2391
+ /**
2392
+ * Select the previous history entry. Unreachable entries (sessions removed
2393
+ * before their prune landed) are pruned and the walk continues to older
2394
+ * entries; the walk stops silently at the oldest bound.
2395
+ */
2396
+ back() {
2397
+ while (this.selectionHistory.canBack()) {
2398
+ const entry = this.selectionHistory.back();
2399
+ if (entry === void 0) return;
2400
+ if (!this.canReplay(entry)) {
2401
+ if (entry.sessionId !== void 0) this.selectionHistory.prune(entry.sessionId);
2402
+ continue;
2403
+ }
2404
+ this.replaySelection(entry);
2405
+ return;
2406
+ }
2407
+ }
2408
+ /**
2409
+ * Select the next history entry after a back navigation; silently a
2410
+ * no-op at the newest bound.
2411
+ */
2412
+ forward() {
2413
+ const entry = this.selectionHistory.forward();
2414
+ if (entry === void 0) return;
2415
+ if (this.canReplay(entry)) this.replaySelection(entry);
2416
+ else if (entry.sessionId !== void 0) this.selectionHistory.prune(entry.sessionId);
2417
+ }
2418
+ /** Whether a history entry names a session that is still listed or addressed. */
2419
+ canReplay(entry) {
2420
+ if (entry.sessionId === void 0) return true;
2421
+ return this.summaries.some((summary) => summary.sessionId === entry.sessionId) || this.addresses.has(entry.sessionId);
2422
+ }
2423
+ /** Replay one entry without recording it back into the history. */
2424
+ replaySelection(entry) {
2425
+ this.navigatingHistory = true;
2426
+ try {
2427
+ if (entry.sessionId === void 0) this.clearSelection();
2428
+ else this.select(entry.sessionId);
2429
+ } finally {
2430
+ this.navigatingHistory = false;
2431
+ }
2432
+ }
2285
2433
  /**
2286
2434
  * Return the durable catalog address retained for one child.
2287
2435
  * @param sessionId - possible addressed child id.
@@ -2498,6 +2646,7 @@ window.__ModuleLoader__.load({
2498
2646
  let summaries = baseline;
2499
2647
  for (const mutation of mutations) {
2500
2648
  summaries = applyMutation(summaries, mutation);
2649
+ if (mutation.kind === "remove") this.selectionHistory.prune(mutation.sessionId);
2501
2650
  this.summaries = summaries;
2502
2651
  this.syncCompletedNotifications();
2503
2652
  }
@@ -2638,6 +2787,7 @@ window.__ModuleLoader__.load({
2638
2787
  recordMutation(mutation) {
2639
2788
  this.listMutations?.push(mutation);
2640
2789
  this.summaries = applyMutation(this.summaries, mutation);
2790
+ if (mutation.kind === "remove") this.selectionHistory.prune(mutation.sessionId);
2641
2791
  this.syncCompletedNotifications();
2642
2792
  this.notifier.markDirty();
2643
2793
  }
@@ -3132,6 +3282,22 @@ window.__ModuleLoader__.load({
3132
3282
  clear() {
3133
3283
  this.manager.clearSelection();
3134
3284
  }
3285
+ /** Whether the transient selection history has an older entry to go back to. */
3286
+ canBack() {
3287
+ return this.manager.canBack();
3288
+ }
3289
+ /** Whether the transient selection history has a newer entry to go forward to. */
3290
+ canForward() {
3291
+ return this.manager.canForward();
3292
+ }
3293
+ /** Select the previous history entry (prunes unreachable entries on the way). */
3294
+ back() {
3295
+ this.manager.back();
3296
+ }
3297
+ /** Select the next history entry after a back navigation. */
3298
+ forward() {
3299
+ this.manager.forward();
3300
+ }
3135
3301
  /**
3136
3302
  * Refresh the real Session baseline, reusing an in-flight pull.
3137
3303
  * @returns completion of the current or newly started baseline pull.
@@ -65,6 +65,18 @@ export interface ISessions {
65
65
  refreshSubagents(parentSessionId: SessionId): Promise<void>;
66
66
  /** Clear the current selection into the no-session view state. */
67
67
  clear(): void;
68
+ /** Whether the transient selection history has an older entry to go back to. */
69
+ canBack(): boolean;
70
+ /** Whether the transient selection history has a newer entry to go forward to. */
71
+ canForward(): boolean;
72
+ /**
73
+ * Select the previous history entry (bounded, consecutive repeats
74
+ * collapsed, forward truncated by a fresh selection, removed sessions
75
+ * pruned). The cleared no-session state participates as an entry.
76
+ */
77
+ back(): void;
78
+ /** Select the next history entry after a back navigation. */
79
+ forward(): void;
68
80
  /**
69
81
  * Refresh the Host-authoritative Session list.
70
82
  * @returns completion of the current or newly started Session-list refresh.
@@ -84,6 +84,10 @@ export declare class SessionManager {
84
84
  */
85
85
  private readonly jobsBySession;
86
86
  private selected;
87
+ /** Toolbar back/forward history over selections (session, addressed child, or clear). */
88
+ private readonly selectionHistory;
89
+ /** While replaying a history entry, the selections it makes must not re-record. */
90
+ private navigatingHistory;
87
91
  private listSnapshotCache;
88
92
  /** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
89
93
  * object when every field matches — wire refreshes mint all-new summary objects, so identity
@@ -108,6 +112,25 @@ export declare class SessionManager {
108
112
  selectSubagent(address: SubagentAddress): void;
109
113
  /** Clear the selection (the layout falls to the no-session view state). */
110
114
  clearSelection(): void;
115
+ /** Whether an older selection exists to go back to. */
116
+ canBack(): boolean;
117
+ /** Whether a newer selection exists to go forward to. */
118
+ canForward(): boolean;
119
+ /**
120
+ * Select the previous history entry. Unreachable entries (sessions removed
121
+ * before their prune landed) are pruned and the walk continues to older
122
+ * entries; the walk stops silently at the oldest bound.
123
+ */
124
+ back(): void;
125
+ /**
126
+ * Select the next history entry after a back navigation; silently a
127
+ * no-op at the newest bound.
128
+ */
129
+ forward(): void;
130
+ /** Whether a history entry names a session that is still listed or addressed. */
131
+ private canReplay;
132
+ /** Replay one entry without recording it back into the history. */
133
+ private replaySelection;
111
134
  /**
112
135
  * Return the durable catalog address retained for one child.
113
136
  * @param sessionId - possible addressed child id.
@@ -8,6 +8,7 @@ import { flattenLineage } from "./lineage.js";
8
8
  import { Notifier } from "./notifier.js";
9
9
  import { ProjectionValueStore } from "./projection-store.js";
10
10
  import { Session } from "./session.js";
11
+ import { SelectionHistory } from "./selection-history.js";
11
12
  function sessionSeqCursor(value) {
12
13
  return value === -1 ? -1 : SessionSeq(value);
13
14
  }
@@ -57,6 +58,10 @@ export class SessionManager {
57
58
  */
58
59
  jobsBySession = new Map();
59
60
  selected;
61
+ /** Toolbar back/forward history over selections (session, addressed child, or clear). */
62
+ selectionHistory = new SelectionHistory();
63
+ /** While replaying a history entry, the selections it makes must not re-record. */
64
+ navigatingHistory = false;
60
65
  listSnapshotCache;
61
66
  /** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
62
67
  * object when every field matches — wire refreshes mint all-new summary objects, so identity
@@ -75,6 +80,11 @@ export class SessionManager {
75
80
  this.selected = restoredSelection;
76
81
  if (restoredAddress !== undefined)
77
82
  this.addresses.set(restoredAddress.childSessionId, restoredAddress);
83
+ // The restored selection seeds the history so back/forward always has a
84
+ // home position; a cold start without one records the cleared state.
85
+ this.selectionHistory.record(restoredSelection === undefined
86
+ ? {}
87
+ : { sessionId: restoredSelection, ...(restoredAddress !== undefined ? { address: restoredAddress } : {}) });
78
88
  this.listSnapshotCache = this.buildListSnapshot();
79
89
  }
80
90
  // ---- Selection ----
@@ -95,6 +105,8 @@ export class SessionManager {
95
105
  this.selected = sessionId;
96
106
  // Looking at the session consumes its completion reminder (dot clears).
97
107
  this.completedNotifications.delete(sessionId);
108
+ if (!this.navigatingHistory)
109
+ this.selectionHistory.record(address === undefined ? { sessionId } : { sessionId, address });
98
110
  void this.refreshSubagents(sessionId);
99
111
  this.notifier.notifyNow();
100
112
  }
@@ -112,14 +124,79 @@ export class SessionManager {
112
124
  this.sessions.get(address.childSessionId)?.configureSubagent(address, catalog?.parentAvailable);
113
125
  this.selected = address.childSessionId;
114
126
  this.completedNotifications.delete(address.childSessionId);
127
+ if (!this.navigatingHistory)
128
+ this.selectionHistory.record({ sessionId: address.childSessionId, address });
115
129
  void this.refreshSubagents(address.childSessionId);
116
130
  this.notifier.notifyNow();
117
131
  }
118
132
  /** Clear the selection (the layout falls to the no-session view state). */
119
133
  clearSelection() {
120
134
  this.selected = undefined;
135
+ if (!this.navigatingHistory)
136
+ this.selectionHistory.record({});
121
137
  this.notifier.notifyNow();
122
138
  }
139
+ // ---- Selection history (toolbar back/forward) ----
140
+ /** Whether an older selection exists to go back to. */
141
+ canBack() {
142
+ return this.selectionHistory.canBack();
143
+ }
144
+ /** Whether a newer selection exists to go forward to. */
145
+ canForward() {
146
+ return this.selectionHistory.canForward();
147
+ }
148
+ /**
149
+ * Select the previous history entry. Unreachable entries (sessions removed
150
+ * before their prune landed) are pruned and the walk continues to older
151
+ * entries; the walk stops silently at the oldest bound.
152
+ */
153
+ back() {
154
+ while (this.selectionHistory.canBack()) {
155
+ const entry = this.selectionHistory.back();
156
+ if (entry === undefined)
157
+ return;
158
+ if (!this.canReplay(entry)) {
159
+ if (entry.sessionId !== undefined)
160
+ this.selectionHistory.prune(entry.sessionId);
161
+ continue;
162
+ }
163
+ this.replaySelection(entry);
164
+ return;
165
+ }
166
+ }
167
+ /**
168
+ * Select the next history entry after a back navigation; silently a
169
+ * no-op at the newest bound.
170
+ */
171
+ forward() {
172
+ const entry = this.selectionHistory.forward();
173
+ if (entry === undefined)
174
+ return;
175
+ if (this.canReplay(entry))
176
+ this.replaySelection(entry);
177
+ else if (entry.sessionId !== undefined)
178
+ this.selectionHistory.prune(entry.sessionId);
179
+ }
180
+ /** Whether a history entry names a session that is still listed or addressed. */
181
+ canReplay(entry) {
182
+ if (entry.sessionId === undefined)
183
+ return true;
184
+ return this.summaries.some(summary => summary.sessionId === entry.sessionId)
185
+ || this.addresses.has(entry.sessionId);
186
+ }
187
+ /** Replay one entry without recording it back into the history. */
188
+ replaySelection(entry) {
189
+ this.navigatingHistory = true;
190
+ try {
191
+ if (entry.sessionId === undefined)
192
+ this.clearSelection();
193
+ else
194
+ this.select(entry.sessionId);
195
+ }
196
+ finally {
197
+ this.navigatingHistory = false;
198
+ }
199
+ }
123
200
  /**
124
201
  * Return the durable catalog address retained for one child.
125
202
  * @param sessionId - possible addressed child id.
@@ -379,6 +456,8 @@ export class SessionManager {
379
456
  let summaries = baseline;
380
457
  for (const mutation of mutations) {
381
458
  summaries = applyMutation(summaries, mutation);
459
+ if (mutation.kind === 'remove')
460
+ this.selectionHistory.prune(mutation.sessionId);
382
461
  this.summaries = summaries;
383
462
  this.syncCompletedNotifications();
384
463
  }
@@ -524,6 +603,9 @@ export class SessionManager {
524
603
  recordMutation(mutation) {
525
604
  this.listMutations?.push(mutation);
526
605
  this.summaries = applyMutation(this.summaries, mutation);
606
+ // A removal forgets the session's toolbar back/forward history entries.
607
+ if (mutation.kind === 'remove')
608
+ this.selectionHistory.prune(mutation.sessionId);
527
609
  // Eager edge reconciliation — a snapshot-build-time pass would miss consecutive status frames.
528
610
  this.syncCompletedNotifications();
529
611
  this.notifier.markDirty();
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Bounded transient selection history behind the desktop toolbar's
3
+ * back/forward navigation. Entries record WHICH selection was made — a
4
+ * listed session, an addressed subagent, or the cleared no-session state —
5
+ * never when, and never any session content. Browser-local and transient by
6
+ * design: nothing here is persisted, so a reload starts empty.
7
+ */
8
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
9
+ import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client';
10
+ /** One selection-history entry; `sessionId: undefined` is the cleared state. */
11
+ export interface SelectionEntry {
12
+ readonly sessionId?: SessionId;
13
+ /** Retained direct-parent address for an addressed subagent selection. */
14
+ readonly address?: SubagentAddress;
15
+ }
16
+ /**
17
+ * Bounded LIFO-with-cursor history. Recording a selection made outside
18
+ * navigation collapses consecutive repeats, truncates the forward tail after
19
+ * a backtrack, and drops the oldest entry past the bound. Pruning removes
20
+ * deleted sessions wherever they sit and re-points the cursor onto the same
21
+ * logical position.
22
+ */
23
+ export declare class SelectionHistory {
24
+ #private;
25
+ private readonly limit;
26
+ constructor(limit?: number);
27
+ get entries(): readonly SelectionEntry[];
28
+ get cursor(): number;
29
+ /** Whether an older entry exists to go back to. */
30
+ canBack(): boolean;
31
+ /** Whether a newer entry exists to go forward to. */
32
+ canForward(): boolean;
33
+ /**
34
+ * Record one selection made outside history navigation.
35
+ * @param entry - the selection (session, addressed subagent, or clear).
36
+ */
37
+ record(entry: SelectionEntry): void;
38
+ /**
39
+ * Step one entry back.
40
+ * @returns the entry to replay, or undefined at the oldest bound.
41
+ */
42
+ back(): SelectionEntry | undefined;
43
+ /**
44
+ * Step one entry forward.
45
+ * @returns the entry to replay, or undefined at the newest bound.
46
+ */
47
+ forward(): SelectionEntry | undefined;
48
+ /**
49
+ * Drop every entry referencing the removed session wherever it sits,
50
+ * keeping the cursor on the same logical position.
51
+ * @param sessionId - the removed session.
52
+ */
53
+ prune(sessionId: SessionId): void;
54
+ }
55
+ //# sourceMappingURL=selection-history.d.ts.map
@@ -0,0 +1,91 @@
1
+ function sameSelection(left, right) {
2
+ return left.sessionId === right.sessionId
3
+ && left.address?.parentSessionId === right.address?.parentSessionId
4
+ && left.address?.childSessionId === right.address?.childSessionId;
5
+ }
6
+ /**
7
+ * Bounded LIFO-with-cursor history. Recording a selection made outside
8
+ * navigation collapses consecutive repeats, truncates the forward tail after
9
+ * a backtrack, and drops the oldest entry past the bound. Pruning removes
10
+ * deleted sessions wherever they sit and re-points the cursor onto the same
11
+ * logical position.
12
+ */
13
+ export class SelectionHistory {
14
+ limit;
15
+ /** Oldest-first; the cursor sits ON the current entry (-1 while empty). */
16
+ #entries = [];
17
+ #cursor = -1;
18
+ constructor(limit = 50) {
19
+ this.limit = limit;
20
+ }
21
+ get entries() {
22
+ return this.#entries;
23
+ }
24
+ get cursor() {
25
+ return this.#cursor;
26
+ }
27
+ /** Whether an older entry exists to go back to. */
28
+ canBack() {
29
+ return this.#cursor > 0;
30
+ }
31
+ /** Whether a newer entry exists to go forward to. */
32
+ canForward() {
33
+ return this.#cursor < this.#entries.length - 1;
34
+ }
35
+ /**
36
+ * Record one selection made outside history navigation.
37
+ * @param entry - the selection (session, addressed subagent, or clear).
38
+ */
39
+ record(entry) {
40
+ const current = this.#entries[this.#cursor];
41
+ if (current !== undefined && sameSelection(current, entry))
42
+ return;
43
+ this.#entries.length = this.#cursor + 1;
44
+ this.#entries.push(entry);
45
+ if (this.#entries.length > this.limit)
46
+ this.#entries.shift();
47
+ this.#cursor = this.#entries.length - 1;
48
+ }
49
+ /**
50
+ * Step one entry back.
51
+ * @returns the entry to replay, or undefined at the oldest bound.
52
+ */
53
+ back() {
54
+ if (!this.canBack())
55
+ return undefined;
56
+ this.#cursor -= 1;
57
+ const entry = this.#entries[this.#cursor];
58
+ return entry;
59
+ }
60
+ /**
61
+ * Step one entry forward.
62
+ * @returns the entry to replay, or undefined at the newest bound.
63
+ */
64
+ forward() {
65
+ if (!this.canForward())
66
+ return undefined;
67
+ this.#cursor += 1;
68
+ const entry = this.#entries[this.#cursor];
69
+ return entry;
70
+ }
71
+ /**
72
+ * Drop every entry referencing the removed session wherever it sits,
73
+ * keeping the cursor on the same logical position.
74
+ * @param sessionId - the removed session.
75
+ */
76
+ prune(sessionId) {
77
+ let removedBeforeCursor = 0;
78
+ for (let index = this.#entries.length - 1; index >= 0; index -= 1) {
79
+ const entry = this.#entries[index];
80
+ if (entry === undefined || entry.sessionId !== sessionId)
81
+ continue;
82
+ this.#entries.splice(index, 1);
83
+ if (index <= this.#cursor)
84
+ removedBeforeCursor += 1;
85
+ }
86
+ this.#cursor -= removedBeforeCursor;
87
+ if (this.#cursor > this.#entries.length - 1)
88
+ this.#cursor = this.#entries.length - 1;
89
+ }
90
+ }
91
+ //# sourceMappingURL=selection-history.js.map
@@ -185,6 +185,14 @@ export declare class ClientSessions implements ISessions {
185
185
  * per the masked-gap contract until the next open() moves the stage.
186
186
  */
187
187
  clear(): void;
188
+ /** Whether the transient selection history has an older entry to go back to. */
189
+ canBack(): boolean;
190
+ /** Whether the transient selection history has a newer entry to go forward to. */
191
+ canForward(): boolean;
192
+ /** Select the previous history entry (prunes unreachable entries on the way). */
193
+ back(): void;
194
+ /** Select the next history entry after a back navigation. */
195
+ forward(): void;
188
196
  /**
189
197
  * Refresh the real Session baseline, reusing an in-flight pull.
190
198
  * @returns completion of the current or newly started baseline pull.
@@ -193,6 +193,22 @@ export class ClientSessions {
193
193
  clear() {
194
194
  this.manager.clearSelection();
195
195
  }
196
+ /** Whether the transient selection history has an older entry to go back to. */
197
+ canBack() {
198
+ return this.manager.canBack();
199
+ }
200
+ /** Whether the transient selection history has a newer entry to go forward to. */
201
+ canForward() {
202
+ return this.manager.canForward();
203
+ }
204
+ /** Select the previous history entry (prunes unreachable entries on the way). */
205
+ back() {
206
+ this.manager.back();
207
+ }
208
+ /** Select the next history entry after a back navigation. */
209
+ forward() {
210
+ this.manager.forward();
211
+ }
196
212
  /**
197
213
  * Refresh the real Session baseline, reusing an in-flight pull.
198
214
  * @returns completion of the current or newly started baseline pull.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@crazx/dsh-api-session-controller",
3
3
  "description": "Session Remote commands, cold reads, and live control transport",
4
- "version": "0.1.5-alpha.1.zw.2",
4
+ "version": "0.1.5-alpha.1.zw.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -73,7 +73,7 @@
73
73
  "peerDependencies": {
74
74
  "@deepseek-ai/cordis": "^4.0.2",
75
75
  "@deepseek-ai/dsh-agent": "^0.1.5-alpha.1",
76
- "@deepseek-ai/dsh-agent-default-model": "npm:@crazx/dsh-agent-default-model@0.1.5-alpha.1.zw.2",
76
+ "@deepseek-ai/dsh-agent-default-model": "npm:@crazx/dsh-agent-default-model@0.1.5-alpha.1.zw.3",
77
77
  "@deepseek-ai/dsh-agent-presets": "^0.1.5-alpha.1",
78
78
  "@deepseek-ai/dsh-api-gateway": "^0.1.5-alpha.1",
79
79
  "@deepseek-ai/dsh-attachment": "^0.1.5-alpha.1",
@@ -115,7 +115,7 @@
115
115
  "devDependencies": {
116
116
  "@deepseek-ai/cordis": "^4.0.2",
117
117
  "@deepseek-ai/dsh-agent": "^0.1.5-alpha.1",
118
- "@deepseek-ai/dsh-agent-default-model": "npm:@crazx/dsh-agent-default-model@0.1.5-alpha.1.zw.2",
118
+ "@deepseek-ai/dsh-agent-default-model": "npm:@crazx/dsh-agent-default-model@0.1.5-alpha.1.zw.3",
119
119
  "@deepseek-ai/dsh-agent-loop": "^0.1.5-alpha.1",
120
120
  "@deepseek-ai/dsh-agent-loop-testkit": "^0.1.5-alpha.1",
121
121
  "@deepseek-ai/dsh-agent-presets": "^0.1.5-alpha.1",