@volter/supercode-frontend-vscode 0.1.45 → 0.1.46

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 CHANGED
@@ -430,3 +430,21 @@ it never submits a new chat prompt. The host guards the addressed conversation,
430
430
  disables the choices during dispatch and retires them only after an acknowledged
431
431
  response or an already-resolved result. Hosts without this capability retain
432
432
  stable command buttons. Other request kinds retain their existing presentation.
433
+
434
+ ## External runtime handoff
435
+
436
+ `supercode.frontend.connect(handoff)` accepts a `SupercodeRuntimeHandoff` with
437
+ `baseUrl`, `clientId`, `connectionId`, `credentialFile`, optional permissions and
438
+ label. Credentials are read from the private file; command arguments contain no
439
+ raw token. External attachments retain their exact identity across reloads.
440
+ `supercode.frontend.openSession(connectionId)` opens that conversation;
441
+ `supercode.frontend.status(true)` reports connection state, conversation view
442
+ readiness/resource and rendered event cursor. View readiness follows history
443
+ restoration and live-response subscription, rather than a socket alone.
444
+ `supercode.frontend.disconnect(connectionId)` also forgets the saved attachment.
445
+ IDs belonging to managed conversations cannot be reused for external attachments.
446
+ Calling `connect` without arguments retains the interactive settings flow.
447
+
448
+ These are ordinary connection/view APIs for any external runtime. Playback or
449
+ recording policy belongs to its runtime/controller; a host needs no replay-specific
450
+ commands. [Replay recording](../../docs/guides/replay-recording.md) illustrates a consumer.
@@ -15,6 +15,12 @@ export interface SupercodeConnectOptions {
15
15
  */
16
16
  unavailable?: string;
17
17
  }
