@deepseek-harness-tui/dsh-tui 0.11.2 → 0.12.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 (168) hide show
  1. package/README.md +21 -5
  2. package/bin/dsh-tui.js +30 -3
  3. package/cordis.patch.yml +24 -5
  4. package/guide/dsh-tui-guide/SKILL.md +37 -0
  5. package/guide/dsh-tui-guide/configuration.en.md +410 -0
  6. package/guide/dsh-tui-guide/configuration.md +349 -0
  7. package/guide/dsh-tui-guide/getting-started.en.md +398 -0
  8. package/guide/dsh-tui-guide/getting-started.md +397 -0
  9. package/guide/dsh-tui-guide/interaction.en.md +674 -0
  10. package/guide/dsh-tui-guide/interaction.md +747 -0
  11. package/guide/dsh-tui-guide/migrate.en.md +166 -0
  12. package/guide/dsh-tui-guide/migrate.md +132 -0
  13. package/guide/dsh-tui-guide/plugins.en.md +68 -0
  14. package/guide/dsh-tui-guide/plugins.md +56 -0
  15. package/guide/dsh-tui-guide/themes.en.md +192 -0
  16. package/guide/dsh-tui-guide/themes.md +185 -0
  17. package/guide/dsh-tui-guide/user-guide.en.md +608 -0
  18. package/guide/dsh-tui-guide/user-guide.md +586 -0
  19. package/guide/dsh-tui-guide/vscode.en.md +301 -0
  20. package/guide/dsh-tui-guide/vscode.md +272 -0
  21. package/lib/settings.json +5 -5
  22. package/lib/types/adapter/channel/ui-policy.d.ts +3 -2
  23. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  24. package/lib/types/adapter/channel/ui-policy.js +4 -0
  25. package/lib/types/adapter/ports/channel-settings.d.ts +2 -1
  26. package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
  27. package/lib/types/adapter/ports/channel-ui.d.ts +29 -2
  28. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  29. package/lib/types/adapter/ports/channel-view.d.ts +43 -9
  30. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  31. package/lib/types/components/Chat/JobCard.d.ts +9 -0
  32. package/lib/types/components/Chat/JobCard.d.ts.map +1 -1
  33. package/lib/types/components/Chat/JobCard.js +42 -15
  34. package/lib/types/components/Chat/SubagentMessage.js +6 -6
  35. package/lib/types/components/JobsPanel.d.ts.map +1 -1
  36. package/lib/types/components/JobsPanel.js +80 -33
  37. package/lib/types/components/MessageList.js +3 -3
  38. package/lib/types/components/PresetPicker.d.ts +4 -0
  39. package/lib/types/components/PresetPicker.d.ts.map +1 -1
  40. package/lib/types/components/PresetPicker.js +5 -1
  41. package/lib/types/components/PromptInput.d.ts.map +1 -1
  42. package/lib/types/components/PromptInput.js +12 -1
  43. package/lib/types/components/StarPrompt.d.ts +6 -0
  44. package/lib/types/components/StarPrompt.d.ts.map +1 -1
  45. package/lib/types/components/StarPrompt.js +45 -0
  46. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  47. package/lib/types/components/SubagentCard.js +8 -8
  48. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  49. package/lib/types/components/SubagentDetailScene.js +108 -10
  50. package/lib/types/components/WhaleGirl.d.ts +6 -0
  51. package/lib/types/components/WhaleGirl.d.ts.map +1 -1
  52. package/lib/types/components/WhaleGirl.js +18 -0
  53. package/lib/types/components/messages/AssistantThinkingMessage.js +4 -4
  54. package/lib/types/components/trajectory/MiniWake.d.ts +11 -1
  55. package/lib/types/components/trajectory/MiniWake.d.ts.map +1 -1
  56. package/lib/types/components/trajectory/MiniWake.js +9 -3
  57. package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -1
  58. package/lib/types/dsh-adapter/channel/external-commands.js +8 -2
  59. package/lib/types/dsh-adapter/channel/job-projection.d.ts +1 -0
  60. package/lib/types/dsh-adapter/channel/job-projection.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/channel/job-projection.js +88 -3
  62. package/lib/types/dsh-adapter/channel/mode-actions.d.ts +13 -0
  63. package/lib/types/dsh-adapter/channel/mode-actions.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/channel/mode-actions.js +48 -7
  65. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -1
  66. package/lib/types/dsh-adapter/channel/mode-permission-actions.js +25 -9
  67. package/lib/types/dsh-adapter/channel/preferences.d.ts +1 -1
  68. package/lib/types/dsh-adapter/channel/preferences.d.ts.map +1 -1
  69. package/lib/types/dsh-adapter/channel/preferences.js +17 -7
  70. package/lib/types/dsh-adapter/channel/state.d.ts +4 -2
  71. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  72. package/lib/types/dsh-adapter/channel/state.js +1 -1
  73. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  74. package/lib/types/dsh-adapter/channel/subagent-projection.js +5 -0
  75. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  76. package/lib/types/dsh-adapter/channel.js +16 -1
  77. package/lib/types/dsh-adapter/contract.d.ts +5 -4
  78. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  79. package/lib/types/dsh-adapter/contract.js +9 -3
  80. package/lib/types/dsh-adapter/jobs.d.ts +82 -16
  81. package/lib/types/dsh-adapter/jobs.d.ts.map +1 -1
  82. package/lib/types/dsh-adapter/jobs.js +104 -9
  83. package/lib/types/dsh-adapter/oauth/bonus.d.ts +27 -0
  84. package/lib/types/dsh-adapter/oauth/bonus.d.ts.map +1 -0
  85. package/lib/types/dsh-adapter/oauth/bonus.js +69 -0
  86. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/command.d.ts +2 -1
  87. package/lib/types/dsh-adapter/oauth/command.d.ts.map +1 -0
  88. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/command.js +7 -4
  89. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/credentials.d.ts +13 -10
  90. package/lib/types/dsh-adapter/oauth/credentials.d.ts.map +1 -0
  91. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/credentials.js +61 -23
  92. package/lib/types/dsh-adapter/oauth/deepseek.d.ts +39 -0
  93. package/lib/types/dsh-adapter/oauth/deepseek.d.ts.map +1 -0
  94. package/lib/types/dsh-adapter/oauth/deepseek.js +189 -0
  95. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/index.d.ts +24 -16
  96. package/lib/types/dsh-adapter/oauth/index.d.ts.map +1 -0
  97. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/index.js +34 -20
  98. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/interaction.d.ts +14 -12
  99. package/lib/types/dsh-adapter/oauth/interaction.d.ts.map +1 -0
  100. package/lib/types/dsh-adapter/oauth/interaction.js +283 -0
  101. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/opener.d.ts +2 -1
  102. package/lib/types/dsh-adapter/oauth/opener.d.ts.map +1 -0
  103. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/opener.js +1 -1
  104. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/pi-ai.d.ts +10 -3
  105. package/lib/types/dsh-adapter/oauth/pi-ai.d.ts.map +1 -0
  106. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/pi-ai.js +8 -3
  107. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/profiles.d.ts +6 -3
  108. package/lib/types/dsh-adapter/oauth/profiles.d.ts.map +1 -0
  109. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/profiles.js +11 -3
  110. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/service.d.ts +14 -7
  111. package/lib/types/dsh-adapter/oauth/service.d.ts.map +1 -0
  112. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/service.js +82 -17
  113. package/lib/types/dsh-adapter/plugin.d.ts +8 -1
  114. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  115. package/lib/types/dsh-adapter/plugin.js +72 -12
  116. package/lib/types/dsh-adapter/presets.d.ts +6 -4
  117. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  118. package/lib/types/dsh-adapter/presets.js +6 -4
  119. package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
  120. package/lib/types/dsh-adapter/providerWizard.js +6 -2
  121. package/lib/types/dsh-adapter/subagents.d.ts +1 -0
  122. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  123. package/lib/types/dsh-adapter/subagents.js +10 -1
  124. package/lib/types/i18n.d.ts +174 -18
  125. package/lib/types/i18n.d.ts.map +1 -1
  126. package/lib/types/i18n.js +53 -11
  127. package/lib/types/ink/components/App.d.ts.map +1 -1
  128. package/lib/types/ink/components/App.js +10 -1
  129. package/lib/types/ink/parse-keypress.d.ts +35 -0
  130. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  131. package/lib/types/ink/parse-keypress.js +611 -83
  132. package/lib/types/ink/terminal-querier.d.ts +25 -0
  133. package/lib/types/ink/terminal-querier.d.ts.map +1 -1
  134. package/lib/types/ink/terminal-querier.js +42 -0
  135. package/lib/types/ink/update-overflow-guard.d.ts +24 -0
  136. package/lib/types/ink/update-overflow-guard.d.ts.map +1 -1
  137. package/lib/types/ink/update-overflow-guard.js +25 -3
  138. package/lib/types/minimalUiMode.d.ts +19 -0
  139. package/lib/types/minimalUiMode.d.ts.map +1 -0
  140. package/lib/types/minimalUiMode.js +23 -0
  141. package/lib/types/oauth.d.ts +5 -10
  142. package/lib/types/oauth.d.ts.map +1 -1
  143. package/lib/types/oauth.js +5 -10
  144. package/lib/types/screens/Chat.d.ts +4 -1
  145. package/lib/types/screens/Chat.d.ts.map +1 -1
  146. package/lib/types/screens/Chat.js +27 -7
  147. package/lib/types/screens/StatusLine.d.ts +4 -0
  148. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  149. package/lib/types/screens/StatusLine.js +12 -12
  150. package/lib/types/settings/definitions.js +4 -4
  151. package/lib/types/tips.d.ts.map +1 -1
  152. package/lib/types/tips.js +10 -4
  153. package/lib/types/update.js +2 -2
  154. package/lib/types/utils/pastedImagePath.d.ts +8 -0
  155. package/lib/types/utils/pastedImagePath.d.ts.map +1 -1
  156. package/lib/types/utils/pastedImagePath.js +29 -5
  157. package/package.json +111 -100
  158. package/presets/liangshen/agent.cordis.yml +4 -1
  159. package/presets/liangshen/preset.yml +1 -1
  160. package/lib/types/minimalMode.d.ts +0 -13
  161. package/lib/types/minimalMode.d.ts.map +0 -1
  162. package/lib/types/minimalMode.js +0 -17
  163. package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +0 -21
  164. package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +0 -143
  165. package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +0 -11
  166. package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +0 -44
  167. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +0 -193
  168. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +0 -69
