@volter/supercode-frontend-vscode 0.1.50 → 0.1.51
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 +59 -80
- package/dist/chat-setup.d.ts +35 -20
- package/dist/chat-setup.js +56 -60
- package/dist/extension.js +95 -90
- package/dist/host-controls.d.ts +8 -0
- package/dist/index.d.ts +2 -1
- package/dist/setup-flow.d.ts +28 -0
- package/dist/setup-flow.js +127 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,74 +11,55 @@ package or npm.
|
|
|
11
11
|
|
|
12
12
|
## Native chat controls
|
|
13
13
|
|
|
14
|
-
When
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
installation
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
command; credentials and
|
|
64
|
-
browser interaction belong to the harness. Failed or cancelled processes can be
|
|
65
|
-
retried, and closing a terminal never counts as successful authentication.
|
|
66
|
-
|
|
67
|
-
While setup is not ready, Chat refreshes `/state` every three seconds and exposes
|
|
68
|
-
**Check again**. When the host returns a ready runtime connection, it attaches
|
|
69
|
-
and reveals that conversation without a reload. Polling only finishes after both
|
|
70
|
-
attachment and the host's guarded readiness reveal command succeed for that same
|
|
71
|
-
session and runtime URL. Readiness/connection work completes before awaiting the
|
|
72
|
-
native reveal, which may call back into activation. The optional
|
|
73
|
-
`revealReadySessionCommand` checks the observed native conversation throughout
|
|
74
|
-
queued navigation; a newer draft or selection cancels that automatic reveal.
|
|
75
|
-
Hosts without that capability leave the handoff incomplete and direct the person
|
|
76
|
-
to open the conversation from history. A transient attach/reveal failure stays pending and is retried;
|
|
77
|
-
**Check again** retries that handoff even when the host already reports ready.
|
|
78
|
-
Changed inventory updates welcome metadata in place, without opening another
|
|
79
|
-
session or moving focus. Ready connections clear the welcome and reveal the
|
|
80
|
-
actual conversation. Hosts without these optional fields keep the existing
|
|
81
|
-
runtime-attachment commands.
|
|
14
|
+
When no coding agent is ready, native Chat offers **Sign in with ChatGPT**
|
|
15
|
+
(Codex) and **Sign in with Claude** (Claude Code), with the subtitle
|
|
16
|
+
“Use the AI plan you already pay for.” Both use the coding agent in this Chat.
|
|
17
|
+
A click starts one install/sign-in operation for that agent. The host returns
|
|
18
|
+
its exact structured terminal launch through the authenticated `/setup` door;
|
|
19
|
+
a missing agent installs from its vendor package, then the same click continues
|
|
20
|
+
to login. The frontend never interprets repair prose as a command.
|
|
21
|
+
|
|
22
|
+
The host's `/state` supplies `setup: {ready, actions, agents, reason, cwd}`.
|
|
23
|
+
Each agent supplies `{harness, name, installed, signedIn, account}`; an unknown
|
|
24
|
+
account is `null`. Each action supplies `{kind, harness, name, command}` with
|
|
25
|
+
`kind` equal to `install` or `login`. The host revalidates the chosen action
|
|
26
|
+
before returning `{program, arguments, cwd, env?}`. Credentials remain with
|
|
27
|
+
the coding agent; browser sign-in requires the person's approval.
|
|
28
|
+
|
|
29
|
+
The versioned local `supercode.frontend.setupState` command returns
|
|
30
|
+
`{version:1, visible, title, subtitle, footnote, composerPlaceholder,
|
|
31
|
+
composerEnabled, rows, signedInNotice}`. Rows include both providers with their
|
|
32
|
+
installation/login state, `phase` (`idle`, `installing`, `signing-in`, `failed`,
|
|
33
|
+
`signed-in`), `buttonLabel`, `caption`, `busy`, and `operationId`. A renderer
|
|
34
|
+
uses `visible` and `composerEnabled`, rather than the mere presence of rows,
|
|
35
|
+
to decide whether the welcome or disabled composer belongs in the current
|
|
36
|
+
view. This local read starts no process and makes no host request.
|
|
37
|
+
|
|
38
|
+
`supercode.frontend.beginSetup(harness)` starts work and returns its receipt.
|
|
39
|
+
Duplicate clicks on a busy agent return the same operation. The other agent
|
|
40
|
+
remains usable. `supercode.frontend.cancelSetup(harness, operationId)` cancels
|
|
41
|
+
only the terminal that operation started; a stale operation id does nothing.
|
|
42
|
+
Cancel returns to idle, while process failure shows **Try again** and
|
|
43
|
+
**Sign-in didn't finish.** The full reason is written to the Chat output log.
|
|
44
|
+
A process finishing never proves authentication: the host's native inventory
|
|
45
|
+
must report a signed-in account. Once it does, readiness polling attaches
|
|
46
|
+
and reveals the conversation without requiring another click, including when
|
|
47
|
+
sign-in finishes in a separate terminal.
|
|
48
|
+
|
|
49
|
+
Welcome metadata changes in place on each phase change. Cyclotron's paired
|
|
50
|
+
native renderer uses it to show primary/secondary buttons, disabled spinners,
|
|
51
|
+
one caption/Cancel/error slot per provider, and **Sign in to start a chat**
|
|
52
|
+
in the disabled composer. Stock hosts retain native Markdown links. The
|
|
53
|
+
editor's renderer/build is a separate integration; the frontend package alone
|
|
54
|
+
does not claim those richer controls are installed in every host. A successful
|
|
55
|
+
explicit sign-in publishes **Signed in with ChatGPT** or **Signed in with
|
|
56
|
+
Claude** above the newly opened Chat.
|
|
57
|
+
|
|
58
|
+
Setup creates no transcript turns. The guarded readiness reveal preserves a
|
|
59
|
+
newer draft or selection; a welcome-owned empty draft can give way to the first
|
|
60
|
+
ready conversation. Hosts without the guarded reveal command report the
|
|
61
|
+
limitation. Passive inventory polling retries a transient handoff failure until
|
|
62
|
+
attachment and reveal both succeed for the same conversation.
|
|
82
63
|
|
|
83
64
|
A bundling host can expose a private lifecycle endpoint through
|
|
84
65
|
`SUPERCODE_FRONTEND_HOST_URL` and `SUPERCODE_FRONTEND_HOST_CREDENTIAL_FILE`.
|
|
@@ -89,17 +70,15 @@ and returns its new scoped connection. The host owns validation, process lifetim
|
|
|
89
70
|
persistence, and failure handling. Prompts and approvals still use the generated frontend client.
|
|
90
71
|
The contract is in `src/host-controls.ts`.
|
|
91
72
|
|
|
92
|
-
With that host, **New Session** / **Chat: New Chat: Choose Agent**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
and reasoning…**, which goes on to the model and effort choices below. Cancelling the agent
|
|
102
|
-
picker starts no backend conversation.
|
|
73
|
+
With that host, **New Session** / **Chat: New Chat: Choose Agent** lists all
|
|
74
|
+
coding agents, including Codex and Claude Code when missing or signed out.
|
|
75
|
+
Their rows say **Signed in with ChatGPT/Claude** or **Sign in with
|
|
76
|
+
ChatGPT/Claude…**. A sign-in row starts the same install/login operation as the
|
|
77
|
+
welcome. Selecting a ready agent leaves model and reasoning at its defaults;
|
|
78
|
+
the separate **<agent>: model and reasoning…** rows retain explicit settings.
|
|
79
|
+
The picker is titled **New chat**, with **Choose your coding agent** as its
|
|
80
|
+
placeholder. Cancelling it starts no backend conversation. Initial automatic
|
|
81
|
+
agent selection and last-used selection remain the host's responsibility.
|
|
103
82
|
|
|
104
83
|
The native blank chat opens before the picker appears. First send waits for the
|
|
105
84
|
choice and the host's new connection through Code-OSS's session-creation handler;
|
|
@@ -111,7 +90,7 @@ paths. A failed startup leaves the draft available to retry.
|
|
|
111
90
|
|
|
112
91
|
The pending agent/model choice belongs to that blank chat's untitled resource
|
|
113
92
|
and is retired when another conversation opens. Cancelling or opening sign-in
|
|
114
|
-
does not choose an agent. Readiness polling
|
|
93
|
+
does not choose an agent. Readiness polling continues to discover
|
|
115
94
|
agents while a draft is open, updating the welcome and model catalogue without
|
|
116
95
|
replacing its widget or input. First send refreshes readiness; after a native
|
|
117
96
|
sign-in choice it uses that agent once ready, otherwise it keeps the draft with
|
package/dist/chat-setup.d.ts
CHANGED
|
@@ -1,25 +1,40 @@
|
|
|
1
|
-
/** Native Chat
|
|
1
|
+
/** Native Chat setup. The host owns inventory, commands and provider credentials. */
|
|
2
2
|
import * as vscode from 'vscode';
|
|
3
|
-
import type {
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
6
|
-
export declare const
|
|
7
|
-
export type
|
|
8
|
-
export interface
|
|
3
|
+
import type { ChatSetupAgent, HostState } from './host-controls.js';
|
|
4
|
+
export declare const SETUP_BEGIN = "supercode.frontend.beginSetup";
|
|
5
|
+
export declare const SETUP_CANCEL = "supercode.frontend.cancelSetup";
|
|
6
|
+
export declare const SETUP_STATE = "supercode.frontend.setupState";
|
|
7
|
+
export type ChatSetupPhase = 'idle' | 'installing' | 'signing-in' | 'failed' | 'signed-in';
|
|
8
|
+
export interface ChatSetupOperation {
|
|
9
9
|
readonly id: string;
|
|
10
|
-
readonly
|
|
10
|
+
readonly harness: string;
|
|
11
|
+
readonly phase: ChatSetupPhase;
|
|
11
12
|
readonly startedAt: number;
|
|
12
13
|
readonly updatedAt: number;
|
|
13
|
-
readonly finishedAt?: number;
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
export interface ChatSetupProvider extends ChatSetupAgent {
|
|
16
|
+
provider: string;
|
|
17
|
+
phase: ChatSetupPhase;
|
|
18
|
+
buttonLabel: string;
|
|
19
|
+
caption: string;
|
|
20
|
+
busy: boolean;
|
|
21
|
+
operationId: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface ChatSetupPresentation {
|
|
24
|
+
version: 1;
|
|
25
|
+
visible: boolean;
|
|
26
|
+
title: string;
|
|
27
|
+
subtitle: string;
|
|
28
|
+
footnote: string;
|
|
29
|
+
composerPlaceholder: string;
|
|
30
|
+
composerEnabled: boolean;
|
|
31
|
+
rows: ChatSetupProvider[];
|
|
32
|
+
signedInNotice: string | null;
|
|
33
|
+
}
|
|
34
|
+
/** Older hosts can describe readiness through actions and startable choices.
|
|
35
|
+
* An absent account stays unknown; a launched process never establishes login. */
|
|
36
|
+
export declare function setupAgents(state: HostState): ChatSetupAgent[];
|
|
37
|
+
export declare function setupPresentation(state: HostState, operations: ReadonlyMap<string, ChatSetupOperation>, notice?: string): ChatSetupPresentation;
|
|
38
|
+
/** Stock hosts keep native Markdown. Cyclotron reads the same state through
|
|
39
|
+
* SETUP_STATE to render disabled buttons, progress and the composer in place. */
|
|
40
|
+
export declare function setupWelcome(presentation: ChatSetupPresentation): vscode.MarkdownString;
|
package/dist/chat-setup.js
CHANGED
|
@@ -1,69 +1,65 @@
|
|
|
1
|
-
/** Native Chat
|
|
1
|
+
/** Native Chat setup. The host owns inventory, commands and provider credentials. */
|
|
2
2
|
import * as vscode from 'vscode';
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
3
|
+
export const SETUP_BEGIN = 'supercode.frontend.beginSetup';
|
|
4
|
+
export const SETUP_CANCEL = 'supercode.frontend.cancelSetup';
|
|
5
|
+
export const SETUP_STATE = 'supercode.frontend.setupState';
|
|
6
|
+
const PROVIDERS = [
|
|
7
|
+
{ harness: 'codex', name: 'Codex', provider: 'ChatGPT', caption: 'Runs Codex · ChatGPT Plus, Pro or Business' },
|
|
8
|
+
{ harness: 'claude-code', name: 'Claude Code', provider: 'Claude', caption: 'Runs Claude Code · Claude Pro or Max' },
|
|
9
|
+
];
|
|
10
|
+
/** Older hosts can describe readiness through actions and startable choices.
|
|
11
|
+
* An absent account stays unknown; a launched process never establishes login. */
|
|
12
|
+
export function setupAgents(state) {
|
|
13
|
+
if (state.setup?.agents)
|
|
14
|
+
return state.setup.agents.map(agent => ({ ...agent }));
|
|
15
|
+
const ids = new Set([...PROVIDERS.map(p => p.harness), ...state.harnesses.map(h => h.id), ...(state.setup?.actions ?? []).map(a => a.harness)]);
|
|
16
|
+
return [...ids].map(harness => {
|
|
17
|
+
const choice = state.harnesses.find(h => h.id === harness);
|
|
18
|
+
const actions = state.setup?.actions.filter(a => a.harness === harness) ?? [];
|
|
19
|
+
return { harness, name: choice?.name ?? PROVIDERS.find(p => p.harness === harness)?.name ?? actions[0]?.name ?? harness,
|
|
20
|
+
installed: !actions.some(a => a.kind === 'install') && Boolean(choice || actions.some(a => a.kind === 'login')),
|
|
21
|
+
signedIn: choice?.autoStart === true && !actions.some(a => a.kind === 'login'), account: null };
|
|
22
|
+
});
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
export function setupPresentation(state, operations, notice) {
|
|
25
|
+
const agents = setupAgents(state);
|
|
26
|
+
const providers = PROVIDERS.map(provider => {
|
|
27
|
+
const agent = agents.find(a => a.harness === provider.harness) ?? { harness: provider.harness, name: provider.name, installed: false, signedIn: false, account: null };
|
|
28
|
+
const operation = operations.get(provider.harness);
|
|
29
|
+
const phase = agent.signedIn ? 'signed-in' : operation?.phase ?? 'idle';
|
|
30
|
+
const busy = phase === 'installing' || phase === 'signing-in';
|
|
31
|
+
return { ...agent, provider: provider.provider, phase,
|
|
32
|
+
buttonLabel: phase === 'installing' ? `Installing ${provider.name}…` : phase === 'signing-in' ? 'Finish signing in in your browser…'
|
|
33
|
+
: phase === 'failed' ? 'Try again' : `Sign in with ${provider.provider}`,
|
|
34
|
+
caption: busy ? 'Cancel' : phase === 'failed' ? "Sign-in didn't finish." : provider.caption,
|
|
35
|
+
busy, operationId: operation?.id ?? null };
|
|
36
|
+
});
|
|
37
|
+
const visible = Boolean(state.setup && !state.setup.ready);
|
|
38
|
+
return { version: 1, visible, title: 'Chat', subtitle: 'Use the AI plan you already pay for.',
|
|
39
|
+
footnote: 'Runs on this computer with your own account.', composerPlaceholder: visible ? 'Sign in to start a chat' : 'Ask a question or describe a task',
|
|
40
|
+
composerEnabled: !visible, rows: providers, signedInNotice: notice ? `Signed in with ${notice}` : null };
|
|
39
41
|
}
|
|
40
|
-
/**
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
* of the welcome, and the host still revalidates every action on click. */
|
|
44
|
-
export function setupWelcome(setup, chatGPT = false) {
|
|
42
|
+
/** Stock hosts keep native Markdown. Cyclotron reads the same state through
|
|
43
|
+
* SETUP_STATE to render disabled buttons, progress and the composer in place. */
|
|
44
|
+
export function setupWelcome(presentation) {
|
|
45
45
|
const welcome = new vscode.MarkdownString();
|
|
46
|
-
welcome.isTrusted = { enabledCommands: [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
welcome.isTrusted = { enabledCommands: [SETUP_BEGIN, SETUP_CANCEL] };
|
|
47
|
+
welcome.appendMarkdown(`${presentation.subtitle}\n\n`);
|
|
48
|
+
for (const provider of presentation.rows) {
|
|
49
|
+
const args = encodeURIComponent(JSON.stringify([provider.harness]));
|
|
50
|
+
if (provider.busy)
|
|
51
|
+
welcome.appendText(provider.buttonLabel);
|
|
52
|
+
else
|
|
53
|
+
welcome.appendMarkdown(`[${provider.buttonLabel}](command:${SETUP_BEGIN}?${args})`);
|
|
50
54
|
welcome.appendMarkdown('\n\n');
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (login.length) {
|
|
55
|
-
for (const action of login) {
|
|
56
|
-
const title = new vscode.MarkdownString().appendText(`Sign in with ${action.name}`).value;
|
|
57
|
-
const args = encodeURIComponent(JSON.stringify([action.kind, action.harness]));
|
|
58
|
-
welcome.appendMarkdown(`- [${title}](command:supercode.frontend.setup?${args})\n`);
|
|
55
|
+
if (provider.busy) {
|
|
56
|
+
const cancel = encodeURIComponent(JSON.stringify([provider.harness, provider.operationId]));
|
|
57
|
+
welcome.appendMarkdown(`[Cancel](command:${SETUP_CANCEL}?${cancel})`);
|
|
59
58
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
else {
|
|
64
|
-
welcome.appendMarkdown('No local agents are ready. Install an agent on this machine to use it with Supercode.');
|
|
59
|
+
else
|
|
60
|
+
welcome.appendText(provider.caption);
|
|
61
|
+
welcome.appendMarkdown('\n\n');
|
|
65
62
|
}
|
|
66
|
-
|
|
67
|
-
welcome.appendMarkdown('\n\nAlready signed in? [Check again](command:supercode.frontend.refreshSetup).');
|
|
63
|
+
welcome.appendText(presentation.footnote);
|
|
68
64
|
return welcome;
|
|
69
65
|
}
|
package/dist/extension.js
CHANGED
|
@@ -2,7 +2,8 @@ import { resolveSessionBinding } from './bindings.js';
|
|
|
2
2
|
import { withTextContext } from './context.js';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { ChatHostControls, runtimeIdentity, harnessDisplayName, turnPermission } from './host-controls.js';
|
|
5
|
-
import {
|
|
5
|
+
import { SETUP_BEGIN, SETUP_CANCEL, SETUP_STATE, setupAgents, setupPresentation, setupWelcome } from './chat-setup.js';
|
|
6
|
+
import { ChatSetupFlow } from './setup-flow.js';
|
|
6
7
|
import * as vscode from 'vscode';
|
|
7
8
|
import { createFrontendClient } from './client.js';
|
|
8
9
|
import { readCredentialFile } from './credential.js';
|
|
@@ -345,38 +346,6 @@ export async function activate(context) {
|
|
|
345
346
|
}
|
|
346
347
|
}
|
|
347
348
|
const controls = await ChatHostControls.fromEnvironment();
|
|
348
|
-
const offerChatGPT = await chatGPTAvailable();
|
|
349
|
-
let signingInWithChatGPT;
|
|
350
|
-
let chatGPTSignInOperation;
|
|
351
|
-
const signInSnapshot = () => chatGPTSignInOperation ? { ...chatGPTSignInOperation } : null;
|
|
352
|
-
const startChatGPTSignIn = () => {
|
|
353
|
-
if (signingInWithChatGPT)
|
|
354
|
-
return signingInWithChatGPT;
|
|
355
|
-
const startedAt = Date.now();
|
|
356
|
-
const operation = { id: randomUUID(), startedAt };
|
|
357
|
-
const update = (phase) => {
|
|
358
|
-
const updatedAt = Date.now();
|
|
359
|
-
chatGPTSignInOperation = { ...operation, phase, updatedAt,
|
|
360
|
-
...(phase === 'completed' || phase === 'failed' ? { finishedAt: updatedAt } : {}) };
|
|
361
|
-
};
|
|
362
|
-
signingInWithChatGPT = signInWithChatGPT(phase => update(phase)).then(() => {
|
|
363
|
-
// Fulfilled install/sidebar commands do not establish authentication.
|
|
364
|
-
update('completed');
|
|
365
|
-
}).catch(error => {
|
|
366
|
-
update('failed');
|
|
367
|
-
log.error(`Could not open ChatGPT sign-in: ${String(error)}`);
|
|
368
|
-
void vscode.window.showErrorMessage(`Could not open ChatGPT sign-in: ${error instanceof Error ? error.message : String(error)}`);
|
|
369
|
-
}).finally(() => { signingInWithChatGPT = undefined; });
|
|
370
|
-
return signingInWithChatGPT;
|
|
371
|
-
};
|
|
372
|
-
context.subscriptions.push(
|
|
373
|
-
// Preserve the completion-awaiting command used by the agent picker.
|
|
374
|
-
vscode.commands.registerCommand(CHATGPT_SIGN_IN, startChatGPTSignIn),
|
|
375
|
-
// A click starts one retained task. Its receipt is not install/login success.
|
|
376
|
-
vscode.commands.registerCommand(CHATGPT_SIGN_IN_BEGIN, () => {
|
|
377
|
-
void startChatGPTSignIn();
|
|
378
|
-
return signInSnapshot();
|
|
379
|
-
}), vscode.commands.registerCommand(CHATGPT_SIGN_IN_STATUS, (operationId) => operationId === undefined || operationId === chatGPTSignInOperation?.id ? signInSnapshot() : null));
|
|
380
349
|
const controlsChanged = new vscode.EventEmitter();
|
|
381
350
|
context.subscriptions.push(controlsChanged);
|
|
382
351
|
// A person's explicit picker choice: per conversation once it is bound, and for the new chat before then.
|
|
@@ -628,6 +597,7 @@ export async function activate(context) {
|
|
|
628
597
|
setupHandoff = { id, baseUrl: connection.baseUrl };
|
|
629
598
|
};
|
|
630
599
|
let setupRevealed = false;
|
|
600
|
+
let welcomeWasVisible = controls?.state.setup?.ready === false;
|
|
631
601
|
const revealSetup = async () => {
|
|
632
602
|
if (!granted.has('chatSessionsProvider') || !controls?.state.setup || controls.state.setup.ready || setupRevealed)
|
|
633
603
|
return;
|
|
@@ -636,6 +606,13 @@ export async function activate(context) {
|
|
|
636
606
|
await vscode.commands.executeCommand(`workbench.action.chat.openNewChatSessionInPlace.${SESSION_SCHEME}`, 'sidebar');
|
|
637
607
|
setupRevealed = true;
|
|
638
608
|
};
|
|
609
|
+
let refreshingInventory;
|
|
610
|
+
const refreshInventory = () => {
|
|
611
|
+
if (!controls)
|
|
612
|
+
return Promise.resolve();
|
|
613
|
+
refreshingInventory ??= controls.refresh().then(() => { controlsChanged.fire(); }).finally(() => { refreshingInventory = undefined; });
|
|
614
|
+
return refreshingInventory;
|
|
615
|
+
};
|
|
639
616
|
let refreshingSetup;
|
|
640
617
|
const refreshSetup = () => {
|
|
641
618
|
if (refreshingSetup)
|
|
@@ -645,9 +622,10 @@ export async function activate(context) {
|
|
|
645
622
|
return;
|
|
646
623
|
const generation = focusGeneration;
|
|
647
624
|
const focusResource = focusedResource?.toString() ?? null;
|
|
625
|
+
welcomeWasVisible ||= controls.state.setup?.ready === false;
|
|
648
626
|
// Discover and publish readiness even while a blank chat holds a draft.
|
|
649
627
|
// Only the automatic reveal is suppressed; discovery must never stall.
|
|
650
|
-
await
|
|
628
|
+
await refreshInventory();
|
|
651
629
|
const state = controls.state;
|
|
652
630
|
controlsChanged.fire();
|
|
653
631
|
if (state.setup?.ready || (!state.setup && state.connection?.SUPERCODE_FRONTEND_URL)) {
|
|
@@ -672,7 +650,7 @@ export async function activate(context) {
|
|
|
672
650
|
const live = target?.kind === 'managed' && Boolean(frontends.get(target.id)?.session.pendingRequests.size
|
|
673
651
|
|| frontends.get(target.id)?.session.descriptor?.turn_state === 'busy');
|
|
674
652
|
if (!target || !controls || applying !== applied || target.generation !== focusGeneration
|
|
675
|
-
|| (focusedResource?.path.startsWith('/untitled-') && !live))
|
|
653
|
+
|| (focusedResource?.path.startsWith('/untitled-') && !live && !welcomeWasVisible))
|
|
676
654
|
return;
|
|
677
655
|
if (target.kind === 'managed') {
|
|
678
656
|
if ((controls.state.setup && !controls.state.setup.ready) || controls.state.activeSession !== target.id
|
|
@@ -680,45 +658,74 @@ export async function activate(context) {
|
|
|
680
658
|
return;
|
|
681
659
|
if (!setupHandoffComplete())
|
|
682
660
|
await revealManaged(target, { resource: target.focusResource });
|
|
661
|
+
if (setupHandoffComplete())
|
|
662
|
+
welcomeWasVisible = false;
|
|
683
663
|
}
|
|
684
664
|
else if (!controls.state.setup?.ready) {
|
|
685
665
|
await revealSetup();
|
|
686
666
|
}
|
|
687
667
|
});
|
|
688
|
-
// Coalesce
|
|
668
|
+
// Coalesce inventory refresh through actual reveal completion or refusal.
|
|
689
669
|
refreshingSetup = revealed.finally(() => { refreshingSetup = undefined; });
|
|
690
670
|
return refreshingSetup;
|
|
691
671
|
};
|
|
692
672
|
const setupTerminals = new Map();
|
|
693
673
|
context.subscriptions.push(vscode.window.onDidCloseTerminal(terminal => {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.refreshSetup', refreshSetup));
|
|
699
|
-
context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.setup', async (kind, harness) => {
|
|
700
|
-
if (!controls)
|
|
701
|
-
throw new Error('This host does not expose sign-in.');
|
|
702
|
-
const key = `${kind}/${harness}`;
|
|
703
|
-
const existing = setupTerminals.get(key);
|
|
704
|
-
if (existing && existing.exitStatus === undefined) {
|
|
705
|
-
existing.show();
|
|
706
|
-
return;
|
|
674
|
+
const resolve = setupTerminals.get(terminal);
|
|
675
|
+
if (resolve) {
|
|
676
|
+
setupTerminals.delete(terminal);
|
|
677
|
+
resolve(terminal.exitStatus?.code);
|
|
707
678
|
}
|
|
708
|
-
await controls.refresh();
|
|
709
|
-
const offered = controls.state.setup?.actions.find(a => a.kind === kind && a.harness === harness);
|
|
710
|
-
if (!offered) {
|
|
711
|
-
await refreshSetup();
|
|
712
|
-
throw new Error('This setup action is no longer available.');
|
|
713
|
-
}
|
|
714
|
-
const action = await controls.setup(kind, harness);
|
|
715
|
-
if (action.command !== offered.command)
|
|
716
|
-
throw new Error('The setup command changed. Check again before running it.');
|
|
717
|
-
const terminal = vscode.window.createTerminal({ name: `${action.name} · ${kind === 'login' ? 'sign in' : 'install'}`,
|
|
718
|
-
cwd: action.cwd, shellPath: action.program, shellArgs: action.arguments, env: action.env });
|
|
719
|
-
setupTerminals.set(key, terminal);
|
|
720
|
-
terminal.show();
|
|
721
679
|
}));
|
|
680
|
+
const setupFlow = controls ? new ChatSetupFlow({
|
|
681
|
+
state: () => controls.state,
|
|
682
|
+
refresh: async () => {
|
|
683
|
+
await refreshInventory();
|
|
684
|
+
// Inventory settles independently of guarded navigation: a person moving
|
|
685
|
+
// focus does not turn a successful native login into a failed one.
|
|
686
|
+
if (controls.state.setup?.ready && !setupHandoffComplete()) {
|
|
687
|
+
void refreshSetup().catch(error => log.warn(`Could not reveal signed-in Chat: ${String(error)}`));
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
changed: () => {
|
|
691
|
+
const choice = activeDraft()?.choice;
|
|
692
|
+
if (choice?.kind === 'signIn' && choice.harness && setupAgents(controls.state).some(agent => agent.harness === choice.harness && agent.signedIn)) {
|
|
693
|
+
activeDraft().choice = { kind: 'agent', selection: { harness: choice.harness, model: '', effort: '' } };
|
|
694
|
+
}
|
|
695
|
+
controlsChanged.fire();
|
|
696
|
+
},
|
|
697
|
+
log: message => log.error(message),
|
|
698
|
+
launch: async (kind, harness, current) => {
|
|
699
|
+
if (!current())
|
|
700
|
+
throw new vscode.CancellationError();
|
|
701
|
+
const offered = controls.state.setup?.actions.find(a => a.kind === kind && a.harness === harness);
|
|
702
|
+
if (!offered)
|
|
703
|
+
throw new Error('This sign-in or install action is unavailable.');
|
|
704
|
+
const action = await controls.setup(kind, harness);
|
|
705
|
+
if (!current())
|
|
706
|
+
throw new vscode.CancellationError();
|
|
707
|
+
if (action.command !== offered.command)
|
|
708
|
+
throw new Error('The sign-in command changed. Try again.');
|
|
709
|
+
const terminal = vscode.window.createTerminal({ name: `${action.name} · ${kind === 'login' ? 'sign in' : 'install'}`,
|
|
710
|
+
cwd: action.cwd, shellPath: action.program, shellArgs: action.arguments, env: action.env });
|
|
711
|
+
const exited = new Promise(resolve => {
|
|
712
|
+
setupTerminals.set(terminal, resolve);
|
|
713
|
+
// The process can finish before the close listener has observed this terminal.
|
|
714
|
+
if (terminal.exitStatus) {
|
|
715
|
+
setupTerminals.delete(terminal);
|
|
716
|
+
resolve(terminal.exitStatus.code);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
terminal.show();
|
|
720
|
+
return { exited, cancel: () => terminal.dispose() };
|
|
721
|
+
},
|
|
722
|
+
}) : undefined;
|
|
723
|
+
const setupState = () => controls ? setupPresentation(controls.state, setupFlow?.operations ?? new Map(), setupFlow?.notice) : null;
|
|
724
|
+
context.subscriptions.push({ dispose: () => setupFlow?.dispose() }, vscode.commands.registerCommand('supercode.frontend.refreshSetup', refreshSetup), vscode.commands.registerCommand(SETUP_STATE, setupState), vscode.commands.registerCommand(SETUP_BEGIN, (harness) => {
|
|
725
|
+
if (!setupFlow)
|
|
726
|
+
throw new Error('This host does not expose sign-in.');
|
|
727
|
+
return setupFlow.begin(harness);
|
|
728
|
+
}), vscode.commands.registerCommand(SETUP_CANCEL, (harness, operationId) => setupFlow?.cancel(harness, operationId) ?? false));
|
|
722
729
|
const restoring = new Map();
|
|
723
730
|
const connectionFor = async (resource, activate = true, token) => {
|
|
724
731
|
if (savedAttachments.has(resource.authority)) {
|
|
@@ -943,7 +950,7 @@ export async function activate(context) {
|
|
|
943
950
|
throw new Error(`Finish signing in with ${harnessDisplayName(loginHarness)} before sending.`);
|
|
944
951
|
}
|
|
945
952
|
if (choice?.kind !== 'agent' && controls.state.setup && !controls.state.setup.ready) {
|
|
946
|
-
throw new Error('Sign in
|
|
953
|
+
throw new Error('Sign in to start a chat.');
|
|
947
954
|
}
|
|
948
955
|
const selection = choice?.kind === 'agent' ? choice.selection
|
|
949
956
|
: { harness: loginHarness ?? controls.state.selection.harness, model: '', effort: '' };
|
|
@@ -1078,41 +1085,31 @@ export async function activate(context) {
|
|
|
1078
1085
|
return;
|
|
1079
1086
|
}
|
|
1080
1087
|
await controls.refresh();
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
// Each agent whose model and reasoning a chat can set also offers that, right where the agent is chosen.
|
|
1088
|
+
const agents = setupAgents(controls.state);
|
|
1089
|
+
const localChoices = agents.map(agent => ({ label: agent.name, id: agent.harness,
|
|
1090
|
+
description: agent.harness === 'codex' ? (agent.signedIn ? 'Signed in with ChatGPT' : 'Sign in with ChatGPT…')
|
|
1091
|
+
: agent.harness === 'claude-code' ? (agent.signedIn ? 'Signed in with Claude' : 'Sign in with Claude…')
|
|
1092
|
+
: controls.state.harnesses.find(h => h.id === agent.harness)?.description,
|
|
1093
|
+
login: controls.state.setup?.actions.some(a => a.harness === agent.harness) ?? false,
|
|
1094
|
+
autoStart: controls.state.harnesses.some(h => h.id === agent.harness && h.autoStart), configure: false }));
|
|
1089
1095
|
const modelChoices = localChoices.filter(choice => !choice.login && CONFIGURABLE_HARNESSES.includes(choice.id))
|
|
1090
|
-
.map(choice => ({ ...choice, label: `${choice.label}: model and reasoning…`, description:
|
|
1091
|
-
const choices = [
|
|
1092
|
-
|
|
1093
|
-
...localChoices,
|
|
1094
|
-
...modelChoices,
|
|
1095
|
-
];
|
|
1096
|
+
.map(choice => ({ ...choice, label: `${choice.label}: model and reasoning…`, description: undefined, autoStart: false, configure: true }));
|
|
1097
|
+
const choices = [...localChoices, ...(modelChoices.length ? [{ label: '', id: '__models__', kind: vscode.QuickPickItemKind.Separator,
|
|
1098
|
+
description: undefined, login: false, autoStart: false, configure: false }, ...modelChoices] : [])];
|
|
1096
1099
|
if (!choices.length) {
|
|
1097
1100
|
await revealSetup();
|
|
1098
1101
|
return;
|
|
1099
1102
|
}
|
|
1100
|
-
// A sole sign-in option still requires a deliberate click before opening its terminal.
|
|
1101
|
-
// The official choice must not disable the existing sole-local-agent shortcut.
|
|
1102
1103
|
await chooseNewChat(async () => {
|
|
1103
|
-
const harness =
|
|
1104
|
+
const harness = await vscode.window.showQuickPick(choices, { title: 'New chat', placeHolder: 'Choose your coding agent' });
|
|
1104
1105
|
if (!harness)
|
|
1105
1106
|
return { kind: 'cancelled' };
|
|
1106
1107
|
if (harness.configure) {
|
|
1107
1108
|
const chosen = await pickModelAndEffort(harness.id);
|
|
1108
1109
|
return chosen ? { kind: 'agent', selection: { harness: harness.id, ...chosen } } : { kind: 'cancelled' };
|
|
1109
1110
|
}
|
|
1110
|
-
if (harness.id === CHATGPT_SIGN_IN) {
|
|
1111
|
-
await vscode.commands.executeCommand(CHATGPT_SIGN_IN);
|
|
1112
|
-
return { kind: 'signIn' };
|
|
1113
|
-
}
|
|
1114
1111
|
if (harness.login) {
|
|
1115
|
-
await vscode.commands.executeCommand(
|
|
1112
|
+
await vscode.commands.executeCommand(SETUP_BEGIN, harness.id);
|
|
1116
1113
|
return { kind: 'signIn', harness: harness.id };
|
|
1117
1114
|
}
|
|
1118
1115
|
return { kind: 'agent', selection: { harness: harness.id, model: '', effort: '' } };
|
|
@@ -1447,14 +1444,18 @@ export async function activate(context) {
|
|
|
1447
1444
|
if (!granted.has('defaultChatParticipant'))
|
|
1448
1445
|
return;
|
|
1449
1446
|
const setup = controls?.state.setup;
|
|
1450
|
-
const
|
|
1447
|
+
const presentation = setupState();
|
|
1448
|
+
const welcome = setup && !setup.ready ? setupWelcome(presentation)
|
|
1451
1449
|
: !controls && frontends.all.length === 0 ? new vscode.MarkdownString(unavailableMarkdown(frontends.unavailable))
|
|
1452
1450
|
: undefined;
|
|
1453
|
-
|
|
1451
|
+
const key = JSON.stringify([welcome?.value, presentation]);
|
|
1452
|
+
if (welcomeValue === key)
|
|
1454
1453
|
return;
|
|
1455
|
-
welcomeValue =
|
|
1454
|
+
welcomeValue = key;
|
|
1456
1455
|
// Metadata updates merge across RPC; undefined can leave the previous
|
|
1457
1456
|
// sign-in message behind. An explicit empty value clears it in place.
|
|
1457
|
+
// The native renderer owns the signed-in notice above a ready conversation.
|
|
1458
|
+
// Keeping it in this fallback message would draw it again below that notice.
|
|
1458
1459
|
for (const participant of participants)
|
|
1459
1460
|
participant.additionalWelcomeMessage = welcome ?? new vscode.MarkdownString('');
|
|
1460
1461
|
};
|
|
@@ -1615,7 +1616,7 @@ export async function activate(context) {
|
|
|
1615
1616
|
await controls.refresh();
|
|
1616
1617
|
if (inspect === true)
|
|
1617
1618
|
return {
|
|
1618
|
-
|
|
1619
|
+
setupState: setupState(),
|
|
1619
1620
|
connections: frontends.all.map(c => ({ ...frontends.info(c), permissions: [...c.session.permissions],
|
|
1620
1621
|
pendingRequests: [...c.session.pendingRequests.values()] })),
|
|
1621
1622
|
selection: controls?.state.selection, activeSession: controls?.state.activeSession,
|
|
@@ -1779,7 +1780,10 @@ export async function activate(context) {
|
|
|
1779
1780
|
let setupDisposed = false;
|
|
1780
1781
|
const pollSetup = () => {
|
|
1781
1782
|
const choice = activeDraft()?.choice;
|
|
1782
|
-
const awaitingLogin = choice?.kind === 'signIn' && choice.harness && !controls
|
|
1783
|
+
const awaitingLogin = (choice?.kind === 'signIn' && choice.harness && !setupAgents(controls.state).some(h => h.harness === choice.harness && h.signedIn))
|
|
1784
|
+
|| [...(setupFlow?.operations.values() ?? [])].some(operation => operation.phase === 'installing' || operation.phase === 'signing-in');
|
|
1785
|
+
if ([...(setupFlow?.operations.values() ?? [])].some(operation => operation.phase === 'installing' || operation.phase === 'signing-in'))
|
|
1786
|
+
return;
|
|
1783
1787
|
if (setupTimer || setupDisposed || !controls?.state.setup || (controls.state.setup.ready && setupHandoffComplete() && !awaitingLogin))
|
|
1784
1788
|
return;
|
|
1785
1789
|
setupTimer = setTimeout(() => {
|
|
@@ -1788,6 +1792,7 @@ export async function activate(context) {
|
|
|
1788
1792
|
}, 3000);
|
|
1789
1793
|
};
|
|
1790
1794
|
pollSetup();
|
|
1795
|
+
context.subscriptions.push(controlsChanged.event(pollSetup));
|
|
1791
1796
|
context.subscriptions.push({ dispose: () => { setupDisposed = true; clearTimeout(setupTimer); } });
|
|
1792
1797
|
return {
|
|
1793
1798
|
connect: async (options) => {
|
package/dist/host-controls.d.ts
CHANGED
|
@@ -35,9 +35,17 @@ export interface ChatSetupAction {
|
|
|
35
35
|
name: string;
|
|
36
36
|
command: string;
|
|
37
37
|
}
|
|
38
|
+
export interface ChatSetupAgent {
|
|
39
|
+
harness: string;
|
|
40
|
+
name: string;
|
|
41
|
+
installed: boolean;
|
|
42
|
+
signedIn: boolean;
|
|
43
|
+
account: string | null;
|
|
44
|
+
}
|
|
38
45
|
export interface ChatSetupState {
|
|
39
46
|
ready: boolean;
|
|
40
47
|
actions: ChatSetupAction[];
|
|
48
|
+
agents?: ChatSetupAgent[];
|
|
41
49
|
reason: string | null;
|
|
42
50
|
cwd: string;
|
|
43
51
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createFrontendClient, type FrontendPermission, type VscodeFrontendConnection } from './client.js';
|
|
2
2
|
export { readCredentialFile } from './credential.js';
|
|
3
|
-
export type {
|
|
3
|
+
export type { ChatSetupPhase, ChatSetupOperation, ChatSetupProvider, ChatSetupPresentation } from './chat-setup.js';
|
|
4
|
+
export type { ChatSetupAgent } from './host-controls.js';
|
|
4
5
|
export { grantedProposals, PROPOSAL_EFFECTS, REQUESTED_PROPOSALS, type ProposalName, } from './proposals.js';
|
|
5
6
|
export { EventSequenceGapError, LEASE_ERROR_NAMES, RequestNotPendingError, TURN_TERMINAL_KINDS, VscodeFrontendSession, type VscodeFrontendActions, type VscodeRuntimeClient, } from './session.js';
|
|
6
7
|
export { HISTORY_MARKER_PROMPT, VSCODE_CHAT_LIMITS, acceptedDecisions, diffPaths, fence, opaqueText, projectEvent, projectPendingRequests, projectHistory, projectReplayTurns, requestAnswers, requestNeedsTypedReply, requestSubject, requestSummary, resolutionSentence, resolveParts, toolSubject, typedAnswer, type ChatAnswer, type ChatPart, type ProjectedPart, type ProjectedTurn, type ProjectionContext, } from './view.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { HostState } from './host-controls.js';
|
|
2
|
+
import { type ChatSetupOperation } from './chat-setup.js';
|
|
3
|
+
export interface SetupProcess {
|
|
4
|
+
exited: Promise<number | undefined>;
|
|
5
|
+
cancel(): void;
|
|
6
|
+
}
|
|
7
|
+
export interface SetupFlowHost {
|
|
8
|
+
state(): HostState;
|
|
9
|
+
refresh(): Promise<void>;
|
|
10
|
+
launch(kind: 'login' | 'install', harness: string, current: () => boolean): Promise<SetupProcess>;
|
|
11
|
+
changed(): void;
|
|
12
|
+
log(message: string): void;
|
|
13
|
+
}
|
|
14
|
+
export declare class ChatSetupFlow {
|
|
15
|
+
private readonly host;
|
|
16
|
+
readonly operations: Map<string, ChatSetupOperation>;
|
|
17
|
+
private processes;
|
|
18
|
+
private disposed;
|
|
19
|
+
notice: string | undefined;
|
|
20
|
+
constructor(host: SetupFlowHost);
|
|
21
|
+
begin(harness: string): ChatSetupOperation;
|
|
22
|
+
cancel(harness: string, id: string): boolean;
|
|
23
|
+
dispose(): void;
|
|
24
|
+
private current;
|
|
25
|
+
private update;
|
|
26
|
+
private refresh;
|
|
27
|
+
private run;
|
|
28
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/** One user-requested install/sign-in operation per agent. No credentials cross this boundary. */
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { setupAgents } from './chat-setup.js';
|
|
4
|
+
export class ChatSetupFlow {
|
|
5
|
+
host;
|
|
6
|
+
operations = new Map();
|
|
7
|
+
processes = new Map();
|
|
8
|
+
disposed = false;
|
|
9
|
+
notice;
|
|
10
|
+
constructor(host) {
|
|
11
|
+
this.host = host;
|
|
12
|
+
}
|
|
13
|
+
begin(harness) {
|
|
14
|
+
if (this.disposed)
|
|
15
|
+
throw new Error('Chat setup has closed.');
|
|
16
|
+
const existing = this.operations.get(harness);
|
|
17
|
+
if (existing?.phase === 'installing' || existing?.phase === 'signing-in')
|
|
18
|
+
return { ...existing };
|
|
19
|
+
const agent = setupAgents(this.host.state()).find(a => a.harness === harness);
|
|
20
|
+
if (!agent)
|
|
21
|
+
throw new Error('That coding agent is unavailable.');
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const operation = { id: randomUUID(), harness, phase: agent.signedIn ? 'signed-in' : agent.installed ? 'signing-in' : 'installing', startedAt: now, updatedAt: now };
|
|
24
|
+
this.operations.set(harness, operation);
|
|
25
|
+
this.host.changed();
|
|
26
|
+
if (!agent.signedIn)
|
|
27
|
+
void this.run(operation);
|
|
28
|
+
return { ...operation };
|
|
29
|
+
}
|
|
30
|
+
cancel(harness, id) {
|
|
31
|
+
const operation = this.operations.get(harness);
|
|
32
|
+
if (!operation || operation.id !== id || !['installing', 'signing-in'].includes(operation.phase))
|
|
33
|
+
return false;
|
|
34
|
+
// Retire ownership before disposing the terminal: its close callback is not a failed sign-in.
|
|
35
|
+
this.operations.delete(harness);
|
|
36
|
+
this.processes.get(id)?.cancel();
|
|
37
|
+
this.processes.delete(id);
|
|
38
|
+
this.host.changed();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
dispose() {
|
|
42
|
+
this.disposed = true;
|
|
43
|
+
for (const operation of this.operations.values())
|
|
44
|
+
this.processes.get(operation.id)?.cancel();
|
|
45
|
+
this.processes.clear();
|
|
46
|
+
}
|
|
47
|
+
current(operation) {
|
|
48
|
+
return !this.disposed && this.operations.get(operation.harness)?.id === operation.id;
|
|
49
|
+
}
|
|
50
|
+
update(operation, phase) {
|
|
51
|
+
if (!this.current(operation))
|
|
52
|
+
return;
|
|
53
|
+
this.operations.set(operation.harness, { ...operation, phase, updatedAt: Date.now() });
|
|
54
|
+
this.host.changed();
|
|
55
|
+
}
|
|
56
|
+
async refresh(operation) {
|
|
57
|
+
await this.host.refresh();
|
|
58
|
+
if (!this.current(operation))
|
|
59
|
+
throw new Error('Sign-in cancelled.');
|
|
60
|
+
}
|
|
61
|
+
async run(operation) {
|
|
62
|
+
const current = () => this.current(operation);
|
|
63
|
+
let process;
|
|
64
|
+
try {
|
|
65
|
+
await this.refresh(operation);
|
|
66
|
+
let agent = setupAgents(this.host.state()).find(a => a.harness === operation.harness);
|
|
67
|
+
if (!agent?.installed) {
|
|
68
|
+
this.update(operation, 'installing');
|
|
69
|
+
process = await this.host.launch('install', operation.harness, current);
|
|
70
|
+
if (!current()) {
|
|
71
|
+
process.cancel();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.processes.set(operation.id, process);
|
|
75
|
+
const exitCode = await process.exited;
|
|
76
|
+
this.processes.delete(operation.id);
|
|
77
|
+
if (!current())
|
|
78
|
+
return;
|
|
79
|
+
if (exitCode !== 0)
|
|
80
|
+
throw new Error(`Installation ended ${exitCode === undefined ? 'without an exit status' : `with exit code ${exitCode}`}.`);
|
|
81
|
+
await this.refresh(operation);
|
|
82
|
+
agent = setupAgents(this.host.state()).find(a => a.harness === operation.harness);
|
|
83
|
+
if (!agent?.installed)
|
|
84
|
+
throw new Error('Installation completed, but the coding agent is not available to this editor.');
|
|
85
|
+
}
|
|
86
|
+
if (!agent.signedIn) {
|
|
87
|
+
this.update(operation, 'signing-in');
|
|
88
|
+
process = await this.host.launch('login', operation.harness, current);
|
|
89
|
+
if (!current()) {
|
|
90
|
+
process.cancel();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.processes.set(operation.id, process);
|
|
94
|
+
let ended = false;
|
|
95
|
+
let exitCode;
|
|
96
|
+
void process.exited.then(code => { ended = true; exitCode = code; });
|
|
97
|
+
// Readiness comes only from the host's native inventory. A successful
|
|
98
|
+
// process exit, a browser opening, and a protocol handshake are not login.
|
|
99
|
+
while (current()) {
|
|
100
|
+
await this.refresh(operation);
|
|
101
|
+
if (setupAgents(this.host.state()).some(a => a.harness === operation.harness && a.signedIn))
|
|
102
|
+
break;
|
|
103
|
+
if (ended)
|
|
104
|
+
throw new Error(`Sign-in ended ${exitCode === undefined ? 'without an exit status' : `with exit code ${exitCode}`}, without a signed-in account.`);
|
|
105
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!current())
|
|
109
|
+
return;
|
|
110
|
+
this.notice = operation.harness === 'codex' ? 'ChatGPT' : operation.harness === 'claude-code' ? 'Claude' : undefined;
|
|
111
|
+
this.update(operation, 'signed-in');
|
|
112
|
+
// Retire only the terminal this click launched. Its native inventory has
|
|
113
|
+
// already confirmed login; closing it never supplies that confirmation.
|
|
114
|
+
process?.cancel();
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (current()) {
|
|
118
|
+
this.host.log(`Could not sign in with ${operation.harness}: ${String(error)}`);
|
|
119
|
+
this.update(operation, 'failed');
|
|
120
|
+
process?.cancel();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
this.processes.delete(operation.id);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-frontend-vscode",
|
|
3
3
|
"displayName": "Volter Harness",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.51",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "VS Code chat-view frontend for an existing Volter Harness SDK runtime",
|
|
7
7
|
"publisher": "volter-ai-dev",
|