@volter/supercode-frontend-vscode 0.1.46 → 0.1.48

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/dist/extension.js CHANGED
@@ -994,6 +994,22 @@ export async function activate(context) {
994
994
  watchApprovals: (connection, stream, token) => watchApprovals(connection, stream, token, () => approvalFor(connection.id)),
995
995
  };
996
996
  context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.activateSession', async (resource) => { await connectionFor(vscode.Uri.parse(resource)); }));
997
+ // A background task the chat named (sayTasks in runTurnInto), stopped through the harness's own control.
998
+ context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.stopBackgroundTask', async (connectionId, taskId) => {
999
+ const connection = frontends.get(connectionId);
1000
+ const task = connection?.session.backgroundTasks.find(candidate => candidate.id === taskId);
1001
+ if (!connection || !task) {
1002
+ void vscode.window.showInformationMessage('That background task is no longer running.');
1003
+ return;
1004
+ }
1005
+ try {
1006
+ const stopped = await connection.session.stopTask(taskId);
1007
+ void vscode.window.showInformationMessage(stopped ? `Stopped ${task.description || taskId}.` : `The harness did not stop ${task.description || taskId}.`);
1008
+ }
1009
+ catch (error) {
1010
+ void vscode.window.showErrorMessage(`Could not stop ${task.description || taskId}: ${String(error?.message ?? error)}`);
1011
+ }
1012
+ }));
997
1013
  // The host's native focus event is authoritative even when a cached model
998
1014
  // skips content resolution. Untitled/non-harness focus never starts a runtime.
999
1015
  context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.focusSession', (resource) => {
@@ -1210,6 +1226,27 @@ export async function activate(context) {
1210
1226
  });
1211
1227
  const watch = watchApprovals(connection, stream, token, approval);
1212
1228
  state.quiet = watch.answers;
1229
+ // The harness's own background tasks (a Monitor, a background shell): each event one reports is a turn of its own,
1230
+ // which this message waits behind, and nothing here would show it. Said once per change of the set, each with a
1231
+ // way to stop it.
1232
+ let saidTasks = '';
1233
+ const sayTasks = () => {
1234
+ const tasks = connection.session.backgroundTasks;
1235
+ const key = tasks.map(task => task.id).join(',');
1236
+ if (key === saidTasks)
1237
+ return;
1238
+ saidTasks = key;
1239
+ if (!tasks.length) {
1240
+ stream.markdown('\n\n_No background task is running any more._\n\n');
1241
+ return;
1242
+ }
1243
+ stream.markdown(`\n\n_${tasks.length === 1 ? 'A background task is' : `${tasks.length} background tasks are`} still running in this session. The harness answers each of its events as a turn of its own, so this message can wait behind them:_\n\n`);
1244
+ for (const task of tasks) {
1245
+ stream.button({ command: 'supercode.frontend.stopBackgroundTask', title: `Stop ${opaqueText(task.description || task.id, 80)}`, arguments: [connection.id, task.id] });
1246
+ }
1247
+ };
1248
+ sayTasks();
1249
+ const unwatchTasks = connection.session.onChange(sayTasks);
1213
1250
  let completed = false;
