chatccc 0.2.230 → 0.2.231

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 (116) hide show
  1. package/.agents/skills/create-chatccc-feishu-app/SKILL.md +85 -85
  2. package/.claude/skills/create-chatccc-feishu-app/SKILL.md +85 -85
  3. package/.cursor/skills/create-chatccc-feishu-app/SKILL.md +85 -85
  4. package/README.md +90 -90
  5. package/agent-prompts/claude_specific.md +45 -45
  6. package/agent-prompts/codex_specific.md +2 -2
  7. package/agent-prompts/cursor_specific.md +13 -13
  8. package/bin/cccagent.mjs +17 -17
  9. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  10. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  11. package/im-skills/feishu-skill/skill.md +3 -3
  12. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  13. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  14. package/im-skills/wechat-file-skill/skill.md +10 -10
  15. package/im-skills/wechat-image-skill/skill.md +10 -10
  16. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  17. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  18. package/im-skills/wechat-video-skill/skill.md +10 -10
  19. package/package.json +1 -1
  20. package/scripts/postinstall-sharp-check.mjs +58 -58
  21. package/src/__tests__/agent-activity.test.ts +86 -76
  22. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  23. package/src/__tests__/agent-reload-config-rpc.test.ts +99 -99
  24. package/src/__tests__/builtin-chat-session.test.ts +65 -1
  25. package/src/__tests__/builtin-cli-json.test.ts +39 -39
  26. package/src/__tests__/builtin-config.test.ts +26 -26
  27. package/src/__tests__/builtin-context.test.ts +202 -163
  28. package/src/__tests__/builtin-permissions.test.ts +211 -211
  29. package/src/__tests__/builtin-session-select.test.ts +116 -116
  30. package/src/__tests__/builtin-sigint.test.ts +56 -56
  31. package/src/__tests__/builtin-web-tools.test.ts +220 -220
  32. package/src/__tests__/card-action-routing.test.ts +18 -18
  33. package/src/__tests__/cardkit.test.ts +60 -60
  34. package/src/__tests__/ccc-adapter.test.ts +170 -136
  35. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  36. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  37. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  38. package/src/__tests__/claude-adapter.test.ts +614 -614
  39. package/src/__tests__/claude-raw-stream-log.test.ts +87 -87
  40. package/src/__tests__/codex-adapter.test.ts +58 -58
  41. package/src/__tests__/codex-raw-stream-log.test.ts +170 -170
  42. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  43. package/src/__tests__/cursor-adapter.test.ts +268 -268
  44. package/src/__tests__/feishu-api.test.ts +60 -60
  45. package/src/__tests__/feishu-avatar.test.ts +164 -164
  46. package/src/__tests__/feishu-message-ingress.test.ts +138 -138
  47. package/src/__tests__/feishu-platform.test.ts +22 -22
  48. package/src/__tests__/format-message.test.ts +47 -47
  49. package/src/__tests__/jsonl-stream.test.ts +79 -79
  50. package/src/__tests__/package-files.test.ts +24 -24
  51. package/src/__tests__/privacy.test.ts +198 -198
  52. package/src/__tests__/progress-reducer.test.ts +121 -110
  53. package/src/__tests__/raw-stream-log.test.ts +106 -106
  54. package/src/__tests__/response-stall.test.ts +49 -49
  55. package/src/__tests__/session.test.ts +11 -16
  56. package/src/__tests__/shared-prefix.test.ts +36 -36
  57. package/src/__tests__/sim-platform.test.ts +16 -16
  58. package/src/__tests__/startup-lifecycle.test.ts +231 -231
  59. package/src/__tests__/stop-session.test.ts +34 -34
  60. package/src/__tests__/stream-state.test.ts +42 -42
  61. package/src/__tests__/terminal-renderer.test.ts +247 -247
  62. package/src/__tests__/update-command-guard.test.ts +144 -144
  63. package/src/__tests__/web-ui.test.ts +326 -326
  64. package/src/adapters/adapter-interface.ts +24 -18
  65. package/src/adapters/ccc-adapter.ts +141 -131
  66. package/src/adapters/claude-adapter.ts +620 -620
  67. package/src/adapters/claude-session-meta-store.ts +120 -120
  68. package/src/adapters/codex-adapter.ts +426 -426
  69. package/src/adapters/cursor-adapter.ts +681 -681
  70. package/src/adapters/jsonl-stream.ts +157 -157
  71. package/src/adapters/raw-stream-log.ts +124 -124
  72. package/src/adapters/resource-monitor.ts +140 -140
  73. package/src/agent-activity.ts +175 -170
  74. package/src/agent-delegate-task-rpc.ts +153 -153
  75. package/src/agent-delegate-task.ts +91 -91
  76. package/src/agent-reload-config-rpc.ts +34 -34
  77. package/src/agent-stop-stuck.ts +129 -129
  78. package/src/builtin/cli.ts +9 -2
  79. package/src/builtin/config.ts +84 -84
  80. package/src/builtin/context.ts +62 -4
  81. package/src/builtin/file-log.ts +38 -38
  82. package/src/builtin/index.ts +83 -19
  83. package/src/builtin/proc-tree-kill.ts +61 -61
  84. package/src/builtin/progress/cards-helpers.ts +76 -76
  85. package/src/builtin/progress/reducer.ts +113 -108
  86. package/src/builtin/progress/terminal-renderer.ts +294 -294
  87. package/src/builtin/progress/view.ts +77 -77
  88. package/src/builtin/raw-stream-log.ts +124 -124
  89. package/src/builtin/session-select.ts +48 -48
  90. package/src/builtin/sigint.ts +50 -50
  91. package/src/builtin/web-tools.ts +313 -313
  92. package/src/card-action-routing.ts +14 -14
  93. package/src/chatgpt-subscription-rpc.ts +27 -27
  94. package/src/chatgpt-subscription.ts +299 -299
  95. package/src/chrome-devtools-guard.ts +318 -318
  96. package/src/codex-reset-actions.ts +184 -184
  97. package/src/feishu-api.ts +193 -193
  98. package/src/feishu-message-ingress.ts +195 -195
  99. package/src/feishu-platform.ts +20 -20
  100. package/src/format-message.ts +293 -293
  101. package/src/litellm-proxy.ts +374 -374
  102. package/src/platform-adapter.ts +6 -6
  103. package/src/privacy.ts +118 -118
  104. package/src/progress/reducer.ts +113 -108
  105. package/src/progress/terminal-renderer.ts +294 -294
  106. package/src/progress/view.ts +77 -77
  107. package/src/response-stall.ts +28 -28
  108. package/src/runtime-reload.ts +34 -34
  109. package/src/session-chat-binding.ts +82 -82
  110. package/src/session-name.ts +8 -8
  111. package/src/session.ts +12 -11
  112. package/src/shared-prefix.ts +29 -29
  113. package/src/sim-platform.ts +20 -20
  114. package/src/startup-lifecycle.ts +250 -250
  115. package/src/turn-cards.ts +117 -117
  116. package/src/update-command-guard.ts +165 -165
