@toddzheng024/dscode-bundle 0.7.27 → 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/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +10 -10
- package/bootstrap.mjs +1 -1
- package/cordis.patch.yml +433 -46
- package/package.json +280 -245
- package/plugins/account/index.mjs +178 -0
- package/plugins/account/state.mjs +123 -0
- package/plugins/auto-review/index.mjs +8 -5
- package/plugins/code-review/index.mjs +1 -1
- package/plugins/computer-use/index.mjs +37 -0
- package/plugins/cordis-config/plain.mjs +31 -0
- package/plugins/dscode/index.mjs +1 -1
- package/plugins/exec/cli.mjs +1 -1
- package/plugins/grok/index.mjs +28 -24
- package/plugins/i18n/messages.mjs +204 -0
- package/plugins/memory/index.mjs +1 -1
- package/plugins/message-source/kind.mjs +20 -0
- package/plugins/openrouter/adapter.mjs +29 -13
- package/plugins/openrouter/index.mjs +31 -28
- package/plugins/session-bridge/communication.mjs +6 -3
- package/plugins/session-bridge/server.mjs +3 -2
- package/plugins/session-bridge/tasks.mjs +3 -1
- package/plugins/session-cards/index.mjs +1 -1
- package/plugins/time-marks/index.mjs +3 -2
- package/plugins/time-marks/marks.mjs +6 -5
- package/plugins/triggers/config.mjs +1 -1
- package/plugins/triggers/tools.mjs +1 -1
- package/plugins/tui-tools/doctor.mjs +1 -1
- package/plugins/tui-tools/index.mjs +16 -4
- package/plugins/ultra/policy.mjs +16 -0
- package/vendor/bash/index.js +455 -186
- package/vendor/bash/types/background.d.ts +38 -7
- package/vendor/bash/types/index.d.ts +20 -4
- package/vendor/bash/types/render.d.ts +28 -9
- package/vendor/command-goal/index.js +3 -1
- package/vendor/deepseek/index.js +1378 -1246
- package/vendor/deepseek/types/adapter.d.ts +16 -155
- package/vendor/deepseek/types/config.d.ts +127 -0
- package/vendor/deepseek/types/defaults.d.ts +24 -0
- package/vendor/deepseek/types/file-store.d.ts +5 -3
- package/vendor/deepseek/types/files-api.d.ts +12 -9
- package/vendor/deepseek/types/image-tokens.d.ts +24 -9
- package/vendor/deepseek/types/images.d.ts +35 -0
- package/vendor/deepseek/types/index.d.ts +10 -94
- package/vendor/deepseek/types/messages-api.d.ts +10 -0
- package/vendor/deepseek/types/model-info.d.ts +16 -0
- package/vendor/deepseek/types/models.d.ts +4 -0
- package/vendor/deepseek/types/replay.d.ts +27 -0
- package/vendor/deepseek/types/request-extensions.d.ts +16 -0
- package/vendor/deepseek/types/request-files.d.ts +49 -0
- package/vendor/deepseek/types/request-pricing.d.ts +32 -20
- package/vendor/deepseek/types/serialize.d.ts +17 -82
- package/vendor/deepseek/types/sse.d.ts +6 -22
- package/vendor/deepseek/types/translate.d.ts +13 -36
- package/vendor/deepseek/types/transport.d.ts +15 -0
- package/vendor/deepseek/types/types.d.ts +106 -167
- package/vendor/deepseek/types/wire-types.d.ts +62 -0
- package/vendor/persistent/index.js +9 -3
- package/vendor/subagent/index.js +11 -8
- package/vendor/subagent/model-selection-settings.js +12 -38
- package/vendor/subagent/types/index.d.ts +4 -3
- package/vendor/subagent/types/model-selection-settings.d.ts +12 -10
- package/vendor/subagent-core/index.js +401 -264
- package/vendor/subagent-core/typert.host.js +103 -113
- package/vendor/subagent-core/typert.remote-client.d.ts +1 -3
- package/vendor/subagent-core/typert.remote-client.js +23 -69
- package/vendor/subagent-core/types/archive-admission.d.ts +16 -0
- package/vendor/subagent-core/types/archive-admission.js +151 -0
- package/vendor/subagent-core/types/assistant-output.d.ts +2 -2
- package/vendor/subagent-core/types/catalog.d.ts +17 -10
- package/vendor/subagent-core/types/catalog.js +12 -6
- package/vendor/subagent-core/types/child-agent.d.ts +8 -5
- package/vendor/subagent-core/types/child-agent.js +11 -5
- package/vendor/subagent-core/types/continuation-activation.d.ts +17 -1
- package/vendor/subagent-core/types/continuation-activation.js +41 -4
- package/vendor/subagent-core/types/continuation-messages.d.ts +1 -1
- package/vendor/subagent-core/types/continuation-messages.js +7 -3
- package/vendor/subagent-core/types/continuation.d.ts +1 -1
- package/vendor/subagent-core/types/continuation.js +2 -2
- package/vendor/subagent-core/types/control-types.d.ts +29 -36
- package/vendor/subagent-core/types/control-types.js +2 -3
- package/vendor/subagent-core/types/control.d.ts +2 -28
- package/vendor/subagent-core/types/control.js +3 -40
- package/vendor/subagent-core/types/index.d.ts +45 -41
- package/vendor/subagent-core/types/index.js +46 -82
- package/vendor/subagent-core/types/internal.d.ts +2 -2
- package/vendor/subagent-core/types/internal.js +2 -2
- package/vendor/subagent-core/types/lifecycle.d.ts +1 -1
- package/vendor/subagent-core/types/lifecycle.js +5 -3
- package/vendor/subagent-core/types/list-children.d.ts +15 -32
- package/vendor/subagent-core/types/list-children.js +45 -49
- package/vendor/subagent-core/types/out-of-process.d.ts +1 -1
- package/vendor/subagent-core/types/projection-types.d.ts +9 -1
- package/vendor/subagent-core/types/projection.d.ts +5 -0
- package/vendor/subagent-core/types/projection.js +10 -4
- package/vendor/subagent-core/types/run-settlement.js +1 -1
- package/vendor/subagent-core/types/types.d.ts +2 -2
- package/vendor/terminal/index.js +94 -30
- package/vendor/tui/lib/index.mjs +2 -1
- package/vendor/tui/lib/kernel-panels.mjs +1 -1
- package/vendor/tui/lib/render/projection.mjs +50 -36
- package/presets/dscode/agent.cordis.yml +0 -321
- package/presets/dscode/preset.yml +0 -3
|
@@ -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
|
+
}
|
|
@@ -24,6 +24,9 @@ export function apply(ctx, config) {
|
|
|
24
24
|
const calls = new WeakMap();
|
|
25
25
|
const budgets = new WeakMap();
|
|
26
26
|
const mode = agent => ctx.permissionPresets.current(agent.session);
|
|
27
|
+
// The preset this plugin reviews for. DSH 0.1.7 reserved `auto` for its own
|
|
28
|
+
// integration, so the reviewed preset carries DSCODE's own name.
|
|
29
|
+
const REVIEWED = 'auto-review';
|
|
27
30
|
const stateFor = agent => {
|
|
28
31
|
const events = agent.session.snapshotEvents();
|
|
29
32
|
const turn = events.findLast(e => e.type === 'turn/start')?.seq;
|
|
@@ -54,7 +57,7 @@ export function apply(ctx, config) {
|
|
|
54
57
|
});
|
|
55
58
|
|
|
56
59
|
const announce = (agent, text) => agent.inject(createUserMessage({
|
|
57
|
-
content: [{ type: 'text', text }], source: { kind:
|
|
60
|
+
content: [{ type: 'text', text }], source: { kind: name },
|
|
58
61
|
}));
|
|
59
62
|
const record = (req, data) => {
|
|
60
63
|
audit.append(req.agent.session.id, { sessionSeq: req.agent.session.seq, callId: req.callId ?? null, toolName: req.toolName, ...data });
|
|
@@ -64,7 +67,7 @@ export function apply(ctx, config) {
|
|
|
64
67
|
async function review(req, next, state) {
|
|
65
68
|
if (req.signal?.aborted) return 'cancelled';
|
|
66
69
|
// A user can switch to ask while a prior review waits in the queue.
|
|
67
|
-
if (mode(req.agent) !==
|
|
70
|
+
if (mode(req.agent) !== REVIEWED) return ctx.approval?.effectivePolicy?.(req.agent.session) === 'never' ? 'rejected' : next();
|
|
68
71
|
const exec = calls.get(req.agent)?.get(req.callId);
|
|
69
72
|
const fallback = async (reason, details = {}) => {
|
|
70
73
|
record(req, { decision: 'human', reason, ...details });
|
|
@@ -82,7 +85,7 @@ export function apply(ctx, config) {
|
|
|
82
85
|
record(req, { decision: 'cancelled', reason: 'Caller cancelled review.', ...details });
|
|
83
86
|
return 'cancelled';
|
|
84
87
|
}
|
|
85
|
-
if (mode(req.agent) !==
|
|
88
|
+
if (mode(req.agent) !== REVIEWED) return fallback('Permission mode changed while review was pending.', details);
|
|
86
89
|
if (decision.decision === 'human') return fallback(decision.reason, details);
|
|
87
90
|
// Bind approval to the still-pending immutable invocation; no grant cache.
|
|
88
91
|
if (calls.get(req.agent)?.get(req.callId) !== exec || fingerprint(action) !== actionHash) return fallback('Pending action changed during review.', details);
|
|
@@ -174,7 +177,7 @@ export function apply(ctx, config) {
|
|
|
174
177
|
...(effort === undefined ? {} : { reasoningEffort: effort }),
|
|
175
178
|
messages: [createSystemMessage(REVIEW_POLICY, name), createUserMessage({
|
|
176
179
|
content: [{ type: 'text', text: JSON.stringify({ action, context }) }],
|
|
177
|
-
source: { kind:
|
|
180
|
+
source: { kind: name },
|
|
178
181
|
})],
|
|
179
182
|
maxTokens: config.maxOutputTokens, signal,
|
|
180
183
|
})) {
|
|
@@ -231,7 +234,7 @@ export function apply(ctx, config) {
|
|
|
231
234
|
}
|
|
232
235
|
announce(req.agent, `Escalation budget reached (${grantBudget} per turn); asking the user.`);
|
|
233
236
|
}
|
|
234
|
-
if (mode(req.agent) !==
|
|
237
|
+
if (mode(req.agent) !== REVIEWED) return next();
|
|
235
238
|
const state = stateFor(req.agent);
|
|
236
239
|
// Serialize per agent so simultaneous calls cannot race the denial budget.
|
|
237
240
|
const pending = state.tail.then(() => review(req, next, state));
|
|
@@ -141,7 +141,7 @@ export async function independentReview(ctx, agent, options = {}, signal, collec
|
|
|
141
141
|
for await (const chunk of ctx.llm.stream({
|
|
142
142
|
provider: route.provider, model: route.model, reasoningEffort, purpose: 'review',
|
|
143
143
|
maxTokens: tokens,
|
|
144
|
-
system: POLICY, messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(request) }], source: { kind:
|
|
144
|
+
system: POLICY, messages: [createUserMessage({ content: [{ type: 'text', text: JSON.stringify(request) }], source: { kind: name } })], signal: deadline,
|
|
145
145
|
})) {
|
|
146
146
|
deadline.throwIfAborted();
|
|
147
147
|
assembler.push(chunk);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer Use, mounted through DSCODE so a retired settings API still resolves.
|
|
3
|
+
*
|
|
4
|
+
* `@anionex/dsh-computer-use` reads its configuration with `ctx.settings.register`, an API
|
|
5
|
+
* DSH removed in 0.1.7: Settings now projects forms from the profile entry itself, and the
|
|
6
|
+
* missing method made the whole Computer Use bundle fail to activate. The package is
|
|
7
|
+
* pinned at 0.3.2 with no newer release, so this row restores the method — as a read-only
|
|
8
|
+
* view over the composition config, which is where DSCODE configures Computer Use anyway —
|
|
9
|
+
* and then mounts the upstream bundle as its child, which is what makes the order
|
|
10
|
+
* dependable: the shim is installed before the provider's constructor runs.
|
|
11
|
+
*
|
|
12
|
+
* Retire this wrapper when the package reads volatile configuration, and mount
|
|
13
|
+
* `@anionex/dsh-computer-use` directly again.
|
|
14
|
+
*
|
|
15
|
+
* @module dscode-computer-use
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import ComputerUseBundle from '@anionex/dsh-computer-use';
|
|
19
|
+
|
|
20
|
+
export const name = 'dscode-computer-use';
|
|
21
|
+
export const inject = ['settings'];
|
|
22
|
+
|
|
23
|
+
/** Section handle the 0.3.x provider expects: one read of the composition config, no live writes. */
|
|
24
|
+
function staticSection(base) {
|
|
25
|
+
return { get: () => base, watch: () => () => {} };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function apply(ctx, config = {}) {
|
|
29
|
+
const settings = ctx.settings;
|
|
30
|
+
// Only ever adds what upstream no longer defines; a Settings service that carries its
|
|
31
|
+
// own `register` again is left untouched, and the shim is dropped with this row.
|
|
32
|
+
if (typeof settings.register !== 'function') {
|
|
33
|
+
settings.register = (_namespace, _schema, options = {}) => staticSection(options.base ?? {});
|
|
34
|
+
ctx.effect(() => () => { delete settings.register; });
|
|
35
|
+
}
|
|
36
|
+
ctx.plugin(ComputerUseBundle, config);
|
|
37
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One plain snapshot of a Cordis plugin configuration.
|
|
3
|
+
*
|
|
4
|
+
* Two shapes make a foreign entry's `config` unusable as a plain object. DSH 0.1.7 hands
|
|
5
|
+
* a `.volatile()` field to its own plugin as a reference rather than a value, and the
|
|
6
|
+
* Loader keeps a YAML `!!js` scalar as an unevaluated `{ __jsExpr }` node until the entry
|
|
7
|
+
* activates — which a reader inspecting another entry's row (the skills conflict report
|
|
8
|
+
* walks every `skill-filesystem` entry, and a preset's rows carry such expressions) sees
|
|
9
|
+
* instead of the value. References are read; an expression cannot be evaluated outside the
|
|
10
|
+
* Loader's own scope, so it reads as absent and the caller applies its own fallback.
|
|
11
|
+
*
|
|
12
|
+
* @param config - a configuration object whose fields may be references or expressions.
|
|
13
|
+
* @returns a shallow copy carrying only plainly readable values.
|
|
14
|
+
*/
|
|
15
|
+
export function plainConfig(config) {
|
|
16
|
+
if (config === null || typeof config !== 'object') return {};
|
|
17
|
+
const entries = Object.entries(config)
|
|
18
|
+
.map(([key, value]) => [key, isReference(value) ? value.get() : value])
|
|
19
|
+
.filter(([, value]) => !isExpression(value));
|
|
20
|
+
return Object.fromEntries(entries);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether a configuration field is a Cordis value reference rather than a plain value. */
|
|
24
|
+
function isReference(value) {
|
|
25
|
+
return value !== null && typeof value === 'object' && typeof value.get === 'function';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Whether a configuration field is still an unevaluated Loader expression node. */
|
|
29
|
+
function isExpression(value) {
|
|
30
|
+
return value !== null && typeof value === 'object' && '__jsExpr' in value;
|
|
31
|
+
}
|
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 });
|
package/plugins/exec/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ Options:
|
|
|
9
9
|
--cwd DIR workspace for the agent (default: current directory)
|
|
10
10
|
--model PROVIDER/ID model route (default: the saved default model)
|
|
11
11
|
--effort LEVEL reasoning effort the model offers: off, minimal, low, medium, high, xhigh, max or ultra
|
|
12
|
-
--permission PRESET permission preset: auto, ask, workspace-write, read-only, danger-full-access
|
|
12
|
+
--permission PRESET permission preset: auto-review, ask, workspace-write, read-only, danger-full-access
|
|
13
13
|
--approve-all answer every approval request with allow (no human is present)
|
|
14
14
|
--resume SESSION_ID continue an existing session instead of starting a new one
|
|
15
15
|
--json emit JSON lines (session, text, tool, result) instead of plain text
|
package/plugins/grok/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { RetryPolicySchema, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
4
|
+
import { deepEqualJson } from "@deepseek-ai/dsh-util-values";
|
|
5
|
+
import { plainConfig } from "../cordis-config/plain.mjs";
|
|
4
6
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
5
7
|
import { GrokAdapter, PROVIDER } from "./adapter.mjs";
|
|
6
8
|
import { ensureGrokModels } from "./models.mjs";
|
|
@@ -19,14 +21,18 @@ const DEFAULT_BASE_URL = "https://api.x.ai/v1";
|
|
|
19
21
|
/** The client version the CLI proxy is asked with; xAI refuses requests without one. */
|
|
20
22
|
const DEFAULT_CLIENT_VERSION = "1.0.34";
|
|
21
23
|
|
|
24
|
+
// DSH 0.1.7 replaced settings sections with volatile configuration: each `.volatile()`
|
|
25
|
+
// field arrives as a reference the Settings form and a profile edit both write, so the
|
|
26
|
+
// reads below always see the current value.
|
|
22
27
|
export const Config = z.object({
|
|
23
|
-
apiKeyEnv: z.string().role("credential-ref").default(GROK_TOKEN_REF),
|
|
24
|
-
baseURL: z.string().default(DEFAULT_BASE_URL),
|
|
25
|
-
clientVersion: z.string().default(DEFAULT_CLIENT_VERSION),
|
|
26
|
-
streamIdleTimeoutMs: z.number().min(1).default(300000),
|
|
27
|
-
retryPolicy: RetryPolicySchema,
|
|
28
|
+
apiKeyEnv: z.string().role("credential-ref").default(GROK_TOKEN_REF).volatile(),
|
|
29
|
+
baseURL: z.string().default(DEFAULT_BASE_URL).volatile(),
|
|
30
|
+
clientVersion: z.string().default(DEFAULT_CLIENT_VERSION).volatile(),
|
|
31
|
+
streamIdleTimeoutMs: z.number().min(1).default(300000).volatile(),
|
|
32
|
+
retryPolicy: RetryPolicySchema.volatile(),
|
|
28
33
|
});
|
|
29
34
|
|
|
35
|
+
|
|
30
36
|
/** Validated connection facts from one config snapshot. */
|
|
31
37
|
export function resolveOptions(config = {}) {
|
|
32
38
|
const streamIdleTimeoutMs = config.streamIdleTimeoutMs ?? 300000;
|
|
@@ -50,18 +56,18 @@ export function loginHint(state) {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
export function apply(ctx, config = {}) {
|
|
53
|
-
|
|
59
|
+
// The TUI renders this route's own page, so Settings must not generate a form for it.
|
|
60
|
+
ctx.inject(["settings"], settingsCtx => {
|
|
61
|
+
settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber));
|
|
62
|
+
});
|
|
63
|
+
let lastGood;
|
|
54
64
|
const options = () => {
|
|
55
|
-
const raw = current();
|
|
56
|
-
if (raw === lastRaw && lastGood !== undefined) return lastGood;
|
|
57
65
|
try {
|
|
58
|
-
lastGood = resolveOptions(
|
|
59
|
-
lastRaw = raw;
|
|
66
|
+
lastGood = resolveOptions(plainConfig(config));
|
|
60
67
|
return lastGood;
|
|
61
68
|
} catch (error) {
|
|
62
69
|
if (lastGood === undefined) throw error;
|
|
63
|
-
|
|
64
|
-
ctx.logger.error(name + ": keeping the last good configuration after an invalid settings section");
|
|
70
|
+
ctx.logger.error(name + ": keeping the last good configuration after an invalid edit");
|
|
65
71
|
return lastGood;
|
|
66
72
|
}
|
|
67
73
|
};
|
|
@@ -89,19 +95,17 @@ export function apply(ctx, config = {}) {
|
|
|
89
95
|
await ensureGrokModels({ home, token, version: options().clientVersion });
|
|
90
96
|
};
|
|
91
97
|
const adapter = new GrokAdapter({ options, ensureModels, resolveToken });
|
|
92
|
-
ctx.llm.registerConfigurableProviders([{ provider: PROVIDER, displayName: "Grok", settingsNs: NS, settingsPath: [] }]);
|
|
98
|
+
ctx.llm.registerConfigurableProviders([{ provider: PROVIDER, displayName: "Grok", settingsNs: ctx.fiber.entry?.options.id ?? NS, settingsPath: [] }]);
|
|
93
99
|
const registration = ctx.llm.registerAdapter([PROVIDER], adapter);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
},
|
|
104
|
-
});
|
|
100
|
+
// The retry policy is captured at registration, and a reference carries no change
|
|
101
|
+
// callback: re-register the route when an edit lands on a different policy.
|
|
102
|
+
let registeredPolicy = options().retryPolicy;
|
|
103
|
+
ctx.on("loader/volatile-update", () => {
|
|
104
|
+
let policy;
|
|
105
|
+
try { policy = options().retryPolicy; } catch (error) { ctx.logger.warn(error); return; }
|
|
106
|
+
if (deepEqualJson(policy, registeredPolicy)) return;
|
|
107
|
+
registration.replace([PROVIDER]);
|
|
108
|
+
registeredPolicy = policy;
|
|
105
109
|
});
|
|
106
110
|
// Warm the catalog and the weekly window for a user who already ran `grok login`, then keep
|
|
107
111
|
// the window fresh at the interval the official CLI itself watches (subscription_watch_interval_secs).
|