@@ -0,0 +1,189 @@
1
+ /**
2
+ * TUI interaction for the Host-owned DeepSeek account authorization flow.
3
+ *
4
+ * The Host owns PKCE, its callback route, credential persistence, and the
5
+ * `deepseek-account` LLM route. This module supplies a loopback callback
6
+ * origin, mounts the official Host listener when an older bundle patch omits
7
+ * it, and displays the Host's safe attempt state through the existing question
8
+ * panel. It never reads or writes the account grant.
9
+ */
10
+ import { readFileSync } from 'node:fs';
11
+ import { getLang } from '../../i18n.js';
12
+ import { QuestionBridge } from './interaction.js';
13
+ export const DEEPSEEK_ACCOUNT_PROVIDER = 'deepseek-account';
14
+ /** Resolve late: the account provider is absent on older DSH hosts. */
15
+ export function deepSeekAccountFrom(ctx) {
16
+ return ctx.get('deepseekAccount');
17
+ }
18
+ /** The Host webServer may be shared, supplied by the TUI row, or mounted as a fallback. */
19
+ export function deepSeekCallbackOrigin(ctx) {
20
+ const server = ctx.get('webServer');
21
+ const port = server?.port;
22
+ if (port === undefined || !Number.isInteger(port) || port < 1 || port > 65535) {
23
+ throw new Error('DeepSeek sign-in needs an active Host webServer for the browser callback');
24
+ }
25
+ return `http://127.0.0.1:${port}`;
26
+ }
27
+ /**
28
+ * Keep DeepSeek login usable after a profile-only update from a global TUI
29
+ * whose older bundle patch still mounts `dsh-tui-auth` but has no webserver
30
+ * row. The official Host listener is mounted only on the first login that
31
+ * needs it; an existing Web or user-owned listener is never replaced.
32
+ */
33
+ export function createDeepSeekCallbackOriginResolver(ctx) {
34
+ let fallback;
35
+ let mounting;
36
+ let disposed = false;
37
+ const mountFallback = async () => {
38
+ const { WebServer } = await import('@deepseek-ai/dsh-host-webserver');
39
+ if (disposed)
40
+ throw new Error('dsh-auth: callback listener owner was disposed');
41
+ // Another Host row may have become available while the import resolved.
42
+ if (ctx.get('webServer') !== undefined)
43
+ return;
44
+ const fiber = ctx.root.plugin(WebServer, { host: '127.0.0.1', port: 0 });
45
+ fallback = fiber;
46
+ try {
47
+ await fiber;
48
+ }
49
+ catch (error) {
50
+ fallback = undefined;
51
+ try {
52
+ await fiber.dispose();
53
+ }
54
+ catch (disposeError) {
55
+ throw new AggregateError([error, disposeError], 'DeepSeek callback listener failed to start and dispose');
56
+ }
57
+ throw error;
58
+ }
59
+ };
60
+ return {
61
+ resolve: async () => {
62
+ if (disposed)
63
+ throw new Error('dsh-auth: callback listener owner was disposed');
64
+ if (ctx.get('webServer') === undefined) {
65
+ // A declared but unavailable listener is a composition decision or
66
+ // failure, not the missing row of an older global TUI patch.
67
+ const loader = ctx.get('loader');
68
+ for (const entry of loader?.entries() ?? []) {
69
+ if (entry.options.id === 'dsh-tui-webserver'
70
+ || entry.options.name === '@deepseek-ai/dsh-host-webserver') {
71
+ return deepSeekCallbackOrigin(ctx);
72
+ }
73
+ }
74
+ mounting ??= mountFallback().finally(() => { mounting = undefined; });
75
+ try {
76
+ await mounting;
77
+ }
78
+ catch (error) {
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ throw new Error(`DeepSeek sign-in could not start a Host webServer callback listener: ${message}`, { cause: error });
81
+ }
82
+ }
83
+ if (disposed)
84
+ throw new Error('dsh-auth: callback listener owner was disposed');
85
+ return deepSeekCallbackOrigin(ctx);
86
+ },
87
+ dispose: async () => {
88
+ disposed = true;
89
+ await mounting?.catch(() => undefined);
90
+ await fallback?.dispose();
91
+ fallback = undefined;
92
+ },
93
+ };
94
+ }
95
+ const tuiVersion = (() => {
96
+ try {
97
+ const manifest = JSON.parse(readFileSync(new URL(import.meta.resolve('@deepseek-harness-tui/dsh-tui/package.json')), 'utf8'));
98
+ return typeof manifest.version === 'string' ? manifest.version : '0.0.0';
99
+ }
100
+ catch {
101
+ return '0.0.0';
102
+ }
103
+ })();
104
+ /** Platform request identity is supplied by the current TUI, not cached by the Host. */
105
+ export function deepSeekClientMetadata() {
106
+ return {
107
+ version: tuiVersion,
108
+ locale: getLang() === 'zh' ? 'zh-CN' : 'en-US',
109
+ timezoneOffsetSeconds: -new Date().getTimezoneOffset() * 60,
110
+ };
111
+ }
112
+ function succeeded(state, id) {
113
+ return state.attempt?.id === id && state.attempt.phase === 'succeeded' && state.status === 'credential-stored';
114
+ }
115
+ /**
116
+ * Start (or join) the upstream browser attempt, display its URL, and wait for
117
+ * the upstream state stream. Esc/external abort cancels the exact attempt;
118
+ * a commit that won that race is reported as success instead of a false
119
+ * cancellation. The question panel is always retired before returning.
120
+ */
121
+ export async function loginDeepSeekAccount(account, callbackOrigin, ask, signal, helpers = {}) {
122
+ const runAbort = new AbortController();
123
+ const forwardAbort = () => runAbort.abort(signal?.reason);
124
+ if (signal !== undefined) {
125
+ if (signal.aborted)
126
+ forwardAbort();
127
+ else
128
+ signal.addEventListener('abort', forwardAbort, { once: true });
129
+ }
130
+ const bridge = new QuestionBridge(ask, runAbort, helpers);
131
+ let id;
132
+ let active = false;
133
+ try {
134
+ runAbort.signal.throwIfAborted();
135
+ // Match Desktop's login_source for Platform-side promotion eligibility.
136
+ const initial = await account.startSignIn(deepSeekClientMetadata(), callbackOrigin, 'desktop');
137
+ id = initial.attempt?.id;
138
+ if (id === undefined)
139
+ throw new Error('DeepSeek sign-in returned no attempt');
140
+ active = true;
141
+ runAbort.signal.throwIfAborted();
142
+ let shownUrl;
143
+ try {
144
+ for await (const state of account.watch(runAbort.signal)) {
145
+ const attempt = state.attempt;
146
+ if (attempt?.id !== id)
147
+ throw new Error('DeepSeek sign-in attempt was replaced');
148
+ if (attempt.phase === 'waiting-browser' && attempt.authorizeUrl !== undefined
149
+ && attempt.authorizeUrl !== shownUrl) {
150
+ shownUrl = attempt.authorizeUrl;
151
+ bridge.notify({ type: 'auth_url', url: shownUrl });
152
+ }
153
+ if (attempt.phase === 'succeeded') {
154
+ active = false;
155
+ if (!succeeded(state, id))
156
+ throw new Error('DeepSeek sign-in succeeded without a stored credential');
157
+ return;
158
+ }
159
+ if (attempt.phase === 'failed' || attempt.phase === 'expired' || attempt.phase === 'cancelled') {
160
+ active = false;
161
+ throw new Error(`DeepSeek sign-in ${attempt.phase}${attempt.errorCode === undefined ? '' : ` (${attempt.errorCode})`}`);
162
+ }
163
+ }
164
+ }
165
+ catch (error) {
166
+ if (!runAbort.signal.aborted)
167
+ throw error;
168
+ }
169
+ if (runAbort.signal.aborted) {
170
+ const afterCancel = await account.cancelSignIn(id);
171
+ active = false;
172
+ if (succeeded(afterCancel, id))
173
+ return;
174
+ throw new Error('DeepSeek sign-in cancelled');
175
+ }
176
+ throw new Error('DeepSeek sign-in state stream ended before completion');
177
+ }
178
+ finally {
179
+ signal?.removeEventListener('abort', forwardAbort);
180
+ runAbort.abort();
181
+ try {
182
+ if (active && id !== undefined)
183
+ await account.cancelSignIn(id);
184
+ }
185
+ finally {
186
+ await bridge.settle();
187
+ }
188
+ }
189
+ }
@@ -1,21 +1,27 @@
1
1
  /**
2
- * dsh-auth — subscription OAuth sign-in as LLM provider routes.
2
+ * Built-in subscription OAuth sign-in as LLM provider routes.
3
3
  *
4
- * One cordis plugin mounts the pi-ai catalog providers that ship OAuth flows
5
- * (ChatGPT/Codex, Claude Pro/Max, SuperGrok) as `llm` registry routes, so
6
- * their models appear in every model picker the moment the plugin loads —
7
- * signing in is the only missing credential. `PiAiAdapter` runs with this
8
- * plugin's file-backed pi-ai `CredentialStore` injected
4
+ * One internal Cordis module mounts supported subscription routes from the
5
+ * installed pi-ai catalog (ChatGPT/Codex, Claude, Grok, and newer flows when
6
+ * available) as `llm` registry routes. Signed-in models appear in model
7
+ * pickers; unsigned routes remain addressable for saved sessions. When the
8
+ * Host supplies `deepseekAccount`, the same sign-in surface also delegates
9
+ * DeepSeek browser authorization to that service; the Host owns its model
10
+ * route, callback, and credential store.
11
+ * `PiAiAdapter` runs with this
12
+ * module's file-backed pi-ai `CredentialStore` injected
9
13
  * (`PiAiAuthInjection`): requests resolve the stored OAuth credential through
10
14
  * the provider's own auth and rotate refresh tokens under the store's lock.
11
15
  * Login/logout run over the `userQuestions` seam, so they work on any
12
16
  * interactive surface and refuse cleanly where none exists (TUI-RUN-001).
13
17
  *
14
18
  * ```yaml
15
- * - id: dsh-auth
16
- * name: 'dsh-auth'
19
+ * - id: dsh-tui-auth
20
+ * name: '@deepseek-harness-tui/dsh-tui/oauth'
21
+ * inject: [llm, commands]
17
22
  * config:
18
- * providers: [openai-codex, anthropic, xai] # subset of the mounted set
23
+ * # providers: [openai-codex, anthropic, xai] # optional catalog subset;
24
+ * # newer pi-ai also offers openai and meta
19
25
  * # credentialsFile: /secure/path/credentials.json # default $DSH_HOME/dsh-auth/
20
26
  * # Per-provider catalog overrides, keyed by provider id then model id:
21
27
  * # any optional field keeps the installed catalog's value. The example
@@ -30,7 +36,7 @@
30
36
  * (an llm-pi-ai settings profile for the same provider) is refused by the
31
37
  * registry — that refusal is logged and the remaining routes still mount.
32
38
  *
33
- * @module dsh-auth
39
+ * @module @deepseek-harness-tui/dsh-tui/oauth
34
40
  */
