@volter-ai-dev/supercode-ui 0.1.31 → 0.1.32

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
@@ -98,6 +98,11 @@ drafts, and artifact materialization remain explicit callbacks. A
98
98
  browser should receive projected state from a trusted host rather than instantiate a local
99
99
  controller or gain filesystem authority.
100
100
 
101
+ Native continuation is headless by default. A host with a real terminal provider can add
102
+ `terminal` to `continuationModes` and handle `onResumeTerminal`; the continuation bar then exposes
103
+ that strategy beside “Continue here.” The UI never infers terminal support from a generic runtime
104
+ handoff, and never presents a terminal strategy when the host cannot create one.
105
+
101
106
  The default controller projection is display-bounded: 120 visible transcript rows, at most 480
102
107
  native entries inspected to fill that tail, 16,000 characters per independent entry field, 100
103
108
  session rows, 20 fidelity-residue details, and 50 subagents. It filters harness-injected context
package/components.mjs CHANGED
@@ -17,6 +17,7 @@ var DEFAULT_LABELS = Object.freeze({
17
17
  searchChats: "Search chats",
18
18
  askAgent: "Ask your agent\u2026",
19
19
  continueHere: "Continue here",
20
+ continueWithTerminal: "Continue with terminal",
20
21
  joinLive: "Join live",
21
22
  forkHere: "Fork here"
22
23
  });
