@volter-ai-dev/supercode-ui 0.1.34 → 0.1.36
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 +6 -0
- package/components.mjs +1 -1
- package/controller.d.ts +39 -0
- package/controller.mjs +108 -2
- package/core.d.ts +1 -0
- package/core.mjs +137 -0
- package/embed.mjs +1 -1
- package/index.d.ts +2 -0
- package/messenger.mjs +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -117,6 +117,9 @@ before counting visible rows, retains native timestamps and typed tool/request l
|
|
|
117
117
|
the uncapped residue count truthful. Hosts can narrow those limits with `ClientProjectionOptions`
|
|
118
118
|
and can overlay a machine-wide session inventory, pagination state, attention, drafts, attachment
|
|
119
119
|
errors, and owned/attached identities without reimplementing transcript or capability semantics.
|
|
120
|
+
`projectSessionInventory` performs the corresponding title, latest-preview, activity, path, age,
|
|
121
|
+
sorting, and row projection for raw machine-wide descriptors; the trusted host supplies only its
|
|
122
|
+
opaque-key callback and retains the reversible locator map.
|
|
120
123
|
|
|
121
124
|
## Modularity contract
|
|
122
125
|
|
|
@@ -140,6 +143,9 @@ errors, and owned/attached identities without reimplementing transcript or capab
|
|
|
140
143
|
|
|
141
144
|
`SupercodeUiState` is a transport-safe view model, not a duplicate controller. A trusted host maps
|
|
142
145
|
`SupercodeController` snapshots and persisted inventory into it, then handles `SupercodeUiIntent`.
|
|
146
|
+
An iframe or extension host can pass unknown payloads through `parseSupercodeUiIntent` before using
|
|
147
|
+
`dispatchControllerIntent`; transport-specific operations can be intercepted with `handleIntent`
|
|
148
|
+
while ordinary controller semantics continue through the shared dispatcher.
|
|
143
149
|
The browser cannot supply locators, credentials, policy, environment variables, or arbitrary
|
|
144
150
|
materialization paths. Session keys and target harnesses must be revalidated by the host.
|
|
145
151
|
Harness configuration is similarly narrow: the UI can only submit a choice from the revisioned
|
package/components.mjs
CHANGED
|
@@ -2414,7 +2414,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2414
2414
|
const back = useRef7(null);
|
|
2415
2415
|
const harness = state.attached?.harness ?? state.harness;
|
|
2416
2416
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
2417
|
-
const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
2417
|
+
const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.messaging === "live_peer" ? "Live" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
2418
2418
|
const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce || state.interopSettings || state.interopSettingsError;
|
|
2419
2419
|
useEffect8(() => {
|
|
2420
2420
|
if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
|
package/controller.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
SessionArtifact,
|
|
3
|
+
SessionDescriptor,
|
|
3
4
|
SessionFormat,
|
|
4
5
|
} from '@volter-ai-dev/supercode-harness-sdk';
|
|
5
6
|
import type {
|
|
@@ -55,6 +56,30 @@ export function projectClientSnapshot(
|
|
|
55
56
|
options?: ClientProjectionOptions,
|
|
56
57
|
): SupercodeUiState;
|
|
57
58
|
|
|
59
|
+
export interface SessionInventoryProjectionOptions {
|
|
60
|
+
/** Mint a transport-safe key while retaining the reversible locator only in the trusted host. */
|
|
61
|
+
keyFor(descriptor: SessionDescriptor): string;
|
|
62
|
+
now?: number;
|
|
63
|
+
home?: string;
|
|
64
|
+
active?: { harness: string; sessionId: string | null } | null;
|
|
65
|
+
maxSessions?: number;
|
|
66
|
+
liveWindowMs?: number;
|
|
67
|
+
preserveOrder?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function sessionConversationUpdatedAt(descriptor: SessionDescriptor): number | null;
|
|
71
|
+
export function sessionDescriptorRuntimeStatus(
|
|
72
|
+
descriptor: SessionDescriptor,
|
|
73
|
+
): 'running' | 'busy' | 'idle' | null;
|
|
74
|
+
export interface ProjectedSessionRowModel extends SessionRowModel {
|
|
75
|
+
preview: string;
|
|
76
|
+
previewUpdatedAt: number | null;
|
|
77
|
+
}
|
|
78
|
+
export function projectSessionInventory(
|
|
79
|
+
descriptors: readonly SessionDescriptor[],
|
|
80
|
+
options: SessionInventoryProjectionOptions,
|
|
81
|
+
): ProjectedSessionRowModel[];
|
|
82
|
+
|
|
58
83
|
export interface ResolvableTranscriptImage {
|
|
59
84
|
id?: string;
|
|
60
85
|
label: string;
|
|
@@ -79,6 +104,8 @@ export interface ControllerBindingOptions {
|
|
|
79
104
|
onIntent?: (intent: SupercodeUiIntent) => void | Promise<void>;
|
|
80
105
|
onUnsupported?: (intent: SupercodeUiIntent) => void | Promise<void>;
|
|
81
106
|
onError?: (error: unknown, intent: SupercodeUiIntent) => void | Promise<void>;
|
|
107
|
+
/** Return true after handling an intent to replace the standard controller mapping. */
|
|
108
|
+
handleIntent?: (intent: SupercodeUiIntent) => boolean | Promise<boolean>;
|
|
82
109
|
onArtifact?: (artifact: SessionArtifact, intent: Extract<SupercodeUiIntent, { action: 'export' }>) => void | Promise<void>;
|
|
83
110
|
onDraft?: (text: string) => void | Promise<void>;
|
|
84
111
|
onAcknowledge?: (key: string) => void | Promise<void>;
|
|
@@ -90,6 +117,18 @@ export interface ControllerBindingOptions {
|
|
|
90
117
|
resolveImage?: UiAdapter['resolveImage'];
|
|
91
118
|
}
|
|
92
119
|
|
|
120
|
+
export interface ControllerIntentTarget {
|
|
121
|
+
getSnapshot(): SupercodeClientSnapshot;
|
|
122
|
+
dispatch(action: Parameters<SupercodeController['dispatch']>[0]): Promise<SupercodeClientSnapshot>;
|
|
123
|
+
exportSession?: SupercodeController['exportSession'];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function dispatchControllerIntent(
|
|
127
|
+
controller: ControllerIntentTarget,
|
|
128
|
+
intent: SupercodeUiIntent,
|
|
129
|
+
options?: ControllerBindingOptions,
|
|
130
|
+
): Promise<void>;
|
|
131
|
+
|
|
93
132
|
export interface SupercodeUiBinding {
|
|
94
133
|
adapter: UiAdapter;
|
|
95
134
|
getState(): SupercodeUiState;
|
package/controller.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { conversationPreviewText } from '@volter-ai-dev/supercode-client';
|
|
1
2
|
import { createToolPresentation, normalizeUiState, relativeAge } from './core.mjs';
|
|
2
3
|
|
|
3
4
|
const HARNESS_LABELS = {
|
|
@@ -28,6 +29,110 @@ function workspaceName(cwd) {
|
|
|
28
29
|
return cwd.replaceAll('\\', '/').split('/').filter(Boolean).at(-1) ?? cwd;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function shortWorkspacePath(cwd, home) {
|
|
33
|
+
if (typeof cwd !== 'string' || !cwd) return '';
|
|
34
|
+
const root = typeof home === 'string' && home.endsWith('/') ? home.slice(0, -1) : home;
|
|
35
|
+
return root && (cwd === root || cwd.startsWith(`${root}/`)) ? `~${cwd.slice(root.length)}` : cwd;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function compactTopic(value) {
|
|
39
|
+
const text = typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
40
|
+
if (text.length <= 72) return text;
|
|
41
|
+
const prefix = text.slice(0, 71);
|
|
42
|
+
const boundary = prefix.lastIndexOf(' ');
|
|
43
|
+
return `${prefix.slice(0, boundary >= 40 ? boundary : 71).trimEnd()}…`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function sessionDescriptorRuntimeStatus(descriptor) {
|
|
47
|
+
const activity = descriptor?.activity;
|
|
48
|
+
if (activity && typeof activity === 'object') {
|
|
49
|
+
if (activity.presence === 'persisted') return null;
|
|
50
|
+
if (activity.turn === 'working') return 'busy';
|
|
51
|
+
if (activity.turn === 'idle' || activity.turn === 'needs_input') return 'idle';
|
|
52
|
+
if (activity.presence === 'running' || activity.presence === 'shutting_down') return 'running';
|
|
53
|
+
}
|
|
54
|
+
return ['running', 'busy', 'idle'].includes(descriptor?.live_status)
|
|
55
|
+
? descriptor.live_status
|
|
56
|
+
: null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function descriptorPreview(descriptor) {
|
|
60
|
+
for (const candidate of descriptor?.latest_message_candidates ?? []) {
|
|
61
|
+
const text = conversationPreviewText([candidate]);
|
|
62
|
+
if (!text) continue;
|
|
63
|
+
return { text, updatedAt: timestampFromMetadata(candidate.metadata) };
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function descriptorTitle(descriptor) {
|
|
69
|
+
const nativeTitle = typeof descriptor?.title === 'string' ? descriptor.title.trim() : '';
|
|
70
|
+
const workspace = workspaceName(descriptor?.cwd);
|
|
71
|
+
if (nativeTitle && nativeTitle !== workspace && nativeTitle !== descriptor?.cwd) {
|
|
72
|
+
return compactTopic(nativeTitle);
|
|
73
|
+
}
|
|
74
|
+
if (descriptor?.locator?.harness !== 'claude-code' && descriptor?.locator?.harness !== 'codex') {
|
|
75
|
+
return compactTopic(nativeTitle || workspace || descriptor?.locator?.session_id?.slice(0, 8)) || 'Untitled chat';
|
|
76
|
+
}
|
|
77
|
+
const openingUserMessages = (descriptor?.preview_candidates ?? []).filter(
|
|
78
|
+
(candidate) => candidate?.role === undefined || candidate.role === 'user',
|
|
79
|
+
);
|
|
80
|
+
return compactTopic(conversationPreviewText(openingUserMessages)) || 'Untitled chat';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Timestamp represented by a messenger row, independent of native store heartbeat writes. */
|
|
84
|
+
export function sessionConversationUpdatedAt(descriptor) {
|
|
85
|
+
return descriptorPreview(descriptor)?.updatedAt ?? descriptor?.updated_at_ms ?? null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Project trusted machine-wide descriptors into the same bounded rows as controller snapshots.
|
|
90
|
+
* The host supplies opaque keys because only it may retain the reversible locator mapping.
|
|
91
|
+
*/
|
|
92
|
+
export function projectSessionInventory(descriptors, options) {
|
|
93
|
+
if (typeof options?.keyFor !== 'function') {
|
|
94
|
+
throw new TypeError('projectSessionInventory requires an opaque keyFor callback');
|
|
95
|
+
}
|
|
96
|
+
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
97
|
+
const maxSessions = positiveInteger(options.maxSessions, DEFAULT_MAX_SESSIONS);
|
|
98
|
+
const liveWindowMs = positiveInteger(options.liveWindowMs, DEFAULT_LIVE_WINDOW_MS);
|
|
99
|
+
const active = options.active;
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
const rows = [];
|
|
102
|
+
const ordered = options.preserveOrder
|
|
103
|
+
? [...(descriptors ?? [])]
|
|
104
|
+
: [...(descriptors ?? [])].sort(
|
|
105
|
+
(left, right) => (sessionConversationUpdatedAt(right) ?? 0) - (sessionConversationUpdatedAt(left) ?? 0),
|
|
106
|
+
);
|
|
107
|
+
for (const descriptor of ordered) {
|
|
108
|
+
if (!descriptor?.locator) continue;
|
|
109
|
+
const key = options.keyFor(descriptor);
|
|
110
|
+
if (typeof key !== 'string' || !key || seen.has(key)) continue;
|
|
111
|
+
seen.add(key);
|
|
112
|
+
const preview = descriptorPreview(descriptor);
|
|
113
|
+
const updatedAt = typeof descriptor.updated_at_ms === 'number' ? descriptor.updated_at_ms : null;
|
|
114
|
+
rows.push({
|
|
115
|
+
key,
|
|
116
|
+
harness: descriptor.locator.harness,
|
|
117
|
+
name: workspaceName(descriptor.cwd) || 'no workspace',
|
|
118
|
+
cwd: shortWorkspacePath(descriptor.cwd, options.home),
|
|
119
|
+
title: descriptorTitle(descriptor),
|
|
120
|
+
preview: preview?.text ?? '',
|
|
121
|
+
age: relativeAge(preview?.updatedAt ?? updatedAt, now),
|
|
122
|
+
previewUpdatedAt: preview?.updatedAt ?? null,
|
|
123
|
+
updatedAt,
|
|
124
|
+
messages: typeof descriptor.message_count === 'number' ? descriptor.message_count : null,
|
|
125
|
+
active: active?.sessionId != null
|
|
126
|
+
&& descriptor.locator.harness === active.harness
|
|
127
|
+
&& descriptor.locator.session_id === active.sessionId,
|
|
128
|
+
live: updatedAt !== null && now - updatedAt <= liveWindowMs,
|
|
129
|
+
runtimeStatus: sessionDescriptorRuntimeStatus(descriptor),
|
|
130
|
+
});
|
|
131
|
+
if (rows.length >= maxSessions) break;
|
|
132
|
+
}
|
|
133
|
+
return rows;
|
|
134
|
+
}
|
|
135
|
+
|
|
31
136
|
function payloadText(payload) {
|
|
32
137
|
if (typeof payload === 'string') return payload;
|
|
33
138
|
try {
|
|
@@ -440,7 +545,8 @@ export function createClientProjection(snapshot, options = {}) {
|
|
|
440
545
|
};
|
|
441
546
|
}
|
|
442
547
|
|
|
443
|
-
async function
|
|
548
|
+
export async function dispatchControllerIntent(controller, intent, options = {}) {
|
|
549
|
+
if (await options.handleIntent?.(intent)) return;
|
|
444
550
|
const snapshot = controller.getSnapshot();
|
|
445
551
|
const active = snapshot.activeSessionKey;
|
|
446
552
|
if (intent.action === 'mounted') return;
|
|
@@ -496,7 +602,7 @@ export function createControllerBinding(controller, options = {}) {
|
|
|
496
602
|
const adapter = {
|
|
497
603
|
onIntent(intent) {
|
|
498
604
|
return Promise.resolve(options.onIntent?.(intent))
|
|
499
|
-
.then(() =>
|
|
605
|
+
.then(() => dispatchControllerIntent(controller, intent, options))
|
|
500
606
|
.catch((error) => options.onError?.(error, intent))
|
|
501
607
|
.then(() => undefined);
|
|
502
608
|
},
|
package/core.d.ts
CHANGED
package/core.mjs
CHANGED
|
@@ -920,3 +920,140 @@ export function terminalCommand(handoff) {
|
|
|
920
920
|
export function isSendKey(event) {
|
|
921
921
|
return event.key === 'Enter' && !event.shiftKey && !event.isComposing;
|
|
922
922
|
}
|
|
923
|
+
|
|
924
|
+
function intentRecord(value) {
|
|
925
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function intentKeys(value, allowed) {
|
|
929
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function intentContext(value) {
|
|
933
|
+
if (value === undefined) return [];
|
|
934
|
+
if (!Array.isArray(value) || value.length > 32) return null;
|
|
935
|
+
const items = [];
|
|
936
|
+
for (const candidate of value) {
|
|
937
|
+
const item = intentRecord(candidate);
|
|
938
|
+
if (!item || !intentKeys(item, ['id', 'kind', 'label', 'detail'])) return null;
|
|
939
|
+
if (typeof item.label !== 'string' || !item.label.trim() || item.label.length > 200) return null;
|
|
940
|
+
if (typeof item.detail !== 'string' || item.detail.length > 20_000) return null;
|
|
941
|
+
if (item.id !== undefined && (typeof item.id !== 'string' || !item.id || item.id.length > 2_000)) return null;
|
|
942
|
+
if (item.kind !== undefined && (typeof item.kind !== 'string' || !item.kind || item.kind.length > 100)) return null;
|
|
943
|
+
items.push({
|
|
944
|
+
...(typeof item.id === 'string' ? { id: item.id } : {}),
|
|
945
|
+
...(typeof item.kind === 'string' ? { kind: item.kind } : {}),
|
|
946
|
+
label: item.label.trim(),
|
|
947
|
+
detail: item.detail,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
return items;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function intentImages(value) {
|
|
954
|
+
if (value === undefined) return [];
|
|
955
|
+
if (!Array.isArray(value) || value.length > 4) return null;
|
|
956
|
+
let totalChars = 0;
|
|
957
|
+
const items = [];
|
|
958
|
+
for (const candidate of value) {
|
|
959
|
+
const item = intentRecord(candidate);
|
|
960
|
+
if (!item || !intentKeys(item, ['id', 'label', 'url'])) return null;
|
|
961
|
+
if (typeof item.label !== 'string' || !item.label.trim() || item.label.length > 200) return null;
|
|
962
|
+
if (typeof item.url !== 'string' || !/^(?:data:image\/|https?:\/\/)/.test(item.url)) return null;
|
|
963
|
+
if (item.url.length > 12 * 1024 * 1024) return null;
|
|
964
|
+
totalChars += item.url.length;
|
|
965
|
+
if (totalChars > 32 * 1024 * 1024) return null;
|
|
966
|
+
if (item.id !== undefined && (typeof item.id !== 'string' || !item.id || item.id.length > 2_000)) return null;
|
|
967
|
+
items.push({ ...(typeof item.id === 'string' ? { id: item.id } : {}), label: item.label.trim(), url: item.url });
|
|
968
|
+
}
|
|
969
|
+
return items;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function intentJson(value, seen = new Set()) {
|
|
973
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return true;
|
|
974
|
+
if (typeof value === 'number') return Number.isFinite(value);
|
|
975
|
+
if (typeof value !== 'object' || seen.has(value)) return false;
|
|
976
|
+
seen.add(value);
|
|
977
|
+
const valid = Array.isArray(value)
|
|
978
|
+
? value.length <= 1_000 && value.every((item) => intentJson(item, seen))
|
|
979
|
+
: Object.keys(value).length <= 1_000 && Object.values(value).every((item) => intentJson(item, seen));
|
|
980
|
+
seen.delete(value);
|
|
981
|
+
return valid;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/** Runtime validation for the transport-safe intent union emitted by the default UI. */
|
|
985
|
+
export function parseSupercodeUiIntent(value) {
|
|
986
|
+
const intent = intentRecord(value);
|
|
987
|
+
if (!intent || typeof intent.action !== 'string') return null;
|
|
988
|
+
const exact = (...keys) => intentKeys(intent, ['action', ...keys]);
|
|
989
|
+
const actionOnly = ['mounted', 'loadSessions', 'loadEarlier', 'join', 'detach', 'terminal', 'interrupt', 'refresh', 'release'];
|
|
990
|
+
if (actionOnly.includes(intent.action)) return exact() ? { action: intent.action } : null;
|
|
991
|
+
if (intent.action === 'attach' || intent.action === 'ack') {
|
|
992
|
+
return exact('key') && typeof intent.key === 'string' && intent.key.trim() && intent.key.length <= 4_000
|
|
993
|
+
? { action: intent.action, key: intent.key.trim() }
|
|
994
|
+
: null;
|
|
995
|
+
}
|
|
996
|
+
if (intent.action === 'draft') {
|
|
997
|
+
return exact('text') && typeof intent.text === 'string' && intent.text.length <= 50_000
|
|
998
|
+
? { action: 'draft', text: intent.text }
|
|
999
|
+
: null;
|
|
1000
|
+
}
|
|
1001
|
+
if (intent.action === 'send' || intent.action === 'new') {
|
|
1002
|
+
const allowed = intent.action === 'new'
|
|
1003
|
+
? exact('harness', 'mode', 'text', 'context', 'images')
|
|
1004
|
+
: exact('text', 'context', 'images');
|
|
1005
|
+
const context = intentContext(intent.context);
|
|
1006
|
+
const images = intentImages(intent.images);
|
|
1007
|
+
if (!allowed || typeof intent.text !== 'string' || context === null || images === null) return null;
|
|
1008
|
+
if (!intent.text.trim() && images.length === 0) return null;
|
|
1009
|
+
const extras = {
|
|
1010
|
+
...(context.length ? { context } : {}),
|
|
1011
|
+
...(images.length ? { images } : {}),
|
|
1012
|
+
};
|
|
1013
|
+
if (intent.action === 'send') return { action: 'send', text: intent.text, ...extras };
|
|
1014
|
+
if (typeof intent.harness !== 'string' || !intent.harness.trim() || intent.harness.length > 100) return null;
|
|
1015
|
+
const mode = intent.mode === undefined ? undefined : intent.mode;
|
|
1016
|
+
if (mode !== undefined && mode !== 'headless' && mode !== 'terminal') return null;
|
|
1017
|
+
if (mode === 'terminal' && !intent.text.trim()) return null;
|
|
1018
|
+
return { action: 'new', harness: intent.harness.trim(), ...(mode ? { mode } : {}), text: intent.text, ...extras };
|
|
1019
|
+
}
|
|
1020
|
+
if (intent.action === 'resume') {
|
|
1021
|
+
return exact('mode') && (intent.mode === undefined || intent.mode === 'headless' || intent.mode === 'terminal')
|
|
1022
|
+
? { action: 'resume', ...(intent.mode ? { mode: intent.mode } : {}) }
|
|
1023
|
+
: null;
|
|
1024
|
+
}
|
|
1025
|
+
if (intent.action === 'branch' || intent.action === 'reduce') {
|
|
1026
|
+
if (!exact('targetHarness')) return null;
|
|
1027
|
+
if (intent.targetHarness === undefined) return { action: intent.action };
|
|
1028
|
+
return typeof intent.targetHarness === 'string' && intent.targetHarness.trim() && intent.targetHarness.length <= 100
|
|
1029
|
+
? { action: intent.action, targetHarness: intent.targetHarness.trim() }
|
|
1030
|
+
: null;
|
|
1031
|
+
}
|
|
1032
|
+
if (intent.action === 'export') {
|
|
1033
|
+
return exact('targetHarness') && typeof intent.targetHarness === 'string' && intent.targetHarness.trim() && intent.targetHarness.length <= 100
|
|
1034
|
+
? { action: 'export', targetHarness: intent.targetHarness.trim() }
|
|
1035
|
+
: null;
|
|
1036
|
+
}
|
|
1037
|
+
if (intent.action === 'respond') {
|
|
1038
|
+
if (!exact('requestId', 'optionId') || !intentJson(intent.requestId)) return null;
|
|
1039
|
+
return intent.optionId === null || typeof intent.optionId === 'string'
|
|
1040
|
+
? { action: 'respond', requestId: intent.requestId, optionId: intent.optionId }
|
|
1041
|
+
: null;
|
|
1042
|
+
}
|
|
1043
|
+
if (intent.action === 'configureHarness') {
|
|
1044
|
+
if (!exact('harness', 'changes', 'expectedRevision')) return null;
|
|
1045
|
+
if (typeof intent.harness !== 'string' || !intent.harness || intent.harness.length > 100) return null;
|
|
1046
|
+
if (typeof intent.expectedRevision !== 'string' || !intent.expectedRevision || intent.expectedRevision.length > 500) return null;
|
|
1047
|
+
if (!Array.isArray(intent.changes) || !intent.changes.length || intent.changes.length > 20) return null;
|
|
1048
|
+
const changes = [];
|
|
1049
|
+
for (const candidate of intent.changes) {
|
|
1050
|
+
const change = intentRecord(candidate);
|
|
1051
|
+
if (!change || !intentKeys(change, ['key', 'value'])) return null;
|
|
1052
|
+
if (typeof change.key !== 'string' || !change.key || change.key.length > 200) return null;
|
|
1053
|
+
if (change.value !== null && (typeof change.value !== 'string' || change.value.length > 500)) return null;
|
|
1054
|
+
changes.push({ key: change.key, value: change.value });
|
|
1055
|
+
}
|
|
1056
|
+
return { action: 'configureHarness', harness: intent.harness, changes, expectedRevision: intent.expectedRevision };
|
|
1057
|
+
}
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
package/embed.mjs
CHANGED
|
@@ -2393,7 +2393,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2393
2393
|
const back = useRef7(null);
|
|
2394
2394
|
const harness = state.attached?.harness ?? state.harness;
|
|
2395
2395
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
2396
|
-
const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
2396
|
+
const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.messaging === "live_peer" ? "Live" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
2397
2397
|
const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce || state.interopSettings || state.interopSettingsError;
|
|
2398
2398
|
useEffect8(() => {
|
|
2399
2399
|
if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
|
package/index.d.ts
CHANGED
|
@@ -449,6 +449,8 @@ export function canContinueHere(state: SupercodeUiState): boolean;
|
|
|
449
449
|
export function operationLabel(operation: string | null): string;
|
|
450
450
|
export function terminalCommand(handoff: NonNullable<SupercodeUiState['terminalHandoff']>): string;
|
|
451
451
|
export function isSendKey(event: Pick<KeyboardEvent, 'key' | 'shiftKey' | 'isComposing'>): boolean;
|
|
452
|
+
/** Strictly validate an unknown browser/wire payload as a bounded UI intent. */
|
|
453
|
+
export function parseSupercodeUiIntent(value: unknown): SupercodeUiIntent | null;
|
|
452
454
|
|
|
453
455
|
export function HarnessLogo(props: { id: HarnessId; activity?: SessionActivity; size?: number; onMissingLogo?(id: string): void }): VNode | null;
|
|
454
456
|
export type UiIconName = 'attach' | 'back' | 'check' | 'chevron' | 'close' | 'copy' | 'down' | 'menu' | 'plus' | 'search' | 'send' | 'stop';
|
package/messenger.mjs
CHANGED
|
@@ -2390,7 +2390,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
|
|
|
2390
2390
|
const back = useRef7(null);
|
|
2391
2391
|
const harness = state.attached?.harness ?? state.harness;
|
|
2392
2392
|
const title = state.attached ? sessionDisplayName(state.attached) : harnessDisplayName(harness) || "Agent chat";
|
|
2393
|
-
const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
2393
|
+
const status = state.needsInput ? "Needs input" : pendingStatus === "failed" ? "Send failed" : state.busy ? "Working" : pendingStatus === "sending" ? "Sending" : pendingStatus === "editing" ? "Editing message" : state.messaging === "live_peer" ? "Live" : state.mode === "mirror" ? "Read-only" : "Ready";
|
|
2394
2394
|
const menu = state.canDetach || state.canOpenTerminal || state.canBranch || state.canAttach || state.canExport || state.canReduce || state.interopSettings || state.interopSettingsError;
|
|
2395
2395
|
useEffect8(() => {
|
|
2396
2396
|
if (state.mode === "mirror" && !state.canSend) back.current?.focus({ preventScroll: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter-ai-dev/supercode-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Composable default UI kit for Supercode-powered coding-agent experiences",
|
|
6
6
|
"exports": {
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
109
|
"devDependencies": {
|
|
110
|
+
"@volter-ai-dev/supercode-client": "0.3.22",
|
|
110
111
|
"@storybook/addon-a11y": "10.5.9",
|
|
111
112
|
"@storybook/addon-docs": "10.5.9",
|
|
112
113
|
"@storybook/addon-themes": "10.5.9",
|