@volter/supercode-frontend-vscode 0.1.11
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/NOTICE.md +19 -0
- package/README.md +293 -0
- package/dist/bindings.d.ts +7 -0
- package/dist/bindings.js +5 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +5 -0
- package/dist/context.d.ts +6 -0
- package/dist/context.js +14 -0
- package/dist/credential.d.ts +17 -0
- package/dist/credential.js +53 -0
- package/dist/extension.d.ts +39 -0
- package/dist/extension.js +1131 -0
- package/dist/host-controls.d.ts +58 -0
- package/dist/host-controls.js +66 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/native-session.d.ts +6 -0
- package/dist/native-session.js +41 -0
- package/dist/proposals.d.ts +24 -0
- package/dist/proposals.js +54 -0
- package/dist/session.d.ts +154 -0
- package/dist/session.js +431 -0
- package/dist/view.d.ts +176 -0
- package/dist/view.js +717 -0
- package/media/anthropic.svg +1 -0
- package/media/google.svg +1 -0
- package/media/icon.png +0 -0
- package/media/openai.svg +1 -0
- package/package.json +200 -0
- package/scripts/pack-extension.mjs +63 -0
- package/vendor/supercode-frontend/README.md +22 -0
- package/vendor/supercode-frontend/client.d.mts +106 -0
- package/vendor/supercode-frontend/client.mjs +300 -0
- package/vendor/supercode-frontend/contract.json +289 -0
- package/vendor/supercode-frontend/generated-client.d.mts +18 -0
- package/vendor/supercode-frontend/generated-client.mjs +20 -0
- package/vendor/supercode-frontend/generated.d.mts +146 -0
- package/vendor/supercode-frontend/generated.mjs +19 -0
- package/vendor/supercode-frontend/package.json +20 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** Optional lifecycle host. The host owns launches; frontend.v2 owns every conversation action. */
|
|
2
|
+
import type { ChatMessage } from '../vendor/supercode-frontend/generated.mjs';
|
|
3
|
+
export interface ChatSelection {
|
|
4
|
+
harness: string;
|
|
5
|
+
model: string;
|
|
6
|
+
effort: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ChatChoice {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ManagedChatSession {
|
|
14
|
+
id: string;
|
|
15
|
+
selection: ChatSelection;
|
|
16
|
+
identity: string | null;
|
|
17
|
+
title: string;
|
|
18
|
+
created: number;
|
|
19
|
+
}
|
|
20
|
+
export interface HostState {
|
|
21
|
+
history?: ChatMessage[];
|
|
22
|
+
historyTruncated?: boolean;
|
|
23
|
+
activeSession: string | null;
|
|
24
|
+
openSessionCommand?: string;
|
|
25
|
+
sessions: ManagedChatSession[];
|
|
26
|
+
selection: ChatSelection;
|
|
27
|
+
connection?: Record<string, string>;
|
|
28
|
+
harnesses: ChatChoice[];
|
|
29
|
+
models: ChatChoice[];
|
|
30
|
+
modelsByHarness?: Record<string, ChatChoice[]>;
|
|
31
|
+
busy: boolean;
|
|
32
|
+
actualModel?: string | null;
|
|
33
|
+
configurable: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface HostSelectionResult extends HostState {
|
|
36
|
+
connection: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
export declare class ChatHostControls {
|
|
39
|
+
readonly baseUrl: string;
|
|
40
|
+
private readonly credential;
|
|
41
|
+
state: HostState;
|
|
42
|
+
private constructor();
|
|
43
|
+
static fromEnvironment(env?: NodeJS.ProcessEnv): Promise<ChatHostControls | undefined>;
|
|
44
|
+
refresh(): Promise<void>;
|
|
45
|
+
select(selection: ChatSelection): Promise<HostSelectionResult>;
|
|
46
|
+
remember(id: string, nativeId: string): Promise<void>;
|
|
47
|
+
open(id: string): Promise<HostSelectionResult>;
|
|
48
|
+
private request;
|
|
49
|
+
}
|
|
50
|
+
/** Approval policy is captured for one turn, never inferred from connection privileges. */
|
|
51
|
+
export declare function turnPermission(value: string | undefined): 'ask' | 'autoApprove';
|
|
52
|
+
export declare function harnessDisplayName(harness: string | null | undefined): string;
|
|
53
|
+
export declare function runtimeIdentity(harness: string | null | undefined, model: string | null | undefined): {
|
|
54
|
+
name: string;
|
|
55
|
+
provider: string | undefined;
|
|
56
|
+
};
|
|
57
|
+
/** Native pickers need a nonempty id; the host uses an empty model for its default. */
|
|
58
|
+
export declare function modelSelection(value: string): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readCredentialFile } from './credential.js';
|
|
2
|
+
export class ChatHostControls {
|
|
3
|
+
baseUrl;
|
|
4
|
+
credential;
|
|
5
|
+
state;
|
|
6
|
+
constructor(baseUrl, credential, state) {
|
|
7
|
+
this.baseUrl = baseUrl;
|
|
8
|
+
this.credential = credential;
|
|
9
|
+
this.state = state;
|
|
10
|
+
}
|
|
11
|
+
static async fromEnvironment(env = process.env) {
|
|
12
|
+
if (!env.SUPERCODE_FRONTEND_HOST_URL || !env.SUPERCODE_FRONTEND_HOST_CREDENTIAL_FILE)
|
|
13
|
+
return;
|
|
14
|
+
const url = new URL(env.SUPERCODE_FRONTEND_HOST_URL);
|
|
15
|
+
if (url.protocol !== 'http:' || url.hostname !== '127.0.0.1' || url.username || url.password || url.pathname !== '/')
|
|
16
|
+
throw new Error('Chat lifecycle host must be a loopback HTTP endpoint.');
|
|
17
|
+
const credential = await readCredentialFile(env.SUPERCODE_FRONTEND_HOST_CREDENTIAL_FILE);
|
|
18
|
+
const host = new ChatHostControls(url.origin, credential, undefined);
|
|
19
|
+
host.state = await host.request('/state');
|
|
20
|
+
return host;
|
|
21
|
+
}
|
|
22
|
+
async refresh() { this.state = await this.request('/state'); }
|
|
23
|
+
async select(selection) {
|
|
24
|
+
const result = await this.request('/select', selection);
|
|
25
|
+
this.state = result;
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
async remember(id, nativeId) { this.state = await this.request('/remember', { id, nativeId }); }
|
|
29
|
+
async open(id) {
|
|
30
|
+
const result = await this.request('/open', { id });
|
|
31
|
+
this.state = result;
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
async request(path, selection) {
|
|
35
|
+
const response = await fetch(this.baseUrl + path, {
|
|
36
|
+
method: selection ? 'POST' : 'GET', headers: { Authorization: `Bearer ${this.credential}`, 'Content-Type': 'application/json' },
|
|
37
|
+
...(selection ? { body: JSON.stringify(selection) } : {}), signal: AbortSignal.timeout(180_000), redirect: 'error',
|
|
38
|
+
});
|
|
39
|
+
const value = await response.json();
|
|
40
|
+
if (!response.ok)
|
|
41
|
+
throw new Error(value.error || `Chat host returned ${response.status}`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Approval policy is captured for one turn, never inferred from connection privileges. */
|
|
46
|
+
export function turnPermission(value) {
|
|
47
|
+
if (!value || value === 'default' || value === 'ask')
|
|
48
|
+
return 'ask';
|
|
49
|
+
if (value === 'autoApprove')
|
|
50
|
+
return 'autoApprove';
|
|
51
|
+
throw new Error(`The selected permission mode (${value}) is not supported by this runtime frontend.`);
|
|
52
|
+
}
|
|
53
|
+
export function harnessDisplayName(harness) {
|
|
54
|
+
return { 'claude-code': 'Claude Code', codex: 'Codex', gemini: 'Gemini CLI', opencode: 'OpenCode', pi: 'Pi', goose: 'Goose' }[harness || ''] || harness || 'Assistant';
|
|
55
|
+
}
|
|
56
|
+
export function runtimeIdentity(harness, model) {
|
|
57
|
+
const harnessName = harnessDisplayName(harness);
|
|
58
|
+
const knownModel = model && model !== harness ? model : '';
|
|
59
|
+
const modelName = { sonnet: 'Claude Sonnet', opus: 'Claude Opus', haiku: 'Claude Haiku' }[knownModel] || knownModel;
|
|
60
|
+
const provider = /claude|sonnet|opus|haiku/i.test(modelName) || (!modelName && harness === 'claude-code') ? 'anthropic'
|
|
61
|
+
: /^(gpt|o[134]|chatgpt)|\/gpt/i.test(modelName) || (!modelName && harness === 'codex') ? 'openai'
|
|
62
|
+
: /gemini/i.test(modelName) || (!modelName && harness === 'gemini') ? 'google' : undefined;
|
|
63
|
+
return { name: modelName ? `${modelName} · ${harnessName}` : `${harnessName} · default model`, provider };
|
|
64
|
+
}
|
|
65
|
+
/** Native pickers need a nonempty id; the host uses an empty model for its default. */
|
|
66
|
+
export function modelSelection(value) { return value === "__default__" ? "" : value; }
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createFrontendClient, type FrontendPermission, type VscodeFrontendConnection } from './client.js';
|
|
2
|
+
export { readCredentialFile } from './credential.js';
|
|
3
|
+
export { grantedProposals, PROPOSAL_EFFECTS, REQUESTED_PROPOSALS, type ProposalName, } from './proposals.js';
|
|
4
|
+
export { EventSequenceGapError, LEASE_ERROR_NAMES, RequestNotPendingError, TURN_TERMINAL_KINDS, VscodeFrontendSession, type VscodeFrontendActions, type VscodeRuntimeClient, } from './session.js';
|
|
5
|
+
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';
|
|
6
|
+
export type { SupercodeConnectOptions, SupercodeConnectionInfo, SupercodeFrontendApi, } from './extension.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createFrontendClient } from './client.js';
|
|
2
|
+
export { readCredentialFile } from './credential.js';
|
|
3
|
+
export { grantedProposals, PROPOSAL_EFFECTS, REQUESTED_PROPOSALS, } from './proposals.js';
|
|
4
|
+
export { EventSequenceGapError, LEASE_ERROR_NAMES, RequestNotPendingError, TURN_TERMINAL_KINDS, VscodeFrontendSession, } from './session.js';
|
|
5
|
+
export { HISTORY_MARKER_PROMPT, VSCODE_CHAT_LIMITS, acceptedDecisions, diffPaths, fence, opaqueText, projectEvent, projectPendingRequests, projectHistory, projectReplayTurns, requestAnswers, requestNeedsTypedReply, requestSubject, requestSummary, resolutionSentence, resolveParts, toolSubject, typedAnswer, } from './view.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CancellationToken } from 'vscode';
|
|
2
|
+
import type { FrontendEvent } from '../vendor/supercode-frontend/generated.mjs';
|
|
3
|
+
import { type VscodeFrontendSession } from './session.js';
|
|
4
|
+
export declare function sessionStatus(session: Pick<VscodeFrontendSession, 'descriptor' | 'pendingRequests'>): 'NeedsInput' | 'InProgress' | 'Completed';
|
|
5
|
+
/** Follow a restored turn, including the interval between snapshot and callback. */
|
|
6
|
+
export declare function observeActiveResponse(session: Pick<VscodeFrontendSession, 'descriptor' | 'onEvent' | 'interrupt'>, token: CancellationToken, write: (event: FrontendEvent) => void): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TURN_TERMINAL_KINDS } from './session.js';
|
|
2
|
+
export function sessionStatus(session) {
|
|
3
|
+
return session.pendingRequests.size ? 'NeedsInput' : session.descriptor?.turn_state === 'busy' ? 'InProgress' : 'Completed';
|
|
4
|
+
}
|
|
5
|
+
/** Follow a restored turn, including the interval between snapshot and callback. */
|
|
6
|
+
export async function observeActiveResponse(session, token, write) {
|
|
7
|
+
let unsubscribe = () => { };
|
|
8
|
+
let cancellation;
|
|
9
|
+
try {
|
|
10
|
+
await new Promise((resolve, reject) => {
|
|
11
|
+
let cancelling = false;
|
|
12
|
+
const cancel = () => {
|
|
13
|
+
if (cancelling)
|
|
14
|
+
return;
|
|
15
|
+
cancelling = true;
|
|
16
|
+
// Wait for the runtime acknowledgement; a stopped UI is not a stopped harness.
|
|
17
|
+
void session.interrupt().then(resolve, reject);
|
|
18
|
+
};
|
|
19
|
+
unsubscribe = session.onEvent(event => {
|
|
20
|
+
try {
|
|
21
|
+
write(event);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
reject(error);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!cancelling && TURN_TERMINAL_KINDS.includes(event.kind))
|
|
28
|
+
resolve();
|
|
29
|
+
});
|
|
30
|
+
cancellation = token.onCancellationRequested(cancel);
|
|
31
|
+
if (token.isCancellationRequested)
|
|
32
|
+
cancel();
|
|
33
|
+
else if (session.descriptor?.turn_state !== 'busy')
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
unsubscribe();
|
|
39
|
+
cancellation?.dispose();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The complete VS Code API-proposal surface this frontend asks for, and what it
|
|
3
|
+
* renders when a host grants none of it. A host grants proposals through
|
|
4
|
+
* `product.json#extensionEnabledApiProposals` (a Code-OSS derivative that
|
|
5
|
+
* bundles this extension) or `--enable-proposed-api <extension id>`; stock
|
|
6
|
+
* VS Code grants nothing, and this package must stay useful there.
|
|
7
|
+
*
|
|
8
|
+
* The granted set is read back from the extension host at runtime rather than
|
|
9
|
+
* from this package's own manifest: `IExtensionDescription.enabledApiProposals`
|
|
10
|
+
* is FILTERED to what the host actually allowed (the request in `package.json`
|
|
11
|
+
* is nulled when it is not), and `vscode.extensions.getExtension(id).packageJSON`
|
|
12
|
+
* is that description. Class constructors on the `vscode` namespace are exposed
|
|
13
|
+
* unconditionally, so a `typeof vscode.ChatResponseConfirmationPart` probe
|
|
14
|
+
* cannot answer this question — only the description can.
|
|
15
|
+
*/
|
|
16
|
+
export declare const REQUESTED_PROPOSALS: readonly ["defaultChatParticipant", "chatParticipantPrivate", "chatParticipantAdditions", "chatSessionsProvider", "chatProvider"];
|
|
17
|
+
export type ProposalName = (typeof REQUESTED_PROPOSALS)[number];
|
|
18
|
+
/** What each requested proposal buys, and what this frontend does without it. */
|
|
19
|
+
export declare const PROPOSAL_EFFECTS: Readonly<Record<ProposalName, {
|
|
20
|
+
grants: string;
|
|
21
|
+
without: string;
|
|
22
|
+
}>>;
|
|
23
|
+
/** Read the proposals the HOST granted this extension, never the ones it asked for. */
|
|
24
|
+
export declare function grantedProposals(packageJSON: unknown): ReadonlySet<ProposalName>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The complete VS Code API-proposal surface this frontend asks for, and what it
|
|
3
|
+
* renders when a host grants none of it. A host grants proposals through
|
|
4
|
+
* `product.json#extensionEnabledApiProposals` (a Code-OSS derivative that
|
|
5
|
+
* bundles this extension) or `--enable-proposed-api <extension id>`; stock
|
|
6
|
+
* VS Code grants nothing, and this package must stay useful there.
|
|
7
|
+
*
|
|
8
|
+
* The granted set is read back from the extension host at runtime rather than
|
|
9
|
+
* from this package's own manifest: `IExtensionDescription.enabledApiProposals`
|
|
10
|
+
* is FILTERED to what the host actually allowed (the request in `package.json`
|
|
11
|
+
* is nulled when it is not), and `vscode.extensions.getExtension(id).packageJSON`
|
|
12
|
+
* is that description. Class constructors on the `vscode` namespace are exposed
|
|
13
|
+
* unconditionally, so a `typeof vscode.ChatResponseConfirmationPart` probe
|
|
14
|
+
* cannot answer this question — only the description can.
|
|
15
|
+
*/
|
|
16
|
+
export const REQUESTED_PROPOSALS = Object.freeze([
|
|
17
|
+
'defaultChatParticipant',
|
|
18
|
+
'chatParticipantPrivate',
|
|
19
|
+
'chatParticipantAdditions',
|
|
20
|
+
'chatSessionsProvider',
|
|
21
|
+
'chatProvider',
|
|
22
|
+
]);
|
|
23
|
+
/** What each requested proposal buys, and what this frontend does without it. */
|
|
24
|
+
export const PROPOSAL_EFFECTS = Object.freeze({
|
|
25
|
+
defaultChatParticipant: {
|
|
26
|
+
grants: 'the `isDefault` participant contribution — supercode answers the Chat view without an `@` mention',
|
|
27
|
+
without: 'the ordinary `@supercode` participant answers; a mention is required',
|
|
28
|
+
},
|
|
29
|
+
chatParticipantPrivate: {
|
|
30
|
+
grants: 'the dynamic-participant and participant-detection APIs a bundling host can use to route without a mention',
|
|
31
|
+
without: 'nothing is lost in this package: it registers no dynamic participant and no detection provider',
|
|
32
|
+
},
|
|
33
|
+
chatParticipantAdditions: {
|
|
34
|
+
grants: 'tool-invocation, confirmation, warning and codeblock-uri response parts',
|
|
35
|
+
without: 'progress lines, collapsed markdown results, typed replies and plain fenced diffs',
|
|
36
|
+
},
|
|
37
|
+
chatProvider: { grants: 'native session model picker and model configuration', without: 'provider option model picker' },
|
|
38
|
+
chatSessionsProvider: {
|
|
39
|
+
grants: 'the runtime’s sessions in the Chat view’s session picker, each reopening with its history',
|
|
40
|
+
without: 'the participant still attaches; there is no session list',
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
/** Read the proposals the HOST granted this extension, never the ones it asked for. */
|
|
44
|
+
export function grantedProposals(packageJSON) {
|
|
45
|
+
const declared = packageJSON?.enabledApiProposals;
|
|
46
|
+
if (!Array.isArray(declared))
|
|
47
|
+
return new Set();
|
|
48
|
+
const granted = new Set();
|
|
49
|
+
for (const name of declared) {
|
|
50
|
+
if (REQUESTED_PROPOSALS.includes(String(name)))
|
|
51
|
+
granted.add(name);
|
|
52
|
+
}
|
|
53
|
+
return granted;
|
|
54
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { ChatMessage, FrontendAttachSnapshot, FrontendEvent, FrontendRequest, FrontendResponse, FrontendRuntimeDescriptor, RuntimeLeaseSnapshot } from '../vendor/supercode-frontend/generated.mjs';
|
|
2
|
+
import type { FrontendPermission } from './client.js';
|
|
3
|
+
/**
|
|
4
|
+
* The subset of the generated client this presentation package uses. Nothing
|
|
5
|
+
* here is a second protocol: every member is `@volter/supercode-frontend`'s.
|
|
6
|
+
*/
|
|
7
|
+
export interface VscodeRuntimeClient {
|
|
8
|
+
describe(): Promise<FrontendRuntimeDescriptor>;
|
|
9
|
+
attach(limit?: number, options?: {
|
|
10
|
+
afterSequence?: number;
|
|
11
|
+
}): Promise<FrontendAttachSnapshot>;
|
|
12
|
+
sendInput(prompt: string): Promise<{
|
|
13
|
+
accepted: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
steer(prompt: string): Promise<{
|
|
16
|
+
queued: boolean;
|
|
17
|
+
}>;
|
|
18
|
+
interrupt(): Promise<{
|
|
19
|
+
interrupted: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
respond(response: FrontendResponse): Promise<{
|
|
22
|
+
accepted: boolean;
|
|
23
|
+
}>;
|
|
24
|
+
detach(): Promise<unknown>;
|
|
25
|
+
close(): Promise<{
|
|
26
|
+
closed: boolean;
|
|
27
|
+
}>;
|
|
28
|
+
takeControl(): Promise<RuntimeLeaseSnapshot>;
|
|
29
|
+
heartbeat(): Promise<RuntimeLeaseSnapshot>;
|
|
30
|
+
on(event: 'event', listener: (event: FrontendEvent) => void): unknown;
|
|
31
|
+
on(event: 'disconnect', listener: () => void): unknown;
|
|
32
|
+
off(event: 'event', listener: (event: FrontendEvent) => void): unknown;
|
|
33
|
+
off(event: 'disconnect', listener: () => void): unknown;
|
|
34
|
+
startEvents(options?: {
|
|
35
|
+
afterSequence?: number;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
stopEvents(): void;
|
|
38
|
+
}
|
|
39
|
+
export interface VscodeFrontendActions {
|
|
40
|
+
submit: boolean;
|
|
41
|
+
steer: boolean;
|
|
42
|
+
interrupt: boolean;
|
|
43
|
+
respond: boolean;
|
|
44
|
+
detach: boolean;
|
|
45
|
+
close: boolean;
|
|
46
|
+
takeover: boolean;
|
|
47
|
+
}
|
|
48
|
+
/** The request this attachment tried to answer is no longer open. */
|
|
49
|
+
export declare class RequestNotPendingError extends Error {
|
|
50
|
+
readonly requestId: number;
|
|
51
|
+
constructor(requestId: number);
|
|
52
|
+
}
|
|
53
|
+
export declare class EventSequenceGapError extends Error {
|
|
54
|
+
constructor(expected: number, actual: number);
|
|
55
|
+
}
|
|
56
|
+
/** The lifecycle kinds that end one submitted turn, from `submit_claimed`. */
|
|
57
|
+
export declare const TURN_TERMINAL_KINDS: readonly ["turn_succeeded", "turn_failed", "turn_interrupted"];
|
|
58
|
+
/**
|
|
59
|
+
* The runtime hands ONE controller a lease and expires it
|
|
60
|
+
* (`crates/harness/src/runtime_lease.rs`); an interactive attachment must hold
|
|
61
|
+
* it, so these two error names are a take-control-and-retry, never a failure
|
|
62
|
+
* the view reports.
|
|
63
|
+
*/
|
|
64
|
+
export declare const LEASE_ERROR_NAMES: readonly string[];
|
|
65
|
+
/**
|
|
66
|
+
* One attachment to one already-running Volter Harness SDK runtime: cursor-safe
|
|
67
|
+
* event projection, permission narrowing, and the semantic actions the runtime
|
|
68
|
+
* advertises. It owns no agent, no model, no tool and no session file.
|
|
69
|
+
*/
|
|
70
|
+
export declare class VscodeFrontendSession {
|
|
71
|
+
#private;
|
|
72
|
+
readonly id: string;
|
|
73
|
+
readonly baseUrl: string;
|
|
74
|
+
readonly client: VscodeRuntimeClient;
|
|
75
|
+
readonly permissions: ReadonlySet<FrontendPermission>;
|
|
76
|
+
descriptor: FrontendRuntimeDescriptor | null;
|
|
77
|
+
history: ChatMessage[];
|
|
78
|
+
historyCursor: number;
|
|
79
|
+
cursor: number;
|
|
80
|
+
/**
|
|
81
|
+
* Every event this attachment has seen, in order — NOT just the last
|
|
82
|
+
* snapshot's. A re-attach asks `afterSequence: cursor`, so its `replay` is
|
|
83
|
+
* empty by design; overwriting with it threw away the conversation a hosted
|
|
84
|
+
* harness keeps there (`HostedHarnessRuntime::attach` returns no canonical
|
|
85
|
+
* history at all). Bounded, because a long session is a lot of events.
|
|
86
|
+
*/
|
|
87
|
+
replay: FrontendEvent[];
|
|
88
|
+
pendingRequests: Map<number, FrontendRequest>;
|
|
89
|
+
/**
|
|
90
|
+
* Requests this attachment has seen resolved. `request_resolved` deletes the
|
|
91
|
+
* pending entry while it is being projected, so without this the resolution
|
|
92
|
+
* could only say "request #1" — it would have lost the very thing it needs to
|
|
93
|
+
* name. Bounded, because a long session resolves a lot of them.
|
|
94
|
+
*/
|
|
95
|
+
resolvedRequests: Map<number, FrontendRequest>;
|
|
96
|
+
connection: 'disconnected' | 'connected' | 'closed';
|
|
97
|
+
/**
|
|
98
|
+
* `frontend.v2`'s descriptor and history carry NO timestamp, so the only
|
|
99
|
+
* true times this frontend knows are its own: when this window attached, and
|
|
100
|
+
* the turn boundaries it watched go by. The editor's session list always
|
|
101
|
+
* reads `timing.created` and renders 1970 when it is absent, so these are
|
|
102
|
+
* what it gets — never a zero dressed up as a date.
|
|
103
|
+
*/
|
|
104
|
+
attachedAtMs: number | null;
|
|
105
|
+
lastTurnStartedMs: number | null;
|
|
106
|
+
lastTurnEndedMs: number | null;
|
|
107
|
+
constructor(id: string, baseUrl: string, client: VscodeRuntimeClient, permissions?: FrontendPermission[]);
|
|
108
|
+
/**
|
|
109
|
+
* Take the attachment snapshot. The generated client opens the SSE stream
|
|
110
|
+
* BEFORE the snapshot RPC, so no event can fall between the two.
|
|
111
|
+
*/
|
|
112
|
+
attach(limit?: number): Promise<FrontendAttachSnapshot>;
|
|
113
|
+
/** Start consuming the already-open live stream. Resolves when it ends. */
|
|
114
|
+
follow(): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Hold the controller lease for as long as this attachment lasts. Observers
|
|
117
|
+
* never call this; only a permission that can submit needs the lease.
|
|
118
|
+
*/
|
|
119
|
+
takeControlAndHold(): Promise<void>;
|
|
120
|
+
private startHeartbeat;
|
|
121
|
+
private stopHeartbeat;
|
|
122
|
+
dispose(): void;
|
|
123
|
+
/** Keep the transcript in order and free of the duplicates a re-attach brings. */
|
|
124
|
+
private appendTranscript;
|
|
125
|
+
applyEvent(event: FrontendEvent): boolean;
|
|
126
|
+
/** Re-take the attachment snapshot after a sequence gap, once at a time. */
|
|
127
|
+
private resync;
|
|
128
|
+
/** Subscribe to every event this session projects, from now on. */
|
|
129
|
+
onEvent(listener: (event: FrontendEvent) => void): () => void;
|
|
130
|
+
onChange(observer: () => void): () => void;
|
|
131
|
+
private projectEvent;
|
|
132
|
+
/** What this attachment may do: permission AND the descriptor's own advertisement. */
|
|
133
|
+
actions(): VscodeFrontendActions;
|
|
134
|
+
/** Run one controller action, re-taking an expired lease exactly once. */
|
|
135
|
+
private withControl;
|
|
136
|
+
submit(prompt: string): Promise<void>;
|
|
137
|
+
steer(prompt: string): Promise<void>;
|
|
138
|
+
interrupt(): Promise<void>;
|
|
139
|
+
respond(response: FrontendResponse): Promise<void>;
|
|
140
|
+
detach(): Promise<void>;
|
|
141
|
+
close(): Promise<void>;
|
|
142
|
+
takeover(): Promise<void>;
|
|
143
|
+
heartbeat(): Promise<RuntimeLeaseSnapshot | null>;
|
|
144
|
+
/**
|
|
145
|
+
* Submit one prompt and feed every event of the resulting turn to `onEvent`,
|
|
146
|
+
* resolving when the runtime reports the turn finished. The subscription is
|
|
147
|
+
* registered BEFORE the prompt is sent, so no early delta is lost.
|
|
148
|
+
*/
|
|
149
|
+
runTurn(prompt: string, onEvent: (event: FrontendEvent) => void, options?: {
|
|
150
|
+
onCancel?: (cancel: () => void) => void;
|
|
151
|
+
}): Promise<FrontendEvent | null>;
|
|
152
|
+
private require;
|
|
153
|
+
private notify;
|
|
154
|
+
}
|