@toddzheng024/dscode-bundle 0.7.25 → 0.7.27

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.
Files changed (40) hide show
  1. package/package.json +2 -1
  2. package/plugins/openrouter/wire.mjs +3 -0
  3. package/plugins/session-metrics/index.mjs +16 -7
  4. package/plugins/session-metrics/rate.mjs +26 -61
  5. package/plugins/session-metrics/view.mjs +26 -12
  6. package/plugins/triggers/cli.mjs +155 -36
  7. package/plugins/triggers/commands.mjs +140 -0
  8. package/plugins/triggers/config.mjs +32 -13
  9. package/plugins/triggers/host.mjs +28 -21
  10. package/plugins/triggers/index.mjs +17 -47
  11. package/plugins/triggers/job-cli.mjs +110 -0
  12. package/plugins/triggers/jobs.mjs +156 -0
  13. package/plugins/triggers/lease.mjs +24 -0
  14. package/plugins/triggers/management.mjs +181 -0
  15. package/plugins/triggers/options.mjs +8 -0
  16. package/plugins/triggers/poll.mjs +12 -4
  17. package/plugins/triggers/run.mjs +1 -0
  18. package/plugins/triggers/schedule.mjs +40 -0
  19. package/plugins/triggers/scheduler-service.mjs +46 -0
  20. package/plugins/triggers/scheduler.mjs +90 -0
  21. package/plugins/triggers/session.mjs +43 -0
  22. package/plugins/triggers/source-emit.mjs +19 -0
  23. package/plugins/triggers/source-host.mjs +78 -0
  24. package/plugins/triggers/source-ingress.mjs +70 -0
  25. package/plugins/triggers/source-sandbox.mjs +35 -0
  26. package/plugins/triggers/sources.mjs +45 -0
  27. package/plugins/triggers/spool.mjs +23 -6
  28. package/plugins/triggers/tools.mjs +74 -0
  29. package/vendor/tui/lib/app.mjs +66 -87
  30. package/vendor/tui/lib/dscode/preset.mjs +18 -0
  31. package/vendor/tui/lib/dscode/telemetry.mjs +25 -9
  32. package/vendor/tui/lib/index.mjs +22 -78
  33. package/vendor/tui/lib/kernel-panels.mjs +3 -2
  34. package/vendor/tui/lib/locales/en.mjs +3 -2
  35. package/vendor/tui/lib/locales/zh.mjs +3 -2
  36. package/vendor/tui/lib/models.mjs +8 -0
  37. package/vendor/tui/lib/render/inspector.mjs +1 -1
  38. package/vendor/tui/lib/render/projection.mjs +11 -6
  39. package/vendor/tui/lib/render/status.mjs +39 -10
  40. package/vendor/tui/lib/startup.mjs +4 -5
