scream-code 0.3.10 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.mjs +1481 -797
  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,
@@ -1035,6 +1042,16 @@ var APIContextOverflowError = class extends APIStatusError {
1035
1042
  }
1036
1043
  };
1037
1044
  /**
1045
+ * HTTP status error that specifically means the provider rate-limited the
1046
+ * request.
1047
+ */
1048
+ var APIProviderRateLimitError = class extends APIStatusError {
1049
+ constructor(message, requestId) {
1050
+ super(429, message, requestId);
1051
+ this.name = "APIProviderRateLimitError";
1052
+ }
1053
+ };
1054
+ /**
1038
1055
  * The API returned an empty response (no content, no tool calls).
1039
1056
  */
1040
1057
  var APIEmptyResponseError = class extends ChatProviderError {
@@ -1068,6 +1085,7 @@ function isContextOverflowErrorCode(code) {
1068
1085
  return code === "context_length_exceeded";
1069
1086
  }
1070
1087
  function normalizeAPIStatusError(statusCode, message, requestId) {
1088
+ if (statusCode === 429) return new APIProviderRateLimitError(message, requestId);
1071
1089
  if (isContextOverflowStatusError(statusCode, message)) return new APIContextOverflowError(statusCode, message, requestId);
1072
1090
  return new APIStatusError(statusCode, message, requestId);
1073
1091
  }
@@ -1077,6 +1095,94 @@ function isContextOverflowStatusError(statusCode, message) {
1077
1095
  return CONTEXT_OVERFLOW_MESSAGE_PATTERNS.some((pattern) => pattern.test(lowerMessage));
1078
1096
  }
1079
1097
  //#endregion
1098
+ //#region ../../packages/ltod/src/providers/tool-call-id.ts
1099
+ const EMPTY_TOOL_CALL_ID = "tool_call";
1100
+ const TOOL_CALL_ID_SAFE_CHARS = /[^a-zA-Z0-9_-]/g;
1101
+ function sanitizeToolCallId(id, maxLength) {
1102
+ const sanitized = id.replace(TOOL_CALL_ID_SAFE_CHARS, "_");
1103
+ return maxLength === void 0 ? sanitized : sanitized.slice(0, maxLength);
1104
+ }
1105
+ function sanitizeOpenAIResponsesCallId(id, maxLength) {
1106
+ const [callId] = id.split("|", 1);
1107
+ return sanitizeToolCallId(callId ?? id, maxLength);
1108
+ }
1109
+ function normalizeToolCallIdsForProvider(messages, policy) {
1110
+ const rawIds = collectToolCallIds(messages);
1111
+ if (rawIds.length === 0) return messages;
1112
+ const mappedIds = buildToolCallIdMap(rawIds, policy);
1113
+ let changed = false;
1114
+ const normalizedMessages = messages.map((message) => {
1115
+ let messageChanged = false;
1116
+ let toolCalls = message.toolCalls;
1117
+ if (message.toolCalls.length > 0) toolCalls = message.toolCalls.map((toolCall) => {
1118
+ const mappedId = mappedIds.get(toolCall.id);
1119
+ if (mappedId === void 0 || mappedId === toolCall.id) return toolCall;
1120
+ messageChanged = true;
1121
+ return {
1122
+ ...toolCall,
1123
+ id: mappedId
1124
+ };
1125
+ });
1126
+ const mappedToolCallId = (message.toolCallId === void 0 ? void 0 : mappedIds.get(message.toolCallId)) ?? message.toolCallId;
1127
+ if (mappedToolCallId !== message.toolCallId) messageChanged = true;
1128
+ if (!messageChanged) return message;
1129
+ changed = true;
1130
+ return {
1131
+ ...message,
1132
+ toolCalls,
1133
+ toolCallId: mappedToolCallId
1134
+ };
1135
+ });
1136
+ return changed ? normalizedMessages : messages;
1137
+ }
1138
+ function collectToolCallIds(messages) {
1139
+ const ids = [];
1140
+ const seen = /* @__PURE__ */ new Set();
1141
+ const append = (id) => {
1142
+ if (seen.has(id)) return;
1143
+ seen.add(id);
1144
+ ids.push(id);
1145
+ };
1146
+ for (const message of messages) {
1147
+ for (const toolCall of message.toolCalls) append(toolCall.id);
1148
+ if (message.toolCallId !== void 0) append(message.toolCallId);
1149
+ }
1150
+ return ids;
1151
+ }
1152
+ function buildToolCallIdMap(rawIds, policy) {
1153
+ const mappedIds = /* @__PURE__ */ new Map();
1154
+ const usedIds = /* @__PURE__ */ new Set();
1155
+ for (const rawId of rawIds) {
1156
+ const normalized = policy.normalize(rawId);
1157
+ if (normalized === rawId && normalized.length > 0) {
1158
+ mappedIds.set(rawId, normalized);
1159
+ usedIds.add(normalized);
1160
+ }
1161
+ }
1162
+ for (const rawId of rawIds) {
1163
+ if (mappedIds.has(rawId)) continue;
1164
+ const unique = makeUniqueToolCallId(policy.normalize(rawId), usedIds, policy.maxLength);
1165
+ mappedIds.set(rawId, unique);
1166
+ usedIds.add(unique);
1167
+ }
1168
+ return mappedIds;
1169
+ }
1170
+ function makeUniqueToolCallId(normalized, usedIds, maxLength) {
1171
+ const base = normalized.length > 0 ? normalized : EMPTY_TOOL_CALL_ID;
1172
+ const candidate = truncateToolCallId(base, maxLength, "");
1173
+ if (!usedIds.has(candidate)) return candidate;
1174
+ for (let i = 2;; i++) {
1175
+ const suffixed = truncateToolCallId(base, maxLength, `_${i}`);
1176
+ if (!usedIds.has(suffixed)) return suffixed;
1177
+ }
1178
+ }
1179
+ function truncateToolCallId(base, maxLength, suffix) {
1180
+ if (maxLength === void 0) return `${base}${suffix}`;
1181
+ const baseLength = maxLength - suffix.length;
1182
+ if (baseLength <= 0) throw new Error(`Tool call id maxLength ${maxLength} is too small for suffix ${suffix}.`);
1183
+ return `${base.slice(0, baseLength)}${suffix}`;
1184
+ }
1185
+ //#endregion
1080
1186
  //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.95.2_zod@4.4.3/node_modules/@anthropic-ai/sdk/internal/tslib.mjs
1081
1187
  function __classPrivateFieldSet$1(receiver, state, value, kind, f) {
1082
1188
  if (kind === "m") throw new TypeError("Private method is not writable");
@@ -8939,6 +9045,10 @@ function budgetTokensForEffort(effort) {
8939
9045
  }
8940
9046
  throw new Error(`Unknown thinking effort: ${String(effort)}`);
8941
9047
  }
9048
+ const ANTHROPIC_TOOL_CALL_ID_POLICY = {
9049
+ normalize: (id) => sanitizeToolCallId(id, 64),
9050
+ maxLength: 64
9051
+ };
8942
9052
  const CACHE_CONTROL = { type: "ephemeral" };
8943
9053
  /**
8944
9054
  * Content block types that support cache_control injection.
@@ -9340,8 +9450,9 @@ var AnthropicChatProvider = class {
9340
9450
  text: systemPrompt,
9341
9451
  cache_control: CACHE_CONTROL
9342
9452
  }] : void 0;
9453
+ const normalizedHistory = normalizeToolCallIdsForProvider(history, ANTHROPIC_TOOL_CALL_ID_POLICY);
9343
9454
  const messages = [];
9344
- for (const msg of history) {
9455
+ for (const msg of normalizedHistory) {
9345
9456
  const converted = convertMessage$3(msg);
9346
9457
  const last = messages.at(-1);
9347
9458
  if (last !== void 0 && isToolResultOnly(last) && isToolResultOnly(converted)) last.content = [...last.content, ...converted.content];
@@ -48877,6 +48988,10 @@ const KNOWN_REASONING_KEYS = [
48877
48988
  "reasoning"
48878
48989
  ];
48879
48990
  const DEFAULT_OUTBOUND_REASONING_KEY = KNOWN_REASONING_KEYS[0];
48991
+ const OPENAI_CHAT_TOOL_CALL_ID_POLICY = {
48992
+ normalize: (id) => sanitizeToolCallId(id, 64),
48993
+ maxLength: 64
48994
+ };
48880
48995
  function extractReasoningContent(source, explicitKey) {
48881
48996
  if (typeof source !== "object" || source === null) return void 0;
48882
48997
  const record = source;
@@ -49053,7 +49168,8 @@ var OpenAILegacyChatProvider = class {
49053
49168
  role: "system",
49054
49169
  content: systemPrompt
49055
49170
  });
49056
- for (const msg of history) messages.push(convertMessage$1(msg, this._reasoningKey, this._toolMessageConversion));
49171
+ const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_CHAT_TOOL_CALL_ID_POLICY);
49172
+ for (const msg of normalizedHistory) messages.push(convertMessage$1(msg, this._reasoningKey, this._toolMessageConversion));
49057
49173
  const kwargs = { ...this._generationKwargs };
49058
49174
  let reasoningEffort = this._reasoningEffort;
49059
49175
  if (reasoningEffort === void 0 && kwargs["reasoning_effort"] === void 0) {
@@ -49154,6 +49270,10 @@ function normalizeResponsesFinishReason(status, incompleteReason) {
49154
49270
  rawFinishReason: null
49155
49271
  };
49156
49272
  }
49273
+ const OPENAI_RESPONSES_TOOL_CALL_ID_POLICY = {
49274
+ normalize: (id) => sanitizeOpenAIResponsesCallId(id, 64),
49275
+ maxLength: 64
49276
+ };
49157
49277
  function asRawObject(value) {
49158
49278
  if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
49159
49279
  return value;
@@ -49678,7 +49798,8 @@ var OpenAIResponsesChatProvider = class {
49678
49798
  if (usesOpenAIResponsesDeveloperRole(this._model)) sysItem["role"] = "developer";
49679
49799
  input.push(sysItem);
49680
49800
  }
49681
- for (const msg of history) input.push(...convertMessage(msg, this._model, this._toolMessageConversion));
49801
+ const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_RESPONSES_TOOL_CALL_ID_POLICY);
49802
+ for (const msg of normalizedHistory) input.push(...convertMessage(msg, this._model, this._toolMessageConversion));
49682
49803
  const kwargs = { ...this._generationKwargs };
49683
49804
  const reasoningEffort = kwargs["reasoning_effort"];
49684
49805
  delete kwargs["reasoning_effort"];
@@ -64622,6 +64743,141 @@ function escapeXml(input) {
64622
64743
  return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
64623
64744
  }
64624
64745
  //#endregion
64746
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/wolfpack.md
64747
+ 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";
64748
+ //#endregion
64749
+ //#region ../../packages/agent-core/src/tools/builtin/collaboration/wolfpack.ts
64750
+ /**
64751
+ * WolfPackTool — batch parallel subagent execution.
64752
+ *
64753
+ * Spawns multiple subagents in parallel using a template + items pattern.
64754
+ * Each item gets its own subagent; results are batched together.
64755
+ * V1 uses Promise.allSettled — no concurrency control or rate-limit handling.
64756
+ */
64757
+ const MAX_ITEMS = 20;
64758
+ const WolfPackToolInputSchema = z.object({
64759
+ description: z.string().min(1).describe("Short task description (3-5 words, e.g., \"Security review all files\")"),
64760
+ subagent_type: z.string().default("coder").describe("Subagent type for all spawned agents (e.g., coder, explore, verify)"),
64761
+ prompt_template: z.string().min(1).describe("Prompt template with {{item}} placeholder. Each item is substituted in."),
64762
+ items: z.array(z.string().min(1)).min(1).max(MAX_ITEMS).describe("Array of items to process. Each item gets its own subagent.")
64763
+ });
64764
+ var WolfPackTool = class {
64765
+ subagentHost;
64766
+ isEnabled;
64767
+ name = "WolfPack";
64768
+ description = wolfpack_default;
64769
+ parameters = toInputJsonSchema(WolfPackToolInputSchema);
64770
+ constructor(subagentHost, isEnabled, _options) {
64771
+ this.subagentHost = subagentHost;
64772
+ this.isEnabled = isEnabled;
64773
+ }
64774
+ resolveExecution(args) {
64775
+ return {
64776
+ description: `WolfPack: ${args.description} (${args.items.length} agents)`,
64777
+ accesses: ToolAccesses.none(),
64778
+ display: {
64779
+ kind: "generic",
64780
+ summary: `WolfPack: ${args.description}`,
64781
+ detail: {
64782
+ itemCount: args.items.length,
64783
+ subagent_type: args.subagent_type
64784
+ }
64785
+ },
64786
+ approvalRule: this.name,
64787
+ execute: (ctx) => this.execution(args, ctx)
64788
+ };
64789
+ }
64790
+ async execution(args, ctx) {
64791
+ ctx.signal.throwIfAborted();
64792
+ if (!this.isEnabled()) return {
64793
+ output: "WolfPack 模式未开启。请输入 /wolfpack 打开后再试。",
64794
+ isError: true
64795
+ };
64796
+ if (args.items.length > MAX_ITEMS) return {
64797
+ output: `WolfPack max ${MAX_ITEMS} items. Got ${args.items.length}.`,
64798
+ isError: true
64799
+ };
64800
+ const profileName = args.subagent_type ?? "coder";
64801
+ const template = args.prompt_template;
64802
+ const handlePromises = args.items.map(async (item) => {
64803
+ ctx.signal.throwIfAborted();
64804
+ const prompt = template.replace(/\{\{item\}\}/g, item);
64805
+ return {
64806
+ item,
64807
+ handle: await this.subagentHost.spawn(profileName, {
64808
+ parentToolCallId: ctx.toolCallId,
64809
+ prompt,
64810
+ description: `${args.description}: ${item}`,
64811
+ runInBackground: false,
64812
+ signal: ctx.signal
64813
+ })
64814
+ };
64815
+ });
64816
+ const completionPromises = (await Promise.allSettled(handlePromises)).map(async (settled) => {
64817
+ if (settled.status === "rejected") return {
64818
+ item: "unknown",
64819
+ result: `Spawn failed: ${settled.reason instanceof Error ? settled.reason.message : String(settled.reason)}`,
64820
+ success: false
64821
+ };
64822
+ const { item, handle } = settled.value;
64823
+ try {
64824
+ return {
64825
+ item,
64826
+ result: (await handle.completion).result,
64827
+ success: true,
64828
+ agentId: handle.agentId
64829
+ };
64830
+ } catch (error) {
64831
+ let message;
64832
+ if (isAbortError$3(error)) message = "The subagent was stopped before it finished.";
64833
+ else message = error instanceof Error ? error.message : String(error);
64834
+ return {
64835
+ item,
64836
+ result: message,
64837
+ success: false,
64838
+ agentId: handle.agentId
64839
+ };
64840
+ }
64841
+ });
64842
+ const completions = await Promise.allSettled(completionPromises);
64843
+ let successCount = 0;
64844
+ let failureCount = 0;
64845
+ const lines = [];
64846
+ for (const settled of completions) {
64847
+ if (settled.status === "fulfilled") {
64848
+ const { item, result: _result, success, agentId } = settled.value;
64849
+ if (success) {
64850
+ successCount++;
64851
+ lines.push(`### ${item} (OK)`);
64852
+ } else {
64853
+ failureCount++;
64854
+ lines.push(`### ${item} (FAILED)`);
64855
+ }
64856
+ if (agentId !== void 0) lines.push(`agent_id: ${agentId}`);
64857
+ } else {
64858
+ failureCount++;
64859
+ const msg = settled.reason instanceof Error ? settled.reason.message : String(settled.reason);
64860
+ lines.push(`### error: ${msg}`);
64861
+ }
64862
+ lines.push("");
64863
+ }
64864
+ const summary = `Success: ${successCount}, Failed: ${failureCount}, Total: ${completions.length}`;
64865
+ if (failureCount > 0 && successCount === 0) return {
64866
+ output: [
64867
+ summary,
64868
+ "",
64869
+ ...lines
64870
+ ].join("\n"),
64871
+ isError: true
64872
+ };
64873
+ return { output: [
64874
+ summary,
64875
+ "",
64876
+ ...lines
64877
+ ].join("\n") };
64878
+ }
64879
+ };
64880
+ //#endregion
64625
64881
  //#region ../../packages/agent-core/src/tools/builtin/file/line-endings.ts
64626
64882
  function detectLineEndingStyle(text) {
64627
64883
  let hasCrLf = false;
@@ -72090,9 +72346,11 @@ function maybeStatusCode(error) {
72090
72346
  //#endregion
72091
72347
  //#region ../../packages/agent-core/src/agent/context/projector.ts
72092
72348
  function project(history) {
72093
- return mergeAdjacentUserMessages(history.filter((message) => {
72349
+ const usable = history.filter((message) => {
72094
72350
  return message.partial !== true && !(message.role === "assistant" && message.content.length === 0 && message.toolCalls.length === 0);
72095
- }));
72351
+ });
72352
+ const last = usable.at(-1);
72353
+ return mergeAdjacentUserMessages(last?.role === "assistant" && last.toolCalls.length > 0 ? usable.slice(0, -1) : usable);
72096
72354
  }
72097
72355
  function mergeAdjacentUserMessages(history) {
72098
72356
  const out = [];
@@ -75385,6 +75643,46 @@ function isTodoStatus(value) {
75385
75643
  return value === "pending" || value === "in_progress" || value === "done";
75386
75644
  }
75387
75645
  //#endregion
75646
+ //#region ../../packages/agent-core/src/agent/injection/wolfpack.ts
75647
+ const WOLFPACK_MODE_ENTER_REMINDER = [
75648
+ "WolfPack mode is active. Prefer using the WolfPack tool for batch parallel execution.",
75649
+ "",
75650
+ "When to use: When the user's task involves performing the same operation across",
75651
+ "multiple independent items (files, directories, data entries, searches).",
75652
+ "",
75653
+ "How to use:",
75654
+ " WolfPack(",
75655
+ " description=\"Short task description (3-5 words)\",",
75656
+ " subagent_type=\"coder\",",
75657
+ " prompt_template=\"Review {{item}} for security issues\",",
75658
+ " items=[\"src/auth.ts\", \"src/api.ts\", \"src/config.ts\"]",
75659
+ " )",
75660
+ "",
75661
+ "This spawns one subagent per item in parallel. Results are batched and returned together.",
75662
+ "Items must be independent — do not use WolfPack when one item depends on another's output.",
75663
+ "Max 20 items per call."
75664
+ ].join("\n");
75665
+ const WOLFPACK_MODE_EXIT_REMINDER = "WolfPack mode is no longer active. The WolfPack tool for batch parallel execution is no longer available.";
75666
+ var WolfPackModeInjector = class extends DynamicInjector {
75667
+ injectionVariant = "wolfpack";
75668
+ wasActive = false;
75669
+ getInjection() {
75670
+ if (!this.agent.wolfpackMode.isActive) {
75671
+ if (this.wasActive) {
75672
+ this.wasActive = false;
75673
+ this.injectedAt = null;
75674
+ return WOLFPACK_MODE_EXIT_REMINDER;
75675
+ }
75676
+ return;
75677
+ }
75678
+ if (!this.wasActive) {
75679
+ this.injectedAt = null;
75680
+ this.wasActive = true;
75681
+ return WOLFPACK_MODE_ENTER_REMINDER;
75682
+ }
75683
+ }
75684
+ };
75685
+ //#endregion
75388
75686
  //#region ../../packages/agent-core/src/agent/injection/manager.ts
75389
75687
  var InjectionManager = class {
75390
75688
  agent;
@@ -75396,6 +75694,7 @@ var InjectionManager = class {
75396
75694
  this.memoryRecall = autoRecallEnabled ? new MemoryRecallInjector(agent) : null;
75397
75695
  this.injectors = [
75398
75696
  new PluginSessionStartInjector(agent),
75697
+ new WolfPackModeInjector(agent),
75399
75698
  new PlanModeInjector(agent),
75400
75699
  new PermissionModeInjector(agent),
75401
75700
  new TodoListReminderInjector(agent),
@@ -75473,7 +75772,8 @@ const DEFAULT_APPROVE_TOOLS = new Set([
75473
75772
  "FetchURL",
75474
75773
  "Agent",
75475
75774
  "AskUserQuestion",
75476
- "Skill"
75775
+ "Skill",
75776
+ "WolfPack"
75477
75777
  ]);
75478
75778
  var DefaultToolApprovePermissionPolicy = class {
75479
75779
  name = "default-tool-approve";
@@ -76052,6 +76352,19 @@ var YoloModeApprovePermissionPolicy = class {
76052
76352
  }
76053
76353
  };
76054
76354
  //#endregion
76355
+ //#region ../../packages/agent-core/src/agent/permission/policies/wolfpack-mode-approve.ts
76356
+ var WolfPackModeApprovePermissionPolicy = class {
76357
+ agent;
76358
+ name = "wolfpack-mode-approve";
76359
+ constructor(agent) {
76360
+ this.agent = agent;
76361
+ }
76362
+ evaluate() {
76363
+ if (!this.agent.wolfpackMode?.isActive) return;
76364
+ return { kind: "approve" };
76365
+ }
76366
+ };
76367
+ //#endregion
76055
76368
  //#region ../../packages/agent-core/src/agent/permission/policies/index.ts
76056
76369
  /** Permission policies run in order; the first non-undefined result wins. */
76057
76370
  function createPermissionDecisionPolicies(agent) {
@@ -76070,6 +76383,7 @@ function createPermissionDecisionPolicies(agent) {
76070
76383
  new GitControlPathAccessAskPermissionPolicy(agent),
76071
76384
  new CwdOutsideFileWriteAskPermissionPolicy(agent),
76072
76385
  new YoloModeApprovePermissionPolicy(agent),
76386
+ new WolfPackModeApprovePermissionPolicy(agent),
76073
76387
  new DefaultToolApprovePermissionPolicy(),
76074
76388
  new GitCwdWriteApprovePermissionPolicy(agent),
76075
76389
  new FallbackAskPermissionPolicy()
@@ -76684,6 +76998,33 @@ function isMissingFileError(error) {
76684
76998
  return error.code === "ENOENT";
76685
76999
  }
76686
77000
  //#endregion
77001
+ //#region ../../packages/agent-core/src/agent/wolfpack/index.ts
77002
+ var WolfPackMode = class {
77003
+ agent;
77004
+ _isActive = false;
77005
+ constructor(agent) {
77006
+ this.agent = agent;
77007
+ }
77008
+ enter() {
77009
+ if (this._isActive) return;
77010
+ this._isActive = true;
77011
+ this.agent.records.logRecord({ type: "wolfpack.enter" });
77012
+ this.agent.emitStatusUpdated();
77013
+ }
77014
+ restoreEnter() {
77015
+ this._isActive = true;
77016
+ }
77017
+ exit() {
77018
+ if (!this._isActive) return;
77019
+ this.agent.records.logRecord({ type: "wolfpack.exit" });
77020
+ this._isActive = false;
77021
+ this.agent.emitStatusUpdated();
77022
+ }
77023
+ get isActive() {
77024
+ return this._isActive;
77025
+ }
77026
+ };
77027
+ //#endregion
76687
77028
  //#region ../../packages/agent-core/src/agent/session-memory.ts
76688
77029
  const MAX_EVENTS = 50;
76689
77030
  const MAX_SUMMARY_LENGTH = 1500;
@@ -77208,6 +77549,12 @@ function restoreAgentRecord(agent, input) {
77208
77549
  case "plan_mode.exit":
77209
77550
  agent.planMode.exit(input.id);
77210
77551
  return;
77552
+ case "wolfpack.enter":
77553
+ agent.wolfpackMode.restoreEnter();
77554
+ return;
77555
+ case "wolfpack.exit":
77556
+ agent.wolfpackMode.exit();
77557
+ return;
77211
77558
  case "context.append_message":
77212
77559
  agent.context.appendMessage(input.message);
77213
77560
  return;
@@ -85268,7 +85615,7 @@ async function finalizePendingToolResult(step, pendingResult) {
85268
85615
  async function executeTool(step, execution, toolCall, toolName, metadata) {
85269
85616
  const { dispatchEvent, signal, turnId } = step;
85270
85617
  signal.throwIfAborted();
85271
- return raceExecuteWithGraceAndHardTimeout(execution.execute({
85618
+ return raceExecuteWithGraceTimeout(execution.execute({
85272
85619
  turnId,
85273
85620
  toolCallId: toolCall.id,
85274
85621
  metadata,
@@ -85283,10 +85630,8 @@ async function executeTool(step, execution, toolCall, toolName, metadata) {
85283
85630
  }
85284
85631
  }), signal, toolName);
85285
85632
  }
85286
- const HARD_TIMEOUT_MS = 12e5;
85287
- async function raceExecuteWithGraceAndHardTimeout(executePromise, signal, toolName) {
85633
+ async function raceExecuteWithGraceTimeout(executePromise, signal, toolName) {
85288
85634
  let graceTimer;
85289
- let hardTimer;
85290
85635
  let onAbort;
85291
85636
  const graceSentinel = new Promise((resolve) => {
85292
85637
  const armTimer = () => {
@@ -85303,23 +85648,10 @@ async function raceExecuteWithGraceAndHardTimeout(executePromise, signal, toolNa
85303
85648
  signal.addEventListener("abort", onAbort, { once: true });
85304
85649
  }
85305
85650
  });
85306
- const hardSentinel = new Promise((resolve) => {
85307
- hardTimer = setTimeout(() => {
85308
- resolve({
85309
- output: `Tool "${toolName}" exceeded hard timeout (${String(HARD_TIMEOUT_MS)}ms)`,
85310
- isError: true
85311
- });
85312
- }, HARD_TIMEOUT_MS);
85313
- });
85314
85651
  try {
85315
- return await Promise.race([
85316
- executePromise,
85317
- graceSentinel,
85318
- hardSentinel
85319
- ]);
85652
+ return await Promise.race([executePromise, graceSentinel]);
85320
85653
  } finally {
85321
85654
  if (graceTimer !== void 0) clearTimeout(graceTimer);
85322
- if (hardTimer !== void 0) clearTimeout(hardTimer);
85323
85655
  if (onAbort !== void 0) try {
85324
85656
  signal.removeEventListener("abort", onAbort);
85325
85657
  } catch {}
@@ -89779,7 +90111,7 @@ function normalizeSourcePath(path) {
89779
90111
  }
89780
90112
  //#endregion
89781
90113
  //#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";
90114
+ 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
90115
  //#endregion
89784
90116
  //#region ../../packages/agent-core/src/profile/default/coder.yaml
89785
90117
  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 +90425,7 @@ var ToolManager = class {
90093
90425
  allowBackground,
90094
90426
  log: this.agent.log
90095
90427
  }),
90428
+ this.agent.subagentHost && new WolfPackTool(this.agent.subagentHost, () => this.agent.wolfpackMode.isActive, { log: this.agent.log }),
90096
90429
  toolServices?.webSearcher && new WebSearchTool(toolServices.webSearcher),
90097
90430
  toolServices?.urlFetcher && new FetchURLTool(toolServices.urlFetcher)
90098
90431
  ].filter((tool) => !!tool).map((tool) => [tool.name, tool]));
@@ -91279,6 +91612,7 @@ var Agent = class {
91279
91612
  injection;
91280
91613
  permission;
91281
91614
  planMode;
91615
+ wolfpackMode;
91282
91616
  usage;
91283
91617
  skills;
91284
91618
  tools;
@@ -91317,8 +91651,9 @@ var Agent = class {
91317
91651
  this.config = new ConfigState(this);
91318
91652
  this.turn = new TurnFlow(this);
91319
91653
  this.injection = new InjectionManager(this);
91320
- this.permission = new PermissionManager(this, options.permission);
91321
91654
  this.planMode = new PlanMode(this);
91655
+ this.wolfpackMode = new WolfPackMode(this);
91656
+ this.permission = new PermissionManager(this, options.permission);
91322
91657
  this.usage = new UsageRecorder(this);
91323
91658
  this.skills = options.skills ? new SkillManager(this, options.skills) : null;
91324
91659
  this.tools = new ToolManager(this);
@@ -91454,6 +91789,12 @@ var Agent = class {
91454
91789
  enterPlan: async () => {
91455
91790
  await this.planMode.enter();
91456
91791
  },
91792
+ enterWolfpack: () => {
91793
+ this.wolfpackMode.enter();
91794
+ },
91795
+ exitWolfpack: () => {
91796
+ this.wolfpackMode.exit();
91797
+ },
91457
91798
  cancelPlan: (payload) => {
91458
91799
  this.planMode.cancel(payload.id);
91459
91800
  },
@@ -96871,6 +97212,16 @@ async function parseManifest(pluginRoot) {
96871
97212
  manifestPath: skillMdPath,
96872
97213
  diagnostics: []
96873
97214
  };
97215
+ const discoveredSkillDirs = await discoverSkillDirs(pluginRoot, 3);
97216
+ if (discoveredSkillDirs.length > 0) return {
97217
+ manifest: {
97218
+ name: path.basename(pluginRoot),
97219
+ skills: discoveredSkillDirs
97220
+ },
97221
+ manifestKind: "bare-skill",
97222
+ manifestPath: path.join(discoveredSkillDirs[0], BARE_SKILL_PATH),
97223
+ diagnostics: []
97224
+ };
96874
97225
  return { diagnostics: [{
96875
97226
  severity: "error",
96876
97227
  message: `No manifest at ${SCREAM_PLUGIN_ROOT_PATH}, ${SCREAM_PLUGIN_DIR_PATH}, or ${CLAUDE_PLUGIN_DIR_PATH}`
@@ -97169,6 +97520,34 @@ function isWithin$1(child, parent) {
97169
97520
  const relative = path.relative(parent, child);
97170
97521
  return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
97171
97522
  }
97523
+ /**
97524
+ * Recursively scan for `SKILL.md` files up to `maxDepth` levels below `root`.
97525
+ * Returns the unique parent directories, deduplicated so that a nested skill
97526
+ * dir is not listed if its ancestor is already a skill root.
97527
+ */
97528
+ async function discoverSkillDirs(root, maxDepth) {
97529
+ const found = [];
97530
+ async function walk(dir, depth) {
97531
+ if (depth > maxDepth) return;
97532
+ let entries;
97533
+ try {
97534
+ entries = await readdir(dir, { withFileTypes: true });
97535
+ } catch {
97536
+ return;
97537
+ }
97538
+ for (const entry of entries) {
97539
+ if (!entry.isDirectory()) continue;
97540
+ const child = path.join(dir, entry.name);
97541
+ if (await isFile$1(path.join(child, BARE_SKILL_PATH))) found.push(child);
97542
+ await walk(child, depth + 1);
97543
+ }
97544
+ }
97545
+ await walk(root, 1);
97546
+ const sorted = found.sort((a, b) => a.length - b.length);
97547
+ const result = [];
97548
+ for (const dir of sorted) if (!result.some((parent) => dir.startsWith(parent + path.sep))) result.push(dir);
97549
+ return result;
97550
+ }
97172
97551
  async function isFile$1(p) {
97173
97552
  try {
97174
97553
  return (await stat(p)).isFile();
@@ -111077,6 +111456,11 @@ const FLAG_DEFINITIONS = [{
111077
111456
  env: "SCREAM_CODE_EXPERIMENTAL_MICRO_COMPACTION",
111078
111457
  default: false,
111079
111458
  surface: "both"
111459
+ }, {
111460
+ id: "wolfpack",
111461
+ env: "SCREAM_CODE_EXPERIMENTAL_WOLFPACK",
111462
+ default: false,
111463
+ surface: "both"
111080
111464
  }];
111081
111465
  /**
111082
111466
  * Pure, synchronous flag resolver. State comes entirely from (env, registry) and nothing is
@@ -112453,6 +112837,12 @@ var SessionAPIImpl = class {
112453
112837
  clearPlan({ agentId, ...payload }) {
112454
112838
  return this.getAgent(agentId).clearPlan(payload);
112455
112839
  }
112840
+ enterWolfpack({ agentId, ...payload }) {
112841
+ return this.getAgent(agentId).enterWolfpack(payload);
112842
+ }
112843
+ exitWolfpack({ agentId, ...payload }) {
112844
+ return this.getAgent(agentId).exitWolfpack(payload);
112845
+ }
112456
112846
  beginCompaction({ agentId, ...payload }) {
112457
112847
  return this.getAgent(agentId).beginCompaction(payload);
112458
112848
  }
@@ -112702,12 +113092,15 @@ var SessionStore = class {
112702
113092
  async create(input) {
112703
113093
  assertSafeSessionId(input.id);
112704
113094
  const workDir = normalizeWorkDir(input.workDir);
112705
- if (await this.findSessionEntry(input.id) !== void 0) throw new ScreamError(ErrorCodes.SESSION_ALREADY_EXISTS, `Session "${input.id}" already exists`);
112706
113095
  const dir = this.sessionDirFor({
112707
113096
  id: input.id,
112708
113097
  workDir
112709
113098
  });
112710
- if (await isDirectory(dir)) throw new ScreamError(ErrorCodes.SESSION_ALREADY_EXISTS, `Session "${input.id}" already exists`);
113099
+ if (await this.findSessionEntry(input.id) !== void 0) await this.delete(input.id);
113100
+ else if (await isDirectory(dir)) await rm(dir, {
113101
+ recursive: true,
113102
+ force: true
113103
+ });
112711
113104
  await mkdir(dir, {
112712
113105
  recursive: true,
112713
113106
  mode: 448
@@ -112778,11 +113171,12 @@ var SessionStore = class {
112778
113171
  }
112779
113172
  async delete(id) {
112780
113173
  assertSafeSessionId(id);
112781
- await rm((await this.findExistingSessionEntry(id)).sessionDir, {
113174
+ const entry = await this.findSessionEntry(id);
113175
+ if (entry !== void 0) await rm(entry.sessionDir, {
112782
113176
  recursive: true,
112783
113177
  force: true
112784
- });
112785
- await removeSessionIndexEntry(this.homeDir, id);
113178
+ }).catch(() => {});
113179
+ await removeSessionIndexEntry(this.homeDir, id).catch(() => {});
112786
113180
  }
112787
113181
  async list(options = {}) {
112788
113182
  const workDir = options.workDir === void 0 ? void 0 : normalizeRequiredWorkDir(options.workDir);
@@ -114079,6 +114473,12 @@ var ScreamCore = class {
114079
114473
  clearPlan({ sessionId, ...payload }) {
114080
114474
  return this.sessionApi(sessionId).clearPlan(payload);
114081
114475
  }
114476
+ enterWolfpack({ sessionId, ...payload }) {
114477
+ return this.sessionApi(sessionId).enterWolfpack(payload);
114478
+ }
114479
+ exitWolfpack({ sessionId, ...payload }) {
114480
+ return this.sessionApi(sessionId).exitWolfpack(payload);
114481
+ }
114082
114482
  beginCompaction({ sessionId, ...payload }) {
114083
114483
  return this.sessionApi(sessionId).beginCompaction(payload);
114084
114484
  }
@@ -114657,6 +115057,17 @@ var SDKRpcClient = class {
114657
115057
  agentId: this.interactiveAgentId
114658
115058
  });
114659
115059
  }
115060
+ async setWolfpackMode(input) {
115061
+ const rpc = await this.getRpc();
115062
+ if (!input.enabled) return rpc.exitWolfpack({
115063
+ sessionId: input.sessionId,
115064
+ agentId: this.interactiveAgentId
115065
+ });
115066
+ return rpc.enterWolfpack({
115067
+ sessionId: input.sessionId,
115068
+ agentId: this.interactiveAgentId
115069
+ });
115070
+ }
114660
115071
  async getPlan(input) {
114661
115072
  return (await this.getRpc()).getPlan({
114662
115073
  sessionId: input.sessionId,
@@ -115053,6 +115464,14 @@ var Session = class {
115053
115464
  enabled
115054
115465
  });
115055
115466
  }
115467
+ async setWolfpackMode(enabled) {
115468
+ this.ensureOpen();
115469
+ if (typeof enabled !== "boolean") throw new ScreamError(ErrorCodes.SESSION_WOLFPACK_MODE_INVALID, "Session wolfpack mode must be a boolean");
115470
+ await this.rpc.setWolfpackMode({
115471
+ sessionId: this.id,
115472
+ enabled
115473
+ });
115474
+ }
115056
115475
  async getPlan() {
115057
115476
  this.ensureOpen();
115058
115477
  return this.rpc.getPlan({ sessionId: this.id });
@@ -117603,75 +118022,82 @@ const BUILTIN_SLASH_COMMANDS = [
117603
118022
  availability: "always"
117604
118023
  },
117605
118024
  {
117606
- name: "power",
117607
- aliases: ["parallel"],
117608
- description: "切换至Agent并发模式",
118025
+ name: "yes",
118026
+ aliases: ["yolo"],
118027
+ description: "切换至自动批准模式(yolo)",
117609
118028
  priority: 124,
117610
118029
  availability: "always"
117611
118030
  },
117612
118031
  {
117613
- name: "yes",
117614
- aliases: ["yolo"],
117615
- description: "切换至自动批准模式(yolo)",
118032
+ name: "wolfpack",
118033
+ aliases: ["wp"],
118034
+ description: "切换群狼协作模式,自动批准+批量并发",
117616
118035
  priority: 123,
117617
118036
  availability: "always"
117618
118037
  },
117619
118038
  {
117620
- name: "model",
117621
- aliases: [],
117622
- description: "切换 LLM 模型",
118039
+ name: "sessions",
118040
+ aliases: ["resume"],
118041
+ description: "浏览并恢复会话",
117623
118042
  priority: 122
117624
118043
  },
117625
- {
117626
- name: "new",
117627
- aliases: ["clear"],
117628
- description: "在当前工作区开启新会话",
117629
- priority: 121
117630
- },
117631
118044
  {
117632
118045
  name: "memory",
117633
118046
  aliases: ["memo", "mem"],
117634
118047
  description: "浏览、搜索、注入记忆备忘录",
117635
- priority: 120,
118048
+ priority: 121,
117636
118049
  availability: "always"
117637
118050
  },
117638
118051
  {
117639
- name: "help",
117640
- aliases: ["h", "?"],
117641
- description: "显示可用命令和快捷键",
117642
- priority: 119,
117643
- availability: "always"
118052
+ name: "new",
118053
+ aliases: ["clear"],
118054
+ description: "在当前工作区开启新会话",
118055
+ priority: 121
118056
+ },
118057
+ {
118058
+ name: "model",
118059
+ aliases: [],
118060
+ description: "切换 LLM 模型",
118061
+ priority: 120
117644
118062
  },
117645
118063
  {
117646
118064
  name: "compact",
117647
118065
  aliases: [],
117648
118066
  description: "压缩对话上下文",
117649
- priority: 118
118067
+ priority: 119
117650
118068
  },
117651
118069
  {
117652
118070
  name: "plan",
117653
118071
  aliases: [],
117654
118072
  description: "切换计划模式",
117655
- priority: 117,
118073
+ priority: 118,
117656
118074
  availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
117657
118075
  },
117658
- {
117659
- name: "sessions",
117660
- aliases: ["resume"],
117661
- description: "浏览并恢复会话",
117662
- priority: 116
117663
- },
117664
118076
  {
117665
118077
  name: "tasks",
117666
118078
  aliases: ["task"],
117667
118079
  description: "浏览后台任务",
118080
+ priority: 117,
118081
+ availability: "always"
118082
+ },
118083
+ {
118084
+ name: "help",
118085
+ aliases: ["h", "?"],
118086
+ description: "显示可用命令和快捷键",
118087
+ priority: 116,
118088
+ availability: "always"
118089
+ },
118090
+ {
118091
+ name: "status",
118092
+ aliases: [],
118093
+ description: "显示当前会话和运行时状态",
117668
118094
  priority: 115,
117669
118095
  availability: "always"
117670
118096
  },
117671
118097
  {
117672
- name: "mcp",
118098
+ name: "usage",
117673
118099
  aliases: [],
117674
- description: "管理 MCP 服务器(安装/停用/卸载)",
118100
+ description: "显示 token 用量和上下文窗口",
117675
118101
  priority: 114,
117676
118102
  availability: "always"
117677
118103
  },
@@ -117683,58 +118109,45 @@ const BUILTIN_SLASH_COMMANDS = [
117683
118109
  availability: "always"
117684
118110
  },
117685
118111
  {
117686
- name: "theme",
118112
+ name: "mcp",
117687
118113
  aliases: [],
117688
- description: "设置终端 UI 主题",
118114
+ description: "管理 MCP 服务器(安装/停用/卸载)",
117689
118115
  priority: 112,
117690
118116
  availability: "always"
117691
118117
  },
117692
118118
  {
117693
- name: "editor",
117694
- aliases: [],
117695
- description: "设置外部编辑器",
118119
+ name: "plugin",
118120
+ aliases: ["plugins"],
118121
+ description: "ScreamCode 插件中心:浏览、安装、卸载插件",
117696
118122
  priority: 111,
117697
118123
  availability: "always"
117698
118124
  },
117699
118125
  {
117700
- name: "status",
118126
+ name: "cc",
117701
118127
  aliases: [],
117702
- description: "显示当前会话和运行时状态",
118128
+ description: "操控你的cc(启动/关闭/重启)",
117703
118129
  priority: 110,
117704
118130
  availability: "always"
117705
118131
  },
117706
118132
  {
117707
- name: "usage",
118133
+ name: "cc-connect",
117708
118134
  aliases: [],
117709
- description: "显示 token 用量和上下文窗口",
118135
+ description: "cc-connect 快速通道配置(需先安装)",
117710
118136
  priority: 109,
117711
118137
  availability: "always"
117712
118138
  },
117713
- {
117714
- name: "permission",
117715
- aliases: [],
117716
- description: "选择权限模式",
117717
- priority: 108,
117718
- availability: "always"
117719
- },
117720
118139
  {
117721
118140
  name: "revoke",
117722
118141
  aliases: [],
117723
118142
  description: "撤回上一次对话(可指定轮数,如 /revoke 3)",
117724
- priority: 107,
118143
+ priority: 108,
117725
118144
  availability: "idle-only"
117726
118145
  },
117727
- {
117728
- name: "config",
117729
- aliases: [],
117730
- description: "浏览并配置模型(远程拉取最新目录)",
117731
- priority: 106
117732
- },
117733
118146
  {
117734
118147
  name: "goal",
117735
118148
  aliases: [],
117736
118149
  description: "管理自动目标(status状态/pause暂停/resume恢复/replace替换,取消用 /goaloff)",
117737
- priority: 105,
118150
+ priority: 107,
117738
118151
  availability: (args) => {
117739
118152
  const trimmed = args.trim();
117740
118153
  return trimmed === "" || trimmed === "status" || trimmed === "pause" ? "always" : "idle-only";
@@ -117744,88 +118157,94 @@ const BUILTIN_SLASH_COMMANDS = [
117744
118157
  name: "goaloff",
117745
118158
  aliases: [],
117746
118159
  description: "取消并清空当前目标",
117747
- priority: 104,
117748
- availability: "always"
117749
- },
117750
- {
117751
- name: "settings",
117752
- aliases: [],
117753
- description: "打开 TUI 设置",
117754
- priority: 103,
118160
+ priority: 106,
117755
118161
  availability: "always"
117756
118162
  },
117757
118163
  {
117758
118164
  name: "fork",
117759
118165
  aliases: [],
117760
118166
  description: "复制当前会话并新开分支",
117761
- priority: 102
118167
+ priority: 105
117762
118168
  },
117763
118169
  {
117764
118170
  name: "title",
117765
118171
  aliases: ["rename"],
117766
118172
  description: "设置或显示会话标题",
117767
- priority: 101,
118173
+ priority: 104,
117768
118174
  availability: "always"
117769
118175
  },
117770
118176
  {
117771
- name: "init",
118177
+ name: "config",
117772
118178
  aliases: [],
117773
- description: "分析代码库并生成 AGENTS.md",
117774
- priority: 100
118179
+ description: "浏览并配置模型(远程拉取最新目录)",
118180
+ priority: 103
117775
118181
  },
117776
118182
  {
117777
- name: "cc",
118183
+ name: "permission",
117778
118184
  aliases: [],
117779
- description: "操控你的cc(启动/关闭/重启)",
117780
- priority: 98,
118185
+ description: "选择权限模式",
118186
+ priority: 102,
117781
118187
  availability: "always"
117782
118188
  },
117783
118189
  {
117784
- name: "cc-connect",
118190
+ name: "theme",
117785
118191
  aliases: [],
117786
- description: "cc-connect 快速通道配置(需先安装)",
117787
- priority: 96,
118192
+ description: "设置终端 UI 主题",
118193
+ priority: 101,
117788
118194
  availability: "always"
117789
118195
  },
117790
118196
  {
117791
- name: "export-md",
117792
- aliases: ["export"],
117793
- description: "导出当前会话为 Markdown",
117794
- priority: 95
118197
+ name: "editor",
118198
+ aliases: [],
118199
+ description: "设置外部编辑器",
118200
+ priority: 100,
118201
+ availability: "always"
117795
118202
  },
117796
118203
  {
117797
- name: "update",
118204
+ name: "settings",
117798
118205
  aliases: [],
117799
- description: "手动更新 Scream Code 到最新版本",
117800
- priority: 94,
117801
- availability: "idle-only"
118206
+ description: "打开 TUI 设置",
118207
+ priority: 99,
118208
+ availability: "always"
117802
118209
  },
117803
118210
  {
117804
- name: "plugin",
117805
- aliases: ["plugins"],
117806
- description: "ScreamCode 插件中心:浏览、安装、卸载插件",
117807
- priority: 93,
117808
- availability: "always"
118211
+ name: "init",
118212
+ aliases: [],
118213
+ description: "分析代码库并生成 AGENTS.md",
118214
+ priority: 98
118215
+ },
118216
+ {
118217
+ name: "export-md",
118218
+ aliases: ["export"],
118219
+ description: "导出当前会话为 Markdown",
118220
+ priority: 97
117809
118221
  },
117810
118222
  {
117811
118223
  name: "export-debug-zip",
117812
118224
  aliases: [],
117813
118225
  description: "导出当前会话为调试 ZIP 存档",
117814
- priority: 92
118226
+ priority: 96
117815
118227
  },
117816
118228
  {
117817
- name: "logout",
117818
- aliases: ["disconnect"],
117819
- description: "删除已配置的模型",
117820
- priority: 91
118229
+ name: "update",
118230
+ aliases: [],
118231
+ description: "手动更新 Scream Code 到最新版本",
118232
+ priority: 95,
118233
+ availability: "idle-only"
117821
118234
  },
117822
118235
  {
117823
118236
  name: "version",
117824
118237
  aliases: [],
117825
118238
  description: "显示版本信息",
117826
- priority: 90,
118239
+ priority: 94,
117827
118240
  availability: "always"
117828
118241
  },
118242
+ {
118243
+ name: "logout",
118244
+ aliases: ["disconnect"],
118245
+ description: "删除已配置的模型",
118246
+ priority: 93
118247
+ },
117829
118248
  {
117830
118249
  name: "exit",
117831
118250
  aliases: ["quit", "q"],
@@ -119846,11 +120265,36 @@ async function handleAutoCommand(host, args) {
119846
120265
  host.showNotice("自动模式:开启", "工具自动批准。代理不会提问。");
119847
120266
  }
119848
120267
  }
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了");
120268
+ async function handleWolfpackCommand(host, args) {
120269
+ const session = host.session;
120270
+ if (session === void 0) {
120271
+ host.showError(NO_ACTIVE_SESSION_MESSAGE);
120272
+ return;
120273
+ }
120274
+ const subcmd = args.trim().toLowerCase();
120275
+ let enabled;
120276
+ if (subcmd.length === 0) enabled = !host.state.appState.wolfpackMode;
120277
+ else if (subcmd === "on") enabled = true;
120278
+ else if (subcmd === "off") enabled = false;
120279
+ else {
120280
+ host.showError(`Unknown wolfpack subcommand: ${subcmd}`);
120281
+ return;
120282
+ }
120283
+ await applyWolfpackMode(host, session, enabled);
120284
+ }
120285
+ async function applyWolfpackMode(host, session, enabled) {
120286
+ try {
120287
+ await session.setWolfpackMode(enabled);
120288
+ host.setAppState({ wolfpackMode: enabled });
120289
+ if (enabled) {
120290
+ host.showNotice("WolfPack 模式:开启", "批量并发代理已激活。");
120291
+ return;
120292
+ }
120293
+ host.showNotice("WolfPack 模式:关闭");
120294
+ } catch (error) {
120295
+ const msg = formatErrorMessage(error);
120296
+ host.showError(`Failed to set wolfpack mode: ${msg}`);
120297
+ }
119854
120298
  }
119855
120299
  async function handleCompactCommand(host, args) {
119856
120300
  const session = host.session;
@@ -120575,12 +121019,608 @@ function syncGoalMetadata(host) {
120575
121019
  session.writeMetadata();
120576
121020
  }
120577
121021
  //#endregion
121022
+ //#region src/utils/git/git-status.ts
121023
+ /**
121024
+ * Cached git branch + working-tree status for the footer/statusline.
121025
+ *
121026
+ * Branch name refreshes every 5s, porcelain status every 15s. Branch
121027
+ * and status reads stay synchronous with short timeouts. Pull request
121028
+ * lookup uses an async cache so a slow `gh pr view` never blocks
121029
+ * footer rendering.
121030
+ */
121031
+ const BRANCH_TTL_MS = 5e3;
121032
+ const STATUS_TTL_MS = 15e3;
121033
+ const PULL_REQUEST_TTL_MS = 6e4;
121034
+ const SPAWN_TIMEOUT_MS = 500;
121035
+ const PR_SPAWN_TIMEOUT_MS = 5e3;
121036
+ const AHEAD_BEHIND_RE = /\[(?:ahead (\d+))?(?:, )?(?:behind (\d+))?\]/;
121037
+ function createGitStatusCache(workDir, options = {}) {
121038
+ const isRepo = detectGitRepo(workDir);
121039
+ let branch = {
121040
+ value: null,
121041
+ fetchedAt: 0
121042
+ };
121043
+ let status = {
121044
+ dirty: false,
121045
+ ahead: 0,
121046
+ behind: 0,
121047
+ diffAdded: 0,
121048
+ diffDeleted: 0,
121049
+ fetchedAt: 0
121050
+ };
121051
+ let pullRequest = {
121052
+ value: null,
121053
+ branch: null,
121054
+ fetchedAt: 0,
121055
+ pendingBranch: null,
121056
+ requestId: 0
121057
+ };
121058
+ return { getStatus: () => {
121059
+ if (!isRepo) return null;
121060
+ const now = Date.now();
121061
+ if (now - branch.fetchedAt >= BRANCH_TTL_MS) branch = {
121062
+ value: readBranch(workDir),
121063
+ fetchedAt: now
121064
+ };
121065
+ if (branch.value === null) return null;
121066
+ if (now - status.fetchedAt >= STATUS_TTL_MS) status = {
121067
+ ...readStatus(workDir),
121068
+ fetchedAt: now
121069
+ };
121070
+ refreshPullRequestIfNeeded(branch.value, now);
121071
+ return {
121072
+ branch: branch.value,
121073
+ dirty: status.dirty,
121074
+ ahead: status.ahead,
121075
+ behind: status.behind,
121076
+ diffAdded: status.diffAdded,
121077
+ diffDeleted: status.diffDeleted,
121078
+ pullRequest: pullRequest.branch === branch.value ? pullRequest.value : null
121079
+ };
121080
+ } };
121081
+ function refreshPullRequestIfNeeded(branchName, now) {
121082
+ if (pullRequest.pendingBranch === branchName) return;
121083
+ const fetchedAt = pullRequest.branch === branchName ? pullRequest.fetchedAt : 0;
121084
+ if (now - fetchedAt < PULL_REQUEST_TTL_MS) return;
121085
+ const requestId = pullRequest.requestId + 1;
121086
+ pullRequest = {
121087
+ value: pullRequest.branch === branchName ? pullRequest.value : null,
121088
+ branch: branchName,
121089
+ fetchedAt,
121090
+ pendingBranch: branchName,
121091
+ requestId
121092
+ };
121093
+ readPullRequest(workDir).then((value) => {
121094
+ if (pullRequest.requestId !== requestId) return;
121095
+ const changed = !samePullRequest(pullRequest.branch === branchName ? pullRequest.value : null, value);
121096
+ pullRequest = {
121097
+ value,
121098
+ branch: branchName,
121099
+ fetchedAt: Date.now(),
121100
+ pendingBranch: null,
121101
+ requestId
121102
+ };
121103
+ if (changed) options.onChange?.();
121104
+ });
121105
+ }
121106
+ }
121107
+ function detectGitRepo(workDir) {
121108
+ try {
121109
+ const result = spawnSync("git", [
121110
+ "-C",
121111
+ workDir,
121112
+ "rev-parse",
121113
+ "--is-inside-work-tree"
121114
+ ], {
121115
+ encoding: "utf8",
121116
+ timeout: SPAWN_TIMEOUT_MS
121117
+ });
121118
+ return result.status === 0 && result.stdout.trim() === "true";
121119
+ } catch {
121120
+ return false;
121121
+ }
121122
+ }
121123
+ function readBranch(workDir) {
121124
+ try {
121125
+ const result = spawnSync("git", [
121126
+ "-C",
121127
+ workDir,
121128
+ "branch",
121129
+ "--show-current"
121130
+ ], {
121131
+ encoding: "utf8",
121132
+ timeout: SPAWN_TIMEOUT_MS
121133
+ });
121134
+ if (result.status !== 0) return null;
121135
+ const name = result.stdout.trim();
121136
+ return name.length > 0 ? name : null;
121137
+ } catch {
121138
+ return null;
121139
+ }
121140
+ }
121141
+ function readStatus(workDir) {
121142
+ try {
121143
+ const result = spawnSync("git", [
121144
+ "-C",
121145
+ workDir,
121146
+ "status",
121147
+ "--porcelain",
121148
+ "-b"
121149
+ ], {
121150
+ encoding: "utf8",
121151
+ timeout: SPAWN_TIMEOUT_MS,
121152
+ maxBuffer: 4 * 1024 * 1024
121153
+ });
121154
+ if (result.status !== 0) return {
121155
+ dirty: false,
121156
+ ahead: 0,
121157
+ behind: 0,
121158
+ diffAdded: 0,
121159
+ diffDeleted: 0
121160
+ };
121161
+ let dirty = false;
121162
+ let ahead = 0;
121163
+ let behind = 0;
121164
+ for (const line of result.stdout.split("\n")) if (line.startsWith("## ")) {
121165
+ const m = AHEAD_BEHIND_RE.exec(line);
121166
+ if (m) {
121167
+ ahead = Number.parseInt(m[1] ?? "0", 10) || 0;
121168
+ behind = Number.parseInt(m[2] ?? "0", 10) || 0;
121169
+ }
121170
+ } else if (line.trim().length > 0) dirty = true;
121171
+ const diff = dirty ? readDiffStats(workDir) : {
121172
+ added: 0,
121173
+ deleted: 0
121174
+ };
121175
+ return {
121176
+ dirty,
121177
+ ahead,
121178
+ behind,
121179
+ diffAdded: diff.added,
121180
+ diffDeleted: diff.deleted
121181
+ };
121182
+ } catch {
121183
+ return {
121184
+ dirty: false,
121185
+ ahead: 0,
121186
+ behind: 0,
121187
+ diffAdded: 0,
121188
+ diffDeleted: 0
121189
+ };
121190
+ }
121191
+ }
121192
+ function readDiffStats(workDir) {
121193
+ try {
121194
+ const result = spawnSync("git", [
121195
+ "-C",
121196
+ workDir,
121197
+ "diff",
121198
+ "--numstat",
121199
+ "HEAD",
121200
+ "--"
121201
+ ], {
121202
+ encoding: "utf8",
121203
+ timeout: SPAWN_TIMEOUT_MS,
121204
+ maxBuffer: 4 * 1024 * 1024
121205
+ });
121206
+ if (result.status !== 0) return {
121207
+ added: 0,
121208
+ deleted: 0
121209
+ };
121210
+ let added = 0;
121211
+ let deleted = 0;
121212
+ for (const line of result.stdout.split("\n")) {
121213
+ if (!line) continue;
121214
+ const [addedText, deletedText] = line.split(" ");
121215
+ added += parseDiffNumstatCount(addedText);
121216
+ deleted += parseDiffNumstatCount(deletedText);
121217
+ }
121218
+ return {
121219
+ added,
121220
+ deleted
121221
+ };
121222
+ } catch {
121223
+ return {
121224
+ added: 0,
121225
+ deleted: 0
121226
+ };
121227
+ }
121228
+ }
121229
+ function parseDiffNumstatCount(value) {
121230
+ if (value === void 0 || value === "-") return 0;
121231
+ const n = Number.parseInt(value, 10);
121232
+ return Number.isFinite(n) && n > 0 ? n : 0;
121233
+ }
121234
+ function readPullRequest(workDir) {
121235
+ return new Promise((resolve) => {
121236
+ try {
121237
+ execFile("gh", [
121238
+ "pr",
121239
+ "view",
121240
+ "--json",
121241
+ "number,url"
121242
+ ], {
121243
+ cwd: workDir,
121244
+ encoding: "utf8",
121245
+ env: {
121246
+ ...process.env,
121247
+ GH_NO_UPDATE_NOTIFIER: "1",
121248
+ GH_PROMPT_DISABLED: "1"
121249
+ },
121250
+ timeout: PR_SPAWN_TIMEOUT_MS,
121251
+ maxBuffer: 256 * 1024
121252
+ }, (error, stdout) => {
121253
+ if (error !== null) {
121254
+ resolve(null);
121255
+ return;
121256
+ }
121257
+ resolve(parsePullRequest(stdout));
121258
+ });
121259
+ } catch {
121260
+ resolve(null);
121261
+ }
121262
+ });
121263
+ }
121264
+ function samePullRequest(a, b) {
121265
+ if (a === null || b === null) return a === b;
121266
+ return a.number === b.number && a.url === b.url;
121267
+ }
121268
+ function parsePullRequest(stdout) {
121269
+ try {
121270
+ const raw = JSON.parse(stdout);
121271
+ if (typeof raw !== "object" || raw === null) return null;
121272
+ const record = raw;
121273
+ const number = record["number"];
121274
+ const url = record["url"];
121275
+ if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) return null;
121276
+ if (typeof url !== "string" || !isSafeHttpUrl(url)) return null;
121277
+ return {
121278
+ number,
121279
+ url
121280
+ };
121281
+ } catch {
121282
+ return null;
121283
+ }
121284
+ }
121285
+ function isSafeHttpUrl(value) {
121286
+ if (hasControlChars(value)) return false;
121287
+ try {
121288
+ const url = new URL(value);
121289
+ return url.protocol === "https:" || url.protocol === "http:";
121290
+ } catch {
121291
+ return false;
121292
+ }
121293
+ }
121294
+ function hasControlChars(value) {
121295
+ for (const char of value) {
121296
+ const code = char.codePointAt(0) ?? 0;
121297
+ if (code <= 31 || code === 127) return true;
121298
+ }
121299
+ return false;
121300
+ }
121301
+ function formatGitBadgeBase(status) {
121302
+ const parts = [];
121303
+ const diff = formatDiffStats(status);
121304
+ if (diff) parts.push(diff);
121305
+ let sync = "";
121306
+ if (status.ahead > 0) sync += `↑${status.ahead}`;
121307
+ if (status.behind > 0) sync += `↓${status.behind}`;
121308
+ if (sync) parts.push(sync);
121309
+ return parts.length === 0 ? status.branch : `${status.branch} [${parts.join(" ")}]`;
121310
+ }
121311
+ function formatPullRequestBadge(pullRequest, options = {}) {
121312
+ const prText = `[PR#${String(pullRequest.number)}]`;
121313
+ return options.linkPullRequest ? toTerminalHyperlink(prText, pullRequest.url) : prText;
121314
+ }
121315
+ function formatDiffStats(status) {
121316
+ const parts = [];
121317
+ if (status.diffAdded > 0) parts.push(`+${String(status.diffAdded)}`);
121318
+ if (status.diffDeleted > 0) parts.push(`-${String(status.diffDeleted)}`);
121319
+ if (parts.length > 0) return parts.join(" ");
121320
+ return status.dirty ? "±" : null;
121321
+ }
121322
+ function toTerminalHyperlink(text, url) {
121323
+ if (!isSafeHttpUrl(url)) return text;
121324
+ return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
121325
+ }
121326
+ //#endregion
121327
+ //#region src/tui/components/chrome/footer.ts
121328
+ const MAX_CWD_SEGMENTS = 3;
121329
+ const TIP_ROTATE_INTERVAL_MS = 1e4;
121330
+ const TOOLBAR_TIPS = [
121331
+ { text: "shift+tab: 计划模式" },
121332
+ { text: "/model: 切换模型" },
121333
+ {
121334
+ text: "ctrl+s: 中途干预",
121335
+ priority: 2
121336
+ },
121337
+ {
121338
+ text: "/compact: 压缩上下文",
121339
+ priority: 2
121340
+ },
121341
+ { text: "ctrl+o: 展开工具输出" },
121342
+ { text: "/tasks: 后台任务" },
121343
+ { text: "shift+enter: 换行" },
121344
+ {
121345
+ text: "/init: 生成 AGENTS.md",
121346
+ priority: 2
121347
+ },
121348
+ { text: "@: 提及文件" },
121349
+ { text: "ctrl+c: 取消" },
121350
+ { text: "/theme: 切换主题" },
121351
+ { text: "/auto: 自动权限模式" },
121352
+ { text: "/yes: 自动批准" },
121353
+ { text: "/help: 显示命令" },
121354
+ {
121355
+ text: "/config: 选择并配置你常用的模型商",
121356
+ solo: true,
121357
+ priority: 3
121358
+ },
121359
+ {
121360
+ text: "让 Scream 安排任务,例如 \"2个小时后提醒我去拿快递\"",
121361
+ solo: true,
121362
+ priority: 3
121363
+ }
121364
+ ];
121365
+ /**
121366
+ * Expand tips into a rotation sequence using smooth weighted round-robin
121367
+ * (the nginx SWRR algorithm). Higher-`priority` tips appear more often while
121368
+ * staying evenly spread, so a tip generally does not land next to its own
121369
+ * duplicate. Deterministic and computed once at module load. Exported for
121370
+ * unit testing.
121371
+ */
121372
+ function buildWeightedTips(tips) {
121373
+ const items = tips.map((t) => ({
121374
+ tip: t,
121375
+ weight: Math.max(1, Math.trunc(t.priority ?? 1)),
121376
+ current: 0
121377
+ }));
121378
+ const total = items.reduce((sum, it) => sum + it.weight, 0);
121379
+ const seq = [];
121380
+ for (let n = 0; n < total; n++) {
121381
+ let best = items[0];
121382
+ for (const it of items) {
121383
+ it.current += it.weight;
121384
+ if (it.current > best.current) best = it;
121385
+ }
121386
+ best.current -= total;
121387
+ seq.push(best.tip);
121388
+ }
121389
+ return seq;
121390
+ }
121391
+ const ROTATION = buildWeightedTips(TOOLBAR_TIPS);
121392
+ function currentTipIndex() {
121393
+ return Math.floor(Date.now() / TIP_ROTATE_INTERVAL_MS);
121394
+ }
121395
+ /**
121396
+ * Pick the tip(s) for a rotation index over the weighted ROTATION sequence.
121397
+ * `primary` is always shown when it fits; `pair` (primary + next tip joined
121398
+ * by the separator) is offered for wide terminals. Pairing is skipped when
121399
+ * the current/next tip is `solo` or when the neighbour is a duplicate of the
121400
+ * current tip (which can happen at the wrap boundary), keeping long/important
121401
+ * tips on their own and avoiding "X | X".
121402
+ */
121403
+ function tipsForIndex(index) {
121404
+ const n = ROTATION.length;
121405
+ if (n === 0) return {
121406
+ primary: "",
121407
+ pair: null
121408
+ };
121409
+ const offset = (index % n + n) % n;
121410
+ const current = ROTATION[offset];
121411
+ if (n === 1 || current.solo) return {
121412
+ primary: current.text,
121413
+ pair: null
121414
+ };
121415
+ const next = ROTATION[(offset + 1) % n];
121416
+ if (next.solo || next.text === current.text) return {
121417
+ primary: current.text,
121418
+ pair: null
121419
+ };
121420
+ return {
121421
+ primary: current.text,
121422
+ pair: current.text + " | " + next.text
121423
+ };
121424
+ }
121425
+ function shortenModel(model) {
121426
+ if (!model) return model;
121427
+ const slash = model.lastIndexOf("/");
121428
+ return slash >= 0 ? model.slice(slash + 1) : model;
121429
+ }
121430
+ function modelDisplayName(state) {
121431
+ const model = state.availableModels[state.model];
121432
+ return model?.displayName ?? model?.model ?? state.model;
121433
+ }
121434
+ function shortenCwd(path) {
121435
+ if (!path) return path;
121436
+ const home = process.env["HOME"] ?? "";
121437
+ let work = path;
121438
+ if (home && path === home) return "~";
121439
+ if (home && path.startsWith(home + "/")) work = "~" + path.slice(home.length);
121440
+ const segments = work.split("/").filter((s) => s.length > 0);
121441
+ if (segments.length <= MAX_CWD_SEGMENTS) return work;
121442
+ return `…/${segments.slice(-3).join("/")}`;
121443
+ }
121444
+ function formatTokenCount(n) {
121445
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
121446
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
121447
+ return String(n);
121448
+ }
121449
+ function safeUsage(usage) {
121450
+ return safeUsageRatio(usage);
121451
+ }
121452
+ function formatContextStatus(usage, tokens, maxTokens) {
121453
+ const pct = `${(safeUsage(usage) * 100).toFixed(1)}%`;
121454
+ if (maxTokens && maxTokens > 0 && tokens !== void 0) return `上下文:${pct} (${formatTokenCount(tokens)}/${formatTokenCount(maxTokens)})`;
121455
+ return `上下文:${pct}`;
121456
+ }
121457
+ const BRAND_COLORS = [
121458
+ "#72A4E9",
121459
+ "#A78BFA",
121460
+ "#34D399"
121461
+ ];
121462
+ const GRADIENT_CYCLE_MS = 4e3;
121463
+ const SPINNER_FRAMES$1 = [
121464
+ "●",
121465
+ "◉",
121466
+ "◎",
121467
+ "◌",
121468
+ "○",
121469
+ "◌",
121470
+ "◎",
121471
+ "◉"
121472
+ ];
121473
+ const SPINNER_TICK_MS = 120;
121474
+ function hexToRgb$1(hex) {
121475
+ const v = parseInt(hex.slice(1), 16);
121476
+ return [
121477
+ v >> 16 & 255,
121478
+ v >> 8 & 255,
121479
+ v & 255
121480
+ ];
121481
+ }
121482
+ function lerpGradient(t) {
121483
+ const count = BRAND_COLORS.length;
121484
+ const segment = Math.min(t * count, count - 1);
121485
+ const idx = Math.floor(segment);
121486
+ const localT = segment - idx;
121487
+ const nextIdx = (idx + 1) % count;
121488
+ const [r0, g0, b0] = hexToRgb$1(BRAND_COLORS[idx]);
121489
+ const [r1, g1, b1] = hexToRgb$1(BRAND_COLORS[nextIdx]);
121490
+ const r = Math.round(r0 + (r1 - r0) * localT);
121491
+ const g = Math.round(g0 + (g1 - g0) * localT);
121492
+ const b = Math.round(b0 + (b1 - b0) * localT);
121493
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
121494
+ }
121495
+ function buildStatusLine(streamingPhase, livePaneMode, streamingStartTime) {
121496
+ if (streamingPhase === "idle" && livePaneMode !== "tool") return "○ 空闲";
121497
+ let label;
121498
+ if (livePaneMode === "tool") label = "执行中";
121499
+ else if (streamingPhase === "waiting") label = "等待响应";
121500
+ else if (streamingPhase === "thinking") label = "思考中";
121501
+ else if (streamingPhase === "composing") label = "输出中";
121502
+ else label = "";
121503
+ const elapsed = Date.now() - streamingStartTime;
121504
+ const totalSeconds = Math.floor(elapsed / 1e3);
121505
+ const elapsedStr = totalSeconds < 60 ? `${totalSeconds}s` : `${Math.floor(totalSeconds / 60)}m${totalSeconds % 60}s`;
121506
+ const now = Date.now();
121507
+ const frame = SPINNER_FRAMES$1[Math.floor(now / SPINNER_TICK_MS) % SPINNER_FRAMES$1.length];
121508
+ const gradientColor = lerpGradient(now % GRADIENT_CYCLE_MS / GRADIENT_CYCLE_MS);
121509
+ return chalk.hex(gradientColor).bold(frame) + " " + label + " " + elapsedStr;
121510
+ }
121511
+ function formatFooterGitBadge(status, colors) {
121512
+ const base = chalk.hex(colors.status)(formatGitBadgeBase(status));
121513
+ if (status.pullRequest === null) return base;
121514
+ return `${base} ${chalk.hex(colors.primary)(formatPullRequestBadge(status.pullRequest, { linkPullRequest: true }))}`;
121515
+ }
121516
+ var FooterComponent = class {
121517
+ state;
121518
+ colors;
121519
+ onGitStatusChange;
121520
+ gitCache;
121521
+ gitCacheWorkDir;
121522
+ transientHint = null;
121523
+ /**
121524
+ * Non-terminal background-task counts split by kind so the footer can
121525
+ * render two distinct badges. `bashTasks` covers `bash-*` BPM tasks
121526
+ * spawned via `Shell run_in_background=true`; `agentTasks` covers
121527
+ * `agent-*` BPM tasks (background subagents). Either zero hides its
121528
+ * respective badge.
121529
+ */
121530
+ backgroundBashTaskCount = 0;
121531
+ backgroundAgentCount = 0;
121532
+ constructor(state, colors, onGitStatusChange = () => {}) {
121533
+ this.state = state;
121534
+ this.colors = colors;
121535
+ this.onGitStatusChange = onGitStatusChange;
121536
+ this.gitCacheWorkDir = state.workDir;
121537
+ this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
121538
+ }
121539
+ setState(state) {
121540
+ if (state.workDir !== this.gitCacheWorkDir) {
121541
+ this.gitCacheWorkDir = state.workDir;
121542
+ this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
121543
+ }
121544
+ this.state = state;
121545
+ }
121546
+ setColors(colors) {
121547
+ this.colors = colors;
121548
+ }
121549
+ /**
121550
+ * Short-lived hint that replaces the rotating toolbar tips on line 1.
121551
+ * Used by the exit-confirmation double-tap flow to show "Press Ctrl+C
121552
+ * again to exit" without requiring a toast/overlay subsystem.
121553
+ * Pass `null` to clear.
121554
+ */
121555
+ setTransientHint(hint) {
121556
+ this.transientHint = hint;
121557
+ }
121558
+ /**
121559
+ * Sync both background-task badges with live counts. Each non-zero
121560
+ * count produces its own bracketed badge on line 1; zeros hide them
121561
+ * independently.
121562
+ */
121563
+ setBackgroundCounts(counts) {
121564
+ this.backgroundBashTaskCount = Math.max(0, counts.bashTasks);
121565
+ this.backgroundAgentCount = Math.max(0, counts.agentTasks);
121566
+ }
121567
+ invalidate() {}
121568
+ render(width) {
121569
+ const colors = this.colors;
121570
+ const state = this.state;
121571
+ const left = [];
121572
+ if (state.permissionMode === "auto") left.push(chalk.hex(colors.warning).bold("auto"));
121573
+ if (state.permissionMode === "yolo") left.push(chalk.hex(colors.warning).bold("YES"));
121574
+ if (state.planMode) left.push(chalk.hex(colors.primary).bold("plan"));
121575
+ if (state.wolfpackMode) left.push(chalk.hex(colors.primary).bold("wolfpack"));
121576
+ if (state.goalActive && state.goal) {
121577
+ const goalText = state.goal.length > 20 ? state.goal.slice(0, 20) + "…" : state.goal;
121578
+ left.push(chalk.hex(colors.success).bold(`🎯 ${goalText}`));
121579
+ }
121580
+ const model = shortenModel(modelDisplayName(state));
121581
+ if (model) {
121582
+ const thinkingLabel = state.thinking ? " 思考中" : "";
121583
+ left.push(chalk.hex(colors.text)(`${model}${thinkingLabel}`));
121584
+ }
121585
+ if (this.backgroundBashTaskCount > 0) {
121586
+ const noun = this.backgroundBashTaskCount === 1 ? "个任务" : "个任务";
121587
+ left.push(chalk.hex(colors.primary)(`[${String(this.backgroundBashTaskCount)}${noun} 运行中]`));
121588
+ }
121589
+ if (this.backgroundAgentCount > 0) {
121590
+ const noun = this.backgroundAgentCount === 1 ? "个代理" : "个代理";
121591
+ left.push(chalk.hex(colors.primary)(`[${String(this.backgroundAgentCount)}${noun} 运行中]`));
121592
+ }
121593
+ const cwd = shortenCwd(state.workDir);
121594
+ if (cwd) left.push(chalk.hex(colors.status)(cwd));
121595
+ const git = this.gitCache.getStatus();
121596
+ if (git !== null) left.push(formatFooterGitBadge(git, colors));
121597
+ const leftLine = left.join(" ");
121598
+ const leftWidth = visibleWidth(leftLine);
121599
+ let rightText;
121600
+ if (this.transientHint) rightText = chalk.hex(colors.warning).bold(this.transientHint);
121601
+ else {
121602
+ const statusLine = buildStatusLine(state.streamingPhase, state.livePaneMode, state.streamingStartTime);
121603
+ const ccDot = state.ccConnectActive ? chalk.hex(colors.success)("●") : chalk.hex(colors.textDim)("●");
121604
+ rightText = chalk.hex(colors.textDim)(ccDot + " " + formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens) + " " + statusLine);
121605
+ }
121606
+ const rightWidth = visibleWidth(rightText);
121607
+ const gap = 3;
121608
+ let line1;
121609
+ if (leftWidth + gap + rightWidth <= width) {
121610
+ const pad = width - leftWidth - rightWidth;
121611
+ line1 = leftLine + " ".repeat(pad) + rightText;
121612
+ } else if (leftWidth <= width) line1 = leftLine;
121613
+ else line1 = truncateToWidth(leftLine, width, "…");
121614
+ return [truncateToWidth(line1, width)];
121615
+ }
121616
+ };
121617
+ //#endregion
120578
121618
  //#region src/tui/components/chrome/welcome.ts
120579
121619
  const HUE_STOPS = 24;
120580
121620
  const SUB_STEPS = 5;
120581
121621
  const BREATHE_STEPS = HUE_STOPS * SUB_STEPS;
120582
121622
  const BREATHE_INTERVAL_MS = 40;
120583
- function hexToRgb$1(hex) {
121623
+ function hexToRgb(hex) {
120584
121624
  return [
120585
121625
  parseInt(hex.slice(1, 3), 16),
120586
121626
  parseInt(hex.slice(3, 5), 16),
@@ -120645,7 +121685,7 @@ function rgbToHex(r, g, b) {
120645
121685
  * it sweeps through all 24 hue stops with smooth sub-step interpolation.
120646
121686
  */
120647
121687
  function buildBreathingPalette(primaryHex, hueStops, subSteps) {
120648
- const [r, g, b] = hexToRgb$1(primaryHex);
121688
+ const [r, g, b] = hexToRgb(primaryHex);
120649
121689
  const [baseHue, sat, lit] = rgbToHsl(r, g, b);
120650
121690
  const steps = hueStops * subSteps;
120651
121691
  const palette = [];
@@ -120662,6 +121702,7 @@ var WelcomeComponent = class {
120662
121702
  breatheFrame = 0;
120663
121703
  breatheTimer = null;
120664
121704
  breathePalette;
121705
+ borderTitle = null;
120665
121706
  constructor(state, colors, ui) {
120666
121707
  this.state = state;
120667
121708
  this.colors = colors;
@@ -120700,7 +121741,7 @@ var WelcomeComponent = class {
120700
121741
  const isLoggedOut = !this.state.model;
120701
121742
  const dim = chalk.hex(this.colors.textDim);
120702
121743
  const labelStyle = chalk.bold.hex(this.colors.textDim);
120703
- const rightRow1 = truncateToWidth(dim(isLoggedOut ? "运行 /config 开始配置。" : "发送 /help 获取帮助信息。/config 配置模型"), textWidth, "…");
121744
+ const rightRow1 = truncateToWidth(dim(isLoggedOut ? "运行 /config 开始配置。" : "发送 / 进入快捷菜单,/exit 保存并退出"), textWidth, "…");
120704
121745
  const headerLines = [logoColor(logo[0].padEnd(logoWidth)) + gap + rightRow0, logoColor(logo[1].padEnd(logoWidth)) + gap + rightRow1];
120705
121746
  const activeModel = this.state.availableModels[this.state.model];
120706
121747
  const modelValue = isLoggedOut ? chalk.hex(this.colors.warning)("未设置,运行 /config") : activeModel?.displayName ?? activeModel?.model ?? this.state.model;
@@ -120712,19 +121753,20 @@ var WelcomeComponent = class {
120712
121753
  labelStyle("模型: ") + modelValue,
120713
121754
  labelStyle("版本: ") + versionValue
120714
121755
  ];
120715
- const hintLine = chalk.hex(this.colors.textMuted)("按 / 可进入快捷指令菜单,输入 /sessions 可恢复或管理会话");
120716
- const tipLine = chalk.hex(this.colors.textMuted)("Tips:试试让它写代码、做研报、做攻略、清理电脑...");
121756
+ const { primary: tipPrimary, pair: tipPair } = tipsForIndex(currentTipIndex());
121757
+ const tip = tipPair && visibleWidth(tipPair) <= innerWidth ? tipPair : tipPrimary;
121758
+ const tipLine = chalk.hex(this.colors.textMuted)("Tips: " + tip);
120717
121759
  const contentLines = [
120718
121760
  ...headerLines,
120719
121761
  "",
120720
121762
  ...infoLines,
120721
121763
  "",
120722
- hintLine,
120723
121764
  tipLine
120724
121765
  ];
121766
+ const borderTitle = this.borderTitle;
120725
121767
  const lines = [
120726
121768
  "",
120727
- primary("╭" + "─".repeat(width - 2) + "╮"),
121769
+ borderTitle ? primary("╭─ " + borderTitle + " " + "─".repeat(Math.max(0, width - 5 - visibleWidth(borderTitle))) + "╮") : primary("╭" + "─".repeat(width - 2) + "╮"),
120728
121770
  primary("│") + " ".repeat(width - 2) + primary("│")
120729
121771
  ];
120730
121772
  for (const content of contentLines) {
@@ -124465,12 +125507,6 @@ const BUILTIN_REGISTRY = [
124465
125507
  description: "GreenSock 动画平台全套参考手册,含核心 API、Timeline、ScrollTrigger、插件、React 集成等 8 个技能",
124466
125508
  source: "https://github.com/greensock/gsap-skills"
124467
125509
  },
124468
- {
124469
- id: "gorden-ppt-skill",
124470
- displayName: "Gorden PPT 助手",
124471
- description: "17 套精修中文 PPT 模板,支持 python-pptx 编辑生成,适配国企/互联网大厂风格",
124472
- source: "https://github.com/GordenSun/GordenPPTSkill"
124473
- },
124474
125510
  {
124475
125511
  id: "claude-design-card",
124476
125512
  displayName: "Claude Design Card",
@@ -124513,18 +125549,6 @@ const BUILTIN_REGISTRY = [
124513
125549
  description: "专利交底书自动生成:专利点挖掘 → 国知局查新 → 脱敏成文 → 自检闭环,Mermaid 附图,输出 .docx",
124514
125550
  source: "https://github.com/handsomestWei/patent-disclosure-skill"
124515
125551
  },
124516
- {
124517
- id: "html-ppt-skill",
124518
- displayName: "HTML PPT Studio 演示文稿",
124519
- description: "AI 驱动 HTML 幻灯片:36 套主题 × 31 种布局 × 47 种动画,纯静态 HTML/CSS/JS,支持演讲者模式",
124520
- source: "https://github.com/lewislulu/html-ppt-skill"
124521
- },
124522
- {
124523
- id: "uzi-skill",
124524
- displayName: "UZI Skill 股票分析引擎",
124525
- description: "A股/港股/美股深度分析:22 数据维度 × 180 量化规则 × 17 机构分析法 × 51 投资大师人格模拟,输出 Bloomberg 风格 HTML 报告",
124526
- source: "https://github.com/wbh604/UZI-Skill"
124527
- },
124528
125552
  {
124529
125553
  id: "contract-review-pro",
124530
125554
  displayName: "Contract Review Pro 合同审查",
@@ -124542,6 +125566,66 @@ const BUILTIN_REGISTRY = [
124542
125566
  displayName: "Headroom 压缩优化",
124543
125567
  description: "在内容送达 LLM 前压缩工具输出、日志、文件和 RAG 块,节省 60-95% Token,答案质量不变",
124544
125568
  source: "https://github.com/chopratejas/headroom"
125569
+ },
125570
+ {
125571
+ id: "xiaohu-wechat-format",
125572
+ displayName: "小壶公众号排版",
125573
+ description: "Markdown → 微信兼容 HTML → 推送草稿箱,30 套主题 + 可视化画廊,一键排版发布",
125574
+ source: "https://github.com/xiaohuailabs/xiaohu-wechat-format"
125575
+ },
125576
+ {
125577
+ id: "huashu-design",
125578
+ displayName: "花束设计",
125579
+ description: "HTML 原生设计技能:高保真原型 / 幻灯片 / 动画 + 20 设计哲学 + 5 维评审 + MP4 导出",
125580
+ source: "https://github.com/alchaincyf/huashu-design"
125581
+ },
125582
+ {
125583
+ id: "html-video",
125584
+ displayName: "HTML Video 视频生成",
125585
+ description: "HTML 转 MP4:可插拔渲染引擎 + 21 套模板 + AI 配乐,全程本地,零渲染费用",
125586
+ source: "https://github.com/nexu-io/html-video"
125587
+ },
125588
+ {
125589
+ id: "xiaohu-video-translate",
125590
+ displayName: "小壶视频翻译",
125591
+ description: "外语视频自动配中文字幕:下载 / 转写 / 翻译 / 润色 / 烧录一条龙,全程本地",
125592
+ source: "https://github.com/xiaohuailabs/xiaohu-video-translate"
125593
+ },
125594
+ {
125595
+ id: "videocut-skills",
125596
+ displayName: "视频剪辑 Agent",
125597
+ description: "Claude Code Skills 驱动的视频剪辑 Agent:口播剪辑 / 字幕导入 / 画质高清化",
125598
+ source: "https://github.com/Ceeon/videocut-skills"
125599
+ },
125600
+ {
125601
+ id: "taste-skill",
125602
+ displayName: "Taste Skill 设计品味",
125603
+ description: "给 AI 好品味:阻止生成无聊通用的设计,输出有质感的方案",
125604
+ source: "https://github.com/Leonxlnx/taste-skill"
125605
+ },
125606
+ {
125607
+ id: "vtake-skills",
125608
+ displayName: "VTake 视频剪辑",
125609
+ description: "Agent Skills 驱动的视频剪辑工具",
125610
+ source: "https://github.com/notedit/vtake-skills"
125611
+ },
125612
+ {
125613
+ id: "remotion-skills",
125614
+ displayName: "Remotion 视频技能",
125615
+ description: "Remotion(React 视频框架)官方技能包",
125616
+ source: "https://github.com/remotion-dev/skills"
125617
+ },
125618
+ {
125619
+ id: "html-anything",
125620
+ displayName: "HTML Anything 全能设计",
125621
+ description: "75 个技能 × 9 种场景:杂志 / 幻灯片 / 海报 / 小红书 / 数据报告 / 原型,零 API 密钥",
125622
+ source: "https://github.com/nexu-io/html-anything"
125623
+ },
125624
+ {
125625
+ id: "guizang-social-card-skill",
125626
+ displayName: "归藏社交卡片",
125627
+ description: "小红书轮播图 + 公众号封面:28 种布局 × 10 套主题,Editorial × Swiss 视觉体系,单文件 HTML → PNG",
125628
+ source: "https://github.com/op7418/guizang-social-card-skill"
124545
125629
  }
124546
125630
  ];
124547
125631
  async function handlePluginCommand(host, _args) {
@@ -124643,10 +125727,33 @@ async function loadInstalled(host) {
124643
125727
  return [];
124644
125728
  }
124645
125729
  }
125730
+ /**
125731
+ * Normalize a GitHub URL to `{owner}/{repo}` for fuzzy matching.
125732
+ * Returns `null` for non-GitHub URLs (caller falls back to exact match).
125733
+ */
125734
+ function normalizeGithubSource(url) {
125735
+ try {
125736
+ const u = new URL(url.trim());
125737
+ if (u.hostname !== "github.com" && u.hostname !== "www.github.com") return null;
125738
+ const segments = u.pathname.split("/").filter((s) => s.length > 0);
125739
+ if (segments.length < 2) return null;
125740
+ return `${segments[0]}/${segments[1].replace(/\.git$/, "")}`.toLowerCase();
125741
+ } catch {
125742
+ return null;
125743
+ }
125744
+ }
125745
+ function isInstalled(marketplaceId, marketplaceSource, installed) {
125746
+ if (installed.some((p) => p.id === marketplaceId)) return true;
125747
+ const normalizedSource = normalizeGithubSource(marketplaceSource);
125748
+ if (normalizedSource === null) return false;
125749
+ return installed.some((p) => {
125750
+ const norm = normalizeGithubSource(p.originalSource ?? "");
125751
+ return norm !== null && norm === normalizedSource;
125752
+ });
125753
+ }
124646
125754
  function buildOptions(marketplace, installed) {
124647
125755
  const options = [];
124648
- const installedIds = new Set(installed.map((p) => p.id));
124649
- const newPlugins = marketplace.filter((p) => !installedIds.has(p.id));
125756
+ const newPlugins = marketplace.filter((p) => !isInstalled(p.id, p.source, installed));
124650
125757
  if (newPlugins.length > 0) {
124651
125758
  options.push({
124652
125759
  value: "__section__marketplace",
@@ -124725,7 +125832,7 @@ async function confirmUninstall(host, label) {
124725
125832
  * restores the editor. The question and answer are never recorded in the
124726
125833
  * main conversation history.
124727
125834
  */
124728
- const SPINNER_FRAMES$1 = [
125835
+ const SPINNER_FRAMES = [
124729
125836
  "⠋",
124730
125837
  "⠙",
124731
125838
  "⠹",
@@ -124785,7 +125892,7 @@ var BtwOverlayComponent = class extends Container {
124785
125892
  startSpinner() {
124786
125893
  this.spinnerFrame = 0;
124787
125894
  this.spinnerInterval = setInterval(() => {
124788
- this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES$1.length;
125895
+ this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
124789
125896
  this.requestRender();
124790
125897
  }, 80);
124791
125898
  }
@@ -124805,7 +125912,7 @@ var BtwOverlayComponent = class extends Container {
124805
125912
  lines.push(chalk.hex(c.primary)("/btw") + chalk.hex(c.textMuted)(" — ") + chalk.hex(c.text)(truncated));
124806
125913
  lines.push("");
124807
125914
  if (this.status === "loading") {
124808
- const spinner = SPINNER_FRAMES$1[this.spinnerFrame];
125915
+ const spinner = SPINNER_FRAMES[this.spinnerFrame];
124809
125916
  lines.push(chalk.hex(c.textMuted)(`${spinner} Answering…`));
124810
125917
  } else if (this.status === "done" && this.markdown !== void 0) {
124811
125918
  const contentWidth = Math.max(20, width - 2);
@@ -124965,8 +126072,8 @@ async function handleBuiltInSlashCommand(host, name, args) {
124965
126072
  case "plan":
124966
126073
  await handlePlanCommand(host, args);
124967
126074
  return;
124968
- case "power":
124969
- await handlePowerCommand(host, args);
126075
+ case "wolfpack":
126076
+ await handleWolfpackCommand(host, args);
124970
126077
  return;
124971
126078
  case "revoke":
124972
126079
  await handleRevokeCommand(host, args);
@@ -129971,635 +131078,6 @@ const INITIAL_LIVE_PANE = {
129971
131078
  pendingQuestion: null
129972
131079
  };
129973
131080
  //#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
131081
  //#region src/tui/components/chrome/todo-panel.ts
130604
131082
  const MAX_VISIBLE = 5;
130605
131083
  /**
@@ -133963,7 +134441,7 @@ function createInitialAppState(input) {
133963
134441
  goalActive: false,
133964
134442
  goalContinuationCount: 0,
133965
134443
  ccConnectActive: false,
133966
- parallelMode: false
134444
+ wolfpackMode: false
133967
134445
  };
133968
134446
  }
133969
134447
  var ScreamTUI = class {
@@ -134380,9 +134858,7 @@ var ScreamTUI = class {
134380
134858
  imageAttachmentIds
134381
134859
  });
134382
134860
  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) => {
134861
+ session.prompt(options?.parts ?? input).catch((error) => {
134386
134862
  const message = formatErrorMessage(error);
134387
134863
  this.failSessionRequest(`发送失败:${message}`);
134388
134864
  });
@@ -134465,6 +134941,7 @@ var ScreamTUI = class {
134465
134941
  return this.state.loadingSessions;
134466
134942
  }
134467
134943
  async deleteSession(sessionId) {
134944
+ if (sessionId === this.session?.id) await this.sessionManager.closeSession("session deleted");
134468
134945
  await this.harness.deleteSession(sessionId);
134469
134946
  }
134470
134947
  async getStartupMcpMs() {
@@ -134479,12 +134956,9 @@ var ScreamTUI = class {
134479
134956
  setAppState(patch) {
134480
134957
  if (!hasPatchChanges(this.state.appState, patch)) return;
134481
134958
  const busyChanged = "streamingPhase" in patch || "isCompacting" in patch;
134482
- const prevParallelMode = this.state.appState.parallelMode;
134483
134959
  Object.assign(this.state.appState, patch);
134484
134960
  if ("planMode" in patch) this.updateEditorBorderHighlight();
134485
134961
  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
134962
  this.state.footer.setState(this.state.appState);
134489
134963
  this.updateActivityPane();
134490
134964
  if (busyChanged) this.updateQueueDisplay();
@@ -134627,6 +135101,7 @@ var ScreamTUI = class {
134627
135101
  renderWelcome() {
134628
135102
  this.welcomeComponent?.stopBreathing();
134629
135103
  const welcome = new WelcomeComponent(this.state.appState, this.state.theme.colors, this.state.ui);
135104
+ welcome.borderTitle = "Scream Code";
134630
135105
  this.welcomeComponent = welcome;
134631
135106
  if (this.state.editor.hasFirstInputFired()) welcome.stopBreathing();
134632
135107
  this.state.transcriptContainer.addChild(welcome);
@@ -134900,6 +135375,214 @@ var ScreamTUI = class {
134900
135375
  }
134901
135376
  };
134902
135377
  //#endregion
135378
+ //#region src/tui/components/chrome/loading.ts
135379
+ const { stdout } = process$1;
135380
+ const BRIGHT = "\x1B[38;2;255;255;255m";
135381
+ const RESET = "\x1B[0m";
135382
+ const THEME_GREEN = {
135383
+ dark: "\x1B[38;2;78;200;126m",
135384
+ light: "\x1B[38;2;14;122;56m"
135385
+ };
135386
+ const FRAMES = [
135387
+ {
135388
+ duration: 80,
135389
+ content: [
135390
+ "┌┐",
135391
+ "││",
135392
+ "││",
135393
+ "└┘"
135394
+ ]
135395
+ },
135396
+ {
135397
+ duration: 80,
135398
+ content: [
135399
+ "┌──────┐",
135400
+ "│ │",
135401
+ "│ │",
135402
+ "└──────┘"
135403
+ ]
135404
+ },
135405
+ {
135406
+ duration: 80,
135407
+ content: [
135408
+ "┌──────────────────┐",
135409
+ "│ │",
135410
+ "│ │",
135411
+ "└──────────────────┘"
135412
+ ]
135413
+ },
135414
+ {
135415
+ duration: 80,
135416
+ content: [
135417
+ "┌──────────────────────────────┐",
135418
+ "│ │",
135419
+ "│ welcome to scream code │",
135420
+ "│ │",
135421
+ "└──────────────────────────────┘"
135422
+ ]
135423
+ },
135424
+ {
135425
+ duration: 100,
135426
+ content: [
135427
+ "┌─────────────────────────────────────────────────────────────────┐",
135428
+ "│ │",
135429
+ "│ welcome to scream code │",
135430
+ "│ │",
135431
+ "│ ███████╗ ██████╗██████╗ ███████╗ █████╗ ███╗ ███╗ │",
135432
+ "│ ██╔════╝ ██╔════╝██╔══██╗██╔════╝██╔══██╗████╗ ████║ │",
135433
+ "│ ███████╗ ██║ ██████╔╝█████╗ ███████║██╔████╔██║ │",
135434
+ "│ ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║ │",
135435
+ "│ ███████║ ╚██████╗██║ ██║███████╗██║ ██║██║ ╚═╝ ██║ │",
135436
+ "│ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ │",
135437
+ "│ │",
135438
+ "└─────────────────────────────────────────────────────────────────┘"
135439
+ ]
135440
+ },
135441
+ {
135442
+ duration: 120,
135443
+ content: [
135444
+ "┌─────────────────────────────────────────────────────────────────┐",
135445
+ "│ │",
135446
+ "│ welcome to scream code │",
135447
+ "│ │",
135448
+ "│ ███████╗ ██████╗██████╗ ███████╗ █████╗ ███╗ ███╗ │",
135449
+ "│ ██╔════╝ ██╔════╝██╔══██╗██╔════╝██╔══██╗████╗ ████║ │",
135450
+ "│ ███████╗ ██║ ██████╔╝█████╗ ███████║██╔████╔██║ │",
135451
+ "│ ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║ │",
135452
+ "│ ███████║ ╚██████╗██║ ██║███████╗██║ ██║██║ ╚═╝ ██║ │",
135453
+ "│ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ │",
135454
+ "│ │",
135455
+ "│ 你的中文智能Ai助手 │",
135456
+ "└─────────────────────────────────────────────────────────────────┘"
135457
+ ]
135458
+ },
135459
+ {
135460
+ duration: 9999,
135461
+ content: [
135462
+ "┌─────────────────────────────────────────────────────────────────┐",
135463
+ "│ │",
135464
+ "│ welcome to scream code │",
135465
+ "│ │",
135466
+ "│ ███████╗ ██████╗██████╗ ███████╗ █████╗ ███╗ ███╗ │",
135467
+ "│ ██╔════╝ ██╔════╝██╔══██╗██╔════╝██╔══██╗████╗ ████║ │",
135468
+ "│ ███████╗ ██║ ██████╔╝█████╗ ███████║██╔████╔██║ │",
135469
+ "│ ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║ │",
135470
+ "│ ███████║ ╚██████╗██║ ██║███████╗██║ ██║██║ ╚═╝ ██║ │",
135471
+ "│ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ │",
135472
+ "│ │",
135473
+ "│ 你的中文智能Ai助手 │",
135474
+ "└─────────────────────────────────────────────────────────────────┘"
135475
+ ]
135476
+ }
135477
+ ];
135478
+ function color(line, green) {
135479
+ const s = line.replace(/[│ ]/g, "");
135480
+ const tb = (line.startsWith("┌") || line.startsWith("└")) && (line.endsWith("┐") || line.endsWith("┘")) && s.replace(/[─┌┐└┘]/g, "") === "";
135481
+ const es = line.startsWith("│") && line.endsWith("│") && s === "";
135482
+ if (tb || es) return green;
135483
+ if (line.includes("welcome") || line.includes("scream") || line.includes("你的中文")) return green;
135484
+ return BRIGHT;
135485
+ }
135486
+ let ansiSupported = null;
135487
+ function supportsAnsi() {
135488
+ if (ansiSupported !== null) return ansiSupported;
135489
+ if (!stdout.isTTY) {
135490
+ ansiSupported = false;
135491
+ return false;
135492
+ }
135493
+ if (process$1.env["NO_COLOR"]) {
135494
+ ansiSupported = false;
135495
+ return false;
135496
+ }
135497
+ if (process$1.env["FORCE_COLOR"]) {
135498
+ ansiSupported = true;
135499
+ return true;
135500
+ }
135501
+ if (process$1.platform === "win32") {
135502
+ const term = (process$1.env["TERM"] ?? "").toLowerCase();
135503
+ const session = (process$1.env["TERM_PROGRAM"] ?? "").toLowerCase();
135504
+ if (term.includes("xterm") || term.includes("vt100") || term.includes("256color")) {
135505
+ ansiSupported = true;
135506
+ return true;
135507
+ }
135508
+ if (session.includes("terminal") || session.includes("vscode")) {
135509
+ ansiSupported = true;
135510
+ return true;
135511
+ }
135512
+ if (process$1.env["CI"]) {
135513
+ ansiSupported = true;
135514
+ return true;
135515
+ }
135516
+ ansiSupported = true;
135517
+ return true;
135518
+ }
135519
+ if (process$1.env["TERM"] && process$1.env["TERM"] !== "dumb") {
135520
+ ansiSupported = true;
135521
+ return true;
135522
+ }
135523
+ ansiSupported = false;
135524
+ return false;
135525
+ }
135526
+ function plainFrame(frameIndex, green) {
135527
+ const f = FRAMES[Math.min(frameIndex, FRAMES.length - 1)];
135528
+ let out = "";
135529
+ for (const l of f.content) out += color(l, green) + (l || " ") + "\x1B[0m\n";
135530
+ return out;
135531
+ }
135532
+ const LOADING_STAGES = [
135533
+ "正在整理记忆区域....",
135534
+ "正在加载用户喜好....",
135535
+ "正在确认模型配置....",
135536
+ "正在加载scream code...."
135537
+ ];
135538
+ const STAGE_DELAYS_MS = [
135539
+ 450,
135540
+ 350,
135541
+ 650,
135542
+ 350
135543
+ ];
135544
+ function runLoadingAnimation(theme = "dark") {
135545
+ const green = THEME_GREEN[theme];
135546
+ if (!supportsAnsi()) {
135547
+ stdout.write(plainFrame(FRAMES.length - 1, green));
135548
+ for (const stage of LOADING_STAGES) stdout.write(green + stage + "\x1B[0m\n");
135549
+ return Promise.resolve();
135550
+ }
135551
+ return new Promise((resolve) => {
135552
+ const last = FRAMES.length - 1;
135553
+ let frame = 0;
135554
+ function draw(i) {
135555
+ stdout.write("\x1B[H" + plainFrame(i, green));
135556
+ }
135557
+ function showStages(stages, index) {
135558
+ if (index >= stages.length) {
135559
+ setTimeout(() => {
135560
+ stdout.write("\x1B[2J\x1B[H\x1B[?25h");
135561
+ resolve();
135562
+ }, 400);
135563
+ return;
135564
+ }
135565
+ stdout.write("\n" + green + stages[index] + RESET);
135566
+ const delay = STAGE_DELAYS_MS[index] ?? 350;
135567
+ setTimeout(() => showStages(stages, index + 1), delay);
135568
+ }
135569
+ function tick() {
135570
+ if (frame >= last) {
135571
+ setTimeout(() => {
135572
+ showStages(LOADING_STAGES, 0);
135573
+ }, 600);
135574
+ return;
135575
+ }
135576
+ draw(frame);
135577
+ frame++;
135578
+ setTimeout(tick, FRAMES[frame - 1].duration);
135579
+ }
135580
+ stdout.write("\x1B[?25l\x1B[2J\x1B[H");
135581
+ draw(0);
135582
+ setTimeout(tick, FRAMES[0].duration);
135583
+ });
135584
+ }
135585
+ //#endregion
134903
135586
  //#region src/cli/run-shell.ts
134904
135587
  async function runShell(opts, version) {
134905
135588
  const startedAt = Date.now();
@@ -134936,6 +135619,7 @@ async function runShell(opts, version) {
134936
135619
  await harness.ensureConfigFile();
134937
135620
  const config = await harness.getConfig();
134938
135621
  const configMs = Date.now() - configStartedAt;
135622
+ await runLoadingAnimation(resolvedTheme);
134939
135623
  const tui = new ScreamTUI(harness, {
134940
135624
  cliOptions: opts,
134941
135625
  tuiConfig,