35
41
  import type { Context } from '@deepseek-ai/cordis';
36
42
  import z from '@deepseek-ai/schemastery';
@@ -43,15 +49,14 @@ export declare const name = "dsh-auth";
43
49
  * Deliberately empty. A hard code-level inject would deadlock any
44
50
  * composition lacking the `llm`/`commands` services at boot ("pending
45
51
  * (waiting for service: …)") — the failure mode dsh-tui documented for its
46
- * own optional rows (#183). Both services resolve per call through
47
- * `ctx.get` in {@link apply}; compositions that guarantee them (the
48
- * dsh-tui patch row, this package's bundle patch) declare the inject at the
49
- * *entry* level, where a stale patch cannot deadlock a newer boot.
52
+ * optional rows (#183). Both services resolve per call through `ctx.get` in
53
+ * {@link apply}; the profile patch declares the inject at the *entry* level,
54
+ * where a stale patch cannot deadlock a newer boot.
50
55
  */
51
56
  export declare const inject: readonly string[];
52
57
  /** Plugin configuration. */
53
58
  export interface Config {
54
- /** Provider routes to mount; every entry must ship an OAuth flow in the installed pi-ai catalog. */
59
+ /** Provider routes to mount; omitted means every supported flow in the installed pi-ai catalog. */
55
60
  providers?: string[];
56
61
  /** Credential file override; default `$DSH_HOME/dsh-auth/credentials.json`. */
57
62
  credentialsFile?: string;
@@ -70,7 +75,9 @@ export { QuestionBridge, describeEvent } from './interaction.js';
70
75
  export type { AskFn, QuestionBridgeHelpers } from './interaction.js';
71
76
  export { copyToClipboard, openInBrowser, openerFor } from './opener.js';
72
77
  export { CredentialFile, defaultCredentialsFile } from './credentials.js';
73
- export { OAUTH_PROVIDER_IDS, buildOAuthProfile, type ModelOverride } from './profiles.js';
78
+ export { OAUTH_PROVIDER_IDS, availableOAuthProviderIds, buildOAuthProfile, type ModelOverride } from './profiles.js';
79
+ export { DEEPSEEK_ACCOUNT_PROVIDER, deepSeekAccountFrom, deepSeekCallbackOrigin, deepSeekClientMetadata, loginDeepSeekAccount } from './deepseek.js';
80
+ export { WhaleCouponStore } from './bonus.js';
74
81
  /**
75
82
  * The adapter the plugin registers: a {@link PiAiAdapter} whose *advisory
76
83
  * catalog* is credential-gated. A provider with no stored OAuth credential
@@ -92,3 +99,4 @@ export declare class CredentialGatedAdapter extends PiAiAdapter {
92
99
  }
93
100
  /** Mount the routes, the service, and the command. */
94
101
  export declare function apply(ctx: Context, config: Config): void;
102
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEpE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAEpE,OAAO,EAAoE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAOpH,eAAO,MAAM,IAAI,aAAa,CAAA;AAC9B;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EAAO,CAAA;AAgB3C,4BAA4B;AAC5B,MAAM,WAAW,MAAM;IACrB,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAA;CAC/D;AAOD,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAI3B,CAAA;AAEF,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChE,YAAY,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACzE,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AACpH,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpJ,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAkB7C;;;;;;;;;;;;;GAaG;AACH,qBAAa,sBAAuB,SAAQ,WAAW;IAGnD,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAD9B,OAAO,EAAE,kBAAkB,EACV,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC;IAKzD,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;CAI9E;AAED,sDAAsD;AACtD,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAuHxD"}
@@ -1,21 +1,27 @@
1
1
  /**
2
- * dsh-auth — subscription OAuth sign-in as LLM provider routes.
2
+ * Built-in subscription OAuth sign-in as LLM provider routes.
3
3
  *
4
- * One cordis plugin mounts the pi-ai catalog providers that ship OAuth flows
5
- * (ChatGPT/Codex, Claude Pro/Max, SuperGrok) as `llm` registry routes, so
6
- * their models appear in every model picker the moment the plugin loads —
7
- * signing in is the only missing credential. `PiAiAdapter` runs with this
8
- * plugin's file-backed pi-ai `CredentialStore` injected
4
+ * One internal Cordis module mounts supported subscription routes from the
5
+ * installed pi-ai catalog (ChatGPT/Codex, Claude, Grok, and newer flows when
6
+ * available) as `llm` registry routes. Signed-in models appear in model
7
+ * pickers; unsigned routes remain addressable for saved sessions. When the
8
+ * Host supplies `deepseekAccount`, the same sign-in surface also delegates
9
+ * DeepSeek browser authorization to that service; the Host owns its model
10
+ * route, callback, and credential store.
11
+ * `PiAiAdapter` runs with this
12
+ * module's file-backed pi-ai `CredentialStore` injected
9
13
  * (`PiAiAuthInjection`): requests resolve the stored OAuth credential through
10
14
  * the provider's own auth and rotate refresh tokens under the store's lock.
11
15
  * Login/logout run over the `userQuestions` seam, so they work on any
12
16
  * interactive surface and refuse cleanly where none exists (TUI-RUN-001).
13
17
  *
14
18
  * ```yaml
15
- * - id: dsh-auth
16
- * name: 'dsh-auth'
19
+ * - id: dsh-tui-auth
20
+ * name: '@deepseek-harness-tui/dsh-tui/oauth'
21
+ * inject: [llm, commands]
17
22
  * config:
18
- * providers: [openai-codex, anthropic, xai] # subset of the mounted set
23
+ * # providers: [openai-codex, anthropic, xai] # optional catalog subset;
24
+ * # newer pi-ai also offers openai and meta
19
25
  * # credentialsFile: /secure/path/credentials.json # default $DSH_HOME/dsh-auth/
20
26
  * # Per-provider catalog overrides, keyed by provider id then model id:
21
27
  * # any optional field keeps the installed catalog's value. The example
@@ -30,7 +36,7 @@
30
36
  * (an llm-pi-ai settings profile for the same provider) is refused by the
31
37
  * registry — that refusal is logged and the remaining routes still mount.
32
38
  *
33
- * @module dsh-auth
39
+ * @module @deepseek-harness-tui/dsh-tui/oauth
34
40
  */
35
41
  import { existsSync } from 'node:fs';
36
42
  import { homedir } from 'node:os';
@@ -38,18 +44,18 @@ import { join } from 'node:path';
38
44
  import z from '@deepseek-ai/schemastery';
39
45
  import { PiAiAdapter } from '@deepseek-ai/dsh-llm-pi-ai';
40
46
  import { CredentialFile, defaultCredentialsFile } from './credentials.js';
41
- import { buildOAuthProfile, OAUTH_PROVIDER_IDS } from './profiles.js';
47
+ import { availableOAuthProviderIds, buildOAuthProfile, OAUTH_PROVIDER_IDS } from './profiles.js';
42
48
  import { createDshAuthApi, DshAuthService } from './service.js';
43
49
  import { createAuthCommandHandler } from './command.js';
50
+ import { createDeepSeekCallbackOriginResolver, deepSeekAccountFrom } from './deepseek.js';
44
51
  export const name = 'dsh-auth';
45
52
  /**
46
53
  * Deliberately empty. A hard code-level inject would deadlock any
47
54
  * composition lacking the `llm`/`commands` services at boot ("pending
48
55
  * (waiting for service: …)") — the failure mode dsh-tui documented for its
49
- * own optional rows (#183). Both services resolve per call through
50
- * `ctx.get` in {@link apply}; compositions that guarantee them (the
51
- * dsh-tui patch row, this package's bundle patch) declare the inject at the
52
- * *entry* level, where a stale patch cannot deadlock a newer boot.
56
+ * optional rows (#183). Both services resolve per call through `ctx.get` in
57
+ * {@link apply}; the profile patch declares the inject at the *entry* level,
58
+ * where a stale patch cannot deadlock a newer boot.
53
59
  */
54
60
  export const inject = [];
55
61
  const modelOverride = z.object({
@@ -57,7 +63,7 @@ const modelOverride = z.object({
57
63
  maxTokens: z.number().step(1).min(1),
58
64
  });
59
65
  export const Config = z.object({
60
- providers: z.array(z.string()).default([...OAUTH_PROVIDER_IDS]),
66
+ providers: z.array(z.string()).default(availableOAuthProviderIds()),
61
67
  credentialsFile: z.string(),
62
68
  modelOverrides: z.dict(z.dict(modelOverride)),
63
69
  });
@@ -65,7 +71,9 @@ export { createDshAuthApi } from './service.js';
65
71
  export { QuestionBridge, describeEvent } from './interaction.js';
66
72
  export { copyToClipboard, openInBrowser, openerFor } from './opener.js';
67
73
  export { CredentialFile, defaultCredentialsFile } from './credentials.js';
68
- export { OAUTH_PROVIDER_IDS, buildOAuthProfile } from './profiles.js';
74
+ export { OAUTH_PROVIDER_IDS, availableOAuthProviderIds, buildOAuthProfile } from './profiles.js';
75
+ export { DEEPSEEK_ACCOUNT_PROVIDER, deepSeekAccountFrom, deepSeekCallbackOrigin, deepSeekClientMetadata, loginDeepSeekAccount } from './deepseek.js';
76
+ export { WhaleCouponStore } from './bonus.js';
69
77
  /**
70
78
  * The ambient auth context providers may consult while resolving their own
71
79
  * auth. `env()` answers from the process environment; `fileExists()` answers
@@ -107,7 +115,7 @@ export class CredentialGatedAdapter extends PiAiAdapter {
107
115
  }
108
116
  /** Mount the routes, the service, and the command. */
109
117
  export function apply(ctx, config) {
110
- const configured = config.providers ?? [...OAUTH_PROVIDER_IDS];
118
+ const configured = config.providers ?? availableOAuthProviderIds();
111
119
  const unknown = configured.filter(id => !OAUTH_PROVIDER_IDS.includes(id));
112
120
  if (unknown.length > 0 || configured.length === 0) {
113
121
  throw new Error(`dsh-auth: providers must be a non-empty subset of [${OAUTH_PROVIDER_IDS.join(', ')}]; got [${configured.join(', ')}]`);
@@ -156,17 +164,23 @@ export function apply(ctx, config) {
156
164
  // the plugin module; the defensive get-then-create matches how the TUI
157
165
  // mounts userQuestions.
158
166
  const service = ctx.get('dshAuth') ?? new DshAuthService(ctx);
167
+ const callbackOrigin = createDeepSeekCallbackOriginResolver(ctx);
159
168
  const api = createDshAuthApi({
160
169
  profiles,
161
170
  store,
171
+ resolveDeepSeekAccount: () => deepSeekAccountFrom(ctx),
172
+ coupons: service.coupons,
173
+ resolveCallbackOrigin: callbackOrigin.resolve,
162
174
  resolveAsk: () => {
163
175
  const questions = ctx.get('userQuestions');
164
176
  return questions === undefined ? undefined : request => questions.ask(request);
165
177
  },
166
- logger: ctx.logger,
167
178
  });
168
179
  service.api = api;
169
180
  ctx.effect(function* () {
181
+ // LIFO disposal: release command registration, drain started logins, then
182
+ // close a fallback callback listener only this module mounted.
183
+ yield () => callbackOrigin.dispose();
170
184
  const releases = [];
171
185
  // Runtime service resolution (never a code-level inject — see `inject`):
172
186
  // absent services keep this plugin inert and logged, not the whole boot
@@ -197,7 +211,7 @@ export function apply(ctx, config) {
197
211
  const handler = createAuthCommandHandler(api);
198
212
  releases.push(commands.register({
199
213
  name: 'auth',
200
- description: 'Provider subscription sign-in (OAuth): status, login, logout',
214
+ description: 'Provider account sign-in (OAuth): status, login, logout',
201
215
  handler: invocation => {
202
216
  const operation = handler(invocation);
203
217
  active.add(operation);
@@ -6,19 +6,17 @@
6
6
  * their options, text/secret/manual-code prompts expect the custom-answer
7
7
  * input row.
8
8
  *
9
- * `notify()` restores the host contract pi-ai's browser flows assume: the
10
- * `auth_url` / `device_code` events expect the *host* to open the browser
11
- * (their authorize URLs run hundreds of characters — wrapped across panel
12
- * lines they are unclickable, and hand-copying picks up wrap artifacts that
13
- * corrupt the encoded `redirect_uri`, which the provider then rejects). So
14
- * the bridge opens the URL itself, then shows one waiting panel carrying
15
- * copy / reopen / cancel actions until the flow settles. The device-code
16
- * path gets the same treatment: its short code is the thing to copy, and
17
- * the verification page is auto-opened too.
9
+ * `notify()` opens browser/device URLs for pi-ai. Browser flows immediately
10
+ * race a callback server against a `manual_code` prompt; the URL and
11
+ * copy/reopen/cancel actions therefore share that SAME question with the
12
+ * paste field. A separate waiting question would claim the FIFO question
13
+ * surface first and make remote/headless callback fallback unreachable.
14
+ * Browser flows without a manual prompt still get a waiting panel. Device
15
+ * flows use that panel to show/copy the short code until polling settles.
18
16
  *
19
17
  * One `AbortController` owns the whole run: pi-ai reads `interaction.signal`
20
- * to abort the flow, every ask request carries it so surfaces close their
21
- * panels, and the waiting panel's cancel action fires it.
18
+ * to abort the flow, ask requests compose it with per-prompt cancellation,
19
+ * and the waiting panel's cancel action fires it.
22
20
  *
23
21
  * Nothing here assumes a browser or GUI *exists* (TUI-RUN-001): when no
24
22
  * opener or clipboard helper is available the panel degrades to showing the
@@ -26,7 +24,7 @@
26
24
  * registered at all, `ask` rejects and the login command turns that into a
27
25
  * clear refusal.
28
26
  *
29
- * @module dsh-auth/interaction
27
+ * @module @deepseek-harness-tui/dsh-tui/oauth/interaction
30
28
  */
31
29
  import type { AskUserQuestionItem, AskUserQuestionAnswer } from '@deepseek-ai/dsh-user-questions';
32
30
  import type { PiAiAuthEvent, PiAiAuthInteraction, PiAiAuthPrompt } from './pi-ai.js';
@@ -54,15 +52,19 @@ export declare class QuestionBridge implements PiAiAuthInteraction {
54
52
  private readonly openUrl;
55
53
  private readonly copy;
56
54
  private waiting;
55
+ private pendingBrowserView;
56
+ private notice;
57
57
  private waitingClosed;
58
58
  constructor(ask: AskFn, runAbort: AbortController, helpers?: QuestionBridgeHelpers);
59
59
  prompt(prompt: PiAiAuthPrompt): Promise<string>;
60
60
  notify(event: PiAiAuthEvent): void;
61
61
  /** Close the waiting panel (if any) and stop answering for this run. */
62
62
  settle(): Promise<void>;
63
+ private retireWaiting;
63
64
  /**
64
65
  * Show the waiting panel until the flow settles or the user cancels,
65
66
  * re-asking after a copy/reopen action with a one-line status prefix.
66
67
  */
67
68
  private runWaitingPanel;
68
69
  }
70
+ //# sourceMappingURL=interaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/oauth/interaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAGjG,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEpF,4EAA4E;AAC5E,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE;IAC5B,SAAS,EAAE,mBAAmB,EAAE,CAAA;IAChC,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAEpC,iEAAiE;AACjE,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC9C;AAED,gDAAgD;AAChD,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAU1D;AA6CD;;;;GAIG;AACH,qBAAa,cAAe,YAAW,mBAAmB;IAatD,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAb3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoC;IACzD,OAAO,CAAC,OAAO,CAGF;IACb,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAAQ;gBAGV,GAAG,EAAE,KAAK,EACV,QAAQ,EAAE,eAAe,EAC1C,OAAO,GAAE,qBAA0B;IAO/B,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAqFrD,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAgClC,wEAAwE;IAClE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;YAOf,aAAa;IAM3B;;;OAGG;YACW,eAAe;CAgD9B"}