@@ -32,6 +33,7 @@ var EMPTY_UI_STATE = Object.freeze({
32
33
  strategy: null,
33
34
  canSend: false,
34
35
  canResume: false,
36
+ continuationModes: Object.freeze([]),
35
37
  canBranch: false,
36
38
  canAttach: false,
37
39
  canDetach: false,
@@ -704,6 +706,8 @@ function normalizeUiState(value) {
704
706
  const pill = record(raw.pill);
705
707
  const history = record(raw.history);
706
708
  const attachError = record(raw.attachError);
709
+ const canResume = raw.canResume === true;
710
+ const continuationModes = Array.isArray(raw.continuationModes) ? [...new Set(raw.continuationModes.filter((mode) => mode === "headless" || mode === "terminal"))] : canResume ? ["headless"] : [];
707
711
  return {
708
712
  pill: { tone: ["live", "warn", "dead"].includes(pill?.tone) ? pill.tone : "off", label: string(pill?.label, "connecting\u2026") },
709
713
  startup: STARTUP.has(raw.startup) ? raw.startup : "connecting",
@@ -715,7 +719,8 @@ function normalizeUiState(value) {
715
719
  mode: MODES.has(raw.mode) ? raw.mode : "none",
716
720
  strategy: STRATEGIES.has(raw.strategy) ? raw.strategy : null,
717
721
  canSend: raw.canSend === true,
718
- canResume: raw.canResume === true,
722
+ canResume,
723
+ continuationModes,
719
724
  canBranch: raw.canBranch === true,
720
725
  canAttach: raw.canAttach === true,
721
726
  canDetach: raw.canDetach === true,
@@ -763,9 +768,9 @@ function sessionDisplayName(session) {
763
768
  function sessionActivity(state, row) {
764
769
  if (state.needsInput && row.active) return "needs-input";
765
770
  if (state.busy && row.active) return "working";
771
+ if (row.runtimeStatus === "busy") return "working";
766
772
  const attention = state.attention.find((item) => item.key === row.key)?.kind;
767
773
  if (attention) return attention;
768
- if (row.runtimeStatus === "busy") return "working";
769
774
  if (row.runtimeStatus === "running") return "running";
770
775
  if (row.live || row.runtimeStatus === "idle") return "recent";
771
776
  return "idle";
@@ -1173,6 +1178,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1173
1178
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1174
1179
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1175
1180
  const resume = canContinueHere(state);
1181
+ const terminal = resume && state.continuationModes?.includes("terminal");
1176
1182
  const join = state.canAttach;
1177
1183
  const branch = state.canBranch;
1178
1184
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -1184,7 +1190,10 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1184
1190
  /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
1185
1191
  /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
1186
1192
  ] }),
1187
- /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
1193
+ /* @__PURE__ */ jsxs3("span", { class: "scui-continuation-actions", children: [
1194
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
1195
+ terminal ? /* @__PURE__ */ jsx3("button", { type: "button", class: "scui-secondary", disabled: Boolean(state.operation), onClick: () => adapter.onIntent({ action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
1196
+ ] })
1188
1197
  ] });
1189
1198
  }
1190
1199
  function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
package/composer.mjs CHANGED
@@ -17,6 +17,7 @@ var DEFAULT_LABELS = Object.freeze({
17
17
  searchChats: "Search chats",
18
18
  askAgent: "Ask your agent\u2026",
19
19
  continueHere: "Continue here",
20
+ continueWithTerminal: "Continue with terminal",
20
21
  joinLive: "Join live",
21
22
  forkHere: "Fork here"
22
23
  });
@@ -32,6 +33,7 @@ var EMPTY_UI_STATE = Object.freeze({
32
33
  strategy: null,
33
34
  canSend: false,
34
35
  canResume: false,
36
+ continuationModes: Object.freeze([]),
35
37
  canBranch: false,
36
38
  canAttach: false,
37
39
  canDetach: false,
@@ -257,6 +259,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
257
259
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
258
260
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
259
261
  const resume = canContinueHere(state);
262
+ const terminal = resume && state.continuationModes?.includes("terminal");
260
263
  const join = state.canAttach;
261
264
  const branch = state.canBranch;
262
265
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -268,7 +271,10 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
268
271
  /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
269
272
  /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
270
273
  ] }),
271
- /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
274
+ /* @__PURE__ */ jsxs3("span", { class: "scui-continuation-actions", children: [
275
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
276
+ terminal ? /* @__PURE__ */ jsx3("button", { type: "button", class: "scui-secondary", disabled: Boolean(state.operation), onClick: () => adapter.onIntent({ action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
277
+ ] })
272
278
  ] });
273
279
  }
274
280
  function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
package/controller.d.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
  } from '@volter-ai-dev/supercode-client';
9
9
  import type {
10
10
  AttachedSessionModel,
11
+ ContinuationMode,
11
12
  SessionAttention,
12
13
  SessionRowModel,
13
14
  StartupPhase,
@@ -40,6 +41,8 @@ export interface ClientProjectionOptions {
40
41
  exportBackTarget?: SessionFormat | null;
41
42
  exportReceipt?: SupercodeUiState['exportReceipt'];
42
43
  reductionReceipt?: SupercodeUiState['reductionReceipt'];
44
+ /** Host-provided execution strategies. A plain controller projects headless resume only. */
45
+ continuationModes?: ContinuationMode[];
43
46
  /** Trusted-host inventory and lifecycle overlays (for example a machine-wide session catalog). */
44
47
  sessions?: SessionRowModel[];
45
48
  attached?: AttachedSessionModel | null;
@@ -81,6 +84,7 @@ export interface ControllerBindingOptions {
81
84
  onAcknowledge?: (key: string) => void | Promise<void>;
82
85
  onLoadSessions?: () => void | Promise<void>;
83
86
  onLoadEarlier?: () => void | Promise<void>;
87
+ onResumeTerminal?: (intent: Extract<SupercodeUiIntent, { action: 'resume' }>) => void | Promise<void>;
84
88
  copyText?: UiAdapter['copyText'];
85
89
  resolveImage?: UiAdapter['resolveImage'];
86
90
  }
package/controller.mjs CHANGED
@@ -366,6 +366,7 @@ function projectClientSnapshotInternal(snapshot, options, imageRegistry) {
366
366
  strategy: snapshot.connection?.strategy ?? null,
367
367
  canSend: actions.send === true,
368
368
  canResume: actions.resume === true,
369
+ continuationModes: options.continuationModes ?? (actions.resume === true ? ['headless'] : []),
369
370
  canBranch: actions.branch === true,
370
371
  canAttach: actions.attach === true,
371
372
  canDetach: actions.detach === true,
@@ -449,7 +450,14 @@ async function dispatchStandard(controller, intent, options) {
449
450
  await controller.dispatch({ type: 'start', harness: intent.harness });
450
451
  return controller.dispatch({ type: 'send', text: intent.text, ...(intent.context?.length ? { context: intent.context } : {}), ...(intent.images?.length ? { images: intent.images } : {}) });
451
452
  }
452
- if (intent.action === 'resume' && active) return controller.dispatch({ type: 'resume', sessionKey: active });
453
+ if (intent.action === 'resume' && active) {
454
+ if (intent.mode === 'terminal') {
455
+ return options.onResumeTerminal
456
+ ? options.onResumeTerminal(intent)
457
+ : options.onUnsupported?.(intent);
458
+ }
459
+ return controller.dispatch({ type: 'resume', sessionKey: active });
460
+ }
453
461
  if (intent.action === 'join' && active) return controller.dispatch({ type: 'attach', sessionKey: active });
454
462
  if (intent.action === 'detach') return controller.dispatch({ type: 'detach' });
455
463
  if (intent.action === 'branch' && active) return controller.dispatch({ type: 'branch', sessionKey: active, ...(intent.targetHarness ? { targetHarness: intent.targetHarness } : {}) });
package/conversation.mjs CHANGED
@@ -18,6 +18,7 @@ var DEFAULT_LABELS = Object.freeze({
18
18
  searchChats: "Search chats",
19
19
  askAgent: "Ask your agent\u2026",
20
20
  continueHere: "Continue here",
21
+ continueWithTerminal: "Continue with terminal",
21
22
  joinLive: "Join live",
22
23
  forkHere: "Fork here"
23
24
  });
@@ -33,6 +34,7 @@ var EMPTY_UI_STATE = Object.freeze({
33
34
  strategy: null,
34
35
  canSend: false,
35
36
  canResume: false,
37
+ continuationModes: Object.freeze([]),
36
38
  canBranch: false,
37
39
  canAttach: false,
38
40
  canDetach: false,
package/core.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export type {
2
2
  AttachedSessionModel,
3
+ ContinuationMode,
3
4
  ControlStrategy,
4
5
  HarnessId,
5
6
  HarnessOption,
package/core.mjs CHANGED
@@ -14,6 +14,7 @@ export const DEFAULT_LABELS = Object.freeze({
14
14
  searchChats: 'Search chats',
15
15
  askAgent: 'Ask your agent…',
16
16
  continueHere: 'Continue here',
17
+ continueWithTerminal: 'Continue with terminal',
17
18
  joinLive: 'Join live',
18
19
  forkHere: 'Fork here',
19
20
  });
@@ -30,6 +31,7 @@ export const EMPTY_UI_STATE = Object.freeze({
30
31
  strategy: null,
31
32
  canSend: false,
32
33
  canResume: false,
34
+ continuationModes: Object.freeze([]),
33
35
  canBranch: false,
34
36
  canAttach: false,
35
37
  canDetach: false,
@@ -754,6 +756,10 @@ export function normalizeUiState(value) {
754
756
  const pill = record(raw.pill);
755
757
  const history = record(raw.history);
756
758
  const attachError = record(raw.attachError);
759
+ const canResume = raw.canResume === true;
760
+ const continuationModes = Array.isArray(raw.continuationModes)
761
+ ? [...new Set(raw.continuationModes.filter((mode) => mode === 'headless' || mode === 'terminal'))]
762
+ : canResume ? ['headless'] : [];
757
763
  return {
758
764
  pill: { tone: ['live', 'warn', 'dead'].includes(pill?.tone) ? pill.tone : 'off', label: string(pill?.label, 'connecting…') },
759
765
  startup: STARTUP.has(raw.startup) ? raw.startup : 'connecting',
@@ -765,7 +771,8 @@ export function normalizeUiState(value) {
765
771
  mode: MODES.has(raw.mode) ? raw.mode : 'none',
766
772
  strategy: STRATEGIES.has(raw.strategy) ? raw.strategy : null,
767
773
  canSend: raw.canSend === true,
768
- canResume: raw.canResume === true,
774
+ canResume,
775
+ continuationModes,
769
776
  canBranch: raw.canBranch === true,
770
777
  canAttach: raw.canAttach === true,
771
778
  canDetach: raw.canDetach === true,
@@ -818,9 +825,11 @@ export function sessionDisplayName(session) {
818
825
  export function sessionActivity(state, row) {
819
826
  if (state.needsInput && row.active) return 'needs-input';
820
827
  if (state.busy && row.active) return 'working';
828
+ // Unread/finished attention has its own badge. It must not suppress the
829
+ // transient working indicator when the harness proves this row is busy.
830
+ if (row.runtimeStatus === 'busy') return 'working';
821
831
  const attention = state.attention.find((item) => item.key === row.key)?.kind;
822
832
  if (attention) return attention;
823
- if (row.runtimeStatus === 'busy') return 'working';
824
833
  if (row.runtimeStatus === 'running') return 'running';
825
834
  if (row.live || row.runtimeStatus === 'idle') return 'recent';
826
835
  return 'idle';
package/embed.mjs CHANGED
@@ -20,6 +20,7 @@ var DEFAULT_LABELS = Object.freeze({
20
20
  searchChats: "Search chats",
21
21
  askAgent: "Ask your agent\u2026",
22
22
  continueHere: "Continue here",
23
+ continueWithTerminal: "Continue with terminal",
23
24
  joinLive: "Join live",
24
25
  forkHere: "Fork here"
25
26
  });
@@ -35,6 +36,7 @@ var EMPTY_UI_STATE = Object.freeze({
35
36
  strategy: null,
36
37
  canSend: false,
37
38
  canResume: false,
39
+ continuationModes: Object.freeze([]),
38
40
  canBranch: false,
39
41
  canAttach: false,
40
42
  canDetach: false,
@@ -707,6 +709,8 @@ function normalizeUiState(value) {
707
709
  const pill = record(raw.pill);
708
710
  const history = record(raw.history);
709
711
  const attachError = record(raw.attachError);
712
+ const canResume = raw.canResume === true;
713
+ const continuationModes = Array.isArray(raw.continuationModes) ? [...new Set(raw.continuationModes.filter((mode) => mode === "headless" || mode === "terminal"))] : canResume ? ["headless"] : [];
710
714
  return {
711
715
  pill: { tone: ["live", "warn", "dead"].includes(pill?.tone) ? pill.tone : "off", label: string(pill?.label, "connecting\u2026") },
712
716
  startup: STARTUP.has(raw.startup) ? raw.startup : "connecting",
@@ -718,7 +722,8 @@ function normalizeUiState(value) {
718
722
  mode: MODES.has(raw.mode) ? raw.mode : "none",
719
723
  strategy: STRATEGIES.has(raw.strategy) ? raw.strategy : null,
720
724
  canSend: raw.canSend === true,
721
- canResume: raw.canResume === true,
725
+ canResume,
726
+ continuationModes,
722
727
  canBranch: raw.canBranch === true,
723
728
  canAttach: raw.canAttach === true,
724
729
  canDetach: raw.canDetach === true,
@@ -766,9 +771,9 @@ function sessionDisplayName(session) {
766
771
  function sessionActivity(state, row) {
767
772
  if (state.needsInput && row.active) return "needs-input";
768
773
  if (state.busy && row.active) return "working";
774
+ if (row.runtimeStatus === "busy") return "working";
769
775
  const attention = state.attention.find((item) => item.key === row.key)?.kind;
770
776
  if (attention) return attention;
771
- if (row.runtimeStatus === "busy") return "working";
772
777
  if (row.runtimeStatus === "running") return "running";
773
778
  if (row.live || row.runtimeStatus === "idle") return "recent";
774
779
  return "idle";
@@ -1179,6 +1184,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1179
1184
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1180
1185
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1181
1186
  const resume = canContinueHere(state);
1187
+ const terminal = resume && state.continuationModes?.includes("terminal");
1182
1188
  const join = state.canAttach;
1183
1189
  const branch = state.canBranch;
1184
1190
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -1190,7 +1196,10 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1190
1196
  /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
1191
1197
  /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
1192
1198
  ] }),
1193
- /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
1199
+ /* @__PURE__ */ jsxs3("span", { class: "scui-continuation-actions", children: [
1200
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
1201
+ terminal ? /* @__PURE__ */ jsx3("button", { type: "button", class: "scui-secondary", disabled: Boolean(state.operation), onClick: () => adapter.onIntent({ action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
1202
+ ] })
1194
1203
  ] });
1195
1204
  }
1196
1205
  function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
package/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export type UiTone = 'live' | 'warn' | 'dead' | 'off';
5
5
  export type StartupPhase = 'connecting' | 'starting' | 'discovering' | 'ready';
6
6
  export type SessionMode = 'none' | 'control' | 'mirror';
7
7
  export type ControlStrategy = 'start' | 'resume' | 'attach' | 'branch' | 'reduce' | null;
8
+ export type ContinuationMode = 'headless' | 'terminal';
8
9
  export type SessionActivity = 'idle' | 'recent' | 'running' | 'working' | 'needs-input' | 'finished' | 'failed' | 'unseen';
9
10
 
10
11
  export interface HarnessOption {
@@ -234,6 +235,8 @@ export interface SupercodeUiState {
234
235
  strategy: ControlStrategy;
235
236
  canSend: boolean;
236
237
  canResume: boolean;
238
+ /** Execution strategies the current host can actually provide. Headless is the default. */
239
+ continuationModes: ContinuationMode[];
237
240
  canBranch: boolean;
238
241
  canAttach: boolean;
239
242
  canDetach: boolean;
@@ -285,7 +288,7 @@ export type SupercodeUiIntent =
285
288
  | { action: 'draft'; text: string }
286
289
  | { action: 'send'; text: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
287
290
  | { action: 'new'; harness: HarnessId; text: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
288
- | { action: 'resume' }
291
+ | { action: 'resume'; mode?: ContinuationMode }
289
292
  | { action: 'join' }
290
293
  | { action: 'detach' }
291
294
  | { action: 'branch'; targetHarness?: HarnessId }
@@ -316,6 +319,7 @@ export interface MessengerLabels {
316
319
  searchChats: string;
317
320
  askAgent: string;
318
321
  continueHere: string;
322
+ continueWithTerminal?: string;
319
323
  joinLive: string;
320
324
  forkHere: string;
321
325
  }
package/messenger.mjs CHANGED
@@ -17,6 +17,7 @@ var DEFAULT_LABELS = Object.freeze({
17
17
  searchChats: "Search chats",
18
18
  askAgent: "Ask your agent\u2026",
19
19
  continueHere: "Continue here",
20
+ continueWithTerminal: "Continue with terminal",
20
21
  joinLive: "Join live",
21
22
  forkHere: "Fork here"
22
23
  });
@@ -32,6 +33,7 @@ var EMPTY_UI_STATE = Object.freeze({
32
33
  strategy: null,
33
34
  canSend: false,
34
35
  canResume: false,
36
+ continuationModes: Object.freeze([]),
35
37
  canBranch: false,
36
38
  canAttach: false,
37
39
  canDetach: false,
@@ -704,6 +706,8 @@ function normalizeUiState(value) {
704
706
  const pill = record(raw.pill);
705
707
  const history = record(raw.history);
706
708
  const attachError = record(raw.attachError);
709
+ const canResume = raw.canResume === true;
710
+ const continuationModes = Array.isArray(raw.continuationModes) ? [...new Set(raw.continuationModes.filter((mode) => mode === "headless" || mode === "terminal"))] : canResume ? ["headless"] : [];
707
711
  return {
708
712
  pill: { tone: ["live", "warn", "dead"].includes(pill?.tone) ? pill.tone : "off", label: string(pill?.label, "connecting\u2026") },
709
713
  startup: STARTUP.has(raw.startup) ? raw.startup : "connecting",
@@ -715,7 +719,8 @@ function normalizeUiState(value) {
715
719
  mode: MODES.has(raw.mode) ? raw.mode : "none",
716
720
  strategy: STRATEGIES.has(raw.strategy) ? raw.strategy : null,
717
721
  canSend: raw.canSend === true,
718
- canResume: raw.canResume === true,
722
+ canResume,
723
+ continuationModes,
719
724
  canBranch: raw.canBranch === true,
720
725
  canAttach: raw.canAttach === true,
721
726
  canDetach: raw.canDetach === true,
@@ -763,9 +768,9 @@ function sessionDisplayName(session) {
763
768
  function sessionActivity(state, row) {
764
769
  if (state.needsInput && row.active) return "needs-input";
765
770
  if (state.busy && row.active) return "working";
771
+ if (row.runtimeStatus === "busy") return "working";
766
772
  const attention = state.attention.find((item) => item.key === row.key)?.kind;
767
773
  if (attention) return attention;
768
- if (row.runtimeStatus === "busy") return "working";
769
774
  if (row.runtimeStatus === "running") return "running";
770
775
  if (row.live || row.runtimeStatus === "idle") return "recent";
771
776
  return "idle";
@@ -1176,6 +1181,7 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1176
1181
  const row = attached?.key ? state.sessions.find((item) => item.key === attached.key) : null;
1177
1182
  const activeElsewhere = row?.runtimeStatus === "running" || row?.runtimeStatus === "busy" || row?.runtimeStatus === "idle";
1178
1183
  const resume = canContinueHere(state);
1184
+ const terminal = resume && state.continuationModes?.includes("terminal");
1179
1185
  const join = state.canAttach;
1180
1186
  const branch = state.canBranch;
1181
1187
  if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
@@ -1187,7 +1193,10 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
1187
1193
  /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
1188
1194
  /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
1189
1195
  ] }),
1190
- /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
1196
+ /* @__PURE__ */ jsxs3("span", { class: "scui-continuation-actions", children: [
1197
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume", mode: "headless" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere }),
1198
+ terminal ? /* @__PURE__ */ jsx3("button", { type: "button", class: "scui-secondary", disabled: Boolean(state.operation), onClick: () => adapter.onIntent({ action: "resume", mode: "terminal" }), children: labels.continueWithTerminal ?? DEFAULT_LABELS.continueWithTerminal }) : null
1199
+ ] })
1191
1200
  ] });
1192
1201
  }
1193
1202
  function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "type": "module",
5
5
  "description": "Composable default UI kit for Supercode-powered coding-agent experiences",
6
6
  "exports": {
package/sessions.mjs CHANGED
@@ -17,6 +17,7 @@ var DEFAULT_LABELS = Object.freeze({
17
17
  searchChats: "Search chats",
18
18
  askAgent: "Ask your agent\u2026",
19
19
  continueHere: "Continue here",
20
+ continueWithTerminal: "Continue with terminal",
20
21
  joinLive: "Join live",
21
22
  forkHere: "Fork here"
22
23
  });
@@ -32,6 +33,7 @@ var EMPTY_UI_STATE = Object.freeze({
32
33
  strategy: null,
33
34
  canSend: false,
34
35
  canResume: false,
36
+ continuationModes: Object.freeze([]),
35
37
  canBranch: false,
36
38
  canAttach: false,
37
39
  canDetach: false,
@@ -81,9 +83,9 @@ function sessionDisplayName(session) {
81
83
  function sessionActivity(state, row) {
82
84
  if (state.needsInput && row.active) return "needs-input";
83
85
  if (state.busy && row.active) return "working";
86
+ if (row.runtimeStatus === "busy") return "working";
84
87
  const attention = state.attention.find((item) => item.key === row.key)?.kind;
85
88
  if (attention) return attention;
86
- if (row.runtimeStatus === "busy") return "working";
87
89
  if (row.runtimeStatus === "running") return "running";
88
90
  if (row.live || row.runtimeStatus === "idle") return "recent";
89
91
  return "idle";
package/settings.mjs CHANGED
@@ -17,6 +17,7 @@ var DEFAULT_LABELS = Object.freeze({
17
17
  searchChats: "Search chats",
18
18
  askAgent: "Ask your agent\u2026",
19
19
  continueHere: "Continue here",
20
+ continueWithTerminal: "Continue with terminal",
20
21
  joinLive: "Join live",
21
22
  forkHere: "Fork here"
22
23
  });
@@ -32,6 +33,7 @@ var EMPTY_UI_STATE = Object.freeze({
32
33
  strategy: null,
33
34
  canSend: false,
34
35
  canResume: false,
36
+ continuationModes: Object.freeze([]),
35
37
  canBranch: false,
36
38
  canAttach: false,
37
39
  canDetach: false,
package/styles.css CHANGED
@@ -179,7 +179,7 @@
179
179
  .scui-plan > summary { display:flex; justify-content:space-between; padding:6px 8px; border:1px solid var(--scui-border); border-radius:7px; background:var(--scui-bg-raised) }.scui-plan ol { display:grid; gap:4px; box-sizing:border-box; max-height:140px; margin:5px 0 0; padding:7px 8px 7px 25px; overflow:auto; border:1px solid var(--scui-border); border-radius:7px }.scui-plan li[data-status="completed"] { color:var(--scui-fg); text-decoration:line-through }
180
180
  .scui-working { display:flex; align-items:center; gap:4px; color:var(--scui-muted) }.scui-working > span { color:var(--scui-accent) }.scui-working > i { width:4px; height:4px; border-radius:50%; background:currentColor; animation:scui-dots 1.2s infinite }.scui-working > i:nth-of-type(2) { animation-delay:.15s }.scui-working > i:nth-of-type(3) { animation-delay:.3s }
181
181
 
182
- .scui-continuation { display:flex; align-items:center; gap:8px; padding:7px 9px; border-top:1px solid var(--scui-border); background:var(--scui-bg-raised) }.scui-continuation > span { display:grid; flex:1 }.scui-continuation small { color:var(--scui-muted); font-size:10px }.scui-continuation button { padding:5px 8px; border:1px solid var(--scui-accent); border-radius:7px; background:transparent; color:var(--scui-accent); cursor:pointer }
182
+ .scui-continuation { display:flex; align-items:center; gap:8px; padding:7px 9px; border-top:1px solid var(--scui-border); background:var(--scui-bg-raised) }.scui-continuation > span:not(.scui-continuation-actions) { display:grid; flex:1 }.scui-continuation small { color:var(--scui-muted); font-size:10px }.scui-continuation-actions { display:flex; align-items:center; gap:5px; flex-wrap:wrap; justify-content:flex-end }.scui-continuation button { padding:5px 8px; border:1px solid var(--scui-accent); border-radius:7px; background:transparent; color:var(--scui-accent); cursor:pointer; white-space:nowrap }.scui-continuation button.scui-secondary { border-color:var(--scui-border-strong); color:var(--scui-fg) }
183
183
  .scui-compose { flex:none; padding:8px; border-top:1px solid var(--scui-border); background:var(--scui-bg-raised) }
184
184
  .scui-envelope { display:flex; align-items:flex-end; gap:7px; padding:7px; border:1px solid var(--scui-border-strong); border-radius:16px; background:var(--scui-bg) }.scui-envelope textarea { flex:1; min-width:0; min-height:34px; max-height:150px; resize:none; overflow-y:hidden; border:0; outline:0; background:transparent; color:var(--scui-fg) }.scui-envelope > span { display:flex; gap:4px }
185
185
  .scui-envelope.scui-drop-target { border-color:var(--scui-accent); background:color-mix(in srgb,var(--scui-accent) 7%,var(--scui-bg)); box-shadow:0 0 0 2px color-mix(in srgb,var(--scui-accent) 16%,transparent) }