scream-code 0.5.0 → 0.5.2

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.
Files changed (2) hide show
  1. package/dist/main.mjs +307 -37
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -55982,7 +55982,7 @@ var GoalMode = class {
55982
55982
  if (state === void 0 || state.status !== "active") return null;
55983
55983
  return this.toSnapshot(state);
55984
55984
  }
55985
- async createGoal(input, actor = "user") {
55985
+ async createGoal(input, _actor = "user") {
55986
55986
  const objective = input.objective.trim();
55987
55987
  if (objective.length === 0) throw new ScreamError(ErrorCodes.GOAL_OBJECTIVE_EMPTY, "Goal objective cannot be empty");
55988
55988
  if (objective.length > MAX_GOAL_OBJECTIVE_LENGTH) throw new ScreamError(ErrorCodes.GOAL_OBJECTIVE_TOO_LONG, `Goal objective cannot exceed ${MAX_GOAL_OBJECTIVE_LENGTH} characters`);
@@ -56056,7 +56056,7 @@ var GoalMode = class {
56056
56056
  this.appendStatusUpdate(state, actor, input.reason);
56057
56057
  return this.toSnapshot(state);
56058
56058
  }
56059
- async setBudgetLimits(input, actor = "user") {
56059
+ async setBudgetLimits(input, _actor = "user") {
56060
56060
  const state = this.requireState();
56061
56061
  state.budgetLimits = {
56062
56062
  ...state.budgetLimits,
@@ -56244,6 +56244,15 @@ function buildGoalCompletionSummaryPrompt(goal) {
56244
56244
  "Write a concise final message for the user. State that the goal is complete, summarize the main work completed, and mention any validation you ran. Do not call more goal tools."
56245
56245
  ].join("\n");
56246
56246
  }
56247
+ function buildGradingFeedbackPrompt(reason) {
56248
+ return [
56249
+ "Goal verification failed. An independent reviewer found that the completion criteria were not genuinely met.",
56250
+ "",
56251
+ `Reviewer feedback:\n${reason}`,
56252
+ "",
56253
+ "Address every issue listed above before calling UpdateGoal with complete again. Do not re-submit until all issues are resolved."
56254
+ ].join("\n");
56255
+ }
56247
56256
  function buildGoalBlockedReasonPrompt(goal) {
56248
56257
  return [
56249
56258
  buildGoalBlockedMessage(goal),
@@ -56279,13 +56288,28 @@ const UpdateGoalToolInputSchema = z.object({ status: z.enum([
56279
56288
  "paused",
56280
56289
  "blocked"
56281
56290
  ]).describe("The lifecycle status to set for the current goal.") }).strict();
56291
+ const MAX_GRADER_OUTPUT_CHARS = 4e3;
56292
+ function extractRecentOutput(history) {
56293
+ const parts = [];
56294
+ for (let i = history.length - 1; i >= 0; i--) {
56295
+ const msg = history[i];
56296
+ if (msg === void 0 || msg.role !== "assistant") continue;
56297
+ const text = msg.content.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
56298
+ if (text) parts.unshift(text);
56299
+ if (parts.join("\n\n").length >= MAX_GRADER_OUTPUT_CHARS) break;
56300
+ }
56301
+ const joined = parts.join("\n\n");
56302
+ return joined.length > MAX_GRADER_OUTPUT_CHARS ? `${joined.slice(0, MAX_GRADER_OUTPUT_CHARS)}…` : joined;
56303
+ }
56282
56304
  var UpdateGoalTool = class {
56283
56305
  agent;
56306
+ grader;
56284
56307
  name = "UpdateGoal";
56285
56308
  description = "Update the current goal's lifecycle status. Use `complete` when the goal is achieved, `blocked` when you cannot proceed, `paused` to park it, or `active` to resume.";
56286
56309
  parameters = toInputJsonSchema(UpdateGoalToolInputSchema);
56287
- constructor(agent) {
56310
+ constructor(agent, grader) {
56288
56311
  this.agent = agent;
56312
+ this.grader = grader;
56289
56313
  }
56290
56314
  resolveExecution(args) {
56291
56315
  const goal = this.agent.goal;
@@ -56297,17 +56321,7 @@ var UpdateGoalTool = class {
56297
56321
  await goal.resumeGoal({}, "model");
56298
56322
  return { output: "Goal resumed." };
56299
56323
  }
56300
- if (args.status === "complete") {
56301
- const completed = await goal.markComplete({}, "model");
56302
- if (completed !== null) this.agent.context.appendSystemReminder(buildGoalCompletionSummaryPrompt(completed), {
56303
- kind: "system_trigger",
56304
- name: GOAL_COMPLETION_REMINDER_NAME
56305
- });
56306
- return {
56307
- output: "Goal marked complete.",
56308
- stopTurn: true
56309
- };
56310
- }
56324
+ if (args.status === "complete") return this.handleComplete(goal);
56311
56325
  if (args.status === "blocked") {
56312
56326
  const blocked = await goal.markBlocked({}, "model");
56313
56327
  if (blocked !== null) this.agent.context.appendSystemReminder(buildGoalBlockedReasonPrompt(blocked), {
@@ -56327,6 +56341,39 @@ var UpdateGoalTool = class {
56327
56341
  }
56328
56342
  };
56329
56343
  }
56344
+ async handleComplete(goal) {
56345
+ const goalState = goal.getGoal().goal;
56346
+ if (!goalState) return { output: "No active goal." };
56347
+ const output = extractRecentOutput(this.agent.context.history);
56348
+ await goal.pauseGoal({ reason: "verifying" }, "system");
56349
+ let pass;
56350
+ let reason;
56351
+ try {
56352
+ const result = await this.grader(goalState.objective, goalState.completionCriterion, output);
56353
+ pass = result.pass;
56354
+ reason = result.reason;
56355
+ } catch {
56356
+ pass = true;
56357
+ reason = "Grader unavailable";
56358
+ }
56359
+ await goal.resumeGoal({}, "system");
56360
+ if (pass) {
56361
+ const completed = await goal.markComplete({}, "model");
56362
+ if (completed !== null) this.agent.context.appendSystemReminder(buildGoalCompletionSummaryPrompt(completed), {
56363
+ kind: "system_trigger",
56364
+ name: GOAL_COMPLETION_REMINDER_NAME
56365
+ });
56366
+ return {
56367
+ output: `Goal verified and marked complete.\n${reason}`,
56368
+ stopTurn: true
56369
+ };
56370
+ }
56371
+ this.agent.context.appendSystemReminder(buildGradingFeedbackPrompt(reason), {
56372
+ kind: "system_trigger",
56373
+ name: "goal_grading_feedback"
56374
+ });
56375
+ return { output: `Verification failed: ${reason}. Continue working.` };
56376
+ }
56330
56377
  };
56331
56378
  //#endregion
56332
56379
  //#region ../../packages/agent-core/src/tools/builtin/goal/write-goal-note.ts
@@ -73362,18 +73409,21 @@ var MemoryMemoStore = class {
73362
73409
  const record = JSON.parse(rawLine);
73363
73410
  if (record["type"] !== "memory_memo" || !record["entry"]) return void 0;
73364
73411
  const entry = record["entry"];
73365
- if (record["version"] === 1 || entry["userRequirement"] !== void 0 && entry["userNeed"] === void 0) return {
73366
- id: String(entry["id"] ?? ""),
73367
- sourceSessionId: String(entry["sourceSessionId"] ?? ""),
73368
- sourceSessionTitle: typeof entry["sourceSessionTitle"] === "string" ? entry["sourceSessionTitle"] : void 0,
73369
- userNeed: String(entry["userRequirement"] ?? ""),
73370
- approach: String(entry["solution"] ?? ""),
73371
- outcome: String(entry["completionStatus"] ?? ""),
73372
- whatFailed: String(entry["problemsEncountered"] ?? "none"),
73373
- whatWorked: "none",
73374
- extractionSource: entry["extractionSource"] === "exit" ? "exit" : "compaction",
73375
- recordedAt: Number(entry["recordedAt"] ?? 0)
73376
- };
73412
+ if (record["version"] === 1 || entry["userRequirement"] !== void 0 && entry["userNeed"] === void 0) {
73413
+ const str = (v, fallback = "") => typeof v === "string" ? v : fallback;
73414
+ return {
73415
+ id: str(entry["id"]),
73416
+ sourceSessionId: str(entry["sourceSessionId"]),
73417
+ sourceSessionTitle: str(entry["sourceSessionTitle"], void 0),
73418
+ userNeed: str(entry["userRequirement"]),
73419
+ approach: str(entry["solution"]),
73420
+ outcome: str(entry["completionStatus"]),
73421
+ whatFailed: str(entry["problemsEncountered"], "none"),
73422
+ whatWorked: "none",
73423
+ extractionSource: entry["extractionSource"] === "exit" ? "exit" : "compaction",
73424
+ recordedAt: typeof entry["recordedAt"] === "number" ? entry["recordedAt"] : 0
73425
+ };
73426
+ }
73377
73427
  return entry;
73378
73428
  } catch {
73379
73429
  return;
@@ -75988,6 +76038,8 @@ function buildGoalReminder(goal) {
75988
76038
  lines.push("When you discover important facts, verify a hypothesis, or hit a dead end, call WriteGoalNote to record it. Future turns will read these notes automatically. Keep notes concise and actionable.");
75989
76039
  lines.push("");
75990
76040
  lines.push("Goal mode is iterative. Keep the self-audit brief each turn. Do not explore unrelated interpretations once the goal can be decided. If the objective is simple, already answered, impossible, unsafe, or contradictory, do not run another goal turn. Explain briefly if useful, then call UpdateGoal with `complete` or `blocked` in the same turn. Otherwise, self-audit against the objective and any completion criteria above, then do one coherent slice of work toward the objective. Use multiple turns when the task naturally has multiple phases. Call UpdateGoal with `complete` only when all required work is done, any stated validation has passed, and there is no useful next action. Do not mark complete after only producing a plan, summary, first pass, or partial result. If an external condition or required user input prevents progress, or the objective cannot be completed as stated, call UpdateGoal with `blocked`. Otherwise keep working — after your turn ends you will be prompted to continue. Call UpdateGoal as soon as the goal is genuinely done or cannot proceed; don't keep going once there is nothing left to do.");
76041
+ lines.push("");
76042
+ lines.push("When you call UpdateGoal with `complete`, an independent reviewer will verify that the completion criteria are met. Provide a clear summary of what you accomplished in your final response so the reviewer can evaluate it.");
75991
76043
  return lines.join("\n");
75992
76044
  }
75993
76045
  function maxBudgetFraction(goal) {
@@ -76485,7 +76537,12 @@ const DEFAULT_APPROVE_TOOLS = new Set([
76485
76537
  "Agent",
76486
76538
  "AskUserQuestion",
76487
76539
  "Skill",
76488
- "WolfPack"
76540
+ "WolfPack",
76541
+ "CreateGoal",
76542
+ "UpdateGoal",
76543
+ "GetGoal",
76544
+ "SetGoalBudget",
76545
+ "WriteGoalNote"
76489
76546
  ]);
76490
76547
  var DefaultToolApprovePermissionPolicy = class {
76491
76548
  name = "default-tool-approve";
@@ -90869,6 +90926,151 @@ const DEFAULT_AGENT_PROFILES = loadAgentProfilesFromSources([
90869
90926
  ].map((file) => `profile/default/${file}`), PROFILE_SOURCES);
90870
90927
  //#endregion
90871
90928
  //#region ../../packages/agent-core/src/agent/tool/index.ts
90929
+ const CRITERIA_SYSTEM_PROMPT = [
90930
+ "You generate concrete, verifiable acceptance criteria for a given objective.",
90931
+ "Criteria must be specific and testable — state what should work end-to-end, not just what should exist.",
90932
+ "Avoid vague criteria like \"feature works\" or \"code is correct\".",
90933
+ "Respond with JSON only: {\"criteria\": [\"criterion 1\", \"criterion 2\", ...]}"
90934
+ ].join(" ");
90935
+ const GRADER_SYSTEM_PROMPT = [
90936
+ "You are a strict goal completion evaluator. Your default judgment is FAIL. Only PASS when there is clear, specific evidence that every acceptance criterion is genuinely met end-to-end.",
90937
+ "Evaluate across three dimensions:",
90938
+ "- Completeness: every acceptance criterion is individually met with concrete evidence in the output. Partial completion is FAIL.",
90939
+ "- Conformance: the work matches what was asked — no scope drift, no over-engineering, no cutting corners.",
90940
+ "- Substance: the output is real, finished, working work — not just a plan, outline, scaffold, stub, mock, or partial implementation, unless the objective specifically asks for those. Surface-level appearance without end-to-end correctness is FAIL.",
90941
+ "When FAIL, you MUST list specific issues with actionable fix directions. Do not accept plausible-sounding but unverified claims of completion.",
90942
+ "Respond with JSON only."
90943
+ ].join(" ");
90944
+ function buildCriteriaPrompt(objective) {
90945
+ return [
90946
+ "## Objective",
90947
+ objective,
90948
+ "",
90949
+ "Generate 3-8 concrete, verifiable acceptance criteria for this objective.",
90950
+ "Each criterion should describe a specific, testable behavior or outcome — focus on end-to-end correctness, not surface existence.",
90951
+ "Respond with JSON: {\"criteria\": [\"criterion 1\", \"criterion 2\", ...]}"
90952
+ ].join("\n");
90953
+ }
90954
+ function buildGraderPrompt(objective, criteria, output) {
90955
+ return [
90956
+ "## Objective",
90957
+ objective,
90958
+ "",
90959
+ "## Acceptance Criteria",
90960
+ criteria,
90961
+ "",
90962
+ "## Agent Output",
90963
+ output || "(no output captured)",
90964
+ "",
90965
+ "Evaluate each dimension independently against the acceptance criteria, then decide overall PASS/FAIL.",
90966
+ "When FAIL, list every specific issue with an actionable fix direction so the agent knows exactly what to address next.",
90967
+ "Respond with JSON:",
90968
+ "{\"completeness\":{\"pass\":true/false,\"detail\":\"...\"},\"conformance\":{\"pass\":true/false,\"detail\":\"...\"},\"substance\":{\"pass\":true/false,\"detail\":\"...\"},\"issues\":[\"issue 1: what to fix\",\"issue 2: what to fix\"],\"pass\":true/false,\"reason\":\"overall summary\"}"
90969
+ ].join("\n");
90970
+ }
90971
+ function parseGraderResponse(text) {
90972
+ try {
90973
+ const match = text.match(/\{[\s\S]*\}/);
90974
+ if (!match) return {
90975
+ pass: true,
90976
+ reason: "No JSON found in grader response",
90977
+ summary: ""
90978
+ };
90979
+ const parsed = JSON.parse(match[0]);
90980
+ const overallPass = parsed.pass === true;
90981
+ const overallReason = typeof parsed.reason === "string" ? parsed.reason : "No reason provided";
90982
+ if (![
90983
+ parsed.completeness,
90984
+ parsed.conformance,
90985
+ parsed.substance
90986
+ ].some((d) => d !== void 0)) return {
90987
+ pass: overallPass,
90988
+ reason: overallReason,
90989
+ summary: ""
90990
+ };
90991
+ const lines = [];
90992
+ const failedDims = [];
90993
+ for (const [name, dim] of Object.entries({
90994
+ Completeness: parsed.completeness,
90995
+ Conformance: parsed.conformance,
90996
+ Substance: parsed.substance
90997
+ })) {
90998
+ if (dim === void 0) continue;
90999
+ const ok = dim.pass === true;
91000
+ const detail = typeof dim.detail === "string" ? dim.detail : "";
91001
+ lines.push(` ${ok ? "✓" : "✗"} ${name}: ${detail}`);
91002
+ if (!ok) failedDims.push(`${name}: ${detail}`);
91003
+ }
91004
+ const issues = Array.isArray(parsed.issues) ? parsed.issues.filter((i) => typeof i === "string") : [];
91005
+ if (issues.length > 0) {
91006
+ lines.push("");
91007
+ lines.push(" Issues to fix:");
91008
+ for (const issue of issues) lines.push(` - ${issue}`);
91009
+ }
91010
+ const summary = lines.join("\n");
91011
+ const reasonParts = [overallReason];
91012
+ if (failedDims.length > 0) reasonParts.push(failedDims.join("\n"));
91013
+ if (issues.length > 0) reasonParts.push(`Issues to fix:\n${issues.map((i) => `- ${i}`).join("\n")}`);
91014
+ return {
91015
+ pass: overallPass,
91016
+ reason: reasonParts.join("\n"),
91017
+ summary
91018
+ };
91019
+ } catch {
91020
+ return {
91021
+ pass: true,
91022
+ reason: "Failed to parse grader response",
91023
+ summary: ""
91024
+ };
91025
+ }
91026
+ }
91027
+ function extractResponseText(response) {
91028
+ return response.message.content.filter((p) => p.type === "text").map((p) => p.text).join("");
91029
+ }
91030
+ async function generateAcceptanceCriteria(agent, objective) {
91031
+ const prompt = buildCriteriaPrompt(objective);
91032
+ const text = extractResponseText(await agent.rawGenerate(agent.config.provider, CRITERIA_SYSTEM_PROMPT, [], [{
91033
+ role: "user",
91034
+ content: [{
91035
+ type: "text",
91036
+ text: prompt
91037
+ }],
91038
+ toolCalls: []
91039
+ }]));
91040
+ try {
91041
+ const match = text.match(/\{[\s\S]*\}/);
91042
+ if (!match) return "";
91043
+ const parsed = JSON.parse(match[0]);
91044
+ if (!Array.isArray(parsed.criteria)) return "";
91045
+ return parsed.criteria.filter((c) => typeof c === "string").map((c, i) => `${i + 1}. ${c}`).join("\n");
91046
+ } catch {
91047
+ return "";
91048
+ }
91049
+ }
91050
+ function createGoalGrader(agent) {
91051
+ return async (objective, criterion, output) => {
91052
+ let criteria;
91053
+ if (criterion !== void 0) criteria = criterion;
91054
+ else {
91055
+ criteria = await generateAcceptanceCriteria(agent, objective);
91056
+ if (!criteria) criteria = "No specific criteria defined. Evaluate based on whether the objective is clearly achieved.";
91057
+ }
91058
+ const user = buildGraderPrompt(objective, criteria, output);
91059
+ const result = parseGraderResponse(extractResponseText(await agent.rawGenerate(agent.config.provider, GRADER_SYSTEM_PROMPT, [], [{
91060
+ role: "user",
91061
+ content: [{
91062
+ type: "text",
91063
+ text: user
91064
+ }],
91065
+ toolCalls: []
91066
+ }])));
91067
+ const reason = result.summary ? `${result.reason}\n${result.summary}` : result.reason;
91068
+ return {
91069
+ pass: result.pass,
91070
+ reason
91071
+ };
91072
+ };
91073
+ }
90872
91074
  var ToolManager = class {
90873
91075
  agent;
90874
91076
  builtinTools = /* @__PURE__ */ new Map();
@@ -91147,7 +91349,7 @@ var ToolManager = class {
91147
91349
  this.agent.cron && new CronListTool(this.agent.cron),
91148
91350
  this.agent.cron && new CronDeleteTool(this.agent.cron),
91149
91351
  this.agent.type === "main" && new CreateGoalTool(this.agent),
91150
- this.agent.type === "main" && new UpdateGoalTool(this.agent),
91352
+ this.agent.type === "main" && new UpdateGoalTool(this.agent, createGoalGrader(this.agent)),
91151
91353
  this.agent.type === "main" && new GetGoalTool(this.agent),
91152
91354
  this.agent.type === "main" && new SetGoalBudgetTool(this.agent),
91153
91355
  this.agent.type === "main" && new WriteGoalNoteTool(this.agent),
@@ -121887,7 +122089,7 @@ function wrap(text, width, maxLines) {
121887
122089
  let current = "";
121888
122090
  for (const word of words) {
121889
122091
  const candidate = current.length === 0 ? word : `${current} ${word}`;
121890
- if (candidate.length > width && current.length > 0) {
122092
+ if (visibleWidth(candidate) > width && current.length > 0) {
121891
122093
  lines.push(current);
121892
122094
  current = word;
121893
122095
  } else current = candidate;
@@ -121973,6 +122175,9 @@ var GoalStatusMessageComponent = class {
121973
122175
  };
121974
122176
  //#endregion
121975
122177
  //#region src/tui/commands/goal.ts
122178
+ const GOAL_STATUS_DISMISS_MS = 1e4;
122179
+ let activeGoalPanel;
122180
+ let activeGoalTimer;
121976
122181
  const CONTROL_SUBCOMMANDS = new Set([
121977
122182
  "pause",
121978
122183
  "resume",
@@ -122122,13 +122327,28 @@ async function showGoalStatus(host) {
122122
122327
  }
122123
122328
  try {
122124
122329
  const result = await session.getGoal();
122125
- host.state.transcriptContainer.addChild(new GoalStatusMessageComponent(result.goal, host.state.theme.colors));
122330
+ dismissGoalPanel(host);
122331
+ const panel = new GoalStatusMessageComponent(result.goal, host.state.theme.colors);
122332
+ host.state.transcriptContainer.addChild(panel);
122333
+ activeGoalPanel = panel;
122334
+ activeGoalTimer = setTimeout(() => dismissGoalPanel(host), GOAL_STATUS_DISMISS_MS);
122126
122335
  host.state.ui.requestRender();
122127
122336
  } catch (error) {
122128
122337
  const message = error instanceof Error ? error.message : String(error);
122129
122338
  host.showError(`获取目标状态失败:${message}`);
122130
122339
  }
122131
122340
  }
122341
+ function dismissGoalPanel(host) {
122342
+ if (activeGoalTimer !== void 0) {
122343
+ clearTimeout(activeGoalTimer);
122344
+ activeGoalTimer = void 0;
122345
+ }
122346
+ if (activeGoalPanel !== void 0) {
122347
+ host.state.transcriptContainer.removeChild(activeGoalPanel);
122348
+ activeGoalPanel = void 0;
122349
+ host.state.ui.requestRender();
122350
+ }
122351
+ }
122132
122352
  //#endregion
122133
122353
  //#region src/utils/git/git-status.ts
122134
122354
  /**
@@ -122684,10 +122904,7 @@ var FooterComponent = class {
122684
122904
  if (state.permissionMode === "yolo") left.push(chalk.hex(colors.warning).bold("YES"));
122685
122905
  if (state.planMode) left.push(chalk.hex(colors.primary).bold("plan"));
122686
122906
  if (state.wolfpackMode) left.push(chalk.hex(colors.primary).bold("wolfpack"));
122687
- if (state.goalActive && state.goal) {
122688
- const goalText = state.goal.length > 20 ? state.goal.slice(0, 20) + "…" : state.goal;
122689
- left.push(chalk.hex(colors.success).bold(`🎯 ${goalText}`));
122690
- }
122907
+ if (state.goalActive) left.push(chalk.hex(colors.primary).bold("goal"));
122691
122908
  const model = shortenModel(modelDisplayName(state));
122692
122909
  if (model) {
122693
122910
  const thinkingLabel = state.thinking ? " 思考中" : "";
@@ -130460,6 +130677,7 @@ var StreamingUIController = class {
130460
130677
  }
130461
130678
  this.host.setAppState({ streamingPhase: "idle" });
130462
130679
  this.host.resetLivePane();
130680
+ this.host.onTurnCompleted();
130463
130681
  notifyTerminalOnce(state, `turn-complete:${completedTurnKey}`, {
130464
130682
  title: "Scream Code 任务完成",
130465
130683
  body: state.appState.sessionTitle ?? void 0
@@ -133609,6 +133827,7 @@ var SessionManager = class {
133609
133827
  this.host.showError("历史回放期间无法启动新会话。");
133610
133828
  return;
133611
133829
  }
133830
+ await this.extractMemoriesBeforeSwitch();
133612
133831
  let session;
133613
133832
  try {
133614
133833
  session = await this.createSessionFromCurrentState();
@@ -133666,6 +133885,20 @@ var SessionManager = class {
133666
133885
  this.host.updateQueueDisplay();
133667
133886
  this.host.stopMemoryIdleTimer();
133668
133887
  }
133888
+ async extractMemoriesBeforeSwitch() {
133889
+ const session = this.host.session;
133890
+ if (session === void 0) return;
133891
+ if (this.host.state.appState.streamingPhase !== "idle") return;
133892
+ this.host.state.footer.setTransientHint("正在整理会话记忆...");
133893
+ this.host.state.ui.requestRender();
133894
+ try {
133895
+ await Promise.race([session.extractMemoriesOnExit(), new Promise((resolve) => setTimeout(resolve, 3e4))]);
133896
+ this.host.showStatus("已沉淀关键信息至记忆备忘录");
133897
+ } catch {} finally {
133898
+ this.host.state.footer.setTransientHint(null);
133899
+ this.host.state.ui.requestRender();
133900
+ }
133901
+ }
133669
133902
  requireSession() {
133670
133903
  if (this.host.session === void 0) throw new Error(NO_ACTIVE_SESSION_MESSAGE);
133671
133904
  return this.host.session;
@@ -134297,6 +134530,7 @@ function sourceLabel(source) {
134297
134530
  var MemoryPickerComponent = class extends Container {
134298
134531
  store;
134299
134532
  colors;
134533
+ ui;
134300
134534
  onCancel;
134301
134535
  onInject;
134302
134536
  focused = false;
@@ -134317,11 +134551,13 @@ var MemoryPickerComponent = class extends Container {
134317
134551
  this.total = opts.total;
134318
134552
  this.loading = opts.loading;
134319
134553
  this.colors = opts.colors;
134554
+ this.ui = opts.ui;
134320
134555
  this.onCancel = opts.onCancel;
134321
134556
  this.onInject = opts.onInject;
134322
134557
  }
134323
134558
  async loadMemos() {
134324
134559
  this.loading = true;
134560
+ this.ui?.requestRender();
134325
134561
  try {
134326
134562
  const result = await this.store.list({
134327
134563
  limit: 50,
@@ -134335,6 +134571,7 @@ var MemoryPickerComponent = class extends Container {
134335
134571
  this.total = 0;
134336
134572
  } finally {
134337
134573
  this.loading = false;
134574
+ this.ui?.requestRender();
134338
134575
  }
134339
134576
  }
134340
134577
  handleInput(data) {
@@ -134426,6 +134663,7 @@ var MemoryPickerComponent = class extends Container {
134426
134663
  this.detailMemo = null;
134427
134664
  }
134428
134665
  if (this.detailMemo) this.mode = "detail";
134666
+ this.ui?.requestRender();
134429
134667
  }
134430
134668
  async deleteAndReload(id) {
134431
134669
  try {
@@ -134434,6 +134672,7 @@ var MemoryPickerComponent = class extends Container {
134434
134672
  await this.loadMemos();
134435
134673
  this.mode = "list";
134436
134674
  if (this.selectedIndex >= this.memos.length) this.selectedIndex = Math.max(0, this.memos.length - 1);
134675
+ this.ui?.requestRender();
134437
134676
  }
134438
134677
  render(width) {
134439
134678
  const c = this.colors;
@@ -135449,6 +135688,7 @@ var DialogManager = class {
135449
135688
  total,
135450
135689
  loading: !hasData,
135451
135690
  colors: this.host.state.theme.colors,
135691
+ ui: this.host.state.ui,
135452
135692
  onCancel: () => {
135453
135693
  this.host.state.activeDialog = null;
135454
135694
  this.restoreEditor();
@@ -135819,16 +136059,25 @@ var ScreamTUI = class ScreamTUI {
135819
136059
  if (this.state.appState.isReplaying) return;
135820
136060
  const session = this.session;
135821
136061
  if (session === void 0) return;
136062
+ this.state.footer.setTransientHint("正在整理会话记忆...");
136063
+ this.state.ui.requestRender();
135822
136064
  try {
135823
136065
  await session.extractMemoriesOnExit();
135824
136066
  this.lastMemoryExtractionTime = Date.now();
135825
136067
  this.showStatus("已沉淀关键信息至记忆备忘录");
135826
- } catch {}
136068
+ } catch {} finally {
136069
+ this.state.footer.setTransientHint(null);
136070
+ this.state.ui.requestRender();
136071
+ }
135827
136072
  }
135828
136073
  /** Called after compaction extraction to avoid duplicate idle extraction within cooldown. */
135829
136074
  markMemoryExtracted() {
135830
136075
  this.lastMemoryExtractionTime = Date.now();
135831
136076
  }
136077
+ /** Called by StreamingUIController when a turn finishes with no queued continuations. */
136078
+ onTurnCompleted() {
136079
+ this.startMemoryIdleTimer();
136080
+ }
135832
136081
  /** Trigger an immediate cc-connect liveness poll. Called by /cc after start/stop/restart. */
135833
136082
  refreshCcStatus() {
135834
136083
  setTimeout(() => {
@@ -135898,6 +136147,27 @@ var ScreamTUI = class ScreamTUI {
135898
136147
  const footerWrap = new GutterContainer(1, 1);
135899
136148
  footerWrap.addChild(this.state.footer);
135900
136149
  this.state.ui.addChild(footerWrap);
136150
+ this.installFixedBottomRegion();
136151
+ }
136152
+ /**
136153
+ * Pin the editor + footer to the terminal bottom. The pi-tui patch adds a
136154
+ * `fixedBottomLineCount` property: the last N rendered lines stay pinned
136155
+ * while the transcript above scrolls independently.
136156
+ *
136157
+ * We override `doRender` to measure the editor + footer height each frame
136158
+ * (they change with multi-line input or terminal resize) and set the count
136159
+ * before the real render runs.
136160
+ */
136161
+ installFixedBottomRegion() {
136162
+ const ui = this.state.ui;
136163
+ const originalDoRender = ui.doRender.bind(ui);
136164
+ const editorContainer = this.state.editorContainer;
136165
+ const footerWrap = ui.children.at(-1);
136166
+ ui.doRender = () => {
136167
+ const w = ui.terminal?.columns ?? 0;
136168
+ if (w > 0) ui.fixedBottomLineCount = editorContainer.render(w).length + (footerWrap ? footerWrap.render(w).length : 0);
136169
+ originalDoRender();
136170
+ };
135901
136171
  }
135902
136172
  handlePlanToggle(next) {
135903
136173
  handlePlanCommand(this, next ? "on" : "off");
@@ -135910,7 +136180,7 @@ var ScreamTUI = class ScreamTUI {
135910
136180
  }
135911
136181
  this.persistInputHistory(text);
135912
136182
  dispatchInput(this, text);
135913
- this.startMemoryIdleTimer();
136183
+ this.stopMemoryIdleTimer();
135914
136184
  }
135915
136185
  sendNormalUserInput(text) {
135916
136186
  if (this.state.appState.model.trim().length === 0) {
@@ -136563,7 +136833,7 @@ const SHADOW_CHARS = new Set([
136563
136833
  ]);
136564
136834
  const SHEEN_STEP = 2;
136565
136835
  const SHEEN_INTERVAL_MS = 150;
136566
- const LOADING_DURATION_MS = 2200;
136836
+ const LOADING_DURATION_MS = 1500;
136567
136837
  const THEME_ACCENT = {
136568
136838
  dark: [
136569
136839
  78,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "The Starting Point for Next-Gen Agents",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",