@vellumai/cli 0.6.5 → 0.7.0
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/AGENTS.md +8 -2
- package/package.json +1 -1
- package/src/__tests__/assistant-config.test.ts +1 -7
- package/src/__tests__/config-utils.test.ts +159 -0
- package/src/__tests__/env-drift.test.ts +10 -32
- package/src/__tests__/llm-provider-env-var-parity.test.ts +1 -21
- package/src/__tests__/multi-local.test.ts +0 -5
- package/src/__tests__/sleep.test.ts +1 -2
- package/src/__tests__/teleport.test.ts +919 -1255
- package/src/commands/env.ts +93 -0
- package/src/commands/events.ts +2 -0
- package/src/commands/exec.ts +40 -8
- package/src/commands/hatch.ts +6 -2
- package/src/commands/login.ts +89 -6
- package/src/commands/ps.ts +104 -20
- package/src/commands/retire.ts +23 -0
- package/src/commands/sleep.ts +5 -2
- package/src/commands/ssh.ts +15 -2
- package/src/commands/teleport.ts +447 -583
- package/src/commands/terminal.ts +225 -0
- package/src/commands/wake.ts +2 -1
- package/src/components/DefaultMainScreen.tsx +304 -152
- package/src/index.ts +6 -0
- package/src/lib/__tests__/docker.test.ts +50 -74
- package/src/lib/__tests__/job-polling.test.ts +278 -0
- package/src/lib/__tests__/local-runtime-client.test.ts +383 -0
- package/src/lib/__tests__/platform-client-signed-url.test.ts +405 -0
- package/src/lib/assistant-config.ts +12 -8
- package/src/lib/client-identity.ts +67 -0
- package/src/lib/config-utils.ts +97 -1
- package/src/lib/docker.ts +73 -75
- package/src/lib/environments/__tests__/paths.test.ts +2 -0
- package/src/lib/environments/resolve.ts +89 -7
- package/src/lib/environments/seeds.ts +8 -5
- package/src/lib/environments/types.ts +10 -0
- package/src/lib/hatch-local.ts +15 -120
- package/src/lib/health-check.ts +98 -0
- package/src/lib/job-polling.ts +195 -0
- package/src/lib/local-runtime-client.ts +178 -0
- package/src/lib/local.ts +139 -15
- package/src/lib/orphan-detection.ts +2 -35
- package/src/lib/platform-client.ts +215 -0
- package/src/lib/retire-local.ts +6 -2
- package/src/lib/terminal-client.ts +177 -0
- package/src/lib/terminal-session.ts +457 -0
- package/src/shared/provider-env-vars.ts +2 -3
- package/src/__tests__/orphan-detection.test.ts +0 -214
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { Box, render as inkRender, Text, useInput, useStdout } from "ink";
|
|
15
15
|
|
|
16
16
|
import { removeAssistantEntry } from "../lib/assistant-config";
|
|
17
|
+
import { getClientRegistrationHeaders } from "../lib/client-identity";
|
|
17
18
|
import { SPECIES_CONFIG, type Species } from "../lib/constants";
|
|
18
19
|
import { callDoctorDaemon, type ChatLogEntry } from "../lib/doctor-client";
|
|
19
20
|
import { checkHealth } from "../lib/health-check";
|
|
@@ -50,9 +51,7 @@ export const SLASH_COMMANDS = [
|
|
|
50
51
|
"/retire",
|
|
51
52
|
];
|
|
52
53
|
|
|
53
|
-
const POLL_INTERVAL_MS = 3000;
|
|
54
54
|
const SEND_TIMEOUT_MS = 5000;
|
|
55
|
-
const RESPONSE_POLL_INTERVAL_MS = 1000;
|
|
56
55
|
|
|
57
56
|
// ── Layout constants ──────────────────────────────────────
|
|
58
57
|
const MAX_TOTAL_WIDTH = 72;
|
|
@@ -148,11 +147,6 @@ interface AddTrustRuleResponse {
|
|
|
148
147
|
accepted: boolean;
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
interface PendingInteractionsResponse {
|
|
152
|
-
pendingConfirmation: (PendingConfirmation & { requestId: string }) | null;
|
|
153
|
-
pendingSecret: (PendingSecret & { requestId?: string }) | null;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
150
|
type TrustDecision = "always_allow" | "always_deny";
|
|
157
151
|
|
|
158
152
|
interface HealthResponse {
|
|
@@ -301,19 +295,114 @@ async function addTrustRule(
|
|
|
301
295
|
);
|
|
302
296
|
}
|
|
303
297
|
|
|
304
|
-
|
|
298
|
+
// ── SSE event types ─────────────────────────────────────────────
|
|
299
|
+
interface SseEvent {
|
|
300
|
+
type: string;
|
|
301
|
+
text?: string;
|
|
302
|
+
thinking?: string;
|
|
303
|
+
toolName?: string;
|
|
304
|
+
toolUseId?: string;
|
|
305
|
+
input?: Record<string, unknown>;
|
|
306
|
+
result?: string;
|
|
307
|
+
isError?: boolean;
|
|
308
|
+
content?: string;
|
|
309
|
+
chunk?: string;
|
|
310
|
+
message?: string;
|
|
311
|
+
conversationId?: string;
|
|
312
|
+
messageId?: string;
|
|
313
|
+
requestId?: string;
|
|
314
|
+
// confirmation_request fields
|
|
315
|
+
riskLevel?: string;
|
|
316
|
+
riskReason?: string;
|
|
317
|
+
executionTarget?: "sandbox" | "host";
|
|
318
|
+
allowlistOptions?: Array<{
|
|
319
|
+
label: string;
|
|
320
|
+
description: string;
|
|
321
|
+
pattern: string;
|
|
322
|
+
}>;
|
|
323
|
+
scopeOptions?: Array<{ label: string; scope: string }>;
|
|
324
|
+
persistentDecisionsAllowed?: boolean;
|
|
325
|
+
isContainerized?: boolean;
|
|
326
|
+
// secret_request fields
|
|
327
|
+
service?: string;
|
|
328
|
+
field?: string;
|
|
329
|
+
label?: string;
|
|
330
|
+
description?: string;
|
|
331
|
+
placeholder?: string;
|
|
332
|
+
purpose?: string;
|
|
333
|
+
allowOneTimeSend?: boolean;
|
|
334
|
+
allowedTools?: string[];
|
|
335
|
+
allowedDomains?: string[];
|
|
336
|
+
// message_complete fields
|
|
337
|
+
source?: "main" | "aux";
|
|
338
|
+
[key: string]: unknown;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Open an SSE stream to the assistant's /events endpoint.
|
|
343
|
+
* Yields unwrapped message payloads from `data:` lines, skipping
|
|
344
|
+
* heartbeat comments. The /events endpoint emits AssistantEvent
|
|
345
|
+
* envelopes (`{ id, assistantId, message: { type, ... } }`); this
|
|
346
|
+
* generator unwraps the envelope so callers switch on `.type` directly.
|
|
347
|
+
*/
|
|
348
|
+
async function* streamEvents(
|
|
305
349
|
baseUrl: string,
|
|
306
350
|
assistantId: string,
|
|
351
|
+
conversationKey: string,
|
|
352
|
+
signal: AbortSignal,
|
|
307
353
|
bearerToken?: string,
|
|
308
|
-
):
|
|
309
|
-
const params = new URLSearchParams({ conversationKey
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
354
|
+
): AsyncGenerator<SseEvent> {
|
|
355
|
+
const params = new URLSearchParams({ conversationKey });
|
|
356
|
+
const url = `${baseUrl}/v1/assistants/${assistantId}/events?${params.toString()}`;
|
|
357
|
+
const response = await fetch(url, {
|
|
358
|
+
headers: {
|
|
359
|
+
Accept: "text/event-stream",
|
|
360
|
+
...(bearerToken ? { Authorization: `Bearer ${bearerToken}` } : {}),
|
|
361
|
+
...getClientRegistrationHeaders(),
|
|
362
|
+
},
|
|
363
|
+
signal,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (!response.ok) {
|
|
367
|
+
const body = await response.text().catch(() => "");
|
|
368
|
+
throw new Error(
|
|
369
|
+
`SSE connection failed (${response.status}): ${body || response.statusText}`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (!response.body) {
|
|
373
|
+
throw new Error("No response body from SSE endpoint");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const decoder = new TextDecoder();
|
|
377
|
+
let buffer = "";
|
|
378
|
+
for await (const chunk of response.body) {
|
|
379
|
+
buffer += decoder.decode(chunk as Uint8Array, { stream: true });
|
|
380
|
+
let boundary: number;
|
|
381
|
+
while ((boundary = buffer.indexOf("\n\n")) !== -1) {
|
|
382
|
+
const frame = buffer.slice(0, boundary);
|
|
383
|
+
buffer = buffer.slice(boundary + 2);
|
|
384
|
+
if (!frame.trim() || frame.startsWith(":")) continue;
|
|
385
|
+
let data: string | undefined;
|
|
386
|
+
for (const line of frame.split("\n")) {
|
|
387
|
+
if (line.startsWith("data: ")) {
|
|
388
|
+
data = line.slice(6);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (!data) continue;
|
|
392
|
+
try {
|
|
393
|
+
const envelope = JSON.parse(data) as {
|
|
394
|
+
message?: SseEvent;
|
|
395
|
+
[key: string]: unknown;
|
|
396
|
+
};
|
|
397
|
+
// Unwrap the AssistantEvent envelope
|
|
398
|
+
if (envelope.message && typeof envelope.message.type === "string") {
|
|
399
|
+
yield envelope.message;
|
|
400
|
+
}
|
|
401
|
+
} catch {
|
|
402
|
+
// skip malformed JSON
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
317
406
|
}
|
|
318
407
|
|
|
319
408
|
function formatConfirmationPreview(
|
|
@@ -508,6 +597,7 @@ export interface ToolCallInfo {
|
|
|
508
597
|
input: Record<string, unknown>;
|
|
509
598
|
result?: string;
|
|
510
599
|
isError?: boolean;
|
|
600
|
+
toolUseId?: string;
|
|
511
601
|
}
|
|
512
602
|
|
|
513
603
|
export interface RuntimeMessage {
|
|
@@ -1307,7 +1397,9 @@ function ChatApp({
|
|
|
1307
1397
|
const connectingRef = useRef(false);
|
|
1308
1398
|
const seenMessageIdsRef = useRef(new Set<string>());
|
|
1309
1399
|
const chatLogRef = useRef<ChatLogEntry[]>([]);
|
|
1310
|
-
const
|
|
1400
|
+
const sseAbortRef = useRef<AbortController | null>(null);
|
|
1401
|
+
const streamingTextRef = useRef("");
|
|
1402
|
+
const streamingToolCallsRef = useRef<ToolCallInfo[]>([]);
|
|
1311
1403
|
const doctorSessionIdRef = useRef(randomUUID());
|
|
1312
1404
|
const handleRef_ = useRef<ChatAppHandle | null>(null);
|
|
1313
1405
|
|
|
@@ -1535,9 +1627,9 @@ function ChatApp({
|
|
|
1535
1627
|
}, []);
|
|
1536
1628
|
|
|
1537
1629
|
const cleanup = useCallback(() => {
|
|
1538
|
-
if (
|
|
1539
|
-
|
|
1540
|
-
|
|
1630
|
+
if (sseAbortRef.current) {
|
|
1631
|
+
sseAbortRef.current.abort();
|
|
1632
|
+
sseAbortRef.current = null;
|
|
1541
1633
|
}
|
|
1542
1634
|
}, []);
|
|
1543
1635
|
|
|
@@ -1595,25 +1687,182 @@ function ChatApp({
|
|
|
1595
1687
|
h.hideSpinner();
|
|
1596
1688
|
}
|
|
1597
1689
|
|
|
1598
|
-
|
|
1690
|
+
// Open SSE stream for real-time events
|
|
1691
|
+
const sseAc = new AbortController();
|
|
1692
|
+
sseAbortRef.current = sseAc;
|
|
1693
|
+
|
|
1694
|
+
// Process SSE events in the background
|
|
1695
|
+
(async () => {
|
|
1599
1696
|
try {
|
|
1600
|
-
const
|
|
1697
|
+
for await (const event of streamEvents(
|
|
1601
1698
|
runtimeUrl,
|
|
1602
1699
|
assistantId,
|
|
1700
|
+
assistantId,
|
|
1701
|
+
sseAc.signal,
|
|
1603
1702
|
bearerToken,
|
|
1604
|
-
)
|
|
1605
|
-
|
|
1606
|
-
if (!
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1703
|
+
)) {
|
|
1704
|
+
const hRef = handleRef_.current;
|
|
1705
|
+
if (!hRef) continue;
|
|
1706
|
+
|
|
1707
|
+
switch (event.type) {
|
|
1708
|
+
case "assistant_text_delta":
|
|
1709
|
+
streamingTextRef.current += event.text ?? "";
|
|
1710
|
+
break;
|
|
1711
|
+
|
|
1712
|
+
case "assistant_thinking_delta":
|
|
1713
|
+
// Thinking deltas are suppressed in the TUI for now
|
|
1714
|
+
break;
|
|
1715
|
+
|
|
1716
|
+
case "tool_use_start":
|
|
1717
|
+
if (event.toolName) {
|
|
1718
|
+
streamingToolCallsRef.current.push({
|
|
1719
|
+
name: event.toolName,
|
|
1720
|
+
input: event.input ?? {},
|
|
1721
|
+
toolUseId: event.toolUseId,
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
break;
|
|
1725
|
+
|
|
1726
|
+
case "tool_result": {
|
|
1727
|
+
// Match by toolUseId first (robust for parallel/same-name calls),
|
|
1728
|
+
// fall back to name + missing result for backwards compat.
|
|
1729
|
+
const tc = event.toolUseId
|
|
1730
|
+
? streamingToolCallsRef.current.find(
|
|
1731
|
+
(t) => t.toolUseId === event.toolUseId,
|
|
1732
|
+
)
|
|
1733
|
+
: streamingToolCallsRef.current.find(
|
|
1734
|
+
(t) =>
|
|
1735
|
+
t.name === event.toolName && t.result === undefined,
|
|
1736
|
+
);
|
|
1737
|
+
if (tc) {
|
|
1738
|
+
tc.result = event.result;
|
|
1739
|
+
tc.isError = event.isError;
|
|
1740
|
+
} else if (event.toolName) {
|
|
1741
|
+
streamingToolCallsRef.current.push({
|
|
1742
|
+
name: event.toolName,
|
|
1743
|
+
input: event.input ?? {},
|
|
1744
|
+
result: event.result,
|
|
1745
|
+
isError: event.isError,
|
|
1746
|
+
toolUseId: event.toolUseId,
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
break;
|
|
1610
1750
|
}
|
|
1751
|
+
|
|
1752
|
+
case "confirmation_request":
|
|
1753
|
+
hRef.hideSpinner();
|
|
1754
|
+
await handleConfirmationPrompt(
|
|
1755
|
+
runtimeUrl,
|
|
1756
|
+
assistantId,
|
|
1757
|
+
event.requestId ?? "",
|
|
1758
|
+
{
|
|
1759
|
+
toolName: event.toolName ?? "",
|
|
1760
|
+
toolUseId: event.toolUseId ?? "",
|
|
1761
|
+
input: event.input ?? {},
|
|
1762
|
+
riskLevel: event.riskLevel ?? "unknown",
|
|
1763
|
+
executionTarget: event.executionTarget,
|
|
1764
|
+
allowlistOptions: event.allowlistOptions?.map((o) => ({
|
|
1765
|
+
label: o.label,
|
|
1766
|
+
pattern: o.pattern,
|
|
1767
|
+
})),
|
|
1768
|
+
scopeOptions: event.scopeOptions,
|
|
1769
|
+
persistentDecisionsAllowed:
|
|
1770
|
+
event.persistentDecisionsAllowed,
|
|
1771
|
+
},
|
|
1772
|
+
hRef,
|
|
1773
|
+
bearerToken,
|
|
1774
|
+
);
|
|
1775
|
+
hRef.showSpinner("Working...");
|
|
1776
|
+
break;
|
|
1777
|
+
|
|
1778
|
+
case "secret_request":
|
|
1779
|
+
hRef.hideSpinner();
|
|
1780
|
+
await hRef.handleSecretPrompt(
|
|
1781
|
+
{
|
|
1782
|
+
requestId: event.requestId ?? "",
|
|
1783
|
+
service: event.service ?? "",
|
|
1784
|
+
field: event.field ?? "",
|
|
1785
|
+
label: event.label ?? "",
|
|
1786
|
+
description: event.description,
|
|
1787
|
+
placeholder: event.placeholder,
|
|
1788
|
+
purpose: event.purpose,
|
|
1789
|
+
allowOneTimeSend: event.allowOneTimeSend,
|
|
1790
|
+
},
|
|
1791
|
+
async (value, delivery) => {
|
|
1792
|
+
await runtimeRequest(
|
|
1793
|
+
runtimeUrl,
|
|
1794
|
+
assistantId,
|
|
1795
|
+
"/secret",
|
|
1796
|
+
{
|
|
1797
|
+
method: "POST",
|
|
1798
|
+
body: JSON.stringify({
|
|
1799
|
+
requestId: event.requestId,
|
|
1800
|
+
value,
|
|
1801
|
+
delivery,
|
|
1802
|
+
}),
|
|
1803
|
+
},
|
|
1804
|
+
bearerToken,
|
|
1805
|
+
);
|
|
1806
|
+
},
|
|
1807
|
+
);
|
|
1808
|
+
hRef.showSpinner("Working...");
|
|
1809
|
+
break;
|
|
1810
|
+
|
|
1811
|
+
case "message_complete": {
|
|
1812
|
+
// Only finalize main turns (ignore aux events like call transcripts)
|
|
1813
|
+
if (event.source === "aux") break;
|
|
1814
|
+
|
|
1815
|
+
const text = streamingTextRef.current;
|
|
1816
|
+
const toolCalls = [...streamingToolCallsRef.current];
|
|
1817
|
+
streamingTextRef.current = "";
|
|
1818
|
+
streamingToolCallsRef.current = [];
|
|
1819
|
+
|
|
1820
|
+
if (text || toolCalls.length > 0) {
|
|
1821
|
+
const msg: RuntimeMessage = {
|
|
1822
|
+
id: event.messageId ?? `sse-${Date.now()}`,
|
|
1823
|
+
role: "assistant",
|
|
1824
|
+
content: text,
|
|
1825
|
+
timestamp: new Date().toISOString(),
|
|
1826
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
1827
|
+
};
|
|
1828
|
+
seenMessageIdsRef.current.add(msg.id);
|
|
1829
|
+
hRef.addMessage(msg);
|
|
1830
|
+
chatLogRef.current.push({
|
|
1831
|
+
role: "assistant",
|
|
1832
|
+
content: text,
|
|
1833
|
+
});
|
|
1834
|
+
process.stdout.write("\x07");
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
hRef.setBusy(false);
|
|
1838
|
+
hRef.hideSpinner();
|
|
1839
|
+
break;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
case "error":
|
|
1843
|
+
hRef.hideSpinner();
|
|
1844
|
+
hRef.showError(event.message ?? "Unknown error");
|
|
1845
|
+
hRef.setBusy(false);
|
|
1846
|
+
break;
|
|
1847
|
+
|
|
1848
|
+
default:
|
|
1849
|
+
// Ignore events we don't handle (activity state, traces, etc.)
|
|
1850
|
+
break;
|
|
1611
1851
|
}
|
|
1612
1852
|
}
|
|
1613
1853
|
} catch {
|
|
1614
|
-
//
|
|
1854
|
+
// Stream ended — only report if not intentionally aborted
|
|
1855
|
+
if (!sseAc.signal.aborted) {
|
|
1856
|
+
handleRef_.current?.addStatus(
|
|
1857
|
+
"SSE stream disconnected — will reconnect on next message",
|
|
1858
|
+
"yellow",
|
|
1859
|
+
);
|
|
1860
|
+
handleRef_.current?.setBusy(false);
|
|
1861
|
+
handleRef_.current?.hideSpinner();
|
|
1862
|
+
connectedRef.current = false;
|
|
1863
|
+
}
|
|
1615
1864
|
}
|
|
1616
|
-
}
|
|
1865
|
+
})();
|
|
1617
1866
|
|
|
1618
1867
|
connectedRef.current = true;
|
|
1619
1868
|
connectingRef.current = false;
|
|
@@ -2049,135 +2298,38 @@ function ChatApp({
|
|
|
2049
2298
|
h.showSpinner("Sending...");
|
|
2050
2299
|
h.setBusy(true);
|
|
2051
2300
|
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
const timeoutId = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS);
|
|
2301
|
+
const controller = new AbortController();
|
|
2302
|
+
const timeoutId = setTimeout(() => controller.abort(), SEND_TIMEOUT_MS);
|
|
2055
2303
|
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
h.setBusy(false);
|
|
2067
|
-
h.hideSpinner();
|
|
2068
|
-
h.showError("Message was not accepted by the assistant");
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
} catch (sendErr) {
|
|
2072
|
-
clearTimeout(timeoutId);
|
|
2304
|
+
try {
|
|
2305
|
+
const sendResult = await sendMessage(
|
|
2306
|
+
runtimeUrl,
|
|
2307
|
+
assistantId,
|
|
2308
|
+
trimmed,
|
|
2309
|
+
controller.signal,
|
|
2310
|
+
bearerToken,
|
|
2311
|
+
);
|
|
2312
|
+
clearTimeout(timeoutId);
|
|
2313
|
+
if (!sendResult.accepted) {
|
|
2073
2314
|
h.setBusy(false);
|
|
2074
2315
|
h.hideSpinner();
|
|
2075
|
-
|
|
2076
|
-
sendErr instanceof Error ? sendErr.message : String(sendErr);
|
|
2077
|
-
h.showError(errorMsg);
|
|
2078
|
-
chatLogRef.current.push({ role: "error", content: errorMsg });
|
|
2316
|
+
h.showError("Message was not accepted by the assistant");
|
|
2079
2317
|
return;
|
|
2080
2318
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
while (true) {
|
|
2085
|
-
await new Promise((resolve) =>
|
|
2086
|
-
setTimeout(resolve, RESPONSE_POLL_INTERVAL_MS),
|
|
2087
|
-
);
|
|
2088
|
-
|
|
2089
|
-
// Check for pending confirmations/secrets
|
|
2090
|
-
try {
|
|
2091
|
-
const pending = await pollPendingInteractions(
|
|
2092
|
-
runtimeUrl,
|
|
2093
|
-
assistantId,
|
|
2094
|
-
bearerToken,
|
|
2095
|
-
);
|
|
2096
|
-
|
|
2097
|
-
if (pending.pendingConfirmation) {
|
|
2098
|
-
h.hideSpinner();
|
|
2099
|
-
await handleConfirmationPrompt(
|
|
2100
|
-
runtimeUrl,
|
|
2101
|
-
assistantId,
|
|
2102
|
-
pending.pendingConfirmation.requestId,
|
|
2103
|
-
pending.pendingConfirmation,
|
|
2104
|
-
h,
|
|
2105
|
-
bearerToken,
|
|
2106
|
-
);
|
|
2107
|
-
h.showSpinner("Working...");
|
|
2108
|
-
continue;
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
if (pending.pendingSecret) {
|
|
2112
|
-
const secretRequestId = pending.pendingSecret.requestId ?? "";
|
|
2113
|
-
h.hideSpinner();
|
|
2114
|
-
await h.handleSecretPrompt(
|
|
2115
|
-
pending.pendingSecret,
|
|
2116
|
-
async (value, delivery) => {
|
|
2117
|
-
await runtimeRequest(
|
|
2118
|
-
runtimeUrl,
|
|
2119
|
-
assistantId,
|
|
2120
|
-
"/secret",
|
|
2121
|
-
{
|
|
2122
|
-
method: "POST",
|
|
2123
|
-
body: JSON.stringify({
|
|
2124
|
-
requestId: secretRequestId,
|
|
2125
|
-
value,
|
|
2126
|
-
delivery,
|
|
2127
|
-
}),
|
|
2128
|
-
},
|
|
2129
|
-
bearerToken,
|
|
2130
|
-
);
|
|
2131
|
-
},
|
|
2132
|
-
);
|
|
2133
|
-
h.showSpinner("Working...");
|
|
2134
|
-
continue;
|
|
2135
|
-
}
|
|
2136
|
-
} catch {
|
|
2137
|
-
// Pending interactions poll failure; fall through to message poll
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
// Poll for new messages to detect completion
|
|
2141
|
-
try {
|
|
2142
|
-
const pollResult = await pollMessages(
|
|
2143
|
-
runtimeUrl,
|
|
2144
|
-
assistantId,
|
|
2145
|
-
bearerToken,
|
|
2146
|
-
);
|
|
2147
|
-
for (const msg of pollResult.messages) {
|
|
2148
|
-
if (!seenMessageIdsRef.current.has(msg.id)) {
|
|
2149
|
-
seenMessageIdsRef.current.add(msg.id);
|
|
2150
|
-
if (msg.role === "assistant") {
|
|
2151
|
-
h.addMessage(msg);
|
|
2152
|
-
chatLogRef.current.push({
|
|
2153
|
-
role: "assistant",
|
|
2154
|
-
content: msg.content,
|
|
2155
|
-
});
|
|
2156
|
-
process.stdout.write("\x07");
|
|
2157
|
-
h.setBusy(false);
|
|
2158
|
-
h.hideSpinner();
|
|
2159
|
-
return;
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
} catch {
|
|
2164
|
-
// Poll failure; retry
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
} catch (error) {
|
|
2319
|
+
} catch (sendErr) {
|
|
2320
|
+
clearTimeout(timeoutId);
|
|
2168
2321
|
h.setBusy(false);
|
|
2169
2322
|
h.hideSpinner();
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
} else {
|
|
2176
|
-
const errorMsg = `Failed to send: ${error instanceof Error ? error.message : error}`;
|
|
2177
|
-
h.showError(errorMsg);
|
|
2178
|
-
chatLogRef.current.push({ role: "error", content: errorMsg });
|
|
2179
|
-
}
|
|
2323
|
+
const errorMsg =
|
|
2324
|
+
sendErr instanceof Error ? sendErr.message : String(sendErr);
|
|
2325
|
+
h.showError(errorMsg);
|
|
2326
|
+
chatLogRef.current.push({ role: "error", content: errorMsg });
|
|
2327
|
+
return;
|
|
2180
2328
|
}
|
|
2329
|
+
|
|
2330
|
+
// Accumulators are reset by message_complete; no reset here to avoid
|
|
2331
|
+
// racing with SSE events that may arrive during the sendMessage await.
|
|
2332
|
+
h.showSpinner("Working...");
|
|
2181
2333
|
},
|
|
2182
2334
|
[
|
|
2183
2335
|
runtimeUrl,
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import cliPkg from "../package.json";
|
|
|
4
4
|
import { backup } from "./commands/backup";
|
|
5
5
|
import { clean } from "./commands/clean";
|
|
6
6
|
import { client } from "./commands/client";
|
|
7
|
+
import { env } from "./commands/env";
|
|
7
8
|
import { events } from "./commands/events";
|
|
8
9
|
import { exec } from "./commands/exec";
|
|
9
10
|
import { hatch } from "./commands/hatch";
|
|
@@ -20,6 +21,7 @@ import { setup } from "./commands/setup";
|
|
|
20
21
|
import { sleep } from "./commands/sleep";
|
|
21
22
|
import { ssh } from "./commands/ssh";
|
|
22
23
|
import { teleport } from "./commands/teleport";
|
|
24
|
+
import { terminal } from "./commands/terminal";
|
|
23
25
|
import { tunnel } from "./commands/tunnel";
|
|
24
26
|
import { upgrade } from "./commands/upgrade";
|
|
25
27
|
import { use } from "./commands/use";
|
|
@@ -37,6 +39,7 @@ const commands = {
|
|
|
37
39
|
backup,
|
|
38
40
|
clean,
|
|
39
41
|
client,
|
|
42
|
+
env,
|
|
40
43
|
events,
|
|
41
44
|
exec,
|
|
42
45
|
hatch,
|
|
@@ -54,6 +57,7 @@ const commands = {
|
|
|
54
57
|
sleep,
|
|
55
58
|
ssh,
|
|
56
59
|
teleport,
|
|
60
|
+
terminal,
|
|
57
61
|
tunnel,
|
|
58
62
|
upgrade,
|
|
59
63
|
use,
|
|
@@ -70,6 +74,7 @@ function printHelp(): void {
|
|
|
70
74
|
console.log(" backup Export a backup of a running assistant");
|
|
71
75
|
console.log(" clean Kill orphaned vellum processes");
|
|
72
76
|
console.log(" client Connect to a hatched assistant");
|
|
77
|
+
console.log(" env Manage the default CLI environment");
|
|
73
78
|
console.log(" events Stream events from a running assistant");
|
|
74
79
|
console.log(" exec Execute a command inside an assistant's container");
|
|
75
80
|
console.log(" hatch Create a new assistant instance");
|
|
@@ -91,6 +96,7 @@ function printHelp(): void {
|
|
|
91
96
|
console.log(" sleep Stop the assistant process");
|
|
92
97
|
console.log(" ssh SSH into a remote assistant instance");
|
|
93
98
|
console.log(" teleport Transfer assistant data between environments");
|
|
99
|
+
console.log(" terminal Open a terminal into a managed assistant container");
|
|
94
100
|
console.log(" tunnel Create a tunnel for a locally hosted assistant");
|
|
95
101
|
console.log(" upgrade Upgrade an assistant to a newer version");
|
|
96
102
|
console.log(" use Set the active assistant for commands");
|