scream-code 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.mjs +290 -60
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -1042,6 +1042,16 @@ var APIContextOverflowError = class extends APIStatusError {
1042
1042
  }
1043
1043
  };
1044
1044
  /**
1045
+ * HTTP status error that specifically means the provider rate-limited the
1046
+ * request.
1047
+ */
1048
+ var APIProviderRateLimitError = class extends APIStatusError {
1049
+ constructor(message, requestId) {
1050
+ super(429, message, requestId);
1051
+ this.name = "APIProviderRateLimitError";
1052
+ }
1053
+ };
1054
+ /**
1045
1055
  * The API returned an empty response (no content, no tool calls).
1046
1056
  */
1047
1057
  var APIEmptyResponseError = class extends ChatProviderError {
@@ -1075,6 +1085,7 @@ function isContextOverflowErrorCode(code) {
1075
1085
  return code === "context_length_exceeded";
1076
1086
  }
1077
1087
  function normalizeAPIStatusError(statusCode, message, requestId) {
1088
+ if (statusCode === 429) return new APIProviderRateLimitError(message, requestId);
1078
1089
  if (isContextOverflowStatusError(statusCode, message)) return new APIContextOverflowError(statusCode, message, requestId);
1079
1090
  return new APIStatusError(statusCode, message, requestId);
1080
1091
  }
@@ -1084,6 +1095,94 @@ function isContextOverflowStatusError(statusCode, message) {
1084
1095
  return CONTEXT_OVERFLOW_MESSAGE_PATTERNS.some((pattern) => pattern.test(lowerMessage));
1085
1096
  }
1086
1097
  //#endregion
1098
+ //#region ../../packages/ltod/src/providers/tool-call-id.ts
1099
+ const EMPTY_TOOL_CALL_ID = "tool_call";
1100
+ const TOOL_CALL_ID_SAFE_CHARS = /[^a-zA-Z0-9_-]/g;
1101
+ function sanitizeToolCallId(id, maxLength) {
1102
+ const sanitized = id.replace(TOOL_CALL_ID_SAFE_CHARS, "_");
1103
+ return maxLength === void 0 ? sanitized : sanitized.slice(0, maxLength);
1104
+ }
1105
+ function sanitizeOpenAIResponsesCallId(id, maxLength) {
1106
+ const [callId] = id.split("|", 1);
1107
+ return sanitizeToolCallId(callId ?? id, maxLength);
1108
+ }
1109
+ function normalizeToolCallIdsForProvider(messages, policy) {
1110
+ const rawIds = collectToolCallIds(messages);
1111
+ if (rawIds.length === 0) return messages;
1112
+ const mappedIds = buildToolCallIdMap(rawIds, policy);
1113
+ let changed = false;
1114
+ const normalizedMessages = messages.map((message) => {
1115
+ let messageChanged = false;
1116
+ let toolCalls = message.toolCalls;
1117
+ if (message.toolCalls.length > 0) toolCalls = message.toolCalls.map((toolCall) => {
1118
+ const mappedId = mappedIds.get(toolCall.id);
1119
+ if (mappedId === void 0 || mappedId === toolCall.id) return toolCall;
1120
+ messageChanged = true;
1121
+ return {
1122
+ ...toolCall,
1123
+ id: mappedId
1124
+ };
1125
+ });
1126
+ const mappedToolCallId = (message.toolCallId === void 0 ? void 0 : mappedIds.get(message.toolCallId)) ?? message.toolCallId;
1127
+ if (mappedToolCallId !== message.toolCallId) messageChanged = true;
1128
+ if (!messageChanged) return message;
1129
+ changed = true;
1130
+ return {
1131
+ ...message,
1132
+ toolCalls,
1133
+ toolCallId: mappedToolCallId
1134
+ };
1135
+ });
1136
+ return changed ? normalizedMessages : messages;
1137
+ }
1138
+ function collectToolCallIds(messages) {
1139
+ const ids = [];
1140
+ const seen = /* @__PURE__ */ new Set();
1141
+ const append = (id) => {
1142
+ if (seen.has(id)) return;
1143
+ seen.add(id);
1144
+ ids.push(id);
1145
+ };
1146
+ for (const message of messages) {
1147
+ for (const toolCall of message.toolCalls) append(toolCall.id);
1148
+ if (message.toolCallId !== void 0) append(message.toolCallId);
1149
+ }
1150
+ return ids;
1151
+ }
1152
+ function buildToolCallIdMap(rawIds, policy) {
1153
+ const mappedIds = /* @__PURE__ */ new Map();
1154
+ const usedIds = /* @__PURE__ */ new Set();
1155
+ for (const rawId of rawIds) {
1156
+ const normalized = policy.normalize(rawId);
1157
+ if (normalized === rawId && normalized.length > 0) {
1158
+ mappedIds.set(rawId, normalized);
1159
+ usedIds.add(normalized);
1160
+ }
1161
+ }
1162
+ for (const rawId of rawIds) {
1163
+ if (mappedIds.has(rawId)) continue;
1164
+ const unique = makeUniqueToolCallId(policy.normalize(rawId), usedIds, policy.maxLength);
1165
+ mappedIds.set(rawId, unique);
1166
+ usedIds.add(unique);
1167
+ }
1168
+ return mappedIds;
1169
+ }
1170
+ function makeUniqueToolCallId(normalized, usedIds, maxLength) {
1171
+ const base = normalized.length > 0 ? normalized : EMPTY_TOOL_CALL_ID;
1172
+ const candidate = truncateToolCallId(base, maxLength, "");
1173
+ if (!usedIds.has(candidate)) return candidate;
1174
+ for (let i = 2;; i++) {
1175
+ const suffixed = truncateToolCallId(base, maxLength, `_${i}`);
1176
+ if (!usedIds.has(suffixed)) return suffixed;
1177
+ }
1178
+ }
1179
+ function truncateToolCallId(base, maxLength, suffix) {
1180
+ if (maxLength === void 0) return `${base}${suffix}`;
1181
+ const baseLength = maxLength - suffix.length;
1182
+ if (baseLength <= 0) throw new Error(`Tool call id maxLength ${maxLength} is too small for suffix ${suffix}.`);
1183
+ return `${base.slice(0, baseLength)}${suffix}`;
1184
+ }
1185
+ //#endregion
1087
1186
  //#region ../../node_modules/.pnpm/@anthropic-ai+sdk@0.95.2_zod@4.4.3/node_modules/@anthropic-ai/sdk/internal/tslib.mjs
1088
1187
  function __classPrivateFieldSet$1(receiver, state, value, kind, f) {
1089
1188
  if (kind === "m") throw new TypeError("Private method is not writable");
@@ -8946,6 +9045,10 @@ function budgetTokensForEffort(effort) {
8946
9045
  }
8947
9046
  throw new Error(`Unknown thinking effort: ${String(effort)}`);
8948
9047
  }
9048
+ const ANTHROPIC_TOOL_CALL_ID_POLICY = {
9049
+ normalize: (id) => sanitizeToolCallId(id, 64),
9050
+ maxLength: 64
9051
+ };
8949
9052
  const CACHE_CONTROL = { type: "ephemeral" };
8950
9053
  /**
8951
9054
  * Content block types that support cache_control injection.
@@ -9347,8 +9450,9 @@ var AnthropicChatProvider = class {
9347
9450
  text: systemPrompt,
9348
9451
  cache_control: CACHE_CONTROL
9349
9452
  }] : void 0;
9453
+ const normalizedHistory = normalizeToolCallIdsForProvider(history, ANTHROPIC_TOOL_CALL_ID_POLICY);
9350
9454
  const messages = [];
9351
- for (const msg of history) {
9455
+ for (const msg of normalizedHistory) {
9352
9456
  const converted = convertMessage$3(msg);
9353
9457
  const last = messages.at(-1);
9354
9458
  if (last !== void 0 && isToolResultOnly(last) && isToolResultOnly(converted)) last.content = [...last.content, ...converted.content];
@@ -48884,6 +48988,10 @@ const KNOWN_REASONING_KEYS = [
48884
48988
  "reasoning"
48885
48989
  ];
48886
48990
  const DEFAULT_OUTBOUND_REASONING_KEY = KNOWN_REASONING_KEYS[0];
48991
+ const OPENAI_CHAT_TOOL_CALL_ID_POLICY = {
48992
+ normalize: (id) => sanitizeToolCallId(id, 64),
48993
+ maxLength: 64
48994
+ };
48887
48995
  function extractReasoningContent(source, explicitKey) {
48888
48996
  if (typeof source !== "object" || source === null) return void 0;
48889
48997
  const record = source;
@@ -49060,7 +49168,8 @@ var OpenAILegacyChatProvider = class {
49060
49168
  role: "system",
49061
49169
  content: systemPrompt
49062
49170
  });
49063
- for (const msg of history) messages.push(convertMessage$1(msg, this._reasoningKey, this._toolMessageConversion));
49171
+ const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_CHAT_TOOL_CALL_ID_POLICY);
49172
+ for (const msg of normalizedHistory) messages.push(convertMessage$1(msg, this._reasoningKey, this._toolMessageConversion));
49064
49173
  const kwargs = { ...this._generationKwargs };
49065
49174
  let reasoningEffort = this._reasoningEffort;
49066
49175
  if (reasoningEffort === void 0 && kwargs["reasoning_effort"] === void 0) {
@@ -49161,6 +49270,10 @@ function normalizeResponsesFinishReason(status, incompleteReason) {
49161
49270
  rawFinishReason: null
49162
49271
  };
49163
49272
  }
49273
+ const OPENAI_RESPONSES_TOOL_CALL_ID_POLICY = {
49274
+ normalize: (id) => sanitizeOpenAIResponsesCallId(id, 64),
49275
+ maxLength: 64
49276
+ };
49164
49277
  function asRawObject(value) {
49165
49278
  if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
49166
49279
  return value;
@@ -49685,7 +49798,8 @@ var OpenAIResponsesChatProvider = class {
49685
49798
  if (usesOpenAIResponsesDeveloperRole(this._model)) sysItem["role"] = "developer";
49686
49799
  input.push(sysItem);
49687
49800
  }
49688
- for (const msg of history) input.push(...convertMessage(msg, this._model, this._toolMessageConversion));
49801
+ const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_RESPONSES_TOOL_CALL_ID_POLICY);
49802
+ for (const msg of normalizedHistory) input.push(...convertMessage(msg, this._model, this._toolMessageConversion));
49689
49803
  const kwargs = { ...this._generationKwargs };
49690
49804
  const reasoningEffort = kwargs["reasoning_effort"];
49691
49805
  delete kwargs["reasoning_effort"];
@@ -72232,9 +72346,11 @@ function maybeStatusCode(error) {
72232
72346
  //#endregion
72233
72347
  //#region ../../packages/agent-core/src/agent/context/projector.ts
72234
72348
  function project(history) {
72235
- return mergeAdjacentUserMessages(history.filter((message) => {
72349
+ const usable = history.filter((message) => {
72236
72350
  return message.partial !== true && !(message.role === "assistant" && message.content.length === 0 && message.toolCalls.length === 0);
72237
- }));
72351
+ });
72352
+ const last = usable.at(-1);
72353
+ return mergeAdjacentUserMessages(last?.role === "assistant" && last.toolCalls.length > 0 ? usable.slice(0, -1) : usable);
72238
72354
  }
72239
72355
  function mergeAdjacentUserMessages(history) {
72240
72356
  const out = [];
@@ -85499,7 +85615,7 @@ async function finalizePendingToolResult(step, pendingResult) {
85499
85615
  async function executeTool(step, execution, toolCall, toolName, metadata) {
85500
85616
  const { dispatchEvent, signal, turnId } = step;
85501
85617
  signal.throwIfAborted();
85502
- return raceExecuteWithGraceAndHardTimeout(execution.execute({
85618
+ return raceExecuteWithGraceTimeout(execution.execute({
85503
85619
  turnId,
85504
85620
  toolCallId: toolCall.id,
85505
85621
  metadata,
@@ -85514,10 +85630,8 @@ async function executeTool(step, execution, toolCall, toolName, metadata) {
85514
85630
  }
85515
85631
  }), signal, toolName);
85516
85632
  }
85517
- const HARD_TIMEOUT_MS = 12e5;
85518
- async function raceExecuteWithGraceAndHardTimeout(executePromise, signal, toolName) {
85633
+ async function raceExecuteWithGraceTimeout(executePromise, signal, toolName) {
85519
85634
  let graceTimer;
85520
- let hardTimer;
85521
85635
  let onAbort;
85522
85636
  const graceSentinel = new Promise((resolve) => {
85523
85637
  const armTimer = () => {
@@ -85534,23 +85648,10 @@ async function raceExecuteWithGraceAndHardTimeout(executePromise, signal, toolNa
85534
85648
  signal.addEventListener("abort", onAbort, { once: true });
85535
85649
  }
85536
85650
  });
85537
- const hardSentinel = new Promise((resolve) => {
85538
- hardTimer = setTimeout(() => {
85539
- resolve({
85540
- output: `Tool "${toolName}" exceeded hard timeout (${String(HARD_TIMEOUT_MS)}ms)`,
85541
- isError: true
85542
- });
85543
- }, HARD_TIMEOUT_MS);
85544
- });
85545
85651
  try {
85546
- return await Promise.race([
85547
- executePromise,
85548
- graceSentinel,
85549
- hardSentinel
85550
- ]);
85652
+ return await Promise.race([executePromise, graceSentinel]);
85551
85653
  } finally {
85552
85654
  if (graceTimer !== void 0) clearTimeout(graceTimer);
85553
- if (hardTimer !== void 0) clearTimeout(hardTimer);
85554
85655
  if (onAbort !== void 0) try {
85555
85656
  signal.removeEventListener("abort", onAbort);
85556
85657
  } catch {}
@@ -97111,6 +97212,16 @@ async function parseManifest(pluginRoot) {
97111
97212
  manifestPath: skillMdPath,
97112
97213
  diagnostics: []
97113
97214
  };
97215
+ const discoveredSkillDirs = await discoverSkillDirs(pluginRoot, 3);
97216
+ if (discoveredSkillDirs.length > 0) return {
97217
+ manifest: {
97218
+ name: path.basename(pluginRoot),
97219
+ skills: discoveredSkillDirs
97220
+ },
97221
+ manifestKind: "bare-skill",
97222
+ manifestPath: path.join(discoveredSkillDirs[0], BARE_SKILL_PATH),
97223
+ diagnostics: []
97224
+ };
97114
97225
  return { diagnostics: [{
97115
97226
  severity: "error",
97116
97227
  message: `No manifest at ${SCREAM_PLUGIN_ROOT_PATH}, ${SCREAM_PLUGIN_DIR_PATH}, or ${CLAUDE_PLUGIN_DIR_PATH}`
@@ -97409,6 +97520,34 @@ function isWithin$1(child, parent) {
97409
97520
  const relative = path.relative(parent, child);
97410
97521
  return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
97411
97522
  }
97523
+ /**
97524
+ * Recursively scan for `SKILL.md` files up to `maxDepth` levels below `root`.
97525
+ * Returns the unique parent directories, deduplicated so that a nested skill
97526
+ * dir is not listed if its ancestor is already a skill root.
97527
+ */
97528
+ async function discoverSkillDirs(root, maxDepth) {
97529
+ const found = [];
97530
+ async function walk(dir, depth) {
97531
+ if (depth > maxDepth) return;
97532
+ let entries;
97533
+ try {
97534
+ entries = await readdir(dir, { withFileTypes: true });
97535
+ } catch {
97536
+ return;
97537
+ }
97538
+ for (const entry of entries) {
97539
+ if (!entry.isDirectory()) continue;
97540
+ const child = path.join(dir, entry.name);
97541
+ if (await isFile$1(path.join(child, BARE_SKILL_PATH))) found.push(child);
97542
+ await walk(child, depth + 1);
97543
+ }
97544
+ }
97545
+ await walk(root, 1);
97546
+ const sorted = found.sort((a, b) => a.length - b.length);
97547
+ const result = [];
97548
+ for (const dir of sorted) if (!result.some((parent) => dir.startsWith(parent + path.sep))) result.push(dir);
97549
+ return result;
97550
+ }
97412
97551
  async function isFile$1(p) {
97413
97552
  try {
97414
97553
  return (await stat(p)).isFile();
@@ -112953,12 +113092,15 @@ var SessionStore = class {
112953
113092
  async create(input) {
112954
113093
  assertSafeSessionId(input.id);
112955
113094
  const workDir = normalizeWorkDir(input.workDir);
112956
- if (await this.findSessionEntry(input.id) !== void 0) throw new ScreamError(ErrorCodes.SESSION_ALREADY_EXISTS, `Session "${input.id}" already exists`);
112957
113095
  const dir = this.sessionDirFor({
112958
113096
  id: input.id,
112959
113097
  workDir
112960
113098
  });
112961
- if (await isDirectory(dir)) throw new ScreamError(ErrorCodes.SESSION_ALREADY_EXISTS, `Session "${input.id}" already exists`);
113099
+ if (await this.findSessionEntry(input.id) !== void 0) await this.delete(input.id);
113100
+ else if (await isDirectory(dir)) await rm(dir, {
113101
+ recursive: true,
113102
+ force: true
113103
+ });
112962
113104
  await mkdir(dir, {
112963
113105
  recursive: true,
112964
113106
  mode: 448
@@ -113029,11 +113171,12 @@ var SessionStore = class {
113029
113171
  }
113030
113172
  async delete(id) {
113031
113173
  assertSafeSessionId(id);
113032
- await rm((await this.findExistingSessionEntry(id)).sessionDir, {
113174
+ const entry = await this.findSessionEntry(id);
113175
+ if (entry !== void 0) await rm(entry.sessionDir, {
113033
113176
  recursive: true,
113034
113177
  force: true
113035
- });
113036
- await removeSessionIndexEntry(this.homeDir, id);
113178
+ }).catch(() => {});
113179
+ await removeSessionIndexEntry(this.homeDir, id).catch(() => {});
113037
113180
  }
113038
113181
  async list(options = {}) {
113039
113182
  const workDir = options.workDir === void 0 ? void 0 : normalizeRequiredWorkDir(options.workDir);
@@ -125364,12 +125507,6 @@ const BUILTIN_REGISTRY = [
125364
125507
  description: "GreenSock 动画平台全套参考手册,含核心 API、Timeline、ScrollTrigger、插件、React 集成等 8 个技能",
125365
125508
  source: "https://github.com/greensock/gsap-skills"
125366
125509
  },
125367
- {
125368
- id: "gorden-ppt-skill",
125369
- displayName: "Gorden PPT 助手",
125370
- description: "17 套精修中文 PPT 模板,支持 python-pptx 编辑生成,适配国企/互联网大厂风格",
125371
- source: "https://github.com/GordenSun/GordenPPTSkill"
125372
- },
125373
125510
  {
125374
125511
  id: "claude-design-card",
125375
125512
  displayName: "Claude Design Card",
@@ -125412,18 +125549,6 @@ const BUILTIN_REGISTRY = [
125412
125549
  description: "专利交底书自动生成:专利点挖掘 → 国知局查新 → 脱敏成文 → 自检闭环,Mermaid 附图,输出 .docx",
125413
125550
  source: "https://github.com/handsomestWei/patent-disclosure-skill"
125414
125551
  },
125415
- {
125416
- id: "html-ppt-skill",
125417
- displayName: "HTML PPT Studio 演示文稿",
125418
- description: "AI 驱动 HTML 幻灯片:36 套主题 × 31 种布局 × 47 种动画,纯静态 HTML/CSS/JS,支持演讲者模式",
125419
- source: "https://github.com/lewislulu/html-ppt-skill"
125420
- },
125421
- {
125422
- id: "uzi-skill",
125423
- displayName: "UZI Skill 股票分析引擎",
125424
- description: "A股/港股/美股深度分析:22 数据维度 × 180 量化规则 × 17 机构分析法 × 51 投资大师人格模拟,输出 Bloomberg 风格 HTML 报告",
125425
- source: "https://github.com/wbh604/UZI-Skill"
125426
- },
125427
125552
  {
125428
125553
  id: "contract-review-pro",
125429
125554
  displayName: "Contract Review Pro 合同审查",
@@ -125441,6 +125566,66 @@ const BUILTIN_REGISTRY = [
125441
125566
  displayName: "Headroom 压缩优化",
125442
125567
  description: "在内容送达 LLM 前压缩工具输出、日志、文件和 RAG 块,节省 60-95% Token,答案质量不变",
125443
125568
  source: "https://github.com/chopratejas/headroom"
125569
+ },
125570
+ {
125571
+ id: "xiaohu-wechat-format",
125572
+ displayName: "小壶公众号排版",
125573
+ description: "Markdown → 微信兼容 HTML → 推送草稿箱,30 套主题 + 可视化画廊,一键排版发布",
125574
+ source: "https://github.com/xiaohuailabs/xiaohu-wechat-format"
125575
+ },
125576
+ {
125577
+ id: "huashu-design",
125578
+ displayName: "花束设计",
125579
+ description: "HTML 原生设计技能:高保真原型 / 幻灯片 / 动画 + 20 设计哲学 + 5 维评审 + MP4 导出",
125580
+ source: "https://github.com/alchaincyf/huashu-design"
125581
+ },
125582
+ {
125583
+ id: "html-video",
125584
+ displayName: "HTML Video 视频生成",
125585
+ description: "HTML 转 MP4:可插拔渲染引擎 + 21 套模板 + AI 配乐,全程本地,零渲染费用",
125586
+ source: "https://github.com/nexu-io/html-video"
125587
+ },
125588
+ {
125589
+ id: "xiaohu-video-translate",
125590
+ displayName: "小壶视频翻译",
125591
+ description: "外语视频自动配中文字幕:下载 / 转写 / 翻译 / 润色 / 烧录一条龙,全程本地",
125592
+ source: "https://github.com/xiaohuailabs/xiaohu-video-translate"
125593
+ },
125594
+ {
125595
+ id: "videocut-skills",
125596
+ displayName: "视频剪辑 Agent",
125597
+ description: "Claude Code Skills 驱动的视频剪辑 Agent:口播剪辑 / 字幕导入 / 画质高清化",
125598
+ source: "https://github.com/Ceeon/videocut-skills"
125599
+ },
125600
+ {
125601
+ id: "taste-skill",
125602
+ displayName: "Taste Skill 设计品味",
125603
+ description: "给 AI 好品味:阻止生成无聊通用的设计,输出有质感的方案",
125604
+ source: "https://github.com/Leonxlnx/taste-skill"
125605
+ },
125606
+ {
125607
+ id: "vtake-skills",
125608
+ displayName: "VTake 视频剪辑",
125609
+ description: "Agent Skills 驱动的视频剪辑工具",
125610
+ source: "https://github.com/notedit/vtake-skills"
125611
+ },
125612
+ {
125613
+ id: "remotion-skills",
125614
+ displayName: "Remotion 视频技能",
125615
+ description: "Remotion(React 视频框架)官方技能包",
125616
+ source: "https://github.com/remotion-dev/skills"
125617
+ },
125618
+ {
125619
+ id: "html-anything",
125620
+ displayName: "HTML Anything 全能设计",
125621
+ description: "75 个技能 × 9 种场景:杂志 / 幻灯片 / 海报 / 小红书 / 数据报告 / 原型,零 API 密钥",
125622
+ source: "https://github.com/nexu-io/html-anything"
125623
+ },
125624
+ {
125625
+ id: "guizang-social-card-skill",
125626
+ displayName: "归藏社交卡片",
125627
+ description: "小红书轮播图 + 公众号封面:28 种布局 × 10 套主题,Editorial × Swiss 视觉体系,单文件 HTML → PNG",
125628
+ source: "https://github.com/op7418/guizang-social-card-skill"
125444
125629
  }
125445
125630
  ];
125446
125631
  async function handlePluginCommand(host, _args) {
@@ -125542,10 +125727,33 @@ async function loadInstalled(host) {
125542
125727
  return [];
125543
125728
  }
125544
125729
  }
125730
+ /**
125731
+ * Normalize a GitHub URL to `{owner}/{repo}` for fuzzy matching.
125732
+ * Returns `null` for non-GitHub URLs (caller falls back to exact match).
125733
+ */
125734
+ function normalizeGithubSource(url) {
125735
+ try {
125736
+ const u = new URL(url.trim());
125737
+ if (u.hostname !== "github.com" && u.hostname !== "www.github.com") return null;
125738
+ const segments = u.pathname.split("/").filter((s) => s.length > 0);
125739
+ if (segments.length < 2) return null;
125740
+ return `${segments[0]}/${segments[1].replace(/\.git$/, "")}`.toLowerCase();
125741
+ } catch {
125742
+ return null;
125743
+ }
125744
+ }
125745
+ function isInstalled(marketplaceId, marketplaceSource, installed) {
125746
+ if (installed.some((p) => p.id === marketplaceId)) return true;
125747
+ const normalizedSource = normalizeGithubSource(marketplaceSource);
125748
+ if (normalizedSource === null) return false;
125749
+ return installed.some((p) => {
125750
+ const norm = normalizeGithubSource(p.originalSource ?? "");
125751
+ return norm !== null && norm === normalizedSource;
125752
+ });
125753
+ }
125545
125754
  function buildOptions(marketplace, installed) {
125546
125755
  const options = [];
125547
- const installedIds = new Set(installed.map((p) => p.id));
125548
- const newPlugins = marketplace.filter((p) => !installedIds.has(p.id));
125756
+ const newPlugins = marketplace.filter((p) => !isInstalled(p.id, p.source, installed));
125549
125757
  if (newPlugins.length > 0) {
125550
125758
  options.push({
125551
125759
  value: "__section__marketplace",
@@ -134733,6 +134941,7 @@ var ScreamTUI = class {
134733
134941
  return this.state.loadingSessions;
134734
134942
  }
134735
134943
  async deleteSession(sessionId) {
134944
+ if (sessionId === this.session?.id) await this.sessionManager.closeSession("session deleted");
134736
134945
  await this.harness.deleteSession(sessionId);
134737
134946
  }
134738
134947
  async getStartupMcpMs() {
@@ -135169,6 +135378,7 @@ var ScreamTUI = class {
135169
135378
  //#region src/tui/components/chrome/loading.ts
135170
135379
  const { stdout } = process$1;
135171
135380
  const BRIGHT = "\x1B[38;2;255;255;255m";
135381
+ const RESET = "\x1B[0m";
135172
135382
  const THEME_GREEN = {
135173
135383
  dark: "\x1B[38;2;78;200;126m",
135174
135384
  light: "\x1B[38;2;14;122;56m"
@@ -135280,17 +135490,17 @@ function supportsAnsi() {
135280
135490
  ansiSupported = false;
135281
135491
  return false;
135282
135492
  }
135283
- if (process$1.env.NO_COLOR) {
135493
+ if (process$1.env["NO_COLOR"]) {
135284
135494
  ansiSupported = false;
135285
135495
  return false;
135286
135496
  }
135287
- if (process$1.env.FORCE_COLOR) {
135497
+ if (process$1.env["FORCE_COLOR"]) {
135288
135498
  ansiSupported = true;
135289
135499
  return true;
135290
135500
  }
135291
135501
  if (process$1.platform === "win32") {
135292
- const term = (process$1.env.TERM ?? "").toLowerCase();
135293
- const session = (process$1.env.TERM_PROGRAM ?? "").toLowerCase();
135502
+ const term = (process$1.env["TERM"] ?? "").toLowerCase();
135503
+ const session = (process$1.env["TERM_PROGRAM"] ?? "").toLowerCase();
135294
135504
  if (term.includes("xterm") || term.includes("vt100") || term.includes("256color")) {
135295
135505
  ansiSupported = true;
135296
135506
  return true;
@@ -135299,14 +135509,14 @@ function supportsAnsi() {
135299
135509
  ansiSupported = true;
135300
135510
  return true;
135301
135511
  }
135302
- if (process$1.env.CI) {
135512
+ if (process$1.env["CI"]) {
135303
135513
  ansiSupported = true;
135304
135514
  return true;
135305
135515
  }
135306
135516
  ansiSupported = true;
135307
135517
  return true;
135308
135518
  }
135309
- if (process$1.env.TERM && process$1.env.TERM !== "dumb") {
135519
+ if (process$1.env["TERM"] && process$1.env["TERM"] !== "dumb") {
135310
135520
  ansiSupported = true;
135311
135521
  return true;
135312
135522
  }
@@ -135319,11 +135529,23 @@ function plainFrame(frameIndex, green) {
135319
135529
  for (const l of f.content) out += color(l, green) + (l || " ") + "\x1B[0m\n";
135320
135530
  return out;
135321
135531
  }
135532
+ const LOADING_STAGES = [
135533
+ "正在整理记忆区域....",
135534
+ "正在加载用户喜好....",
135535
+ "正在确认模型配置....",
135536
+ "正在加载scream code...."
135537
+ ];
135538
+ const STAGE_DELAYS_MS = [
135539
+ 450,
135540
+ 350,
135541
+ 650,
135542
+ 350
135543
+ ];
135322
135544
  function runLoadingAnimation(theme = "dark") {
135323
135545
  const green = THEME_GREEN[theme];
135324
135546
  if (!supportsAnsi()) {
135325
135547
  stdout.write(plainFrame(FRAMES.length - 1, green));
135326
- stdout.write("\n\x1B[38;2;136;136;136m正在加载scream code....\x1B[0m\n");
135548
+ for (const stage of LOADING_STAGES) stdout.write(green + stage + "\x1B[0m\n");
135327
135549
  return Promise.resolve();
135328
135550
  }
135329
135551
  return new Promise((resolve) => {
@@ -135332,14 +135554,22 @@ function runLoadingAnimation(theme = "dark") {
135332
135554
  function draw(i) {
135333
135555
  stdout.write("\x1B[H" + plainFrame(i, green));
135334
135556
  }
135557
+ function showStages(stages, index) {
135558
+ if (index >= stages.length) {
135559
+ setTimeout(() => {
135560
+ stdout.write("\x1B[2J\x1B[H\x1B[?25h");
135561
+ resolve();
135562
+ }, 400);
135563
+ return;
135564
+ }
135565
+ stdout.write("\n" + green + stages[index] + RESET);
135566
+ const delay = STAGE_DELAYS_MS[index] ?? 350;
135567
+ setTimeout(() => showStages(stages, index + 1), delay);
135568
+ }
135335
135569
  function tick() {
135336
135570
  if (frame >= last) {
135337
135571
  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);
135572
+ showStages(LOADING_STAGES, 0);
135343
135573
  }, 600);
135344
135574
  return;
135345
135575
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "The Starting Point for Next-Gen Agents",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",