18
+ /** An external runtime handoff carries a private credential file, never a token in arguments. */
19
+ export interface SupercodeRuntimeHandoff extends Omit<SupercodeConnectOptions, 'token' | 'unavailable'> {
20
+ baseUrl: string;
21
+ clientId: string;
22
+ credentialFile: string;
23
+ }
18
24
  export interface SupercodeConnectionInfo {
19
25
  readonly id: string;
20
26
  readonly baseUrl: string;
@@ -22,6 +28,10 @@ export interface SupercodeConnectionInfo {
22
28
  readonly sessionId: string | null;
23
29
  readonly model: string | null;
24
30
  readonly turnState: 'idle' | 'busy' | null;
31
+ readonly connectionState: 'disconnected' | 'connected' | 'closed';
32
+ readonly viewReady: boolean;
33
+ readonly viewResource: string | null;
34
+ readonly cursor: number;
25
35
  }
26
36
  /** What `activate` returns: the door a host uses to hand over a connection. */
27
37
  export interface SupercodeFrontendApi {
package/dist/extension.js CHANGED
@@ -87,6 +87,10 @@ class SupercodeFrontends {
87
87
  sessionId: connection.session.descriptor?.session_id ?? null,
88
88
  model: connection.session.descriptor?.model ?? null,
89
89
  turnState: connection.session.descriptor?.turn_state ?? null,
90
+ connectionState: connection.session.connection,
91
+ viewReady: connection.viewReady === true && connection.session.connection === 'connected',
92
+ viewResource: connection.viewResource ?? null,
93
+ cursor: connection.session.cursor,
90
94
  };
91
95
  }
92
96
  async connect(options, log) {
@@ -436,6 +440,37 @@ export async function activate(context) {
436
440
  // them, so they open as what they are: attachments, shown as their runtime
437
441
  // reports them, and never taken for the conversation the host manages.
438
442
  const attachedIds = new Set();
443
+ const savedAttachments = new Map(context.workspaceState.get('supercode.externalAttachments', [])
444
+ .map(entry => [entry.connectionId, entry]));
445
+ const restoringAttachments = new Map();
446
+ const attachmentSecretKey = (id) => `supercode.frontend.attachment:${id}`;
447
+ const saveAttachments = () => context.workspaceState.update('supercode.externalAttachments', [...savedAttachments.values()]);
448
+ const restoreAttachment = (id) => {
449
+ const existing = frontends.get(id);
450
+ if (existing)
451
+ return Promise.resolve(existing);
452
+ const pending = restoringAttachments.get(id);
453
+ if (pending)
454
+ return pending;
455
+ const saved = savedAttachments.get(id);
456
+ if (!saved)
457
+ return Promise.resolve(undefined);
458
+ const restoration = (async () => {
459
+ const token = saved.credentialFile ? await readCredentialFile(saved.credentialFile) : await context.secrets.get(attachmentSecretKey(id));
460
+ if (!token)
461
+ throw new Error('The saved runtime credential is unavailable. Attach to the runtime again.');
462
+ const connection = await frontends.connect({ ...saved, token }, log);
463
+ if (connection)
464
+ attachedIds.add(id);
465
+ return connection;
466
+ })().finally(() => restoringAttachments.delete(id));
467
+ restoringAttachments.set(id, restoration);
468
+ return restoration;
469
+ };
470
+ for (const id of savedAttachments.keys()) {
471
+ attachedIds.add(id);
472
+ await restoreAttachment(id).catch(error => log.warn(`Could not restore runtime attachment ${id}: ${String(error)}`));
473
+ }
439
474
  const managed = () => frontends.all.find((connection) => !attachedIds.has(connection.id));
440
475
  // The host runs one conversation at a time, so a running or waiting turn refuses a new or another chat. This is
441
476
  // the sentence that refusal says to the person; each caller picks the one place it is shown (see its uses).
@@ -448,7 +483,8 @@ export async function activate(context) {
448
483
  return `A turn is still running in the current chat. Let it finish or cancel it before ${action}.`;
449
484
  };
450
485
  // The attached runtime the person is looking at: the Chat names its harness, not the host's.
451
- let focusedAttached;
486
+ const restoredFocus = context.workspaceState.get('supercode.focusedAttachment');
487
+ let focusedAttached = restoredFocus ? frontends.get(restoredFocus) : undefined;
452
488
  let approvalAvailable = false;
453
489
  context.subscriptions.push(frontends.onDidChange(() => {
454
490
  const active = managed();
@@ -661,7 +697,25 @@ export async function activate(context) {
661
697
  terminal.show();
662
698
  }));
663
699
  const restoring = new Map();
664
- const connectionFor = async (resource, activate = true) => {
700
+ const connectionFor = async (resource, activate = true, token) => {
701
+ if (savedAttachments.has(resource.authority)) {
702
+ let attached;
703
+ while (!attached) {
704
+ if (token?.isCancellationRequested)
705
+ throw new vscode.CancellationError();
706
+ try {
707
+ attached = await restoreAttachment(resource.authority);
708
+ if (!attached)
709
+ throw new Error('This runtime attachment is no longer saved.');
710
+ }
711
+ catch (error) {
712
+ log.warn(`Waiting for runtime attachment ${resource.authority}: ${String(error)}`);
713
+ if (!token)
714
+ throw error;
715
+ await new Promise(resolve => setTimeout(resolve, 500));
716
+ }
717
+ }
718
+ }
665
719
  if (!controls) {
666
720
  const connection = frontends.get(resource.authority);
667
721
  if (!connection)
@@ -671,6 +725,7 @@ export async function activate(context) {
671
725
  const attached = attachedIds.has(resource.authority) ? frontends.get(resource.authority) : undefined;
672
726
  if (activate && focusedAttached !== attached) {
673
727
  focusedAttached = attached;
728
+ await context.workspaceState.update('supercode.focusedAttachment', attached?.id);
674
729
  controlsChanged.fire();
675
730
  }
676
731
  if (attached)
@@ -932,7 +987,7 @@ export async function activate(context) {
932
987
  return undefined;
933
988
  }
934
989
  },
935
- resolve: (resource) => connectionFor(resource, false),
990
+ resolve: (resource, token) => connectionFor(resource, false, token),
936
991
  attached: (connection) => attachedIds.has(connection.id),
937
992
  catalogue: () => controls?.state.sessions ?? [],
938
993
  bindings: boundId,
@@ -951,7 +1006,13 @@ export async function activate(context) {
951
1006
  await connectionFor(resource);
952
1007
  if (generation !== focusGeneration)
953
1008
  return;
954
- await revealManaged();
1009
+ if (attachedIds.has(id)) {
1010
+ if (!controls?.state.openSessionCommand)
1011
+ throw new Error('The host cannot reveal this conversation.');
1012
+ await vscode.commands.executeCommand(controls.state.openSessionCommand, resource.toString());
1013
+ }
1014
+ else
1015
+ await revealManaged();
955
1016
  }));
956
1017
  context.subscriptions.push(vscode.commands.registerCommand('supercode.frontend.configure', async (supplied) => {
957
1018
  if (supplied?.permissions) {
@@ -1391,7 +1452,17 @@ export async function activate(context) {
1391
1452
  await settings.update('baseUrl', baseUrl, vscode.ConfigurationTarget.Global);
1392
1453
  await context.secrets.store(secretKey(baseUrl), token);
1393
1454
  await connectFromSettings();
1394
- }), vscode.commands.registerCommand('supercode.frontend.connect', () => connectFromSettings()),
1455
+ }), vscode.commands.registerCommand('supercode.frontend.connect', async (handoff) => {
1456
+ if (handoff === undefined)
1457
+ return connectFromSettings();
1458
+ if (!handoff || typeof handoff.baseUrl !== 'string' || typeof handoff.clientId !== 'string' || !handoff.clientId.trim() || typeof handoff.credentialFile !== 'string') {
1459
+ throw new TypeError('A runtime handoff needs baseUrl, clientId and credentialFile.');
1460
+ }
1461
+ const token = await readCredentialFile(handoff.credentialFile);
1462
+ const connection = await connectExternal({ baseUrl: handoff.baseUrl, token, clientId: handoff.clientId,
1463
+ connectionId: handoff.connectionId, permissions: handoff.permissions, label: handoff.label }, handoff.credentialFile);
1464
+ return connection ? frontends.info(connection) : undefined;
1465
+ }),
1395
1466
  // The create command the core's own `chatSessions/newSession` menu invokes
1396
1467
  // for this session type (`chatSessions.contribution.ts`
1397
1468
  // `_resolveCreateSubMenuCommandId`). It opens a chat on the supercode
@@ -1404,7 +1475,7 @@ export async function activate(context) {
1404
1475
  }
1405
1476
  const where = position === 'editor' ? 'editor' : 'sidebar';
1406
1477
  await vscode.commands.executeCommand(`workbench.action.chat.openNewChatSessionInPlace.${SESSION_SCHEME}`, where);
1407
- }), vscode.commands.registerCommand('supercode.frontend.disconnect', () => frontends.disconnect()),
1478
+ }), vscode.commands.registerCommand('supercode.frontend.disconnect', (id) => disconnectExternal(id)),
1408
1479
  // The one door an answer button uses. It answers the runtime's pending
1409
1480
  // request through `frontend.v2.respond` and narrates itself, because a
1410
1481
  // click whose effect is invisible is a click nobody can trust.
@@ -1434,6 +1505,40 @@ export async function activate(context) {
1434
1505
  : REQUESTED_PROPOSALS.map((name) => `${name} not granted: ${PROPOSAL_EFFECTS[name].without}`).join('\n'),
1435
1506
  });
1436
1507
  }));
