@electric-agent/studio 1.3.0 → 1.5.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/dist/bridge/claude-code-docker.d.ts +3 -0
- package/dist/bridge/claude-code-docker.d.ts.map +1 -1
- package/dist/bridge/claude-code-docker.js +33 -30
- package/dist/bridge/claude-code-docker.js.map +1 -1
- package/dist/bridge/claude-code-sprites.d.ts +3 -0
- package/dist/bridge/claude-code-sprites.d.ts.map +1 -1
- package/dist/bridge/claude-code-sprites.js +52 -46
- package/dist/bridge/claude-code-sprites.js.map +1 -1
- package/dist/bridge/claude-md-generator.d.ts.map +1 -1
- package/dist/bridge/claude-md-generator.js +3 -1
- package/dist/bridge/claude-md-generator.js.map +1 -1
- package/dist/bridge/gate-response.d.ts +10 -0
- package/dist/bridge/gate-response.d.ts.map +1 -0
- package/dist/bridge/gate-response.js +40 -0
- package/dist/bridge/gate-response.js.map +1 -0
- package/dist/bridge/hosted.d.ts +1 -0
- package/dist/bridge/hosted.d.ts.map +1 -1
- package/dist/bridge/hosted.js +4 -0
- package/dist/bridge/hosted.js.map +1 -1
- package/dist/bridge/index.d.ts +1 -0
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +1 -0
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/stream-json-parser.js +1 -0
- package/dist/bridge/stream-json-parser.js.map +1 -1
- package/dist/bridge/types.d.ts +6 -0
- package/dist/bridge/types.d.ts.map +1 -1
- package/dist/client/assets/index-DDzmxYub.js +234 -0
- package/dist/client/assets/index-DcP7prsZ.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/git.d.ts +8 -5
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +8 -5
- package/dist/git.js.map +1 -1
- package/dist/sandbox/docker.d.ts.map +1 -1
- package/dist/sandbox/docker.js +1 -0
- package/dist/sandbox/docker.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +208 -37
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/client/assets/index-Bq9zwhHj.css +0 -1
- package/dist/client/assets/index-Dgpqg5fv.js +0 -234
package/dist/server.js
CHANGED
|
@@ -164,6 +164,7 @@ function mapHookToEngineEvent(body) {
|
|
|
164
164
|
tool_use_id: toolUseId,
|
|
165
165
|
question: firstQuestion?.question || toolInput.question || "",
|
|
166
166
|
options: firstQuestion?.options,
|
|
167
|
+
questions: questions ?? undefined,
|
|
167
168
|
ts: now,
|
|
168
169
|
};
|
|
169
170
|
}
|
|
@@ -432,7 +433,7 @@ export function createApp(config) {
|
|
|
432
433
|
console.log(`[hook-event] Blocking for ask_user_question gate: ${toolUseId}`);
|
|
433
434
|
try {
|
|
434
435
|
const gateTimeout = 5 * 60 * 1000; // 5 minutes
|
|
435
|
-
const
|
|
436
|
+
const result = await Promise.race([
|
|
436
437
|
createGate(sessionId, `ask_user_question:${toolUseId}`),
|
|
437
438
|
new Promise((_, reject) => setTimeout(() => reject(new Error("AskUserQuestion gate timed out")), gateTimeout)),
|
|
438
439
|
]);
|
|
@@ -443,7 +444,7 @@ export function createApp(config) {
|
|
|
443
444
|
permissionDecision: "allow",
|
|
444
445
|
updatedInput: {
|
|
445
446
|
questions: body.tool_input?.questions,
|
|
446
|
-
answers:
|
|
447
|
+
answers: result.answers,
|
|
447
448
|
},
|
|
448
449
|
},
|
|
449
450
|
});
|
|
@@ -562,7 +563,7 @@ export function createApp(config) {
|
|
|
562
563
|
console.log(`[hook] Blocking for ask_user_question gate: ${toolUseId}`);
|
|
563
564
|
try {
|
|
564
565
|
const gateTimeout = 5 * 60 * 1000;
|
|
565
|
-
const
|
|
566
|
+
const result = await Promise.race([
|
|
566
567
|
createGate(sessionId, `ask_user_question:${toolUseId}`),
|
|
567
568
|
new Promise((_, reject) => setTimeout(() => reject(new Error("AskUserQuestion gate timed out")), gateTimeout)),
|
|
568
569
|
]);
|
|
@@ -574,7 +575,7 @@ export function createApp(config) {
|
|
|
574
575
|
permissionDecision: "allow",
|
|
575
576
|
updatedInput: {
|
|
576
577
|
questions: body.tool_input?.questions,
|
|
577
|
-
answers:
|
|
578
|
+
answers: result.answers,
|
|
578
579
|
},
|
|
579
580
|
},
|
|
580
581
|
});
|
|
@@ -717,8 +718,9 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
717
718
|
// Write user prompt to the stream so it shows in the UI
|
|
718
719
|
await bridge.emit({ type: "user_prompt", message: body.description, ts: ts() });
|
|
719
720
|
// Gather GitHub accounts for the merged setup gate
|
|
721
|
+
// Only check if the client provided a token — never fall back to server-side GH_TOKEN
|
|
720
722
|
let ghAccounts = [];
|
|
721
|
-
if (isGhAuthenticated(body.ghToken)) {
|
|
723
|
+
if (body.ghToken && isGhAuthenticated(body.ghToken)) {
|
|
722
724
|
try {
|
|
723
725
|
ghAccounts = ghListAccounts(body.ghToken);
|
|
724
726
|
}
|
|
@@ -932,14 +934,16 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
932
934
|
}
|
|
933
935
|
config.sessions.update(sessionId, updates);
|
|
934
936
|
// Check if the app is running after completion
|
|
935
|
-
// and emit
|
|
937
|
+
// and emit app_status so the UI shows the preview link
|
|
936
938
|
if (success) {
|
|
937
939
|
try {
|
|
938
940
|
const appRunning = await config.sandbox.isAppRunning(handle);
|
|
939
941
|
if (appRunning) {
|
|
940
942
|
await bridge.emit({
|
|
941
|
-
type: "
|
|
943
|
+
type: "app_status",
|
|
944
|
+
status: "running",
|
|
942
945
|
port: handle.port ?? session.appPort,
|
|
946
|
+
previewUrl: handle.previewUrl ?? session.previewUrl,
|
|
943
947
|
ts: ts(),
|
|
944
948
|
});
|
|
945
949
|
}
|
|
@@ -949,6 +953,13 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
949
953
|
}
|
|
950
954
|
}
|
|
951
955
|
});
|
|
956
|
+
// Show the command being sent to Claude Code
|
|
957
|
+
await bridge.emit({
|
|
958
|
+
type: "log",
|
|
959
|
+
level: "build",
|
|
960
|
+
message: `Running: claude "/create-app ${body.description}"`,
|
|
961
|
+
ts: ts(),
|
|
962
|
+
});
|
|
952
963
|
console.log(`[session:${sessionId}] Starting bridge listener...`);
|
|
953
964
|
await bridge.start();
|
|
954
965
|
console.log(`[session:${sessionId}] Bridge started, sending 'new' command...`);
|
|
@@ -1015,7 +1026,13 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
1015
1026
|
message: "App started",
|
|
1016
1027
|
ts: ts(),
|
|
1017
1028
|
});
|
|
1018
|
-
await bridge.emit({
|
|
1029
|
+
await bridge.emit({
|
|
1030
|
+
type: "app_status",
|
|
1031
|
+
status: "running",
|
|
1032
|
+
port: session.appPort,
|
|
1033
|
+
previewUrl: session.previewUrl,
|
|
1034
|
+
ts: ts(),
|
|
1035
|
+
});
|
|
1019
1036
|
}
|
|
1020
1037
|
}
|
|
1021
1038
|
catch (err) {
|
|
@@ -1082,8 +1099,10 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
1082
1099
|
if (!toolUseId) {
|
|
1083
1100
|
return c.json({ error: "toolUseId is required for ask_user_question" }, 400);
|
|
1084
1101
|
}
|
|
1085
|
-
|
|
1086
|
-
const
|
|
1102
|
+
// Accept either answers (Record<string, string>) or legacy answer (string)
|
|
1103
|
+
const answers = body.answers ??
|
|
1104
|
+
(body.answer ? { [body.question || "answer"]: body.answer } : {});
|
|
1105
|
+
const resolved = resolveGate(sessionId, `ask_user_question:${toolUseId}`, { answers });
|
|
1087
1106
|
if (resolved) {
|
|
1088
1107
|
// Hook session — gate was blocking, now resolved
|
|
1089
1108
|
try {
|
|
@@ -1234,6 +1253,24 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
1234
1253
|
}
|
|
1235
1254
|
return c.json({ success: true });
|
|
1236
1255
|
});
|
|
1256
|
+
// Interrupt the running Claude Code process without destroying the session.
|
|
1257
|
+
// The sandbox stays alive and the bridge remains open for follow-up messages.
|
|
1258
|
+
app.post("/api/sessions/:id/interrupt", async (c) => {
|
|
1259
|
+
const sessionId = c.req.param("id");
|
|
1260
|
+
const bridge = bridges.get(sessionId);
|
|
1261
|
+
if (bridge) {
|
|
1262
|
+
bridge.interrupt();
|
|
1263
|
+
// Emit session_end so the UI knows the process stopped
|
|
1264
|
+
await bridge.emit({
|
|
1265
|
+
type: "session_end",
|
|
1266
|
+
success: false,
|
|
1267
|
+
ts: ts(),
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
rejectAllGates(sessionId);
|
|
1271
|
+
config.sessions.update(sessionId, { status: "complete" });
|
|
1272
|
+
return c.json({ ok: true });
|
|
1273
|
+
});
|
|
1237
1274
|
// Cancel a running session
|
|
1238
1275
|
app.post("/api/sessions/:id/cancel", async (c) => {
|
|
1239
1276
|
const sessionId = c.req.param("id");
|
|
@@ -1694,9 +1731,11 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
1694
1731
|
}
|
|
1695
1732
|
return c.json({ content });
|
|
1696
1733
|
});
|
|
1697
|
-
// List GitHub accounts (personal + orgs)
|
|
1734
|
+
// List GitHub accounts (personal + orgs) — requires client-provided token
|
|
1698
1735
|
app.get("/api/github/accounts", (c) => {
|
|
1699
|
-
const token = c.req.header("X-GH-Token")
|
|
1736
|
+
const token = c.req.header("X-GH-Token");
|
|
1737
|
+
if (!token)
|
|
1738
|
+
return c.json({ accounts: [] });
|
|
1700
1739
|
try {
|
|
1701
1740
|
const accounts = ghListAccounts(token);
|
|
1702
1741
|
return c.json({ accounts });
|
|
@@ -1705,9 +1744,11 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
1705
1744
|
return c.json({ error: e instanceof Error ? e.message : "Failed to list accounts" }, 500);
|
|
1706
1745
|
}
|
|
1707
1746
|
});
|
|
1708
|
-
// List GitHub repos for the authenticated user
|
|
1747
|
+
// List GitHub repos for the authenticated user — requires client-provided token
|
|
1709
1748
|
app.get("/api/github/repos", (c) => {
|
|
1710
|
-
const token = c.req.header("X-GH-Token")
|
|
1749
|
+
const token = c.req.header("X-GH-Token");
|
|
1750
|
+
if (!token)
|
|
1751
|
+
return c.json({ repos: [] });
|
|
1711
1752
|
try {
|
|
1712
1753
|
const repos = ghListRepos(50, token);
|
|
1713
1754
|
return c.json({ repos });
|
|
@@ -1719,7 +1760,9 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
1719
1760
|
app.get("/api/github/repos/:owner/:repo/branches", (c) => {
|
|
1720
1761
|
const owner = c.req.param("owner");
|
|
1721
1762
|
const repo = c.req.param("repo");
|
|
1722
|
-
const token = c.req.header("X-GH-Token")
|
|
1763
|
+
const token = c.req.header("X-GH-Token");
|
|
1764
|
+
if (!token)
|
|
1765
|
+
return c.json({ branches: [] });
|
|
1723
1766
|
try {
|
|
1724
1767
|
const branches = ghListBranches(`${owner}/${repo}`, token);
|
|
1725
1768
|
return c.json({ branches });
|
|
@@ -1772,24 +1815,47 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
1772
1815
|
catch {
|
|
1773
1816
|
return c.json({ error: "Failed to create event stream" }, 500);
|
|
1774
1817
|
}
|
|
1775
|
-
|
|
1818
|
+
// Create the initial session bridge for emitting progress events
|
|
1819
|
+
const bridge = getOrCreateBridge(config, sessionId);
|
|
1820
|
+
// Record session as running (like normal session creation)
|
|
1821
|
+
const sandboxProjectDir = `/home/agent/workspace/${repoName}`;
|
|
1822
|
+
const session = {
|
|
1823
|
+
id: sessionId,
|
|
1824
|
+
projectName: body.branch ? `${repoName}/${body.branch}` : repoName,
|
|
1825
|
+
sandboxProjectDir,
|
|
1826
|
+
description: `Resumed from ${body.repoUrl}`,
|
|
1827
|
+
createdAt: new Date().toISOString(),
|
|
1828
|
+
lastActiveAt: new Date().toISOString(),
|
|
1829
|
+
status: "running",
|
|
1830
|
+
};
|
|
1831
|
+
config.sessions.add(session);
|
|
1832
|
+
// Write user prompt to the stream so it shows in the UI
|
|
1833
|
+
await bridge.emit({
|
|
1834
|
+
type: "user_prompt",
|
|
1835
|
+
message: `Resume from ${body.repoUrl}`,
|
|
1836
|
+
ts: ts(),
|
|
1837
|
+
});
|
|
1838
|
+
// Launch async flow: clone repo → set up Claude Code → start exploring
|
|
1839
|
+
const asyncFlow = async () => {
|
|
1840
|
+
// 1. Clone the repo into a sandbox
|
|
1841
|
+
await bridge.emit({
|
|
1842
|
+
type: "log",
|
|
1843
|
+
level: "build",
|
|
1844
|
+
message: "Cloning repository...",
|
|
1845
|
+
ts: ts(),
|
|
1846
|
+
});
|
|
1776
1847
|
const handle = await config.sandbox.createFromRepo(sessionId, body.repoUrl, {
|
|
1777
1848
|
branch: body.branch,
|
|
1778
1849
|
apiKey: body.apiKey,
|
|
1779
1850
|
oauthToken: body.oauthToken,
|
|
1780
1851
|
ghToken: body.ghToken,
|
|
1781
1852
|
});
|
|
1782
|
-
// Get git state from cloned repo
|
|
1853
|
+
// Get git state from cloned repo
|
|
1783
1854
|
const gs = await config.sandbox.gitStatus(handle, handle.projectDir);
|
|
1784
|
-
|
|
1785
|
-
id: sessionId,
|
|
1786
|
-
projectName: repoName,
|
|
1787
|
-
sandboxProjectDir: handle.projectDir,
|
|
1788
|
-
description: `Resumed from ${body.repoUrl}`,
|
|
1789
|
-
createdAt: new Date().toISOString(),
|
|
1790
|
-
lastActiveAt: new Date().toISOString(),
|
|
1791
|
-
status: "complete",
|
|
1855
|
+
config.sessions.update(sessionId, {
|
|
1792
1856
|
appPort: handle.port,
|
|
1857
|
+
sandboxProjectDir: handle.projectDir,
|
|
1858
|
+
previewUrl: handle.previewUrl,
|
|
1793
1859
|
git: {
|
|
1794
1860
|
branch: gs.branch ?? body.branch ?? "main",
|
|
1795
1861
|
remoteUrl: body.repoUrl,
|
|
@@ -1798,23 +1864,128 @@ echo "Start claude in this project — the session will appear in the studio UI.
|
|
|
1798
1864
|
lastCommitMessage: gs.lastCommitMessage ?? null,
|
|
1799
1865
|
lastCheckpointAt: null,
|
|
1800
1866
|
},
|
|
1801
|
-
};
|
|
1802
|
-
config.sessions.add(session);
|
|
1803
|
-
// Write initial message to stream
|
|
1804
|
-
const bridge = getOrCreateBridge(config, sessionId);
|
|
1867
|
+
});
|
|
1805
1868
|
await bridge.emit({
|
|
1806
1869
|
type: "log",
|
|
1807
1870
|
level: "done",
|
|
1808
|
-
message:
|
|
1871
|
+
message: "Repository cloned",
|
|
1809
1872
|
ts: ts(),
|
|
1810
1873
|
});
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1874
|
+
// 2. Write CLAUDE.md to the sandbox workspace
|
|
1875
|
+
const claudeMd = generateClaudeMd({
|
|
1876
|
+
description: `Resumed from ${body.repoUrl}`,
|
|
1877
|
+
projectName: repoName,
|
|
1878
|
+
projectDir: handle.projectDir,
|
|
1879
|
+
runtime: config.sandbox.runtime,
|
|
1880
|
+
});
|
|
1881
|
+
try {
|
|
1882
|
+
await config.sandbox.exec(handle, `cat > '${handle.projectDir}/CLAUDE.md' << 'CLAUDEMD_EOF'\n${claudeMd}\nCLAUDEMD_EOF`);
|
|
1883
|
+
}
|
|
1884
|
+
catch (err) {
|
|
1885
|
+
console.error(`[session:${sessionId}] Failed to write CLAUDE.md:`, err);
|
|
1886
|
+
}
|
|
1887
|
+
// Ensure the create-app skill is present in the project
|
|
1888
|
+
if (createAppSkillContent) {
|
|
1889
|
+
try {
|
|
1890
|
+
const skillDir = `${handle.projectDir}/.claude/skills/create-app`;
|
|
1891
|
+
const skillB64 = Buffer.from(createAppSkillContent).toString("base64");
|
|
1892
|
+
await config.sandbox.exec(handle, `mkdir -p '${skillDir}' && echo '${skillB64}' | base64 -d > '${skillDir}/SKILL.md'`);
|
|
1893
|
+
}
|
|
1894
|
+
catch (err) {
|
|
1895
|
+
console.error(`[session:${sessionId}] Failed to write create-app skill:`, err);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
// 3. Create Claude Code bridge with a resume prompt
|
|
1899
|
+
const resumePrompt = "You are resuming work on an existing project. Explore the codebase to understand its structure, then wait for instructions from the user.";
|
|
1900
|
+
const claudeConfig = config.sandbox.runtime === "sprites"
|
|
1901
|
+
? {
|
|
1902
|
+
prompt: resumePrompt,
|
|
1903
|
+
cwd: handle.projectDir,
|
|
1904
|
+
studioUrl: resolveStudioUrl(config.port),
|
|
1905
|
+
}
|
|
1906
|
+
: {
|
|
1907
|
+
prompt: resumePrompt,
|
|
1908
|
+
cwd: handle.projectDir,
|
|
1909
|
+
studioPort: config.port,
|
|
1910
|
+
};
|
|
1911
|
+
const ccBridge = createClaudeCodeBridge(config, sessionId, claudeConfig);
|
|
1912
|
+
// 4. Register event listeners (reuse pattern from normal flow)
|
|
1913
|
+
ccBridge.onAgentEvent((event) => {
|
|
1914
|
+
if (event.type === "session_start") {
|
|
1915
|
+
const ccSessionId = event.session_id;
|
|
1916
|
+
console.log(`[session:${sessionId}] Captured Claude Code session ID: ${ccSessionId}`);
|
|
1917
|
+
if (ccSessionId) {
|
|
1918
|
+
config.sessions.update(sessionId, { lastCoderSessionId: ccSessionId });
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
ccBridge.onComplete(async (success) => {
|
|
1923
|
+
const updates = {
|
|
1924
|
+
status: success ? "complete" : "error",
|
|
1925
|
+
};
|
|
1926
|
+
try {
|
|
1927
|
+
const latestGs = await config.sandbox.gitStatus(handle, handle.projectDir);
|
|
1928
|
+
if (latestGs.initialized) {
|
|
1929
|
+
const existing = config.sessions.get(sessionId);
|
|
1930
|
+
updates.git = {
|
|
1931
|
+
branch: latestGs.branch ?? "main",
|
|
1932
|
+
remoteUrl: existing?.git?.remoteUrl ?? null,
|
|
1933
|
+
repoName: existing?.git?.repoName ?? null,
|
|
1934
|
+
repoVisibility: existing?.git?.repoVisibility,
|
|
1935
|
+
lastCommitHash: latestGs.lastCommitHash ?? null,
|
|
1936
|
+
lastCommitMessage: latestGs.lastCommitMessage ?? null,
|
|
1937
|
+
lastCheckpointAt: existing?.git?.lastCheckpointAt ?? null,
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
catch {
|
|
1942
|
+
// Container may already be stopped
|
|
1943
|
+
}
|
|
1944
|
+
config.sessions.update(sessionId, updates);
|
|
1945
|
+
// Check if the app is running after completion
|
|
1946
|
+
if (success) {
|
|
1947
|
+
try {
|
|
1948
|
+
const appRunning = await config.sandbox.isAppRunning(handle);
|
|
1949
|
+
if (appRunning) {
|
|
1950
|
+
await ccBridge.emit({
|
|
1951
|
+
type: "app_status",
|
|
1952
|
+
status: "running",
|
|
1953
|
+
port: handle.port ?? session.appPort,
|
|
1954
|
+
previewUrl: handle.previewUrl ?? session.previewUrl,
|
|
1955
|
+
ts: ts(),
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
catch {
|
|
1960
|
+
// Container may already be stopped
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
// 5. Start the bridge and send command
|
|
1965
|
+
await ccBridge.emit({
|
|
1966
|
+
type: "log",
|
|
1967
|
+
level: "build",
|
|
1968
|
+
message: "Starting Claude Code...",
|
|
1969
|
+
ts: ts(),
|
|
1970
|
+
});
|
|
1971
|
+
console.log(`[session:${sessionId}] Starting bridge listener...`);
|
|
1972
|
+
await ccBridge.start();
|
|
1973
|
+
console.log(`[session:${sessionId}] Bridge started, sending 'new' command...`);
|
|
1974
|
+
const newCmd = {
|
|
1975
|
+
command: "new",
|
|
1976
|
+
description: resumePrompt,
|
|
1977
|
+
projectName: repoName,
|
|
1978
|
+
baseDir: "/home/agent/workspace",
|
|
1979
|
+
};
|
|
1980
|
+
await ccBridge.sendCommand(newCmd);
|
|
1981
|
+
console.log(`[session:${sessionId}] Command sent, waiting for agent...`);
|
|
1982
|
+
};
|
|
1983
|
+
asyncFlow().catch(async (err) => {
|
|
1984
|
+
console.error(`[session:${sessionId}] Resume flow failed:`, err);
|
|
1985
|
+
config.sessions.update(sessionId, { status: "error" });
|
|
1986
|
+
});
|
|
1987
|
+
const sessionToken = deriveSessionToken(config.streamConfig.secret, sessionId);
|
|
1988
|
+
return c.json({ sessionId, session, sessionToken }, 201);
|
|
1818
1989
|
});
|
|
1819
1990
|
// Serve static SPA files (if built)
|
|
1820
1991
|
const clientDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), "./client");
|