@itookit/dsht 0.2.1 → 0.3.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/dist/cli.js CHANGED
@@ -12,6 +12,7 @@ import { App } from "./app.js";
12
12
  import { sessionLabel } from "./navigation.js";
13
13
  import { CookieStore, login } from "./auth.js";
14
14
  import { Client } from "./client.js";
15
+ import { historyLimits } from "./memory.js";
15
16
  import { Controller } from "./controller.js";
16
17
  import { endpoint } from "./endpoint.js";
17
18
  import { errorText, safeText, string } from "./wire.js";
@@ -23,6 +24,8 @@ With no command, choose a workspace and session interactively.
23
24
  --workspace <id> Filter list sessions by workspace
24
25
  --session <id> Open a session directly
25
26
  --auth-dir <path> Private cookie directory (or DSHT_AUTH_DIR)
27
+ --history-records <n> Soft history record limit (default 2000)
28
+ --history-mb <n> Soft history payload budget in MiB (default 16)
26
29
  --json Print machine-readable list output
27
30
  --help Show this help
28
31
 
@@ -39,6 +42,7 @@ Examples:
39
42
  async function main() {
40
43
  const { values, positionals } = parseArgs({ allowPositionals: true, options: {
41
44
  url: { type: 'string', default: process.env.DSH_URL ?? 'http://127.0.0.1:3080' },
45
+ 'history-records': { type: 'string' }, 'history-mb': { type: 'string' },
42
46
  workspace: { type: 'string' }, session: { type: 'string' }, 'auth-dir': { type: 'string' }, json: { type: 'boolean' }, help: { type: 'boolean' },
43
47
  } });
44
48
  if (values.help) {
@@ -52,6 +56,7 @@ async function main() {
52
56
  throw new Error('--json and --workspace apply to list commands');
53
57
  if (list && values.session)
54
58
  throw new Error('--session applies to interactive mode');
59
+ const limits = historyLimits(values['history-records'], values['history-mb']);
55
60
  const { url, token } = endpoint(values.url, process.env.DSH_TOKEN);
56
61
  const store = new CookieStore(values['auth-dir']);
57
62
  if (list) {
@@ -91,7 +96,7 @@ async function main() {
91
96
  const costDirectory = join(process.env.DSHT_STATE_DIR ?? join(process.env.XDG_STATE_HOME ?? join(homedir(), '.local', 'state'), 'dsht'), 'cost', createHash('sha256').update(new URL(url).origin).digest('hex'));
92
97
  const costs = new CostLedger(prices, costDirectory);
93
98
  await costs.load();
94
- const controller = new Controller(url, token, values.session, undefined, client => login(client, token, store), costs);
99
+ const controller = new Controller(url, token, values.session, undefined, client => login(client, token, store), costs, limits);
95
100
  const app = render(_jsx(App, { controller: controller }), { exitOnCtrlC: false });
96
101
  const terminate = () => app.unmount();
97
102
  process.once('SIGTERM', terminate);
package/dist/client.d.ts CHANGED
@@ -37,17 +37,26 @@ export declare class Client {
37
37
  cookie: string;
38
38
  expiresAt: number;
39
39
  } | undefined;
40
+ /** Download the authenticated host ZIP without using a mutation or ordinary RPC deadline.
41
+ * @param sessionId - Selected session identity.
42
+ * @param signal - Cancels the streaming response.
43
+ * @returns Response whose body the caller must consume or cancel.
44
+ */
45
+ sessionLog(sessionId: string, signal: AbortSignal): Promise<Response>;
40
46
  /** Invoke an exact endpoint once. Mutations are never automatically retried.
41
47
  * @param endpoint - Namespace/method endpoint.
42
48
  * @param args - Host parameter names and JSON values.
43
49
  * @param signal - Optional caller cancellation, combined with client close and timeout.
50
+ * @param timeoutMs - Per-call timeout; null waits for caller cancellation or client close.
44
51
  * @returns The decoded result value; HTTP, remote, and cancellation errors reject.
45
52
  */
46
- call(endpoint: string, args?: ObjectValue, signal?: AbortSignal): Promise<Json | undefined>;
53
+ call(endpoint: string, args?: ObjectValue, signal?: AbortSignal, timeoutMs?: number | null): Promise<Json | undefined>;
47
54
  /** Connect the physical mux. A disconnected instance may reconnect with its cookie. */
48
55
  connect(): Promise<void>;
49
56
  /** Subscribe on the existing mux; each subscription has a fresh stream identity. */
50
57
  subscribe(endpoint: string, args: ObjectValue, listener: Listener): Subscription;
58
+ /** Archived session IDs from the latest authoritative workspace baseline. */
59
+ archivedSessionIds: ReadonlySet<string>;
51
60
  /** List workspaces by consuming and cancelling the authoritative opening baseline. */
52
61
  listWorkspaces(): Promise<ObjectValue[]>;
53
62
  /** List visible sessions, optionally filtering by the workspace's accounted IDs. */
package/dist/client.js CHANGED
@@ -69,18 +69,35 @@ export class Client {
69
69
  get persistentCookie() {
70
70
  return this.expiresAt === undefined ? undefined : { cookie: this.cookie, expiresAt: this.expiresAt };
71
71
  }
72
+ /** Download the authenticated host ZIP without using a mutation or ordinary RPC deadline.
73
+ * @param sessionId - Selected session identity.
74
+ * @param signal - Cancels the streaming response.
75
+ * @returns Response whose body the caller must consume or cancel.
76
+ */
77
+ async sessionLog(sessionId, signal) {
78
+ const url = new URL('/api/session.export', this.base);
79
+ url.searchParams.set('sessionId', sessionId);
80
+ const response = await fetch(url, { redirect: 'error', headers: { cookie: this.cookie },
81
+ signal: AbortSignal.any([this.lifetime.signal, signal]) });
82
+ if (!response.ok) {
83
+ await response.body?.cancel();
84
+ throw new HttpError(response.status, 'Session log export');
85
+ }
86
+ return response;
87
+ }
72
88
  /** Invoke an exact endpoint once. Mutations are never automatically retried.
73
89
  * @param endpoint - Namespace/method endpoint.
74
90
  * @param args - Host parameter names and JSON values.
75
91
  * @param signal - Optional caller cancellation, combined with client close and timeout.
92
+ * @param timeoutMs - Per-call timeout; null waits for caller cancellation or client close.
76
93
  * @returns The decoded result value; HTTP, remote, and cancellation errors reject.
77
94
  */
78
- async call(endpoint, args = {}, signal) {
95
+ async call(endpoint, args = {}, signal, timeoutMs = this.timeoutMs) {
79
96
  if (!/^[\w$-]+\/[\w$-]+$/.test(endpoint))
80
97
  throw new Error('Invalid RPC endpoint');
81
98
  const rpcId = randomUUID();
82
99
  const response = await fetch(new URL(`/api/${endpoint}`, this.base), {
83
- method: 'POST', redirect: 'error', signal: signal ? AbortSignal.any([this.signal(), signal]) : this.signal(),
100
+ method: 'POST', redirect: 'error', signal: signal ? AbortSignal.any([this.signal(timeoutMs), signal]) : this.signal(timeoutMs),
84
101
  headers: { 'content-type': 'application/json', cookie: this.cookie },
85
102
  body: JSON.stringify({ type: 'client-request', rpcId, method: endpoint, payload: { args } }),
86
103
  });
@@ -160,6 +177,8 @@ export class Client {
160
177
  socket.send(JSON.stringify({ type: 'cancel', streamId }));
161
178
  } };
162
179
  }
180
+ /** Archived session IDs from the latest authoritative workspace baseline. */
181
+ archivedSessionIds = new Set();
163
182
  /** List workspaces by consuming and cancelling the authoritative opening baseline. */
164
183
  async listWorkspaces() {
165
184
  return new Promise((resolve, reject) => {
@@ -174,6 +193,7 @@ export class Client {
174
193
  const frame = object(value);
175
194
  if (frame.type !== 'baseline')
176
195
  throw new Error('Workspace stream omitted its baseline');
196
+ this.archivedSessionIds = new Set(array(object(frame.value).archivedSessionIds).map(string));
177
197
  resolve(array(object(frame.value).items).map(object));
178
198
  }
179
199
  catch (error) {
@@ -212,8 +232,8 @@ export class Client {
212
232
  await closed;
213
233
  }
214
234
  }
215
- signal() {
216
- return AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(this.timeoutMs)]);
235
+ signal(timeoutMs = this.timeoutMs) {
236
+ return timeoutMs === null ? this.lifetime.signal : AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(timeoutMs)]);
217
237
  }
218
238
  fail(error) {
219
239
  const listeners = [...this.listeners.values()];
@@ -2,8 +2,17 @@ import { Client } from './client.ts';
2
2
  import { CostLedger } from './cost.ts';
3
3
  import { Telemetry } from './telemetry.ts';
4
4
  import { Transcript } from './transcript.ts';
5
+ import { type HistoryLimits } from './memory.ts';
5
6
  import { type FileReference } from './references.ts';
6
7
  import { type Json, type ObjectValue } from './wire.ts';
8
+ /** Resolved navigation-removal identity; empty marks a fresh blank, idle session eligible for immediate archival. */
9
+ export interface RemovalTarget {
10
+ kind: 'workspace' | 'session';
11
+ id: string;
12
+ name: string;
13
+ path?: string;
14
+ empty?: boolean;
15
+ }
7
16
  /** State shared by the picker and conversation view. */
8
17
  export interface State {
9
18
  version: number;
@@ -20,9 +29,28 @@ export interface State {
20
29
  pending: ObjectValue[];
21
30
  controlError?: string;
22
31
  modelError?: string;
32
+ presetError?: string;
33
+ presets?: ObjectValue[];
23
34
  defaultModel?: ObjectValue;
24
35
  transcript: Transcript;
25
36
  }
37
+ /** Bounded search results contain navigation summaries, never complete message bodies. */
38
+ export interface HistorySearch {
39
+ items: {
40
+ seq: number;
41
+ role: string;
42
+ preview: string;
43
+ }[];
44
+ truncated: boolean;
45
+ }
46
+ /** Wire addresses for one `session/list` row, in the order the cost scan should try them.
47
+ *
48
+ * A subagent child is reachable only under its durable parent, and the list row omits the delivery
49
+ * mode, so both modes are offered with the continuable form first.
50
+ * @param session - One row from the host session list.
51
+ * @returns One plain-session address, or both subagent forms when the row is a child.
52
+ */
53
+ export declare function costAddresses(session: ObjectValue): ObjectValue[];
26
54
  /** Owns reconnects and subscriptions. User commands remain single-attempt operations. */
27
55
  export declare class Controller {
28
56
  readonly base: string;
@@ -30,8 +58,10 @@ export declare class Controller {
30
58
  private makeClient;
31
59
  private authenticate;
32
60
  readonly costs?: CostLedger | undefined;
61
+ readonly historyLimits: HistoryLimits;
33
62
  state: State;
34
63
  private observers;
64
+ private interactions;
35
65
  private abort;
36
66
  private client;
37
67
  private clientId;
@@ -39,8 +69,10 @@ export declare class Controller {
39
69
  private runTask;
40
70
  private generationFailed;
41
71
  private selection;
72
+ private historyPinned;
42
73
  telemetry: Telemetry;
43
74
  private observedRunningAt;
75
+ private presetClient?;
44
76
  private catalogRevision;
45
77
  private catalogTasks;
46
78
  private runningUpdates;
@@ -55,6 +87,20 @@ export declare class Controller {
55
87
  get running(): boolean;
56
88
  /** Current title projection, falling back to the list title and then the session ID. */
57
89
  get sessionName(): string | undefined;
90
+ /** Current agent-preset name, matching the web header's built-in labels and custom metadata. */
91
+ get sessionMode(): string | undefined;
92
+ /** Load the optional preset roster once per connection, only when a session names a preset. */
93
+ loadPresetNames(): void;
94
+ /** Fetch current model routes and adapter-owned reasoning choices for the selected session.
95
+ * @returns Host catalog; provider failures remain available to the selector.
96
+ */
97
+ modelCatalog(): Promise<ObjectValue>;
98
+ /** Select the next request's model; the host also attempts to save its deployment default.
99
+ * @param provider - Host provider route ID.
100
+ * @param model - Exact model ID.
101
+ * @param reasoningEffort - Optional adapter-owned effort ID; omission uses its default.
102
+ */
103
+ selectModel(provider: string, model: string, reasoningEffort?: string): Promise<void>;
58
104
  /** Epoch start from the retained turn log, or when this client first observed the run. */
59
105
  get workingSince(): number | undefined;
60
106
  /** Stop the selected turn, or allow exit only while idle. Repeated keys share one request.
@@ -62,7 +108,7 @@ export declare class Controller {
62
108
  * @returns True when the caller may exit; cancellation failures retain the client.
63
109
  */
64
110
  interrupt(force?: boolean): Promise<boolean>;
65
- constructor(base: string, token: string | undefined, initialSession?: string | undefined, makeClient?: () => Client, authenticate?: (client: Client) => Promise<void>, costs?: CostLedger | undefined);
111
+ constructor(base: string, token: string | undefined, initialSession?: string | undefined, makeClient?: () => Client, authenticate?: (client: Client) => Promise<void>, costs?: CostLedger | undefined, historyLimits?: HistoryLimits);
66
112
  /** React-compatible state subscription. */
67
113
  subscribe: (listener: () => void) => (() => void);
68
114
  /** Snapshot identity changes only when the controller publishes. */
@@ -71,6 +117,12 @@ export declare class Controller {
71
117
  start(): void;
72
118
  /** Cancel retries and HTTP, close the socket, and wait for the loop to settle. */
73
119
  stop(): Promise<void>;
120
+ /** Keep history stable while the user reads, searches, or expands it; trim again at the live tail.
121
+ * @param pinned - Whether the main transcript is actively being read away from its tail.
122
+ */
123
+ pinHistory(pinned: boolean): void;
124
+ private reclaimHistory;
125
+ private releaseTranscript;
74
126
  /** Stop the selected turn and then close, so quitting does not leave host work running.
75
127
  * An idle session stays untouched, and an in-flight cancellation is awaited rather than repeated.
76
128
  */
@@ -81,8 +133,22 @@ export declare class Controller {
81
133
  * @param signal - Optional cancellation for an explicit /cost refresh.
82
134
  */
83
135
  refreshCosts(signal?: AbortSignal): Promise<void>;
136
+ /** Read one session's complete cost history, retrying a subagent child with its other delivery mode. */
137
+ private sessionCostHistory;
138
+ /** Page one addressed session's history into the billing events the ledger folds. */
139
+ private readCostHistory;
84
140
  /** Refresh both lists from the host, then show the requested picker. */
85
141
  showPicker(screen: 'workspaces' | 'sessions'): Promise<void>;
142
+ /** Resolve a removal command to one reviewable object without changing the selection.
143
+ * @param kind - Workspace registration removal or session archival.
144
+ * @param query - Exact name, ID, or unambiguous ID prefix.
145
+ * @returns The fixed identity and display details for confirmation.
146
+ */
147
+ removalTarget(kind: 'workspace' | 'session', query: string): Promise<RemovalTarget>;
148
+ /** Apply a confirmed removal or freshly verified empty-session archival; directories and logs are preserved.
149
+ * @param target - Exact workspace or session identity reviewed by the user.
150
+ */
151
+ removeTarget(target: RemovalTarget): Promise<void>;
86
152
  /** Pick a workspace, or use all sessions when the identity is omitted. */
87
153
  pickWorkspace(workspaceId?: string): void;
88
154
  /** Open a workspace picker, or resolve a workspace by ID, exact title/path, or unique ID prefix. */
@@ -117,12 +183,40 @@ export declare class Controller {
117
183
  * @returns Validated candidates in host order.
118
184
  */
119
185
  references(query: string, signal: AbortSignal): Promise<FileReference[]>;
120
- /** Admit a prompt once; a failed response can have an uncertain delivery outcome. */
121
- prompt(text: string, mode?: 'queue' | 'steer'): Promise<void>;
186
+ /** Execute a human command directly, outside the model prompt queue.
187
+ * @param line - Complete slash command, including arguments.
188
+ * @param signal - Cancels the request while the host performs compaction.
189
+ * @returns The host's successful command result text.
190
+ */
191
+ command(line: string, signal: AbortSignal): Promise<string>;
192
+ /** Remove one host-owned pending input; an already claimed item reports a host error.
193
+ * @param itemId - Queue occurrence identity from session/control.
194
+ */
195
+ removeQueued(itemId: string): Promise<void>;
196
+ /** Export the selected host log to a new local ZIP file.
197
+ * @param path - Optional local destination; existing files are never overwritten.
198
+ * @param signal - Cancels the download and removes an incomplete file.
199
+ * @returns Absolute saved filename.
200
+ */
201
+ exportLog(path: string | undefined, signal: AbortSignal): Promise<string>;
202
+ /** Admit text once as steering while running, or a new turn while idle; a lost response can leave delivery uncertain. */
203
+ prompt(text: string): Promise<void>;
122
204
  /** Cancel the active turn; pending queue items remain host-owned. */
123
205
  cancelTurn(): Promise<void>;
124
206
  /** Add a page before the retained window using its fixed opening cut. */
125
- older(signal?: AbortSignal): Promise<void>;
207
+ older(signal?: AbortSignal, transcript?: Transcript): Promise<void>;
208
+ /** Search one page at a time, preserving only the first 200 matches and releasing temporary content.
209
+ * @param query - Literal, case-insensitive text including folded reasoning.
210
+ * @param signal - Cancels HTTP and processing without cancelling the agent.
211
+ * @returns Newest-first bounded summaries and an explicit truncation flag.
212
+ */
213
+ searchHistory(query: string, signal: AbortSignal): Promise<HistorySearch>;
214
+ /** Load a separate small window ending at a search target; the live transcript keeps following.
215
+ * @param target - Durable message sequence to display.
216
+ * @param signal - Cancels the target-page request.
217
+ * @returns A caller-owned historical window that must be disposed when closed.
218
+ */
219
+ historyAt(target: number, signal: AbortSignal): Promise<Transcript>;
126
220
  /** Load the prefix required for an explicit history jump; never loop on an unadvancing page.
127
221
  * @param target - Visible record sequence, or first for the oldest available history.
128
222
  * @param signal - Cancels local paging without interrupting the remote agent.
@@ -138,7 +232,6 @@ export declare class Controller {
138
232
  private get sessionId();
139
233
  private update;
140
234
  private reply;
141
- private releasePending;
142
235
  private refreshCatalog;
143
236
  private run;
144
237
  }