1214
1251
  try {
1215
1252
  await connection.session.runTurn(prompt, (event) => {
@@ -1219,6 +1256,7 @@ export async function activate(context) {
1219
1256
  completed = true;
1220
1257
  }
1221
1258
  finally {
1259
+ unwatchTasks();
1222
1260
  watch.dispose();
1223
1261
  cancellation.dispose();
1224
1262
  if (controls && !attachedIds.has(connection.id)) {
package/dist/session.d.ts CHANGED
@@ -15,6 +15,9 @@ export interface VscodeRuntimeClient {
15
15
  steer(prompt: string): Promise<{
16
16
  queued: boolean;
17
17
  }>;
18
+ stopTask(taskId: string): Promise<{
19
+ stopped: boolean;
20
+ }>;
18
21
  interrupt(): Promise<{
19
22
  interrupted: boolean;
20
23
  }>;
@@ -62,6 +65,12 @@ export declare const TURN_TERMINAL_KINDS: readonly ["turn_succeeded", "turn_fail
62
65
  * the view reports.
63
66
  */
64
67
  export declare const LEASE_ERROR_NAMES: readonly string[];
68
+ /** One of the harness's own background tasks (a Monitor, a background shell), as it last listed them. */
69
+ export interface BackgroundTask {
70
+ id: string;
71
+ kind: string;
72
+ description: string;
73
+ }
65
74
  /**
66
75
  * One attachment to one already-running Volter Harness SDK runtime: cursor-safe
67
76
  * event projection, permission narrowing, and the semantic actions the runtime
@@ -86,6 +95,11 @@ export declare class VscodeFrontendSession {
86
95
  */
87
96
  replay: FrontendEvent[];
88
97
  pendingRequests: Map<number, FrontendRequest>;
98
+ /**
99
+ * The harness's own background tasks as it last listed them (`background_tasks`). Each event one reports starts a
100
+ * turn of its own, so while any runs the session can be busy with turns no one here submitted.
101
+ */
102
+ backgroundTasks: BackgroundTask[];
89
103
  /**
90
104
  * Requests this attachment has seen resolved. `request_resolved` deletes the
91
105
  * pending entry while it is being projected, so without this the resolution
@@ -136,6 +150,8 @@ export declare class VscodeFrontendSession {
136
150
  submit(prompt: string): Promise<void>;
137
151
  steer(prompt: string): Promise<void>;
138
152
  interrupt(): Promise<void>;
153
+ /** Stop one of the harness's own background tasks, by the id it listed it with. */
154
+ stopTask(taskId: string): Promise<boolean>;
139
155
  respond(response: FrontendResponse): Promise<void>;
140
156
  detach(): Promise<void>;
141
157
  close(): Promise<void>;
package/dist/session.js CHANGED
@@ -1,3 +1,6 @@
1
+ /** How long a turn waits on a connection that dropped before saying it was lost (the follow loop's re-attach backs
2
+ * off to 15 s; this outlasts a few of its attempts). */
3
+ const DISCONNECT_GRACE_MS = 60_000;
1
4
  /** The request this attachment tried to answer is no longer open. */
2
5
  export class RequestNotPendingError extends Error {
3
6
  requestId;
@@ -53,6 +56,11 @@ export class VscodeFrontendSession {
53
56
  */
54
57
  replay = [];
55
58
  pendingRequests = new Map();
59
+ /**
60
+ * The harness's own background tasks as it last listed them (`background_tasks`). Each event one reports starts a
61
+ * turn of its own, so while any runs the session can be busy with turns no one here submitted.
62
+ */
63
+ backgroundTasks = [];
56
64
  /**
57
65
  * Requests this attachment has seen resolved. `request_resolved` deletes the
58
66
  * pending entry while it is being projected, so without this the resolution
@@ -102,6 +110,7 @@ export class VscodeFrontendSession {
102
110
  const staged = {
103
111
  descriptor: structuredClone(snapshot.descriptor),
104
112
  pendingRequests: new Map([...this.pendingRequests].map(([id, request]) => [id, structuredClone(request)])),
113
+ backgroundTasks: structuredClone(this.backgroundTasks),
105
114
  cursor: Math.max(this.cursor, snapshot.history_cursor),
106
115
  };
107
116
  // Validate and project the whole attachment before any observable field
@@ -126,6 +135,7 @@ export class VscodeFrontendSession {
126
135
  this.historyCursor = snapshot.history_cursor;
127
136
  this.appendTranscript(structuredClone(snapshot.replay));
128
137
  this.pendingRequests = staged.pendingRequests;
138
+ this.backgroundTasks = staged.backgroundTasks;
129
139
  this.cursor = staged.cursor;
130
140
  this.connection = 'connected';
131
141
  this.attachedAtMs ??= Date.now();
@@ -260,6 +270,11 @@ export class VscodeFrontendSession {
260
270
  if (request && typeof request.id === 'number')
261
271
  state.pendingRequests.set(request.id, structuredClone(request));
262
272
  }
273
+ if (event.kind === 'background_tasks' && Array.isArray(payload.tasks)) {
274
+ state.backgroundTasks = payload.tasks
275
+ .filter(task => task && typeof task.id === 'string')
276
+ .map(task => ({ id: task.id, kind: String(task.kind ?? 'task'), description: String(task.description ?? '') }));
277
+ }
263
278
  if (event.kind === 'request_resolved' && typeof payload.request_id === 'number') {
264
279
  const resolved = state.pendingRequests.get(payload.request_id);
265
280
  if (resolved) {
@@ -334,6 +349,11 @@ export class VscodeFrontendSession {
334
349
  this.require('interrupt');
335
350
  await this.withControl(() => this.client.interrupt());
336
351
  }
352
+ /** Stop one of the harness's own background tasks, by the id it listed it with. */
353
+ async stopTask(taskId) {
354
+ const result = await this.withControl(() => this.client.stopTask(taskId));
355
+ return result?.stopped === true;
356
+ }
337
357
  async respond(response) {
338
358
  this.require('respond');
339
359
  if (!this.pendingRequests.has(response.request_id))
@@ -402,7 +422,25 @@ export class VscodeFrontendSession {
402
422
  // that hangs one is worse than one that ends it a beat early. The
403
423
  // descriptor going idle after the submit is the second, independent signal.
404
424
  let sawBusy = false;
425
+ // A stream that ends is re-attached from its cursor (the extension's follow loop), and the replay ends the turn;
426
+ // only a connection closed, or one still down after DISCONNECT_GRACE_MS, ends the wait, said: a response held open
427
+ // with nothing in it is the silent failure.
428
+ let down;
429
+ const lost = () => fail?.(new Error('The connection to this conversation\'s runtime was lost before its turn ended; the turn may still be running. Reopen the chat to see where it stands.'));
430
+ finished.catch(() => undefined); // a loss while the submit is still in flight is answered by the submit's own error
405
431
  const unwatch = this.onChange(() => {
432
+ if (this.connection === 'closed') {
433
+ lost();
434
+ return;
435
+ }
436
+ if (this.connection === 'connected') {
437
+ clearTimeout(down);
438
+ down = undefined;
439
+ }
440
+ else {
441
+ down ??= setTimeout(lost, DISCONNECT_GRACE_MS);
442
+ return;
443
+ }
406
444
  const state = this.descriptor?.turn_state;
407
445
  if (state === 'busy')
408
446
  sawBusy = true;
@@ -417,6 +455,7 @@ export class VscodeFrontendSession {
417
455
  return await finished;
418
456
  }
419
457
  finally {
458
+ clearTimeout(down);
420
459
  unsubscribe();
421
460
  unwatch();
422
461
  }
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.46",
4
+ "version": "0.1.48",
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",
@@ -190,7 +190,7 @@
190
190
  },
191
191
  "supercodeFrontendContract": {
192
192
  "name": "@volter/supercode-frontend",
193
- "version": "0.2.8",
193
+ "version": "0.2.9",
194
194
  "mode": "generated-artifact"
195
195
  },
196
196
  "license": "MIT OR Apache-2.0"
@@ -251,6 +251,8 @@
251
251
  "Reply": {"kind": "object", "fields": {"reply": {"type": "string"}}},
252
252
  "Interrupted": {"kind": "object", "fields": {"interrupted": {"type": "boolean"}}},
253
253
  "Queued": {"kind": "object", "fields": {"queued": {"type": "boolean"}}},
254
+ "TaskParams": {"kind": "object", "fields": {"task_id": {"type": "string"}}},
255
+ "Stopped": {"kind": "object", "fields": {"stopped": {"type": "boolean"}}},
254
256
  "Closed": {"kind": "object", "fields": {"closed": {"type": "boolean"}}}
255
257
  },
256
258
  "methods": [
@@ -260,6 +262,7 @@
260
262
  {"id": "submit", "wire": "frontend.v2.submit", "sdk_action": "input", "params": "MultimodalPromptParams", "result": "Reply", "compatibility": "completion_waiting_alias", "transports": ["local", "http", "websocket", "acp"]},
261
263
  {"id": "interrupt", "wire": "frontend.v2.interrupt", "sdk_action": "interrupt", "params": "Empty", "result": "Interrupted", "transports": ["local", "http", "websocket", "acp"]},
262
264
  {"id": "steer", "wire": "frontend.v2.steer", "sdk_action": "steer", "params": "PromptParams", "result": "Queued", "transports": ["local", "http", "websocket", "acp"]},
265
+ {"id": "stop_task", "wire": "frontend.v2.stop_task", "sdk_action": "stop_task", "params": "TaskParams", "result": "Stopped", "transports": ["local", "http", "websocket", "acp"]},
263
266
  {"id": "respond", "wire": "frontend.v2.respond", "sdk_action": "respond", "params": "RespondParams", "result": "Accepted", "transports": ["local", "http", "websocket", "acp"]},
264
267
  {"id": "invoke", "wire": "frontend.v2.invoke", "sdk_action": "input", "params": "InvokeParams", "result": "FrontendOperationResult", "transports": ["local", "http", "websocket", "acp"]},
265
268
  {"id": "lease", "wire": "frontend.v2.lease", "sdk_action": "events", "params": "Empty", "result": "RuntimeLeaseSnapshot", "transports": ["local", "http", "websocket", "acp"]},
@@ -1,5 +1,5 @@
1
1
  // @generated by sdk/frontend/generate.mjs from contract.json. Do not edit.
2
- import type { Accepted, Closed, FrontendAttachSnapshot, FrontendOperationInvocation, FrontendOperationResult, FrontendResponse, FrontendRuntimeDescriptor, FrontendWireMethod, Interrupted, Queued, Reply, RuntimeLeaseSnapshot } from './generated.mjs';
2
+ import type { Accepted, Closed, FrontendAttachSnapshot, FrontendOperationInvocation, FrontendOperationResult, FrontendResponse, FrontendRuntimeDescriptor, FrontendWireMethod, Interrupted, Queued, Reply, RuntimeLeaseSnapshot, Stopped } from './generated.mjs';
3
3
  export abstract class GeneratedFrontendClient<Options = unknown> {
4
4
  abstract rpc(method: FrontendWireMethod, params?: Record<string, unknown>, options?: Options): Promise<unknown>;
5
5
  describe(options?: Options): Promise<FrontendRuntimeDescriptor>;
@@ -8,6 +8,7 @@ export abstract class GeneratedFrontendClient<Options = unknown> {
8
8
  submit(prompt: string, options?: Options & {imageUrls?: string[]}): Promise<Reply>;
9
9
  interrupt(options?: Options): Promise<Interrupted>;
10
10
  steer(prompt: string, options?: Options): Promise<Queued>;
11
+ stopTask(taskId: string, options?: Options): Promise<Stopped>;
11
12
  respond(response: FrontendResponse, options?: Options): Promise<Accepted>;
12
13
  invoke(operation: FrontendOperationInvocation, options?: Options): Promise<FrontendOperationResult>;
13
14
  lease(options?: Options): Promise<RuntimeLeaseSnapshot>;
@@ -9,6 +9,7 @@ export class GeneratedFrontendClient {
9
9
  submit(prompt, { imageUrls, ...options } = {}) { return this.rpc(FRONTEND_METHODS.submit, { prompt, ...(imageUrls?.length ? {image_urls:imageUrls} : {}) }, options); }
10
10
  interrupt(options) { return this.rpc(FRONTEND_METHODS.interrupt, {}, options); }
11
11
  steer(prompt, options) { return this.rpc(FRONTEND_METHODS.steer, { prompt }, options); }
12
+ stopTask(taskId, options) { return this.rpc(FRONTEND_METHODS.stop_task, { task_id: taskId }, options); }
12
13
  respond(response, options) { return this.rpc(FRONTEND_METHODS.respond, { response }, options); }
13
14
  invoke(operation, options) { return this.rpc(FRONTEND_METHODS.invoke, { operation }, options); }
14
15
  lease(options) { return this.rpc(FRONTEND_METHODS.lease, {}, options); }
@@ -139,8 +139,14 @@ export interface Interrupted {
139
139
  export interface Queued {
140
140
  queued: boolean;
141
141
  }
142
+ export interface TaskParams {
143
+ task_id: string;
144
+ }
145
+ export interface Stopped {
146
+ stopped: boolean;
147
+ }
142
148
  export interface Closed {
143
149
  closed: boolean;
144
150
  }
145
- export type FrontendMethodId = 'describe' | 'attach' | 'send_input' | 'submit' | 'interrupt' | 'steer' | 'respond' | 'invoke' | 'lease' | 'acquire_control' | 'take_control' | 'heartbeat' | 'detach' | 'close';
146
- export type FrontendWireMethod = 'frontend.v2.describe' | 'frontend.v2.attach' | 'frontend.v2.send_input' | 'frontend.v2.submit' | 'frontend.v2.interrupt' | 'frontend.v2.steer' | 'frontend.v2.respond' | 'frontend.v2.invoke' | 'frontend.v2.lease' | 'frontend.v2.acquire_control' | 'frontend.v2.take_control' | 'frontend.v2.heartbeat' | 'frontend.v2.detach' | 'frontend.v2.close';
151
+ export type FrontendMethodId = 'describe' | 'attach' | 'send_input' | 'submit' | 'interrupt' | 'steer' | 'stop_task' | 'respond' | 'invoke' | 'lease' | 'acquire_control' | 'take_control' | 'heartbeat' | 'detach' | 'close';
152
+ export type FrontendWireMethod = 'frontend.v2.describe' | 'frontend.v2.attach' | 'frontend.v2.send_input' | 'frontend.v2.submit' | 'frontend.v2.interrupt' | 'frontend.v2.steer' | 'frontend.v2.stop_task' | 'frontend.v2.respond' | 'frontend.v2.invoke' | 'frontend.v2.lease' | 'frontend.v2.acquire_control' | 'frontend.v2.take_control' | 'frontend.v2.heartbeat' | 'frontend.v2.detach' | 'frontend.v2.close';
@@ -7,6 +7,7 @@ export const FRONTEND_METHODS = Object.freeze({
7
7
  "submit": "frontend.v2.submit",
8
8
  "interrupt": "frontend.v2.interrupt",
9
9
  "steer": "frontend.v2.steer",
10
+ "stop_task": "frontend.v2.stop_task",
10
11
  "respond": "frontend.v2.respond",
11
12
  "invoke": "frontend.v2.invoke",
12
13
  "lease": "frontend.v2.lease",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-frontend",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "type": "module",
5
5
  "description": "Generated language-neutral attachment client for Volter Harness SDK runtimes",
6
6
  "exports": {