evolclaw 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +7 -3
- package/dist/agents/claude-runner.js +23 -27
- package/dist/agents/codex-runner.js +90 -6
- package/dist/agents/runner-types.js +30 -0
- package/dist/aun/outbox.js +14 -2
- package/dist/channels/aun.js +506 -108
- package/dist/channels/feishu.js +29 -5
- package/dist/cli/agent-command.js +591 -0
- package/dist/cli/agent.js +15 -3
- package/dist/cli/aun-commands.js +1444 -0
- package/dist/cli/ctl-command.js +78 -0
- package/dist/cli/daemon-commands.js +2707 -0
- package/dist/cli/index.js +12 -5027
- package/dist/cli/restart-monitor.js +539 -0
- package/dist/cli/watch-logs.js +33 -0
- package/dist/core/channel-loader.js +4 -1
- package/dist/core/command/command-handler.js +1189 -0
- package/dist/core/command/menu-handler.js +1478 -0
- package/dist/core/command/slash-gate.js +142 -0
- package/dist/core/command/slash-handler.js +2090 -0
- package/dist/core/evolagent-registry.js +81 -0
- package/dist/core/evolagent.js +16 -0
- package/dist/core/message/im-renderer.js +67 -49
- package/dist/core/message/message-bridge.js +30 -9
- package/dist/core/message/message-processor.js +200 -122
- package/dist/core/message/message-queue.js +68 -0
- package/dist/core/permission.js +16 -0
- package/dist/core/session/session-manager.js +59 -13
- package/dist/core/stats/db.js +20 -0
- package/dist/core/stats/writer.js +3 -3
- package/dist/data/error-dict.json +7 -0
- package/dist/index.js +49 -6
- package/dist/ipc.js +99 -0
- package/dist/utils/cross-platform.js +35 -0
- package/dist/utils/ecweb-launch.js +49 -0
- package/dist/utils/error-utils.js +18 -5
- package/dist/utils/npm-ops.js +38 -8
- package/dist/utils/stats.js +63 -6
- package/kits/eck_manifest.json +0 -12
- package/package.json +2 -3
- package/dist/core/command-handler.js +0 -4235
- package/dist/core/message/response-depth.js +0 -56
- package/kits/templates/system-fragments/response-depth.md +0 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v3.4.0 (2026-06-12)
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **CLI 模块化** — 将4640行 `command-handler.ts` 和5131行 `cli/index.ts` 拆分为8个专注模块(`command/`、`cli/`子目录),claude-agent-sdk 升级至 ^0.3.170,净减约10000行
|
|
8
|
+
- **ECWeb Monitor 视图** — 新增实时监控页面:进程级 CPU/内存采样(1s 后台循环)、全局统计、per-agent 摘要;IPC 新增 `monitor-snapshot` 处理器
|
|
9
|
+
- **Agent 运行时控制** — ECWeb 支持对每个 agent 执行 start/stop/mute/unmute/queue-clear,stop 中断进行中的模型调用,mute 暂停队列消费同时保留入队消息
|
|
10
|
+
- **`ec watch logs` 多选** — 新增日志类型多选菜单,支持按类型(session/tool/error等)筛选实时聚合日志;`watch.logTypes` 配置默认可选集
|
|
11
|
+
- **AUN 结构化出站 payload** — task status 改走 `notify`(`event/app.task.status`)不入消息历史;activity 逐条结构化(`type:'activity'` + `item`);notice/error 结构化;`ref_message_id`/`initiator`/`thread_id` 统一透传
|
|
12
|
+
- **Context-aware auto-compact** — 根据 DB 中上次 model call 的实际 context token 记录决策压缩时机,在下一任务开始前执行;DB 新增 `context_tokens`/`max_tokens`/`auto_compact_tokens` 字段
|
|
13
|
+
- **Codex Edit events 统一 diff** — fileChange 映射为 `Edit` tool_use 事件并附带 unified diff,permission 层直接渲染,不重新计算
|
|
14
|
+
- **AUN 群命令 mention 过滤** — broadcast 指令强制要求 @ 触发;`action_card_reply` 归属由消息上下文精确判定
|
|
15
|
+
- **群话题创建权限** — 由 AUN `group.get_admins` 实时查询,仅 owner/admin 可建话题,fail-closed;无权时静默丢弃避免 agent 互相拒绝循环
|
|
16
|
+
- **`/baseagent scope` 参数** — 支持 `session`/`default`/`both` 三档控制切换范围
|
|
17
|
+
- **Session topic rename** — 支持 `/rename` 重命名当前话题会话
|
|
18
|
+
- **evolclaw-web 自动升级** — ECWeb 启动时检测并自动升级新版本,对标 fastaun 升级机制
|
|
19
|
+
- **Agent displayName 解析** — 从 agent.md 本地缓存 + 异步网络拉取 displayName,ECWeb 展示更友好
|
|
20
|
+
|
|
21
|
+
### Improvements
|
|
22
|
+
|
|
23
|
+
- **用户中断归类** — 新消息/`/stop`/撤回触发的流中断独立为 `task:interrupted` 事件,不计入 `task:error`
|
|
24
|
+
- **统一出站响应投递** — AUN 渠道所有出站路径收敛到 `adapter.send`,消除渠道间重复分支
|
|
25
|
+
- **Feishu Pin→CheckMark 两阶段 ack** — 收到消息先加 Pin(排队中),runner 开始时升级为 CheckMark 并移除 Pin,视觉无空窗
|
|
26
|
+
- **dispatchModeOverride 分离** — 动态覆盖与持久化 `dispatchMode` 解耦,避免一次性覆盖污染会话配置
|
|
27
|
+
- **ECWeb token TTL 延长至30天** — 支持滑动续期;端口被占时杀旧进程而非漂移到 port+1
|
|
28
|
+
- **`evolclaw status` 展示 ECWeb** — 状态命令新增 ECWeb 进程与 HTTP 就绪状态
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
- **话题回复上下文丢失** — `ctl send/file` 在 Feishu 话题内未透传 `replyToMessageId`/`replyInThread`,回复落到主会话气泡
|
|
33
|
+
- **文件标记提前暴露** — 定时 flush(非最终)触发时 `[SEND_FILE:...]` 未过滤,原样发给用户
|
|
34
|
+
- **AUN inbound replyContext 缺字段** — 入站消息未填充 `peerId`/`replyToMessageId`,导致 task.status `initiator` 为空
|
|
35
|
+
- **Codex SSE idle 重连误报错** — SSE 超时重连的空 error 事件被当作任务错误处理
|
|
36
|
+
- **JSON parse error 自动重试** — API 返回 JSON 解析异常时触发指数退避重试
|
|
37
|
+
- **ECWeb 启动就绪检测** — HTTP 探测根路径判断就绪,避免进程存活但 HTTP 未就绪的误判
|
|
38
|
+
|
|
3
39
|
## v3.3.0 (2026-06-10)
|
|
4
40
|
|
|
5
41
|
### New Features
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ EvolClaw 是一个轻量级 AI Agent 网关系统。它为 Claude Code / Codex
|
|
|
42
42
|
|
|
43
43
|
1. **消息渠道层** (`src/channels/`) - Feishu + WeChat + DingTalk + QQBot + WeCom + AUN 网络
|
|
44
44
|
2. **消息队列层** (`src/core/message/message-queue.ts`) - 会话级串行处理 + 中断支持
|
|
45
|
-
3. **命令处理层** (`src/core/command
|
|
45
|
+
3. **命令处理层** (`src/core/command/`) - 斜杠命令处理(slash-handler / menu-handler / command-handler)
|
|
46
46
|
4. **消息处理层** (`src/core/message/message-processor.ts`) - 统一事件处理引擎
|
|
47
47
|
5. **会话管理层** (`src/core/session/session-manager.ts`) - 多项目会话管理
|
|
48
48
|
6. **交互路由层** (`src/core/interaction-router.ts`) - 卡片交互回调注册与路由
|
|
@@ -190,6 +190,11 @@ evolclaw/
|
|
|
190
190
|
│ │ └── gemini-runner.ts # Gemini CLI 封装
|
|
191
191
|
│ ├── aun/ # AUN 协议工具
|
|
192
192
|
│ ├── core/
|
|
193
|
+
│ │ ├── command/
|
|
194
|
+
│ │ │ ├── command-handler.ts # 命令派发入口
|
|
195
|
+
│ │ │ ├── slash-handler.ts # 斜杠命令实现
|
|
196
|
+
│ │ │ ├── menu-handler.ts # Menu 协议处理
|
|
197
|
+
│ │ │ └── slash-gate.ts # 权限前置拦截
|
|
193
198
|
│ │ ├── message/
|
|
194
199
|
│ │ │ ├── message-bridge.ts # 渠道 ↔ 核心消息桥
|
|
195
200
|
│ │ │ ├── message-processor.ts # 统一消息处理引擎
|
|
@@ -202,7 +207,6 @@ evolclaw/
|
|
|
202
207
|
│ │ │ ├── session-fs-store.ts # 文件系统存储原语
|
|
203
208
|
│ │ │ └── session-manager.ts # 会话管理(多项目支持)
|
|
204
209
|
│ │ ├── trigger/ # 触发器引擎
|
|
205
|
-
│ │ ├── command-handler.ts # 斜杠命令处理
|
|
206
210
|
│ │ ├── evolagent.ts # EvolAgent 实体
|
|
207
211
|
│ │ ├── evolagent-registry.ts # Agent 注册表(扫描/路由/热重载)
|
|
208
212
|
│ │ ├── interaction-router.ts # 卡片交互回调路由
|
|
@@ -295,7 +299,7 @@ v3.2 新增进程级身份标识。启动时自动生成 `ec+5位数字.agentid.
|
|
|
295
299
|
## 技术栈
|
|
296
300
|
|
|
297
301
|
- **运行时**:Node.js >= 22 + TypeScript(ES modules)
|
|
298
|
-
- **AI 后端**:@anthropic-ai/claude-agent-sdk >= 0.
|
|
302
|
+
- **AI 后端**:@anthropic-ai/claude-agent-sdk >= 0.3.170、Codex CLI app-server、Gemini CLI
|
|
299
303
|
- **消息渠道**:飞书(@larksuiteoapi/node-sdk)、微信(ClawBot ilink API)、钉钉(dingtalk-stream)、QQ频道(pure-qqbot)、企业微信(AI Bot API)、AUN 网络
|
|
300
304
|
- **数据存储**:文件系统(per-chat 目录) + JSONL(CLI 共用)
|
|
301
305
|
- **测试框架**:Vitest
|
|
@@ -10,7 +10,7 @@ import os from 'os';
|
|
|
10
10
|
import { logger } from '../utils/logger.js';
|
|
11
11
|
import { checkBlacklist, checkReadonly, summarizeToolInput } from '../core/permission.js';
|
|
12
12
|
import { encodePath } from '../utils/cross-platform.js';
|
|
13
|
-
import { contextTokensForUsage, usageForContext } from './runner-types.js';
|
|
13
|
+
import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
|
|
14
14
|
export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
|
|
15
15
|
// ── 模型别名解析 ──
|
|
16
16
|
// SDK 内置的别名表可能落后于代理实际可用的最新模型,
|
|
@@ -99,8 +99,6 @@ function resolveModelAlias(model, baseUrl) {
|
|
|
99
99
|
// 回退静态表
|
|
100
100
|
return STATIC_MODEL_ALIASES[model] || model;
|
|
101
101
|
}
|
|
102
|
-
/** 支持 1M 上下文窗口的模型 ID 前缀(SDK 通过 `[1m]` 后缀启用)。 */
|
|
103
|
-
const ONE_M_CONTEXT_PREFIXES = ['claude-opus-4-8', 'claude-sonnet-4-6'];
|
|
104
102
|
/**
|
|
105
103
|
* 为支持 1M 上下文的模型追加 `[1m]` 后缀——仅在交给 SDK query() 时调用。
|
|
106
104
|
* 目录与校验层始终使用不带后缀的基础 ID,避免与网关 /models 返回值(无 `[1m]`)冲突。
|
|
@@ -108,26 +106,10 @@ const ONE_M_CONTEXT_PREFIXES = ['claude-opus-4-8', 'claude-sonnet-4-6'];
|
|
|
108
106
|
function applyContextWindow(modelId) {
|
|
109
107
|
if (/\[1m\]$/.test(modelId))
|
|
110
108
|
return modelId; // 已带后缀
|
|
111
|
-
if (
|
|
109
|
+
if (isOneMillionContextModel(modelId))
|
|
112
110
|
return `${modelId}[1m]`;
|
|
113
111
|
return modelId;
|
|
114
112
|
}
|
|
115
|
-
/** 真实上下文窗口大小:1M 模型 = 1000000,否则 200000 */
|
|
116
|
-
function realContextWindowFor(sdkModel) {
|
|
117
|
-
if (/\[1m\]$/.test(sdkModel))
|
|
118
|
-
return 1000000;
|
|
119
|
-
if (/deepseek-v4/i.test(sdkModel))
|
|
120
|
-
return 1000000; // deepseek-v4 系列原生 1M 窗口(无 [1m] 后缀)
|
|
121
|
-
return 200000;
|
|
122
|
-
}
|
|
123
|
-
/** autoCompact 触发阈值:1M 模型 = 900000(留 ~100k buffer),否则 200000 */
|
|
124
|
-
function autoCompactWindowFor(sdkModel) {
|
|
125
|
-
if (/\[1m\]$/.test(sdkModel))
|
|
126
|
-
return 900000;
|
|
127
|
-
if (/deepseek-v4/i.test(sdkModel))
|
|
128
|
-
return 900000;
|
|
129
|
-
return 200000;
|
|
130
|
-
}
|
|
131
113
|
/** 解析别名 + 追加 1M 后缀,得到最终交给 SDK 的 model 串。 */
|
|
132
114
|
function resolveSdkModel(model, baseUrl) {
|
|
133
115
|
return applyContextWindow(resolveModelAlias(model, baseUrl));
|
|
@@ -850,10 +832,10 @@ export class AgentRunner {
|
|
|
850
832
|
// input_tokens 本身就是完整的上下文输入量。
|
|
851
833
|
const u = event.usage;
|
|
852
834
|
const effectiveModel = callModel ?? this.model;
|
|
853
|
-
const isClaudeModel = effectiveModel
|
|
835
|
+
const isClaudeModel = isClaudeContextUsageModel(effectiveModel);
|
|
854
836
|
const totalTokens = contextTokensForUsage(u, !!isClaudeModel);
|
|
855
|
-
const contextWindowTokens =
|
|
856
|
-
const autoCompactTokens =
|
|
837
|
+
const contextWindowTokens = realContextWindowForModel(sdkModel);
|
|
838
|
+
const autoCompactTokens = autoCompactWindowForModel(sdkModel);
|
|
857
839
|
const contextUsage = totalTokens > 0 ? {
|
|
858
840
|
totalTokens,
|
|
859
841
|
maxTokens: contextWindowTokens,
|
|
@@ -877,21 +859,35 @@ export class AgentRunner {
|
|
|
877
859
|
} : undefined,
|
|
878
860
|
};
|
|
879
861
|
}
|
|
862
|
+
const contextUsageForCall = (usage) => {
|
|
863
|
+
const callTotalTokens = contextTokensForUsage(usageForContext(usage), !!isClaudeModel);
|
|
864
|
+
return callTotalTokens > 0 ? {
|
|
865
|
+
totalTokens: callTotalTokens,
|
|
866
|
+
maxTokens: contextWindowTokens,
|
|
867
|
+
percentage: Math.round((callTotalTokens / contextWindowTokens) * 100),
|
|
868
|
+
autoCompactTokens,
|
|
869
|
+
model: callModel ?? this.model,
|
|
870
|
+
effort: callEffort ?? this.effort,
|
|
871
|
+
} : undefined;
|
|
872
|
+
};
|
|
880
873
|
// 组装 modelCalls:优先 SDK iterations,fallback 流式收集,兜底降级单行。
|
|
881
874
|
const callModel_ = callModel ?? this.model;
|
|
882
875
|
let modelCalls;
|
|
883
876
|
const iterArr = Array.isArray(u?.iterations) && u.iterations.length > 0 ? u.iterations : null;
|
|
884
877
|
if (iterArr) {
|
|
885
878
|
modelCalls = iterArr.map((it, i) => ({
|
|
886
|
-
call_index: i, model: callModel_, tokenUsage: it,
|
|
879
|
+
call_index: i, model: callModel_, tokenUsage: it, contextUsage: contextUsageForCall(it),
|
|
887
880
|
}));
|
|
888
881
|
}
|
|
889
882
|
else if (collectedCalls.length > 0) {
|
|
890
|
-
modelCalls = collectedCalls
|
|
883
|
+
modelCalls = collectedCalls.map(call => ({
|
|
884
|
+
...call,
|
|
885
|
+
contextUsage: contextUsageForCall(call.tokenUsage),
|
|
886
|
+
}));
|
|
891
887
|
}
|
|
892
888
|
else if (u) {
|
|
893
889
|
// 降级:无逐次数据,写一条累计行
|
|
894
|
-
modelCalls = [{ call_index: 0, model: callModel_, tokenUsage: u, degraded: true }];
|
|
890
|
+
modelCalls = [{ call_index: 0, model: callModel_, tokenUsage: u, contextUsage: contextUsageForCall(u), degraded: true }];
|
|
895
891
|
}
|
|
896
892
|
yield {
|
|
897
893
|
type: 'complete',
|
|
@@ -1122,7 +1118,7 @@ export class AgentRunner {
|
|
|
1122
1118
|
model: sdkModel,
|
|
1123
1119
|
...(callEffort ? { effort: callEffort } : {}),
|
|
1124
1120
|
...(this.claudeExecutablePath ? { pathToClaudeCodeExecutable: this.claudeExecutablePath } : {}),
|
|
1125
|
-
autoCompactWindow:
|
|
1121
|
+
autoCompactWindow: autoCompactWindowForModel(sdkModel),
|
|
1126
1122
|
advisorModel: 'haiku',
|
|
1127
1123
|
canUseTool: canUseToolCallback,
|
|
1128
1124
|
permissionMode: sdkPermissionMode,
|
|
@@ -307,6 +307,7 @@ export class CodexRunner {
|
|
|
307
307
|
streamedAgentMessageIds: new Set(),
|
|
308
308
|
agentMessageDeltaText: new Map(),
|
|
309
309
|
completedItemIds: new Set(),
|
|
310
|
+
emittedEditCallIds: new Set(),
|
|
310
311
|
completedTurnIds: new Set(),
|
|
311
312
|
};
|
|
312
313
|
const unsubscribe = appServer.onNotification(notification => {
|
|
@@ -1073,7 +1074,7 @@ export class CodexRunner {
|
|
|
1073
1074
|
break;
|
|
1074
1075
|
}
|
|
1075
1076
|
case 'item/started': {
|
|
1076
|
-
yield* this.mapAppServerItemStarted(params.item);
|
|
1077
|
+
yield* this.mapAppServerItemStarted(params.item, state);
|
|
1077
1078
|
break;
|
|
1078
1079
|
}
|
|
1079
1080
|
case 'item/agentMessage/delta': {
|
|
@@ -1092,6 +1093,10 @@ export class CodexRunner {
|
|
|
1092
1093
|
yield* this.mapAppServerItemCompleted(item, state);
|
|
1093
1094
|
break;
|
|
1094
1095
|
}
|
|
1096
|
+
case 'item/fileChange/patchUpdated': {
|
|
1097
|
+
yield* this.mapAppServerFileChangePatchUpdated(params, state);
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1095
1100
|
case 'turn/plan/updated': {
|
|
1096
1101
|
const plan = Array.isArray(params.plan) ? params.plan : [];
|
|
1097
1102
|
const completed = plan.filter((step) => step?.status === 'completed').length;
|
|
@@ -1123,12 +1128,17 @@ export class CodexRunner {
|
|
|
1123
1128
|
break;
|
|
1124
1129
|
}
|
|
1125
1130
|
case 'error': {
|
|
1126
|
-
|
|
1131
|
+
if (!params.message) {
|
|
1132
|
+
// SSE idle timeout reconnect — not a real task error, suppress
|
|
1133
|
+
logger.debug(`[CodexRunner] app-server SSE reconnect (no message)`);
|
|
1134
|
+
break;
|
|
1135
|
+
}
|
|
1136
|
+
yield { type: 'error', error: params.message, errorType: 'unknown' };
|
|
1127
1137
|
break;
|
|
1128
1138
|
}
|
|
1129
1139
|
}
|
|
1130
1140
|
}
|
|
1131
|
-
*mapAppServerItemStarted(item) {
|
|
1141
|
+
*mapAppServerItemStarted(item, state) {
|
|
1132
1142
|
if (!item)
|
|
1133
1143
|
return;
|
|
1134
1144
|
switch (item.type) {
|
|
@@ -1142,8 +1152,12 @@ export class CodexRunner {
|
|
|
1142
1152
|
yield { type: 'tool_use', name: item.namespace ? `${item.namespace}:${item.tool}` : item.tool, input: item.arguments, callId: item.id };
|
|
1143
1153
|
break;
|
|
1144
1154
|
case 'fileChange': {
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1155
|
+
const editEvent = this.buildCodexEditEvent(item.id, item.changes);
|
|
1156
|
+
if (editEvent) {
|
|
1157
|
+
if (item.id)
|
|
1158
|
+
state?.emittedEditCallIds.add(item.id);
|
|
1159
|
+
yield editEvent;
|
|
1160
|
+
}
|
|
1147
1161
|
break;
|
|
1148
1162
|
}
|
|
1149
1163
|
case 'webSearch':
|
|
@@ -1197,10 +1211,80 @@ export class CodexRunner {
|
|
|
1197
1211
|
};
|
|
1198
1212
|
break;
|
|
1199
1213
|
case 'fileChange':
|
|
1200
|
-
|
|
1214
|
+
if (this.fileChangesHaveProtocolDiff(item.changes)) {
|
|
1215
|
+
if (item.id && !state.emittedEditCallIds.has(item.id)) {
|
|
1216
|
+
const editEvent = this.buildCodexEditEvent(item.id, item.changes);
|
|
1217
|
+
if (editEvent) {
|
|
1218
|
+
state.emittedEditCallIds.add(item.id);
|
|
1219
|
+
yield editEvent;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
yield { type: 'tool_result', name: 'Edit', result: item.changes, isError: item.status === 'failed', callId: item.id };
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
const desc = this.normalizeFileChanges(item.changes).map((change) => this.describeFileChange(change)).join(', ');
|
|
1226
|
+
yield { type: 'tool_use', name: 'FileChange', input: { description: desc }, callId: item.id };
|
|
1227
|
+
yield { type: 'tool_result', name: 'FileChange', result: item.changes, isError: item.status === 'failed', callId: item.id };
|
|
1228
|
+
}
|
|
1201
1229
|
break;
|
|
1202
1230
|
}
|
|
1203
1231
|
}
|
|
1232
|
+
*mapAppServerFileChangePatchUpdated(params, state) {
|
|
1233
|
+
const itemId = typeof params.itemId === 'string' ? params.itemId : undefined;
|
|
1234
|
+
if (!itemId || state.emittedEditCallIds.has(itemId))
|
|
1235
|
+
return;
|
|
1236
|
+
const editEvent = this.buildCodexEditEvent(itemId, params.changes);
|
|
1237
|
+
if (!editEvent)
|
|
1238
|
+
return;
|
|
1239
|
+
state.emittedEditCallIds.add(itemId);
|
|
1240
|
+
yield editEvent;
|
|
1241
|
+
}
|
|
1242
|
+
buildCodexEditEvent(callId, changes) {
|
|
1243
|
+
const editInput = this.buildCodexEditInput(changes);
|
|
1244
|
+
if (!editInput)
|
|
1245
|
+
return null;
|
|
1246
|
+
return { type: 'tool_use', name: 'Edit', input: editInput, callId };
|
|
1247
|
+
}
|
|
1248
|
+
buildCodexEditInput(changes) {
|
|
1249
|
+
const normalized = this.normalizeFileChanges(changes)
|
|
1250
|
+
.map((change) => this.normalizeCodexProtocolDiffChange(change))
|
|
1251
|
+
.filter((change) => !!change);
|
|
1252
|
+
if (normalized.length === 0)
|
|
1253
|
+
return null;
|
|
1254
|
+
const first = normalized[0];
|
|
1255
|
+
return {
|
|
1256
|
+
file_path: first.path,
|
|
1257
|
+
unified_diff: normalized.map(change => this.formatCodexUnifiedDiff(change)).join('\n'),
|
|
1258
|
+
codex_file_changes: normalized,
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
normalizeCodexProtocolDiffChange(change) {
|
|
1262
|
+
const filePath = typeof change?.path === 'string' ? change.path : '';
|
|
1263
|
+
const diff = typeof change?.diff === 'string' ? change.diff
|
|
1264
|
+
: typeof change?.unified_diff === 'string' ? change.unified_diff
|
|
1265
|
+
: typeof change?.unifiedDiff === 'string' ? change.unifiedDiff
|
|
1266
|
+
: '';
|
|
1267
|
+
if (!filePath || !diff)
|
|
1268
|
+
return null;
|
|
1269
|
+
return {
|
|
1270
|
+
path: filePath,
|
|
1271
|
+
diff,
|
|
1272
|
+
kind: this.normalizeFileChangeKind(change.kind ?? change.type),
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
fileChangesHaveProtocolDiff(changes) {
|
|
1276
|
+
return this.buildCodexEditInput(changes) !== null;
|
|
1277
|
+
}
|
|
1278
|
+
formatCodexUnifiedDiff(change) {
|
|
1279
|
+
const pathLabel = change.path.replace(/\\/g, '/');
|
|
1280
|
+
const diffPath = pathLabel.replace(/^\/+/, '');
|
|
1281
|
+
const header = change.diff.startsWith('diff ')
|
|
1282
|
+
|| change.diff.startsWith('--- ')
|
|
1283
|
+
|| change.diff.startsWith('+++ ')
|
|
1284
|
+
? ''
|
|
1285
|
+
: `--- a/${diffPath}\n+++ b/${diffPath}\n`;
|
|
1286
|
+
return `${header}${change.diff.trimEnd()}`;
|
|
1287
|
+
}
|
|
1204
1288
|
pickNumber(...values) {
|
|
1205
1289
|
for (const value of values) {
|
|
1206
1290
|
if (typeof value === 'number' && Number.isFinite(value))
|
|
@@ -26,3 +26,33 @@ export function usageForContext(usage) {
|
|
|
26
26
|
const lastIteration = iterations?.slice().reverse().find(it => contextTokensForUsage(it, true) > 0);
|
|
27
27
|
return lastIteration ?? usage;
|
|
28
28
|
}
|
|
29
|
+
/** Models whose base ID uses a 1M context window when sent to the SDK with [1m]. */
|
|
30
|
+
export const ONE_M_CONTEXT_MODEL_PREFIXES = ['claude-opus-4-8', 'claude-sonnet-4-6'];
|
|
31
|
+
const ONE_M_CONTEXT_MODEL_ALIASES = ['opus', 'sonnet'];
|
|
32
|
+
const CLAUDE_CONTEXT_MODEL_ALIASES = ['opus', 'sonnet', 'haiku'];
|
|
33
|
+
export function isClaudeContextUsageModel(model) {
|
|
34
|
+
if (!model)
|
|
35
|
+
return false;
|
|
36
|
+
const baseModel = model.replace(/\[1m\]$/i, '');
|
|
37
|
+
return /^claude-/i.test(baseModel) || CLAUDE_CONTEXT_MODEL_ALIASES.includes(baseModel);
|
|
38
|
+
}
|
|
39
|
+
export function isOneMillionContextModel(model) {
|
|
40
|
+
if (!model)
|
|
41
|
+
return false;
|
|
42
|
+
if (ONE_M_CONTEXT_MODEL_ALIASES.includes(model))
|
|
43
|
+
return true;
|
|
44
|
+
if (/\[1m\]$/i.test(model))
|
|
45
|
+
return true;
|
|
46
|
+
if (/deepseek-v4/i.test(model))
|
|
47
|
+
return true;
|
|
48
|
+
const baseModel = model.replace(/\[1m\]$/i, '');
|
|
49
|
+
return ONE_M_CONTEXT_MODEL_PREFIXES.some(prefix => baseModel === prefix || baseModel.startsWith(`${prefix}-`));
|
|
50
|
+
}
|
|
51
|
+
/** Real context window size: 1M models = 1000000, otherwise 200000. */
|
|
52
|
+
export function realContextWindowForModel(model) {
|
|
53
|
+
return isOneMillionContextModel(model) ? 1000000 : 200000;
|
|
54
|
+
}
|
|
55
|
+
/** autoCompact trigger threshold: 1M models = 900000, otherwise 200000. */
|
|
56
|
+
export function autoCompactWindowForModel(model) {
|
|
57
|
+
return isOneMillionContextModel(model) ? 900000 : 200000;
|
|
58
|
+
}
|
package/dist/aun/outbox.js
CHANGED
|
@@ -59,10 +59,14 @@ export function enqueue(aid, opts) {
|
|
|
59
59
|
aid,
|
|
60
60
|
channelId: opts.channelId,
|
|
61
61
|
type: opts.type,
|
|
62
|
+
contentKind: opts.contentKind,
|
|
63
|
+
payload: opts.payload,
|
|
62
64
|
text: opts.text,
|
|
63
65
|
filePath: opts.filePath,
|
|
66
|
+
logText: opts.logText,
|
|
64
67
|
context: opts.context,
|
|
65
68
|
ttl: opts.ttl ?? DEFAULT_TTL,
|
|
69
|
+
postSend: opts.postSend,
|
|
66
70
|
};
|
|
67
71
|
const dir = outboxDir();
|
|
68
72
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -97,6 +101,7 @@ export async function drain(aid, sender) {
|
|
|
97
101
|
const entries = readEntries(aid);
|
|
98
102
|
if (entries.length === 0)
|
|
99
103
|
return { sent: 0, expired: 0, failed: 0 };
|
|
104
|
+
const drainedIds = new Set(entries.map(e => e.id));
|
|
100
105
|
let sent = 0;
|
|
101
106
|
let expired = 0;
|
|
102
107
|
let failed = 0;
|
|
@@ -107,21 +112,28 @@ export async function drain(aid, sender) {
|
|
|
107
112
|
continue;
|
|
108
113
|
}
|
|
109
114
|
try {
|
|
115
|
+
entry.attempts = (entry.attempts ?? 0) + 1;
|
|
110
116
|
const ok = await sender(entry);
|
|
111
117
|
if (ok) {
|
|
112
118
|
sent++;
|
|
113
119
|
}
|
|
114
120
|
else {
|
|
115
121
|
failed++;
|
|
122
|
+
entry.lastError = 'sender returned false';
|
|
116
123
|
remaining.push(entry);
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
|
-
catch {
|
|
126
|
+
catch (e) {
|
|
120
127
|
failed++;
|
|
128
|
+
entry.lastError = e instanceof Error ? e.message : String(e);
|
|
121
129
|
remaining.push(entry);
|
|
122
130
|
}
|
|
123
131
|
}
|
|
124
|
-
|
|
132
|
+
const current = readEntries(aid);
|
|
133
|
+
const currentIds = new Set(current.map(e => e.id));
|
|
134
|
+
const retainedNewEntries = current.filter(e => !drainedIds.has(e.id));
|
|
135
|
+
const retainedFailedEntries = remaining.filter(e => currentIds.has(e.id));
|
|
136
|
+
writeEntries(aid, [...retainedFailedEntries, ...retainedNewEntries]);
|
|
125
137
|
return { sent, expired, failed };
|
|
126
138
|
}
|
|
127
139
|
export function hasPending(aid) {
|