scream-code 0.3.7 → 0.3.8

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 +176 -22
  2. package/package.json +24 -24
package/dist/main.mjs CHANGED
@@ -87211,10 +87211,25 @@ var Session$1 = class {
87211
87211
  const main = this.agents.get("main");
87212
87212
  const profile = DEFAULT_AGENT_PROFILES["agent"];
87213
87213
  if (main !== void 0 && profile !== void 0) await this.bootstrapAgentProfile(main, profile);
87214
+ if (main !== void 0 && this.metadata.custom?.["recap"]) main.context.appendSystemReminder(this.metadata.custom["recap"], {
87215
+ kind: "injection",
87216
+ variant: "recap"
87217
+ });
87214
87218
  await this.triggerSessionStart("resume");
87215
87219
  return { warning: resumeWarning };
87216
87220
  }
87217
87221
  async close() {
87222
+ const main = this.agents.get("main");
87223
+ if (main !== void 0) {
87224
+ const recap = main.sessionMemory.getSessionSummary();
87225
+ if (recap.length > 0) {
87226
+ this.metadata.custom = {
87227
+ ...this.metadata.custom,
87228
+ recap
87229
+ };
87230
+ this.writeMetadata();
87231
+ }
87232
+ }
87218
87233
  try {
87219
87234
  await Promise.allSettled(Array.from(this.agents.values(), async (agent) => agent.cron?.stop()));
87220
87235
  await this.stopBackgroundTasksOnExit();
@@ -103346,7 +103361,13 @@ function slugifyWorkDirName(name) {
103346
103361
  const WORKDIR_KEY_PREFIX = "wd_";
103347
103362
  const HASH_LENGTH = 12;
103348
103363
  function normalizeWorkDir(workDir) {
103349
- return resolve$1(workDir);
103364
+ const resolved = resolve$1(workDir);
103365
+ let dir = resolved;
103366
+ while (dir !== dirname$2(dir)) {
103367
+ if (existsSync(join$1(dir, ".git")) || existsSync(join$1(dir, "package.json"))) return dir;
103368
+ dir = dirname$2(dir);
103369
+ }
103370
+ return resolved;
103350
103371
  }
103351
103372
  function encodeWorkDirKey(workDir) {
103352
103373
  const normalized = normalizeWorkDir(workDir);
@@ -115063,11 +115084,11 @@ function checkCcConnect$1() {
115063
115084
  }
115064
115085
  function detectScreamPath$1() {
115065
115086
  try {
115066
- const which = execSync(process.platform === "win32" ? "where scream" : "which scream 2>/dev/null", {
115087
+ const first = execSync(process.platform === "win32" ? "where scream" : "which scream 2>/dev/null", {
115067
115088
  encoding: "utf-8",
115068
115089
  timeout: 3e3
115069
- }).trim();
115070
- if (which) return `${which} stream-json`;
115090
+ }).trim().split(/[\r\n]+/)[0]?.trim() ?? "";
115091
+ if (first) return `${first} stream-json`;
115071
115092
  } catch {}
115072
115093
  return "scream stream-json";
115073
115094
  }
@@ -115075,18 +115096,40 @@ function readConfiguredType() {
115075
115096
  if (!existsSync(CONFIG_PATH)) return void 0;
115076
115097
  try {
115077
115098
  const content = readFileSync(CONFIG_PATH, "utf-8");
115099
+ let inPlatforms = false;
115078
115100
  for (const line of content.split("\n")) {
115079
- const m = line.match(/^type\s*=\s*"(\S+)"/);
115080
- if (m) return m[1];
115101
+ const trimmed = line.trim();
115102
+ if (trimmed === "[[projects.platforms]]") {
115103
+ inPlatforms = true;
115104
+ continue;
115105
+ }
115106
+ if (trimmed.startsWith("[[") && trimmed !== "[[projects.platforms]]") {
115107
+ inPlatforms = false;
115108
+ continue;
115109
+ }
115110
+ if (inPlatforms) {
115111
+ const m = line.match(/^type\s*=\s*"(\S+)"/);
115112
+ if (m) return m[1];
115113
+ }
115081
115114
  }
115082
115115
  return;
115083
115116
  } catch {
115084
115117
  return;
115085
115118
  }
115086
115119
  }
115120
+ function escapeSingleQuotes(str) {
115121
+ return str.replace(/'/g, "\\'");
115122
+ }
115087
115123
  function generateConfig$1(platform) {
115088
115124
  const dir = dirname$1(CONFIG_PATH);
115089
115125
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
115126
+ const platformBlock = `\n[[projects.platforms]]\ntype = "${platform.type}"\n`;
115127
+ if (existsSync(CONFIG_PATH)) {
115128
+ const existing = readFileSync(CONFIG_PATH, "utf-8");
115129
+ if (existing.includes(`type = "${platform.type}"`)) return;
115130
+ writeFileSync(CONFIG_PATH, existing + platformBlock, "utf-8");
115131
+ return;
115132
+ }
115090
115133
  writeFileSync(CONFIG_PATH, [
115091
115134
  "# 全局:允许/禁止图片和文件回传到聊天(on = 开启,off = 关闭)",
115092
115135
  "attachment_send = \"on\"",
@@ -115098,8 +115141,8 @@ function generateConfig$1(platform) {
115098
115141
  "type = \"claudecode\"",
115099
115142
  "",
115100
115143
  "[projects.agent.options]",
115101
- `cli_path = '${detectScreamPath$1()}'`,
115102
- `work_dir = '${process.cwd()}'`,
115144
+ `cli_path = '${escapeSingleQuotes(detectScreamPath$1())}'`,
115145
+ `work_dir = '${escapeSingleQuotes(process.cwd())}'`,
115103
115146
  "mode = \"default\"",
115104
115147
  "",
115105
115148
  "[[projects.platforms]]",
@@ -121098,7 +121141,7 @@ const TOOLBAR_TIPS = [
121098
121141
  priority: 3
121099
121142
  },
121100
121143
  {
121101
- text: "让 Scream 安排任务,例如 \"八点提醒我上号打三角洲\"",
121144
+ text: "让 Scream 安排任务,例如 \"2个小时后提醒我去拿快递\"",
121102
121145
  solo: true,
121103
121146
  priority: 3
121104
121147
  }
@@ -125908,11 +125951,11 @@ function detectScreamPath() {
125908
125951
  if (arg1.includes("scream-code") || arg1.includes("scream")) return `node ${arg1} stream-json`;
125909
125952
  }
125910
125953
  try {
125911
- const which = execSync("which scream 2>/dev/null", {
125954
+ const first = execSync("which scream 2>/dev/null", {
125912
125955
  encoding: "utf-8",
125913
125956
  timeout: 3e3
125914
- }).trim();
125915
- if (which) return `${which} stream-json`;
125957
+ }).trim().split(/[\r\n]+/)[0]?.trim() ?? "";
125958
+ if (first) return `${first} stream-json`;
125916
125959
  } catch {}
125917
125960
  return "scream stream-json";
125918
125961
  }
@@ -126199,6 +126242,18 @@ var ClaudeStreamJsonWriter = class {
126199
126242
  content: `Resume this session: scream -r ${sessionId}`
126200
126243
  });
126201
126244
  }
126245
+ /** Emit a `control_request` event for cc-connect permission flow. */
126246
+ emitControlRequest(requestId, toolCallId, toolName, input) {
126247
+ this.writeJson({
126248
+ type: "control_request",
126249
+ request_id: requestId,
126250
+ tool_use: {
126251
+ id: toolCallId,
126252
+ name: toolName,
126253
+ input
126254
+ }
126255
+ });
126256
+ }
126202
126257
  updateUsage(input, output) {
126203
126258
  if (input > 0) this.tokenUsage.input += input;
126204
126259
  if (output > 0) this.tokenUsage.output += output;
@@ -126248,6 +126303,39 @@ function extractUserText(msg) {
126248
126303
  if (typeof content === "string") return content;
126249
126304
  return content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
126250
126305
  }
126306
+ function mapCcConnectMode(mode) {
126307
+ switch (mode) {
126308
+ case "default": return {
126309
+ permission: "manual",
126310
+ planMode: false
126311
+ };
126312
+ case "acceptEdits": return {
126313
+ permission: "manual",
126314
+ planMode: false
126315
+ };
126316
+ case "plan": return {
126317
+ permission: "manual",
126318
+ planMode: true
126319
+ };
126320
+ case "auto": return {
126321
+ permission: "auto",
126322
+ planMode: false
126323
+ };
126324
+ case "yolo":
126325
+ case "bypassPermissions": return {
126326
+ permission: "yolo",
126327
+ planMode: false
126328
+ };
126329
+ case "dontAsk": return {
126330
+ permission: "manual",
126331
+ planMode: false
126332
+ };
126333
+ default: return {
126334
+ permission: "auto",
126335
+ planMode: false
126336
+ };
126337
+ }
126338
+ }
126251
126339
  async function runStreamJson(opts) {
126252
126340
  const workDir = opts.workDir ?? process.cwd();
126253
126341
  const telemetryBootstrap = createCliTelemetryBootstrap();
@@ -126264,6 +126352,9 @@ async function runStreamJson(opts) {
126264
126352
  });
126265
126353
  let session;
126266
126354
  let currentSessionId;
126355
+ let sessionKey = "cc-connect-main";
126356
+ const pendingApprovals = /* @__PURE__ */ new Map();
126357
+ const subagentNames = /* @__PURE__ */ new Map();
126267
126358
  const agentsMdPath = join(workDir, ".scream-code", "AGENTS.md");
126268
126359
  let originalAgentsMd;
126269
126360
  let injectedAgentsMd = false;
@@ -126272,7 +126363,12 @@ async function runStreamJson(opts) {
126272
126363
  originalAgentsMd = await readFile(agentsMdPath, "utf-8");
126273
126364
  } catch {}
126274
126365
  await mkdir(join(workDir, ".scream-code"), { recursive: true });
126275
- await writeFile$1(agentsMdPath, originalAgentsMd ? `${opts.appendSystemPrompt}\n\n${originalAgentsMd}` : opts.appendSystemPrompt, "utf-8");
126366
+ const ccPrompt = `【重要】你可以通过以下命令向用户发送图片或文件:
126367
+ cc-connect send --image /absolute/path/to/image.png
126368
+ cc-connect send --file /absolute/path/to/file.pdf
126369
+ 当用户要求你发送文件、截图、生成的图片时,使用 Bash 工具执行上述命令即可。
126370
+ \n${opts.appendSystemPrompt}`;
126371
+ await writeFile$1(agentsMdPath, originalAgentsMd ? `${ccPrompt}\n\n${originalAgentsMd}` : ccPrompt, "utf-8");
126276
126372
  injectedAgentsMd = true;
126277
126373
  log.info("stream-json: injected cc-connect system prompt into AGENTS.md");
126278
126374
  }
@@ -126281,10 +126377,16 @@ async function runStreamJson(opts) {
126281
126377
  const config = await harness.getConfig();
126282
126378
  for await (const msg of readStdinMessages()) {
126283
126379
  if (msg.type === "control_response") {
126284
- log.debug("stream-json: control_response received (auto-approved mode)", {
126285
- requestId: msg.response.request_id,
126286
- behavior: msg.response.response.behavior
126287
- });
126380
+ const pending = pendingApprovals.get(msg.response.request_id);
126381
+ if (pending) {
126382
+ pendingApprovals.delete(msg.response.request_id);
126383
+ const behavior = msg.response.response.behavior;
126384
+ pending.resolve({
126385
+ decision: behavior === "allow" ? "approved" : "rejected",
126386
+ scope: behavior === "allow" ? "session" : void 0,
126387
+ feedback: msg.response.response.message
126388
+ });
126389
+ } else log.warn("stream-json: control_response for unknown request_id", { requestId: msg.response.request_id });
126288
126390
  continue;
126289
126391
  }
126290
126392
  const userText = extractUserText(msg);
@@ -126292,7 +126394,8 @@ async function runStreamJson(opts) {
126292
126394
  log.warn("stream-json: empty user message, skipping");
126293
126395
  continue;
126294
126396
  }
126295
- const sessionKey = "cc-connect-main";
126397
+ sessionKey = opts.resume ?? "cc-connect-main";
126398
+ const { permission: mappedPermission, planMode: mappedPlanMode } = mapCcConnectMode(opts.permissionMode);
126296
126399
  if (!session) {
126297
126400
  const existing = await harness.listSessions({
126298
126401
  sessionId: sessionKey,
@@ -126317,7 +126420,8 @@ async function runStreamJson(opts) {
126317
126420
  id: sessionKey,
126318
126421
  workDir,
126319
126422
  model,
126320
- permission: "auto"
126423
+ permission: mappedPermission,
126424
+ planMode: mappedPlanMode
126321
126425
  });
126322
126426
  log.info("stream-json: recreated session", { sessionId: session.id });
126323
126427
  }
@@ -126327,7 +126431,8 @@ async function runStreamJson(opts) {
126327
126431
  id: sessionKey,
126328
126432
  workDir,
126329
126433
  model,
126330
- permission: "auto"
126434
+ permission: mappedPermission,
126435
+ planMode: mappedPlanMode
126331
126436
  });
126332
126437
  log.info("stream-json: created session", { sessionId: session.id });
126333
126438
  }
@@ -126335,7 +126440,34 @@ async function runStreamJson(opts) {
126335
126440
  writer.setSessionId(session.id);
126336
126441
  writer.setModel(opts.model ?? config.defaultModel ?? "");
126337
126442
  writer.emitSystem(sessionKey);
126338
- session.setApprovalHandler(() => ({ decision: "approved" }));
126443
+ function sendControlRequest(request) {
126444
+ return new Promise((resolve) => {
126445
+ const reqId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
126446
+ pendingApprovals.set(reqId, { resolve });
126447
+ writer.emitControlRequest(reqId, request.toolCallId, request.toolName, request.display);
126448
+ });
126449
+ }
126450
+ switch (opts.permissionMode) {
126451
+ case "yolo":
126452
+ case "bypassPermissions":
126453
+ session.setApprovalHandler(() => ({ decision: "approved" }));
126454
+ break;
126455
+ case "dontAsk":
126456
+ session.setApprovalHandler(() => ({
126457
+ decision: "rejected",
126458
+ feedback: "dontAsk 模式下工具调用被自动拒绝"
126459
+ }));
126460
+ break;
126461
+ case "acceptEdits":
126462
+ session.setApprovalHandler((request) => {
126463
+ if (["Edit", "Write"].includes(request.toolName)) return { decision: "approved" };
126464
+ return sendControlRequest(request);
126465
+ });
126466
+ break;
126467
+ default:
126468
+ session.setApprovalHandler((request) => sendControlRequest(request));
126469
+ break;
126470
+ }
126339
126471
  session.setQuestionHandler(() => null);
126340
126472
  }
126341
126473
  let activeTurnId;
@@ -126361,6 +126493,23 @@ async function runStreamJson(opts) {
126361
126493
  finish(/* @__PURE__ */ new Error(`${event.code}: ${event.message}`));
126362
126494
  return;
126363
126495
  }
126496
+ if (event.type === "subagent.spawned") {
126497
+ subagentNames.set(event.subagentId, event.subagentName);
126498
+ writer.writeAssistantDelta(`\n[子任务: ${event.subagentName}]\n`);
126499
+ return;
126500
+ }
126501
+ if (event.type === "subagent.completed") {
126502
+ const name = subagentNames.get(event.subagentId) ?? event.subagentId;
126503
+ writer.writeAssistantDelta(`\n[子任务完成: ${name}]\n`);
126504
+ subagentNames.delete(event.subagentId);
126505
+ return;
126506
+ }
126507
+ if (event.type === "subagent.failed") {
126508
+ const name = subagentNames.get(event.subagentId) ?? event.subagentId;
126509
+ writer.writeAssistantDelta(`\n[子任务失败: ${name} - ${event.error}]\n`);
126510
+ subagentNames.delete(event.subagentId);
126511
+ return;
126512
+ }
126364
126513
  if (event.type === "turn.started" && activeTurnId === void 0) {
126365
126514
  if (event.agentId !== "main") return;
126366
126515
  activeTurnId = event.turnId;
@@ -126405,7 +126554,12 @@ async function runStreamJson(opts) {
126405
126554
  writer.emitResult("error", error instanceof Error ? error.message : String(error));
126406
126555
  process.exitCode = 1;
126407
126556
  } finally {
126408
- if (currentSessionId) writer.emitResumeHint(currentSessionId);
126557
+ for (const [, pending] of pendingApprovals) pending.resolve({
126558
+ decision: "rejected",
126559
+ feedback: "会话已结束"
126560
+ });
126561
+ pendingApprovals.clear();
126562
+ if (currentSessionId) writer.emitResumeHint(sessionKey);
126409
126563
  try {
126410
126564
  if (session) await session.close();
126411
126565
  await harness.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "The Starting Point for Next-Gen Agents",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",
@@ -43,22 +43,8 @@
43
43
  "access": "public",
44
44
  "provenance": true
45
45
  },
46
- "scripts": {
47
- "build": "tsdown",
48
- "dev": "node scripts/dev.mjs",
49
- "dev:cli-only": "tsx --import ../../build/register-raw-text-loader.mjs ./src/main.ts",
50
- "dev:prod": "node dist/main.mjs",
51
- "clean": "rm -rf dist",
52
- "typecheck": "tsc -p tsconfig.json --noEmit",
53
- "test": "pnpm -w run build:packages && vitest run",
54
- "e2e": "pnpm -w run build:packages && SCREAM_E2E=1 vitest run test/e2e",
55
- "e2e:real": "pnpm -w run build:packages && SCREAM_E2E_REAL=1 vitest run test/e2e/real-llm-smoke.e2e.test.ts",
56
- "preinstall": "node -e \"console.log('\\n📦 正在安装 scream-code,请稍候...\\n')\"",
57
- "postinstall": "node scripts/postinstall.mjs",
58
- "smoke": "node dist/main.mjs --version"
59
- },
60
46
  "dependencies": {
61
- "@earendil-works/pi-tui": "^0.74.0",
47
+ "@earendil-works/pi-tui": "^0.78.1",
62
48
  "@mariozechner/clipboard": "^0.3.2",
63
49
  "chalk": "^5.4.1",
64
50
  "cli-highlight": "^2.1.11",
@@ -69,16 +55,30 @@
69
55
  },
70
56
  "devDependencies": {
71
57
  "@modelcontextprotocol/sdk": "^1.29.0",
72
- "@scream-cli/agent-core": "workspace:^",
73
- "@scream-cli/config": "workspace:^",
74
- "@scream-cli/migration-legacy": "workspace:^",
75
- "@scream-cli/scream-code-sdk": "workspace:^",
76
- "@scream-cli/scream-telemetry": "workspace:^",
77
- "@scream-code/memory": "workspace:*",
78
58
  "@types/semver": "^7.7.0",
79
- "tsx": "^4.21.0"
59
+ "tsx": "^4.21.0",
60
+ "@scream-cli/agent-core": "^0.5.0",
61
+ "@scream-cli/config": "^0.1.0",
62
+ "@scream-cli/migration-legacy": "^0.1.5",
63
+ "@scream-cli/scream-telemetry": "^0.1.1",
64
+ "@scream-cli/scream-code-sdk": "^0.4.0",
65
+ "@scream-code/memory": "0.1.0"
80
66
  },
81
67
  "engines": {
82
68
  "node": ">=22.19.0"
69
+ },
70
+ "scripts": {
71
+ "build": "tsdown",
72
+ "dev": "node scripts/dev.mjs",
73
+ "dev:cli-only": "tsx --import ../../build/register-raw-text-loader.mjs ./src/main.ts",
74
+ "dev:prod": "node dist/main.mjs",
75
+ "clean": "rm -rf dist",
76
+ "typecheck": "tsc -p tsconfig.json --noEmit",
77
+ "test": "pnpm -w run build:packages && vitest run",
78
+ "e2e": "pnpm -w run build:packages && SCREAM_E2E=1 vitest run test/e2e",
79
+ "e2e:real": "pnpm -w run build:packages && SCREAM_E2E_REAL=1 vitest run test/e2e/real-llm-smoke.e2e.test.ts",
80
+ "preinstall": "node -e \"console.log('\\n📦 正在安装 scream-code,请稍候...\\n')\"",
81
+ "postinstall": "node scripts/postinstall.mjs",
82
+ "smoke": "node dist/main.mjs --version"
83
83
  }
84
- }
84
+ }