1508
+ async function connectExternal(options, credentialFile) {
1509
+ const id = options.connectionId ?? options.clientId;
1510
+ if (id && controls?.state.sessions.some(session => session.id === id))
1511
+ throw new Error('An external runtime cannot replace a host-managed conversation.');
1512
+ const existing = id ? frontends.get(id) : undefined;
1513
+ if (existing && existing.baseUrl !== options.baseUrl)
1514
+ throw new Error('This connection identity already belongs to another runtime.');
1515
+ const connection = await frontends.connect(options, log);
1516
+ if (connection && options.token) {
1517
+ if (!credentialFile)
1518
+ await context.secrets.store(attachmentSecretKey(connection.id), options.token);
1519
+ savedAttachments.set(connection.id, { baseUrl: connection.baseUrl, clientId: options.clientId ?? connection.id,
1520
+ connectionId: connection.id, permissions: [...connection.session.permissions], label: connection.label, credentialFile });
1521
+ attachedIds.add(connection.id);
1522
+ await saveAttachments();
1523
+ controlsChanged.fire();
1524
+ }
1525
+ return connection;
1526
+ }
1527
+ async function disconnectExternal(id) {
1528
+ const ids = id ? [id] : [...savedAttachments.keys()];
1529
+ for (const target of ids) {
1530
+ savedAttachments.delete(target);
1531
+ attachedIds.delete(target);
1532
+ await context.secrets.delete(attachmentSecretKey(target));
1533
+ if (focusedAttached?.id === target)
1534
+ focusedAttached = undefined;
1535
+ }
1536
+ await saveAttachments();
1537
+ if (!focusedAttached)
1538
+ await context.workspaceState.update('supercode.focusedAttachment', undefined);
1539
+ await frontends.disconnect(id);
1540
+ controlsChanged.fire();
1541
+ }
1437
1542
  async function connectFromSettings() {
1438
1543
  const settings = vscode.workspace.getConfiguration('supercode.frontend');
1439
1544
  const baseUrl = settings.get('baseUrl')?.trim() || context.globalState.get(HANDED_OVER_KEY);
@@ -1444,16 +1549,11 @@ export async function activate(context) {
1444
1549
  log.info('Volter Harness: a base URL is configured but no token is stored; run Volter Harness: Set Runtime Token.');
1445
1550
  return;
1446
1551
  }
1447
- const connection = await frontends
1448
- .connect({ baseUrl, token, permissions: normalizePermissions(settings.get('permissions'), ['observe']) }, log)
1552
+ await connectExternal({ baseUrl, token, permissions: normalizePermissions(settings.get('permissions'), ['observe']) })
1449
1553
  .catch((error) => {
1450
1554
  log.error(`Volter Harness could not attach to ${baseUrl}: ${String(error?.message ?? error)}`);
1451
1555
  return undefined;
1452
1556
  });
1453
- if (controls && connection) {
1454
- attachedIds.add(connection.id);
1455
- controlsChanged.fire();
1456
- }
1457
1557
  }
