@toddzheng024/dscode-bundle 0.7.28 → 0.7.29
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/cordis.patch.yml +19 -5
- package/package.json +2 -1
- package/plugins/account/index.mjs +178 -0
- package/plugins/account/state.mjs +123 -0
- package/plugins/dscode/index.mjs +1 -1
- package/plugins/i18n/messages.mjs +204 -0
package/cordis.patch.yml
CHANGED
|
@@ -621,7 +621,11 @@
|
|
|
621
621
|
disabled: true
|
|
622
622
|
- id: tool-subagent-fork
|
|
623
623
|
disabled: true
|
|
624
|
-
|
|
624
|
+
# The workflow engine is preset-owned: DSCODE's delegation group mounts it inside an
|
|
625
|
+
# entry-local `workflowEngine` realm, so the host row stays off. DSH 0.1.7 renamed this
|
|
626
|
+
# row from `workflow-worker-thread`, and a patch addresses rows by id — an id that no
|
|
627
|
+
# longer exists silently patches nothing, which is how the host engine came back.
|
|
628
|
+
- id: workflow-ptc
|
|
625
629
|
disabled: true
|
|
626
630
|
- id: tool-workflow
|
|
627
631
|
disabled: true
|
|
@@ -638,10 +642,18 @@
|
|
|
638
642
|
# preset became a single-tool composition), so this patch no longer lists it.
|
|
639
643
|
|
|
640
644
|
- insert:
|
|
641
|
-
#
|
|
642
|
-
#
|
|
643
|
-
-
|
|
644
|
-
|
|
645
|
+
# The account login's browser callback: `deepseekAccount` registers /oauth/callback on
|
|
646
|
+
# `ctx.webServer` for the lifetime of one attempt, so the terminal needs an HTTP
|
|
647
|
+
# transport of its own. Loopback only and an OS-assigned port, and no route exists
|
|
648
|
+
# until a login runs — the provider's route is the only one this profile registers,
|
|
649
|
+
# because the Web shell's frontend and API rows stay in the web-app bundle.
|
|
650
|
+
# `DSCODE_ACCOUNT_LOGIN=0` removes the listener, and `/account` then says why.
|
|
651
|
+
- id: webserver
|
|
652
|
+
name: '@deepseek-ai/dsh-host-webserver'
|
|
653
|
+
disabled: !!js process.env.DSCODE_ACCOUNT_LOGIN === '0'
|
|
654
|
+
config:
|
|
655
|
+
host: 127.0.0.1
|
|
656
|
+
port: 0
|
|
645
657
|
|
|
646
658
|
# Cross-session @mentions: bounded read-only snapshots of other sessions
|
|
647
659
|
# as sourced model context. Opt-in upstream; the TUI's completion menu
|
|
@@ -897,6 +909,8 @@
|
|
|
897
909
|
keyboardPolicy: activate
|
|
898
910
|
pointerInputPolicy: targeted
|
|
899
911
|
cursorVisualization: visible
|
|
912
|
+
- id: dscode-account
|
|
913
|
+
name: "@toddzheng024/dscode-bundle/account"
|
|
900
914
|
- id: dscode-hooks
|
|
901
915
|
name: "@deepseek-ai/dsh-hooks-codex"
|
|
902
916
|
inject:
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.7.
|
|
2
|
+
"version": "0.7.29",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Todd Zheng",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"./tui-tools": "./plugins/tui-tools/index.mjs",
|
|
61
61
|
"./triggers": "./plugins/triggers/index.mjs",
|
|
62
62
|
"./computer-use": "./plugins/computer-use/index.mjs",
|
|
63
|
+
"./account": "./plugins/account/index.mjs",
|
|
63
64
|
"./command-goal": "./vendor/command-goal/index.js"
|
|
64
65
|
},
|
|
65
66
|
"dependencies": {
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/account`: the DeepSeek account login, from the terminal.
|
|
3
|
+
*
|
|
4
|
+
* DSH 0.1.7 added an account plane beside API keys: `deepseekAccount` signs in through the
|
|
5
|
+
* system browser with PKCE, keeps the grant in the local credential store, and the DeepSeek
|
|
6
|
+
* route resolves it for the configured inference origin — so a signed-in user needs no
|
|
7
|
+
* `DEEPSEEK_API_KEY` at all. Upstream drives that plane from its Web settings page; this
|
|
8
|
+
* command is DSCODE's terminal equivalent.
|
|
9
|
+
*
|
|
10
|
+
* The provider registers `/oauth/callback` on the Host webserver for the lifetime of one
|
|
11
|
+
* attempt, which is why the TUI profile composes a loopback webserver (see
|
|
12
|
+
* `packages/tui/cordis.patch.yml`). Without it the command says so instead of starting an
|
|
13
|
+
* attempt the browser could never complete.
|
|
14
|
+
*
|
|
15
|
+
* Tokens never pass through here: the service's view is the client-safe projection, and
|
|
16
|
+
* `resolveToken` stays Host-only inside the LLM route.
|
|
17
|
+
*
|
|
18
|
+
* @module dscode-account
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { execFile } from 'node:child_process';
|
|
22
|
+
import { readLanguage, t } from '../i18n/messages.mjs';
|
|
23
|
+
import { ACTIVE_PHASES, TERMINAL_PHASES, callbackOrigin, outcomeLines, signInBlocked, statusLines } from './state.mjs';
|
|
24
|
+
|
|
25
|
+
export const name = 'dscode-account';
|
|
26
|
+
export const inject = ['commands'];
|
|
27
|
+
|
|
28
|
+
/** Upper bound for one terminal-driven attempt; the provider applies its own deadline too. */
|
|
29
|
+
const ATTEMPT_WAIT_MS = 300000;
|
|
30
|
+
|
|
31
|
+
const ok = text => ({ kind: 'success', text });
|
|
32
|
+
const fail = text => ({ kind: 'error', text });
|
|
33
|
+
|
|
34
|
+
/** Open a URL in the user's browser, reporting failure instead of throwing. */
|
|
35
|
+
export function openBrowser(url, { platform = process.platform, run = execFile } = {}) {
|
|
36
|
+
return new Promise(resolve => {
|
|
37
|
+
if (platform === 'win32') { resolve(false); return; }
|
|
38
|
+
run(platform === 'darwin' ? 'open' : 'xdg-open', [url], { timeout: 10000 }, error => resolve(!error));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Follow one attempt until `done(view)` accepts it, the attempt is replaced, or the wait
|
|
44
|
+
* runs out.
|
|
45
|
+
*
|
|
46
|
+
* `watch()` yields the current state first and then on every change, so an already
|
|
47
|
+
* settled attempt returns immediately and a cancelled one does not hang. Both the
|
|
48
|
+
* caller's signal and the bounded wait end the iteration.
|
|
49
|
+
*
|
|
50
|
+
* @param account - the account service.
|
|
51
|
+
* @param id - the attempt this wait belongs to; a different attempt ends the wait.
|
|
52
|
+
* @param done - predicate over each view; true stops the wait.
|
|
53
|
+
* @param signal - the command's cancellation.
|
|
54
|
+
* @returns the last view seen.
|
|
55
|
+
*/
|
|
56
|
+
export async function followAttempt(account, id, done, signal, { timeoutMs = ATTEMPT_WAIT_MS } = {}) {
|
|
57
|
+
const deadline = AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]);
|
|
58
|
+
let last = await account.getState();
|
|
59
|
+
if (done(last)) return last;
|
|
60
|
+
try {
|
|
61
|
+
for await (const view of account.watch(deadline)) {
|
|
62
|
+
last = view;
|
|
63
|
+
const attempt = view.attempt;
|
|
64
|
+
if (attempt === null || attempt.id !== id) break;
|
|
65
|
+
if (done(view)) break;
|
|
66
|
+
}
|
|
67
|
+
} catch { /* an aborted watch leaves the last view in place */ }
|
|
68
|
+
return last;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A view whose attempt carries the browser URL, or has already settled without one. */
|
|
72
|
+
export function authorizeReady(view, id) {
|
|
73
|
+
const attempt = view.attempt;
|
|
74
|
+
if (attempt === null || attempt.id !== id) return true;
|
|
75
|
+
return attempt.authorizeUrl !== undefined || TERMINAL_PHASES.includes(attempt.phase);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A view whose attempt will not change again. */
|
|
79
|
+
export function settled(view, id) {
|
|
80
|
+
const attempt = view.attempt;
|
|
81
|
+
if (attempt === null || attempt.id !== id) return true;
|
|
82
|
+
return TERMINAL_PHASES.includes(attempt.phase);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Run one browser sign-in to settlement.
|
|
87
|
+
*
|
|
88
|
+
* Kept apart from the command so the whole flow — waiting for the URL, opening the
|
|
89
|
+
* browser, following the attempt, reading the result — is drivable with a fake service
|
|
90
|
+
* and a fake opener.
|
|
91
|
+
*
|
|
92
|
+
* @param service - the account service.
|
|
93
|
+
* @param origin - the loopback callback origin.
|
|
94
|
+
* @param locale - DSCODE interface language.
|
|
95
|
+
* @param signal - the command's cancellation.
|
|
96
|
+
* @param open - browser opener returning whether it launched.
|
|
97
|
+
* @returns the lines to print and whether the sign-in succeeded.
|
|
98
|
+
*/
|
|
99
|
+
export async function runSignIn({ service, origin, locale, signal, open = openBrowser }) {
|
|
100
|
+
// An attempt already in flight is returned unchanged rather than replaced, so this also
|
|
101
|
+
// covers "resume the sign-in you started a moment ago".
|
|
102
|
+
const started = await service.startSignIn(locale, origin, 'desktop');
|
|
103
|
+
const attempt = started.attempt;
|
|
104
|
+
if (attempt === null || attempt === undefined) return { ok: false, lines: [t(locale, 'account.startFailed')] };
|
|
105
|
+
const lines = [t(locale, 'account.starting')];
|
|
106
|
+
// The browser URL is not part of `startSignIn`'s answer: the provider returns as soon as
|
|
107
|
+
// the attempt exists and publishes the URL once `auth_init` replies, so it is awaited
|
|
108
|
+
// before anything is opened.
|
|
109
|
+
const ready = await followAttempt(service, attempt.id, view => authorizeReady(view, attempt.id), signal);
|
|
110
|
+
const url = ready.attempt?.id === attempt.id ? ready.attempt.authorizeUrl : undefined;
|
|
111
|
+
if (url !== undefined) {
|
|
112
|
+
lines.push(await open(url) ? t(locale, 'account.browserOpened') : t(locale, 'account.browserManual'));
|
|
113
|
+
lines.push(url);
|
|
114
|
+
}
|
|
115
|
+
const final = settled(ready, attempt.id) ? ready : await followAttempt(service, attempt.id, view => settled(view, attempt.id), signal);
|
|
116
|
+
const details = final.status === 'credential-stored' ? await readDetails(service) : undefined;
|
|
117
|
+
return {
|
|
118
|
+
ok: final.attempt?.phase === 'succeeded' || final.status === 'credential-stored',
|
|
119
|
+
lines: [...lines, '', ...outcomeLines(final, details, locale)],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function apply(ctx) {
|
|
124
|
+
const language = () => readLanguage();
|
|
125
|
+
const account = () => ctx.get('deepseekAccount');
|
|
126
|
+
|
|
127
|
+
ctx.commands.register({
|
|
128
|
+
name: 'account',
|
|
129
|
+
description: 'Sign in to a DeepSeek account through the browser, or read its status (/account [login|cancel|logout])',
|
|
130
|
+
input: { hint: '[login|cancel|logout]' },
|
|
131
|
+
handler: async ({ rawInput, signal }) => {
|
|
132
|
+
const locale = language();
|
|
133
|
+
const service = account();
|
|
134
|
+
const argument = String(rawInput ?? '').trim().toLowerCase();
|
|
135
|
+
if (service === undefined) return fail(t(locale, 'account.unavailable'));
|
|
136
|
+
|
|
137
|
+
if (argument === '' || argument === 'status') {
|
|
138
|
+
const view = await service.getState();
|
|
139
|
+
const details = view.status === 'credential-stored' ? await readDetails(service) : undefined;
|
|
140
|
+
return ok(statusLines(view, details, locale).join('\n'));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (argument === 'logout' || argument === 'sign-out' || argument === 'signout') {
|
|
144
|
+
const view = await service.signOut();
|
|
145
|
+
return ok(statusLines(view, undefined, locale).join('\n'));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (argument === 'cancel') {
|
|
149
|
+
const current = await service.getState();
|
|
150
|
+
const attempt = current.attempt;
|
|
151
|
+
if (attempt === null || !ACTIVE_PHASES.includes(attempt.phase)) return ok(t(locale, 'account.nothingToCancel'));
|
|
152
|
+
const view = await service.cancelSignIn(attempt.id);
|
|
153
|
+
return ok(outcomeLines(view, undefined, locale).join('\n'));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (argument !== 'login' && argument !== 'signin' && argument !== 'sign-in') {
|
|
157
|
+
return fail(t(locale, 'account.usage'));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const origin = callbackOrigin(ctx.get('webServer'));
|
|
161
|
+
const blocked = signInBlocked(service, origin, locale);
|
|
162
|
+
if (blocked !== undefined) return fail(blocked);
|
|
163
|
+
|
|
164
|
+
const result = await runSignIn({ service, origin, locale, signal });
|
|
165
|
+
ctx.logger?.info?.(`account: sign-in ${result.ok ? 'succeeded' : 'did not complete'}`);
|
|
166
|
+
return (result.ok ? ok : fail)(result.lines.join('\n'));
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Read identity and balance, letting either failure render on its own. */
|
|
172
|
+
async function readDetails(service) {
|
|
173
|
+
const [profile, balance] = await Promise.all([
|
|
174
|
+
service.getProfile().catch(() => ({ status: 'failed' })),
|
|
175
|
+
service.getBalance().catch(() => ({ status: 'failed' })),
|
|
176
|
+
]);
|
|
177
|
+
return { profile: profile ?? undefined, balance: balance ?? undefined };
|
|
178
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rendering and validation for the DeepSeek account surface.
|
|
3
|
+
*
|
|
4
|
+
* The account service (`@deepseek-ai/dsh-deepseek-account`) owns the login protocol: a
|
|
5
|
+
* system-browser sign-in with PKCE, a callback the provider registers on the Host
|
|
6
|
+
* webserver, and a grant kept in the local credential store. Nothing here touches that
|
|
7
|
+
* protocol — it turns one `AccountView` (plus the optional profile and balance reads)
|
|
8
|
+
* into the lines `/account` prints, so the command stays a thin driver and every rule
|
|
9
|
+
* below is testable without a Host.
|
|
10
|
+
*
|
|
11
|
+
* Credentials never reach this module: `AccountView` is the client-safe projection and
|
|
12
|
+
* carries no token.
|
|
13
|
+
*
|
|
14
|
+
* @module dscode-account/state
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { t } from '../i18n/messages.mjs';
|
|
18
|
+
|
|
19
|
+
/** Attempt phases that will not change again without a new sign-in. */
|
|
20
|
+
export const TERMINAL_PHASES = Object.freeze(['succeeded', 'cancelled', 'expired', 'failed']);
|
|
21
|
+
|
|
22
|
+
/** Phases during which a sign-in is still moving. */
|
|
23
|
+
export const ACTIVE_PHASES = Object.freeze(['initializing', 'waiting-browser', 'exchanging', 'committing']);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The browser-reachable callback origin for one sign-in.
|
|
27
|
+
*
|
|
28
|
+
* The provider accepts exactly `http://127.0.0.1:<port>` (or localhost/[::1]) with a
|
|
29
|
+
* non-zero explicit port and no path, because that is the loopback redirect the browser
|
|
30
|
+
* is sent back to. An unmounted or unbound webserver has no origin, and the caller
|
|
31
|
+
* reports that rather than starting an attempt that cannot complete.
|
|
32
|
+
*
|
|
33
|
+
* @param webServer - the Host webserver service, or undefined when none is composed.
|
|
34
|
+
* @returns the origin, or undefined when no login can be served.
|
|
35
|
+
*/
|
|
36
|
+
export function callbackOrigin(webServer) {
|
|
37
|
+
const port = webServer?.port;
|
|
38
|
+
if (!Number.isInteger(port) || port <= 0) return undefined;
|
|
39
|
+
return `http://127.0.0.1:${port}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One wallet as `12.34 CNY`, keeping the server's decimal string exactly. */
|
|
43
|
+
export function formatWallet(wallet) {
|
|
44
|
+
return `${wallet.balance} ${wallet.currency}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The balance line, or undefined when the deployment reported none.
|
|
49
|
+
*
|
|
50
|
+
* Recharge and bonus wallets stay separate: upstream keeps them apart because a bonus
|
|
51
|
+
* wallet is not spendable credit in the same sense, and a failed query supplies neither.
|
|
52
|
+
*/
|
|
53
|
+
export function balanceLine(balance, locale) {
|
|
54
|
+
if (balance === undefined || balance === null) return undefined;
|
|
55
|
+
if (balance.status !== 'ready') return t(locale, 'account.balanceFailed');
|
|
56
|
+
const wallets = balance.value.map(formatWallet).join(', ');
|
|
57
|
+
const bonus = balance.bonusWallets.map(formatWallet).join(', ');
|
|
58
|
+
if (wallets === '' && bonus === '') return t(locale, 'account.balanceEmpty');
|
|
59
|
+
const parts = [wallets === '' ? undefined : wallets, bonus === '' ? undefined : t(locale, 'account.bonus', { wallets: bonus })];
|
|
60
|
+
return t(locale, 'account.balance', { wallets: parts.filter(Boolean).join(' · ') });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The identity line, or undefined when the deployment reported none. */
|
|
64
|
+
export function profileLine(profile, locale) {
|
|
65
|
+
if (profile === undefined || profile === null) return undefined;
|
|
66
|
+
if (profile.status !== 'ready') return t(locale, 'account.profileFailed');
|
|
67
|
+
const { name, contact, id } = profile.value;
|
|
68
|
+
const identity = [name, contact].filter(value => typeof value === 'string' && value !== '').join(' · ');
|
|
69
|
+
return t(locale, 'account.profile', { identity: identity === '' ? (id ?? t(locale, 'account.unknownIdentity')) : identity });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One line for the latest attempt, including a terminal outcome, or undefined when there was none. */
|
|
73
|
+
export function attemptLine(attempt, locale) {
|
|
74
|
+
if (attempt === null || attempt === undefined) return undefined;
|
|
75
|
+
const phase = t(locale, `account.phase.${attempt.phase}`);
|
|
76
|
+
if (attempt.errorCode === undefined) return t(locale, 'account.attempt', { phase });
|
|
77
|
+
return t(locale, 'account.attemptFailed', { phase, reason: t(locale, `account.error.${attempt.errorCode}`) });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render `/account` with no argument: stored-credential status, the identity and balance
|
|
82
|
+
* when they were read, the latest attempt, and the platform's own pages.
|
|
83
|
+
*
|
|
84
|
+
* `credential-stored` is deliberately not a claim that the server still accepts the
|
|
85
|
+
* grant — upstream stores no expiry — so the wording says what is on this machine.
|
|
86
|
+
*
|
|
87
|
+
* @param view - client-safe account state.
|
|
88
|
+
* @param details - optional profile and balance reads; absent when signed out or unread.
|
|
89
|
+
* @param locale - DSCODE interface language.
|
|
90
|
+
* @returns the lines to print, in order.
|
|
91
|
+
*/
|
|
92
|
+
export function statusLines(view, details, locale) {
|
|
93
|
+
const signedIn = view.status === 'credential-stored';
|
|
94
|
+
const lines = [t(locale, signedIn ? 'account.signedIn' : 'account.signedOut')];
|
|
95
|
+
if (signedIn) {
|
|
96
|
+
for (const line of [profileLine(details?.profile, locale), balanceLine(details?.balance, locale)]) if (line !== undefined) lines.push(line);
|
|
97
|
+
}
|
|
98
|
+
const attempt = attemptLine(view.attempt, locale);
|
|
99
|
+
if (attempt !== undefined) lines.push(attempt);
|
|
100
|
+
lines.push(t(locale, 'account.links', { usage: view.links.usageUrl, topUp: view.links.topUpUrl }));
|
|
101
|
+
lines.push(t(locale, signedIn ? 'account.hintSignedIn' : 'account.hintSignedOut'));
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Why a sign-in cannot start, or undefined when it can.
|
|
107
|
+
*
|
|
108
|
+
* @param account - the account service, or undefined when the deployment composes none.
|
|
109
|
+
* @param origin - the resolved callback origin from {@link callbackOrigin}.
|
|
110
|
+
* @param locale - DSCODE interface language.
|
|
111
|
+
*/
|
|
112
|
+
export function signInBlocked(account, origin, locale) {
|
|
113
|
+
if (account === undefined) return t(locale, 'account.unavailable');
|
|
114
|
+
if (origin === undefined) return t(locale, 'account.noCallback');
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The lines printed once an attempt settles. */
|
|
119
|
+
export function outcomeLines(view, details, locale) {
|
|
120
|
+
const phase = view.attempt?.phase;
|
|
121
|
+
if (phase === 'succeeded') return statusLines(view, details, locale);
|
|
122
|
+
return [attemptLine(view.attempt, locale) ?? t(locale, 'account.signedOut')];
|
|
123
|
+
}
|
package/plugins/dscode/index.mjs
CHANGED
|
@@ -30,7 +30,7 @@ const DELEGATION_TOOLS = ['subagent', 'subagent_fork', 'workflow', 'ralph'];
|
|
|
30
30
|
// repository there instead of pointing at a path that does not exist.
|
|
31
31
|
const DOCS_DIR = fileURLToPath(new URL('../../docs/', import.meta.url));
|
|
32
32
|
const DOCS_REPO = 'https://github.com/qiz029/dscode/blob/main/docs/';
|
|
33
|
-
export const DOCS_SECTION = `To answer questions about DSCODE itself - what it supports, how a feature is configured, what a command does - treat the bundled user guides as the source of truth instead of guessing: ${existsSync(DOCS_DIR) ? `${DOCS_DIR} plus the repository root README.md (session-bridge.md, session-communication.md, session-cards.md, memory.md, exec.md, email.md, skills.md, tui-commands.md, session-metrics.md, dscode-ultra.md, auto-review.md, demo.md, triggers.md)` : `the repository documentation at ${DOCS_REPO} plus the root README.md (session-bridge.md, session-communication.md, session-cards.md, memory.md, exec.md, email.md, skills.md, tui-commands.md, session-metrics.md, dscode-ultra.md, auto-review.md, demo.md, triggers.md)`}. Read the relevant guide before answering rather than relying on memory of an earlier session. docs/CONTEXT-HANDOFF.md, docs/session-messaging-design.md, docs/cloud-webapp-host.md, docs/triggers-design.md, docs/verification.md, docs/maintainability.md and docs/vendored-tui-upgrade.md are internal development records, not user documentation: never quote them to a user or treat them as a specification. When the guides do not cover something, describe only what the running installation actually does and say plainly that it is not documented.`;
|
|
33
|
+
export const DOCS_SECTION = `To answer questions about DSCODE itself - what it supports, how a feature is configured, what a command does - treat the bundled user guides as the source of truth instead of guessing: ${existsSync(DOCS_DIR) ? `${DOCS_DIR} plus the repository root README.md (session-bridge.md, session-communication.md, session-cards.md, memory.md, exec.md, email.md, skills.md, tui-commands.md, session-metrics.md, dscode-ultra.md, auto-review.md, account-login.md, demo.md, triggers.md)` : `the repository documentation at ${DOCS_REPO} plus the root README.md (session-bridge.md, session-communication.md, session-cards.md, memory.md, exec.md, email.md, skills.md, tui-commands.md, session-metrics.md, dscode-ultra.md, auto-review.md, account-login.md, demo.md, triggers.md)`}. Read the relevant guide before answering rather than relying on memory of an earlier session. docs/CONTEXT-HANDOFF.md, docs/session-messaging-design.md, docs/cloud-webapp-host.md, docs/triggers-design.md, docs/verification.md, docs/maintainability.md and docs/vendored-tui-upgrade.md are internal development records, not user documentation: never quote them to a user or treat them as a specification. When the guides do not cover something, describe only what the running installation actually does and say plainly that it is not documented.`;
|
|
34
34
|
|
|
35
35
|
export function apply(ctx) {
|
|
36
36
|
ctx.systemPrompt.section({ name: 'dscode:shell-policy', order: 1050, text: SHELL_POLICY });
|
|
@@ -53,6 +53,40 @@ export const MESSAGES = {
|
|
|
53
53
|
'doctor.noRoute': 'No model route is available, so the model analysis could not run.',
|
|
54
54
|
'doctor.scope': 'Evidence scope: {traces} recent sessions, {logs} warnings/errors; conversation text, tool arguments and tool output were not read.',
|
|
55
55
|
'doctor.failed': 'Model analysis did not finish: {error}.',
|
|
56
|
+
'account.signedIn': 'DeepSeek account: signed in on this machine (the stored grant is not re-checked here).',
|
|
57
|
+
'account.signedOut': 'DeepSeek account: signed out. The DeepSeek route falls back to an API key.',
|
|
58
|
+
'account.profile': 'Identity: {identity}',
|
|
59
|
+
'account.profileFailed': 'Identity: could not be read just now.',
|
|
60
|
+
'account.unknownIdentity': 'no display name',
|
|
61
|
+
'account.balance': 'Balance: {wallets}',
|
|
62
|
+
'account.bonus': 'bonus {wallets}',
|
|
63
|
+
'account.balanceFailed': 'Balance: could not be read just now.',
|
|
64
|
+
'account.balanceEmpty': 'Balance: no wallet reported.',
|
|
65
|
+
'account.attempt': 'Last sign-in: {phase}',
|
|
66
|
+
'account.attemptFailed': 'Last sign-in: {phase} ({reason})',
|
|
67
|
+
'account.phase.initializing': 'starting',
|
|
68
|
+
'account.phase.waiting-browser': 'waiting for the browser',
|
|
69
|
+
'account.phase.exchanging': 'exchanging the code',
|
|
70
|
+
'account.phase.committing': 'saving the grant',
|
|
71
|
+
'account.phase.succeeded': 'signed in',
|
|
72
|
+
'account.phase.cancelled': 'cancelled',
|
|
73
|
+
'account.phase.expired': 'expired',
|
|
74
|
+
'account.phase.failed': 'failed',
|
|
75
|
+
'account.error.network': 'the platform could not be reached',
|
|
76
|
+
'account.error.protocol': 'the platform answered unexpectedly',
|
|
77
|
+
'account.error.expired': 'the attempt timed out',
|
|
78
|
+
'account.error.storage': 'the credential store refused the grant',
|
|
79
|
+
'account.links': 'Usage: {usage} · Top-up: {topUp}',
|
|
80
|
+
'account.hintSignedIn': '/account logout signs out on this machine.',
|
|
81
|
+
'account.hintSignedOut': '/account login opens the browser to sign in.',
|
|
82
|
+
'account.unavailable': 'This deployment composes no DeepSeek account service.',
|
|
83
|
+
'account.noCallback': 'Account login needs the loopback callback server; it is disabled (DSCODE_ACCOUNT_LOGIN=0).',
|
|
84
|
+
'account.usage': 'Usage: /account [login|cancel|logout]',
|
|
85
|
+
'account.startFailed': 'The sign-in did not start.',
|
|
86
|
+
'account.nothingToCancel': 'No sign-in is in progress.',
|
|
87
|
+
'account.starting': 'Signing in to DeepSeek through the browser…',
|
|
88
|
+
'account.browserOpened': 'The browser was opened. Finish the sign-in there; it lands back on this machine.',
|
|
89
|
+
'account.browserManual': 'Open this URL to finish the sign-in:',
|
|
56
90
|
},
|
|
57
91
|
'zh-CN': {
|
|
58
92
|
'activity.replying': '正在回复', 'activity.thinking': '正在思考', 'activity.running': '正在执行',
|
|
@@ -83,6 +117,40 @@ export const MESSAGES = {
|
|
|
83
117
|
'doctor.noRoute': '没有可用的模型路由,无法运行模型分析。',
|
|
84
118
|
'doctor.scope': '证据范围:{traces} 个近期会话、{logs} 条 warning/error;未读取对话正文、工具参数或工具输出。',
|
|
85
119
|
'doctor.failed': '模型分析未完成:{error}。',
|
|
120
|
+
'account.signedIn': 'DeepSeek 账号:本机已登录(此处不重新校验凭据是否仍被服务端接受)。',
|
|
121
|
+
'account.signedOut': 'DeepSeek 账号:未登录。DeepSeek 路由将回退到 API key。',
|
|
122
|
+
'account.profile': '身份:{identity}',
|
|
123
|
+
'account.profileFailed': '身份:暂时读取失败。',
|
|
124
|
+
'account.unknownIdentity': '无显示名',
|
|
125
|
+
'account.balance': '余额:{wallets}',
|
|
126
|
+
'account.bonus': '赠额 {wallets}',
|
|
127
|
+
'account.balanceFailed': '余额:暂时读取失败。',
|
|
128
|
+
'account.balanceEmpty': '余额:未返回任何钱包。',
|
|
129
|
+
'account.attempt': '最近一次登录:{phase}',
|
|
130
|
+
'account.attemptFailed': '最近一次登录:{phase}({reason})',
|
|
131
|
+
'account.phase.initializing': '正在开始',
|
|
132
|
+
'account.phase.waiting-browser': '等待浏览器',
|
|
133
|
+
'account.phase.exchanging': '正在兑换授权码',
|
|
134
|
+
'account.phase.committing': '正在保存凭据',
|
|
135
|
+
'account.phase.succeeded': '已登录',
|
|
136
|
+
'account.phase.cancelled': '已取消',
|
|
137
|
+
'account.phase.expired': '已过期',
|
|
138
|
+
'account.phase.failed': '失败',
|
|
139
|
+
'account.error.network': '无法连接平台',
|
|
140
|
+
'account.error.protocol': '平台返回异常',
|
|
141
|
+
'account.error.expired': '尝试超时',
|
|
142
|
+
'account.error.storage': '凭据存储拒绝写入',
|
|
143
|
+
'account.links': '用量:{usage} · 充值:{topUp}',
|
|
144
|
+
'account.hintSignedIn': '/account logout 在本机退出登录。',
|
|
145
|
+
'account.hintSignedOut': '/account login 打开浏览器登录。',
|
|
146
|
+
'account.unavailable': '当前部署没有挂载 DeepSeek 账号服务。',
|
|
147
|
+
'account.noCallback': '账号登录需要回环回调服务,它已被关闭(DSCODE_ACCOUNT_LOGIN=0)。',
|
|
148
|
+
'account.usage': '用法:/account [login|cancel|logout]',
|
|
149
|
+
'account.startFailed': '登录没有启动。',
|
|
150
|
+
'account.nothingToCancel': '当前没有正在进行的登录。',
|
|
151
|
+
'account.starting': '正在通过浏览器登录 DeepSeek……',
|
|
152
|
+
'account.browserOpened': '已打开浏览器,请在那里完成登录,完成后会回到本机。',
|
|
153
|
+
'account.browserManual': '打开这个地址完成登录:',
|
|
86
154
|
},
|
|
87
155
|
'zh-TW': {
|
|
88
156
|
'activity.replying': '正在回覆', 'activity.thinking': '正在思考', 'activity.running': '正在執行',
|
|
@@ -113,6 +181,40 @@ export const MESSAGES = {
|
|
|
113
181
|
'doctor.noRoute': '沒有可用的模型路由,無法執行模型分析。',
|
|
114
182
|
'doctor.scope': '證據範圍:{traces} 個近期工作階段、{logs} 筆 warning/error;未讀取對話內容、工具參數或工具輸出。',
|
|
115
183
|
'doctor.failed': '模型分析未完成:{error}。',
|
|
184
|
+
'account.signedIn': 'DeepSeek 帳號:本機已登入(此處不重新驗證憑證是否仍被服務端接受)。',
|
|
185
|
+
'account.signedOut': 'DeepSeek 帳號:未登入。DeepSeek 路由將回退到 API key。',
|
|
186
|
+
'account.profile': '身分:{identity}',
|
|
187
|
+
'account.profileFailed': '身分:暫時讀取失敗。',
|
|
188
|
+
'account.unknownIdentity': '無顯示名稱',
|
|
189
|
+
'account.balance': '餘額:{wallets}',
|
|
190
|
+
'account.bonus': '贈額 {wallets}',
|
|
191
|
+
'account.balanceFailed': '餘額:暫時讀取失敗。',
|
|
192
|
+
'account.balanceEmpty': '餘額:未回傳任何錢包。',
|
|
193
|
+
'account.attempt': '最近一次登入:{phase}',
|
|
194
|
+
'account.attemptFailed': '最近一次登入:{phase}({reason})',
|
|
195
|
+
'account.phase.initializing': '正在開始',
|
|
196
|
+
'account.phase.waiting-browser': '等待瀏覽器',
|
|
197
|
+
'account.phase.exchanging': '正在兌換授權碼',
|
|
198
|
+
'account.phase.committing': '正在儲存憑證',
|
|
199
|
+
'account.phase.succeeded': '已登入',
|
|
200
|
+
'account.phase.cancelled': '已取消',
|
|
201
|
+
'account.phase.expired': '已過期',
|
|
202
|
+
'account.phase.failed': '失敗',
|
|
203
|
+
'account.error.network': '無法連線平台',
|
|
204
|
+
'account.error.protocol': '平台回應異常',
|
|
205
|
+
'account.error.expired': '嘗試逾時',
|
|
206
|
+
'account.error.storage': '憑證儲存拒絕寫入',
|
|
207
|
+
'account.links': '用量:{usage} · 充值:{topUp}',
|
|
208
|
+
'account.hintSignedIn': '/account logout 在本機登出。',
|
|
209
|
+
'account.hintSignedOut': '/account login 開啟瀏覽器登入。',
|
|
210
|
+
'account.unavailable': '目前部署沒有掛載 DeepSeek 帳號服務。',
|
|
211
|
+
'account.noCallback': '帳號登入需要回環回呼服務,它已被關閉(DSCODE_ACCOUNT_LOGIN=0)。',
|
|
212
|
+
'account.usage': '用法:/account [login|cancel|logout]',
|
|
213
|
+
'account.startFailed': '登入沒有啟動。',
|
|
214
|
+
'account.nothingToCancel': '目前沒有進行中的登入。',
|
|
215
|
+
'account.starting': '正在透過瀏覽器登入 DeepSeek……',
|
|
216
|
+
'account.browserOpened': '已開啟瀏覽器,請在那裡完成登入,完成後會回到本機。',
|
|
217
|
+
'account.browserManual': '開啟這個網址完成登入:',
|
|
116
218
|
},
|
|
117
219
|
ja: {
|
|
118
220
|
'activity.replying': '応答中', 'activity.thinking': '思考中', 'activity.running': '実行中',
|
|
@@ -143,6 +245,40 @@ export const MESSAGES = {
|
|
|
143
245
|
'doctor.noRoute': '利用できるモデルルートがないため、モデル分析を実行できません。',
|
|
144
246
|
'doctor.scope': '根拠の範囲:直近のセッション {traces} 件、warning/error {logs} 件。会話本文、ツール引数、ツール出力は読んでいません。',
|
|
145
247
|
'doctor.failed': 'モデル分析が完了しませんでした:{error}。',
|
|
248
|
+
'account.signedIn': 'DeepSeek アカウント:このマシンでサインイン済み(保存済み認可の有効性はここでは再確認しません)。',
|
|
249
|
+
'account.signedOut': 'DeepSeek アカウント:サインアウト。DeepSeek ルートは API キーにフォールバックします。',
|
|
250
|
+
'account.profile': 'アカウント:{identity}',
|
|
251
|
+
'account.profileFailed': 'アカウント:今は取得できませんでした。',
|
|
252
|
+
'account.unknownIdentity': '表示名なし',
|
|
253
|
+
'account.balance': '残高:{wallets}',
|
|
254
|
+
'account.bonus': 'ボーナス {wallets}',
|
|
255
|
+
'account.balanceFailed': '残高:今は取得できませんでした。',
|
|
256
|
+
'account.balanceEmpty': '残高:ウォレットの報告がありません。',
|
|
257
|
+
'account.attempt': '直近のサインイン:{phase}',
|
|
258
|
+
'account.attemptFailed': '直近のサインイン:{phase}({reason})',
|
|
259
|
+
'account.phase.initializing': '開始中',
|
|
260
|
+
'account.phase.waiting-browser': 'ブラウザ待ち',
|
|
261
|
+
'account.phase.exchanging': 'コード交換中',
|
|
262
|
+
'account.phase.committing': '認可を保存中',
|
|
263
|
+
'account.phase.succeeded': 'サインイン済み',
|
|
264
|
+
'account.phase.cancelled': 'キャンセル',
|
|
265
|
+
'account.phase.expired': '期限切れ',
|
|
266
|
+
'account.phase.failed': '失敗',
|
|
267
|
+
'account.error.network': 'プラットフォームに接続できません',
|
|
268
|
+
'account.error.protocol': 'プラットフォームの応答が不正です',
|
|
269
|
+
'account.error.expired': 'タイムアウトしました',
|
|
270
|
+
'account.error.storage': '認証情報ストアが保存を拒否しました',
|
|
271
|
+
'account.links': '使用量:{usage} · チャージ:{topUp}',
|
|
272
|
+
'account.hintSignedIn': '/account logout でこのマシンからサインアウトします。',
|
|
273
|
+
'account.hintSignedOut': '/account login でブラウザを開いてサインインします。',
|
|
274
|
+
'account.unavailable': 'この構成には DeepSeek アカウントサービスがありません。',
|
|
275
|
+
'account.noCallback': 'アカウントサインインにはループバックのコールバックサーバーが必要ですが、無効です(DSCODE_ACCOUNT_LOGIN=0)。',
|
|
276
|
+
'account.usage': '使い方:/account [login|cancel|logout]',
|
|
277
|
+
'account.startFailed': 'サインインを開始できませんでした。',
|
|
278
|
+
'account.nothingToCancel': '進行中のサインインはありません。',
|
|
279
|
+
'account.starting': 'ブラウザで DeepSeek にサインインしています……',
|
|
280
|
+
'account.browserOpened': 'ブラウザを開きました。そこでサインインを完了すると、このマシンに戻ります。',
|
|
281
|
+
'account.browserManual': 'この URL を開いてサインインを完了してください:',
|
|
146
282
|
},
|
|
147
283
|
ko: {
|
|
148
284
|
'activity.replying': '응답 중', 'activity.thinking': '생각 중', 'activity.running': '실행 중',
|
|
@@ -173,6 +309,40 @@ export const MESSAGES = {
|
|
|
173
309
|
'doctor.noRoute': '사용 가능한 모델 경로가 없어 모델 분석을 실행할 수 없습니다.',
|
|
174
310
|
'doctor.scope': '근거 범위: 최근 세션 {traces}개, warning/error {logs}건. 대화 본문, 도구 인수, 도구 출력은 읽지 않았습니다.',
|
|
175
311
|
'doctor.failed': '모델 분석이 완료되지 않았습니다: {error}.',
|
|
312
|
+
'account.signedIn': 'DeepSeek 계정: 이 기기에서 로그인됨(저장된 인증이 여전히 유효한지는 여기서 다시 확인하지 않습니다).',
|
|
313
|
+
'account.signedOut': 'DeepSeek 계정: 로그아웃 상태. DeepSeek 경로는 API 키로 대체됩니다.',
|
|
314
|
+
'account.profile': '계정: {identity}',
|
|
315
|
+
'account.profileFailed': '계정: 지금은 읽을 수 없습니다.',
|
|
316
|
+
'account.unknownIdentity': '표시 이름 없음',
|
|
317
|
+
'account.balance': '잔액: {wallets}',
|
|
318
|
+
'account.bonus': '보너스 {wallets}',
|
|
319
|
+
'account.balanceFailed': '잔액: 지금은 읽을 수 없습니다.',
|
|
320
|
+
'account.balanceEmpty': '잔액: 보고된 지갑이 없습니다.',
|
|
321
|
+
'account.attempt': '최근 로그인: {phase}',
|
|
322
|
+
'account.attemptFailed': '최근 로그인: {phase} ({reason})',
|
|
323
|
+
'account.phase.initializing': '시작 중',
|
|
324
|
+
'account.phase.waiting-browser': '브라우저 대기 중',
|
|
325
|
+
'account.phase.exchanging': '코드 교환 중',
|
|
326
|
+
'account.phase.committing': '인증 저장 중',
|
|
327
|
+
'account.phase.succeeded': '로그인됨',
|
|
328
|
+
'account.phase.cancelled': '취소됨',
|
|
329
|
+
'account.phase.expired': '만료됨',
|
|
330
|
+
'account.phase.failed': '실패',
|
|
331
|
+
'account.error.network': '플랫폼에 연결할 수 없습니다',
|
|
332
|
+
'account.error.protocol': '플랫폼 응답이 올바르지 않습니다',
|
|
333
|
+
'account.error.expired': '시간이 초과되었습니다',
|
|
334
|
+
'account.error.storage': '자격 증명 저장소가 저장을 거부했습니다',
|
|
335
|
+
'account.links': '사용량: {usage} · 충전: {topUp}',
|
|
336
|
+
'account.hintSignedIn': '/account logout 으로 이 기기에서 로그아웃합니다.',
|
|
337
|
+
'account.hintSignedOut': '/account login 으로 브라우저를 열어 로그인합니다.',
|
|
338
|
+
'account.unavailable': '이 구성에는 DeepSeek 계정 서비스가 없습니다.',
|
|
339
|
+
'account.noCallback': '계정 로그인에는 루프백 콜백 서버가 필요하지만 비활성화되어 있습니다(DSCODE_ACCOUNT_LOGIN=0).',
|
|
340
|
+
'account.usage': '사용법: /account [login|cancel|logout]',
|
|
341
|
+
'account.startFailed': '로그인이 시작되지 않았습니다.',
|
|
342
|
+
'account.nothingToCancel': '진행 중인 로그인이 없습니다.',
|
|
343
|
+
'account.starting': '브라우저로 DeepSeek에 로그인하고 있습니다……',
|
|
344
|
+
'account.browserOpened': '브라우저를 열었습니다. 거기서 로그인을 완료하면 이 기기로 돌아옵니다.',
|
|
345
|
+
'account.browserManual': '이 URL을 열어 로그인을 완료하세요:',
|
|
176
346
|
},
|
|
177
347
|
es: {
|
|
178
348
|
'activity.replying': 'Respondiendo', 'activity.thinking': 'Pensando', 'activity.running': 'Ejecutando',
|
|
@@ -203,6 +373,40 @@ export const MESSAGES = {
|
|
|
203
373
|
'doctor.noRoute': 'No hay una ruta de modelo disponible, así que el análisis con modelo no se pudo ejecutar.',
|
|
204
374
|
'doctor.scope': 'Alcance de la evidencia: {traces} sesiones recientes, {logs} warnings/errores; no se leyó el texto de la conversación, los argumentos ni la salida de las herramientas.',
|
|
205
375
|
'doctor.failed': 'El análisis con modelo no terminó: {error}.',
|
|
376
|
+
'account.signedIn': 'Cuenta DeepSeek: sesión iniciada en esta máquina (aquí no se vuelve a comprobar la concesión guardada).',
|
|
377
|
+
'account.signedOut': 'Cuenta DeepSeek: sin sesión. La ruta DeepSeek recurre a una clave de API.',
|
|
378
|
+
'account.profile': 'Identidad: {identity}',
|
|
379
|
+
'account.profileFailed': 'Identidad: no se pudo leer ahora.',
|
|
380
|
+
'account.unknownIdentity': 'sin nombre visible',
|
|
381
|
+
'account.balance': 'Saldo: {wallets}',
|
|
382
|
+
'account.bonus': 'bonificación {wallets}',
|
|
383
|
+
'account.balanceFailed': 'Saldo: no se pudo leer ahora.',
|
|
384
|
+
'account.balanceEmpty': 'Saldo: ninguna cartera informada.',
|
|
385
|
+
'account.attempt': 'Último inicio de sesión: {phase}',
|
|
386
|
+
'account.attemptFailed': 'Último inicio de sesión: {phase} ({reason})',
|
|
387
|
+
'account.phase.initializing': 'iniciando',
|
|
388
|
+
'account.phase.waiting-browser': 'esperando el navegador',
|
|
389
|
+
'account.phase.exchanging': 'canjeando el código',
|
|
390
|
+
'account.phase.committing': 'guardando la concesión',
|
|
391
|
+
'account.phase.succeeded': 'sesión iniciada',
|
|
392
|
+
'account.phase.cancelled': 'cancelado',
|
|
393
|
+
'account.phase.expired': 'caducado',
|
|
394
|
+
'account.phase.failed': 'fallido',
|
|
395
|
+
'account.error.network': 'no se pudo contactar con la plataforma',
|
|
396
|
+
'account.error.protocol': 'la plataforma respondió de forma inesperada',
|
|
397
|
+
'account.error.expired': 'el intento caducó',
|
|
398
|
+
'account.error.storage': 'el almacén de credenciales rechazó la concesión',
|
|
399
|
+
'account.links': 'Uso: {usage} · Recarga: {topUp}',
|
|
400
|
+
'account.hintSignedIn': '/account logout cierra la sesión en esta máquina.',
|
|
401
|
+
'account.hintSignedOut': '/account login abre el navegador para iniciar sesión.',
|
|
402
|
+
'account.unavailable': 'Esta composición no incluye el servicio de cuenta DeepSeek.',
|
|
403
|
+
'account.noCallback': 'El inicio de sesión necesita el servidor de retorno en loopback; está desactivado (DSCODE_ACCOUNT_LOGIN=0).',
|
|
404
|
+
'account.usage': 'Uso: /account [login|cancel|logout]',
|
|
405
|
+
'account.startFailed': 'El inicio de sesión no comenzó.',
|
|
406
|
+
'account.nothingToCancel': 'No hay ningún inicio de sesión en curso.',
|
|
407
|
+
'account.starting': 'Iniciando sesión en DeepSeek a través del navegador…',
|
|
408
|
+
'account.browserOpened': 'Se abrió el navegador. Termina ahí el inicio de sesión y volverá a esta máquina.',
|
|
409
|
+
'account.browserManual': 'Abre esta URL para terminar el inicio de sesión:',
|
|
206
410
|
},
|
|
207
411
|
};
|
|
208
412
|
|