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.
- package/dist/main.mjs +1481 -797
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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: "
|
|
117607
|
-
aliases: ["
|
|
117608
|
-
description: "
|
|
118025
|
+
name: "yes",
|
|
118026
|
+
aliases: ["yolo"],
|
|
118027
|
+
description: "切换至自动批准模式(yolo)",
|
|
117609
118028
|
priority: 124,
|
|
117610
118029
|
availability: "always"
|
|
117611
118030
|
},
|
|
117612
118031
|
{
|
|
117613
|
-
name: "
|
|
117614
|
-
aliases: ["
|
|
117615
|
-
description: "
|
|
118032
|
+
name: "wolfpack",
|
|
118033
|
+
aliases: ["wp"],
|
|
118034
|
+
description: "切换群狼协作模式,自动批准+批量并发",
|
|
117616
118035
|
priority: 123,
|
|
117617
118036
|
availability: "always"
|
|
117618
118037
|
},
|
|
117619
118038
|
{
|
|
117620
|
-
name: "
|
|
117621
|
-
aliases: [],
|
|
117622
|
-
description: "
|
|
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:
|
|
118048
|
+
priority: 121,
|
|
117636
118049
|
availability: "always"
|
|
117637
118050
|
},
|
|
117638
118051
|
{
|
|
117639
|
-
name: "
|
|
117640
|
-
aliases: ["
|
|
117641
|
-
description: "
|
|
117642
|
-
priority:
|
|
117643
|
-
|
|
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:
|
|
118067
|
+
priority: 119
|
|
117650
118068
|
},
|
|
117651
118069
|
{
|
|
117652
118070
|
name: "plan",
|
|
117653
118071
|
aliases: [],
|
|
117654
118072
|
description: "切换计划模式",
|
|
117655
|
-
priority:
|
|
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: "
|
|
118098
|
+
name: "usage",
|
|
117673
118099
|
aliases: [],
|
|
117674
|
-
description: "
|
|
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: "
|
|
118112
|
+
name: "mcp",
|
|
117687
118113
|
aliases: [],
|
|
117688
|
-
description: "
|
|
118114
|
+
description: "管理 MCP 服务器(安装/停用/卸载)",
|
|
117689
118115
|
priority: 112,
|
|
117690
118116
|
availability: "always"
|
|
117691
118117
|
},
|
|
117692
118118
|
{
|
|
117693
|
-
name: "
|
|
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: "
|
|
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: "
|
|
118133
|
+
name: "cc-connect",
|
|
117708
118134
|
aliases: [],
|
|
117709
|
-
description: "
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
118167
|
+
priority: 105
|
|
117762
118168
|
},
|
|
117763
118169
|
{
|
|
117764
118170
|
name: "title",
|
|
117765
118171
|
aliases: ["rename"],
|
|
117766
118172
|
description: "设置或显示会话标题",
|
|
117767
|
-
priority:
|
|
118173
|
+
priority: 104,
|
|
117768
118174
|
availability: "always"
|
|
117769
118175
|
},
|
|
117770
118176
|
{
|
|
117771
|
-
name: "
|
|
118177
|
+
name: "config",
|
|
117772
118178
|
aliases: [],
|
|
117773
|
-
description: "
|
|
117774
|
-
priority:
|
|
118179
|
+
description: "浏览并配置模型(远程拉取最新目录)",
|
|
118180
|
+
priority: 103
|
|
117775
118181
|
},
|
|
117776
118182
|
{
|
|
117777
|
-
name: "
|
|
118183
|
+
name: "permission",
|
|
117778
118184
|
aliases: [],
|
|
117779
|
-
description: "
|
|
117780
|
-
priority:
|
|
118185
|
+
description: "选择权限模式",
|
|
118186
|
+
priority: 102,
|
|
117781
118187
|
availability: "always"
|
|
117782
118188
|
},
|
|
117783
118189
|
{
|
|
117784
|
-
name: "
|
|
118190
|
+
name: "theme",
|
|
117785
118191
|
aliases: [],
|
|
117786
|
-
description: "
|
|
117787
|
-
priority:
|
|
118192
|
+
description: "设置终端 UI 主题",
|
|
118193
|
+
priority: 101,
|
|
117788
118194
|
availability: "always"
|
|
117789
118195
|
},
|
|
117790
118196
|
{
|
|
117791
|
-
name: "
|
|
117792
|
-
aliases: [
|
|
117793
|
-
description: "
|
|
117794
|
-
priority:
|
|
118197
|
+
name: "editor",
|
|
118198
|
+
aliases: [],
|
|
118199
|
+
description: "设置外部编辑器",
|
|
118200
|
+
priority: 100,
|
|
118201
|
+
availability: "always"
|
|
117795
118202
|
},
|
|
117796
118203
|
{
|
|
117797
|
-
name: "
|
|
118204
|
+
name: "settings",
|
|
117798
118205
|
aliases: [],
|
|
117799
|
-
description: "
|
|
117800
|
-
priority:
|
|
117801
|
-
availability: "
|
|
118206
|
+
description: "打开 TUI 设置",
|
|
118207
|
+
priority: 99,
|
|
118208
|
+
availability: "always"
|
|
117802
118209
|
},
|
|
117803
118210
|
{
|
|
117804
|
-
name: "
|
|
117805
|
-
aliases: [
|
|
117806
|
-
description: "
|
|
117807
|
-
priority:
|
|
117808
|
-
|
|
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:
|
|
118226
|
+
priority: 96
|
|
117815
118227
|
},
|
|
117816
118228
|
{
|
|
117817
|
-
name: "
|
|
117818
|
-
aliases: [
|
|
117819
|
-
description: "
|
|
117820
|
-
priority:
|
|
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:
|
|
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
|
|
119850
|
-
const
|
|
119851
|
-
|
|
119852
|
-
|
|
119853
|
-
|
|
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
|
|
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
|
|
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 开始配置。" : "发送 /
|
|
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
|
|
120716
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 "
|
|
124969
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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,
|