1458
1558
  /**
1459
1559
  * The handover path: a runtime (or a product that launched this editor's
@@ -1507,6 +1607,12 @@ export async function activate(context) {
1507
1607
  async function openResumedSession() {
1508
1608
  if (!granted.has('chatSessionsProvider'))
1509
1609
  return;
1610
+ const focusedId = context.workspaceState.get('supercode.focusedAttachment');
1611
+ const attached = focusedId ? frontends.get(focusedId) : undefined;
1612
+ if (attached && controls?.state.openSessionCommand) {
1613
+ await vscode.commands.executeCommand('supercode.frontend.openSession', attached.id);
1614
+ return;
1615
+ }
1510
1616
  if (controls) {
1511
1617
  await refreshSetup();
1512
1618
  return;
@@ -1554,10 +1660,10 @@ export async function activate(context) {
1554
1660
  context.subscriptions.push({ dispose: () => { setupDisposed = true; clearTimeout(setupTimer); } });
1555
1661
  return {
1556
1662
  connect: async (options) => {
1557
- const connection = await frontends.connect(options, log);
1663
+ const connection = await connectExternal(options);
1558
1664
  return connection ? frontends.info(connection) : undefined;
1559
1665
  },
1560
- disconnect: (connectionId) => frontends.disconnect(connectionId),
1666
+ disconnect: (connectionId) => disconnectExternal(connectionId),
1561
1667
  get connections() {
1562
1668
  return frontends.all.map((connection) => frontends.info(connection));
1563
1669
  },
@@ -1756,12 +1862,14 @@ function registerSessions(context, frontends, participant, granted, handler, his
1756
1862
  const boundHandler = (request, chatContext, stream, token) => handler(request, { ...chatContext, chatSessionContext: { chatSessionItem: { resource, label: 'Conversation' } } }, stream, token);
1757
1863
  // Only a native untitled resource can create a conversation. Existing
1758
1864
  // resources must resolve their exact saved binding; there is no fallback.
1759
- const connection = controls ? (resource.path.startsWith('/untitled-') && !controls.bindings(resource) ? undefined : await controls.resolve(resource)) : frontends.get(resource.authority);
1865
+ const connection = controls ? (resource.path.startsWith('/untitled-') && !controls.bindings(resource) ? undefined : await controls.resolve(resource, token)) : frontends.get(resource.authority);
1760
1866
  if (token.isCancellationRequested)
1761
1867
  throw new vscode.CancellationError();
1762
1868
  if (!connection) {
1763
1869
  return { history: [], requestHandler: boundHandler };
1764
1870
  }
1871
+ connection.viewReady = false;
1872
+ connection.viewResource = resource.toString();
1765
1873
  if (controls && !controls.attached(connection)) {
1766
1874
  controls.setPermission(resource, inputState.groups.find(group => group.id === 'permissions')?.selected?.id);
1767
1875
  inputState.groups = controls.options(resource);
@@ -1813,6 +1921,9 @@ function registerSessions(context, frontends, participant, granted, handler, his
1813
1921
  history.push(new turns.ChatResponseTurn(historyParts(oneRowPerToolCall(turn.parts ?? [])), {}, authorId));
1814
1922
  }
1815
1923
  const busy = connection.session.descriptor?.turn_state === 'busy';
1924
+ const renderedCursor = connection.session.cursor;
1925
+ if (!busy)
1926
+ connection.viewReady = attached;
1816
1927
  return {
1817
1928
  title: connection.label,
1818
1929
  options: controls?.attached(connection) ? undefined : controls?.currentOptions(resource),
@@ -1838,9 +1949,10 @@ function registerSessions(context, frontends, participant, granted, handler, his
1838
1949
  canRespond: connection.session.descriptor?.actions.respond !== false,
1839
1950
  }), granted, state);
1840
1951
  watch?.sweep();
1841
- });
1952
+ }, () => { connection.viewReady = true; }, renderedCursor);
1842
1953
  }
1843
1954
  finally {
1955
+ connection.viewReady = connection.session.descriptor?.turn_state !== 'busy';
1844
1956
  watch?.dispose();
1845
1957
  }
1846
1958
  }
package/dist/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export type { ChatGPTSignInWorkPhase, ChatGPTSignInOperation } from './chat-setu
4
4
  export { grantedProposals, PROPOSAL_EFFECTS, REQUESTED_PROPOSALS, type ProposalName, } from './proposals.js';
5
5
  export { EventSequenceGapError, LEASE_ERROR_NAMES, RequestNotPendingError, TURN_TERMINAL_KINDS, VscodeFrontendSession, type VscodeFrontendActions, type VscodeRuntimeClient, } from './session.js';
6
6
  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';
7
- export type { SupercodeConnectOptions, SupercodeConnectionInfo, SupercodeFrontendApi, } from './extension.js';
7
+ export type { SupercodeConnectOptions, SupercodeRuntimeHandoff, SupercodeConnectionInfo, SupercodeFrontendApi, } from './extension.js';
@@ -3,4 +3,4 @@ import type { FrontendEvent } from '../vendor/supercode-frontend/generated.mjs';
3
3
  import { type VscodeFrontendSession } from './session.js';
4
4
  export declare function sessionStatus(session: Pick<VscodeFrontendSession, 'descriptor' | 'pendingRequests'>): 'NeedsInput' | 'InProgress' | 'Completed';
5
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>;
6
+ export declare function observeActiveResponse(session: Pick<VscodeFrontendSession, 'descriptor' | 'onEvent' | 'interrupt' | 'actions' | 'replay'>, token: CancellationToken, write: (event: FrontendEvent) => void, onSubscribed?: () => void, afterSequence?: number): Promise<void>;
@@ -3,7 +3,7 @@ export function sessionStatus(session) {
3
3
  return session.pendingRequests.size ? 'NeedsInput' : session.descriptor?.turn_state === 'busy' ? 'InProgress' : 'Completed';
4
4
  }
5
5
  /** Follow a restored turn, including the interval between snapshot and callback. */
