@toddzheng024/dscode-bundle 0.1.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/THIRD_PARTY_NOTICES.md +11 -0
  4. package/bin/apply_patch +4 -0
  5. package/bootstrap.mjs +21 -0
  6. package/cordis.patch.yml +756 -0
  7. package/package.json +327 -0
  8. package/plugins/auto-review/audit.mjs +20 -0
  9. package/plugins/auto-review/index.mjs +181 -0
  10. package/plugins/auto-review/policy.mjs +59 -0
  11. package/plugins/dscode/index.mjs +35 -0
  12. package/plugins/session-metrics/index.mjs +44 -0
  13. package/plugins/session-metrics/pricing.mjs +18 -0
  14. package/plugins/session-metrics/store.mjs +24 -0
  15. package/plugins/session-metrics/view.mjs +58 -0
  16. package/plugins/tui-tools/hooks.mjs +23 -0
  17. package/plugins/tui-tools/index.mjs +164 -0
  18. package/plugins/ultra/policy.mjs +11 -0
  19. package/presets/dscode/agent.cordis.yml +288 -0
  20. package/presets/dscode/preset.yml +3 -0
  21. package/vendor/bash/LICENSE +21 -0
  22. package/vendor/bash/index.js +449 -0
  23. package/vendor/bash/types/background.d.ts +19 -0
  24. package/vendor/bash/types/index.d.ts +22 -0
  25. package/vendor/bash/types/render.d.ts +38 -0
  26. package/vendor/deepseek/LICENSE +21 -0
  27. package/vendor/deepseek/index.js +2102 -0
  28. package/vendor/deepseek/types/adapter.d.ts +163 -0
  29. package/vendor/deepseek/types/file-id.d.ts +19 -0
  30. package/vendor/deepseek/types/file-store.d.ts +83 -0
  31. package/vendor/deepseek/types/files-api.d.ts +103 -0
  32. package/vendor/deepseek/types/image-tokens.d.ts +19 -0
  33. package/vendor/deepseek/types/index.d.ts +106 -0
  34. package/vendor/deepseek/types/request-pricing.d.ts +48 -0
  35. package/vendor/deepseek/types/serialize.d.ts +85 -0
  36. package/vendor/deepseek/types/sse.d.ts +24 -0
  37. package/vendor/deepseek/types/translate.d.ts +37 -0
  38. package/vendor/deepseek/types/types.d.ts +178 -0
  39. package/vendor/deepseek/types/upload-index.d.ts +68 -0
  40. package/vendor/persistent/LICENSE +21 -0
  41. package/vendor/persistent/index.js +386 -0
  42. package/vendor/persistent/types/index.d.ts +24 -0
  43. package/vendor/tui/LICENSE +21 -0
  44. package/vendor/tui/devtools-CdTl3MNy.mjs +3643 -0
  45. package/vendor/tui/index.mjs +39105 -0
  46. package/vendor/tui/invariant.mjs +21 -0
  47. package/vendor/tui/rolldown-runtime-CMFfr-1z.mjs +26 -0
  48. package/vendor/tui/startup.mjs +109 -0
  49. package/vendor/tui/theme-DCT8Y2xf.mjs +628 -0
  50. package/vendor/tui/types/app.d.ts +304 -0
  51. package/vendor/tui/types/approval.d.ts +59 -0
  52. package/vendor/tui/types/attachments.d.ts +52 -0
  53. package/vendor/tui/types/authorization-panel.d.ts +22 -0
  54. package/vendor/tui/types/authorization.d.ts +36 -0
  55. package/vendor/tui/types/commands.d.ts +52 -0
  56. package/vendor/tui/types/editor-keys.d.ts +105 -0
  57. package/vendor/tui/types/editor.d.ts +6 -0
  58. package/vendor/tui/types/fork.d.ts +8 -0
  59. package/vendor/tui/types/git-workflow.d.ts +32 -0
  60. package/vendor/tui/types/history.d.ts +97 -0
  61. package/vendor/tui/types/index.d.ts +124 -0
  62. package/vendor/tui/types/input-split.d.ts +54 -0
  63. package/vendor/tui/types/internals.d.ts +26 -0
  64. package/vendor/tui/types/invariant.d.ts +15 -0
  65. package/vendor/tui/types/kernel-panels.d.ts +167 -0
  66. package/vendor/tui/types/keyboard.d.ts +80 -0
  67. package/vendor/tui/types/mentions.d.ts +81 -0
  68. package/vendor/tui/types/model-capabilities.d.ts +82 -0
  69. package/vendor/tui/types/models.d.ts +119 -0
  70. package/vendor/tui/types/permissions.d.ts +27 -0
  71. package/vendor/tui/types/plugin-inventory.d.ts +11 -0
  72. package/vendor/tui/types/presets.d.ts +22 -0
  73. package/vendor/tui/types/provider-settings.d.ts +239 -0
  74. package/vendor/tui/types/questions.d.ts +54 -0
  75. package/vendor/tui/types/render/animations.d.ts +265 -0
  76. package/vendor/tui/types/render/editor.d.ts +162 -0
  77. package/vendor/tui/types/render/export.d.ts +9 -0
  78. package/vendor/tui/types/render/fuzzy.d.ts +21 -0
  79. package/vendor/tui/types/render/inspector.d.ts +36 -0
  80. package/vendor/tui/types/render/lines.d.ts +66 -0
  81. package/vendor/tui/types/render/markdown.d.ts +29 -0
  82. package/vendor/tui/types/render/projection.d.ts +461 -0
  83. package/vendor/tui/types/render/status.d.ts +196 -0
  84. package/vendor/tui/types/render/text.d.ts +58 -0
  85. package/vendor/tui/types/render/tool-detail.d.ts +94 -0
  86. package/vendor/tui/types/render/tool-preview.d.ts +28 -0
  87. package/vendor/tui/types/render/width.d.ts +29 -0
  88. package/vendor/tui/types/session-directory.d.ts +173 -0
  89. package/vendor/tui/types/session-switch.d.ts +17 -0
  90. package/vendor/tui/types/settings-file.d.ts +41 -0
  91. package/vendor/tui/types/skills.d.ts +47 -0
  92. package/vendor/tui/types/startup.d.ts +65 -0
  93. package/vendor/tui/types/store.d.ts +58 -0
  94. package/vendor/tui/types/subagents.d.ts +70 -0
  95. package/vendor/tui/types/theme-panel.d.ts +24 -0
  96. package/vendor/tui/types/theme.d.ts +215 -0
  97. package/vendor/tui/types/version.d.ts +18 -0
  98. package/vendor/tui/types/whale-glyph.d.ts +6 -0
