scream-code 0.4.0 → 0.4.2

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 +444 -185
  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 = [];
@@ -85152,6 +85268,11 @@ async function runToolCallBatch(step, response) {
85152
85268
  try {
85153
85269
  for (let index = 0; index < calls.length; index += 1) {
85154
85270
  const call = calls[index];
85271
+ if (step.signal.aborted) {
85272
+ await dispatchToolCall(step, call, call.args);
85273
+ pendingResults.push(Promise.resolve(makeErrorToolResult(call, call.args, abortedToolOutput(call.toolName, step.signal))));
85274
+ continue;
85275
+ }
85155
85276
  const prepared = await prepareToolCall(step, call);
85156
85277
  pendingResults.push(scheduler.add(prepared.task));
85157
85278
  if (prepared.stopBatchAfterThis === true) {
@@ -85499,7 +85620,7 @@ async function finalizePendingToolResult(step, pendingResult) {
85499
85620
  async function executeTool(step, execution, toolCall, toolName, metadata) {
85500
85621
  const { dispatchEvent, signal, turnId } = step;
85501
85622
  signal.throwIfAborted();
85502
- return raceExecuteWithGraceAndHardTimeout(execution.execute({
85623
+ return raceExecuteWithGraceTimeout(execution.execute({
85503
85624
  turnId,
85504
85625
  toolCallId: toolCall.id,
85505
85626
  metadata,
@@ -85514,10 +85635,8 @@ async function executeTool(step, execution, toolCall, toolName, metadata) {
85514
85635
  }
85515
85636
  }), signal, toolName);
85516
85637
  }
85517
- const HARD_TIMEOUT_MS = 12e5;
85518
- async function raceExecuteWithGraceAndHardTimeout(executePromise, signal, toolName) {
85638
+ async function raceExecuteWithGraceTimeout(executePromise, signal, toolName) {
85519
85639
  let graceTimer;
85520
- let hardTimer;
85521
85640
  let onAbort;
85522
85641
  const graceSentinel = new Promise((resolve) => {
85523
85642
  const armTimer = () => {
@@ -85534,23 +85653,10 @@ async function raceExecuteWithGraceAndHardTimeout(executePromise, signal, toolNa
85534
85653
  signal.addEventListener("abort", onAbort, { once: true });
85535
85654
  }
85536
85655
  });
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
85656
  try {
85546
- return await Promise.race([
85547
- executePromise,
85548
- graceSentinel,
85549
- hardSentinel
85550
- ]);
85657
+ return await Promise.race([executePromise, graceSentinel]);
85551
85658
  } finally {
85552
85659
  if (graceTimer !== void 0) clearTimeout(graceTimer);
85553
- if (hardTimer !== void 0) clearTimeout(hardTimer);
85554
85660
  if (onAbort !== void 0) try {
85555
85661
  signal.removeEventListener("abort", onAbort);
85556
85662
  } catch {}
@@ -97111,6 +97217,16 @@ async function parseManifest(pluginRoot) {
97111
97217
  manifestPath: skillMdPath,
97112
97218
  diagnostics: []
97113
97219
  };
97220
+ const discoveredSkillDirs = await discoverSkillDirs(pluginRoot, 3);
97221
+ if (discoveredSkillDirs.length > 0) return {
97222
+ manifest: {
97223
+ name: path.basename(pluginRoot),
97224
+ skills: discoveredSkillDirs
97225
+ },
97226
+ manifestKind: "bare-skill",
97227
+ manifestPath: path.join(discoveredSkillDirs[0], BARE_SKILL_PATH),
97228
+ diagnostics: []
97229
+ };
97114
97230
  return { diagnostics: [{
97115
97231
  severity: "error",
97116
97232
  message: `No manifest at ${SCREAM_PLUGIN_ROOT_PATH}, ${SCREAM_PLUGIN_DIR_PATH}, or ${CLAUDE_PLUGIN_DIR_PATH}`
@@ -97409,6 +97525,34 @@ function isWithin$1(child, parent) {
97409
97525
  const relative = path.relative(parent, child);
97410
97526
  return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
97411
97527
  }
97528
+ /**
97529
+ * Recursively scan for `SKILL.md` files up to `maxDepth` levels below `root`.
97530
+ * Returns the unique parent directories, deduplicated so that a nested skill
97531
+ * dir is not listed if its ancestor is already a skill root.
97532
+ */
97533
+ async function discoverSkillDirs(root, maxDepth) {
97534
+ const found = [];
97535
+ async function walk(dir, depth) {
97536
+ if (depth > maxDepth) return;
97537
+ let entries;
97538
+ try {
97539
+ entries = await readdir(dir, { withFileTypes: true });
97540
+ } catch {
97541
+ return;
97542
+ }
97543
+ for (const entry of entries) {
97544
+ if (!entry.isDirectory()) continue;
97545
+ const child = path.join(dir, entry.name);
97546
+ if (await isFile$1(path.join(child, BARE_SKILL_PATH))) found.push(child);
97547
+ await walk(child, depth + 1);
97548
+ }
97549
+ }
97550
+ await walk(root, 1);
97551
+ const sorted = found.sort((a, b) => a.length - b.length);
97552
+ const result = [];
97553
+ for (const dir of sorted) if (!result.some((parent) => dir.startsWith(parent + path.sep))) result.push(dir);
97554
+ return result;
97555
+ }
97412
97556
  async function isFile$1(p) {
97413
97557
  try {
97414
97558
  return (await stat(p)).isFile();
@@ -112953,12 +113097,15 @@ var SessionStore = class {
112953
113097
  async create(input) {
112954
113098
  assertSafeSessionId(input.id);
112955
113099
  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
113100
  const dir = this.sessionDirFor({
112958
113101
  id: input.id,
112959
113102
  workDir
112960
113103
  });
112961
- if (await isDirectory(dir)) throw new ScreamError(ErrorCodes.SESSION_ALREADY_EXISTS, `Session "${input.id}" already exists`);
113104
+ if (await this.findSessionEntry(input.id) !== void 0) await this.delete(input.id);
113105
+ else if (await isDirectory(dir)) await rm(dir, {
113106
+ recursive: true,
113107
+ force: true
113108
+ });
112962
113109
  await mkdir(dir, {
112963
113110
  recursive: true,
112964
113111
  mode: 448
@@ -113029,11 +113176,12 @@ var SessionStore = class {
113029
113176
  }
113030
113177
  async delete(id) {
113031
113178
  assertSafeSessionId(id);
113032
- await rm((await this.findExistingSessionEntry(id)).sessionDir, {
113179
+ const entry = await this.findSessionEntry(id);
113180
+ if (entry !== void 0) await rm(entry.sessionDir, {
113033
113181
  recursive: true,
113034
113182
  force: true
113035
- });
113036
- await removeSessionIndexEntry(this.homeDir, id);
113183
+ }).catch(() => {});
113184
+ await removeSessionIndexEntry(this.homeDir, id).catch(() => {});
113037
113185
  }
113038
113186
  async list(options = {}) {
113039
113187
  const workDir = options.workDir === void 0 ? void 0 : normalizeRequiredWorkDir(options.workDir);
@@ -125364,12 +125512,6 @@ const BUILTIN_REGISTRY = [
125364
125512
  description: "GreenSock 动画平台全套参考手册,含核心 API、Timeline、ScrollTrigger、插件、React 集成等 8 个技能",
125365
125513
  source: "https://github.com/greensock/gsap-skills"
125366
125514
  },
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
125515
  {
125374
125516
  id: "claude-design-card",
125375
125517
  displayName: "Claude Design Card",
@@ -125412,18 +125554,6 @@ const BUILTIN_REGISTRY = [
125412
125554
  description: "专利交底书自动生成:专利点挖掘 → 国知局查新 → 脱敏成文 → 自检闭环,Mermaid 附图,输出 .docx",
125413
125555
  source: "https://github.com/handsomestWei/patent-disclosure-skill"
125414
125556
  },
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
125557
  {
125428
125558
  id: "contract-review-pro",
125429
125559
  displayName: "Contract Review Pro 合同审查",
@@ -125441,6 +125571,66 @@ const BUILTIN_REGISTRY = [
125441
125571
  displayName: "Headroom 压缩优化",
125442
125572
  description: "在内容送达 LLM 前压缩工具输出、日志、文件和 RAG 块,节省 60-95% Token,答案质量不变",
125443
125573
  source: "https://github.com/chopratejas/headroom"
125574
+ },
125575
+ {
125576
+ id: "xiaohu-wechat-format",
125577
+ displayName: "小壶公众号排版",
125578
+ description: "Markdown → 微信兼容 HTML → 推送草稿箱,30 套主题 + 可视化画廊,一键排版发布",
125579
+ source: "https://github.com/xiaohuailabs/xiaohu-wechat-format"
125580
+ },
125581
+ {
125582
+ id: "huashu-design",
125583
+ displayName: "花束设计",
125584
+ description: "HTML 原生设计技能:高保真原型 / 幻灯片 / 动画 + 20 设计哲学 + 5 维评审 + MP4 导出",
125585
+ source: "https://github.com/alchaincyf/huashu-design"
125586
+ },
125587
+ {
125588
+ id: "html-video",
125589
+ displayName: "HTML Video 视频生成",
125590
+ description: "HTML 转 MP4:可插拔渲染引擎 + 21 套模板 + AI 配乐,全程本地,零渲染费用",
125591
+ source: "https://github.com/nexu-io/html-video"
125592
+ },
125593
+ {
125594
+ id: "xiaohu-video-translate",
125595
+ displayName: "小壶视频翻译",
125596
+ description: "外语视频自动配中文字幕:下载 / 转写 / 翻译 / 润色 / 烧录一条龙,全程本地",
125597
+ source: "https://github.com/xiaohuailabs/xiaohu-video-translate"
125598
+ },
125599
+ {
125600
+ id: "videocut-skills",
125601
+ displayName: "视频剪辑 Agent",
125602
+ description: "Claude Code Skills 驱动的视频剪辑 Agent:口播剪辑 / 字幕导入 / 画质高清化",
125603
+ source: "https://github.com/Ceeon/videocut-skills"
125604
+ },
125605
+ {
125606
+ id: "taste-skill",
125607
+ displayName: "Taste Skill 设计品味",
125608
+ description: "给 AI 好品味:阻止生成无聊通用的设计,输出有质感的方案",
125609
+ source: "https://github.com/Leonxlnx/taste-skill"
125610
+ },
125611
+ {
125612
+ id: "vtake-skills",
125613
+ displayName: "VTake 视频剪辑",
125614
+ description: "Agent Skills 驱动的视频剪辑工具",
125615
+ source: "https://github.com/notedit/vtake-skills"
125616
+ },
125617
+ {
125618
+ id: "remotion-skills",
125619
+ displayName: "Remotion 视频技能",
125620
+ description: "Remotion(React 视频框架)官方技能包",
125621
+ source: "https://github.com/remotion-dev/skills"
125622
+ },
125623
+ {
125624
+ id: "html-anything",
125625
+ displayName: "HTML Anything 全能设计",
125626
+ description: "75 个技能 × 9 种场景:杂志 / 幻灯片 / 海报 / 小红书 / 数据报告 / 原型,零 API 密钥",
125627
+ source: "https://github.com/nexu-io/html-anything"
125628
+ },
125629
+ {
125630
+ id: "guizang-social-card-skill",
125631
+ displayName: "归藏社交卡片",
125632
+ description: "小红书轮播图 + 公众号封面:28 种布局 × 10 套主题,Editorial × Swiss 视觉体系,单文件 HTML → PNG",
125633
+ source: "https://github.com/op7418/guizang-social-card-skill"
125444
125634
  }
125445
125635
  ];
125446
125636
  async function handlePluginCommand(host, _args) {
@@ -125542,10 +125732,33 @@ async function loadInstalled(host) {
125542
125732
  return [];
125543
125733
  }
125544
125734
  }
125735
+ /**
125736
+ * Normalize a GitHub URL to `{owner}/{repo}` for fuzzy matching.
125737
+ * Returns `null` for non-GitHub URLs (caller falls back to exact match).
125738
+ */
125739
+ function normalizeGithubSource(url) {
125740
+ try {
125741
+ const u = new URL(url.trim());
125742
+ if (u.hostname !== "github.com" && u.hostname !== "www.github.com") return null;
125743
+ const segments = u.pathname.split("/").filter((s) => s.length > 0);
125744
+ if (segments.length < 2) return null;
125745
+ return `${segments[0]}/${segments[1].replace(/\.git$/, "")}`.toLowerCase();
125746
+ } catch {
125747
+ return null;
125748
+ }
125749
+ }
125750
+ function isInstalled(marketplaceId, marketplaceSource, installed) {
125751
+ if (installed.some((p) => p.id === marketplaceId)) return true;
125752
+ const normalizedSource = normalizeGithubSource(marketplaceSource);
125753
+ if (normalizedSource === null) return false;
125754
+ return installed.some((p) => {
125755
+ const norm = normalizeGithubSource(p.originalSource ?? "");
125756
+ return norm !== null && norm === normalizedSource;
125757
+ });
125758
+ }
125545
125759
  function buildOptions(marketplace, installed) {
125546
125760
  const options = [];
125547
- const installedIds = new Set(installed.map((p) => p.id));
125548
- const newPlugins = marketplace.filter((p) => !installedIds.has(p.id));
125761
+ const newPlugins = marketplace.filter((p) => !isInstalled(p.id, p.source, installed));
125549
125762
  if (newPlugins.length > 0) {
125550
125763
  options.push({
125551
125764
  value: "__section__marketplace",
@@ -131319,6 +131532,15 @@ function createTUIState(options) {
131319
131532
  const theme = createScreamTUIThemeBundle(initialAppState.theme, options.resolvedTheme);
131320
131533
  const terminal = new ProcessTerminal();
131321
131534
  const ui = new TUI(terminal);
131535
+ const uiAny = ui;
131536
+ const originalDoRender = uiAny["doRender"].bind(ui);
131537
+ uiAny["doRender"] = () => {
131538
+ try {
131539
+ originalDoRender();
131540
+ } catch (error) {
131541
+ console.error("[scream-code] render error:", error);
131542
+ }
131543
+ };
131322
131544
  const transcriptContainer = new GutterContainer(1, 1);
131323
131545
  const activityContainer = new GutterContainer(1, 1);
131324
131546
  const todoPanelContainer = new GutterContainer(1, 1);
@@ -134733,6 +134955,7 @@ var ScreamTUI = class {
134733
134955
  return this.state.loadingSessions;
134734
134956
  }
134735
134957
  async deleteSession(sessionId) {
134958
+ if (sessionId === this.session?.id) await this.sessionManager.closeSession("session deleted");
134736
134959
  await this.harness.deleteSession(sessionId);
134737
134960
  }
134738
134961
  async getStartupMcpMs() {
@@ -135167,111 +135390,114 @@ var ScreamTUI = class {
135167
135390
  };
135168
135391
  //#endregion
135169
135392
  //#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"
135393
+ const { stdout, stdin } = process$1;
135394
+ const LOGO = [
135395
+ "███████╗ ██████╗██████╗ ███████╗ █████╗ ███╗ ███╗ ██████╗ ██████╗ ██████╗ ███████╗",
135396
+ "██╔════╝██╔════╝██╔══██╗██╔════╝██╔══██╗████╗ ████║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝",
135397
+ "███████╗██║ ██████╔╝█████╗ ███████║██╔████╔██║ ██║ ██║ ██║██║ ██║█████╗ ",
135398
+ "╚════██║██║ ██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║ ██║ ██║ ██║██║ ██║██╔══╝ ",
135399
+ "███████║╚██████╗██║ ██║███████╗██║ ██║██║ ╚═╝ ██║ ╚██████╗╚██████╔╝██████╔╝███████╗",
135400
+ "╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝"
135401
+ ];
135402
+ const SHADOW_CHARS = new Set([
135403
+ "╚",
135404
+ "═",
135405
+ "╝",
135406
+ "║",
135407
+ "╔",
135408
+ "╗",
135409
+ "╠",
135410
+ "╣",
135411
+ "╦",
135412
+ "╩",
135413
+ "╬"
135414
+ ]);
135415
+ const SHEEN_STEP = 2;
135416
+ const SHEEN_INTERVAL_MS = 150;
135417
+ const THEME_ACCENT = {
135418
+ dark: [
135419
+ 78,
135420
+ 200,
135421
+ 126
135422
+ ],
135423
+ light: [
135424
+ 14,
135425
+ 122,
135426
+ 56
135427
+ ]
135175
135428
  };
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
- }
135429
+ const BLOCK_RGB = [
135430
+ 255,
135431
+ 255,
135432
+ 255
135433
+ ];
135434
+ const LOGO_RGB = [
135435
+ 136,
135436
+ 136,
135437
+ 136
135267
135438
  ];
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;
135439
+ const DIM_RGB = [
135440
+ 85,
135441
+ 85,
135442
+ 85
135443
+ ];
135444
+ function fg(r, g, b) {
135445
+ return `\x1b[38;2;${r};${g};${b}m`;
135446
+ }
135447
+ const RESET = "\x1B[0m";
135448
+ const BOLD = "\x1B[1m";
135449
+ const DIM = "\x1B[2m";
135450
+ function renderSheen(char, charIndex, sheenPos, isReversing, accent) {
135451
+ if (char === " ") return " ";
135452
+ if (char === "█") return `${fg(...BLOCK_RGB)}█${RESET}`;
135453
+ if (!SHADOW_CHARS.has(char)) return `${fg(...LOGO_RGB)}${char}${RESET}`;
135454
+ let color;
135455
+ if (isReversing) color = charIndex <= sheenPos ? LOGO_RGB : accent;
135456
+ else color = charIndex <= sheenPos ? accent : LOGO_RGB;
135457
+ return `${fg(...color)}${char}${RESET}`;
135458
+ }
135459
+ const LOADING_TEXT = "加载中...";
135460
+ function buildShimmerPalette(n, accent) {
135461
+ const size = Math.max(8, Math.min(20, Math.ceil(n * 1.5)));
135462
+ const palette = [];
135463
+ for (let i = 0; i < size; i++) {
135464
+ const t = i / (size - 1);
135465
+ palette.push([
135466
+ Math.round(accent[0] - t * accent[0] * .35),
135467
+ Math.round(accent[1] - t * accent[1] * .6),
135468
+ Math.round(accent[2] - t * accent[2] * .33)
135469
+ ]);
135470
+ }
135471
+ return palette;
135472
+ }
135473
+ function renderShimmer(pulse, accent) {
135474
+ const chars = LOADING_TEXT.split("");
135475
+ const n = chars.length;
135476
+ const palette = buildShimmerPalette(n, accent);
135477
+ let out = "";
135478
+ for (let i = 0; i < n; i++) {
135479
+ const phase = (pulse - i + n) % n;
135480
+ const color = palette[phase];
135481
+ const ratio = n <= 1 ? 0 : phase / (n - 1);
135482
+ out += `${ratio < .23 ? BOLD : ratio < .69 ? "" : DIM}${fg(...color)}${chars[i]}${RESET}`;
135483
+ }
135484
+ return out;
135485
+ }
135486
+ function getTerminalSize() {
135487
+ return {
135488
+ cols: stdout.columns || 80,
135489
+ rows: stdout.rows || 24
135490
+ };
135491
+ }
135492
+ function visualWidth(s) {
135493
+ let w = 0;
135494
+ for (const ch of s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "")) w += /[一-鿿 -〿＀-￯]/.test(ch) ? 2 : 1;
135495
+ return w;
135496
+ }
135497
+ function centerPad(text, width) {
135498
+ const plainW = visualWidth(text);
135499
+ const pad = Math.max(0, Math.floor((width - plainW) / 2));
135500
+ return " ".repeat(pad) + text;
135275
135501
  }
135276
135502
  let ansiSupported = null;
135277
135503
  function supportsAnsi() {
@@ -135280,17 +135506,17 @@ function supportsAnsi() {
135280
135506
  ansiSupported = false;
135281
135507
  return false;
135282
135508
  }
135283
- if (process$1.env.NO_COLOR) {
135509
+ if (process$1.env["NO_COLOR"]) {
135284
135510
  ansiSupported = false;
135285
135511
  return false;
135286
135512
  }
135287
- if (process$1.env.FORCE_COLOR) {
135513
+ if (process$1.env["FORCE_COLOR"]) {
135288
135514
  ansiSupported = true;
135289
135515
  return true;
135290
135516
  }
135291
135517
  if (process$1.platform === "win32") {
135292
- const term = (process$1.env.TERM ?? "").toLowerCase();
135293
- const session = (process$1.env.TERM_PROGRAM ?? "").toLowerCase();
135518
+ const term = (process$1.env["TERM"] ?? "").toLowerCase();
135519
+ const session = (process$1.env["TERM_PROGRAM"] ?? "").toLowerCase();
135294
135520
  if (term.includes("xterm") || term.includes("vt100") || term.includes("256color")) {
135295
135521
  ansiSupported = true;
135296
135522
  return true;
@@ -135299,57 +135525,90 @@ function supportsAnsi() {
135299
135525
  ansiSupported = true;
135300
135526
  return true;
135301
135527
  }
135302
- if (process$1.env.CI) {
135528
+ if (process$1.env["CI"]) {
135303
135529
  ansiSupported = true;
135304
135530
  return true;
135305
135531
  }
135306
135532
  ansiSupported = true;
135307
135533
  return true;
135308
135534
  }
135309
- if (process$1.env.TERM && process$1.env.TERM !== "dumb") {
135535
+ if (process$1.env["TERM"] && process$1.env["TERM"] !== "dumb") {
135310
135536
  ansiSupported = true;
135311
135537
  return true;
135312
135538
  }
135313
135539
  ansiSupported = false;
135314
135540
  return false;
135315
135541
  }
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
135542
  function runLoadingAnimation(theme = "dark") {
135323
- const green = THEME_GREEN[theme];
135324
135543
  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");
135544
+ for (const line of LOGO) stdout.write(`${fg(...LOGO_RGB)}${line}${RESET}\n`);
135327
135545
  return Promise.resolve();
135328
135546
  }
135329
135547
  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));
135548
+ stdout.write("\x1B[?1049h");
135549
+ stdout.write("\x1B[2J");
135550
+ stdout.write("\x1B[?25l");
135551
+ const accent = THEME_ACCENT[theme];
135552
+ let sheenPos = 0;
135553
+ let isReversing = false;
135554
+ let shimmerPulse = 0;
135555
+ let phase = "loading";
135556
+ function render() {
135557
+ const { cols, rows } = getTerminalSize();
135558
+ const lines = [];
135559
+ const contentHeight = LOGO.length + 4;
135560
+ const topPad = Math.max(0, Math.floor((rows - contentHeight) / 2));
135561
+ for (let i = 0; i < topPad; i++) lines.push("");
135562
+ for (const line of LOGO) {
135563
+ let colored = "";
135564
+ for (let ci = 0; ci < line.length; ci++) colored += renderSheen(line[ci], ci, sheenPos, isReversing, accent);
135565
+ lines.push(centerPad(colored, cols));
135566
+ }
135567
+ if (phase === "loading") lines.push(centerPad(renderShimmer(shimmerPulse, accent), cols));
135568
+ else lines.push(centerPad(`${BOLD}${fg(...accent)}点击 ENTER 进入${RESET}`, cols));
135569
+ lines.push("");
135570
+ lines.push("");
135571
+ lines.push(centerPad(`${fg(...DIM_RGB)}按 Ctrl+C 即可退出Scream Code${RESET}`, cols));
135572
+ while (lines.length < rows) lines.push("");
135573
+ stdout.write("\x1B[H");
135574
+ stdout.write(lines.join("\n"));
135334
135575
  }
135335
135576
  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;
135577
+ sheenPos += SHEEN_STEP;
135578
+ if (sheenPos >= 90) {
135579
+ isReversing = !isReversing;
135580
+ sheenPos = 0;
135345
135581
  }
135346
- draw(frame);
135347
- frame++;
135348
- setTimeout(tick, FRAMES[frame - 1].duration);
135582
+ shimmerPulse = (shimmerPulse + 1) % 6;
135583
+ render();
135349
135584
  }
135350
- stdout.write("\x1B[?25l\x1B[2J\x1B[H");
135351
- draw(0);
135352
- setTimeout(tick, FRAMES[0].duration);
135585
+ function cleanup() {
135586
+ clearInterval(timer);
135587
+ stdin.removeAllListeners("data");
135588
+ stdin.setRawMode(false);
135589
+ stdout.write("\x1B[?25h");
135590
+ stdout.write("\x1B[?1049l");
135591
+ }
135592
+ function interrupt() {
135593
+ cleanup();
135594
+ process$1.exit(0);
135595
+ }
135596
+ process$1.on("SIGINT", interrupt);
135597
+ process$1.on("SIGTERM", interrupt);
135598
+ stdin.setRawMode(true);
135599
+ stdin.on("data", (data) => {
135600
+ const key = data.toString();
135601
+ if ((key === "\r" || key === "\n") && phase === "ready") {
135602
+ cleanup();
135603
+ resolve();
135604
+ }
135605
+ });
135606
+ render();
135607
+ const timer = setInterval(tick, SHEEN_INTERVAL_MS);
135608
+ setTimeout(() => {
135609
+ phase = "ready";
135610
+ render();
135611
+ }, 400);
135353
135612
  });
135354
135613
  }
135355
135614
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "The Starting Point for Next-Gen Agents",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",