6
- export async function observeActiveResponse(session, token, write) {
6
+ export async function observeActiveResponse(session, token, write, onSubscribed, afterSequence) {
7
7
  let unsubscribe = () => { };
8
8
  let cancellation;
9
9
  try {
@@ -13,10 +13,20 @@ export async function observeActiveResponse(session, token, write) {
13
13
  if (cancelling)
14
14
  return;
15
15
  cancelling = true;
16
+ // Closing an observed view cannot interrupt a runtime that grants no
17
+ // interrupt action. Stop observing without inventing control authority.
18
+ if (!session.actions().interrupt) {
19
+ resolve();
20
+ return;
21
+ }
16
22
  // Wait for the runtime acknowledgement; a stopped UI is not a stopped harness.
17
23
  void session.interrupt().then(resolve, reject);
18
24
  };
19
- unsubscribe = session.onEvent(event => {
25
+ let delivered = afterSequence ?? 0;
26
+ const consume = (event) => {
27
+ if (afterSequence !== undefined && event.sequence <= delivered)
28
+ return;
29
+ delivered = event.sequence;
20
30
  try {
21
31
  write(event);
22
32
  }
@@ -26,7 +36,14 @@ export async function observeActiveResponse(session, token, write) {
26
36
  }
27
37
  if (!cancelling && TURN_TERMINAL_KINDS.includes(event.kind))
28
38
  resolve();
29
- });
39
+ };
40
+ unsubscribe = session.onEvent(consume);
41
+ if (afterSequence !== undefined) {
42
+ for (const event of session.replay)
43
+ if (event.sequence > afterSequence)
44
+ consume(event);
45
+ }
46
+ onSubscribed?.();
30
47
  cancellation = token.onCancellationRequested(cancel);
31
48
  if (token.isCancellationRequested)
32
49
  cancel();
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.45",
4
+ "version": "0.1.46",
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.7",
193
+ "version": "0.2.8",
194
194
  "mode": "generated-artifact"
195
195
  },
196
196
  "license": "MIT OR Apache-2.0"
@@ -73,7 +73,7 @@
73
73
  "argument_hint": {"type": "string", "optional": true, "nullable": true}
74
74
  }
75
75
  },
