scream-code 0.10.13 → 0.11.1
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/{app-DBxlfmut.mjs → app-p7tymZds.mjs} +513 -97
- package/dist/main.mjs +1 -1
- package/dist/public/assets/index-DZO0r34N.css +1 -0
- package/dist/public/assets/index-_4G7zaK5.js +91 -0
- package/dist/public/assets/index-_4G7zaK5.js.map +1 -0
- package/dist/public/index.html +2 -2
- package/package.json +1 -1
- package/dist/public/assets/index-DDxNjEwB.css +0 -1
- package/dist/public/assets/index-Dh3SHY7E.js +0 -93
- package/dist/public/assets/index-Dh3SHY7E.js.map +0 -1
|
@@ -717,7 +717,7 @@ function isOrphanedToolCallMessage(lowerMessage) {
|
|
|
717
717
|
*/
|
|
718
718
|
function isOrphanedToolCallError(error) {
|
|
719
719
|
if (error instanceof APIOrphanedToolCallError) return true;
|
|
720
|
-
return isOrphanedToolCallMessage(errorMessage$
|
|
720
|
+
return isOrphanedToolCallMessage(errorMessage$7(error).toLowerCase());
|
|
721
721
|
}
|
|
722
722
|
/**
|
|
723
723
|
* The API returned an empty response (no content, no tool calls).
|
|
@@ -764,7 +764,7 @@ function isContextOverflowStatusError(statusCode, message) {
|
|
|
764
764
|
const lowerMessage = message.toLowerCase();
|
|
765
765
|
return CONTEXT_OVERFLOW_MESSAGE_PATTERNS.some((pattern) => pattern.test(lowerMessage));
|
|
766
766
|
}
|
|
767
|
-
function errorMessage$
|
|
767
|
+
function errorMessage$7(error) {
|
|
768
768
|
return error instanceof Error ? error.message : String(error);
|
|
769
769
|
}
|
|
770
770
|
//#endregion
|
|
@@ -47296,7 +47296,7 @@ function isAbortError$1(err) {
|
|
|
47296
47296
|
if (err instanceof Error) return err.name === "AbortError";
|
|
47297
47297
|
return false;
|
|
47298
47298
|
}
|
|
47299
|
-
function errorMessage$
|
|
47299
|
+
function errorMessage$6(err) {
|
|
47300
47300
|
if (err instanceof Error) return err.message;
|
|
47301
47301
|
return String(err);
|
|
47302
47302
|
}
|
|
@@ -52494,7 +52494,7 @@ var GoalMode = class {
|
|
|
52494
52494
|
const state = this.state;
|
|
52495
52495
|
if (state === void 0 || state.status !== "active") return null;
|
|
52496
52496
|
state.tokensUsed += Math.max(0, tokenDelta);
|
|
52497
|
-
this.persistState(state
|
|
52497
|
+
this.persistState(state);
|
|
52498
52498
|
this.appendGoalUpdate({ tokensUsed: state.tokensUsed });
|
|
52499
52499
|
return this.toSnapshot(state);
|
|
52500
52500
|
}
|
|
@@ -52516,7 +52516,7 @@ var GoalMode = class {
|
|
|
52516
52516
|
time: Date.now()
|
|
52517
52517
|
});
|
|
52518
52518
|
if (state.notes.length > MAX_GOAL_NOTES) state.notes = state.notes.slice(-60);
|
|
52519
|
-
this.persistState(state
|
|
52519
|
+
this.persistState(state);
|
|
52520
52520
|
return this.toSnapshot(state);
|
|
52521
52521
|
}
|
|
52522
52522
|
clearInternal(actor, opts = {}) {
|
|
@@ -52581,7 +52581,7 @@ var GoalMode = class {
|
|
|
52581
52581
|
wallClockMs: liveWallClockMs(state, Date.now()),
|
|
52582
52582
|
budget: computeBudgetReport(state, Date.now()),
|
|
52583
52583
|
terminalReason: state.terminalReason,
|
|
52584
|
-
notes: state.notes
|
|
52584
|
+
notes: state.notes.map((note) => ({ ...note }))
|
|
52585
52585
|
};
|
|
52586
52586
|
}
|
|
52587
52587
|
};
|
|
@@ -52865,7 +52865,7 @@ var UpdateGoalTool = class {
|
|
|
52865
52865
|
try {
|
|
52866
52866
|
await goal.pauseGoal({ reason: "verifying" }, "system");
|
|
52867
52867
|
} catch (error) {
|
|
52868
|
-
return toolError(`Failed to pause goal for verification: ${errorMessage$
|
|
52868
|
+
return toolError(`Failed to pause goal for verification: ${errorMessage$5(error)}`, goal);
|
|
52869
52869
|
}
|
|
52870
52870
|
let rawGrade;
|
|
52871
52871
|
try {
|
|
@@ -52873,7 +52873,7 @@ var UpdateGoalTool = class {
|
|
|
52873
52873
|
} catch (error) {
|
|
52874
52874
|
const resumeError = await resumeAfterGrading(goal);
|
|
52875
52875
|
if (resumeError !== void 0) return resumeError;
|
|
52876
|
-
const reason = `Goal verification could not be completed: ${errorMessage$
|
|
52876
|
+
const reason = `Goal verification could not be completed: ${errorMessage$5(error)}`;
|
|
52877
52877
|
this.appendGradingFeedback(reason);
|
|
52878
52878
|
return {
|
|
52879
52879
|
isError: true,
|
|
@@ -52901,7 +52901,7 @@ var UpdateGoalTool = class {
|
|
|
52901
52901
|
name: GOAL_COMPLETION_REMINDER_NAME
|
|
52902
52902
|
});
|
|
52903
52903
|
} catch (error) {
|
|
52904
|
-
return toolError(`Failed to mark verified goal complete: ${errorMessage$
|
|
52904
|
+
return toolError(`Failed to mark verified goal complete: ${errorMessage$5(error)}`, goal);
|
|
52905
52905
|
}
|
|
52906
52906
|
return {
|
|
52907
52907
|
output: `Goal verified and marked complete.\n${grade.reason}`,
|
|
@@ -52936,7 +52936,7 @@ async function resumeAfterGrading(goal) {
|
|
|
52936
52936
|
await goal.resumeGoal({}, "system");
|
|
52937
52937
|
return;
|
|
52938
52938
|
} catch (error) {
|
|
52939
|
-
return toolError(`Failed to restore active goal after verification: ${errorMessage$
|
|
52939
|
+
return toolError(`Failed to restore active goal after verification: ${errorMessage$5(error)}`, goal);
|
|
52940
52940
|
}
|
|
52941
52941
|
}
|
|
52942
52942
|
function toolError(message, goal) {
|
|
@@ -52945,7 +52945,7 @@ function toolError(message, goal) {
|
|
|
52945
52945
|
output: `${message}. Current goal status: ${goal.getGoal().goal?.status ?? "missing"}.`
|
|
52946
52946
|
};
|
|
52947
52947
|
}
|
|
52948
|
-
function errorMessage$
|
|
52948
|
+
function errorMessage$5(error) {
|
|
52949
52949
|
return error instanceof Error ? error.message : String(error);
|
|
52950
52950
|
}
|
|
52951
52951
|
//#endregion
|
|
@@ -78351,7 +78351,7 @@ async function runHook(command, input, options) {
|
|
|
78351
78351
|
detached: process.platform !== "win32"
|
|
78352
78352
|
});
|
|
78353
78353
|
} catch (error) {
|
|
78354
|
-
return allowResult({ stderr: errorMessage$
|
|
78354
|
+
return allowResult({ stderr: errorMessage$4(error) });
|
|
78355
78355
|
}
|
|
78356
78356
|
return new Promise((resolve) => {
|
|
78357
78357
|
let stdout = "";
|
|
@@ -78399,7 +78399,7 @@ async function runHook(command, input, options) {
|
|
|
78399
78399
|
child.on("error", (error) => {
|
|
78400
78400
|
settle(allowResult({
|
|
78401
78401
|
stdout,
|
|
78402
|
-
stderr: stderr + errorMessage$
|
|
78402
|
+
stderr: stderr + errorMessage$4(error)
|
|
78403
78403
|
}));
|
|
78404
78404
|
});
|
|
78405
78405
|
child.on("close", (code) => {
|
|
@@ -78495,7 +78495,7 @@ function tryKillProcess(child, signal) {
|
|
|
78495
78495
|
function isRecord$5(value) {
|
|
78496
78496
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
78497
78497
|
}
|
|
78498
|
-
function errorMessage$
|
|
78498
|
+
function errorMessage$4(error) {
|
|
78499
78499
|
return error instanceof Error ? error.message : String(error);
|
|
78500
78500
|
}
|
|
78501
78501
|
//#endregion
|
|
@@ -89581,7 +89581,7 @@ function parseToolCallArguments(raw) {
|
|
|
89581
89581
|
} catch {
|
|
89582
89582
|
return {
|
|
89583
89583
|
success: false,
|
|
89584
|
-
error: errorMessage$
|
|
89584
|
+
error: errorMessage$6(error)
|
|
89585
89585
|
};
|
|
89586
89586
|
}
|
|
89587
89587
|
}
|
|
@@ -89661,7 +89661,7 @@ async function prepareToolCall(step, call) {
|
|
|
89661
89661
|
toolCallId: call.toolCall.id,
|
|
89662
89662
|
error
|
|
89663
89663
|
});
|
|
89664
|
-
return settleError(effectiveArgs, error instanceof PathSecurityError ? error.message : `Tool "${call.toolName}" failed to resolve execution: ${errorMessage$
|
|
89664
|
+
return settleError(effectiveArgs, error instanceof PathSecurityError ? error.message : `Tool "${call.toolName}" failed to resolve execution: ${errorMessage$6(error)}`);
|
|
89665
89665
|
}
|
|
89666
89666
|
const displayFields = toolCallDisplayFieldsFromExecution(execution);
|
|
89667
89667
|
const settleAborted = () => settleError(effectiveArgs, abortedToolOutput(call.toolName, step.signal), displayFields);
|
|
@@ -89720,7 +89720,7 @@ async function runPrepareToolExecutionHook(step, call) {
|
|
|
89720
89720
|
return {
|
|
89721
89721
|
kind: "hookFailed",
|
|
89722
89722
|
args,
|
|
89723
|
-
output: `prepareToolExecution hook failed for "${call.toolName}": ${errorMessage$
|
|
89723
|
+
output: `prepareToolExecution hook failed for "${call.toolName}": ${errorMessage$6(error)}`
|
|
89724
89724
|
};
|
|
89725
89725
|
}
|
|
89726
89726
|
const effectiveArgs = hookResult?.updatedArgs ?? args;
|
|
@@ -89761,7 +89761,7 @@ async function runAuthorizeToolExecutionHook(step, call, args, execution) {
|
|
|
89761
89761
|
};
|
|
89762
89762
|
return {
|
|
89763
89763
|
block: true,
|
|
89764
|
-
reason: `authorizeToolExecution hook failed for "${call.toolName}": ${errorMessage$
|
|
89764
|
+
reason: `authorizeToolExecution hook failed for "${call.toolName}": ${errorMessage$6(error)}`
|
|
89765
89765
|
};
|
|
89766
89766
|
}
|
|
89767
89767
|
}
|
|
@@ -89788,7 +89788,7 @@ async function runRunnableToolCall(step, call, effectiveArgs, metadata, executio
|
|
|
89788
89788
|
toolCallId: toolCall.id,
|
|
89789
89789
|
error
|
|
89790
89790
|
});
|
|
89791
|
-
return makeErrorToolResult(call, effectiveArgs, aborted ? abortedToolOutput(toolName, signal) : `Tool "${toolName}" failed: ${errorMessage$
|
|
89791
|
+
return makeErrorToolResult(call, effectiveArgs, aborted ? abortedToolOutput(toolName, signal) : `Tool "${toolName}" failed: ${errorMessage$6(error)}`);
|
|
89792
89792
|
}
|
|
89793
89793
|
return makeToolResult(call, effectiveArgs, toolResult);
|
|
89794
89794
|
}
|
|
@@ -89820,7 +89820,7 @@ async function finalizePendingToolResult(step, pendingResult) {
|
|
|
89820
89820
|
toolCallId: pendingResult.toolCall.id,
|
|
89821
89821
|
error
|
|
89822
89822
|
});
|
|
89823
|
-
const output = aborted ? `Tool "${pendingResult.toolName}" aborted during finalizeToolResult hook.` : `finalizeToolResult hook failed for "${pendingResult.toolName}": ${errorMessage$
|
|
89823
|
+
const output = aborted ? `Tool "${pendingResult.toolName}" aborted during finalizeToolResult hook.` : `finalizeToolResult hook failed for "${pendingResult.toolName}": ${errorMessage$6(error)}`;
|
|
89824
89824
|
return {
|
|
89825
89825
|
...pendingResult,
|
|
89826
89826
|
stopTurn: pendingResult.stopTurn,
|
|
@@ -90178,7 +90178,7 @@ async function runTurn(input) {
|
|
|
90178
90178
|
usage
|
|
90179
90179
|
};
|
|
90180
90180
|
}
|
|
90181
|
-
dispatchEvent(makeInterruptedEvent(isMaxStepsExceededError(error) ? "max_steps" : "error", steps, activeStep, errorMessage$
|
|
90181
|
+
dispatchEvent(makeInterruptedEvent(isMaxStepsExceededError(error) ? "max_steps" : "error", steps, activeStep, errorMessage$6(error)));
|
|
90182
90182
|
throw error;
|
|
90183
90183
|
}
|
|
90184
90184
|
return {
|
|
@@ -94455,6 +94455,13 @@ var LspRegistry = class {
|
|
|
94455
94455
|
};
|
|
94456
94456
|
//#endregion
|
|
94457
94457
|
//#region ../../packages/agent-core/src/agent/tool/index.ts
|
|
94458
|
+
function cloneTodos(todos) {
|
|
94459
|
+
return todos.map((todo) => ({
|
|
94460
|
+
title: todo.title,
|
|
94461
|
+
status: todo.status,
|
|
94462
|
+
phase: todo.phase
|
|
94463
|
+
}));
|
|
94464
|
+
}
|
|
94458
94465
|
const CRITERIA_SYSTEM_PROMPT = [
|
|
94459
94466
|
"You generate concrete, verifiable acceptance criteria for a given objective.",
|
|
94460
94467
|
"Criteria must be specific and testable — state what should work end-to-end, not just what should exist.",
|
|
@@ -94645,12 +94652,20 @@ var ToolManager = class {
|
|
|
94645
94652
|
});
|
|
94646
94653
|
}
|
|
94647
94654
|
updateStore(key, value) {
|
|
94655
|
+
const storedValue = key === "todo" ? cloneTodos(value ?? []) : value;
|
|
94648
94656
|
this.agent.records.logRecord({
|
|
94649
94657
|
type: "tools.update_store",
|
|
94650
94658
|
key,
|
|
94651
|
-
value
|
|
94659
|
+
value: storedValue
|
|
94660
|
+
});
|
|
94661
|
+
this.store[key] = storedValue;
|
|
94662
|
+
if (key === "todo") this.agent.emitEvent({
|
|
94663
|
+
type: "todo.updated",
|
|
94664
|
+
todos: this.getTodos()
|
|
94652
94665
|
});
|
|
94653
|
-
|
|
94666
|
+
}
|
|
94667
|
+
getTodos() {
|
|
94668
|
+
return cloneTodos(this.store.todo ?? []);
|
|
94654
94669
|
}
|
|
94655
94670
|
registerUserTool(input) {
|
|
94656
94671
|
this.agent.records.logRecord({
|
|
@@ -96700,6 +96715,9 @@ var Agent = class {
|
|
|
96700
96715
|
getGoal: () => {
|
|
96701
96716
|
return this.goal.getGoal();
|
|
96702
96717
|
},
|
|
96718
|
+
getTodos: () => {
|
|
96719
|
+
return this.tools.getTodos();
|
|
96720
|
+
},
|
|
96703
96721
|
setGoalBudget: async (payload) => {
|
|
96704
96722
|
const { value, unit } = payload;
|
|
96705
96723
|
let budgetLimits;
|
|
@@ -114592,7 +114610,7 @@ function markitConversionCacheKey(bytes, extension) {
|
|
|
114592
114610
|
function cacheEntryPath(key) {
|
|
114593
114611
|
return path$8.join(documentConversionCacheDir(), `${key}.json`);
|
|
114594
114612
|
}
|
|
114595
|
-
function errorMessage$
|
|
114613
|
+
function errorMessage$3(error) {
|
|
114596
114614
|
return error instanceof Error ? error.message : String(error);
|
|
114597
114615
|
}
|
|
114598
114616
|
function parseCacheEntry(raw) {
|
|
@@ -114611,14 +114629,14 @@ async function readMarkitConversionCache(key) {
|
|
|
114611
114629
|
try {
|
|
114612
114630
|
raw = await fs$1.readFile(target, "utf8");
|
|
114613
114631
|
} catch (error) {
|
|
114614
|
-
if (!isEnoent(error)) log.debug("document conversion cache read failed", { error: errorMessage$
|
|
114632
|
+
if (!isEnoent(error)) log.debug("document conversion cache read failed", { error: errorMessage$3(error) });
|
|
114615
114633
|
return { status: "miss" };
|
|
114616
114634
|
}
|
|
114617
114635
|
let entry;
|
|
114618
114636
|
try {
|
|
114619
114637
|
entry = parseCacheEntry(raw);
|
|
114620
114638
|
} catch (error) {
|
|
114621
|
-
log.debug("document conversion cache read failed", { error: errorMessage$
|
|
114639
|
+
log.debug("document conversion cache read failed", { error: errorMessage$3(error) });
|
|
114622
114640
|
entry = null;
|
|
114623
114641
|
}
|
|
114624
114642
|
if (entry === null) {
|
|
@@ -114635,7 +114653,7 @@ async function pruneMarkitConversionCache(cacheDir) {
|
|
|
114635
114653
|
try {
|
|
114636
114654
|
names = await fs$1.readdir(cacheDir);
|
|
114637
114655
|
} catch (error) {
|
|
114638
|
-
if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$
|
|
114656
|
+
if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$3(error) });
|
|
114639
114657
|
return;
|
|
114640
114658
|
}
|
|
114641
114659
|
const now = Date.now();
|
|
@@ -114647,7 +114665,7 @@ async function pruneMarkitConversionCache(cacheDir) {
|
|
|
114647
114665
|
try {
|
|
114648
114666
|
stat = await fs$1.stat(entryPath);
|
|
114649
114667
|
} catch (error) {
|
|
114650
|
-
if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$
|
|
114668
|
+
if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$3(error) });
|
|
114651
114669
|
continue;
|
|
114652
114670
|
}
|
|
114653
114671
|
if (!stat.isFile()) continue;
|
|
@@ -114671,7 +114689,7 @@ async function pruneMarkitConversionCache(cacheDir) {
|
|
|
114671
114689
|
await fs$1.rm(entry.path, { force: true });
|
|
114672
114690
|
totalBytes -= entry.size;
|
|
114673
114691
|
} catch (error) {
|
|
114674
|
-
if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$
|
|
114692
|
+
if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$3(error) });
|
|
114675
114693
|
}
|
|
114676
114694
|
}
|
|
114677
114695
|
}
|
|
@@ -114689,11 +114707,11 @@ async function writeMarkitConversionCache(key, content) {
|
|
|
114689
114707
|
await fs$1.rename(tempPath, target);
|
|
114690
114708
|
} catch (error) {
|
|
114691
114709
|
await fs$1.rm(tempPath, { force: true }).catch(() => void 0);
|
|
114692
|
-
log.debug("document conversion cache write failed", { error: errorMessage$
|
|
114710
|
+
log.debug("document conversion cache write failed", { error: errorMessage$3(error) });
|
|
114693
114711
|
return;
|
|
114694
114712
|
}
|
|
114695
114713
|
pruneMarkitConversionCache(cacheDir).catch((error) => {
|
|
114696
|
-
log.debug("document conversion cache prune failed", { error: errorMessage$
|
|
114714
|
+
log.debug("document conversion cache prune failed", { error: errorMessage$3(error) });
|
|
114697
114715
|
});
|
|
114698
114716
|
}
|
|
114699
114717
|
//#endregion
|
|
@@ -116759,6 +116777,9 @@ var SessionAPIImpl = class {
|
|
|
116759
116777
|
getGoal({ agentId, ...payload }) {
|
|
116760
116778
|
return this.getAgent(agentId).getGoal(payload);
|
|
116761
116779
|
}
|
|
116780
|
+
getTodos({ agentId, ...payload }) {
|
|
116781
|
+
return this.getAgent(agentId).getTodos(payload);
|
|
116782
|
+
}
|
|
116762
116783
|
setGoalBudget({ agentId, ...payload }) {
|
|
116763
116784
|
return this.getAgent(agentId).setGoalBudget(payload);
|
|
116764
116785
|
}
|
|
@@ -118504,6 +118525,9 @@ var ScreamCore = class {
|
|
|
118504
118525
|
getGoal({ sessionId, ...payload }) {
|
|
118505
118526
|
return this.sessionApi(sessionId).getGoal(payload);
|
|
118506
118527
|
}
|
|
118528
|
+
getTodos({ sessionId, ...payload }) {
|
|
118529
|
+
return this.sessionApi(sessionId).getTodos(payload);
|
|
118530
|
+
}
|
|
118507
118531
|
setGoalBudget({ sessionId, ...payload }) {
|
|
118508
118532
|
return this.sessionApi(sessionId).setGoalBudget(payload);
|
|
118509
118533
|
}
|
|
@@ -118970,6 +118994,12 @@ var SDKRpcClient = class {
|
|
|
118970
118994
|
agentId: this.interactiveAgentId
|
|
118971
118995
|
});
|
|
118972
118996
|
}
|
|
118997
|
+
async getTodos(input) {
|
|
118998
|
+
return (await this.getRpc()).getTodos({
|
|
118999
|
+
sessionId: input.sessionId,
|
|
119000
|
+
agentId: this.interactiveAgentId
|
|
119001
|
+
});
|
|
119002
|
+
}
|
|
118973
119003
|
async setGoalBudget(input) {
|
|
118974
119004
|
return (await this.getRpc()).setGoalBudget({
|
|
118975
119005
|
sessionId: input.sessionId,
|
|
@@ -119289,7 +119319,7 @@ var SDKRpcClient = class {
|
|
|
119289
119319
|
type: "error",
|
|
119290
119320
|
sessionId: request.sessionId,
|
|
119291
119321
|
agentId: request.agentId,
|
|
119292
|
-
...makeErrorPayload(ErrorCodes.SESSION_APPROVAL_HANDLER_ERROR, errorMessage$
|
|
119322
|
+
...makeErrorPayload(ErrorCodes.SESSION_APPROVAL_HANDLER_ERROR, errorMessage$2(error))
|
|
119293
119323
|
});
|
|
119294
119324
|
return {
|
|
119295
119325
|
decision: "cancelled",
|
|
@@ -119307,7 +119337,7 @@ var SDKRpcClient = class {
|
|
|
119307
119337
|
type: "error",
|
|
119308
119338
|
sessionId: request.sessionId,
|
|
119309
119339
|
agentId: request.agentId,
|
|
119310
|
-
...makeErrorPayload(ErrorCodes.SESSION_QUESTION_HANDLER_ERROR, errorMessage$
|
|
119340
|
+
...makeErrorPayload(ErrorCodes.SESSION_QUESTION_HANDLER_ERROR, errorMessage$2(error))
|
|
119311
119341
|
});
|
|
119312
119342
|
return null;
|
|
119313
119343
|
}
|
|
@@ -119342,7 +119372,7 @@ var ClientAPI = class {
|
|
|
119342
119372
|
return this.client.toolCall(request);
|
|
119343
119373
|
}
|
|
119344
119374
|
};
|
|
119345
|
-
function errorMessage$
|
|
119375
|
+
function errorMessage$2(error) {
|
|
119346
119376
|
return error instanceof Error ? error.message : String(error);
|
|
119347
119377
|
}
|
|
119348
119378
|
//#endregion
|
|
@@ -119733,6 +119763,10 @@ var Session = class {
|
|
|
119733
119763
|
this.ensureOpen();
|
|
119734
119764
|
return this.rpc.getGoal({ sessionId: this.id });
|
|
119735
119765
|
}
|
|
119766
|
+
async getTodos() {
|
|
119767
|
+
this.ensureOpen();
|
|
119768
|
+
return this.rpc.getTodos({ sessionId: this.id });
|
|
119769
|
+
}
|
|
119736
119770
|
async setGoalBudget(value, unit) {
|
|
119737
119771
|
this.ensureOpen();
|
|
119738
119772
|
return this.rpc.setGoalBudget({
|
|
@@ -120184,7 +120218,7 @@ function optionalBuildString(value) {
|
|
|
120184
120218
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
120185
120219
|
}
|
|
120186
120220
|
const SCREAM_BUILD_INFO = {
|
|
120187
|
-
version: optionalBuildString("0.
|
|
120221
|
+
version: optionalBuildString("0.11.1"),
|
|
120188
120222
|
channel: optionalBuildString(""),
|
|
120189
120223
|
commit: optionalBuildString(""),
|
|
120190
120224
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -120273,7 +120307,7 @@ async function handleExport(deps, sessionId, output, opts) {
|
|
|
120273
120307
|
});
|
|
120274
120308
|
deps.stdout.write(`${result.zipPath}\n`);
|
|
120275
120309
|
} catch (error) {
|
|
120276
|
-
deps.stderr.write(`${errorMessage(error)}\n`);
|
|
120310
|
+
deps.stderr.write(`${errorMessage$1(error)}\n`);
|
|
120277
120311
|
deps.exit(1);
|
|
120278
120312
|
}
|
|
120279
120313
|
}
|
|
@@ -120336,7 +120370,7 @@ async function confirmPreviousSession(summary) {
|
|
|
120336
120370
|
rl.close();
|
|
120337
120371
|
}
|
|
120338
120372
|
}
|
|
120339
|
-
function errorMessage(error) {
|
|
120373
|
+
function errorMessage$1(error) {
|
|
120340
120374
|
return error instanceof Error ? error.message : String(error);
|
|
120341
120375
|
}
|
|
120342
120376
|
//#endregion
|
|
@@ -125537,6 +125571,28 @@ function detectGoalConflict(state, _action) {
|
|
|
125537
125571
|
return null;
|
|
125538
125572
|
}
|
|
125539
125573
|
//#endregion
|
|
125574
|
+
//#region src/utils/goal-refiner.md
|
|
125575
|
+
var goal_refiner_default = "You are a goal refiner. Given a brief task description, produce one clear, actionable objective sentence of at most 200 characters. Return only the objective: no explanation, quotation marks, labels, or prefixes.\n";
|
|
125576
|
+
/**
|
|
125577
|
+
* Normalize an LLM-refined objective. Invalid output falls back to the user's
|
|
125578
|
+
* original description so refinement never loses the requested task.
|
|
125579
|
+
*/
|
|
125580
|
+
function parseRefinedGoal(rawOutput, fallback) {
|
|
125581
|
+
const normalized = rawOutput.trim().replace(/^```(?:text)?\s*/i, "").replace(/\s*```$/, "").trim().replace(/^(?:objective|goal)\s*:\s*/i, "").replace(/^["'“‘](.*)["'”’]$/s, "$1").replaceAll(/\s+/g, " ").trim();
|
|
125582
|
+
if (normalized.length === 0 || normalized.length > 200) return fallback.trim();
|
|
125583
|
+
return normalized;
|
|
125584
|
+
}
|
|
125585
|
+
/** Refine a goal through the session LLM, with a deterministic raw-input fallback. */
|
|
125586
|
+
async function refineGoal(session, description) {
|
|
125587
|
+
const fallback = description.trim();
|
|
125588
|
+
if (fallback.length === 0) return "";
|
|
125589
|
+
try {
|
|
125590
|
+
return parseRefinedGoal(await session.generateText(goal_refiner_default, fallback), fallback);
|
|
125591
|
+
} catch {
|
|
125592
|
+
return fallback;
|
|
125593
|
+
}
|
|
125594
|
+
}
|
|
125595
|
+
//#endregion
|
|
125540
125596
|
//#region src/tui/commands/goal.ts
|
|
125541
125597
|
const GOAL_STATUS_DISMISS_MS = 1e4;
|
|
125542
125598
|
let activeGoalPanel;
|
|
@@ -125633,7 +125689,6 @@ async function createGoal(host, parsed) {
|
|
|
125633
125689
|
}
|
|
125634
125690
|
await showGoalConfigWizard(host, session, parsed.objective, parsed.replace);
|
|
125635
125691
|
}
|
|
125636
|
-
const GOAL_REFINER_SYSTEM_PROMPT = "You are a goal refiner. Given a brief task description, produce a single clear, actionable objective sentence (max 200 chars). Do not add explanations, quotes, or prefixes.";
|
|
125637
125692
|
/**
|
|
125638
125693
|
* Guided goal creation: collect a brief task description, refine it via the
|
|
125639
125694
|
* LLM into a single objective sentence, let the user confirm/edit, then enter
|
|
@@ -125659,13 +125714,7 @@ async function guidedGoalSetup(host) {
|
|
|
125659
125714
|
});
|
|
125660
125715
|
if (initialDesc === void 0) return;
|
|
125661
125716
|
host.showStatus(t("goal.setup_refining"));
|
|
125662
|
-
|
|
125663
|
-
try {
|
|
125664
|
-
objective = (await session.generateText(GOAL_REFINER_SYSTEM_PROMPT, initialDesc)).trim();
|
|
125665
|
-
if (objective.length === 0) objective = initialDesc;
|
|
125666
|
-
} catch {
|
|
125667
|
-
objective = initialDesc;
|
|
125668
|
-
}
|
|
125717
|
+
const objective = await refineGoal(session, initialDesc);
|
|
125669
125718
|
const confirmed = await promptText(host, TextInputDialogComponent, {
|
|
125670
125719
|
title: t("goal.setup_title_confirm"),
|
|
125671
125720
|
subtitle: t("goal.setup_confirm_hint"),
|
|
@@ -131153,7 +131202,7 @@ async function readMarketplaceText(location, fetchImpl) {
|
|
|
131153
131202
|
}
|
|
131154
131203
|
function parseMarketplaceEntry(value, index, location) {
|
|
131155
131204
|
if (!isRecord$3(value)) throw new TypeError(`插件市场条目 ${index + 1} 必须是一个对象.`);
|
|
131156
|
-
const id = requiredString(value, "id", index);
|
|
131205
|
+
const id = requiredString$1(value, "id", index);
|
|
131157
131206
|
const source = stringField$1(value, "source") ?? stringField$1(value, "url") ?? stringField$1(value, "downloadUrl");
|
|
131158
131207
|
if (source === void 0) throw new Error(`插件市场条目 ${id} 必须定义 "source".`);
|
|
131159
131208
|
return {
|
|
@@ -131188,7 +131237,7 @@ function resolveLocalPath(input, workDir) {
|
|
|
131188
131237
|
if (input.startsWith("~/")) return join(homedir(), input.slice(2));
|
|
131189
131238
|
return isAbsolute(input) ? input : resolve(workDir, input);
|
|
131190
131239
|
}
|
|
131191
|
-
function requiredString(value, field, index) {
|
|
131240
|
+
function requiredString$1(value, field, index) {
|
|
131192
131241
|
const result = stringField$1(value, field);
|
|
131193
131242
|
if (result === void 0) throw new Error(`插件市场条目 ${index + 1} 必须定义 "${field}".`);
|
|
131194
131243
|
return result;
|
|
@@ -135727,6 +135776,9 @@ var SessionEventHandler = class {
|
|
|
135727
135776
|
case "goal.updated":
|
|
135728
135777
|
this.handleGoalUpdated(event);
|
|
135729
135778
|
break;
|
|
135779
|
+
case "todo.updated":
|
|
135780
|
+
this.handleTodoUpdated(event);
|
|
135781
|
+
break;
|
|
135730
135782
|
default: break;
|
|
135731
135783
|
}
|
|
135732
135784
|
}
|
|
@@ -135830,7 +135882,7 @@ var SessionEventHandler = class {
|
|
|
135830
135882
|
handleTurnEnd(event, sendQueued) {
|
|
135831
135883
|
this.host.setAppState({ reconnectAttempt: 0 });
|
|
135832
135884
|
const todos = this.host.state.todoPanel.getTodos();
|
|
135833
|
-
if (todos.length > 0 && todos.every((
|
|
135885
|
+
if (todos.length > 0 && todos.every((todo) => todo.status === "done")) this.host.streamingUI.setTodoList([]);
|
|
135834
135886
|
this.host.streamingUI.resetToolUi();
|
|
135835
135887
|
this.host.streamingUI.finalizeTurn(sendQueued);
|
|
135836
135888
|
}
|
|
@@ -136001,17 +136053,7 @@ var SessionEventHandler = class {
|
|
|
136001
136053
|
synthetic: event.synthetic,
|
|
136002
136054
|
display: event.display
|
|
136003
136055
|
};
|
|
136004
|
-
|
|
136005
|
-
if (matchedCall !== void 0 && matchedCall.name === "TodoList" && !event.isError) {
|
|
136006
|
-
const rawTodos = matchedCall.args.todos;
|
|
136007
|
-
if (Array.isArray(rawTodos)) {
|
|
136008
|
-
const sanitized = rawTodos.filter((todo) => isTodoItemShape(todo)).map((t) => ({
|
|
136009
|
-
title: t.title,
|
|
136010
|
-
status: t.status
|
|
136011
|
-
}));
|
|
136012
|
-
streamingUI.setTodoList(sanitized);
|
|
136013
|
-
}
|
|
136014
|
-
}
|
|
136056
|
+
streamingUI.completeToolResult(event.toolCallId, resultData);
|
|
136015
136057
|
if (canTransitionTo(this.host.state.appState.streamingPhase, "waiting")) this.host.setAppState({ streamingPhase: "waiting" });
|
|
136016
136058
|
}
|
|
136017
136059
|
handleStatusUpdate(event) {
|
|
@@ -136155,6 +136197,9 @@ var SessionEventHandler = class {
|
|
|
136155
136197
|
goalActive: snapshot.status === "active"
|
|
136156
136198
|
});
|
|
136157
136199
|
}
|
|
136200
|
+
handleTodoUpdated(event) {
|
|
136201
|
+
this.host.streamingUI.setTodoList(event.todos);
|
|
136202
|
+
}
|
|
136158
136203
|
handleCompactionBegin(event) {
|
|
136159
136204
|
this.host.streamingUI.finalizeLiveTextBuffers();
|
|
136160
136205
|
this.lastCompactionTrigger = event.trigger;
|
|
@@ -146297,9 +146342,11 @@ function installStreamJsonTerminationHandlers(cleanup) {
|
|
|
146297
146342
|
/** Error carrying an HTTP status code for REST handlers. */
|
|
146298
146343
|
var HttpError = class extends Error {
|
|
146299
146344
|
statusCode;
|
|
146300
|
-
|
|
146345
|
+
code;
|
|
146346
|
+
constructor(statusCode, message, code) {
|
|
146301
146347
|
super(message);
|
|
146302
146348
|
this.statusCode = statusCode;
|
|
146349
|
+
this.code = code;
|
|
146303
146350
|
}
|
|
146304
146351
|
};
|
|
146305
146352
|
const HEARTBEAT_INTERVAL_MS = 3e4;
|
|
@@ -146331,20 +146378,45 @@ const contentTypes = {
|
|
|
146331
146378
|
ttf: "font/ttf"
|
|
146332
146379
|
};
|
|
146333
146380
|
/** Reads and parses a JSON request body (64 KiB cap). */
|
|
146381
|
+
function cloneSnapshot(value) {
|
|
146382
|
+
return structuredClone(value);
|
|
146383
|
+
}
|
|
146384
|
+
function errorMessage(error) {
|
|
146385
|
+
return error instanceof Error ? error.message : String(error);
|
|
146386
|
+
}
|
|
146387
|
+
function sendJson(res, statusCode, body) {
|
|
146388
|
+
res.writeHead(statusCode, { "Content-Type": "application/json" });
|
|
146389
|
+
res.end(JSON.stringify(body));
|
|
146390
|
+
}
|
|
146334
146391
|
function readJsonBody(req) {
|
|
146335
146392
|
return new Promise((resolve, reject) => {
|
|
146336
146393
|
let data = "";
|
|
146394
|
+
let size = 0;
|
|
146395
|
+
let rejected = false;
|
|
146337
146396
|
req.on("data", (chunk) => {
|
|
146397
|
+
if (rejected) return;
|
|
146398
|
+
size += chunk.length;
|
|
146399
|
+
if (size > 64 * 1024) {
|
|
146400
|
+
rejected = true;
|
|
146401
|
+
data = "";
|
|
146402
|
+
reject(new HttpError(413, "Request body too large"));
|
|
146403
|
+
return;
|
|
146404
|
+
}
|
|
146338
146405
|
data += chunk.toString("utf-8");
|
|
146339
|
-
if (data.length > 64 * 1024) reject(new HttpError(413, "Request body too large"));
|
|
146340
146406
|
});
|
|
146341
146407
|
req.on("end", () => {
|
|
146408
|
+
if (rejected) return;
|
|
146342
146409
|
if (!data) {
|
|
146343
146410
|
resolve({});
|
|
146344
146411
|
return;
|
|
146345
146412
|
}
|
|
146346
146413
|
try {
|
|
146347
|
-
|
|
146414
|
+
const parsed = JSON.parse(data);
|
|
146415
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
146416
|
+
reject(new HttpError(400, "JSON body must be an object"));
|
|
146417
|
+
return;
|
|
146418
|
+
}
|
|
146419
|
+
resolve(parsed);
|
|
146348
146420
|
} catch {
|
|
146349
146421
|
reject(new HttpError(400, "Invalid JSON body"));
|
|
146350
146422
|
}
|
|
@@ -146352,6 +146424,75 @@ function readJsonBody(req) {
|
|
|
146352
146424
|
req.on("error", reject);
|
|
146353
146425
|
});
|
|
146354
146426
|
}
|
|
146427
|
+
function requiredString(body, field) {
|
|
146428
|
+
const value = body[field];
|
|
146429
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new HttpError(400, `Missing or empty ${field} field`, "request.invalid");
|
|
146430
|
+
return value.trim();
|
|
146431
|
+
}
|
|
146432
|
+
function optionalString(body, field) {
|
|
146433
|
+
const value = body[field];
|
|
146434
|
+
if (value === void 0) return void 0;
|
|
146435
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new HttpError(400, `${field} must be a non-empty string`, "request.invalid");
|
|
146436
|
+
return value.trim();
|
|
146437
|
+
}
|
|
146438
|
+
function optionalBoolean(body, field, fallback) {
|
|
146439
|
+
const value = body[field];
|
|
146440
|
+
if (value === void 0) return fallback;
|
|
146441
|
+
if (typeof value !== "boolean") throw new HttpError(400, `${field} must be a boolean`, "request.invalid");
|
|
146442
|
+
return value;
|
|
146443
|
+
}
|
|
146444
|
+
function parseGoalBudgets(body) {
|
|
146445
|
+
const raw = body["budgets"];
|
|
146446
|
+
if (raw === void 0) return [];
|
|
146447
|
+
if (!Array.isArray(raw)) throw new HttpError(400, "budgets must be an array", "request.invalid");
|
|
146448
|
+
const validUnits = new Set([
|
|
146449
|
+
"turns",
|
|
146450
|
+
"tokens",
|
|
146451
|
+
"milliseconds",
|
|
146452
|
+
"seconds",
|
|
146453
|
+
"minutes",
|
|
146454
|
+
"hours"
|
|
146455
|
+
]);
|
|
146456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
146457
|
+
return raw.map((item) => {
|
|
146458
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) throw new HttpError(400, "Each budget must be an object", "request.invalid");
|
|
146459
|
+
const value = item["value"];
|
|
146460
|
+
const unit = item["unit"];
|
|
146461
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new HttpError(400, "Budget value must be a positive safe integer", "request.invalid");
|
|
146462
|
+
if (typeof unit !== "string" || !validUnits.has(unit)) throw new HttpError(400, "Budget unit must be turns, tokens, milliseconds, seconds, minutes, or hours", "request.invalid");
|
|
146463
|
+
const typedUnit = unit;
|
|
146464
|
+
const dimension = typedUnit === "turns" || typedUnit === "tokens" ? typedUnit : "time";
|
|
146465
|
+
if (seen.has(dimension)) throw new HttpError(400, `Duplicate budget dimension: ${dimension}`, "request.invalid");
|
|
146466
|
+
seen.add(dimension);
|
|
146467
|
+
return {
|
|
146468
|
+
value,
|
|
146469
|
+
unit: typedUnit
|
|
146470
|
+
};
|
|
146471
|
+
});
|
|
146472
|
+
}
|
|
146473
|
+
function toHttpError(error) {
|
|
146474
|
+
if (error instanceof HttpError) return error;
|
|
146475
|
+
if (isScreamError(error)) switch (error.code) {
|
|
146476
|
+
case ErrorCodes.GOAL_OBJECTIVE_EMPTY:
|
|
146477
|
+
case ErrorCodes.GOAL_OBJECTIVE_TOO_LONG:
|
|
146478
|
+
case ErrorCodes.REQUEST_INVALID: return new HttpError(400, error.message, error.code);
|
|
146479
|
+
case ErrorCodes.GOAL_NOT_FOUND:
|
|
146480
|
+
case ErrorCodes.SESSION_NOT_FOUND: return new HttpError(404, error.message, error.code);
|
|
146481
|
+
case ErrorCodes.GOAL_ALREADY_EXISTS:
|
|
146482
|
+
case ErrorCodes.GOAL_NOT_RESUMABLE:
|
|
146483
|
+
case ErrorCodes.GOAL_STATUS_INVALID:
|
|
146484
|
+
case ErrorCodes.TURN_AGENT_BUSY: return new HttpError(409, error.message, error.code);
|
|
146485
|
+
default: return new HttpError(500, error.message, error.code);
|
|
146486
|
+
}
|
|
146487
|
+
return new HttpError(500, errorMessage(error));
|
|
146488
|
+
}
|
|
146489
|
+
function sendHttpError(res, error) {
|
|
146490
|
+
const mapped = toHttpError(error);
|
|
146491
|
+
sendJson(res, mapped.statusCode, {
|
|
146492
|
+
code: mapped.code ?? mapped.statusCode,
|
|
146493
|
+
message: mapped.message
|
|
146494
|
+
});
|
|
146495
|
+
}
|
|
146355
146496
|
function isVolatileEvent(event) {
|
|
146356
146497
|
switch (event.type) {
|
|
146357
146498
|
case "assistant.delta":
|
|
@@ -146539,11 +146680,21 @@ var WebSession = class {
|
|
|
146539
146680
|
nextSeq = 1;
|
|
146540
146681
|
epoch = 1;
|
|
146541
146682
|
cachedStatus = null;
|
|
146683
|
+
cachedGoal = null;
|
|
146684
|
+
cachedTodos = [];
|
|
146685
|
+
goalEventRevision = 0;
|
|
146686
|
+
todoEventRevision = 0;
|
|
146687
|
+
goalMutationTail = Promise.resolve();
|
|
146688
|
+
pendingGoalMutations = 0;
|
|
146689
|
+
coreSessionId;
|
|
146690
|
+
ready;
|
|
146542
146691
|
title = "New Session";
|
|
146543
146692
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
146544
146693
|
heartbeatTimer = null;
|
|
146545
146694
|
unsubscribe = null;
|
|
146546
146695
|
busy = false;
|
|
146696
|
+
/** pendingMsgId awaiting compaction completion/cancellation event. */
|
|
146697
|
+
pendingCompactionMsgId = null;
|
|
146547
146698
|
homeDir;
|
|
146548
146699
|
yolo;
|
|
146549
146700
|
/** Fork hook supplied by SessionManager; absent in standalone mode. */
|
|
@@ -146558,13 +146709,14 @@ var WebSession = class {
|
|
|
146558
146709
|
this.yolo = opts.yolo;
|
|
146559
146710
|
this.homeDir = opts.homeDir ?? null;
|
|
146560
146711
|
this.createdAt = opts.createdAt;
|
|
146712
|
+
this.coreSessionId = session?.id ?? opts.coreSessionId ?? opts.sessionId;
|
|
146561
146713
|
this.onFork = opts.onFork;
|
|
146562
146714
|
if (opts.title) this.title = opts.title;
|
|
146563
146715
|
if (session) {
|
|
146564
|
-
this.refreshStatus();
|
|
146565
146716
|
this.subscribeEvents();
|
|
146566
146717
|
this.setupApprovalHandler();
|
|
146567
|
-
|
|
146718
|
+
this.ready = this.initializeCoreState();
|
|
146719
|
+
} else this.ready = Promise.resolve();
|
|
146568
146720
|
this.startHeartbeat();
|
|
146569
146721
|
}
|
|
146570
146722
|
get isActive() {
|
|
@@ -146586,7 +146738,8 @@ var WebSession = class {
|
|
|
146586
146738
|
for (const um of this.userMessages) entries.push({
|
|
146587
146739
|
type: "user_message",
|
|
146588
146740
|
text: um.msg.content,
|
|
146589
|
-
beforeSeq: um.beforeSeq
|
|
146741
|
+
beforeSeq: um.beforeSeq,
|
|
146742
|
+
clientMessageId: um.msg.clientMessageId
|
|
146590
146743
|
});
|
|
146591
146744
|
return entries;
|
|
146592
146745
|
}
|
|
@@ -146598,11 +146751,12 @@ var WebSession = class {
|
|
|
146598
146751
|
});
|
|
146599
146752
|
await appendJournalLine(this.homeDir, this.sessionId, line);
|
|
146600
146753
|
}
|
|
146601
|
-
async persistUserMessage(text, beforeSeq) {
|
|
146754
|
+
async persistUserMessage(text, beforeSeq, clientMessageId) {
|
|
146602
146755
|
this.userMessages.push({
|
|
146603
146756
|
msg: {
|
|
146604
146757
|
role: "user",
|
|
146605
146758
|
content: text,
|
|
146759
|
+
clientMessageId,
|
|
146606
146760
|
tools: []
|
|
146607
146761
|
},
|
|
146608
146762
|
beforeSeq
|
|
@@ -146611,7 +146765,8 @@ var WebSession = class {
|
|
|
146611
146765
|
const line = JSON.stringify({
|
|
146612
146766
|
type: "user_message",
|
|
146613
146767
|
text,
|
|
146614
|
-
beforeSeq
|
|
146768
|
+
beforeSeq,
|
|
146769
|
+
clientMessageId
|
|
146615
146770
|
});
|
|
146616
146771
|
await appendJournalLine(this.homeDir, this.sessionId, line);
|
|
146617
146772
|
}
|
|
@@ -146622,6 +146777,7 @@ var WebSession = class {
|
|
|
146622
146777
|
msg: {
|
|
146623
146778
|
role: "user",
|
|
146624
146779
|
content: um.text,
|
|
146780
|
+
clientMessageId: um.clientMessageId,
|
|
146625
146781
|
tools: []
|
|
146626
146782
|
},
|
|
146627
146783
|
beforeSeq: um.beforeSeq
|
|
@@ -146630,6 +146786,8 @@ var WebSession = class {
|
|
|
146630
146786
|
const je = entry;
|
|
146631
146787
|
this.journal.push(je);
|
|
146632
146788
|
this.nextSeq = Math.max(this.nextSeq, je.seq + 1);
|
|
146789
|
+
if (this.session === null && je.payload.agentId === "main" && je.payload.type === "goal.updated") this.cachedGoal = cloneSnapshot(je.payload.snapshot);
|
|
146790
|
+
else if (this.session === null && je.payload.agentId === "main" && je.payload.type === "todo.updated") this.cachedTodos = cloneSnapshot(je.payload.todos);
|
|
146633
146791
|
}
|
|
146634
146792
|
const msgs = this.buildMessages();
|
|
146635
146793
|
if (msgs.length > 0 && !this.customTitle) this.title = deriveTitle(msgs);
|
|
@@ -146645,6 +146803,7 @@ var WebSession = class {
|
|
|
146645
146803
|
getMetadata() {
|
|
146646
146804
|
return {
|
|
146647
146805
|
sessionId: this.sessionId,
|
|
146806
|
+
coreSessionId: this.coreSessionId,
|
|
146648
146807
|
workDir: this.workDir,
|
|
146649
146808
|
title: this.title,
|
|
146650
146809
|
createdAt: this.createdAt,
|
|
@@ -146653,11 +146812,12 @@ var WebSession = class {
|
|
|
146653
146812
|
};
|
|
146654
146813
|
}
|
|
146655
146814
|
appendEvent(event) {
|
|
146815
|
+
const payload = event.type === "goal.updated" || event.type === "todo.updated" ? cloneSnapshot(event) : event;
|
|
146656
146816
|
const entry = {
|
|
146657
146817
|
seq: this.nextSeq++,
|
|
146658
146818
|
epoch: this.epoch,
|
|
146659
|
-
volatile: isVolatileEvent(
|
|
146660
|
-
payload
|
|
146819
|
+
volatile: isVolatileEvent(payload),
|
|
146820
|
+
payload
|
|
146661
146821
|
};
|
|
146662
146822
|
this.journal.push(entry);
|
|
146663
146823
|
if (!entry.volatile) this.persistEntry(entry);
|
|
@@ -146666,10 +146826,17 @@ var WebSession = class {
|
|
|
146666
146826
|
subscribeEvents() {
|
|
146667
146827
|
if (!this.session) return;
|
|
146668
146828
|
this.unsubscribe = this.session.onEvent((event) => {
|
|
146829
|
+
if ((event.type === "goal.updated" || event.type === "todo.updated") && event.agentId !== "main") return;
|
|
146669
146830
|
if (event.type === "turn.started") this.busy = true;
|
|
146670
146831
|
else if (event.type === "turn.ended") {
|
|
146671
146832
|
this.busy = false;
|
|
146672
146833
|
this.updateTitle();
|
|
146834
|
+
} else if (event.type === "goal.updated") {
|
|
146835
|
+
this.goalEventRevision += 1;
|
|
146836
|
+
this.cachedGoal = cloneSnapshot(event.snapshot);
|
|
146837
|
+
} else if (event.type === "todo.updated") {
|
|
146838
|
+
this.todoEventRevision += 1;
|
|
146839
|
+
this.cachedTodos = cloneSnapshot(event.todos);
|
|
146673
146840
|
}
|
|
146674
146841
|
if (event.type === "session.meta.updated" || event.type === "turn.ended") this.refreshStatus();
|
|
146675
146842
|
const entry = this.appendEvent(event);
|
|
@@ -146677,10 +146844,46 @@ var WebSession = class {
|
|
|
146677
146844
|
type: "event",
|
|
146678
146845
|
seq: entry.seq,
|
|
146679
146846
|
epoch: entry.epoch,
|
|
146680
|
-
payload:
|
|
146847
|
+
payload: entry.payload
|
|
146681
146848
|
}, entry.volatile);
|
|
146849
|
+
if (event.type === "compaction.completed") {
|
|
146850
|
+
const msgId = this.pendingCompactionMsgId;
|
|
146851
|
+
this.pendingCompactionMsgId = null;
|
|
146852
|
+
this.refreshStatus().then(() => this.broadcastStatus());
|
|
146853
|
+
if (msgId) this.broadcast({
|
|
146854
|
+
type: "command_result",
|
|
146855
|
+
command: "compact",
|
|
146856
|
+
ok: true,
|
|
146857
|
+
message: "会话上下文已压缩。",
|
|
146858
|
+
pendingMsgId: msgId
|
|
146859
|
+
}, false);
|
|
146860
|
+
} else if (event.type === "compaction.cancelled") {
|
|
146861
|
+
const msgId = this.pendingCompactionMsgId;
|
|
146862
|
+
this.pendingCompactionMsgId = null;
|
|
146863
|
+
if (msgId) this.broadcast({
|
|
146864
|
+
type: "command_result",
|
|
146865
|
+
command: "compact",
|
|
146866
|
+
ok: false,
|
|
146867
|
+
message: `压缩已取消${event.reason ? `:${event.reason}` : "。"}`,
|
|
146868
|
+
pendingMsgId: msgId
|
|
146869
|
+
}, false);
|
|
146870
|
+
}
|
|
146682
146871
|
});
|
|
146683
146872
|
}
|
|
146873
|
+
async initializeCoreState() {
|
|
146874
|
+
const session = this.session;
|
|
146875
|
+
if (!session) return;
|
|
146876
|
+
const goalRevision = this.goalEventRevision;
|
|
146877
|
+
const todoRevision = this.todoEventRevision;
|
|
146878
|
+
const [status, goal, todos] = await Promise.all([
|
|
146879
|
+
session.getStatus().catch(() => null),
|
|
146880
|
+
session.getGoal(),
|
|
146881
|
+
session.getTodos()
|
|
146882
|
+
]);
|
|
146883
|
+
if (status !== null) this.cachedStatus = status;
|
|
146884
|
+
if (this.goalEventRevision === goalRevision) this.cachedGoal = cloneSnapshot(goal.goal);
|
|
146885
|
+
if (this.todoEventRevision === todoRevision) this.cachedTodos = cloneSnapshot(todos);
|
|
146886
|
+
}
|
|
146684
146887
|
async refreshStatus() {
|
|
146685
146888
|
if (!this.session) return;
|
|
146686
146889
|
try {
|
|
@@ -146766,26 +146969,30 @@ var WebSession = class {
|
|
|
146766
146969
|
this.send(ws, {
|
|
146767
146970
|
type: "error",
|
|
146768
146971
|
code: "session.inactive",
|
|
146769
|
-
message: "Session is archived (read-only)"
|
|
146972
|
+
message: "Session is archived (read-only)",
|
|
146973
|
+
clientMessageId
|
|
146770
146974
|
});
|
|
146771
146975
|
return;
|
|
146772
146976
|
}
|
|
146773
|
-
if (!text || this.busy) {
|
|
146977
|
+
if (!text || this.busy || this.pendingGoalMutations > 0) {
|
|
146774
146978
|
this.send(ws, {
|
|
146775
146979
|
type: "error",
|
|
146776
146980
|
code: "session.busy",
|
|
146777
|
-
message: "Session is busy"
|
|
146981
|
+
message: "Session is busy",
|
|
146982
|
+
clientMessageId
|
|
146778
146983
|
});
|
|
146779
146984
|
return;
|
|
146780
146985
|
}
|
|
146781
|
-
this.
|
|
146986
|
+
this.busy = true;
|
|
146987
|
+
this.persistUserMessage(text, this.nextSeq, clientMessageId);
|
|
146782
146988
|
this.broadcast({
|
|
146783
146989
|
type: "user_message",
|
|
146784
146990
|
clientMessageId,
|
|
146785
146991
|
text
|
|
146786
146992
|
}, false);
|
|
146787
146993
|
this.session.prompt(text).catch((error) => {
|
|
146788
|
-
this.
|
|
146994
|
+
this.busy = false;
|
|
146995
|
+
this.sendError(ws, error, clientMessageId);
|
|
146789
146996
|
});
|
|
146790
146997
|
break;
|
|
146791
146998
|
}
|
|
@@ -146848,7 +147055,7 @@ var WebSession = class {
|
|
|
146848
147055
|
});
|
|
146849
147056
|
return;
|
|
146850
147057
|
}
|
|
146851
|
-
const missing = this.journal.
|
|
147058
|
+
const missing = this.journal.filter((entry) => entry.seq > lastSeq);
|
|
146852
147059
|
for (const entry of missing) if (!entry.volatile) this.send(ws, {
|
|
146853
147060
|
type: "event",
|
|
146854
147061
|
seq: entry.seq,
|
|
@@ -146856,11 +147063,12 @@ var WebSession = class {
|
|
|
146856
147063
|
payload: entry.payload
|
|
146857
147064
|
});
|
|
146858
147065
|
}
|
|
146859
|
-
sendError(ws, error) {
|
|
147066
|
+
sendError(ws, error, clientMessageId) {
|
|
146860
147067
|
const message = error instanceof Error ? error.message : String(error);
|
|
146861
147068
|
this.send(ws, {
|
|
146862
147069
|
type: "error",
|
|
146863
|
-
message
|
|
147070
|
+
message,
|
|
147071
|
+
clientMessageId
|
|
146864
147072
|
});
|
|
146865
147073
|
}
|
|
146866
147074
|
async handleCommand(ws, command, args, pendingMsgId) {
|
|
@@ -146893,13 +147101,15 @@ var WebSession = class {
|
|
|
146893
147101
|
fail("会话忙碌中,无法压缩,请稍后再试。");
|
|
146894
147102
|
return;
|
|
146895
147103
|
}
|
|
146896
|
-
|
|
147104
|
+
if (this.pendingCompactionMsgId !== null) {
|
|
147105
|
+
fail("正在压缩中,请稍候。");
|
|
147106
|
+
return;
|
|
147107
|
+
}
|
|
147108
|
+
this.pendingCompactionMsgId = pendingMsgId ?? null;
|
|
146897
147109
|
try {
|
|
146898
147110
|
await this.session.compact();
|
|
146899
|
-
await this.refreshStatus();
|
|
146900
|
-
this.broadcastStatus();
|
|
146901
|
-
ok("会话上下文已压缩。");
|
|
146902
147111
|
} catch (error) {
|
|
147112
|
+
this.pendingCompactionMsgId = null;
|
|
146903
147113
|
fail(`压缩失败:${errMsg(error)}`);
|
|
146904
147114
|
}
|
|
146905
147115
|
return;
|
|
@@ -146943,7 +147153,7 @@ var WebSession = class {
|
|
|
146943
147153
|
return;
|
|
146944
147154
|
}
|
|
146945
147155
|
try {
|
|
146946
|
-
const result = await this.onFork(this.
|
|
147156
|
+
const result = await this.onFork(this.sessionId);
|
|
146947
147157
|
if (!result) {
|
|
146948
147158
|
fail("Fork 失败:无法复制当前会话。");
|
|
146949
147159
|
return;
|
|
@@ -147026,9 +147236,11 @@ var WebSession = class {
|
|
|
147026
147236
|
}, HEARTBEAT_INTERVAL_MS);
|
|
147027
147237
|
}
|
|
147028
147238
|
getSnapshot() {
|
|
147029
|
-
return {
|
|
147239
|
+
return cloneSnapshot({
|
|
147030
147240
|
sessionId: this.sessionId,
|
|
147031
147241
|
workDir: this.workDir,
|
|
147242
|
+
seq: this.nextSeq - 1,
|
|
147243
|
+
epoch: this.epoch,
|
|
147032
147244
|
model: this.cachedStatus?.model ?? "unknown",
|
|
147033
147245
|
permission: this.cachedStatus?.permission ?? this.permission,
|
|
147034
147246
|
messages: this.buildMessages(),
|
|
@@ -147050,8 +147262,10 @@ var WebSession = class {
|
|
|
147050
147262
|
},
|
|
147051
147263
|
busy: this.busy,
|
|
147052
147264
|
createdAt: this.createdAt,
|
|
147053
|
-
title: this.title
|
|
147054
|
-
|
|
147265
|
+
title: this.title,
|
|
147266
|
+
goal: this.cachedGoal,
|
|
147267
|
+
todos: this.cachedTodos
|
|
147268
|
+
});
|
|
147055
147269
|
}
|
|
147056
147270
|
getListItem() {
|
|
147057
147271
|
return {
|
|
@@ -147088,6 +147302,114 @@ var WebSession = class {
|
|
|
147088
147302
|
status: this.cachedStatus
|
|
147089
147303
|
}, false);
|
|
147090
147304
|
}
|
|
147305
|
+
getCoreSessionId() {
|
|
147306
|
+
return this.coreSessionId;
|
|
147307
|
+
}
|
|
147308
|
+
async refineGoalDescription(description) {
|
|
147309
|
+
await this.ready;
|
|
147310
|
+
return refineGoal(this.requireGoalSession(), description);
|
|
147311
|
+
}
|
|
147312
|
+
async createGoal(objective, completionCriterion, replace, budgets) {
|
|
147313
|
+
return this.serializeGoalMutation(async (session) => {
|
|
147314
|
+
let goal = await session.createGoal(objective, {
|
|
147315
|
+
completionCriterion,
|
|
147316
|
+
replace
|
|
147317
|
+
});
|
|
147318
|
+
try {
|
|
147319
|
+
for (const budget of budgets) goal = await session.setGoalBudget(budget.value, budget.unit);
|
|
147320
|
+
} catch (error) {
|
|
147321
|
+
throw new HttpError(500, `Goal configuration failed and the new goal was ${await this.pauseOrClearUnsafeActiveGoal(session)}: ${errorMessage(error)}`);
|
|
147322
|
+
}
|
|
147323
|
+
try {
|
|
147324
|
+
await this.startGoalExecution(session, objective);
|
|
147325
|
+
} catch (error) {
|
|
147326
|
+
throw new HttpError(500, `Goal was created but execution could not start; it was ${await this.pauseOrClearUnsafeActiveGoal(session)}: ${errorMessage(error)}`);
|
|
147327
|
+
}
|
|
147328
|
+
return cloneSnapshot(goal);
|
|
147329
|
+
});
|
|
147330
|
+
}
|
|
147331
|
+
async updateGoal(objective, budgets) {
|
|
147332
|
+
return this.serializeGoalMutation(async (session) => {
|
|
147333
|
+
const current = await session.getGoal();
|
|
147334
|
+
if (current.goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
|
|
147335
|
+
let goal = current.goal;
|
|
147336
|
+
if (objective !== void 0) goal = await session.updateGoalObjective(objective);
|
|
147337
|
+
for (const budget of budgets) goal = await session.setGoalBudget(budget.value, budget.unit);
|
|
147338
|
+
return cloneSnapshot(goal);
|
|
147339
|
+
});
|
|
147340
|
+
}
|
|
147341
|
+
async pauseGoal() {
|
|
147342
|
+
return this.serializeGoalMutation(async (session) => {
|
|
147343
|
+
const goal = await session.updateGoalStatus("paused");
|
|
147344
|
+
if (goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
|
|
147345
|
+
return cloneSnapshot(goal);
|
|
147346
|
+
}, true);
|
|
147347
|
+
}
|
|
147348
|
+
async resumeGoal() {
|
|
147349
|
+
return this.serializeGoalMutation(async (session) => {
|
|
147350
|
+
if ((await session.getGoal()).goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
|
|
147351
|
+
const goal = await session.updateGoalStatus("active");
|
|
147352
|
+
if (goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
|
|
147353
|
+
try {
|
|
147354
|
+
await this.startGoalExecution(session, "Continue working toward the active goal. Review current progress and remaining work before proceeding.");
|
|
147355
|
+
} catch (error) {
|
|
147356
|
+
throw new HttpError(500, `Goal was resumed but execution could not continue; it was ${await this.pauseOrClearUnsafeActiveGoal(session)}: ${errorMessage(error)}`);
|
|
147357
|
+
}
|
|
147358
|
+
return cloneSnapshot(goal);
|
|
147359
|
+
});
|
|
147360
|
+
}
|
|
147361
|
+
async cancelGoal() {
|
|
147362
|
+
await this.serializeGoalMutation(async (session) => {
|
|
147363
|
+
if ((await session.getGoal()).goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
|
|
147364
|
+
await session.cancelGoal();
|
|
147365
|
+
}, true);
|
|
147366
|
+
}
|
|
147367
|
+
requireGoalSession(allowBusy = false) {
|
|
147368
|
+
if (!this.session) throw new HttpError(409, "Session is archived (read-only)", ErrorCodes.SESSION_CLOSED);
|
|
147369
|
+
if (!allowBusy && this.busy) throw new HttpError(409, "Session is busy", ErrorCodes.TURN_AGENT_BUSY);
|
|
147370
|
+
return this.session;
|
|
147371
|
+
}
|
|
147372
|
+
serializeGoalMutation(mutation, allowBusy = false) {
|
|
147373
|
+
this.pendingGoalMutations += 1;
|
|
147374
|
+
const run = this.goalMutationTail.then(async () => {
|
|
147375
|
+
await this.ready;
|
|
147376
|
+
return mutation(this.requireGoalSession(allowBusy));
|
|
147377
|
+
});
|
|
147378
|
+
this.goalMutationTail = run.then(() => void 0, () => void 0);
|
|
147379
|
+
return run.finally(() => {
|
|
147380
|
+
this.pendingGoalMutations -= 1;
|
|
147381
|
+
});
|
|
147382
|
+
}
|
|
147383
|
+
async startGoalExecution(session, prompt) {
|
|
147384
|
+
this.busy = true;
|
|
147385
|
+
await this.persistUserMessage(prompt, this.nextSeq);
|
|
147386
|
+
this.broadcast({
|
|
147387
|
+
type: "user_message",
|
|
147388
|
+
text: prompt
|
|
147389
|
+
}, false);
|
|
147390
|
+
try {
|
|
147391
|
+
await session.prompt(prompt);
|
|
147392
|
+
} catch (error) {
|
|
147393
|
+
this.busy = false;
|
|
147394
|
+
throw error;
|
|
147395
|
+
}
|
|
147396
|
+
}
|
|
147397
|
+
async clearUnsafeActiveGoal(session) {
|
|
147398
|
+
try {
|
|
147399
|
+
await session.cancelGoal();
|
|
147400
|
+
} catch (rollbackError) {
|
|
147401
|
+
throw new HttpError(500, `Goal setup failed and rollback also failed: ${errorMessage(rollbackError)}`);
|
|
147402
|
+
}
|
|
147403
|
+
}
|
|
147404
|
+
async pauseOrClearUnsafeActiveGoal(session) {
|
|
147405
|
+
try {
|
|
147406
|
+
await session.updateGoalStatus("paused");
|
|
147407
|
+
return "paused";
|
|
147408
|
+
} catch {
|
|
147409
|
+
await this.clearUnsafeActiveGoal(session);
|
|
147410
|
+
return "cancelled";
|
|
147411
|
+
}
|
|
147412
|
+
}
|
|
147091
147413
|
get connectionCount() {
|
|
147092
147414
|
return this.connections.size;
|
|
147093
147415
|
}
|
|
@@ -147173,6 +147495,64 @@ var WebSession = class {
|
|
|
147173
147495
|
if (this.homeDir) await deletePersistedSession(this.homeDir, this.sessionId);
|
|
147174
147496
|
}
|
|
147175
147497
|
};
|
|
147498
|
+
async function handleGoalRoute(req, res, url, resolveSession) {
|
|
147499
|
+
const match = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/goal(?:/(refine|pause|resume|cancel))?$`).exec(url);
|
|
147500
|
+
if (!match) return false;
|
|
147501
|
+
const webSession = resolveSession(match[1]);
|
|
147502
|
+
if (!webSession) {
|
|
147503
|
+
sendJson(res, 404, {
|
|
147504
|
+
code: ErrorCodes.SESSION_NOT_FOUND,
|
|
147505
|
+
message: "Session not found"
|
|
147506
|
+
});
|
|
147507
|
+
return true;
|
|
147508
|
+
}
|
|
147509
|
+
const operation = match[2];
|
|
147510
|
+
const method = req.method ?? "GET";
|
|
147511
|
+
const expectedMethod = operation === void 0 ? new Set(["POST", "PATCH"]) : new Set(["POST"]);
|
|
147512
|
+
if (!expectedMethod.has(method)) {
|
|
147513
|
+
res.setHeader("Allow", [...expectedMethod].join(", "));
|
|
147514
|
+
sendJson(res, 405, {
|
|
147515
|
+
code: 405,
|
|
147516
|
+
message: "Method not allowed"
|
|
147517
|
+
});
|
|
147518
|
+
return true;
|
|
147519
|
+
}
|
|
147520
|
+
try {
|
|
147521
|
+
const body = await readJsonBody(req);
|
|
147522
|
+
if (operation === "refine") {
|
|
147523
|
+
sendJson(res, 200, { objective: await webSession.refineGoalDescription(requiredString(body, "description")) });
|
|
147524
|
+
return true;
|
|
147525
|
+
}
|
|
147526
|
+
if (operation === "pause") {
|
|
147527
|
+
await webSession.pauseGoal();
|
|
147528
|
+
sendJson(res, 202, { ok: true });
|
|
147529
|
+
return true;
|
|
147530
|
+
}
|
|
147531
|
+
if (operation === "resume") {
|
|
147532
|
+
await webSession.resumeGoal();
|
|
147533
|
+
sendJson(res, 202, { ok: true });
|
|
147534
|
+
return true;
|
|
147535
|
+
}
|
|
147536
|
+
if (operation === "cancel") {
|
|
147537
|
+
await webSession.cancelGoal();
|
|
147538
|
+
sendJson(res, 202, { ok: true });
|
|
147539
|
+
return true;
|
|
147540
|
+
}
|
|
147541
|
+
const budgets = parseGoalBudgets(body);
|
|
147542
|
+
if (method === "POST") {
|
|
147543
|
+
await webSession.createGoal(requiredString(body, "objective"), optionalString(body, "completionCriterion"), optionalBoolean(body, "replace", false), budgets);
|
|
147544
|
+
sendJson(res, 202, { ok: true });
|
|
147545
|
+
return true;
|
|
147546
|
+
}
|
|
147547
|
+
const objective = optionalString(body, "objective");
|
|
147548
|
+
if (objective === void 0 && body["budgets"] === void 0) throw new HttpError(400, "PATCH requires objective or budgets", "request.invalid");
|
|
147549
|
+
await webSession.updateGoal(objective, budgets);
|
|
147550
|
+
sendJson(res, 202, { ok: true });
|
|
147551
|
+
} catch (error) {
|
|
147552
|
+
sendHttpError(res, error);
|
|
147553
|
+
}
|
|
147554
|
+
return true;
|
|
147555
|
+
}
|
|
147176
147556
|
var SessionManager = class {
|
|
147177
147557
|
sessions = /* @__PURE__ */ new Map();
|
|
147178
147558
|
/** In-flight activation promises to deduplicate concurrent calls. */
|
|
@@ -147198,6 +147578,7 @@ var SessionManager = class {
|
|
|
147198
147578
|
const entries = await loadJournal(this.homeDir, meta.sessionId);
|
|
147199
147579
|
const ws = new WebSession(null, {
|
|
147200
147580
|
sessionId: meta.sessionId,
|
|
147581
|
+
coreSessionId: meta.coreSessionId ?? meta.sessionId,
|
|
147201
147582
|
workDir: meta.workDir,
|
|
147202
147583
|
permission: meta.permission,
|
|
147203
147584
|
yolo: this.yolo,
|
|
@@ -147227,6 +147608,12 @@ var SessionManager = class {
|
|
|
147227
147608
|
createdAt,
|
|
147228
147609
|
onFork: (id) => this.forkSession(id)
|
|
147229
147610
|
});
|
|
147611
|
+
try {
|
|
147612
|
+
await webSession.ready;
|
|
147613
|
+
} catch (error) {
|
|
147614
|
+
await webSession.close();
|
|
147615
|
+
throw error;
|
|
147616
|
+
}
|
|
147230
147617
|
await saveMetadata(this.homeDir, webSession.getMetadata());
|
|
147231
147618
|
this.sessions.set(sessionId, webSession);
|
|
147232
147619
|
log.info("web: session created", {
|
|
@@ -147250,13 +147637,8 @@ var SessionManager = class {
|
|
|
147250
147637
|
const existing = this.sessions.get(sessionId);
|
|
147251
147638
|
if (!existing) return null;
|
|
147252
147639
|
if (existing.isActive) return existing;
|
|
147253
|
-
const session = await this.harness.createSession({
|
|
147254
|
-
workDir: existing.workDir,
|
|
147255
|
-
model: this.model,
|
|
147256
|
-
permission: this.permission
|
|
147257
|
-
});
|
|
147258
147640
|
const meta = existing.getMetadata();
|
|
147259
|
-
await
|
|
147641
|
+
const session = await this.harness.resumeSession({ id: meta.coreSessionId ?? meta.sessionId });
|
|
147260
147642
|
const reactivated = new WebSession(session, {
|
|
147261
147643
|
sessionId,
|
|
147262
147644
|
workDir: meta.workDir,
|
|
@@ -147265,10 +147647,18 @@ var SessionManager = class {
|
|
|
147265
147647
|
homeDir: this.homeDir,
|
|
147266
147648
|
createdAt: meta.createdAt,
|
|
147267
147649
|
title: meta.title,
|
|
147650
|
+
coreSessionId: session.id,
|
|
147268
147651
|
onFork: (id) => this.forkSession(id)
|
|
147269
147652
|
});
|
|
147270
147653
|
const entries = await loadJournal(this.homeDir, sessionId);
|
|
147271
147654
|
reactivated.loadFromPersisted(entries);
|
|
147655
|
+
try {
|
|
147656
|
+
await reactivated.ready;
|
|
147657
|
+
} catch (error) {
|
|
147658
|
+
await reactivated.close();
|
|
147659
|
+
throw error;
|
|
147660
|
+
}
|
|
147661
|
+
await existing.close();
|
|
147272
147662
|
this.sessions.set(sessionId, reactivated);
|
|
147273
147663
|
await saveMetadata(this.homeDir, reactivated.getMetadata());
|
|
147274
147664
|
log.info("web: session reactivated", { sessionId });
|
|
@@ -147286,7 +147676,7 @@ var SessionManager = class {
|
|
|
147286
147676
|
const source = this.sessions.get(sourceId);
|
|
147287
147677
|
if (!source || !source.isActive || source.isBusy) return null;
|
|
147288
147678
|
try {
|
|
147289
|
-
const session = await this.harness.forkSession({ id:
|
|
147679
|
+
const session = await this.harness.forkSession({ id: source.getCoreSessionId() });
|
|
147290
147680
|
const newId = session.id;
|
|
147291
147681
|
const createdAt = Date.now();
|
|
147292
147682
|
const title = `${source.getTitle()} (fork)`;
|
|
@@ -147306,6 +147696,12 @@ var SessionManager = class {
|
|
|
147306
147696
|
await writeFile(getJournalPath(this.homeDir, newId), lines);
|
|
147307
147697
|
}
|
|
147308
147698
|
webSession.loadFromPersisted(entries);
|
|
147699
|
+
try {
|
|
147700
|
+
await webSession.ready;
|
|
147701
|
+
} catch (error) {
|
|
147702
|
+
await webSession.close();
|
|
147703
|
+
throw error;
|
|
147704
|
+
}
|
|
147309
147705
|
await saveMetadata(this.homeDir, webSession.getMetadata());
|
|
147310
147706
|
this.sessions.set(newId, webSession);
|
|
147311
147707
|
log.info("web: session forked", {
|
|
@@ -147439,7 +147835,10 @@ async function runWebServer(opts) {
|
|
|
147439
147835
|
yolo: opts.yolo
|
|
147440
147836
|
});
|
|
147441
147837
|
await manager.init();
|
|
147442
|
-
if (!manager.list().some((s) => s.active))
|
|
147838
|
+
if (!manager.list().some((s) => s.active)) {
|
|
147839
|
+
const recent = manager.list().find((s) => s.workDir === workDir);
|
|
147840
|
+
if (recent) await manager.activateSession(recent.sessionId);
|
|
147841
|
+
}
|
|
147443
147842
|
const baseDir = import.meta.dirname;
|
|
147444
147843
|
const prodPublicDir = join(baseDir, "public");
|
|
147445
147844
|
const devPublicDir = join(baseDir, "frontend", "dist");
|
|
@@ -147470,6 +147869,7 @@ async function runWebServer(opts) {
|
|
|
147470
147869
|
}
|
|
147471
147870
|
return;
|
|
147472
147871
|
}
|
|
147872
|
+
if (await handleGoalRoute(req, res, url, (sessionId) => manager.get(sessionId))) return;
|
|
147473
147873
|
const snapshotMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/snapshot$`).exec(url);
|
|
147474
147874
|
if (snapshotMatch && method === "GET") {
|
|
147475
147875
|
const sessionId = snapshotMatch[1];
|
|
@@ -147482,8 +147882,12 @@ async function runWebServer(opts) {
|
|
|
147482
147882
|
}));
|
|
147483
147883
|
return;
|
|
147484
147884
|
}
|
|
147485
|
-
|
|
147486
|
-
|
|
147885
|
+
try {
|
|
147886
|
+
await ws.ready;
|
|
147887
|
+
sendJson(res, 200, ws.getSnapshot());
|
|
147888
|
+
} catch (error) {
|
|
147889
|
+
sendHttpError(res, error);
|
|
147890
|
+
}
|
|
147487
147891
|
return;
|
|
147488
147892
|
}
|
|
147489
147893
|
if (url === `${API_PREFIX}/git/status` && method === "GET") {
|
|
@@ -147651,12 +148055,24 @@ async function runWebServer(opts) {
|
|
|
147651
148055
|
manager.activateSession(sessionId).then((reactivated) => {
|
|
147652
148056
|
if (reactivated) reactivated.addConnection(ws);
|
|
147653
148057
|
else ws.close(1008, "Session not found");
|
|
148058
|
+
}).catch((error) => {
|
|
148059
|
+
log.warn("web: session activation failed", {
|
|
148060
|
+
sessionId,
|
|
148061
|
+
error: errorMessage(error)
|
|
148062
|
+
});
|
|
148063
|
+
ws.close(1011, "Session activation failed");
|
|
147654
148064
|
});
|
|
147655
148065
|
return;
|
|
147656
148066
|
}
|
|
147657
148067
|
manager.activateSession(sessionId).then((reactivated) => {
|
|
147658
148068
|
if (reactivated) reactivated.addConnection(ws);
|
|
147659
148069
|
else ws.close(1008, "Session not found");
|
|
148070
|
+
}).catch((error) => {
|
|
148071
|
+
log.warn("web: session activation failed", {
|
|
148072
|
+
sessionId,
|
|
148073
|
+
error: errorMessage(error)
|
|
148074
|
+
});
|
|
148075
|
+
ws.close(1011, "Session activation failed");
|
|
147660
148076
|
});
|
|
147661
148077
|
return;
|
|
147662
148078
|
}
|
|
@@ -147668,7 +148084,7 @@ async function runWebServer(opts) {
|
|
|
147668
148084
|
return;
|
|
147669
148085
|
}
|
|
147670
148086
|
}
|
|
147671
|
-
ws.
|
|
148087
|
+
ws.send(JSON.stringify({ type: "server_empty" }));
|
|
147672
148088
|
});
|
|
147673
148089
|
await new Promise((resolve) => {
|
|
147674
148090
|
httpServer.listen(opts.port, "127.0.0.1", resolve);
|