@@ -1,6 +1,7 @@
1
1
  /** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
2
2
  import { createElement, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Box, Text, useInput, useStdout } from 'ink';
4
+ import { resolveModelEffort } from './models.mjs';
4
5
  import { formatRelativeTime } from './session-directory.mjs';
5
6
  import { panelViewport, revealRow } from './render/inspector.mjs';
6
7
  import { markdownLines, textLines } from './render/lines.mjs';
@@ -893,8 +894,8 @@ export function EffortPanel({ row, current, select, back, onExit }) {
893
894
  : hasDefaultRow
894
895
  ? [{ id: '', name: 'Default' }, ...advertised]
895
896
  : advertised;
896
- // An absent or cleared effort is the Default row's current state.
897
- const effective = current === undefined || current === '' ? '' : current;
897
+ // A different model's effort must not override this model's default.
898
+ const effective = resolveModelEffort(row, current) ?? '';
898
899
  // The list opens ON the effective level (or the model's default row), so a
899
900
  // quick re-pick never restarts the cursor from the top.
900
901
  const wanted = effective === '' ? row.reasoning?.defaultEffort ?? '' : effective;
@@ -100,9 +100,10 @@ export const en = {
100
100
  'cmd.help': 'show this overlay',
101
101
  'cmd.model': 'switch the model and manage providers',
102
102
  'cmd.effort': 'adjust reasoning effort for the current model',
103
+ 'notice.modeLocked': 'DSCODE mode is fixed to dscode.',
103
104
  'cmd.mode': 'inspect or select the agent preset (/mode [preset])',
104
105
  'cmd.permission': 'inspect or select the permission preset (/permission [preset])',
105
- 'cmd.new': 'create and switch to a fresh session (/new [preset])',
106
+ 'cmd.new': 'create and switch to a fresh DSCODE session (/new)',
106
107
  'cmd.fork': 'fork at the latest completed turn (/fork [event-seq])',
107
108
  'cmd.resume': 'browse or switch root sessions (/resume [id|prefix])',
108
109
  'cmd.search': 'full-text search across persisted sessions (/search [query])',
@@ -249,7 +250,7 @@ export const en = {
249
250
  'status.label.cache': 'cache',
250
251
  'status.label.in': 'in',
251
252
  'status.label.out': 'out',
252
- 'status.label.mode': '/mode',
253
+ 'status.label.mode': 'Mode',
253
254
  'status.label.context': 'context',
254
255
  'status.label.skills': 'skills',
255
256
  // Frozen band
@@ -100,9 +100,10 @@ export const zh = {
100
100
  'cmd.help': '显示本帮助',
101
101
  'cmd.model': '切换模型并管理供应商',
102
102
  'cmd.effort': '调整当前模型的推理档位',
103
+ 'notice.modeLocked': 'DSCODE 已固定使用 dscode 模式。',
103
104
  'cmd.mode': '查看或选择 Agent 预设(/mode [preset])',
104
105
  'cmd.permission': '查看或选择权限预设(/permission [preset])',
105
- 'cmd.new': '新建并切换到新会话(/new [preset])',
106
+ 'cmd.new': '新建并切换到 DSCODE 会话(/new)',
106
107
  'cmd.fork': '从最近的完成回合分叉(/fork [event-seq])',
107
108
  'cmd.resume': '浏览或恢复历史会话(/resume [id|前缀])',
108
109
  'cmd.search': '跨历史会话全文检索(/search [关键词])',
@@ -249,7 +250,7 @@ export const zh = {
249
250
  'status.label.cache': '缓存',
250
251
  'status.label.in': '入',
251
252
  'status.label.out': '出',
252
- 'status.label.mode': '/mode',
253
+ 'status.label.mode': '模式',
253
254
  'status.label.context': '上下文',
254
255
  'status.label.skills': '技能',
255
256
  // Frozen band
@@ -8,6 +8,14 @@
8
8
  * @module @deepseek-ai/dsh-tui/models
9
9
  */