76
- "FrontendOperationKind": {"kind": "enum", "values": ["prompt", "file", "model", "session", "subagent", "image", "reduction"]},
76
+ "FrontendOperationKind": {"kind": "enum", "values": ["prompt", "file", "model", "session", "subagent", "image", "reduction", "replay"]},
77
77
  "FrontendOperationDescriptor": {
78
78
  "kind": "object",
79
79
  "fields": {
@@ -175,6 +175,10 @@
175
175
  "prompt": {
176
176
  "operation_id": {"type": "string"},
177
177
  "arguments": {"type": "string"}
178
+ },
179
+ "replay": {
180
+ "operation_id": {"type": "string"},
181
+ "control": {"type": "string"}
178
182
  }
179
183
  }
180
184
  },
@@ -182,7 +186,8 @@
182
186
  "kind": "tagged_union",
183
187
  "tag": "kind",
184
188
  "variants": {
185
- "prompt": {"reply": {"type": "string"}}
189
+ "prompt": {"reply": {"type": "string"}},
190
+ "replay": {"status": {"type": "JsonValue"}}
186
191
  }
187
192
  },
188
193
  "RuntimeControllerLease": {
@@ -32,7 +32,7 @@ export interface FrontendCommandDescriptor {
32
32
  description: string | null;
33
33
  argument_hint?: string | null;
34
34
  }
35
- export type FrontendOperationKind = "prompt" | "file" | "model" | "session" | "subagent" | "image" | "reduction";
35
+ export type FrontendOperationKind = "prompt" | "file" | "model" | "session" | "subagent" | "image" | "reduction" | "replay";
36
36
  export interface FrontendOperationDescriptor {
37
37
  id: string;
38
38
  kind: FrontendOperationKind;
@@ -84,8 +84,8 @@ export interface FrontendAttachSnapshot {
84
84
  export type FrontendApprovalDecision = "deny" | "allow" | "allow_for_session";
85
85
  export type FrontendElicitationAction = "accept" | "decline" | "cancel";
86
86
  export type FrontendResponse = { kind: "approval"; request_id: number; decision: FrontendApprovalDecision } | { kind: "elicitation"; request_id: number; action: FrontendElicitationAction; content?: JsonValue | null } | { kind: "other"; request_id: number; action: FrontendElicitationAction; content?: JsonValue | null };
87
- export type FrontendOperationInvocation = { kind: "prompt"; operation_id: string; arguments: string };
88
- export type FrontendOperationResult = { kind: "prompt"; reply: string };
87
+ export type FrontendOperationInvocation = { kind: "prompt"; operation_id: string; arguments: string } | { kind: "replay"; operation_id: string; control: string };
88
+ export type FrontendOperationResult = { kind: "prompt"; reply: string } | { kind: "replay"; status: JsonValue };
89
89
  export interface RuntimeControllerLease {
90
90
  client_id: string;
91
91
  expires_at_ms: number;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-frontend",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "description": "Generated language-neutral attachment client for Volter Harness SDK runtimes",
6
6
  "exports": {