@antst/dashi 0.1.0-alpha.13 → 0.1.0-alpha.15

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/catalogs.d.ts CHANGED
@@ -14,7 +14,7 @@ export declare function sessionOverlay(items: readonly SessionSummary[], current
14
14
  export declare function modelOverlay(catalog: ModelCatalog, selected?: ModelSelection): Overlay;
15
15
  /** Build the preset picker from the native preset service. */
16
16
  export declare function permissionOverlay(ctx: Context, agent: Agent): Overlay;
17
- export declare function sessionStatusLine(ctx: Context, agent: Agent, root: RootView, branch?: string): string;
17
+ export declare function sessionStatusLine(ctx: Context, agent: Agent, root: RootView, repo?: string): string;
18
18
  /** Read DSH-owned root facts for /status in one projection cut. */
19
19
  export declare function statusOverlay(ctx: Context, agent: Agent, root: RootView): Overlay;
20
20
  /** Show DSH's heuristic next-request composition without treating it as an exact total. */
package/lib/catalogs.js CHANGED
@@ -177,18 +177,32 @@ function number(value) {
177
177
  function milliseconds(value) {
178
178
  return typeof value === 'number' ? `${number(value)} ms` : 'unavailable';
179
179
  }
180
- export function sessionStatusLine(ctx, agent, root, branch) {
180
+ function compactTokens(value) {
181
+ return Intl.NumberFormat('en-US', { maximumFractionDigits: 1, notation: 'compact' }).format(value).replace('K', 'k');
182
+ }
183
+ export function sessionStatusLine(ctx, agent, root, repo) {
181
184
  const { modelSelection, permissions, tokenUsage, contextPressure } = ctx.sessionProjections
182
185
  .snapshot(agent.session, ['modelSelection', 'permissions', 'tokenUsage', 'contextPressure']).values;
183
186
  const model = modelSelection?.next ?? modelSelection?.lastUsed;
184
187
  const input = tokenUsage === undefined ? 0 : tokenUsage.uncachedInputTokens + tokenUsage.cacheReadTokens + tokenUsage.cacheWriteTokens;
188
+ const total = input + (tokenUsage?.outputTokens ?? 0);
185
189
  const used = contextPressure?.projectedTokens ?? contextPressure?.pressureTokens;
190
+ const context = used === undefined ? undefined
191
+ : contextPressure?.contextWindow === undefined ? `ctx ${compactTokens(used)}`
192
+ : `ctx ${compactTokens(used)}/${compactTokens(contextPressure.contextWindow)} ${String(Math.min(100, Math.round(used / contextPressure.contextWindow * 100)))}%`;
193
+ const agents = root.subagents?.filter(item => item.state === 'running').length ?? 0;
194
+ const jobs = root.jobs?.filter(item => item.status === 'running' || item.status === 'stopping').length ?? 0;
186
195
  return [
187
- `model ${model == null ? `${root.provider === undefined ? '' : `${root.provider}/`}${root.model}` : `${model.provider}/${model.model}`}`,
188
- `permission ${permissions?.currentValue ?? root.permission ?? 'unknown'}`,
189
- ...(used === undefined || contextPressure?.contextWindow === undefined ? [] : [`context ${number(used)}/${number(contextPressure.contextWindow)}`]),
196
+ model?.model ?? root.model,
197
+ ...(model?.reasoningEffort === undefined ? [] : [model.reasoningEffort]),
198
+ permissions?.currentValue ?? root.permission ?? 'unknown',
199
+ ...(context === undefined ? [] : [context]),
190
200
  ...(input === 0 ? [] : [`cache ${String(Math.min(tokenUsage?.cacheReadTokens === input ? 100 : 99, Math.round((tokenUsage?.cacheReadTokens ?? 0) / input * 100)))}%`]),
191
- ...(branch === undefined ? [] : [`branch ${branch}`]),
201
+ ...(tokenUsage === undefined ? [] : [`${compactTokens(total)} tok`]),
202
+ ...(agents === 0 ? [] : [`${String(agents)} agents`]),
203
+ ...(jobs === 0 ? [] : [`${String(jobs)} jobs`]),
204
+ ...(root.title === undefined ? [] : [root.title]),
205
+ ...(repo === undefined ? [] : [repo]),
192
206
  ].join(' · ');
193
207
  }
194
208
  /** Read DSH-owned root facts for /status in one projection cut. */
package/lib/renderer.js CHANGED
@@ -401,8 +401,8 @@ export function createRenderer(options) {
401
401
  : root === undefined
402
402
  ? `${DIM}no session · cards ${state.toolMode} · Ctrl+J newline · Ctrl+L redraw${RESET}`
403
403
  : root.status === 'running'
404
- ? `${DIM}running · ${plain(root.model)} · Enter ${state.sendMode === 'steer' ? 'steer' : 'queue next turn'} · Ctrl+T toggle · cards ${state.toolMode} · Esc interrupt${RESET}`
405
- : `${DIM}idle · ${plain(root.model)} · Enter send · cards ${state.toolMode}${RESET}`;
404
+ ? `${DIM}running${accessible ? ` · ${plain(root.model)}` : ''} · Enter ${state.sendMode === 'steer' ? 'steer' : 'queue next turn'} · Ctrl+T toggle · cards ${state.toolMode} · Esc interrupt${RESET}`
405
+ : `${DIM}idle${accessible ? ` · ${plain(root.model)}` : ''} · Enter send · cards ${state.toolMode}${RESET}`;
406
406
  const activityCount = activities(state).length;
407
407
  const baseStatus = `${baseStatusText}${root?.contextPercent === undefined ? '' : ` · ${String(root.contextPercent)}% context`}${activityCount === 0 ? '' : ' · Ctrl+B activity'}`;
408
408
  const status = state.newOutput === 0 ? baseStatus
package/lib/rewind.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ModelSelection } from '@deepseek-ai/dsh-api-session-controller';
1
2
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
2
3
  import type { Overlay, OverlayValue } from './state.js';
3
4
  export interface RewindBoundary {
@@ -5,6 +6,11 @@ export interface RewindBoundary {
5
6
  readonly label: string;
6
7
  readonly prompt: string;
7
8
  }
9
+ /** Return only a selection that DSH recorded for the source session. */
10
+ export declare function rewindModelSelection(projection: {
11
+ readonly lastUsed: ModelSelection | null;
12
+ readonly next: ModelSelection | null;
13
+ } | undefined): ModelSelection | undefined;
8
14
  /** Return the text of a direct human message, excluding injected context. */
9
15
  export declare function humanPrompt(event: SessionEvent): string | undefined;
10
16
  /** Recover one composer input from DSH's durable log. */
package/lib/rewind.js CHANGED
@@ -1,3 +1,7 @@
1
+ /** Return only a selection that DSH recorded for the source session. */
2
+ export function rewindModelSelection(projection) {
3
+ return projection?.next ?? projection?.lastUsed ?? undefined;
4
+ }
1
5
  /** Return the text of a direct human message, excluding injected context. */
2
6
  export function humanPrompt(event) {
3
7
  if (event.type !== 'user/message' || event.data.source.kind !== 'user')
@@ -7,6 +11,13 @@ export function humanPrompt(event) {
7
11
  .map(block => block.text).join('');
8
12
  return text === '' ? undefined : text;
9
13
  }
14
+ function shellInput(message) {
15
+ if (message.source.kind !== 'plugin' || message.source.plugin !== 'dashi'
16
+ || message.source.form !== 'notice')
17
+ return undefined;
18
+ const first = message.content.find(block => block.type === 'text')?.text.split(/\r?\n/u)[0];
19
+ return first?.startsWith('$ ') === true ? `!${first.slice(2)}` : undefined;
20
+ }
10
21
  /** Recover one composer input from DSH's durable log. */
11
22
  export function historyInput(event) {
12
23
  const prompt = humanPrompt(event);
@@ -15,11 +26,9 @@ export function historyInput(event) {
15
26
  if (event.type === 'command/run' && event.data.args !== undefined) {
16
27
  return `/${event.data.name}${event.data.args}`;
17
28
  }
18
- if (event.type !== 'user/message' || event.data.source.kind !== 'plugin'
19
- || event.data.source.plugin !== 'dashi' || event.data.source.form !== 'notice')
29
+ if (event.type !== 'agent/inbox/spliced')
20
30
  return undefined;
21
- const first = event.data.content.find(block => block.type === 'text')?.text.split(/\r?\n/u)[0];
22
- return first?.startsWith('$ ') === true ? `!${first.slice(2)}` : undefined;
31
+ return event.data.inserted.map(shellInput).find(input => input !== undefined);
23
32
  }
24
33
  export function historyInputs(events) {
25
34
  return events.flatMap(event => historyInput(event) ?? []);
@@ -9,7 +9,7 @@ import { diffOverlay } from './diff-view.js';
9
9
  import { eventsFromRecords } from './history-records.js';
10
10
  import { admitDraftImages, encodeDraftImages, readDraftImages } from './image-input.js';
11
11
  import { contextPercent, jobViews, subagentViews } from './presentation.js';
12
- import { historyInput, historyInputs, humanPrompt, inheritedTurn, rewindActionOverlay, rewindOverlay, } from './rewind.js';
12
+ import { historyInput, historyInputs, humanPrompt, inheritedTurn, rewindActionOverlay, rewindOverlay, rewindModelSelection, } from './rewind.js';
13
13
  import { createToolPresenter } from './tool-presentation.js';
14
14
  import { quoteShellWord, runHumanShell } from './shell-command.js';
15
15
  import { markdownTranscript } from './session-export.js';
@@ -80,16 +80,18 @@ async function firstControl(iterator) {
80
80
  }
81
81
  return first.value;
82
82
  }
83
- async function gitBranch(ctx, agent, signal) {
83
+ async function gitRepo(ctx, agent, signal) {
84
+ const fallback = basename(agent.session.header.cwd ?? '');
84
85
  try {
85
86
  const result = await ctx.shell.run(ctx.shell.resolve({
86
- command: 'git branch --show-current', sandboxPolicy: ctx.sandboxPolicy.resolve({ mode: 'read-only' }),
87
- signal, stdoutMaxBytes: 512, timeoutMs: 2_000, workdir: agent.session.header.cwd,
87
+ command: 'git rev-parse --show-toplevel && git branch --show-current', sandboxPolicy: ctx.sandboxPolicy.resolve({ mode: 'read-only' }),
88
+ signal, stdoutMaxBytes: 1_024, timeoutMs: 2_000, workdir: agent.session.header.cwd,
88
89
  }));
89
- return result.exitCode === 0 && result.stdout.text.trim() !== '' ? result.stdout.text.trim() : undefined;
90
+ const [root, branch] = result.stdout.text.trim().split(/\r?\n/u);
91
+ return result.exitCode === 0 && root !== undefined && branch !== undefined ? `${basename(root)}/${branch}` : fallback;
90
92
  }
91
93
  catch {
92
- return undefined;
94
+ return fallback;
93
95
  }
94
96
  }
95
97
  function usage(raw, expected) {
@@ -165,9 +167,9 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
165
167
  address: { kind: 'session', sessionId }, maxMessages: 50,
166
168
  }, abort.signal)[Symbol.asyncIterator]();
167
169
  const controlIterator = ctx.sessionController.control(abort.signal)[Symbol.asyncIterator]();
168
- const [opening, controlOpening, subagents, branch] = await Promise.all([
170
+ const [opening, controlOpening, subagents, repo] = await Promise.all([
169
171
  firstSnapshot(iterator), firstControl(controlIterator), subagentViews(ctx, sessionId, abort.signal),
170
- gitBranch(ctx, agent, abort.signal),
172
+ gitRepo(ctx, agent, abort.signal),
171
173
  ]);
172
174
  title ??= projectionString(opening.projections, 'title');
173
175
  const selected = projectionModel(opening.projections);
@@ -179,7 +181,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
179
181
  return {
180
182
  abort,
181
183
  agent,
182
- branch,
184
+ repo,
183
185
  controlIterator,
184
186
  cursor: opening.cursor,
185
187
  disposers,
@@ -289,11 +291,11 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
289
291
  });
290
292
  }, 34);
291
293
  };
292
- const refreshBranch = async (bound) => {
293
- const branch = await gitBranch(ctx, bound.agent, bound.abort.signal);
294
- if (bound.abort.signal.aborted || current() !== bound || bound.branch === branch)
294
+ const refreshRepo = async (bound) => {
295
+ const repo = await gitRepo(ctx, bound.agent, bound.abort.signal);
296
+ if (bound.abort.signal.aborted || current() !== bound || bound.repo === repo)
295
297
  return;
296
- bound.branch = branch;
298
+ bound.repo = repo;
297
299
  dispatch({ type: 'redraw' });
298
300
  };
299
301
  const consume = (bound, frame) => {
@@ -302,7 +304,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
302
304
  return;
303
305
  bound.events.push(event);
304
306
  if (event.type === 'turn/end') {
305
- void refreshBranch(bound);
307
+ void refreshRepo(bound);
306
308
  const startedAt = turnStartTime(bound.events, event.data.turn);
307
309
  if (startedAt !== undefined) {
308
310
  dispatch({
@@ -327,7 +329,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
327
329
  ...(model.reasoningEffort === undefined ? {} : { effort: model.reasoningEffort }),
328
330
  });
329
331
  }
330
- const prompt = historyInput(event);
332
+ const prompt = event.type === 'agent/inbox/spliced' ? undefined : historyInput(event);
331
333
  if (prompt !== undefined)
332
334
  dispatch({ type: 'prompt-recorded', rootId: bound.root.id, text: prompt });
333
335
  schedule(bound);
@@ -447,13 +449,12 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
447
449
  ?? ctx.agentPresets.defaultId;
448
450
  const modelProjection = ctx.sessionProjections.snapshot(bound.agent.session, ['modelSelection'])
449
451
  .values.modelSelection;
450
- const selection = modelProjection?.next ?? modelProjection?.lastUsed ?? undefined;
452
+ const selection = rewindModelSelection(modelProjection);
451
453
  const created = await ctx.sessionController.create({ cwd: bound.root.cwd, agentPreset });
452
454
  let selectionFailure;
453
455
  try {
454
- if (selection === undefined)
455
- throw new Error('the current session has no durable model selection');
456
- await ctx.sessionController.selectModel({ sessionId: created.sessionId, ...selection });
456
+ if (selection !== undefined)
457
+ await ctx.sessionController.selectModel({ sessionId: created.sessionId, ...selection });
457
458
  }
458
459
  catch (error) {
459
460
  selectionFailure = error;
@@ -1279,7 +1280,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
1279
1280
  get initialHasMore() { return current().hasMore; },
1280
1281
  get initialPrompts() { return historyInputs(current().events); },
1281
1282
  get root() { return current().root; },
1282
- get statusLine() { const bound = current(); return sessionStatusLine(ctx, bound.agent, bound.root, bound.branch); },
1283
+ get statusLine() { const bound = current(); return sessionStatusLine(ctx, bound.agent, bound.root, bound.repo); },
1283
1284
  get summary() { return `Resume with: dsh --profile dashi --resume ${current().root.id}`; },
1284
1285
  activate,
1285
1286
  async attach(path) {
@@ -1428,6 +1429,7 @@ export async function createSessionRuntime(ctx, options, tuiRoot, dispatch, deci
1428
1429
  bound.agent.inject(message);
1429
1430
  await ctx.sessions.flush(bound.agent.session);
1430
1431
  });
1432
+ dispatch({ type: 'prompt-recorded', rootId: bound.root.id, text: `!${command}` });
1431
1433
  publish(bound);
1432
1434
  return;
1433
1435
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antst/dashi",
3
- "version": "0.1.0-alpha.13",
3
+ "version": "0.1.0-alpha.15",
4
4
  "description": "A terminal UI for DeepSeek Harness",
5
5
  "publishConfig": {
6
6
  "access": "public"