scream-code 0.3.10 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.mjs +1203 -749
  2. package/package.json +24 -24
package/dist/main.mjs CHANGED
@@ -555,6 +555,7 @@ const ErrorCodes = {
555
555
  SESSION_THINKING_EMPTY: "session.thinking_empty",
556
556
  SESSION_MODEL_EMPTY: "session.model_empty",
557
557
  SESSION_PLAN_MODE_INVALID: "session.plan_mode_invalid",
558
+ SESSION_WOLFPACK_MODE_INVALID: "session.wolfpack_mode_invalid",
558
559
  SESSION_APPROVAL_HANDLER_ERROR: "session.approval_handler_error",
559
560
  SESSION_QUESTION_HANDLER_ERROR: "session.question_handler_error",
560
561
  SESSION_INIT_FAILED: "session.init_failed",
@@ -692,6 +693,12 @@ const SCREAM_ERROR_INFO = {
692
693
  public: true,
693
694
  action: "Provide a boolean plan mode."
694
695
  },
696
+ "session.wolfpack_mode_invalid": {
697
+ title: "Invalid wolfpack mode",
698
+ retryable: false,
699
+ public: true,
700
+ action: "Provide a boolean wolfpack mode."
701
+ },
695
702
  "session.approval_handler_error": {
696
703
  title: "Approval handler threw",
697
704
  retryable: false,
@@ -64622,6 +64629,141 @@ function escapeXml(input) {
64622
64629
  return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
64623
64630
  }
64624
64631
  //#endregion
64632
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/wolfpack.md
64633
+ var wolfpack_default = "Use WolfPack to spawn multiple subagents in parallel for batch operations.\nThis is ideal when processing many independent items (files, checks, searches)\nthat all use the same subagent type and follow a similar pattern.\n\nInput:\n- description: Brief (3-5 word) task summary.\n- subagent_type: Subagent profile name. Defaults to \"coder\".\n- prompt_template: A prompt pattern where each item value is substituted in\n to produce a per-item prompt. See the parameter schema for placeholder syntax.\n- items: Array of item strings. Each item gets its own subagent (max 20).\n\nItems must be independent — no subagent depends on another's output.\nIf items depend on each other, use separate Agent calls instead.\n\nExample: review three source files for OWASP vulnerabilities by setting\nitems to the file paths and prompt_template to the review instruction.\n";
64634
+ //#endregion
64635
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/wolfpack.ts
64636
+ /**
64637
+ * WolfPackTool — batch parallel subagent execution.
64638
+ *
64639
+ * Spawns multiple subagents in parallel using a template + items pattern.
64640
+ * Each item gets its own subagent; results are batched together.
64641
+ * V1 uses Promise.allSettled — no concurrency control or rate-limit handling.
64642
+ */
64643
+ const MAX_ITEMS = 20;
64644
+ const WolfPackToolInputSchema = z.object({
64645
+ description: z.string().min(1).describe("Short task description (3-5 words, e.g., \"Security review all files\")"),
64646
+ subagent_type: z.string().default("coder").describe("Subagent type for all spawned agents (e.g., coder, explore, verify)"),
64647
+ prompt_template: z.string().min(1).describe("Prompt template with {{item}} placeholder. Each item is substituted in."),
64648
+ items: z.array(z.string().min(1)).min(1).max(MAX_ITEMS).describe("Array of items to process. Each item gets its own subagent.")
64649
+ });
64650
+ var WolfPackTool = class {
64651
+ subagentHost;
64652
+ isEnabled;
64653
+ name = "WolfPack";
64654
+ description = wolfpack_default;
64655
+ parameters = toInputJsonSchema(WolfPackToolInputSchema);
64656
+ constructor(subagentHost, isEnabled, _options) {
64657
+ this.subagentHost = subagentHost;
64658
+ this.isEnabled = isEnabled;
64659
+ }
64660
+ resolveExecution(args) {
64661
+ return {
64662
+ description: `WolfPack: ${args.description} (${args.items.length} agents)`,
64663
+ accesses: ToolAccesses.none(),
64664
+ display: {
64665
+ kind: "generic",
64666
+ summary: `WolfPack: ${args.description}`,
64667
+ detail: {
64668
+ itemCount: args.items.length,
64669
+ subagent_type: args.subagent_type
64670
+ }
64671
+ },
64672
+ approvalRule: this.name,
64673
+ execute: (ctx) => this.execution(args, ctx)
64674
+ };
64675
+ }
64676
+ async execution(args, ctx) {
64677
+ ctx.signal.throwIfAborted();
64678
+ if (!this.isEnabled()) return {
64679
+ output: "WolfPack 模式未开启。请输入 /wolfpack 打开后再试。",
64680
+ isError: true
64681
+ };
64682
+ if (args.items.length > MAX_ITEMS) return {
64683
+ output: `WolfPack max ${MAX_ITEMS} items. Got ${args.items.length}.`,
64684
+ isError: true
64685
+ };
64686
+ const profileName = args.subagent_type ?? "coder";
64687
+ const template = args.prompt_template;
64688
+ const handlePromises = args.items.map(async (item) => {
64689
+ ctx.signal.throwIfAborted();
64690
+ const prompt = template.replace(/\{\{item\}\}/g, item);
64691
+ return {
64692
+ item,
64693
+ handle: await this.subagentHost.spawn(profileName, {
64694
+ parentToolCallId: ctx.toolCallId,
64695
+ prompt,
64696
+ description: `${args.description}: ${item}`,
64697
+ runInBackground: false,
64698
+ signal: ctx.signal
64699
+ })
64700
+ };
64701
+ });
64702
+ const completionPromises = (await Promise.allSettled(handlePromises)).map(async (settled) => {
64703
+ if (settled.status === "rejected") return {
64704
+ item: "unknown",
64705
+ result: `Spawn failed: ${settled.reason instanceof Error ? settled.reason.message : String(settled.reason)}`,
64706
+ success: false
64707
+ };
64708
+ const { item, handle } = settled.value;
64709
+ try {
64710
+ return {
64711
+ item,
64712
+ result: (await handle.completion).result,
64713
+ success: true,
64714
+ agentId: handle.agentId
64715
+ };
64716
+ } catch (error) {
64717
+ let message;
64718
+ if (isAbortError$3(error)) message = "The subagent was stopped before it finished.";
64719
+ else message = error instanceof Error ? error.message : String(error);
64720
+ return {
64721
+ item,
64722
+ result: message,
64723
+ success: false,
64724
+ agentId: handle.agentId
64725
+ };
64726
+ }
64727
+ });
64728
+ const completions = await Promise.allSettled(completionPromises);
64729
+ let successCount = 0;
64730
+ let failureCount = 0;
64731
+ const lines = [];
64732
+ for (const settled of completions) {
64733
+ if (settled.status === "fulfilled") {
64734
+ const { item, result: _result, success, agentId } = settled.value;
64735
+ if (success) {
64736
+ successCount++;
64737
+ lines.push(`### ${item} (OK)`);
64738
+ } else {
64739
+ failureCount++;
64740
+ lines.push(`### ${item} (FAILED)`);
64741
+ }
64742
+ if (agentId !== void 0) lines.push(`agent_id: ${agentId}`);
64743
+ } else {
64744
+ failureCount++;
64745
+ const msg = settled.reason instanceof Error ? settled.reason.message : String(settled.reason);
64746
+ lines.push(`### error: ${msg}`);
64747
+ }
64748
+ lines.push("");
64749
+ }
64750
+ const summary = `Success: ${successCount}, Failed: ${failureCount}, Total: ${completions.length}`;
64751
+ if (failureCount > 0 && successCount === 0) return {
64752
+ output: [
64753
+ summary,
64754
+ "",
64755
+ ...lines
64756
+ ].join("\n"),
64757
+ isError: true
64758
+ };
64759
+ return { output: [
64760
+ summary,
64761
+ "",
64762
+ ...lines
64763
+ ].join("\n") };
64764
+ }
64765
+ };
64766
+ //#endregion
64625
64767
  //#region ../../packages/agent-core/src/tools/builtin/file/line-endings.ts
64626
64768
  function detectLineEndingStyle(text) {
64627
64769
  let hasCrLf = false;
@@ -75385,6 +75527,46 @@ function isTodoStatus(value) {
75385
75527
  return value === "pending" || value === "in_progress" || value === "done";
75386
75528
  }
75387
75529
  //#endregion
75530
+ //#region ../../packages/agent-core/src/agent/injection/wolfpack.ts
75531
+ const WOLFPACK_MODE_ENTER_REMINDER = [
75532
+ "WolfPack mode is active. Prefer using the WolfPack tool for batch parallel execution.",
75533
+ "",
75534
+ "When to use: When the user's task involves performing the same operation across",
75535
+ "multiple independent items (files, directories, data entries, searches).",
75536
+ "",
75537
+ "How to use:",
75538
+ " WolfPack(",
75539
+ " description=\"Short task description (3-5 words)\",",
75540
+ " subagent_type=\"coder\",",
75541
+ " prompt_template=\"Review {{item}} for security issues\",",
75542
+ " items=[\"src/auth.ts\", \"src/api.ts\", \"src/config.ts\"]",
75543
+ " )",
75544
+ "",
75545
+ "This spawns one subagent per item in parallel. Results are batched and returned together.",
75546
+ "Items must be independent — do not use WolfPack when one item depends on another's output.",
75547
+ "Max 20 items per call."
75548
+ ].join("\n");
75549
+ const WOLFPACK_MODE_EXIT_REMINDER = "WolfPack mode is no longer active. The WolfPack tool for batch parallel execution is no longer available.";
75550
+ var WolfPackModeInjector = class extends DynamicInjector {
75551
+ injectionVariant = "wolfpack";
75552
+ wasActive = false;
75553
+ getInjection() {
75554
+ if (!this.agent.wolfpackMode.isActive) {
75555
+ if (this.wasActive) {
75556
+ this.wasActive = false;
75557
+ this.injectedAt = null;
75558
+ return WOLFPACK_MODE_EXIT_REMINDER;
75559
+ }
75560
+ return;
75561
+ }
75562
+ if (!this.wasActive) {
75563
+ this.injectedAt = null;
75564
+ this.wasActive = true;
75565
+ return WOLFPACK_MODE_ENTER_REMINDER;
75566
+ }
75567
+ }
75568
+ };
75569
+ //#endregion
75388
75570
  //#region ../../packages/agent-core/src/agent/injection/manager.ts
75389
75571
  var InjectionManager = class {
75390
75572
  agent;
@@ -75396,6 +75578,7 @@ var InjectionManager = class {
75396
75578
  this.memoryRecall = autoRecallEnabled ? new MemoryRecallInjector(agent) : null;
75397
75579
  this.injectors = [
75398
75580
  new PluginSessionStartInjector(agent),
75581
+ new WolfPackModeInjector(agent),
75399
75582
  new PlanModeInjector(agent),
75400
75583
  new PermissionModeInjector(agent),
75401
75584
  new TodoListReminderInjector(agent),
@@ -75473,7 +75656,8 @@ const DEFAULT_APPROVE_TOOLS = new Set([
75473
75656
  "FetchURL",
75474
75657
  "Agent",
75475
75658
  "AskUserQuestion",
75476
- "Skill"
75659
+ "Skill",
75660
+ "WolfPack"
75477
75661
  ]);
75478
75662
  var DefaultToolApprovePermissionPolicy = class {
75479
75663
  name = "default-tool-approve";
@@ -76052,6 +76236,19 @@ var YoloModeApprovePermissionPolicy = class {
76052
76236
  }
76053
76237
  };
76054
76238
  //#endregion
76239
+ //#region ../../packages/agent-core/src/agent/permission/policies/wolfpack-mode-approve.ts
76240
+ var WolfPackModeApprovePermissionPolicy = class {
76241
+ agent;
76242
+ name = "wolfpack-mode-approve";
76243
+ constructor(agent) {
76244
+ this.agent = agent;
76245
+ }
76246
+ evaluate() {
76247
+ if (!this.agent.wolfpackMode?.isActive) return;
76248
+ return { kind: "approve" };
76249
+ }
76250
+ };
76251
+ //#endregion
76055
76252
  //#region ../../packages/agent-core/src/agent/permission/policies/index.ts
76056
76253
  /** Permission policies run in order; the first non-undefined result wins. */
76057
76254
  function createPermissionDecisionPolicies(agent) {
@@ -76070,6 +76267,7 @@ function createPermissionDecisionPolicies(agent) {
76070
76267
  new GitControlPathAccessAskPermissionPolicy(agent),
76071
76268
  new CwdOutsideFileWriteAskPermissionPolicy(agent),
76072
76269
  new YoloModeApprovePermissionPolicy(agent),
76270
+ new WolfPackModeApprovePermissionPolicy(agent),
76073
76271
  new DefaultToolApprovePermissionPolicy(),
76074
76272
  new GitCwdWriteApprovePermissionPolicy(agent),
76075
76273
  new FallbackAskPermissionPolicy()
@@ -76684,6 +76882,33 @@ function isMissingFileError(error) {
76684
76882
  return error.code === "ENOENT";
76685
76883
  }
76686
76884
  //#endregion
76885
+ //#region ../../packages/agent-core/src/agent/wolfpack/index.ts
76886
+ var WolfPackMode = class {
76887
+ agent;
76888
+ _isActive = false;
76889
+ constructor(agent) {
76890
+ this.agent = agent;
76891
+ }
76892
+ enter() {
76893
+ if (this._isActive) return;
76894
+ this._isActive = true;
76895
+ this.agent.records.logRecord({ type: "wolfpack.enter" });
76896
+ this.agent.emitStatusUpdated();
76897
+ }
76898
+ restoreEnter() {
76899
+ this._isActive = true;
76900
+ }
76901
+ exit() {
76902
+ if (!this._isActive) return;
76903
+ this.agent.records.logRecord({ type: "wolfpack.exit" });
76904
+ this._isActive = false;
76905
+ this.agent.emitStatusUpdated();
76906
+ }
76907
+ get isActive() {
76908
+ return this._isActive;
76909
+ }
76910
+ };
76911
+ //#endregion
76687
76912
  //#region ../../packages/agent-core/src/agent/session-memory.ts
76688
76913
  const MAX_EVENTS = 50;
76689
76914
  const MAX_SUMMARY_LENGTH = 1500;
@@ -77208,6 +77433,12 @@ function restoreAgentRecord(agent, input) {
77208
77433
  case "plan_mode.exit":
77209
77434
  agent.planMode.exit(input.id);
77210
77435
  return;
77436
+ case "wolfpack.enter":
77437
+ agent.wolfpackMode.restoreEnter();
77438
+ return;
77439
+ case "wolfpack.exit":
77440
+ agent.wolfpackMode.exit();
77441
+ return;
77211
77442
  case "context.append_message":
77212
77443
  agent.context.appendMessage(input.message);
77213
77444
  return;
@@ -89779,7 +90010,7 @@ function normalizeSourcePath(path) {
89779
90010
  }
89780
90011
  //#endregion
89781
90012
  //#region ../../packages/agent-core/src/profile/default/agent.yaml
89782
- var agent_default = "name: agent\ndescription: Default Scream Code agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - ReadMediaFile\n - TodoList\n - Skill\n - WebSearch\n - Agent\n\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - ExitPlanMode\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n verify:\n description: Verification specialist. Runs build, test, and lint commands to validate code changes.\n writer:\n description: Content production and research specialist. Produces structured, data-driven reports, analyses, and Markdown documents.\n";
90013
+ var agent_default = "name: agent\ndescription: Default Scream Code agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - ReadMediaFile\n - TodoList\n - Skill\n - WebSearch\n - Agent\n - WolfPack\n\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - ExitPlanMode\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n verify:\n description: Verification specialist. Runs build, test, and lint commands to validate code changes.\n writer:\n description: Content production and research specialist. Produces structured, data-driven reports, analyses, and Markdown documents.\n";
89783
90014
  //#endregion
89784
90015
  //#region ../../packages/agent-core/src/profile/default/coder.yaml
89785
90016
  var coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - mcp__*\n";
@@ -90093,6 +90324,7 @@ var ToolManager = class {
90093
90324
  allowBackground,
90094
90325
  log: this.agent.log
90095
90326
  }),
90327
+ this.agent.subagentHost && new WolfPackTool(this.agent.subagentHost, () => this.agent.wolfpackMode.isActive, { log: this.agent.log }),
90096
90328
  toolServices?.webSearcher && new WebSearchTool(toolServices.webSearcher),
90097
90329
  toolServices?.urlFetcher && new FetchURLTool(toolServices.urlFetcher)
90098
90330
  ].filter((tool) => !!tool).map((tool) => [tool.name, tool]));
@@ -91279,6 +91511,7 @@ var Agent = class {
91279
91511
  injection;
91280
91512
  permission;
91281
91513
  planMode;
91514
+ wolfpackMode;
91282
91515
  usage;
91283
91516
  skills;
91284
91517
  tools;
@@ -91317,8 +91550,9 @@ var Agent = class {
91317
91550
  this.config = new ConfigState(this);
91318
91551
  this.turn = new TurnFlow(this);
91319
91552
  this.injection = new InjectionManager(this);
91320
- this.permission = new PermissionManager(this, options.permission);
91321
91553
  this.planMode = new PlanMode(this);
91554
+ this.wolfpackMode = new WolfPackMode(this);
91555
+ this.permission = new PermissionManager(this, options.permission);
91322
91556
  this.usage = new UsageRecorder(this);
91323
91557
  this.skills = options.skills ? new SkillManager(this, options.skills) : null;
91324
91558
  this.tools = new ToolManager(this);
@@ -91454,6 +91688,12 @@ var Agent = class {
91454
91688
  enterPlan: async () => {
91455
91689
  await this.planMode.enter();
91456
91690
  },
91691
+ enterWolfpack: () => {
91692
+ this.wolfpackMode.enter();
91693
+ },
91694
+ exitWolfpack: () => {
91695
+ this.wolfpackMode.exit();
91696
+ },
91457
91697
  cancelPlan: (payload) => {
91458
91698
  this.planMode.cancel(payload.id);
91459
91699
  },
@@ -111077,6 +111317,11 @@ const FLAG_DEFINITIONS = [{
111077
111317
  env: "SCREAM_CODE_EXPERIMENTAL_MICRO_COMPACTION",
111078
111318
  default: false,
111079
111319
  surface: "both"
111320
+ }, {
111321
+ id: "wolfpack",
111322
+ env: "SCREAM_CODE_EXPERIMENTAL_WOLFPACK",
111323
+ default: false,
111324
+ surface: "both"
111080
111325
  }];
111081
111326
  /**
111082
111327
  * Pure, synchronous flag resolver. State comes entirely from (env, registry) and nothing is
@@ -112453,6 +112698,12 @@ var SessionAPIImpl = class {
112453
112698
  clearPlan({ agentId, ...payload }) {
112454
112699
  return this.getAgent(agentId).clearPlan(payload);
112455
112700
  }
112701
+ enterWolfpack({ agentId, ...payload }) {
112702
+ return this.getAgent(agentId).enterWolfpack(payload);
112703
+ }
112704
+ exitWolfpack({ agentId, ...payload }) {
112705
+ return this.getAgent(agentId).exitWolfpack(payload);
112706
+ }
112456
112707
  beginCompaction({ agentId, ...payload }) {
112457
112708
  return this.getAgent(agentId).beginCompaction(payload);
112458
112709
  }
@@ -114079,6 +114330,12 @@ var ScreamCore = class {
114079
114330
  clearPlan({ sessionId, ...payload }) {
114080
114331
  return this.sessionApi(sessionId).clearPlan(payload);
114081
114332
  }
114333
+ enterWolfpack({ sessionId, ...payload }) {
114334
+ return this.sessionApi(sessionId).enterWolfpack(payload);
114335
+ }
114336
+ exitWolfpack({ sessionId, ...payload }) {
114337
+ return this.sessionApi(sessionId).exitWolfpack(payload);
114338
+ }
114082
114339
  beginCompaction({ sessionId, ...payload }) {
114083
114340
  return this.sessionApi(sessionId).beginCompaction(payload);
114084
114341
  }
@@ -114657,6 +114914,17 @@ var SDKRpcClient = class {
114657
114914
  agentId: this.interactiveAgentId
114658
114915
  });
114659
114916
  }
114917
+ async setWolfpackMode(input) {
114918
+ const rpc = await this.getRpc();
114919
+ if (!input.enabled) return rpc.exitWolfpack({
114920
+ sessionId: input.sessionId,
114921
+ agentId: this.interactiveAgentId
114922
+ });
114923
+ return rpc.enterWolfpack({
114924
+ sessionId: input.sessionId,
114925
+ agentId: this.interactiveAgentId
114926
+ });
114927
+ }
114660
114928
  async getPlan(input) {
114661
114929
  return (await this.getRpc()).getPlan({
114662
114930
  sessionId: input.sessionId,
@@ -115053,6 +115321,14 @@ var Session = class {
115053
115321
  enabled
115054
115322
  });
115055
115323
  }
115324
+ async setWolfpackMode(enabled) {
115325
+ this.ensureOpen();
115326
+ if (typeof enabled !== "boolean") throw new ScreamError(ErrorCodes.SESSION_WOLFPACK_MODE_INVALID, "Session wolfpack mode must be a boolean");
115327
+ await this.rpc.setWolfpackMode({
115328
+ sessionId: this.id,
115329
+ enabled
115330
+ });
115331
+ }
115056
115332
  async getPlan() {
115057
115333
  this.ensureOpen();
115058
115334
  return this.rpc.getPlan({ sessionId: this.id });
@@ -117603,75 +117879,82 @@ const BUILTIN_SLASH_COMMANDS = [
117603
117879
  availability: "always"
117604
117880
  },
117605
117881
  {
117606
- name: "power",
117607
- aliases: ["parallel"],
117608
- description: "切换至Agent并发模式",
117882
+ name: "yes",
117883
+ aliases: ["yolo"],
117884
+ description: "切换至自动批准模式(yolo)",
117609
117885
  priority: 124,
117610
117886
  availability: "always"
117611
117887
  },
117612
117888
  {
117613
- name: "yes",
117614
- aliases: ["yolo"],
117615
- description: "切换至自动批准模式(yolo)",
117889
+ name: "wolfpack",
117890
+ aliases: ["wp"],
117891
+ description: "切换群狼协作模式,自动批准+批量并发",
117616
117892
  priority: 123,
117617
117893
  availability: "always"
117618
117894
  },
117619
117895
  {
117620
- name: "model",
117621
- aliases: [],
117622
- description: "切换 LLM 模型",
117896
+ name: "sessions",
117897
+ aliases: ["resume"],
117898
+ description: "浏览并恢复会话",
117623
117899
  priority: 122
117624
117900
  },
117625
- {
117626
- name: "new",
117627
- aliases: ["clear"],
117628
- description: "在当前工作区开启新会话",
117629
- priority: 121
117630
- },
117631
117901
  {
117632
117902
  name: "memory",
117633
117903
  aliases: ["memo", "mem"],
117634
117904
  description: "浏览、搜索、注入记忆备忘录",
117635
- priority: 120,
117905
+ priority: 121,
117636
117906
  availability: "always"
117637
117907
  },
117638
117908
  {
117639
- name: "help",
117640
- aliases: ["h", "?"],
117641
- description: "显示可用命令和快捷键",
117642
- priority: 119,
117643
- availability: "always"
117909
+ name: "new",
117910
+ aliases: ["clear"],
117911
+ description: "在当前工作区开启新会话",
117912
+ priority: 121
117913
+ },
117914
+ {
117915
+ name: "model",
117916
+ aliases: [],
117917
+ description: "切换 LLM 模型",
117918
+ priority: 120
117644
117919
  },
117645
117920
  {
117646
117921
  name: "compact",
117647
117922
  aliases: [],
117648
117923
  description: "压缩对话上下文",
117649
- priority: 118
117924
+ priority: 119
117650
117925
  },
117651
117926
  {
117652
117927
  name: "plan",
117653
117928
  aliases: [],
117654
117929
  description: "切换计划模式",
117655
- priority: 117,
117930
+ priority: 118,
117656
117931
  availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
117657
117932
  },
117658
- {
117659
- name: "sessions",
117660
- aliases: ["resume"],
117661
- description: "浏览并恢复会话",
117662
- priority: 116
117663
- },
117664
117933
  {
117665
117934
  name: "tasks",
117666
117935
  aliases: ["task"],
117667
117936
  description: "浏览后台任务",
117937
+ priority: 117,
117938
+ availability: "always"
117939
+ },
117940
+ {
117941
+ name: "help",
117942
+ aliases: ["h", "?"],
117943
+ description: "显示可用命令和快捷键",
117944
+ priority: 116,
117945
+ availability: "always"
117946
+ },
117947
+ {
117948
+ name: "status",
117949
+ aliases: [],
117950
+ description: "显示当前会话和运行时状态",
117668
117951
  priority: 115,
117669
117952
  availability: "always"
117670
117953
  },
117671
117954
  {
117672
- name: "mcp",
117955
+ name: "usage",
117673
117956
  aliases: [],
117674
- description: "管理 MCP 服务器(安装/停用/卸载)",
117957
+ description: "显示 token 用量和上下文窗口",
117675
117958
  priority: 114,
117676
117959
  availability: "always"
117677
117960
  },
@@ -117683,58 +117966,45 @@ const BUILTIN_SLASH_COMMANDS = [
117683
117966
  availability: "always"
117684
117967
  },
117685
117968
  {
117686
- name: "theme",
117969
+ name: "mcp",
117687
117970
  aliases: [],
117688
- description: "设置终端 UI 主题",
117971
+ description: "管理 MCP 服务器(安装/停用/卸载)",
117689
117972
  priority: 112,
117690
117973
  availability: "always"
117691
117974
  },
117692
117975
  {
117693
- name: "editor",
117694
- aliases: [],
117695
- description: "设置外部编辑器",
117976
+ name: "plugin",
117977
+ aliases: ["plugins"],
117978
+ description: "ScreamCode 插件中心:浏览、安装、卸载插件",
117696
117979
  priority: 111,
117697
117980
  availability: "always"
117698
117981
  },
117699
117982
  {
117700
- name: "status",
117983
+ name: "cc",
117701
117984
  aliases: [],
117702
- description: "显示当前会话和运行时状态",
117985
+ description: "操控你的cc(启动/关闭/重启)",
117703
117986
  priority: 110,
117704
117987
  availability: "always"
117705
117988
  },
117706
117989
  {
117707
- name: "usage",
117990
+ name: "cc-connect",
117708
117991
  aliases: [],
117709
- description: "显示 token 用量和上下文窗口",
117992
+ description: "cc-connect 快速通道配置(需先安装)",
117710
117993
  priority: 109,
117711
117994
  availability: "always"
117712
117995
  },
117713
- {
117714
- name: "permission",
117715
- aliases: [],
117716
- description: "选择权限模式",
117717
- priority: 108,
117718
- availability: "always"
117719
- },
117720
117996
  {
117721
117997
  name: "revoke",
117722
117998
  aliases: [],
117723
117999
  description: "撤回上一次对话(可指定轮数,如 /revoke 3)",
117724
- priority: 107,
118000
+ priority: 108,
117725
118001
  availability: "idle-only"
117726
118002
  },
117727
- {
117728
- name: "config",
117729
- aliases: [],
117730
- description: "浏览并配置模型(远程拉取最新目录)",
117731
- priority: 106
117732
- },
117733
118003
  {
117734
118004
  name: "goal",
117735
118005
  aliases: [],
117736
118006
  description: "管理自动目标(status状态/pause暂停/resume恢复/replace替换,取消用 /goaloff)",
117737
- priority: 105,
118007
+ priority: 107,
117738
118008
  availability: (args) => {
117739
118009
  const trimmed = args.trim();
117740
118010
  return trimmed === "" || trimmed === "status" || trimmed === "pause" ? "always" : "idle-only";
@@ -117744,88 +118014,94 @@ const BUILTIN_SLASH_COMMANDS = [
117744
118014
  name: "goaloff",
117745
118015
  aliases: [],
117746
118016
  description: "取消并清空当前目标",
117747
- priority: 104,
117748
- availability: "always"
117749
- },
117750
- {
117751
- name: "settings",
117752
- aliases: [],
117753
- description: "打开 TUI 设置",
117754
- priority: 103,
118017
+ priority: 106,
117755
118018
  availability: "always"
117756
118019
  },
117757
118020
  {
117758
118021
  name: "fork",
117759
118022
  aliases: [],
117760
118023
  description: "复制当前会话并新开分支",
117761
- priority: 102
118024
+ priority: 105
117762
118025
  },
117763
118026
  {
117764
118027
  name: "title",
117765
118028
  aliases: ["rename"],
117766
118029
  description: "设置或显示会话标题",
117767
- priority: 101,
118030
+ priority: 104,
117768
118031
  availability: "always"
117769
118032
  },
117770
118033
  {
117771
- name: "init",
118034
+ name: "config",
117772
118035
  aliases: [],
117773
- description: "分析代码库并生成 AGENTS.md",
117774
- priority: 100
118036
+ description: "浏览并配置模型(远程拉取最新目录)",
118037
+ priority: 103
117775
118038
  },
117776
118039
  {
117777
- name: "cc",
118040
+ name: "permission",
117778
118041
  aliases: [],
117779
- description: "操控你的cc(启动/关闭/重启)",
117780
- priority: 98,
118042
+ description: "选择权限模式",
118043
+ priority: 102,
117781
118044
  availability: "always"
117782
118045
  },
117783
118046
  {
117784
- name: "cc-connect",
118047
+ name: "theme",
117785
118048
  aliases: [],
117786
- description: "cc-connect 快速通道配置(需先安装)",
117787
- priority: 96,
118049
+ description: "设置终端 UI 主题",
118050
+ priority: 101,
117788
118051
  availability: "always"
117789
118052
  },
117790
118053
  {
117791
- name: "export-md",
117792
- aliases: ["export"],
117793
- description: "导出当前会话为 Markdown",
117794
- priority: 95
118054
+ name: "editor",
118055
+ aliases: [],
118056
+ description: "设置外部编辑器",
118057
+ priority: 100,
118058
+ availability: "always"
117795
118059
  },
117796
118060
  {
117797
- name: "update",
118061
+ name: "settings",
117798
118062
  aliases: [],
117799
- description: "手动更新 Scream Code 到最新版本",
117800
- priority: 94,
117801
- availability: "idle-only"
118063
+ description: "打开 TUI 设置",
118064
+ priority: 99,
118065
+ availability: "always"
117802
118066
  },
117803
118067
  {
117804
- name: "plugin",
117805
- aliases: ["plugins"],
117806
- description: "ScreamCode 插件中心:浏览、安装、卸载插件",
117807
- priority: 93,
117808
- availability: "always"
118068
+ name: "init",
118069
+ aliases: [],
118070
+ description: "分析代码库并生成 AGENTS.md",
118071
+ priority: 98
118072
+ },
118073
+ {
118074
+ name: "export-md",
118075
+ aliases: ["export"],
118076
+ description: "导出当前会话为 Markdown",
118077
+ priority: 97
117809
118078
  },
117810
118079
  {
117811
118080
  name: "export-debug-zip",
117812
118081
  aliases: [],
117813
118082
  description: "导出当前会话为调试 ZIP 存档",
117814
- priority: 92
118083
+ priority: 96
117815
118084
  },
117816
118085
  {
117817
- name: "logout",
117818
- aliases: ["disconnect"],
117819
- description: "删除已配置的模型",
117820
- priority: 91
118086
+ name: "update",
118087
+ aliases: [],
118088
+ description: "手动更新 Scream Code 到最新版本",
118089
+ priority: 95,
118090
+ availability: "idle-only"
117821
118091
  },
117822
118092
  {
117823
118093
  name: "version",
117824
118094
  aliases: [],
117825
118095
  description: "显示版本信息",
117826
- priority: 90,
118096
+ priority: 94,
117827
118097
  availability: "always"
117828
118098
  },
118099
+ {
118100
+ name: "logout",
118101
+ aliases: ["disconnect"],
118102
+ description: "删除已配置的模型",
118103
+ priority: 93
118104
+ },
117829
118105
  {
117830
118106
  name: "exit",
117831
118107
  aliases: ["quit", "q"],
@@ -119846,11 +120122,36 @@ async function handleAutoCommand(host, args) {
119846
120122
  host.showNotice("自动模式:开启", "工具自动批准。代理不会提问。");
119847
120123
  }
119848
120124
  }
119849
- async function handlePowerCommand(host, _args) {
119850
- const next = !host.state.appState.parallelMode;
119851
- host.setAppState({ parallelMode: next });
119852
- if (next) host.showNotice("Power 模式:开启", "成功注入power能量 你的任务交给我了");
119853
- else host.showNotice("Power 模式:关闭", "可惜了 早知道再多花点Token了");
120125
+ async function handleWolfpackCommand(host, args) {
120126
+ const session = host.session;
120127
+ if (session === void 0) {
120128
+ host.showError(NO_ACTIVE_SESSION_MESSAGE);
120129
+ return;
120130
+ }
120131
+ const subcmd = args.trim().toLowerCase();
120132
+ let enabled;
120133
+ if (subcmd.length === 0) enabled = !host.state.appState.wolfpackMode;
120134
+ else if (subcmd === "on") enabled = true;
120135
+ else if (subcmd === "off") enabled = false;
120136
+ else {
120137
+ host.showError(`Unknown wolfpack subcommand: ${subcmd}`);
120138
+ return;
120139
+ }
120140
+ await applyWolfpackMode(host, session, enabled);
120141
+ }
120142
+ async function applyWolfpackMode(host, session, enabled) {
120143
+ try {
120144
+ await session.setWolfpackMode(enabled);
120145
+ host.setAppState({ wolfpackMode: enabled });
120146
+ if (enabled) {
120147
+ host.showNotice("WolfPack 模式:开启", "批量并发代理已激活。");
120148
+ return;
120149
+ }
120150
+ host.showNotice("WolfPack 模式:关闭");
120151
+ } catch (error) {
120152
+ const msg = formatErrorMessage(error);
120153
+ host.showError(`Failed to set wolfpack mode: ${msg}`);
120154
+ }
119854
120155
  }
119855
120156
  async function handleCompactCommand(host, args) {
119856
120157
  const session = host.session;
@@ -120575,12 +120876,608 @@ function syncGoalMetadata(host) {
120575
120876
  session.writeMetadata();
120576
120877
  }
120577
120878
  //#endregion
120879
+ //#region src/utils/git/git-status.ts
120880
+ /**
120881
+ * Cached git branch + working-tree status for the footer/statusline.
120882
+ *
120883
+ * Branch name refreshes every 5s, porcelain status every 15s. Branch
120884
+ * and status reads stay synchronous with short timeouts. Pull request
120885
+ * lookup uses an async cache so a slow `gh pr view` never blocks
120886
+ * footer rendering.
120887
+ */
120888
+ const BRANCH_TTL_MS = 5e3;
120889
+ const STATUS_TTL_MS = 15e3;
120890
+ const PULL_REQUEST_TTL_MS = 6e4;
120891
+ const SPAWN_TIMEOUT_MS = 500;
120892
+ const PR_SPAWN_TIMEOUT_MS = 5e3;
120893
+ const AHEAD_BEHIND_RE = /\[(?:ahead (\d+))?(?:, )?(?:behind (\d+))?\]/;
120894
+ function createGitStatusCache(workDir, options = {}) {
120895
+ const isRepo = detectGitRepo(workDir);
120896
+ let branch = {
120897
+ value: null,
120898
+ fetchedAt: 0
120899
+ };
120900
+ let status = {
120901
+ dirty: false,
120902
+ ahead: 0,
120903
+ behind: 0,
120904
+ diffAdded: 0,
120905
+ diffDeleted: 0,
120906
+ fetchedAt: 0
120907
+ };
120908
+ let pullRequest = {
120909
+ value: null,
120910
+ branch: null,
120911
+ fetchedAt: 0,
120912
+ pendingBranch: null,
120913
+ requestId: 0
120914
+ };
120915
+ return { getStatus: () => {
120916
+ if (!isRepo) return null;
120917
+ const now = Date.now();
120918
+ if (now - branch.fetchedAt >= BRANCH_TTL_MS) branch = {
120919
+ value: readBranch(workDir),
120920
+ fetchedAt: now
120921
+ };
120922
+ if (branch.value === null) return null;
120923
+ if (now - status.fetchedAt >= STATUS_TTL_MS) status = {
120924
+ ...readStatus(workDir),
120925
+ fetchedAt: now
120926
+ };
120927
+ refreshPullRequestIfNeeded(branch.value, now);
120928
+ return {
120929
+ branch: branch.value,
120930
+ dirty: status.dirty,
120931
+ ahead: status.ahead,
120932
+ behind: status.behind,
120933
+ diffAdded: status.diffAdded,
120934
+ diffDeleted: status.diffDeleted,
120935
+ pullRequest: pullRequest.branch === branch.value ? pullRequest.value : null
120936
+ };
120937
+ } };
120938
+ function refreshPullRequestIfNeeded(branchName, now) {
120939
+ if (pullRequest.pendingBranch === branchName) return;
120940
+ const fetchedAt = pullRequest.branch === branchName ? pullRequest.fetchedAt : 0;
120941
+ if (now - fetchedAt < PULL_REQUEST_TTL_MS) return;
120942
+ const requestId = pullRequest.requestId + 1;
120943
+ pullRequest = {
120944
+ value: pullRequest.branch === branchName ? pullRequest.value : null,
120945
+ branch: branchName,
120946
+ fetchedAt,
120947
+ pendingBranch: branchName,
120948
+ requestId
120949
+ };
120950
+ readPullRequest(workDir).then((value) => {
120951
+ if (pullRequest.requestId !== requestId) return;
120952
+ const changed = !samePullRequest(pullRequest.branch === branchName ? pullRequest.value : null, value);
120953
+ pullRequest = {
120954
+ value,
120955
+ branch: branchName,
120956
+ fetchedAt: Date.now(),
120957
+ pendingBranch: null,
120958
+ requestId
120959
+ };
120960
+ if (changed) options.onChange?.();
120961
+ });
120962
+ }
120963
+ }
120964
+ function detectGitRepo(workDir) {
120965
+ try {
120966
+ const result = spawnSync("git", [
120967
+ "-C",
120968
+ workDir,
120969
+ "rev-parse",
120970
+ "--is-inside-work-tree"
120971
+ ], {
120972
+ encoding: "utf8",
120973
+ timeout: SPAWN_TIMEOUT_MS
120974
+ });
120975
+ return result.status === 0 && result.stdout.trim() === "true";
120976
+ } catch {
120977
+ return false;
120978
+ }
120979
+ }
120980
+ function readBranch(workDir) {
120981
+ try {
120982
+ const result = spawnSync("git", [
120983
+ "-C",
120984
+ workDir,
120985
+ "branch",
120986
+ "--show-current"
120987
+ ], {
120988
+ encoding: "utf8",
120989
+ timeout: SPAWN_TIMEOUT_MS
120990
+ });
120991
+ if (result.status !== 0) return null;
120992
+ const name = result.stdout.trim();
120993
+ return name.length > 0 ? name : null;
120994
+ } catch {
120995
+ return null;
120996
+ }
120997
+ }
120998
+ function readStatus(workDir) {
120999
+ try {
121000
+ const result = spawnSync("git", [
121001
+ "-C",
121002
+ workDir,
121003
+ "status",
121004
+ "--porcelain",
121005
+ "-b"
121006
+ ], {
121007
+ encoding: "utf8",
121008
+ timeout: SPAWN_TIMEOUT_MS,
121009
+ maxBuffer: 4 * 1024 * 1024
121010
+ });
121011
+ if (result.status !== 0) return {
121012
+ dirty: false,
121013
+ ahead: 0,
121014
+ behind: 0,
121015
+ diffAdded: 0,
121016
+ diffDeleted: 0
121017
+ };
121018
+ let dirty = false;
121019
+ let ahead = 0;
121020
+ let behind = 0;
121021
+ for (const line of result.stdout.split("\n")) if (line.startsWith("## ")) {
121022
+ const m = AHEAD_BEHIND_RE.exec(line);
121023
+ if (m) {
121024
+ ahead = Number.parseInt(m[1] ?? "0", 10) || 0;
121025
+ behind = Number.parseInt(m[2] ?? "0", 10) || 0;
121026
+ }
121027
+ } else if (line.trim().length > 0) dirty = true;
121028
+ const diff = dirty ? readDiffStats(workDir) : {
121029
+ added: 0,
121030
+ deleted: 0
121031
+ };
121032
+ return {
121033
+ dirty,
121034
+ ahead,
121035
+ behind,
121036
+ diffAdded: diff.added,
121037
+ diffDeleted: diff.deleted
121038
+ };
121039
+ } catch {
121040
+ return {
121041
+ dirty: false,
121042
+ ahead: 0,
121043
+ behind: 0,
121044
+ diffAdded: 0,
121045
+ diffDeleted: 0
121046
+ };
121047
+ }
121048
+ }
121049
+ function readDiffStats(workDir) {
121050
+ try {
121051
+ const result = spawnSync("git", [
121052
+ "-C",
121053
+ workDir,
121054
+ "diff",
121055
+ "--numstat",
121056
+ "HEAD",
121057
+ "--"
121058
+ ], {
121059
+ encoding: "utf8",
121060
+ timeout: SPAWN_TIMEOUT_MS,
121061
+ maxBuffer: 4 * 1024 * 1024
121062
+ });
121063
+ if (result.status !== 0) return {
121064
+ added: 0,
121065
+ deleted: 0
121066
+ };
121067
+ let added = 0;
121068
+ let deleted = 0;
121069
+ for (const line of result.stdout.split("\n")) {
121070
+ if (!line) continue;
121071
+ const [addedText, deletedText] = line.split(" ");
121072
+ added += parseDiffNumstatCount(addedText);
121073
+ deleted += parseDiffNumstatCount(deletedText);
121074
+ }
121075
+ return {
121076
+ added,
121077
+ deleted
121078
+ };
121079
+ } catch {
121080
+ return {
121081
+ added: 0,
121082
+ deleted: 0
121083
+ };
121084
+ }
121085
+ }
121086
+ function parseDiffNumstatCount(value) {
121087
+ if (value === void 0 || value === "-") return 0;
121088
+ const n = Number.parseInt(value, 10);
121089
+ return Number.isFinite(n) && n > 0 ? n : 0;
121090
+ }
121091
+ function readPullRequest(workDir) {
121092
+ return new Promise((resolve) => {
121093
+ try {
121094
+ execFile("gh", [
121095
+ "pr",
121096
+ "view",
121097
+ "--json",
121098
+ "number,url"
121099
+ ], {
121100
+ cwd: workDir,
121101
+ encoding: "utf8",
121102
+ env: {
121103
+ ...process.env,
121104
+ GH_NO_UPDATE_NOTIFIER: "1",
121105
+ GH_PROMPT_DISABLED: "1"
121106
+ },
121107
+ timeout: PR_SPAWN_TIMEOUT_MS,
121108
+ maxBuffer: 256 * 1024
121109
+ }, (error, stdout) => {
121110
+ if (error !== null) {
121111
+ resolve(null);
121112
+ return;
121113
+ }
121114
+ resolve(parsePullRequest(stdout));
121115
+ });
121116
+ } catch {
121117
+ resolve(null);
121118
+ }
121119
+ });
121120
+ }
121121
+ function samePullRequest(a, b) {
121122
+ if (a === null || b === null) return a === b;
121123
+ return a.number === b.number && a.url === b.url;
121124
+ }
121125
+ function parsePullRequest(stdout) {
121126
+ try {
121127
+ const raw = JSON.parse(stdout);
121128
+ if (typeof raw !== "object" || raw === null) return null;
121129
+ const record = raw;
121130
+ const number = record["number"];
121131
+ const url = record["url"];
121132
+ if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) return null;
121133
+ if (typeof url !== "string" || !isSafeHttpUrl(url)) return null;
121134
+ return {
121135
+ number,
121136
+ url
121137
+ };
121138
+ } catch {
121139
+ return null;
121140
+ }
121141
+ }
121142
+ function isSafeHttpUrl(value) {
121143
+ if (hasControlChars(value)) return false;
121144
+ try {
121145
+ const url = new URL(value);
121146
+ return url.protocol === "https:" || url.protocol === "http:";
121147
+ } catch {
121148
+ return false;
121149
+ }
121150
+ }
121151
+ function hasControlChars(value) {
121152
+ for (const char of value) {
121153
+ const code = char.codePointAt(0) ?? 0;
121154
+ if (code <= 31 || code === 127) return true;
121155
+ }
121156
+ return false;
121157
+ }
121158
+ function formatGitBadgeBase(status) {
121159
+ const parts = [];
121160
+ const diff = formatDiffStats(status);
121161
+ if (diff) parts.push(diff);
121162
+ let sync = "";
121163
+ if (status.ahead > 0) sync += `↑${status.ahead}`;
121164
+ if (status.behind > 0) sync += `↓${status.behind}`;
121165
+ if (sync) parts.push(sync);
121166
+ return parts.length === 0 ? status.branch : `${status.branch} [${parts.join(" ")}]`;
121167
+ }
121168
+ function formatPullRequestBadge(pullRequest, options = {}) {
121169
+ const prText = `[PR#${String(pullRequest.number)}]`;
121170
+ return options.linkPullRequest ? toTerminalHyperlink(prText, pullRequest.url) : prText;
121171
+ }
121172
+ function formatDiffStats(status) {
121173
+ const parts = [];
121174
+ if (status.diffAdded > 0) parts.push(`+${String(status.diffAdded)}`);
121175
+ if (status.diffDeleted > 0) parts.push(`-${String(status.diffDeleted)}`);
121176
+ if (parts.length > 0) return parts.join(" ");
121177
+ return status.dirty ? "±" : null;
121178
+ }
121179
+ function toTerminalHyperlink(text, url) {
121180
+ if (!isSafeHttpUrl(url)) return text;
121181
+ return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
121182
+ }
121183
+ //#endregion
121184
+ //#region src/tui/components/chrome/footer.ts
121185
+ const MAX_CWD_SEGMENTS = 3;
121186
+ const TIP_ROTATE_INTERVAL_MS = 1e4;
121187
+ const TOOLBAR_TIPS = [
121188
+ { text: "shift+tab: 计划模式" },
121189
+ { text: "/model: 切换模型" },
121190
+ {
121191
+ text: "ctrl+s: 中途干预",
121192
+ priority: 2
121193
+ },
121194
+ {
121195
+ text: "/compact: 压缩上下文",
121196
+ priority: 2
121197
+ },
121198
+ { text: "ctrl+o: 展开工具输出" },
121199
+ { text: "/tasks: 后台任务" },
121200
+ { text: "shift+enter: 换行" },
121201
+ {
121202
+ text: "/init: 生成 AGENTS.md",
121203
+ priority: 2
121204
+ },
121205
+ { text: "@: 提及文件" },
121206
+ { text: "ctrl+c: 取消" },
121207
+ { text: "/theme: 切换主题" },
121208
+ { text: "/auto: 自动权限模式" },
121209
+ { text: "/yes: 自动批准" },
121210
+ { text: "/help: 显示命令" },
121211
+ {
121212
+ text: "/config: 选择并配置你常用的模型商",
121213
+ solo: true,
121214
+ priority: 3
121215
+ },
121216
+ {
121217
+ text: "让 Scream 安排任务,例如 \"2个小时后提醒我去拿快递\"",
121218
+ solo: true,
121219
+ priority: 3
121220
+ }
121221
+ ];
121222
+ /**
121223
+ * Expand tips into a rotation sequence using smooth weighted round-robin
121224
+ * (the nginx SWRR algorithm). Higher-`priority` tips appear more often while
121225
+ * staying evenly spread, so a tip generally does not land next to its own
121226
+ * duplicate. Deterministic and computed once at module load. Exported for
121227
+ * unit testing.
121228
+ */
121229
+ function buildWeightedTips(tips) {
121230
+ const items = tips.map((t) => ({
121231
+ tip: t,
121232
+ weight: Math.max(1, Math.trunc(t.priority ?? 1)),
121233
+ current: 0
121234
+ }));
121235
+ const total = items.reduce((sum, it) => sum + it.weight, 0);
121236
+ const seq = [];
121237
+ for (let n = 0; n < total; n++) {
121238
+ let best = items[0];
121239
+ for (const it of items) {
121240
+ it.current += it.weight;
121241
+ if (it.current > best.current) best = it;
121242
+ }
121243
+ best.current -= total;
121244
+ seq.push(best.tip);
121245
+ }
121246
+ return seq;
121247
+ }
121248
+ const ROTATION = buildWeightedTips(TOOLBAR_TIPS);
121249
+ function currentTipIndex() {
121250
+ return Math.floor(Date.now() / TIP_ROTATE_INTERVAL_MS);
121251
+ }
121252
+ /**
121253
+ * Pick the tip(s) for a rotation index over the weighted ROTATION sequence.
121254
+ * `primary` is always shown when it fits; `pair` (primary + next tip joined
121255
+ * by the separator) is offered for wide terminals. Pairing is skipped when
121256
+ * the current/next tip is `solo` or when the neighbour is a duplicate of the
121257
+ * current tip (which can happen at the wrap boundary), keeping long/important
121258
+ * tips on their own and avoiding "X | X".
121259
+ */
121260
+ function tipsForIndex(index) {
121261
+ const n = ROTATION.length;
121262
+ if (n === 0) return {
121263
+ primary: "",
121264
+ pair: null
121265
+ };
121266
+ const offset = (index % n + n) % n;
121267
+ const current = ROTATION[offset];
121268
+ if (n === 1 || current.solo) return {
121269
+ primary: current.text,
121270
+ pair: null
121271
+ };
121272
+ const next = ROTATION[(offset + 1) % n];
121273
+ if (next.solo || next.text === current.text) return {
121274
+ primary: current.text,
121275
+ pair: null
121276
+ };
121277
+ return {
121278
+ primary: current.text,
121279
+ pair: current.text + " | " + next.text
121280
+ };
121281
+ }
121282
+ function shortenModel(model) {
121283
+ if (!model) return model;
121284
+ const slash = model.lastIndexOf("/");
121285
+ return slash >= 0 ? model.slice(slash + 1) : model;
121286
+ }
121287
+ function modelDisplayName(state) {
121288
+ const model = state.availableModels[state.model];
121289
+ return model?.displayName ?? model?.model ?? state.model;
121290
+ }
121291
+ function shortenCwd(path) {
121292
+ if (!path) return path;
121293
+ const home = process.env["HOME"] ?? "";
121294
+ let work = path;
121295
+ if (home && path === home) return "~";
121296
+ if (home && path.startsWith(home + "/")) work = "~" + path.slice(home.length);
121297
+ const segments = work.split("/").filter((s) => s.length > 0);
121298
+ if (segments.length <= MAX_CWD_SEGMENTS) return work;
121299
+ return `…/${segments.slice(-3).join("/")}`;
121300
+ }
121301
+ function formatTokenCount(n) {
121302
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
121303
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
121304
+ return String(n);
121305
+ }
121306
+ function safeUsage(usage) {
121307
+ return safeUsageRatio(usage);
121308
+ }
121309
+ function formatContextStatus(usage, tokens, maxTokens) {
121310
+ const pct = `${(safeUsage(usage) * 100).toFixed(1)}%`;
121311
+ if (maxTokens && maxTokens > 0 && tokens !== void 0) return `上下文:${pct} (${formatTokenCount(tokens)}/${formatTokenCount(maxTokens)})`;
121312
+ return `上下文:${pct}`;
121313
+ }
121314
+ const BRAND_COLORS = [
121315
+ "#72A4E9",
121316
+ "#A78BFA",
121317
+ "#34D399"
121318
+ ];
121319
+ const GRADIENT_CYCLE_MS = 4e3;
121320
+ const SPINNER_FRAMES$1 = [
121321
+ "●",
121322
+ "◉",
121323
+ "◎",
121324
+ "◌",
121325
+ "○",
121326
+ "◌",
121327
+ "◎",
121328
+ "◉"
121329
+ ];
121330
+ const SPINNER_TICK_MS = 120;
121331
+ function hexToRgb$1(hex) {
121332
+ const v = parseInt(hex.slice(1), 16);
121333
+ return [
121334
+ v >> 16 & 255,
121335
+ v >> 8 & 255,
121336
+ v & 255
121337
+ ];
121338
+ }
121339
+ function lerpGradient(t) {
121340
+ const count = BRAND_COLORS.length;
121341
+ const segment = Math.min(t * count, count - 1);
121342
+ const idx = Math.floor(segment);
121343
+ const localT = segment - idx;
121344
+ const nextIdx = (idx + 1) % count;
121345
+ const [r0, g0, b0] = hexToRgb$1(BRAND_COLORS[idx]);
121346
+ const [r1, g1, b1] = hexToRgb$1(BRAND_COLORS[nextIdx]);
121347
+ const r = Math.round(r0 + (r1 - r0) * localT);
121348
+ const g = Math.round(g0 + (g1 - g0) * localT);
121349
+ const b = Math.round(b0 + (b1 - b0) * localT);
121350
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
121351
+ }
121352
+ function buildStatusLine(streamingPhase, livePaneMode, streamingStartTime) {
121353
+ if (streamingPhase === "idle" && livePaneMode !== "tool") return "○ 空闲";
121354
+ let label;
121355
+ if (livePaneMode === "tool") label = "执行中";
121356
+ else if (streamingPhase === "waiting") label = "等待响应";
121357
+ else if (streamingPhase === "thinking") label = "思考中";
121358
+ else if (streamingPhase === "composing") label = "输出中";
121359
+ else label = "";
121360
+ const elapsed = Date.now() - streamingStartTime;
121361
+ const totalSeconds = Math.floor(elapsed / 1e3);
121362
+ const elapsedStr = totalSeconds < 60 ? `${totalSeconds}s` : `${Math.floor(totalSeconds / 60)}m${totalSeconds % 60}s`;
121363
+ const now = Date.now();
121364
+ const frame = SPINNER_FRAMES$1[Math.floor(now / SPINNER_TICK_MS) % SPINNER_FRAMES$1.length];
121365
+ const gradientColor = lerpGradient(now % GRADIENT_CYCLE_MS / GRADIENT_CYCLE_MS);
121366
+ return chalk.hex(gradientColor).bold(frame) + " " + label + " " + elapsedStr;
121367
+ }
121368
+ function formatFooterGitBadge(status, colors) {
121369
+ const base = chalk.hex(colors.status)(formatGitBadgeBase(status));
121370
+ if (status.pullRequest === null) return base;
121371
+ return `${base} ${chalk.hex(colors.primary)(formatPullRequestBadge(status.pullRequest, { linkPullRequest: true }))}`;
121372
+ }
121373
+ var FooterComponent = class {
121374
+ state;
121375
+ colors;
121376
+ onGitStatusChange;
121377
+ gitCache;
121378
+ gitCacheWorkDir;
121379
+ transientHint = null;
121380
+ /**
121381
+ * Non-terminal background-task counts split by kind so the footer can
121382
+ * render two distinct badges. `bashTasks` covers `bash-*` BPM tasks
121383
+ * spawned via `Shell run_in_background=true`; `agentTasks` covers
121384
+ * `agent-*` BPM tasks (background subagents). Either zero hides its
121385
+ * respective badge.
121386
+ */
121387
+ backgroundBashTaskCount = 0;
121388
+ backgroundAgentCount = 0;
121389
+ constructor(state, colors, onGitStatusChange = () => {}) {
121390
+ this.state = state;
121391
+ this.colors = colors;
121392
+ this.onGitStatusChange = onGitStatusChange;
121393
+ this.gitCacheWorkDir = state.workDir;
121394
+ this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
121395
+ }
121396
+ setState(state) {
121397
+ if (state.workDir !== this.gitCacheWorkDir) {
121398
+ this.gitCacheWorkDir = state.workDir;
121399
+ this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
121400
+ }
121401
+ this.state = state;
121402
+ }
121403
+ setColors(colors) {
121404
+ this.colors = colors;
121405
+ }
121406
+ /**
121407
+ * Short-lived hint that replaces the rotating toolbar tips on line 1.
121408
+ * Used by the exit-confirmation double-tap flow to show "Press Ctrl+C
121409
+ * again to exit" without requiring a toast/overlay subsystem.
121410
+ * Pass `null` to clear.
121411
+ */
121412
+ setTransientHint(hint) {
121413
+ this.transientHint = hint;
121414
+ }
121415
+ /**
121416
+ * Sync both background-task badges with live counts. Each non-zero
121417
+ * count produces its own bracketed badge on line 1; zeros hide them
121418
+ * independently.
121419
+ */
121420
+ setBackgroundCounts(counts) {
121421
+ this.backgroundBashTaskCount = Math.max(0, counts.bashTasks);
121422
+ this.backgroundAgentCount = Math.max(0, counts.agentTasks);
121423
+ }
121424
+ invalidate() {}
121425
+ render(width) {
121426
+ const colors = this.colors;
121427
+ const state = this.state;
121428
+ const left = [];
121429
+ if (state.permissionMode === "auto") left.push(chalk.hex(colors.warning).bold("auto"));
121430
+ if (state.permissionMode === "yolo") left.push(chalk.hex(colors.warning).bold("YES"));
121431
+ if (state.planMode) left.push(chalk.hex(colors.primary).bold("plan"));
121432
+ if (state.wolfpackMode) left.push(chalk.hex(colors.primary).bold("wolfpack"));
121433
+ if (state.goalActive && state.goal) {
121434
+ const goalText = state.goal.length > 20 ? state.goal.slice(0, 20) + "…" : state.goal;
121435
+ left.push(chalk.hex(colors.success).bold(`🎯 ${goalText}`));
121436
+ }
121437
+ const model = shortenModel(modelDisplayName(state));
121438
+ if (model) {
121439
+ const thinkingLabel = state.thinking ? " 思考中" : "";
121440
+ left.push(chalk.hex(colors.text)(`${model}${thinkingLabel}`));
121441
+ }
121442
+ if (this.backgroundBashTaskCount > 0) {
121443
+ const noun = this.backgroundBashTaskCount === 1 ? "个任务" : "个任务";
121444
+ left.push(chalk.hex(colors.primary)(`[${String(this.backgroundBashTaskCount)}${noun} 运行中]`));
121445
+ }
121446
+ if (this.backgroundAgentCount > 0) {
121447
+ const noun = this.backgroundAgentCount === 1 ? "个代理" : "个代理";
121448
+ left.push(chalk.hex(colors.primary)(`[${String(this.backgroundAgentCount)}${noun} 运行中]`));
121449
+ }
121450
+ const cwd = shortenCwd(state.workDir);
121451
+ if (cwd) left.push(chalk.hex(colors.status)(cwd));
121452
+ const git = this.gitCache.getStatus();
121453
+ if (git !== null) left.push(formatFooterGitBadge(git, colors));
121454
+ const leftLine = left.join(" ");
121455
+ const leftWidth = visibleWidth(leftLine);
121456
+ let rightText;
121457
+ if (this.transientHint) rightText = chalk.hex(colors.warning).bold(this.transientHint);
121458
+ else {
121459
+ const statusLine = buildStatusLine(state.streamingPhase, state.livePaneMode, state.streamingStartTime);
121460
+ const ccDot = state.ccConnectActive ? chalk.hex(colors.success)("●") : chalk.hex(colors.textDim)("●");
121461
+ rightText = chalk.hex(colors.textDim)(ccDot + " " + formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens) + " " + statusLine);
121462
+ }
121463
+ const rightWidth = visibleWidth(rightText);
121464
+ const gap = 3;
121465
+ let line1;
121466
+ if (leftWidth + gap + rightWidth <= width) {
121467
+ const pad = width - leftWidth - rightWidth;
121468
+ line1 = leftLine + " ".repeat(pad) + rightText;
121469
+ } else if (leftWidth <= width) line1 = leftLine;
121470
+ else line1 = truncateToWidth(leftLine, width, "…");
121471
+ return [truncateToWidth(line1, width)];
121472
+ }
121473
+ };
121474
+ //#endregion
120578
121475
  //#region src/tui/components/chrome/welcome.ts
120579
121476
  const HUE_STOPS = 24;
120580
121477
  const SUB_STEPS = 5;
120581
121478
  const BREATHE_STEPS = HUE_STOPS * SUB_STEPS;
120582
121479
  const BREATHE_INTERVAL_MS = 40;
120583
- function hexToRgb$1(hex) {
121480
+ function hexToRgb(hex) {
120584
121481
  return [
120585
121482
  parseInt(hex.slice(1, 3), 16),
120586
121483
  parseInt(hex.slice(3, 5), 16),
@@ -120645,7 +121542,7 @@ function rgbToHex(r, g, b) {
120645
121542
  * it sweeps through all 24 hue stops with smooth sub-step interpolation.
120646
121543
  */
120647
121544
  function buildBreathingPalette(primaryHex, hueStops, subSteps) {
120648
- const [r, g, b] = hexToRgb$1(primaryHex);
121545
+ const [r, g, b] = hexToRgb(primaryHex);
120649
121546
  const [baseHue, sat, lit] = rgbToHsl(r, g, b);
120650
121547
  const steps = hueStops * subSteps;
120651
121548
  const palette = [];
@@ -120662,6 +121559,7 @@ var WelcomeComponent = class {
120662
121559
  breatheFrame = 0;
120663
121560
  breatheTimer = null;
120664
121561
  breathePalette;
121562
+ borderTitle = null;
120665
121563
  constructor(state, colors, ui) {
120666
121564
  this.state = state;
120667
121565
  this.colors = colors;
@@ -120700,7 +121598,7 @@ var WelcomeComponent = class {
120700
121598
  const isLoggedOut = !this.state.model;
120701
121599
  const dim = chalk.hex(this.colors.textDim);
120702
121600
  const labelStyle = chalk.bold.hex(this.colors.textDim);
120703
- const rightRow1 = truncateToWidth(dim(isLoggedOut ? "运行 /config 开始配置。" : "发送 /help 获取帮助信息。/config 配置模型"), textWidth, "…");
121601
+ const rightRow1 = truncateToWidth(dim(isLoggedOut ? "运行 /config 开始配置。" : "发送 / 进入快捷菜单,/exit 保存并退出"), textWidth, "…");
120704
121602
  const headerLines = [logoColor(logo[0].padEnd(logoWidth)) + gap + rightRow0, logoColor(logo[1].padEnd(logoWidth)) + gap + rightRow1];
120705
121603
  const activeModel = this.state.availableModels[this.state.model];
120706
121604
  const modelValue = isLoggedOut ? chalk.hex(this.colors.warning)("未设置,运行 /config") : activeModel?.displayName ?? activeModel?.model ?? this.state.model;
@@ -120712,19 +121610,20 @@ var WelcomeComponent = class {
120712
121610
  labelStyle("模型: ") + modelValue,
120713
121611
  labelStyle("版本: ") + versionValue
120714
121612
  ];
120715
- const hintLine = chalk.hex(this.colors.textMuted)("按 / 可进入快捷指令菜单,输入 /sessions 可恢复或管理会话");
120716
- const tipLine = chalk.hex(this.colors.textMuted)("Tips:试试让它写代码、做研报、做攻略、清理电脑...");
121613
+ const { primary: tipPrimary, pair: tipPair } = tipsForIndex(currentTipIndex());
121614
+ const tip = tipPair && visibleWidth(tipPair) <= innerWidth ? tipPair : tipPrimary;
121615
+ const tipLine = chalk.hex(this.colors.textMuted)("Tips: " + tip);
120717
121616
  const contentLines = [
120718
121617
  ...headerLines,
120719
121618
  "",
120720
121619
  ...infoLines,
120721
121620
  "",
120722
- hintLine,
120723
121621
  tipLine
120724
121622
  ];
121623
+ const borderTitle = this.borderTitle;
120725
121624
  const lines = [
120726
121625
  "",
120727
- primary("╭" + "─".repeat(width - 2) + "╮"),
121626
+ borderTitle ? primary("╭─ " + borderTitle + " " + "─".repeat(Math.max(0, width - 5 - visibleWidth(borderTitle))) + "╮") : primary("╭" + "─".repeat(width - 2) + "╮"),
120728
121627
  primary("│") + " ".repeat(width - 2) + primary("│")
120729
121628
  ];
120730
121629
  for (const content of contentLines) {
@@ -124725,7 +125624,7 @@ async function confirmUninstall(host, label) {
124725
125624
  * restores the editor. The question and answer are never recorded in the
124726
125625
  * main conversation history.
124727
125626
  */
124728
- const SPINNER_FRAMES$1 = [
125627
+ const SPINNER_FRAMES = [
124729
125628
  "⠋",
124730
125629
  "⠙",
124731
125630
  "⠹",
@@ -124785,7 +125684,7 @@ var BtwOverlayComponent = class extends Container {
124785
125684
  startSpinner() {
124786
125685
  this.spinnerFrame = 0;
124787
125686
  this.spinnerInterval = setInterval(() => {
124788
- this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES$1.length;
125687
+ this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
124789
125688
  this.requestRender();
124790
125689
  }, 80);
124791
125690
  }
@@ -124805,7 +125704,7 @@ var BtwOverlayComponent = class extends Container {
124805
125704
  lines.push(chalk.hex(c.primary)("/btw") + chalk.hex(c.textMuted)(" — ") + chalk.hex(c.text)(truncated));
124806
125705
  lines.push("");
124807
125706
  if (this.status === "loading") {
124808
- const spinner = SPINNER_FRAMES$1[this.spinnerFrame];
125707
+ const spinner = SPINNER_FRAMES[this.spinnerFrame];
124809
125708
  lines.push(chalk.hex(c.textMuted)(`${spinner} Answering…`));
124810
125709
  } else if (this.status === "done" && this.markdown !== void 0) {
124811
125710
  const contentWidth = Math.max(20, width - 2);
@@ -124965,8 +125864,8 @@ async function handleBuiltInSlashCommand(host, name, args) {
124965
125864
  case "plan":
124966
125865
  await handlePlanCommand(host, args);
124967
125866
  return;
124968
- case "power":
124969
- await handlePowerCommand(host, args);
125867
+ case "wolfpack":
125868
+ await handleWolfpackCommand(host, args);
124970
125869
  return;
124971
125870
  case "revoke":
124972
125871
  await handleRevokeCommand(host, args);
@@ -129971,635 +130870,6 @@ const INITIAL_LIVE_PANE = {
129971
130870
  pendingQuestion: null
129972
130871
  };
129973
130872
  //#endregion
129974
- //#region src/utils/git/git-status.ts
129975
- /**
129976
- * Cached git branch + working-tree status for the footer/statusline.
129977
- *
129978
- * Branch name refreshes every 5s, porcelain status every 15s. Branch
129979
- * and status reads stay synchronous with short timeouts. Pull request
129980
- * lookup uses an async cache so a slow `gh pr view` never blocks
129981
- * footer rendering.
129982
- */
129983
- const BRANCH_TTL_MS = 5e3;
129984
- const STATUS_TTL_MS = 15e3;
129985
- const PULL_REQUEST_TTL_MS = 6e4;
129986
- const SPAWN_TIMEOUT_MS = 500;
129987
- const PR_SPAWN_TIMEOUT_MS = 5e3;
129988
- const AHEAD_BEHIND_RE = /\[(?:ahead (\d+))?(?:, )?(?:behind (\d+))?\]/;
129989
- function createGitStatusCache(workDir, options = {}) {
129990
- const isRepo = detectGitRepo(workDir);
129991
- let branch = {
129992
- value: null,
129993
- fetchedAt: 0
129994
- };
129995
- let status = {
129996
- dirty: false,
129997
- ahead: 0,
129998
- behind: 0,
129999
- diffAdded: 0,
130000
- diffDeleted: 0,
130001
- fetchedAt: 0
130002
- };
130003
- let pullRequest = {
130004
- value: null,
130005
- branch: null,
130006
- fetchedAt: 0,
130007
- pendingBranch: null,
130008
- requestId: 0
130009
- };
130010
- return { getStatus: () => {
130011
- if (!isRepo) return null;
130012
- const now = Date.now();
130013
- if (now - branch.fetchedAt >= BRANCH_TTL_MS) branch = {
130014
- value: readBranch(workDir),
130015
- fetchedAt: now
130016
- };
130017
- if (branch.value === null) return null;
130018
- if (now - status.fetchedAt >= STATUS_TTL_MS) status = {
130019
- ...readStatus(workDir),
130020
- fetchedAt: now
130021
- };
130022
- refreshPullRequestIfNeeded(branch.value, now);
130023
- return {
130024
- branch: branch.value,
130025
- dirty: status.dirty,
130026
- ahead: status.ahead,
130027
- behind: status.behind,
130028
- diffAdded: status.diffAdded,
130029
- diffDeleted: status.diffDeleted,
130030
- pullRequest: pullRequest.branch === branch.value ? pullRequest.value : null
130031
- };
130032
- } };
130033
- function refreshPullRequestIfNeeded(branchName, now) {
130034
- if (pullRequest.pendingBranch === branchName) return;
130035
- const fetchedAt = pullRequest.branch === branchName ? pullRequest.fetchedAt : 0;
130036
- if (now - fetchedAt < PULL_REQUEST_TTL_MS) return;
130037
- const requestId = pullRequest.requestId + 1;
130038
- pullRequest = {
130039
- value: pullRequest.branch === branchName ? pullRequest.value : null,
130040
- branch: branchName,
130041
- fetchedAt,
130042
- pendingBranch: branchName,
130043
- requestId
130044
- };
130045
- readPullRequest(workDir).then((value) => {
130046
- if (pullRequest.requestId !== requestId) return;
130047
- const changed = !samePullRequest(pullRequest.branch === branchName ? pullRequest.value : null, value);
130048
- pullRequest = {
130049
- value,
130050
- branch: branchName,
130051
- fetchedAt: Date.now(),
130052
- pendingBranch: null,
130053
- requestId
130054
- };
130055
- if (changed) options.onChange?.();
130056
- });
130057
- }
130058
- }
130059
- function detectGitRepo(workDir) {
130060
- try {
130061
- const result = spawnSync("git", [
130062
- "-C",
130063
- workDir,
130064
- "rev-parse",
130065
- "--is-inside-work-tree"
130066
- ], {
130067
- encoding: "utf8",
130068
- timeout: SPAWN_TIMEOUT_MS
130069
- });
130070
- return result.status === 0 && result.stdout.trim() === "true";
130071
- } catch {
130072
- return false;
130073
- }
130074
- }
130075
- function readBranch(workDir) {
130076
- try {
130077
- const result = spawnSync("git", [
130078
- "-C",
130079
- workDir,
130080
- "branch",
130081
- "--show-current"
130082
- ], {
130083
- encoding: "utf8",
130084
- timeout: SPAWN_TIMEOUT_MS
130085
- });
130086
- if (result.status !== 0) return null;
130087
- const name = result.stdout.trim();
130088
- return name.length > 0 ? name : null;
130089
- } catch {
130090
- return null;
130091
- }
130092
- }
130093
- function readStatus(workDir) {
130094
- try {
130095
- const result = spawnSync("git", [
130096
- "-C",
130097
- workDir,
130098
- "status",
130099
- "--porcelain",
130100
- "-b"
130101
- ], {
130102
- encoding: "utf8",
130103
- timeout: SPAWN_TIMEOUT_MS,
130104
- maxBuffer: 4 * 1024 * 1024
130105
- });
130106
- if (result.status !== 0) return {
130107
- dirty: false,
130108
- ahead: 0,
130109
- behind: 0,
130110
- diffAdded: 0,
130111
- diffDeleted: 0
130112
- };
130113
- let dirty = false;
130114
- let ahead = 0;
130115
- let behind = 0;
130116
- for (const line of result.stdout.split("\n")) if (line.startsWith("## ")) {
130117
- const m = AHEAD_BEHIND_RE.exec(line);
130118
- if (m) {
130119
- ahead = Number.parseInt(m[1] ?? "0", 10) || 0;
130120
- behind = Number.parseInt(m[2] ?? "0", 10) || 0;
130121
- }
130122
- } else if (line.trim().length > 0) dirty = true;
130123
- const diff = dirty ? readDiffStats(workDir) : {
130124
- added: 0,
130125
- deleted: 0
130126
- };
130127
- return {
130128
- dirty,
130129
- ahead,
130130
- behind,
130131
- diffAdded: diff.added,
130132
- diffDeleted: diff.deleted
130133
- };
130134
- } catch {
130135
- return {
130136
- dirty: false,
130137
- ahead: 0,
130138
- behind: 0,
130139
- diffAdded: 0,
130140
- diffDeleted: 0
130141
- };
130142
- }
130143
- }
130144
- function readDiffStats(workDir) {
130145
- try {
130146
- const result = spawnSync("git", [
130147
- "-C",
130148
- workDir,
130149
- "diff",
130150
- "--numstat",
130151
- "HEAD",
130152
- "--"
130153
- ], {
130154
- encoding: "utf8",
130155
- timeout: SPAWN_TIMEOUT_MS,
130156
- maxBuffer: 4 * 1024 * 1024
130157
- });
130158
- if (result.status !== 0) return {
130159
- added: 0,
130160
- deleted: 0
130161
- };
130162
- let added = 0;
130163
- let deleted = 0;
130164
- for (const line of result.stdout.split("\n")) {
130165
- if (!line) continue;
130166
- const [addedText, deletedText] = line.split(" ");
130167
- added += parseDiffNumstatCount(addedText);
130168
- deleted += parseDiffNumstatCount(deletedText);
130169
- }
130170
- return {
130171
- added,
130172
- deleted
130173
- };
130174
- } catch {
130175
- return {
130176
- added: 0,
130177
- deleted: 0
130178
- };
130179
- }
130180
- }
130181
- function parseDiffNumstatCount(value) {
130182
- if (value === void 0 || value === "-") return 0;
130183
- const n = Number.parseInt(value, 10);
130184
- return Number.isFinite(n) && n > 0 ? n : 0;
130185
- }
130186
- function readPullRequest(workDir) {
130187
- return new Promise((resolve) => {
130188
- try {
130189
- execFile("gh", [
130190
- "pr",
130191
- "view",
130192
- "--json",
130193
- "number,url"
130194
- ], {
130195
- cwd: workDir,
130196
- encoding: "utf8",
130197
- env: {
130198
- ...process.env,
130199
- GH_NO_UPDATE_NOTIFIER: "1",
130200
- GH_PROMPT_DISABLED: "1"
130201
- },
130202
- timeout: PR_SPAWN_TIMEOUT_MS,
130203
- maxBuffer: 256 * 1024
130204
- }, (error, stdout) => {
130205
- if (error !== null) {
130206
- resolve(null);
130207
- return;
130208
- }
130209
- resolve(parsePullRequest(stdout));
130210
- });
130211
- } catch {
130212
- resolve(null);
130213
- }
130214
- });
130215
- }
130216
- function samePullRequest(a, b) {
130217
- if (a === null || b === null) return a === b;
130218
- return a.number === b.number && a.url === b.url;
130219
- }
130220
- function parsePullRequest(stdout) {
130221
- try {
130222
- const raw = JSON.parse(stdout);
130223
- if (typeof raw !== "object" || raw === null) return null;
130224
- const record = raw;
130225
- const number = record["number"];
130226
- const url = record["url"];
130227
- if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) return null;
130228
- if (typeof url !== "string" || !isSafeHttpUrl(url)) return null;
130229
- return {
130230
- number,
130231
- url
130232
- };
130233
- } catch {
130234
- return null;
130235
- }
130236
- }
130237
- function isSafeHttpUrl(value) {
130238
- if (hasControlChars(value)) return false;
130239
- try {
130240
- const url = new URL(value);
130241
- return url.protocol === "https:" || url.protocol === "http:";
130242
- } catch {
130243
- return false;
130244
- }
130245
- }
130246
- function hasControlChars(value) {
130247
- for (const char of value) {
130248
- const code = char.codePointAt(0) ?? 0;
130249
- if (code <= 31 || code === 127) return true;
130250
- }
130251
- return false;
130252
- }
130253
- function formatGitBadgeBase(status) {
130254
- const parts = [];
130255
- const diff = formatDiffStats(status);
130256
- if (diff) parts.push(diff);
130257
- let sync = "";
130258
- if (status.ahead > 0) sync += `↑${status.ahead}`;
130259
- if (status.behind > 0) sync += `↓${status.behind}`;
130260
- if (sync) parts.push(sync);
130261
- return parts.length === 0 ? status.branch : `${status.branch} [${parts.join(" ")}]`;
130262
- }
130263
- function formatPullRequestBadge(pullRequest, options = {}) {
130264
- const prText = `[PR#${String(pullRequest.number)}]`;
130265
- return options.linkPullRequest ? toTerminalHyperlink(prText, pullRequest.url) : prText;
130266
- }
130267
- function formatDiffStats(status) {
130268
- const parts = [];
130269
- if (status.diffAdded > 0) parts.push(`+${String(status.diffAdded)}`);
130270
- if (status.diffDeleted > 0) parts.push(`-${String(status.diffDeleted)}`);
130271
- if (parts.length > 0) return parts.join(" ");
130272
- return status.dirty ? "±" : null;
130273
- }
130274
- function toTerminalHyperlink(text, url) {
130275
- if (!isSafeHttpUrl(url)) return text;
130276
- return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
130277
- }
130278
- //#endregion
130279
- //#region src/tui/components/chrome/footer.ts
130280
- const MAX_CWD_SEGMENTS = 3;
130281
- const TIP_ROTATE_INTERVAL_MS = 1e4;
130282
- const TIP_SEPARATOR = " | ";
130283
- const TOOLBAR_TIPS = [
130284
- { text: "shift+tab: 计划模式" },
130285
- { text: "/model: 切换模型" },
130286
- {
130287
- text: "ctrl+s: 中途干预",
130288
- priority: 2
130289
- },
130290
- {
130291
- text: "/compact: 压缩上下文",
130292
- priority: 2
130293
- },
130294
- { text: "ctrl+o: 展开工具输出" },
130295
- { text: "/tasks: 后台任务" },
130296
- { text: "shift+enter: 换行" },
130297
- {
130298
- text: "/init: 生成 AGENTS.md",
130299
- priority: 2
130300
- },
130301
- { text: "@: 提及文件" },
130302
- { text: "ctrl+c: 取消" },
130303
- { text: "/theme: 切换主题" },
130304
- { text: "/auto: 自动权限模式" },
130305
- { text: "/yes: 自动批准" },
130306
- { text: "/help: 显示命令" },
130307
- {
130308
- text: "/config: 选择并配置你常用的模型商",
130309
- solo: true,
130310
- priority: 3
130311
- },
130312
- {
130313
- text: "让 Scream 安排任务,例如 \"2个小时后提醒我去拿快递\"",
130314
- solo: true,
130315
- priority: 3
130316
- }
130317
- ];
130318
- /**
130319
- * Expand tips into a rotation sequence using smooth weighted round-robin
130320
- * (the nginx SWRR algorithm). Higher-`priority` tips appear more often while
130321
- * staying evenly spread, so a tip generally does not land next to its own
130322
- * duplicate. Deterministic and computed once at module load. Exported for
130323
- * unit testing.
130324
- */
130325
- function buildWeightedTips(tips) {
130326
- const items = tips.map((t) => ({
130327
- tip: t,
130328
- weight: Math.max(1, Math.trunc(t.priority ?? 1)),
130329
- current: 0
130330
- }));
130331
- const total = items.reduce((sum, it) => sum + it.weight, 0);
130332
- const seq = [];
130333
- for (let n = 0; n < total; n++) {
130334
- let best = items[0];
130335
- for (const it of items) {
130336
- it.current += it.weight;
130337
- if (it.current > best.current) best = it;
130338
- }
130339
- best.current -= total;
130340
- seq.push(best.tip);
130341
- }
130342
- return seq;
130343
- }
130344
- const ROTATION = buildWeightedTips(TOOLBAR_TIPS);
130345
- function currentTipIndex() {
130346
- return Math.floor(Date.now() / TIP_ROTATE_INTERVAL_MS);
130347
- }
130348
- /**
130349
- * Pick the tip(s) for a rotation index over the weighted ROTATION sequence.
130350
- * `primary` is always shown when it fits; `pair` (primary + next tip joined
130351
- * by the separator) is offered for wide terminals. Pairing is skipped when
130352
- * the current/next tip is `solo` or when the neighbour is a duplicate of the
130353
- * current tip (which can happen at the wrap boundary), keeping long/important
130354
- * tips on their own and avoiding "X | X".
130355
- */
130356
- function tipsForIndex(index) {
130357
- const n = ROTATION.length;
130358
- if (n === 0) return {
130359
- primary: "",
130360
- pair: null
130361
- };
130362
- const offset = (index % n + n) % n;
130363
- const current = ROTATION[offset];
130364
- if (n === 1 || current.solo) return {
130365
- primary: current.text,
130366
- pair: null
130367
- };
130368
- const next = ROTATION[(offset + 1) % n];
130369
- if (next.solo || next.text === current.text) return {
130370
- primary: current.text,
130371
- pair: null
130372
- };
130373
- return {
130374
- primary: current.text,
130375
- pair: current.text + TIP_SEPARATOR + next.text
130376
- };
130377
- }
130378
- function shortenModel(model) {
130379
- if (!model) return model;
130380
- const slash = model.lastIndexOf("/");
130381
- return slash >= 0 ? model.slice(slash + 1) : model;
130382
- }
130383
- function modelDisplayName(state) {
130384
- const model = state.availableModels[state.model];
130385
- return model?.displayName ?? model?.model ?? state.model;
130386
- }
130387
- function shortenCwd(path) {
130388
- if (!path) return path;
130389
- const home = process.env["HOME"] ?? "";
130390
- let work = path;
130391
- if (home && path === home) return "~";
130392
- if (home && path.startsWith(home + "/")) work = "~" + path.slice(home.length);
130393
- const segments = work.split("/").filter((s) => s.length > 0);
130394
- if (segments.length <= MAX_CWD_SEGMENTS) return work;
130395
- return `…/${segments.slice(-3).join("/")}`;
130396
- }
130397
- function formatTokenCount(n) {
130398
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
130399
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
130400
- return String(n);
130401
- }
130402
- function safeUsage(usage) {
130403
- return safeUsageRatio(usage);
130404
- }
130405
- function formatContextStatus(usage, tokens, maxTokens) {
130406
- const pct = `${(safeUsage(usage) * 100).toFixed(1)}%`;
130407
- if (maxTokens && maxTokens > 0 && tokens !== void 0) return `上下文:${pct} (${formatTokenCount(tokens)}/${formatTokenCount(maxTokens)})`;
130408
- return `上下文:${pct}`;
130409
- }
130410
- const BRAND_COLORS = [
130411
- "#72A4E9",
130412
- "#A78BFA",
130413
- "#34D399"
130414
- ];
130415
- const GRADIENT_CYCLE_MS = 4e3;
130416
- const SPINNER_FRAMES = [
130417
- "●",
130418
- "◉",
130419
- "◎",
130420
- "◌",
130421
- "○",
130422
- "◌",
130423
- "◎",
130424
- "◉"
130425
- ];
130426
- const SPINNER_TICK_MS = 120;
130427
- function hexToRgb(hex) {
130428
- const v = parseInt(hex.slice(1), 16);
130429
- return [
130430
- v >> 16 & 255,
130431
- v >> 8 & 255,
130432
- v & 255
130433
- ];
130434
- }
130435
- function lerpGradient(t) {
130436
- const count = BRAND_COLORS.length;
130437
- const segment = Math.min(t * count, count - 1);
130438
- const idx = Math.floor(segment);
130439
- const localT = segment - idx;
130440
- const nextIdx = (idx + 1) % count;
130441
- const [r0, g0, b0] = hexToRgb(BRAND_COLORS[idx]);
130442
- const [r1, g1, b1] = hexToRgb(BRAND_COLORS[nextIdx]);
130443
- const r = Math.round(r0 + (r1 - r0) * localT);
130444
- const g = Math.round(g0 + (g1 - g0) * localT);
130445
- const b = Math.round(b0 + (b1 - b0) * localT);
130446
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
130447
- }
130448
- const POWER_GRADIENT_CYCLE_MS = 2e3;
130449
- /** Each character of `text` gets a phase-offset colour from the brand gradient, creating a flowing rainbow effect. */
130450
- function gradientText(text, now) {
130451
- const t = now % POWER_GRADIENT_CYCLE_MS / POWER_GRADIENT_CYCLE_MS;
130452
- let out = "";
130453
- for (let i = 0; i < text.length; i++) {
130454
- const charT = (t + i / text.length) % 1;
130455
- out += chalk.hex(lerpGradient(charT))(text[i]);
130456
- }
130457
- return chalk.bold(out);
130458
- }
130459
- function buildStatusLine(streamingPhase, livePaneMode, streamingStartTime) {
130460
- if (streamingPhase === "idle" && livePaneMode !== "tool") return "○ 空闲";
130461
- let label;
130462
- if (livePaneMode === "tool") label = "执行中";
130463
- else if (streamingPhase === "waiting") label = "等待响应";
130464
- else if (streamingPhase === "thinking") label = "思考中";
130465
- else if (streamingPhase === "composing") label = "输出中";
130466
- else label = "";
130467
- const elapsed = Date.now() - streamingStartTime;
130468
- const totalSeconds = Math.floor(elapsed / 1e3);
130469
- const elapsedStr = totalSeconds < 60 ? `${totalSeconds}s` : `${Math.floor(totalSeconds / 60)}m${totalSeconds % 60}s`;
130470
- const now = Date.now();
130471
- const frame = SPINNER_FRAMES[Math.floor(now / SPINNER_TICK_MS) % SPINNER_FRAMES.length];
130472
- const gradientColor = lerpGradient(now % GRADIENT_CYCLE_MS / GRADIENT_CYCLE_MS);
130473
- return chalk.hex(gradientColor).bold(frame) + " " + label + " " + elapsedStr;
130474
- }
130475
- function formatFooterGitBadge(status, colors) {
130476
- const base = chalk.hex(colors.status)(formatGitBadgeBase(status));
130477
- if (status.pullRequest === null) return base;
130478
- return `${base} ${chalk.hex(colors.primary)(formatPullRequestBadge(status.pullRequest, { linkPullRequest: true }))}`;
130479
- }
130480
- var FooterComponent = class {
130481
- state;
130482
- colors;
130483
- onGitStatusChange;
130484
- gitCache;
130485
- gitCacheWorkDir;
130486
- transientHint = null;
130487
- /**
130488
- * Non-terminal background-task counts split by kind so the footer can
130489
- * render two distinct badges. `bashTasks` covers `bash-*` BPM tasks
130490
- * spawned via `Shell run_in_background=true`; `agentTasks` covers
130491
- * `agent-*` BPM tasks (background subagents). Either zero hides its
130492
- * respective badge.
130493
- */
130494
- backgroundBashTaskCount = 0;
130495
- backgroundAgentCount = 0;
130496
- powerGradientTimer;
130497
- constructor(state, colors, onGitStatusChange = () => {}) {
130498
- this.state = state;
130499
- this.colors = colors;
130500
- this.onGitStatusChange = onGitStatusChange;
130501
- this.gitCacheWorkDir = state.workDir;
130502
- this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
130503
- }
130504
- setState(state) {
130505
- if (state.workDir !== this.gitCacheWorkDir) {
130506
- this.gitCacheWorkDir = state.workDir;
130507
- this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
130508
- }
130509
- this.state = state;
130510
- }
130511
- startPowerGradient() {
130512
- if (this.powerGradientTimer !== void 0) return;
130513
- this.powerGradientTimer = setInterval(() => {
130514
- this.onGitStatusChange();
130515
- }, 80);
130516
- }
130517
- stopPowerGradient() {
130518
- if (this.powerGradientTimer === void 0) return;
130519
- clearInterval(this.powerGradientTimer);
130520
- this.powerGradientTimer = void 0;
130521
- }
130522
- setColors(colors) {
130523
- this.colors = colors;
130524
- }
130525
- /**
130526
- * Short-lived hint that replaces the rotating toolbar tips on line 1.
130527
- * Used by the exit-confirmation double-tap flow to show "Press Ctrl+C
130528
- * again to exit" without requiring a toast/overlay subsystem.
130529
- * Pass `null` to clear.
130530
- */
130531
- setTransientHint(hint) {
130532
- this.transientHint = hint;
130533
- }
130534
- /**
130535
- * Sync both background-task badges with live counts. Each non-zero
130536
- * count produces its own bracketed badge on line 1; zeros hide them
130537
- * independently.
130538
- */
130539
- setBackgroundCounts(counts) {
130540
- this.backgroundBashTaskCount = Math.max(0, counts.bashTasks);
130541
- this.backgroundAgentCount = Math.max(0, counts.agentTasks);
130542
- }
130543
- invalidate() {}
130544
- render(width) {
130545
- const colors = this.colors;
130546
- const state = this.state;
130547
- const left = [];
130548
- if (state.permissionMode === "auto") left.push(chalk.hex(colors.warning).bold("auto"));
130549
- if (state.permissionMode === "yolo") left.push(chalk.hex(colors.warning).bold("YES"));
130550
- if (state.planMode) left.push(chalk.hex(colors.primary).bold("plan"));
130551
- if (state.parallelMode) left.push(gradientText("power", Date.now()));
130552
- if (state.goalActive && state.goal) {
130553
- const goalText = state.goal.length > 20 ? state.goal.slice(0, 20) + "…" : state.goal;
130554
- left.push(chalk.hex(colors.success).bold(`🎯 ${goalText}`));
130555
- }
130556
- const model = shortenModel(modelDisplayName(state));
130557
- if (model) {
130558
- const thinkingLabel = state.thinking ? " 思考中" : "";
130559
- left.push(chalk.hex(colors.text)(`${model}${thinkingLabel}`));
130560
- }
130561
- if (this.backgroundBashTaskCount > 0) {
130562
- const noun = this.backgroundBashTaskCount === 1 ? "个任务" : "个任务";
130563
- left.push(chalk.hex(colors.primary)(`[${String(this.backgroundBashTaskCount)}${noun} 运行中]`));
130564
- }
130565
- if (this.backgroundAgentCount > 0) {
130566
- const noun = this.backgroundAgentCount === 1 ? "个代理" : "个代理";
130567
- left.push(chalk.hex(colors.primary)(`[${String(this.backgroundAgentCount)}${noun} 运行中]`));
130568
- }
130569
- const cwd = shortenCwd(state.workDir);
130570
- if (cwd) left.push(chalk.hex(colors.status)(cwd));
130571
- const git = this.gitCache.getStatus();
130572
- if (git !== null) left.push(formatFooterGitBadge(git, colors));
130573
- const leftLine = left.join(" ");
130574
- const leftWidth = visibleWidth(leftLine);
130575
- const { primary, pair } = tipsForIndex(currentTipIndex());
130576
- const remaining = Math.max(0, width - leftWidth - 2);
130577
- let tipText = "";
130578
- if (pair && visibleWidth(pair) <= remaining) tipText = pair;
130579
- else if (primary && visibleWidth(primary) <= remaining) tipText = primary;
130580
- let line1;
130581
- if (tipText) {
130582
- const pad = width - leftWidth - visibleWidth(tipText);
130583
- line1 = leftLine + " ".repeat(Math.max(0, pad)) + chalk.hex(colors.textMuted)(tipText);
130584
- } else if (leftWidth <= width) line1 = leftLine;
130585
- else line1 = truncateToWidth(leftLine, width, "…");
130586
- const statusLine = buildStatusLine(state.streamingPhase, state.livePaneMode, state.streamingStartTime);
130587
- const rightText = (state.ccConnectActive ? chalk.hex(colors.success)("●") : chalk.hex(colors.textDim)("●")) + " " + formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens) + " " + statusLine;
130588
- const rightWidth = visibleWidth(rightText);
130589
- let line2;
130590
- if (this.transientHint) {
130591
- const hintText = chalk.hex(colors.warning).bold(this.transientHint);
130592
- const hintWidth = visibleWidth(hintText);
130593
- const pad = Math.max(2, width - hintWidth - rightWidth);
130594
- line2 = hintText + " ".repeat(pad) + chalk.hex(colors.textDim)(rightText);
130595
- } else {
130596
- const lpad = Math.max(0, width - rightWidth);
130597
- line2 = " ".repeat(lpad) + chalk.hex(colors.textDim)(rightText);
130598
- }
130599
- return [truncateToWidth(line1, width), truncateToWidth(line2, width)];
130600
- }
130601
- };
130602
- //#endregion
130603
130873
  //#region src/tui/components/chrome/todo-panel.ts
130604
130874
  const MAX_VISIBLE = 5;
130605
130875
  /**
@@ -133963,7 +134233,7 @@ function createInitialAppState(input) {
133963
134233
  goalActive: false,
133964
134234
  goalContinuationCount: 0,
133965
134235
  ccConnectActive: false,
133966
- parallelMode: false
134236
+ wolfpackMode: false
133967
134237
  };
133968
134238
  }
133969
134239
  var ScreamTUI = class {
@@ -134380,9 +134650,7 @@ var ScreamTUI = class {
134380
134650
  imageAttachmentIds
134381
134651
  });
134382
134652
  this.beginSessionRequest();
134383
- let modelInput = options?.parts ?? input;
134384
- if (this.state.appState.parallelMode && options?.parts === void 0) modelInput = "[系统指令:Power 并行模式已激活。你必须将用户的任务拆解为多个独立的子任务,并在同一个回复中同时调用多个 Agent 工具并行执行。每个 Agent 处理一个子任务,系统自动并发。不要逐个串行调用 Agent——除非子任务之间有严格的顺序依赖或会写入同一文件。即使是简单任务,也至少拆成 2 个 Agent 并行。优先并行,宁可多拆不要少拆。]\n\n" + input;
134385
- session.prompt(modelInput).catch((error) => {
134653
+ session.prompt(options?.parts ?? input).catch((error) => {
134386
134654
  const message = formatErrorMessage(error);
134387
134655
  this.failSessionRequest(`发送失败:${message}`);
134388
134656
  });
@@ -134479,12 +134747,9 @@ var ScreamTUI = class {
134479
134747
  setAppState(patch) {
134480
134748
  if (!hasPatchChanges(this.state.appState, patch)) return;
134481
134749
  const busyChanged = "streamingPhase" in patch || "isCompacting" in patch;
134482
- const prevParallelMode = this.state.appState.parallelMode;
134483
134750
  Object.assign(this.state.appState, patch);
134484
134751
  if ("planMode" in patch) this.updateEditorBorderHighlight();
134485
134752
  if ("streamingPhase" in patch && patch.streamingPhase !== "idle") this.welcomeComponent?.stopBreathing();
134486
- if ("parallelMode" in patch && patch.parallelMode !== prevParallelMode) if (patch.parallelMode) this.state.footer.startPowerGradient();
134487
- else this.state.footer.stopPowerGradient();
134488
134753
  this.state.footer.setState(this.state.appState);
134489
134754
  this.updateActivityPane();
134490
134755
  if (busyChanged) this.updateQueueDisplay();
@@ -134627,6 +134892,7 @@ var ScreamTUI = class {
134627
134892
  renderWelcome() {
134628
134893
  this.welcomeComponent?.stopBreathing();
134629
134894
  const welcome = new WelcomeComponent(this.state.appState, this.state.theme.colors, this.state.ui);
134895
+ welcome.borderTitle = "Scream Code";
134630
134896
  this.welcomeComponent = welcome;
134631
134897
  if (this.state.editor.hasFirstInputFired()) welcome.stopBreathing();
134632
134898
  this.state.transcriptContainer.addChild(welcome);
@@ -134900,6 +135166,193 @@ var ScreamTUI = class {
134900
135166
  }
134901
135167
  };
134902
135168
  //#endregion
135169
+ //#region src/tui/components/chrome/loading.ts
135170
+ const { stdout } = process$1;
135171
+ const BRIGHT = "\x1B[38;2;255;255;255m";
135172
+ const THEME_GREEN = {
135173
+ dark: "\x1B[38;2;78;200;126m",
135174
+ light: "\x1B[38;2;14;122;56m"
135175
+ };
135176
+ const FRAMES = [
135177
+ {
135178
+ duration: 80,
135179
+ content: [
135180
+ "┌┐",
135181
+ "││",
135182
+ "││",
135183
+ "└┘"
135184
+ ]
135185
+ },
135186
+ {
135187
+ duration: 80,
135188
+ content: [
135189
+ "┌──────┐",
135190
+ "│ │",
135191
+ "│ │",
135192
+ "└──────┘"
135193
+ ]
135194
+ },
135195
+ {
135196
+ duration: 80,
135197
+ content: [
135198
+ "┌──────────────────┐",
135199
+ "│ │",
135200
+ "│ │",
135201
+ "└──────────────────┘"
135202
+ ]
135203
+ },
135204
+ {
135205
+ duration: 80,
135206
+ content: [
135207
+ "┌──────────────────────────────┐",
135208
+ "│ │",
135209
+ "│ welcome to scream code │",
135210
+ "│ │",
135211
+ "└──────────────────────────────┘"
135212
+ ]
135213
+ },
135214
+ {
135215
+ duration: 100,
135216
+ content: [
135217
+ "┌─────────────────────────────────────────────────────────────────┐",
135218
+ "│ │",
135219
+ "│ welcome to scream code │",
135220
+ "│ │",
135221
+ "│ ███████╗ ██████╗██████╗ ███████╗ █████╗ ███╗ ███╗ │",
135222
+ "│ ██╔════╝ ██╔════╝██╔══██╗██╔════╝██╔══██╗████╗ ████║ │",
135223
+ "│ ███████╗ ██║ ██████╔╝█████╗ ███████║██╔████╔██║ │",
135224
+ "│ ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║ │",
135225
+ "│ ███████║ ╚██████╗██║ ██║███████╗██║ ██║██║ ╚═╝ ██║ │",
135226
+ "│ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ │",
135227
+ "│ │",
135228
+ "└─────────────────────────────────────────────────────────────────┘"
135229
+ ]
135230
+ },
135231
+ {
135232
+ duration: 120,
135233
+ content: [
135234
+ "┌─────────────────────────────────────────────────────────────────┐",
135235
+ "│ │",
135236
+ "│ welcome to scream code │",
135237
+ "│ │",
135238
+ "│ ███████╗ ██████╗██████╗ ███████╗ █████╗ ███╗ ███╗ │",
135239
+ "│ ██╔════╝ ██╔════╝██╔══██╗██╔════╝██╔══██╗████╗ ████║ │",
135240
+ "│ ███████╗ ██║ ██████╔╝█████╗ ███████║██╔████╔██║ │",
135241
+ "│ ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║ │",
135242
+ "│ ███████║ ╚██████╗██║ ██║███████╗██║ ██║██║ ╚═╝ ██║ │",
135243
+ "│ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ │",
135244
+ "│ │",
135245
+ "│ 你的中文智能Ai助手 │",
135246
+ "└─────────────────────────────────────────────────────────────────┘"
135247
+ ]
135248
+ },
135249
+ {
135250
+ duration: 9999,
135251
+ content: [
135252
+ "┌─────────────────────────────────────────────────────────────────┐",
135253
+ "│ │",
135254
+ "│ welcome to scream code │",
135255
+ "│ │",
135256
+ "│ ███████╗ ██████╗██████╗ ███████╗ █████╗ ███╗ ███╗ │",
135257
+ "│ ██╔════╝ ██╔════╝██╔══██╗██╔════╝██╔══██╗████╗ ████║ │",
135258
+ "│ ███████╗ ██║ ██████╔╝█████╗ ███████║██╔████╔██║ │",
135259
+ "│ ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║ │",
135260
+ "│ ███████║ ╚██████╗██║ ██║███████╗██║ ██║██║ ╚═╝ ██║ │",
135261
+ "│ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ │",
135262
+ "│ │",
135263
+ "│ 你的中文智能Ai助手 │",
135264
+ "└─────────────────────────────────────────────────────────────────┘"
135265
+ ]
135266
+ }
135267
+ ];
135268
+ function color(line, green) {
135269
+ const s = line.replace(/[│ ]/g, "");
135270
+ const tb = (line.startsWith("┌") || line.startsWith("└")) && (line.endsWith("┐") || line.endsWith("┘")) && s.replace(/[─┌┐└┘]/g, "") === "";
135271
+ const es = line.startsWith("│") && line.endsWith("│") && s === "";
135272
+ if (tb || es) return green;
135273
+ if (line.includes("welcome") || line.includes("scream") || line.includes("你的中文")) return green;
135274
+ return BRIGHT;
135275
+ }
135276
+ let ansiSupported = null;
135277
+ function supportsAnsi() {
135278
+ if (ansiSupported !== null) return ansiSupported;
135279
+ if (!stdout.isTTY) {
135280
+ ansiSupported = false;
135281
+ return false;
135282
+ }
135283
+ if (process$1.env.NO_COLOR) {
135284
+ ansiSupported = false;
135285
+ return false;
135286
+ }
135287
+ if (process$1.env.FORCE_COLOR) {
135288
+ ansiSupported = true;
135289
+ return true;
135290
+ }
135291
+ if (process$1.platform === "win32") {
135292
+ const term = (process$1.env.TERM ?? "").toLowerCase();
135293
+ const session = (process$1.env.TERM_PROGRAM ?? "").toLowerCase();
135294
+ if (term.includes("xterm") || term.includes("vt100") || term.includes("256color")) {
135295
+ ansiSupported = true;
135296
+ return true;
135297
+ }
135298
+ if (session.includes("terminal") || session.includes("vscode")) {
135299
+ ansiSupported = true;
135300
+ return true;
135301
+ }
135302
+ if (process$1.env.CI) {
135303
+ ansiSupported = true;
135304
+ return true;
135305
+ }
135306
+ ansiSupported = true;
135307
+ return true;
135308
+ }
135309
+ if (process$1.env.TERM && process$1.env.TERM !== "dumb") {
135310
+ ansiSupported = true;
135311
+ return true;
135312
+ }
135313
+ ansiSupported = false;
135314
+ return false;
135315
+ }
135316
+ function plainFrame(frameIndex, green) {
135317
+ const f = FRAMES[Math.min(frameIndex, FRAMES.length - 1)];
135318
+ let out = "";
135319
+ for (const l of f.content) out += color(l, green) + (l || " ") + "\x1B[0m\n";
135320
+ return out;
135321
+ }
135322
+ function runLoadingAnimation(theme = "dark") {
135323
+ const green = THEME_GREEN[theme];
135324
+ if (!supportsAnsi()) {
135325
+ stdout.write(plainFrame(FRAMES.length - 1, green));
135326
+ stdout.write("\n\x1B[38;2;136;136;136m正在加载scream code....\x1B[0m\n");
135327
+ return Promise.resolve();
135328
+ }
135329
+ return new Promise((resolve) => {
135330
+ const last = FRAMES.length - 1;
135331
+ let frame = 0;
135332
+ function draw(i) {
135333
+ stdout.write("\x1B[H" + plainFrame(i, green));
135334
+ }
135335
+ function tick() {
135336
+ if (frame >= last) {
135337
+ setTimeout(() => {
135338
+ stdout.write("\n\x1B[38;2;136;136;136m正在加载scream code....\x1B[0m\n");
135339
+ setTimeout(() => {
135340
+ stdout.write("\x1B[2J\x1B[H\x1B[?25h");
135341
+ resolve();
135342
+ }, 400);
135343
+ }, 600);
135344
+ return;
135345
+ }
135346
+ draw(frame);
135347
+ frame++;
135348
+ setTimeout(tick, FRAMES[frame - 1].duration);
135349
+ }
135350
+ stdout.write("\x1B[?25l\x1B[2J\x1B[H");
135351
+ draw(0);
135352
+ setTimeout(tick, FRAMES[0].duration);
135353
+ });
135354
+ }
135355
+ //#endregion
134903
135356
  //#region src/cli/run-shell.ts
134904
135357
  async function runShell(opts, version) {
134905
135358
  const startedAt = Date.now();
@@ -134936,6 +135389,7 @@ async function runShell(opts, version) {
134936
135389
  await harness.ensureConfigFile();
134937
135390
  const config = await harness.getConfig();
134938
135391
  const configMs = Date.now() - configStartedAt;
135392
+ await runLoadingAnimation(resolvedTheme);
134939
135393
  const tui = new ScreamTUI(harness, {
134940
135394
  cliOptions: opts,
134941
135395
  tuiConfig,