@@ -0,0 +1,44 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { appendMetric } from './store.mjs';
3
+ import { estimateCost, PRICE_VERSION } from './pricing.mjs';
4
+ import { setMetricSource } from './view.mjs';
5
+ export const name = 'dscode-session-metrics';
6
+ export const inject = ['llm', 'agents', 'tokenMeter', 'sessionProjections'];
7
+ export function apply(ctx) {
8
+ const snapshots = new WeakMap();
9
+ ctx.effect(() => setMetricSource(id => {
10
+ const agent = ctx.agents.get(id);
11
+ if (!agent) return undefined;
12
+ const session = agent.session;
13
+ const cached = snapshots.get(session);
14
+ if (cached?.seq === session.seq) return cached.value;
15
+ const state = ctx.sessionProjections.stateOf(session, 'contextPressure');
16
+ const measurement = ctx.tokenMeter.measure(session);
17
+ const value = { events: session.snapshotEvents(), used: measurement.totalTokens, capacity: state?.contextWindow };
18
+ snapshots.set(session, { seq: session.seq, value });
19
+ return value;
20
+ }));
21
+ const home = process.env.DSH_HOME;
22
+ const record = (id, entry) => { try { appendMetric(home, id, entry); } catch { ctx.logger.warn('Session cost telemetry could not be saved.'); } };
23
+ ctx.on('llm/stream', async function* (options, next) {
24
+ if (!options.sessionId || !home) { yield* next(); return; }
25
+ const id = randomUUID(), time = Date.now();
26
+ const recipients = new Set([options.sessionId]);
27
+ let child = ctx.agents.get(options.sessionId);
28
+ while (child?.session.header.origin === 'subagent' && child.session.header.parentSession && !recipients.has(child.session.header.parentSession)) {
29
+ recipients.add(child.session.header.parentSession);
30
+ child = ctx.agents.get(child.session.header.parentSession);
31
+ }
32
+ const save = entry => { for (const recipient of recipients) record(recipient, { ...entry, sessionId: options.sessionId }); };
33
+ save( { kind: 'start', id, time, provider: options.provider, model: options.model, purpose: options.purpose ?? 'agent' });
34
+ let usage;
35
+ try {
36
+ for await (const chunk of next()) {
37
+ if (chunk.type === 'usage') usage = chunk.usage;
38
+ yield chunk;
39
+ }
40
+ } finally {
41
+ save({ kind: 'end', id, time, usage: usage ?? null, cost: estimateCost(options.provider, options.model, usage, time), priceVersion: PRICE_VERSION });
42
+ }
43
+ });
44
+ }
@@ -0,0 +1,18 @@
1
+ // USD per million tokens. Snapshot of the official page opened 2026-09-11.
2
+ // https://api-docs.deepseek.com/quick_start/pricing/
3
+ export const PRICE_SOURCE = 'https://api-docs.deepseek.com/quick_start/pricing/';
4
+ export const PRICE_VERSION = 'deepseek-2026-09-11';
5
+ export function estimateCost(provider, model, usage, time) {
6
+ if (provider !== 'deepseek-official' || !usage || !Number.isFinite(time)) return null;
7
+ // Earlier requests require an older price table; never back-price them at today's rate.
8
+ if (time < Date.UTC(2026, 8, 11)) return null;
9
+ const flash = ['deepseek-flash', 'deepseek-v4-flash', 'deepseek-v4-flash-vision-exp'].includes(model)
10
+ || model === 'deepseek-v4-pro' && time >= Date.UTC(2026, 8, 14, 4);
11
+ if (!flash && model !== 'deepseek-v4-pro') return null;
12
+ const date = new Date(time), day = date.getUTCDay(), hour = date.getUTCHours();
13
+ const peak = day >= 1 && day <= 5 && (hour >= 1 && hour < 4 || hour >= 6 && hour < 10);
14
+ const [read, input, output] = flash ? [0.003, 0.15, 0.6] : [0.022, 0.66, 1.98];
15
+ const values = [usage.inputTokens, usage.outputTokens, usage.cacheReadTokens ?? 0, usage.cacheWriteTokens ?? 0];
16
+ if (!values.every(n => Number.isFinite(n) && n >= 0) || values[3] !== 0) return null;
17
+ return (values[0] * input + values[1] * output + values[2] * read) * (peak ? 2 : 1) / 1e6;
18
+ }
@@ -0,0 +1,24 @@
1
+ import { appendFileSync, mkdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ const cache = new Map();
5
+ export const ledgerPath = (home, id) => join(home, 'session-metrics', createHash('sha256').update(id).digest('hex') + '.jsonl');
6
+ export function appendMetric(home, id, entry) {
7
+ const path = ledgerPath(home, id);
8
+ mkdirSync(join(home, 'session-metrics'), { recursive: true, mode: 0o700 });
9
+ appendFileSync(path, JSON.stringify(entry) + '\n', { mode: 0o600, flush: true });
10
+ }
11
+ export function readMetrics(home, id) {
12
+ const path = ledgerPath(home, id);
13
+ try {
14
+ const st = statSync(path), key = `${st.mtimeMs}:${st.size}`;
15
+ if (cache.get(path)?.key === key) return cache.get(path).value;
16
+ let corrupt = false;
17
+ const rows = readFileSync(path, 'utf8').split('\n').filter(Boolean).flatMap(line => {
18
+ try { return [JSON.parse(line)]; } catch { corrupt = true; return []; }
19
+ });
20
+ const value = { rows, corrupt };
21
+ cache.set(path, { key, value });
22
+ return value;
23
+ } catch (e) { return { rows: [], corrupt: e.code !== 'ENOENT' }; }
24
+ }
@@ -0,0 +1,58 @@
1
+ import { readMetrics } from './store.mjs';
2
+ import { estimateCost } from './pricing.mjs';
3
+ let source;
4
+ export function setMetricSource(next) { source = next; return () => { if (source === next) source = undefined; }; }
5
+ export function summarize(rows, events = [], corrupt = false) {
6
+ const requests = new Map();
7
+ let first = Infinity;
8
+ for (const row of rows) {
9
+ if (row.kind === 'start') first = Math.min(first, row.time);
10
+ requests.set(row.id, { ...requests.get(row.id), ...row });
11
+ }
12
+ // Backfill old main/compaction calls from the durable log, only before the new ledger.
13
+ let route = {};
14
+ const history = [];
15
+ for (const event of events) {
16
+ if (event.type === 'request/header') route = event.data.header.config;
17
+ if (event.time >= first) continue;
18
+ if (event.type === 'assistant/message' || event.type === 'compaction/summary' && event.data.llmStreamCall) {
19
+ const r = event.type === 'compaction/summary' ? event.data : route;
20
+ history.push({ usage: event.data.usage, cost: estimateCost(r.provider, r.model, event.data.usage, event.time), kind: 'end' });
21
+ } else if (event.type === 'assistant/attempt') history.push({ kind: 'end', cost: null });
22
+ }
23
+ let cost = 0, unknown = corrupt || history.length > 0, input = 0, hit = 0, cacheUnknown = false, calls = 0, pending = 0;
24
+ // Historical overhead (review/title) was not recorded by this collector: mark partial.
25
+ for (const row of [...history, ...requests.values()]) {
26
+ calls++;
27
+ if (row.kind !== 'end') { pending++; continue; }
28
+ if (!Number.isFinite(row.cost)) unknown = true;
29
+ else cost += row.cost;
30
+ const u = row.usage;
31
+ if (!u || !Number.isFinite(u.inputTokens) || !Number.isFinite(u.outputTokens)) { cacheUnknown = true; continue; }
32
+ const total = u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWriteTokens ?? 0);
33
+ input += total; hit += u.cacheReadTokens ?? 0;
34
+ if (total > 0 && u.cacheReadTokens === undefined) cacheUnknown = true;
35
+ }
36
+ return { cost, unknown, calls, pending, cache: input > 0 && !cacheUnknown ? Math.min(100, hit / input * 100) : null };
37
+ }
38
+ export function formatFooter(metrics, context, columns = 80) {
39
+ const ctx = Number.isFinite(context) ? `${Math.round(context)}%` : '--';
40
+ const cache = metrics.cache === null ? '--' : `${metrics.cache.toFixed(1)}%`;
41
+ const dollars = metrics.unknown && metrics.cost === 0 ? '--' : `~$${metrics.cost.toFixed(metrics.cost < 1 ? 4 : 2)}${metrics.unknown ? '+' : ''}${metrics.pending ? '…' : ''}`;
42
+ const full = `ctx ${ctx} · session ${dollars} · cache ${cache}`;
43
+ if (full.length <= columns) return full;
44
+ const short = `ctx ${ctx} $ ${dollars.replace('~$', '~')} hit ${cache}`;
45
+ if (short.length <= columns) return short;
46
+ const tiny = `${ctx} ${dollars} ${cache}`;
47
+ return tiny.length <= columns ? tiny : tiny.slice(0, Math.max(0, columns));
48
+ }
49
+ export function footerFor(id, stats, columns) {
50
+ try {
51
+ const data = id ? source?.(id) : undefined;
52
+ const ledger = id && process.env.DSH_HOME ? readMetrics(process.env.DSH_HOME, id) : { rows: [], corrupt: false };
53
+ const summary = summarize(ledger.rows, data?.events ?? [], ledger.corrupt);
54
+ const used = data?.used;
55
+ const capacity = data?.capacity ?? stats.contextWindow;
56
+ return formatFooter(summary, Number.isFinite(used) && capacity > 0 ? used / capacity * 100 : undefined, columns);
57
+ } catch { return formatFooter({ cost: 0, unknown: true, cache: null }, undefined, columns); }
58
+ }
@@ -0,0 +1,23 @@
1
+ export const hookEvents = ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop'];
2
+ // Fail visibly instead of letting the upstream compatibility bridge silently skip gates.
3
+ export function validateHooks(raw) {
4
+ const hooks = raw?.hooks ?? raw;
5
+ if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks)) throw new Error('hooks must be an event map');
6
+ for (const [event, groups] of Object.entries(hooks)) {
7
+ if (!hookEvents.includes(event)) throw new Error(`Unsupported hook event: ${event}`);
8
+ if (!Array.isArray(groups)) throw new Error(`${event}: expected matcher groups`);
9
+ for (const group of groups) {
10
+ if (!group || !Array.isArray(group.hooks)) throw new Error(`${event}: expected hooks array`);
11
+ if (group.matcher !== undefined) {
12
+ if (typeof group.matcher !== 'string') throw new Error(`${event}: matcher must be a regex string`);
13
+ new RegExp(group.matcher);
14
+ }
15
+ for (const hook of group.hooks) {
16
+ if (!hook || (hook.type !== undefined && hook.type !== 'command') || hook.async === true || typeof hook.command !== 'string' || !hook.command.trim()) throw new Error(`${event}: only synchronous command hooks are supported`);
17
+ const timeout = hook.timeout ?? hook.timeoutSec;
18
+ if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0 || timeout > 600)) throw new Error(`${event}: timeout must be 0–600 seconds, excluding zero`);
19
+ }
20
+ }
21
+ }
22
+ return hooks;
23
+ }
@@ -0,0 +1,164 @@
1
+ import { readFile, readdir, access } from 'node:fs/promises';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { parse } from 'yaml';
4
+ import { hookEvents, validateHooks } from './hooks.mjs';
5
+ import { redact } from '../auto-review/policy.mjs';
6
+
7
+ export const name = 'dscode-tui-tools';
8
+ export const inject = ['commands', 'agents', 'tools', 'skills', 'permissionPresets'];
9
+ const phases = ['pending', 'loading', 'active', 'failed', 'unavailable', 'unloading'];
10
+ const ok = text => ({ kind: 'success', text });
11
+ const fail = text => ({ kind: 'error', text });
12
+ const show = value => typeof value === 'string' ? value : JSON.stringify(value ?? null);
13
+ const exists = path => access(path).then(() => true, () => false);
14
+
15
+ export async function findConflicts(cwd, configs, winners, env = process.env) {
16
+ let project = resolve(cwd);
17
+ while (!await exists(join(project, '.git')) && dirname(project) !== project) project = dirname(project);
18
+ if (!await exists(join(project, '.git'))) project = resolve(cwd);
19
+ const roots = [];
20
+ const skipSystemRoots = new Set();
21
+ for (const config of configs) {
22
+ if (config.includeDefaultRoots !== false) {
23
+ roots.push(join(project, '.dsh/skills'), join(project, '.agents/skills'));
24
+ const dshHome = config.dshHome ?? env.DSH_HOME;
25
+ const agentsHome = config.agentsHome ?? env.DSH_AGENTS_HOME;
26
+ if (typeof dshHome === 'string') { const path = resolve(dshHome, 'skills'); roots.push(path); skipSystemRoots.add(path); }
27
+ if (typeof agentsHome === 'string') roots.push(resolve(agentsHome, 'skills'));
28
+ }
29
+ roots.push(...(config.customSkillDirs ?? []).filter(x => typeof x === 'string').map(x => resolve(x)));
30
+ const bundled = config.bundledSkillDir ?? (config.includeDefaultRoots !== false ? env.DSH_BUNDLED_SKILL_DIR : undefined);
31
+ if (typeof bundled === 'string') roots.push(resolve(bundled));
32
+ }
33
+ const candidates = new Map();
34
+ const errors = [];
35
+ for (const root of new Set(roots)) {
36
+ let entries;
37
+ try { entries = await readdir(root, { withFileTypes: true }); }
38
+ catch (e) { if (e.code !== 'ENOENT') errors.push(`${root}: ${e.code}`); continue; }
39
+ for (const entry of entries) {
40
+ if (entry.name === '.system' && skipSystemRoots.has(root)) continue;
41
+ const path = entry.isFile() && entry.name.endsWith('.md') ? join(root, entry.name) : join(root, entry.name, 'SKILL.md');
42
+ try {
43
+ const raw = await readFile(path, 'utf8');
44
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
45
+ const meta = match && parse(match[1]);
46
+ if (typeof meta?.name !== 'string' || typeof meta.description !== 'string') continue;
47
+ const list = candidates.get(meta.name) ?? [];
48
+ list.push(path); candidates.set(meta.name, list);
49
+ } catch (e) { if (!['ENOENT', 'ENOTDIR'].includes(e.code)) errors.push(`${path}: invalid or unreadable`); }
50
+ }
51
+ }
52
+ const lines = [];
53
+ for (const [name, paths] of candidates) if (paths.length > 1) {
54
+ const winner = winners.find(s => s.name === name);
55
+ lines.push(`${name}: effective source=${winner?.source ?? 'not in catalog'} provider=${winner?.provider ?? '?'}\n${paths.map(p => ` ${p}`).join('\n')}`);
56
+ }
57
+ return [...lines, ...errors, 'Scope: filesystem roots only; runtime/remote provider shadowed candidates are not exposed by DSH.'].join('\n');
58
+ }
59
+
60
+ export function apply(ctx) {
61
+ const entries = () => [...(ctx.get('loader')?.entries() ?? [])].filter(e => e.options.group !== true);
62
+ const mcps = () => entries().filter(e => e.options.name === '@deepseek-ai/dsh-mcp-client');
63
+ const hook = () => entries().find(e => e.options.id === 'dscode-hooks');
64
+ const state = e => e.disabled ? 'disabled' : phases[e.fiber?.state] ?? 'unavailable';
65
+ let changing = false;
66
+ const mutate = async fn => {
67
+ if (changing || ctx.agents.list().some(a => a.status === 'running')) return fail('Wait until all agents are idle before changing process-wide plugins.');
68
+ changing = true;
69
+ try { return await fn(); } finally { changing = false; }
70
+ };
71
+ const register = (name, description, handler) => ctx.commands.register({ name, description, handler: async inv => {
72
+ try { const result = await handler(inv); return { ...result, text: redact(result.text ?? '') }; }
73
+ catch (e) { return fail(redact(`${name}: ${e.message}`)); }
74
+ }});
75
+ register('status', 'Session, model, permissions, usage and plugin health', ({ agent }) => {
76
+ const session = agent.session;
77
+ const route = session.requestHeader()?.config ?? agent.options;
78
+ const events = session.snapshotEvents();
79
+ const projections = ctx.get('sessionProjections');
80
+ const usage = projections?.stateOf(session, 'tokenUsage')?.totals;
81
+ const pressure = projections?.stateOf(session, 'contextPressure');
82
+ return ok([
83
+ `Session: ${session.id}`, `Workspace: ${session.header.cwd ?? process.cwd()}`,
84
+ `Agent: ${agent.status} | preset: ${session.header.agentPreset ?? 'standard'}`,
85
+ `Model: ${route?.provider ?? 'default'} / ${route?.model ?? 'default'}`,
86
+ `Effort: ${route?.reasoningEffort ?? 'model default'}${route?.reasoningEffort === 'ultra' ? ' (DeepSeek wire: max; collaboration enabled)' : ''}`,
87
+ `Permission: ${show(ctx.permissionPresets.current(session))}`,
88
+ `Tokens: ${show(usage ?? 'no provider usage yet')}`,
89
+ `Context: ${pressure?.pressureTokens ?? pressure?.surfaceTokens ?? '?'} / ${pressure?.contextWindow ?? '?'} tokens`,
90
+ `Events: ${events.length}; /review-usage shows reviewer tokens`,
91
+ `Tools: ${ctx.tools.schemas(agent).length}; MCP entries: ${mcps().length}`,
92
+ `Plugins: ${entries().filter(e => state(e) === 'active').length} active, ${entries().filter(e => state(e) === 'failed').length} failed`,
93
+ 'Use /doctor for diagnostics; /statusline for live context/token display.',
94
+ ].join('\n'));
95
+ });
96
+ register('doctor', 'Read-only runtime diagnostics (no model requests)', async ({ agent, signal }) => {
97
+ const tools = ctx.tools.schemas(agent).map(t => t.name);
98
+ const catalog = await ctx.skills.snapshot({ cwd: agent.session.header.cwd, scope: agent, signal });
99
+ const computer = ctx.get('computerUse');
100
+ return ok([
101
+ `Node: ${process.version}; platform: ${process.platform}/${process.arch}`,
102
+ ...entries().filter(e => !e.disabled && state(e) !== 'active').map(e => `CHECK plugin ${e.id}: ${state(e)}`),
103
+ `Skills: ${catalog.skills.length}; discovery ${catalog.complete ? 'complete' : 'incomplete'}`,
104
+ `Core tools: ${['bash', 'skill', 'computer_use_activate'].map(n => `${n}=${tools.includes(n)}`).join(', ')}`,
105
+ ...mcps().map(e => `MCP ${e.id}: ${state(e)}; ${tools.filter(n => n.startsWith(`mcp__${e.options.config?.serverName}__`)).length} registered tools`),
106
+ `Computer Use: ${computer ? show(computer.status()) : 'service unavailable'}`,
107
+ 'Credentials and remote model access: not tested. No credential values are read or printed.',
108
+ 'For deterministic execution checks outside this session: npm run doctor (in the installation directory).',
109
+ ].join('\n'));
110
+ });
111
+ register('mcp', 'MCP list, tools <id>, enable/disable/reconnect <id>', async ({ agent, rawInput }) => {
112
+ const [action = 'list', id, extra] = rawInput.trim().split(/\s+/).filter(Boolean);
113
+ const list = mcps();
114
+ if (action === 'list') return ok(list.map(e => `${e.id}: ${state(e)} | server=${show(e.options.config?.serverName)} | transport=${show(e.options.config?.transport)}`).join('\n') + '\n/mcp tools|enable|disable|reconnect <entry-id> — changes last for this process; persist startup config in mcp.local.yml.');
115
+ const matches = list.filter(e => e.id === id || e.options.id === id);
116
+ const entry = matches.length === 1 ? matches[0] : undefined;
117
+ if (!entry || extra) return fail('Usage: /mcp [list | tools|enable|disable|reconnect <entry-id>]');
118
+ if (action === 'tools') return ok(ctx.tools.schemas(agent).filter(t => t.name.startsWith(`mcp__${entry.options.config?.serverName}__`)).map(t => t.name).join('\n') || 'No tools currently registered.');
119
+ if (!['enable', 'disable', 'reconnect'].includes(action)) return fail('Unknown MCP action.');
120
+ return mutate(async () => {
121
+ if (action === 'reconnect' && entry.disabled) return fail('Enable this server before reconnecting.');
122
+ if (action === 'reconnect') await entry.update({ disabled: true });
123
+ await entry.update({ disabled: action === 'disable' });
124
+ return ok(`${entry.id}: ${state(entry)}. Use /mcp tools ${entry.id} to check discovery.`);
125
+ });
126
+ });
127
+ register('skills', 'Skill catalog, sources, details and filesystem conflicts', async ({ agent, rawInput, signal }) => {
128
+ const options = { cwd: agent.session.header.cwd ?? process.cwd(), scope: agent, signal };
129
+ const { skills, complete } = await ctx.skills.snapshot(options);
130
+ const arg = rawInput.trim();
131
+ if (arg === 'conflicts') return ok(await findConflicts(options.cwd, entries().filter(e => !e.disabled && e.options.name === '@deepseek-ai/dsh-skill-filesystem').map(e => e.options.config ?? {}), skills));
132
+ if (arg && arg !== 'list') {
133
+ const skill = await ctx.skills.get(arg, options);
134
+ return skill ? ok(`${skill.name}\n${skill.description}\nsource: ${skill.source}\nprovider: ${skill.provider}\npath: ${skill.path ?? '(provider managed)'}\ninvocation: ${show(skill.invocation)}`) : fail(`Unknown skill: ${arg}`);
135
+ }
136
+ return ok(`Discovery: ${complete ? 'complete' : 'incomplete'}\n${skills.map(s => `${s.name} [${s.source}; ${s.provider}] user=${s.invocation.userInvocable} model=${s.invocation.modelInvocable}\n ${s.description}`).join('\n')}\n/skills <name> for details; /skills conflicts for duplicate filesystem names.`);
137
+ });
138
+ const hookPaths = new WeakMap();
139
+ const hookPath = entry => {
140
+ const path = entry.fiber?.config?.configPath ?? entry.options.config?.configPath;
141
+ if (typeof path === 'string') { hookPaths.set(entry, path); return path; }
142
+ const cached = hookPaths.get(entry) ?? ctx.get('dscodePaths')?.hooks;
143
+ if (typeof cached !== 'string') throw new Error('Hook configuration path has not resolved');
144
+ return cached;
145
+ };
146
+ register('hooks', 'Hook status and reload/disable/enable (installation-owned config)', async ({ rawInput }) => {
147
+ const entry = hook();
148
+ if (!entry) return fail('Hooks plugin is not mounted. Restart dscode after setup.');
149
+ const action = rawInput.trim() || 'list';
150
+ if (action !== 'list') {
151
+ if (!['reload', 'disable', 'enable'].includes(action)) return fail('Usage: /hooks [list|reload|disable|enable]');
152
+ return mutate(async () => {
153
+ if (action !== 'disable') validateHooks(JSON.parse(await readFile(hookPath(entry), 'utf8')));
154
+ if (action === 'reload') await entry.update({ disabled: true });
155
+ await entry.update({ disabled: action === 'disable' });
156
+ return ok(`Hooks: ${state(entry)}. Configuration changes apply after reload or restart.`);
157
+ });
158
+ }
159
+ const path = hookPath(entry);
160
+ const raw = JSON.parse(await readFile(path, 'utf8'));
161
+ const hooks = raw.hooks ?? raw;
162
+ return ok(`Hooks: ${state(entry)}\nConfig: ${path}\n${Object.entries(hooks).map(([event, groups]) => `${event}: ${hookEvents.includes(event) ? 'supported' : 'UNSUPPORTED'}; ${Array.isArray(groups) ? groups.length : 0} groups`).join('\n')}\nSupported: ${hookEvents.join(', ')}\nOnly synchronous command hooks. Runs as your OS user, outside tool approval. Edit only trusted installation-owned config; /hooks reload applies it. No project hook auto-loading.\nPreCompact/PostCompact, PermissionRequest and subagent events are not supported by this bridge.`);
163
+ });
164
+ }
@@ -0,0 +1,11 @@
1
+ export const ULTRA_POLICY = `DSCODE ULTRA — max reasoning with deliberate collaboration.
2
+ Actively look for useful independent tasks: investigation, implementation in disjoint files, and independent review. Delegate only when it improves the outcome; simple tasks need no child agents. Give each child a bounded objective, relevant context, file ownership and acceptance criteria. Keep useful work for yourself while children run. Never have multiple agents edit the same files concurrently. Shared files are not isolated worktrees.
3
+ In ultra use subagent/subagent_fork and send_message for delegation, not workflow or ralph. Use at most three child agents concurrently across this root session. Do not recursively proliferate agents or duplicate investigations. The parent owns integration, verifies child claims, resolves conflicts and runs appropriate checks. Seek independent review of substantial changes when useful. Parent/child messages are available; sibling direct messaging is not. Preserve the user's permission policy: ultra grants no extra authority. Track progress and costs, reuse findings, and stop delegating when coordination costs outweigh value.`;
4
+ export function ultraRequest(options, messages) {
5
+ if (options.reasoningEffort !== 'ultra' || options.purpose || !options.tools?.some(t => t.name === 'subagent' || t.name === 'subagent_fork')) return messages;
6
+ const copy = messages.map(m => ({ ...m }));
7
+ const system = copy.findLast(m => m.role === 'system' && typeof m.content === 'string');
8
+ if (system) system.content += '\n\n' + ULTRA_POLICY;
9
+ else copy.unshift({ role: 'system', content: ULTRA_POLICY });
10
+ return copy;
11
+ }
@@ -0,0 +1,288 @@
1
+ # The `dscode` agent preset: the full coding agent, mounted once per process.
2
+ #
3
+ # This file is an AGENT-PLANE composition. The roster mounts it ONCE under a
4
+ # standing scope; every session naming it joins by scope parentage, so the
5
+ # tools and prompt sections registered here cover each joined agent while a
6
+ # session's own state stays keyed per Session/Agent inside the plugins. The
7
+ # host composition (`base.cordis.yml` + `web.cordis.yml`) keeps everything a
8
+ # preset must not own: the registries themselves, the sandbox and approval
9
+ # stack, persistence, and the model route.
10
+ #
11
+ # A service row here MUST sit inside a group carrying an `isolate` realm.
12
+ # Without one it publishes into the root realm, where it is process-global —
13
+ # another preset publishing the same name collides, and a host reader would
14
+ # resolve one preset's instance for every session; `dsh-agent-presets` rejects
15
+ # that at mount. `true` means an entry-local realm: this standing mount's own
16
+ # private instance, apart from every other preset's. (A shared label does NOT
17
+ # pool instances — `provide()` throws on the second registration under the
18
+ # same realm symbol; labels join REALMS, and are not what this file needs.)
19
+
20
+ # ── identity ────────────────────────────────────────────────────────────────
21
+
22
+ # The preset's own persona, shadowing the deployment default for this agent.
23
+ # `{{model}}` and `{{cwd}}` resolve from the agent's own route and workspace.
24
+ - id: persona
25
+ name: '@deepseek-ai/dsh-persona'
26
+ config:
27
+ suffix: Your working directory is {{cwd}}.
28
+ prefix: >-
29
+ You are a coding agent powered by the {{model}} model. Use the persistent shell for file reading, searching, and editing. Prefer rg and precise patches.
30
+
31
+ - id: agent-instructions
32
+ name: '@deepseek-ai/dsh-agent-instructions'
33
+ config:
34
+ maxBytes: 65536
35
+
36
+ - id: persistent-shell
37
+ name: cordis:group
38
+ group: true
39
+ isolate:
40
+ terminals: true
41
+ config:
42
+ - id: pty
43
+ name: '@deepseek-ai/dsh-terminal'
44
+
45
+ - id: terminal-bash
46
+ name: '@deepseek-ai/dsh-terminal-bash'
47
+ disabled: !!js process.platform === 'win32'
48
+ config:
49
+ timeoutMs: 300000
50
+
51
+ - id: persistent-bash
52
+ name: '@toddzheng024/dscode-bundle/persistent'
53
+ disabled: !!js process.platform === 'win32'
54
+ config:
55
+ timeoutMs: 300000
56
+ description: |-
57
+ Run commands in a bash shell
58
+ * When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
59
+ * Network access depends on the task environment. Prefer configured mirrors/proxies when they are available.
60
+ * State is persistent across command calls and discussions with the user.
61
+ * To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'.
62
+ * Please avoid commands that may produce a very large amount of output.
63
+ * Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.
64
+
65
+ - id: terminal-pwsh
66
+ name: '@deepseek-ai/dsh-terminal-bash'
67
+ disabled: !!js process.platform !== 'win32'
68
+ config:
69
+ shellDialect: pwsh
70
+ timeoutMs: 300000
71
+
72
+ - id: persistent-pwsh
73
+ name: '@deepseek-ai/dsh-tool-pwsh-persistent'
74
+ disabled: !!js process.platform !== 'win32'
75
+ config:
76
+ timeoutMs: 300000
77
+ description: |-
78
+ Run commands in a PowerShell shell
79
+ * When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
80
+ * You don't have access to the internet via this tool.
81
+ * State is persistent across command calls and discussions with the user.
82
+ * Use native Windows paths (C:\...) and $env:NAME variables; this is PowerShell, not bash.
83
+ * Please avoid commands that may produce a very large amount of output.
84
+ * Please run long lived commands in the background, e.g. 'Start-Job' or start a server with Start-Process.
85
+
86
+ - id: dscode-policy
87
+ name: '@toddzheng024/dscode-bundle/policy'
88
+
89
+ # A fresh shell for reviewed escalation. It does not inherit PTY state.
90
+ - id: shell-retry
91
+ name: '@toddzheng024/dscode-bundle/bash'
92
+ config:
93
+ toolName: shell_retry
94
+
95
+
96
+ # ── background jobs ────────────────────────────────────────────────────────
97
+
98
+ # Only the model-facing controls. The task REGISTRY stays on the host plane:
99
+ # its producers sit outside any realm this file could put it in — `tool-bash`
100
+ # above resolves it with `ctx.get`, and an entry-local realm here is invisible
101
+ # to every sibling row, so `run_in_background` would answer "background jobs
102
+ # unavailable" while these controls sat in the catalog. The registry is keyed by
103
+ # owning agent anyway, so one host instance serves every session. What a preset
104
+ # chooses is whether its agent can collect and stop background work at all.
105
+ - id: tool-jobs
106
+ name: '@deepseek-ai/dsh-tool-jobs'
107
+
108
+ # ── skills ──────────────────────────────────────────────────────────────────
109
+
110
+ # The skill REGISTRY lives in the host composition and is layered per scope:
111
+ # these rows register into THIS preset's layer of it, so they need no realm.
112
+ # `skill-filesystem` contributes local-root discovery for agents on this preset, and
113
+ # `tool-skill` gives them the catalog and loader; the merged catalog also
114
+ # carries whatever the deployment registered globally (repository plugins).
115
+ - id: skill-filesystem
116
+ name: '@deepseek-ai/dsh-skill-filesystem'
117
+
118
+ - id: tool-skill
119
+ name: '@deepseek-ai/dsh-tool-skill'
120
+
121
+ # ── goals ───────────────────────────────────────────────────────────────────
122
+
123
+ # The goal service and session driver stay on the host plane, where the Gateway
124
+ # can resolve them. The human command and model-facing tool register into this
125
+ # preset's scoped layers.
126
+ - id: command-goal
127
+ name: '@deepseek-ai/dsh-command-goal'
128
+
129
+ - id: tool-goal
130
+ name: '@deepseek-ai/dsh-tool-goal'
131
+
132
+ # ── plan mode ───────────────────────────────────────────────────────────────
133
+
134
+ # Plan state is per-agent by nature, so an entry-local realm is not a
135
+ # workaround here — it is the correct lifetime.
136
+ - id: planning
137
+ name: cordis:group
138
+ group: true
139
+ isolate:
140
+ planMode: true
141
+ config:
142
+ - id: plan-mode
143
+ name: '@deepseek-ai/dsh-plan-mode'
144
+ config:
145
+ section: |
146
+ You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
147
+
148
+ Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
149
+
150
+ The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
151
+
152
+ Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
153
+
154
+ Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
155
+
156
+ When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
157
+
158
+ # ── compaction ──────────────────────────────────────────────────────────────
159
+
160
+ # `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
161
+ # share this realm rather than sit outside it.
162
+ #
163
+ # `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
164
+ # plane, and the rows here resolve that one instance. It takes no configuration,
165
+ # keys every fold by Session, and owns the context-meter projection units the
166
+ # browser reads for every session — behind a realm those units would come and go
167
+ # with whichever presets happen to be mounted. What a preset chooses is whether
168
+ # its agent compacts at all, which is `compaction-basic` below.
169
+ - id: compaction
170
+ name: cordis:group
171
+ group: true
172
+ isolate:
173
+ compaction: true
174
+ toolResultPruner: true
175
+ config:
176
+ - id: compaction-basic
177
+ name: '@deepseek-ai/dsh-compaction-basic'
178
+
179
+ - id: command-compact
180
+ name: '@deepseek-ai/dsh-command-compact'
181
+
182
+ - id: tool-result-pruner
183
+ name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
184
+ config:
185
+ thresholdChars: 8192
186
+ headChars: 4096
187
+ tailChars: 1024
188
+
189
+ # ── delegation and workflows ────────────────────────────────────────────────
190
+
191
+ # The `subagents` registry and its spawn/fork backends live in the HOST
192
+ # composition: the registry is a process singleton whose cross-session queries
193
+ # the api-proxy serves to the browser, and a provider name may only be
194
+ # registered once. This preset contributes the delegation TOOLS, which resolve
195
+ # that host registry.
196
+ #
197
+ # `workflows` is different — nothing outside an agent reads it — so every row
198
+ # that reaches it shares one entry-local realm here, and a consumer left
199
+ # outside would resolve a host registry this preset does not populate.
200
+ - id: delegation
201
+ name: cordis:group
202
+ group: true
203
+ isolate:
204
+ workflowEngine: true
205
+ config:
206
+ - id: tool-subagent-control
207
+ name: '@deepseek-ai/dsh-tool-subagent-control'
208
+
209
+ - id: tool-subagent-list-agents
210
+ name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
211
+
212
+ - id: tool-subagent
213
+ name: '@deepseek-ai/dsh-tool-subagent'
214
+ config:
215
+ provider: spawn
216
+ toolName: subagent
217
+ modelSelectionSettings: true
218
+ backgroundMode: continuable
219
+ maxDepth: 1
220
+
221
+ # Fork omits model selection so provider/model stay equal to the parent and
222
+ # the inherited history remains eligible for KV Cache reuse. This preset
223
+ # keeps fork continuable; parent and child inherit the same messaging tool,
224
+ # while the parent id and return guidance follow the inherited history.
225
+ - id: tool-subagent-fork
226
+ name: '@deepseek-ai/dsh-tool-subagent'
227
+ config:
228
+ provider: fork
229
+ toolName: subagent_fork
230
+ backgroundMode: continuable
231
+ maxDepth: 1
232
+
233
+ # Production dsh does not install these optional providers. Install the
234
+ # matching Bundle in this Profile and restart the Host, then copy this
235
+ # preset and remove `disabled` from the matching tool row. Host availability
236
+ # alone grants no tool.
237
+ - id: tool-subagent-codex
238
+ name: '@deepseek-ai/dsh-tool-subagent'
239
+ disabled: true
240
+ config:
241
+ provider: codex
242
+ toolName: subagent_codex
243
+ backgroundMode: one-shot
244
+ maxDepth: provider-managed
245
+
246
+ - id: tool-subagent-claude-code
247
+ name: '@deepseek-ai/dsh-tool-subagent'
248
+ disabled: true
249
+ config:
250
+ provider: claude-code
251
+ toolName: subagent_claude_code
252
+ backgroundMode: one-shot
253
+ maxDepth: provider-managed
254
+
255
+ - id: workflow-worker-thread
256
+ name: '@deepseek-ai/dsh-workflow-worker-thread'
257
+ config:
258
+ provider: spawn
259
+
260
+ - id: tool-workflow
261
+ name: '@deepseek-ai/dsh-tool-workflow'
262
+
263
+ - id: tool-ralph
264
+ name: '@deepseek-ai/dsh-tool-ralph'
265
+ config:
266
+ subagentProvider: spawn
267
+ maxRounds: 64
268
+
269
+ # ── remaining model-facing rows ─────────────────────────────────────────────
270
+
271
+ - id: tool-ask-user
272
+ name: '@deepseek-ai/dsh-tool-ask-user'
273
+
274
+ - id: tool-todo
275
+ name: '@deepseek-ai/dsh-tool-todo'
276
+ config:
277
+ allowParallelInProgress: true
278
+
279
+ # The `web` service and its search provider stay in the host composition; only
280
+ # the model-facing tool is per-session.
281
+ - id: tool-web
282
+ name: '@deepseek-ai/dsh-tool-web'
283
+ config:
284
+ fetch: true
285
+ searchTimeoutMs: 60000
286
+
287
+ - id: present
288
+ name: '@deepseek-ai/dsh-tool-present'
@@ -0,0 +1,3 @@
1
+ name: DSCODE
2
+ description: 持久 Shell 编码环境,保留 Skills、压缩、目标、子代理与工作流。
3
+ order: 0