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