10
10
  import { ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
11
+ /** Preserve an advertised effort, otherwise use the target model's own default. */
12
+ export function resolveModelEffort(row, current) {
13
+ const reasoning = row.reasoning;
14
+ const offered = reasoning?.efforts ?? [];
15
+ if (current && offered.some(effort => effort.id === current))
16
+ return current;
17
+ return offered.find(effort => effort.id === reasoning?.defaultEffort)?.id;
18
+ }
11
19
  /** Map an adapter's reasoning capability onto the panel's plain-id shape. */
12
20
  export function mapReasoning(reasoning) {
13
21
  return {
@@ -15,7 +15,7 @@ export function liveRegionBudget(chrome) {
15
15
  const terminal = Math.max(1, Math.floor(chrome.terminalRows));
16
16
  const editor = Math.max(1, Math.floor(chrome.composerRows));
17
17
  const band = editor + 2;
18
- const status = chrome.statusBarRows === 2 ? 2 : 1;
18
+ const status = chrome.statusBarRows;
19
19
  const menu = Math.max(0, Math.floor(chrome.menuRows));
20
20
  const gutter = chrome.gutterRows;
21
21
  const notice = chrome.notice ? 1 : 0;
@@ -247,6 +247,15 @@ function estimateTokens(text) {
247
247
  }
248
248
  return wide + Math.ceil(narrow / 4);
249
249
  }
250
+ /** Recovery text shared by live events and replayed history. */
251
+ function failureRecovery(code) {
252
+ if (code === 'MISSING_CREDENTIAL')
253
+ return ' · open /model to add an API key';
254
+ if (code === 'CONTENT_POLICY' || code === 'CONTENT_FILTER') {
255
+ return ' · turn stopped by provider content filtering; review the request and context before continuing';
256
+ }
257
+ return '';
258
+ }
250
259
  /** A fresh, empty transcript view. */
251
260
  export function createTranscriptView() {
252
261
  return {
@@ -695,9 +704,7 @@ export function projectEvent(view, event) {
695
704
  const reason = event.data.reason;
696
705
  const appended = [];
697
706
  if (reason.kind === 'error') {
698
- const recovery = reason.error.code === 'MISSING_CREDENTIAL'
699
- ? ' · open /model to add an API key'
700
- : '';
707
+ const recovery = failureRecovery(reason.error.code);
701
708
  appended.push({ kind: 'error', text: `${reason.error.code}: ${reason.error.message}${recovery}` });
702
709
  }
703
710
  else {
@@ -1441,9 +1448,7 @@ export function replayProjectEvent(acc, event) {
1441
1448
  acc.streamingReasoning = '';
1442
1449
  acc.streaming = '';
1443
1450
  if (reason.kind === 'error') {
1444
- const recovery = reason.error.code === 'MISSING_CREDENTIAL'
1445
- ? ' · open /model to add an API key'
1446
- : '';
1451
+ const recovery = failureRecovery(reason.error.code);
1447
1452
  appended.push({ kind: 'error', text: `${reason.error.code}: ${reason.error.message}${recovery}` });
1448
1453
  }
1449
1454
  else {
@@ -306,7 +306,7 @@ function buildCandidates(facts, stats, busy, enabled, contextWidth) {
306
306
  push({ text: lead, tone: 'model' });
307
307
  }
308
308
  if (model !== '' && enabled.has('model')) {
309
- const effort = safe(facts.effort ?? stats.reasoningEffort);
309
+ const effort = safe(facts.effort ?? '');
310
310
  push({ text: effort === '' ? model : model + ' @ ' + effort, tone: 'accent' });
311
311
  }
312
312
  const cwd = safe(facts.cwd);
@@ -426,7 +426,7 @@ function buildCandidates(facts, stats, busy, enabled, contextWidth) {
426
426
  },
427
427
  ],
428
428
  },
429
- rank: RANK2_SKILLS,
429
+ rank: facts.telemetryFigures === undefined ? RANK2_SKILLS : 100,
430
430
  id: 'skills',
431
431
  });
432
432
  }
@@ -476,16 +476,26 @@ function buildCandidates(facts, stats, busy, enabled, contextWidth) {
476
476
  if (facts.plan && enabled.has('plan')) {
477
477
  row2.push({ group: { spans: [{ text: '⧉ plan', tone: 'accent' }] }, rank: RANK2_PLAN, id: 'plan' });
478
478
  }
479
- // dscode: the live figures close row 2 as one ordinary left-hand group, so the
480
- // row reads as a single cluster instead of a pinned right edge with a gap. It
481
- // drops last: the running cost is the last thing the width may take away.
482
- if (facts.telemetry !== undefined && facts.telemetry !== '') {
479
+ // Structured runtime figures sit on the left; finances are placed separately
480
+ // after fitting that row. Keep the flat form for older text-only callers.
481
+ if (facts.telemetryFigures !== undefined) {
482
+ const figures = facts.telemetryFigures;
483
+ for (const [id, text, rank] of [
484
+ ['telemetry-current', figures.current, 95],
485
+ ['telemetry-average', figures.average, 30],
486
+ ['telemetry-context', figures.context, 94],
487
+ ]) {
488
+ if (text !== '')
489
+ row2.push({ group: { spans: [{ text, tone: 'meta' }] }, rank, id });
490
+ }
491
+ }
492
+ else if (facts.telemetry !== undefined && facts.telemetry !== '') {
483
493
  row2.push({ group: { spans: [{ text: facts.telemetry, tone: 'meta' }] }, rank: RANK2_TELEMETRY, id: 'telemetry' });
484
494
  }
485
495
  return { left, right, badge, row2 };
486
496
  }
487
497
  /**
488
- * Compose the two-row footer layout under a column budget. Row 1 keeps the
498
+ * Compose the responsive footer layout under a column budget. Row 1 keeps the
489
499
  * session identity, cwd, mode, branch, context, then the right-pinned permission
490
500
  * badge and cycle hint. It drops hint, context, and permission before
491
501
  * ellipsizing identity. Row 2 fits all secondary figures, the live telemetry
@@ -497,7 +507,7 @@ function buildCandidates(facts, stats, busy, enabled, contextWidth) {
497
507
  * keeps mode hints idle-only); 'items' is the ordered enabled-item config
498
508
  * from /statusline (defaults to the full catalog). Display order follows the
499
509
  * config per side while the drop ladder keeps its fixed ranks.
500
- * @returns the two rows to render; row1.left is never empty.
510
+ * @returns the rows to render; row1.left is never empty.
501
511
  */
502
512
  export function layoutStatusBar(facts, stats, columns, options = {}) {
503
513
  const busy = options.busy === true;
@@ -517,7 +527,10 @@ export function layoutStatusBar(facts, stats, columns, options = {}) {
517
527
  const byPosition = (a, b) => (position.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (position.get(b.id) ?? Number.MAX_SAFE_INTEGER);
518
528
  const orderedLeft = [left[0], ...left.slice(1).sort(byPosition)];
519
529
  const orderedRight = right.slice().sort(byPosition);
520
- const orderedRow2 = row2.slice().sort(byPosition);
530
+ const orderedRow2 = facts.telemetryFigures === undefined ? row2.slice().sort(byPosition) : [
531
+ ...row2.filter(entry => entry.id.startsWith('telemetry-')),
532
+ ...row2.filter(entry => !entry.id.startsWith('telemetry-')).sort(byPosition),
533
+ ];
521
534
  // The cycle hint keeps its columns reserved whether or not it is painted:
522
535
  // the badge anchors the right edge, so a turn opening or closing must not
523
536
  // change what the left clusters may occupy.
@@ -655,6 +668,21 @@ export function layoutStatusBar(facts, stats, columns, options = {}) {
655
668
  }
656
669
  row2Kept.splice(dropIndex, 1);
657
670
  }
671
+ // Keep fees, balance, peak/off-peak and cache together. Move the group down
672
+ // before sacrificing left-side figures just to seat it on the same row.
673
+ const figures = facts.telemetryFigures;
674
+ let financial = [];
675
+ if (figures !== undefined) {
676
+ const parts = [figures.money, figures.turn, figures.cache].filter(Boolean);
677
+ let text = parts.join(STATUS_ITEM_SEPARATOR);
678
+ // The optional per-turn cost goes first on extremely narrow terminals.
679
+ if (visibleColumns(text) > row2Budget)
680
+ text = [figures.money, figures.cache].filter(Boolean).join(STATUS_ITEM_SEPARATOR);
681
+ financial = [{ text: truncateColumns(text, row2Budget), tone: 'meta', telemetry: true }];
682
+ }
683
+ const financialWidth = spansWidth(financial);
684
+ const financialWrap = financial.length > 0 && row2Kept.length > 0
685
+ && row2Width() + LEFT_RIGHT_GAP + financialWidth > row2Budget;
658
686
  return {
659
687
  row1: {
660
688
  left: leftKept.map(entry => ({ ...entry.group, id: entry.id })),
@@ -663,8 +691,9 @@ export function layoutStatusBar(facts, stats, columns, options = {}) {
663
691
  },
664
692
  row2: {
665
693
  left: row2Kept.map(entry => ({ ...entry.group, id: entry.id })),
666
- right: [],
694
+ right: financialWrap ? [] : financial,
667
695
  hint: false,
668
696
  },
697
+ ...(financialWrap ? { row3: { left: [], right: financial, hint: false } } : {}),
669
698
  };
670
699
  }
@@ -20,7 +20,8 @@
20
20
  *
21
21
  * @module @deepseek-ai/dsh-tui/startup
22
22
  */
23
- import { Command } from 'commander';
23
+ import { Command, Option } from 'commander';
24
+ import { requireDscodePreset } from './dscode/preset.mjs';
24
25
  import { parseCmdline } from '@deepseek-ai/dsh-cmdline';
25
26
  import { THEME_NAMES } from './theme.mjs';
26
27
  /** Stable Cordis plugin name. */
@@ -38,8 +39,7 @@ export function resolveTuiStartup(options) {
38
39
  throw new Error('--session needs an id');
39
40
  if (options.resume === '')
40
41
  throw new Error('--resume needs a session id or id prefix');
41
- if (options.mode === '')
42
- throw new Error('--mode needs a preset id');
42
+ requireDscodePreset(options.mode);
43
43
  if (options.mode !== undefined && (options.resume !== undefined || options.continue === true)) {
44
44
  throw new Error('--mode applies only to a new session; it cannot be combined with --resume or --continue');
45
45
  }
@@ -72,7 +72,7 @@ function tuiCommand() {
72
72
  .option('-r, --resume <session>', 'resume the persisted session with this id (or unique id prefix)')
73
73
  .option('-c, --continue', 'resume the most recent persisted session for this working directory')
74
74
  .option('--session <id>', 'create a new session under this explicit id')
75
- .option('--mode <preset>', 'agent preset for a newly created session')
75
+ .addOption(new Option('--mode <preset>', 'compatibility flag; only dscode is accepted').hideHelp())
76
76
  .option('--theme <name>', 'color theme: dark (default), light, prismatic, rainbow, or auto')
77
77
  .option('-i, --image <path>', 'attach an image to the initial prompt (repeatable)', (path, paths) => [...paths, path], [])
78
78
  .argument('[prompt...]', 'initial prompt; sends immediately after startup')
@@ -81,7 +81,6 @@ Examples:
81
81
  dsh --profile cli fresh session, minted id
82
82
  dsh --profile cli --resume abc123 resume session by id prefix
83
83
  dsh --profile cli --continue resume the latest local session
84
- dsh --profile cli --mode minimal fresh session using the minimal preset
85
84
  dsh --profile cli --theme light light palette for bright terminals
86
85
  dsh --profile cli "explain this repo" start and send an initial prompt
87
86
  dsh --profile cli -i diagram.png "review this diagram"