@@ -1,77 +1,77 @@
1
- /**
2
- * progress/view.ts — 平台无关的"过程进度"视图模型
3
- *
4
- * 这是飞书过程卡片与终端过程区块共享的唯一数据模型:
5
- * - 飞书:buildProgressCard(view) 把 ProgressView 渲染为卡片 JSON
6
- * - 终端:TerminalProgressRenderer 把 ProgressView 渲染为 ANSI 区块
7
- * - 事件流:reduceProgress(prev, event) 把 ChatEvent 增量合并进 ProgressView
8
- *
9
- * 语义与飞书卡片对齐:
10
- * - headerTitle 是"生成中"阶段展示的标题(如"正在启动 Agent · 0秒")
11
- * - status 决定终态外观(完成 / 已停止 / 异常结束)
12
- * - text 是正文累积(对应卡片 main_content / 终端区块正文)
13
- * - tools 是本轮工具调用列表(飞书卡片暂不展示,终端折叠为单行)
14
- */
15
-
16
- export type ProgressStatus = "generating" | "done" | "stopped" | "error";
17
-
18
- export type ProgressToolStatus = "running" | "ok" | "error";
19
-
20
- export interface ProgressToolCall {
21
- /** 工具调用 ID(ChatEvent.tool_use.id,可能缺失) */
22
- id: string;
23
- /** 工具名,如 edit_file */
24
- name: string;
25
- status: ProgressToolStatus;
26
- /** 工具输入摘要(终端折叠行展示,截断保存) */
27
- detail?: string;
28
- /** 工具结果摘要(成功/失败,截断保存) */
29
- summary?: string;
30
- }
31
-
32
- export interface ProgressView {
33
- /** 当前阶段状态,决定终态外观 */
34
- status: ProgressStatus;
35
- /** 生成中阶段的头部标题(对应飞书卡片 header title) */
36
- headerTitle: string;
37
- /** 卡片头部颜色模板(飞书用),终端忽略 */
38
- headerTemplate: string;
39
- /** 正文累积内容 */
40
- text: string;
41
- /** 本轮工具调用列表 */
42
- tools: ProgressToolCall[];
43
- /** 是否展示停止按钮 / 停止提示 */
44
- showStop: boolean;
45
- /** 最近一次更新时间戳 */
46
- updatedAt: number;
47
- }
48
-
49
- export interface ProgressViewInit {
50
- status?: ProgressStatus;
51
- headerTitle?: string;
52
- headerTemplate?: string;
53
- text?: string;
54
- tools?: ProgressToolCall[];
55
- showStop?: boolean;
56
- }
57
-
58
- /** 创建 ProgressView,未提供的字段使用与飞书 buildProgressCard 一致的默认值 */
59
- export function progressView(init: ProgressViewInit = {}): ProgressView {
60
- return {
61
- status: init.status ?? "generating",
62
- headerTitle: init.headerTitle ?? "生成中...",
63
- headerTemplate: init.headerTemplate ?? "blue",
64
- text: init.text ?? "",
65
- tools: init.tools ?? [],
66
- showStop: init.showStop ?? true,
67
- updatedAt: Date.now(),
68
- };
69
- }
70
-
71
- /** 只读浅拷贝并覆盖部分字段,返回新视图(reducer 的不可变更新用) */
72
- export function withProgressView(
73
- prev: ProgressView,
74
- patch: Partial<ProgressView>,
75
- ): ProgressView {
76
- return { ...prev, ...patch, updatedAt: Date.now() };
77
- }
1
+ /**
2
+ * progress/view.ts — 平台无关的"过程进度"视图模型
3
+ *
4
+ * 这是飞书过程卡片与终端过程区块共享的唯一数据模型:
5
+ * - 飞书:buildProgressCard(view) 把 ProgressView 渲染为卡片 JSON
6
+ * - 终端:TerminalProgressRenderer 把 ProgressView 渲染为 ANSI 区块
7
+ * - 事件流:reduceProgress(prev, event) 把 ChatEvent 增量合并进 ProgressView
8
+ *
9
+ * 语义与飞书卡片对齐:
10
+ * - headerTitle 是"生成中"阶段展示的标题(如"正在启动 Agent · 0秒")
11
+ * - status 决定终态外观(完成 / 已停止 / 异常结束)
12
+ * - text 是正文累积(对应卡片 main_content / 终端区块正文)
13
+ * - tools 是本轮工具调用列表(飞书卡片暂不展示,终端折叠为单行)
14
+ */
15
+
16
+ export type ProgressStatus = "generating" | "done" | "stopped" | "error";
17
+
18
+ export type ProgressToolStatus = "running" | "ok" | "error";
19
+
20
+ export interface ProgressToolCall {
21
+ /** 工具调用 ID(ChatEvent.tool_use.id,可能缺失) */
22
+ id: string;
23
+ /** 工具名,如 edit_file */
24
+ name: string;
25
+ status: ProgressToolStatus;
26
+ /** 工具输入摘要(终端折叠行展示,截断保存) */
27
+ detail?: string;
28
+ /** 工具结果摘要(成功/失败,截断保存) */
29
+ summary?: string;
30
+ }
31
+
32
+ export interface ProgressView {
33
+ /** 当前阶段状态,决定终态外观 */
34
+ status: ProgressStatus;
35
+ /** 生成中阶段的头部标题(对应飞书卡片 header title) */
36
+ headerTitle: string;
37
+ /** 卡片头部颜色模板(飞书用),终端忽略 */
38
+ headerTemplate: string;
39
+ /** 正文累积内容 */
40
+ text: string;
41
+ /** 本轮工具调用列表 */
42
+ tools: ProgressToolCall[];
43
+ /** 是否展示停止按钮 / 停止提示 */
44
+ showStop: boolean;
45
+ /** 最近一次更新时间戳 */
46
+ updatedAt: number;
47
+ }
48
+
49
+ export interface ProgressViewInit {
50
+ status?: ProgressStatus;
51
+ headerTitle?: string;
52
+ headerTemplate?: string;
53
+ text?: string;
54
+ tools?: ProgressToolCall[];
55
+ showStop?: boolean;
56
+ }
57
+
58
+ /** 创建 ProgressView,未提供的字段使用与飞书 buildProgressCard 一致的默认值 */
59
+ export function progressView(init: ProgressViewInit = {}): ProgressView {
60
+ return {
61
+ status: init.status ?? "generating",
62
+ headerTitle: init.headerTitle ?? "生成中...",
63
+ headerTemplate: init.headerTemplate ?? "blue",
64
+ text: init.text ?? "",
65
+ tools: init.tools ?? [],
66
+ showStop: init.showStop ?? true,
67
+ updatedAt: Date.now(),
68
+ };
69
+ }
70
+
71
+ /** 只读浅拷贝并覆盖部分字段,返回新视图(reducer 的不可变更新用) */
72
+ export function withProgressView(
73
+ prev: ProgressView,
74
+ patch: Partial<ProgressView>,
75
+ ): ProgressView {
76
+ return { ...prev, ...patch, updatedAt: Date.now() };
77
+ }
@@ -1,124 +1,124 @@
1
- import { createWriteStream, type Dirent } from "node:fs";
2
- import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
3
- import { once } from "node:events";
4
- import { join } from "node:path";
5
- import { createGzip } from "node:zlib";
6
-
7
- export interface RawStreamLogOptions {
8
- enabled: boolean;
9
- rootDir: string;
10
- tool: string;
11
- sessionId: string;
12
- label: string;
13
- maxBytesPerTurn: number;
14
- retentionDays: number;
15
- }
16
-
17
- export interface RawStreamLogHandle {
18
- filePath: string;
19
- writeLine(line: string): void;
20
- close(options: { keep: boolean }): Promise<void>;
21
- }
22
-
23
- export function sanitizeLogPathSegment(value: string): string {
24
- const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^[._]+|_+$/g, "");
25
- return safe || "unknown";
26
- }
27
-
28
- async function cleanupOldRawStreamLogs(rootDir: string, retentionDays: number): Promise<void> {
29
- if (!Number.isFinite(retentionDays) || retentionDays <= 0) return;
30
- const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
31
-
32
- const visit = async (dir: string): Promise<void> => {
33
- let entries: Dirent[];
34
- try {
35
- entries = await readdir(dir, { withFileTypes: true });
36
- } catch {
37
- return;
38
- }
39
-
40
- await Promise.all(entries.map(async (entry) => {
41
- const path = join(dir, entry.name);
42
- if (entry.isDirectory()) {
43
- await visit(path);
44
- try {
45
- await rm(path, { recursive: false });
46
- } catch {
47
- // Directory is not empty or already gone.
48
- }
49
- return;
50
- }
51
-
52
- if (!entry.isFile()) return;
53
- try {
54
- const info = await stat(path);
55
- if (info.mtimeMs < cutoff) await rm(path, { force: true });
56
- } catch {
57
- // Best-effort cleanup only.
58
- }
59
- }));
60
- };
61
-
62
- await visit(rootDir);
63
- }
64
-
65
- export async function createRawStreamLog(options: RawStreamLogOptions): Promise<RawStreamLogHandle | null> {
66
- if (!options.enabled) return null;
67
-
68
- const maxBytes = Math.max(0, Math.floor(options.maxBytesPerTurn));
69
- const tool = sanitizeLogPathSegment(options.tool);
70
- const session = sanitizeLogPathSegment(options.sessionId);
71
- const label = sanitizeLogPathSegment(options.label);
72
- const dir = join(options.rootDir, tool, session);
73
- await mkdir(dir, { recursive: true });
74
- void cleanupOldRawStreamLogs(join(options.rootDir, tool), options.retentionDays);
75
-
76
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
77
- const filePath = join(dir, `${timestamp}-${label}.jsonl.gz`);
78
- const output = createWriteStream(filePath);
79
- const gzip = createGzip();
80
- gzip.pipe(output);
81
-
82
- let bytes = 0;
83
- let truncated = false;
84
- let ended = false;
85
-
86
- const writeLine = (line: string): void => {
87
- if (ended || truncated) return;
88
- const payload = `${line}\n`;
89
- const payloadBytes = Buffer.byteLength(payload, "utf-8");
90
- if (maxBytes > 0 && bytes + payloadBytes > maxBytes) {
91
- const marker = JSON.stringify({
92
- type: "deepccc_raw_stream_log_truncated",
93
- reason: "max_bytes_per_turn_exceeded",
94
- maxBytesPerTurn: maxBytes,
95
- writtenBytes: bytes,
96
- });
97
- gzip.write(`${marker}\n`);
98
- truncated = true;
99
- return;
100
- }
101
- bytes += payloadBytes;
102
- gzip.write(payload);
103
- };
104
-
105
- const close = async ({ keep }: { keep: boolean }): Promise<void> => {
106
- if (ended) return;
107
- ended = true;
108
- gzip.end();
109
- try {
110
- await once(output, "finish");
111
- } catch {
112
- // Ignore close errors; caller cannot recover from debug log failures.
113
- }
114
- if (!keep) {
115
- try {
116
- await unlink(filePath);
117
- } catch {
118
- // Best-effort cleanup only.
119
- }
120
- }
121
- };
122
-
123
- return { filePath, writeLine, close };
124
- }
1
+ import { createWriteStream, type Dirent } from "node:fs";
2
+ import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
3
+ import { once } from "node:events";
4
+ import { join } from "node:path";
5
+ import { createGzip } from "node:zlib";
6
+
7
+ export interface RawStreamLogOptions {
8
+ enabled: boolean;
9
+ rootDir: string;
10
+ tool: string;
11
+ sessionId: string;
12
+ label: string;
13
+ maxBytesPerTurn: number;
14
+ retentionDays: number;
15
+ }
16
+
17
+ export interface RawStreamLogHandle {
18
+ filePath: string;
19
+ writeLine(line: string): void;
20
+ close(options: { keep: boolean }): Promise<void>;
21
+ }
22
+
23
+ export function sanitizeLogPathSegment(value: string): string {
24
+ const safe = value.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^[._]+|_+$/g, "");
25
+ return safe || "unknown";
26
+ }
27
+
28
+ async function cleanupOldRawStreamLogs(rootDir: string, retentionDays: number): Promise<void> {
29
+ if (!Number.isFinite(retentionDays) || retentionDays <= 0) return;
30
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
31
+
32
+ const visit = async (dir: string): Promise<void> => {
33
+ let entries: Dirent[];
34
+ try {
35
+ entries = await readdir(dir, { withFileTypes: true });
36
+ } catch {
37
+ return;
38
+ }
39
+
40
+ await Promise.all(entries.map(async (entry) => {
41
+ const path = join(dir, entry.name);
42
+ if (entry.isDirectory()) {
43
+ await visit(path);
44
+ try {
45
+ await rm(path, { recursive: false });
46
+ } catch {
47
+ // Directory is not empty or already gone.
48
+ }
49
+ return;
50
+ }
51
+
52
+ if (!entry.isFile()) return;
53
+ try {
54
+ const info = await stat(path);
55
+ if (info.mtimeMs < cutoff) await rm(path, { force: true });
56
+ } catch {
57
+ // Best-effort cleanup only.
58
+ }
59
+ }));
60
+ };
61
+
62
+ await visit(rootDir);
63
+ }
64
+
65
+ export async function createRawStreamLog(options: RawStreamLogOptions): Promise<RawStreamLogHandle | null> {
66
+ if (!options.enabled) return null;
67
+
68
+ const maxBytes = Math.max(0, Math.floor(options.maxBytesPerTurn));
69
+ const tool = sanitizeLogPathSegment(options.tool);
70
+ const session = sanitizeLogPathSegment(options.sessionId);
71
+ const label = sanitizeLogPathSegment(options.label);
72
+ const dir = join(options.rootDir, tool, session);
73
+ await mkdir(dir, { recursive: true });
74
+ void cleanupOldRawStreamLogs(join(options.rootDir, tool), options.retentionDays);
75
+
76
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
77
+ const filePath = join(dir, `${timestamp}-${label}.jsonl.gz`);
78
+ const output = createWriteStream(filePath);
79
+ const gzip = createGzip();
80
+ gzip.pipe(output);
81
+
82
+ let bytes = 0;
83
+ let truncated = false;
84
+ let ended = false;
85
+
86
+ const writeLine = (line: string): void => {
87
+ if (ended || truncated) return;
88
+ const payload = `${line}\n`;
89
+ const payloadBytes = Buffer.byteLength(payload, "utf-8");
90
+ if (maxBytes > 0 && bytes + payloadBytes > maxBytes) {
91
+ const marker = JSON.stringify({
92
+ type: "deepccc_raw_stream_log_truncated",
93
+ reason: "max_bytes_per_turn_exceeded",
94
+ maxBytesPerTurn: maxBytes,
95
+ writtenBytes: bytes,
96
+ });
97
+ gzip.write(`${marker}\n`);
98
+ truncated = true;
99
+ return;
100
+ }
101
+ bytes += payloadBytes;
102
+ gzip.write(payload);
103
+ };
104
+
105
+ const close = async ({ keep }: { keep: boolean }): Promise<void> => {
106
+ if (ended) return;
107
+ ended = true;
108
+ gzip.end();
109
+ try {
110
+ await once(output, "finish");
111
+ } catch {
112
+ // Ignore close errors; caller cannot recover from debug log failures.
113
+ }
114
+ if (!keep) {
115
+ try {
116
+ await unlink(filePath);
117
+ } catch {
118
+ // Best-effort cleanup only.
119
+ }
120
+ }
121
+ };
122
+
123
+ return { filePath, writeLine, close };
124
+ }
@@ -1,48 +1,48 @@
1
- import {
2
- DEFAULT_BUILTIN_CONTEXT_DIR,
3
- getBuiltinContextSession,
4
- latestBuiltinSessionForCwd,
5
- newBuiltinSessionId,
6
- normalizeBuiltinSessionId,
7
- } from "./context.js";
8
-
9
- export type BuiltinResumeRequest = true | string | undefined;
10
-
11
- export interface ResolveBuiltinSessionOptions {
12
- cwd: string;
13
- contextDir?: string;
14
- resume?: BuiltinResumeRequest;
15
- now?: Date;
16
- randomSuffix?: string;
17
- }
18
-
19
- export interface ResolvedBuiltinSession {
20
- mode: "new" | "resumed";
21
- sessionId: string;
22
- }
23
-
24
- export function resolveBuiltinSession(options: ResolveBuiltinSessionOptions): ResolvedBuiltinSession {
25
- const contextDir = options.contextDir ?? DEFAULT_BUILTIN_CONTEXT_DIR;
26
-
27
- if (options.resume === true) {
28
- const latest = latestBuiltinSessionForCwd(options.cwd, contextDir);
29
- if (!latest) {
30
- throw new Error(`No resumable DeepCCC session found for cwd: ${options.cwd}`);
31
- }
32
- return { mode: "resumed", sessionId: latest.sessionId };
33
- }
34
-
35
- if (typeof options.resume === "string") {
36
- const sessionId = normalizeBuiltinSessionId(options.resume);
37
- const existing = getBuiltinContextSession(sessionId, contextDir);
38
- if (!existing) {
39
- throw new Error(`DeepCCC session not found: ${sessionId}`);
40
- }
41
- return { mode: "resumed", sessionId };
42
- }
43
-
44
- return {
45
- mode: "new",
46
- sessionId: newBuiltinSessionId(options.now, options.randomSuffix),
47
- };
48
- }
1
+ import {
2
+ DEFAULT_BUILTIN_CONTEXT_DIR,
3
+ getBuiltinContextSession,
4
+ latestBuiltinSessionForCwd,
5
+ newBuiltinSessionId,
6
+ normalizeBuiltinSessionId,
7
+ } from "./context.js";
8
+
9
+ export type BuiltinResumeRequest = true | string | undefined;
10
+
11
+ export interface ResolveBuiltinSessionOptions {
12
+ cwd: string;
13
+ contextDir?: string;
14
+ resume?: BuiltinResumeRequest;
15
+ now?: Date;
16
+ randomSuffix?: string;
17
+ }
18
+
19
+ export interface ResolvedBuiltinSession {
20
+ mode: "new" | "resumed";
21
+ sessionId: string;
22
+ }
23
+
24
+ export function resolveBuiltinSession(options: ResolveBuiltinSessionOptions): ResolvedBuiltinSession {
25
+ const contextDir = options.contextDir ?? DEFAULT_BUILTIN_CONTEXT_DIR;
26
+
27
+ if (options.resume === true) {
28
+ const latest = latestBuiltinSessionForCwd(options.cwd, contextDir);
29
+ if (!latest) {
30
+ throw new Error(`No resumable DeepCCC session found for cwd: ${options.cwd}`);
31
+ }
32
+ return { mode: "resumed", sessionId: latest.sessionId };
33
+ }
34
+
35
+ if (typeof options.resume === "string") {
36
+ const sessionId = normalizeBuiltinSessionId(options.resume);
37
+ const existing = getBuiltinContextSession(sessionId, contextDir);
38
+ if (!existing) {
39
+ throw new Error(`DeepCCC session not found: ${sessionId}`);
40
+ }
41
+ return { mode: "resumed", sessionId };
42
+ }
43
+
44
+ return {
45
+ mode: "new",
46
+ sessionId: newBuiltinSessionId(options.now, options.randomSuffix),
47
+ };
48
+ }
@@ -1,50 +1,50 @@
1
- export const CTRL_C_CONFIRM_WINDOW_MS = 2000;
2
-
3
- export type CtrlCAction =
4
- | "arm-interrupt"
5
- | "interrupt"
6
- | "arm-exit"
7
- | "exit";
8
-
9
- type PendingCtrlCAction = "interrupt" | "exit";
10
-
11
- export interface CtrlCStateOptions {
12
- windowMs?: number;
13
- now?: () => number;
14
- }
15
-
16
- export interface CtrlCState {
17
- press(isGenerating: boolean): CtrlCAction;
18
- reset(): void;
19
- }
20
-
21
- export function createCtrlCState(options: CtrlCStateOptions = {}): CtrlCState {
22
- const windowMs = options.windowMs ?? CTRL_C_CONFIRM_WINDOW_MS;
23
- const now = options.now ?? Date.now;
24
-
25
- let pending: PendingCtrlCAction | null = null;
26
- let lastPressAt = 0;
27
-
28
- return {
29
- press(isGenerating: boolean): CtrlCAction {
30
- const action: PendingCtrlCAction = isGenerating ? "interrupt" : "exit";
31
- const current = now();
32
- const isConfirmed = pending === action && current - lastPressAt <= windowMs;
33
-
34
- if (isConfirmed) {
35
- pending = null;
36
- lastPressAt = 0;
37
- return action;
38
- }
39
-
40
- pending = action;
41
- lastPressAt = current;
42
- return isGenerating ? "arm-interrupt" : "arm-exit";
43
- },
44
-
45
- reset(): void {
46
- pending = null;
47
- lastPressAt = 0;
48
- },
49
- };
50
- }
1
+ export const CTRL_C_CONFIRM_WINDOW_MS = 2000;
2
+
3
+ export type CtrlCAction =
4
+ | "arm-interrupt"
5
+ | "interrupt"
6
+ | "arm-exit"
7
+ | "exit";
8
+
9
+ type PendingCtrlCAction = "interrupt" | "exit";
10
+
11
+ export interface CtrlCStateOptions {
12
+ windowMs?: number;
13
+ now?: () => number;
14
+ }
15
+
16
+ export interface CtrlCState {
17
+ press(isGenerating: boolean): CtrlCAction;
18
+ reset(): void;
19
+ }
20
+
21
+ export function createCtrlCState(options: CtrlCStateOptions = {}): CtrlCState {
22
+ const windowMs = options.windowMs ?? CTRL_C_CONFIRM_WINDOW_MS;
23
+ const now = options.now ?? Date.now;
24
+
25
+ let pending: PendingCtrlCAction | null = null;
26
+ let lastPressAt = 0;
27
+
28
+ return {
29
+ press(isGenerating: boolean): CtrlCAction {
30
+ const action: PendingCtrlCAction = isGenerating ? "interrupt" : "exit";
31
+ const current = now();
32
+ const isConfirmed = pending === action && current - lastPressAt <= windowMs;
33
+
34
+ if (isConfirmed) {
35
+ pending = null;
36
+ lastPressAt = 0;
37
+ return action;
38
+ }
39
+
40
+ pending = action;
41
+ lastPressAt = current;
42
+ return isGenerating ? "arm-interrupt" : "arm-exit";
43
+ },
44
+
45
+ reset(): void {
46
+ pending = null;
47
+ lastPressAt = 0;
48
+ },
49
+ };
50
+ }