lark-remote 0.5.19 → 0.5.21
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 +29 -0
- package/dist/bridge/approval-actions.d.ts +1 -1
- package/dist/bridge/approval-actions.js +1 -1
- package/dist/bridge/bash-run.d.ts +71 -0
- package/dist/bridge/bash-run.js +217 -0
- package/dist/bridge/compact.d.ts +155 -0
- package/dist/bridge/compact.js +450 -0
- package/dist/bridge/final-usage.js +4 -4
- package/dist/bridge/inbound-media.js +72 -46
- package/dist/bridge/index.d.ts +106 -153
- package/dist/bridge/index.js +400 -1000
- package/dist/bridge/job-watch.d.ts +68 -0
- package/dist/bridge/job-watch.js +146 -0
- package/dist/bridge/queue-manager.d.ts +3 -3
- package/dist/bridge/queue-manager.js +10 -29
- package/dist/card/approval-render.d.ts +2 -2
- package/dist/card/approval-render.js +141 -130
- package/dist/card/card-budget.js +123 -98
- package/dist/card/card-helpers.d.ts +3 -3
- package/dist/card/card-helpers.js +6 -6
- package/dist/card/final-answer-file.js +1 -1
- package/dist/card/run-renderer.d.ts +3 -3
- package/dist/card/run-renderer.js +114 -159
- package/dist/card/run-state.d.ts +1 -15
- package/dist/card/run-state.js +358 -292
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +21 -17
- package/dist/card/usage-format.d.ts +22 -7
- package/dist/card/usage-format.js +119 -100
- package/dist/cli.js +15 -3
- package/dist/clone.js +2 -2
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/config/codex-config.js +130 -106
- package/dist/config/index.d.ts +16 -2
- package/dist/config/index.js +23 -8
- package/dist/connector/index.js +6 -6
- package/dist/error-classification.js +3 -2
- package/dist/inbound/placeholder.d.ts +1 -1
- package/dist/inbound/placeholder.js +1 -1
- package/dist/index.js +387 -281
- package/dist/logger/index.js +6 -6
- package/dist/message-handlers.d.ts +0 -5
- package/dist/message-handlers.js +254 -237
- package/dist/model-catalog.d.ts +2 -2
- package/dist/model-catalog.js +3 -8
- package/dist/platform/identity.js +1 -1
- package/dist/platform/path.d.ts +1 -1
- package/dist/platform/path.js +1 -1
- package/dist/platform/sleep-blocker.js +1 -1
- package/dist/platform/terminator-win32.js +2 -1
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +190 -152
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/config/dsh.d.ts +4 -8
- package/dist/router/config/dsh.js +3 -10
- package/dist/router/config/kimi.js +1 -1
- package/dist/router/config/types.d.ts +1 -1
- package/dist/router/config-diff.js +1 -1
- package/dist/router/index.d.ts +56 -11
- package/dist/router/index.js +401 -391
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +6 -4
- package/dist/runner/codex/app-server/runner.d.ts +9 -2
- package/dist/runner/codex/app-server/runner.js +30 -25
- package/dist/runner/codex/app-server/translator.d.ts +2 -5
- package/dist/runner/codex/app-server/translator.js +291 -238
- package/dist/runner/common/acp/acp-approval.d.ts +1 -1
- package/dist/runner/common/acp/acp-approval.js +1 -1
- package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
- package/dist/runner/common/acp/base-acp-runner.js +26 -65
- package/dist/runner/common/acp/base-translator.d.ts +12 -2
- package/dist/runner/common/acp/base-translator.js +50 -26
- package/dist/runner/common/acp/protocol-types.d.ts +1 -1
- package/dist/runner/common/acp/protocol-types.js +2 -2
- package/dist/runner/common/connection-based-runner.d.ts +32 -1
- package/dist/runner/common/connection-based-runner.js +32 -2
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
- package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
- package/dist/runner/common/spawning-runner.d.ts +6 -1
- package/dist/runner/common/spawning-runner.js +9 -4
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/catalog-probe.d.ts +17 -0
- package/dist/runner/dsh/catalog-probe.js +10 -0
- package/dist/runner/dsh/client.d.ts +7 -11
- package/dist/runner/dsh/client.js +356 -289
- package/dist/runner/dsh/exit-cancel.d.ts +2 -2
- package/dist/runner/dsh/exit-cancel.js +2 -2
- package/dist/runner/dsh/inflight.d.ts +1 -1
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +44 -4
- package/dist/runner/dsh/runner.js +296 -199
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +1 -2
- package/dist/runner/kimi/acp/runner.js +82 -46
- package/dist/runner/kimi/acp/translator.d.ts +1 -1
- package/dist/runner/kimi/acp/translator.js +3 -4
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +4 -4
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
- package/dist/runner/pi/rpc/protocol-types.js +1 -1
- package/dist/runner/pi/rpc/runner.d.ts +3 -0
- package/dist/runner/pi/rpc/runner.js +27 -42
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/runner/types.d.ts +2 -2
- package/dist/runner/types.js +1 -1
- package/dist/session/claude/sessions.d.ts +45 -0
- package/dist/session/claude/sessions.js +160 -122
- package/dist/session/codex/rollout-reader.js +181 -182
- package/dist/session/common/throw-session-list-error.d.ts +10 -0
- package/dist/session/common/throw-session-list-error.js +16 -0
- package/dist/session/dsh/sessions.js +291 -235
- package/dist/session/kimi/sessions.d.ts +40 -8
- package/dist/session/kimi/sessions.js +373 -301
- package/dist/session/opencode/sessions.d.ts +142 -1
- package/dist/session/opencode/sessions.js +378 -321
- package/dist/session/pi/sessions.js +4 -5
- package/dist/session/session-store.d.ts +9 -1
- package/dist/session/session-store.js +99 -99
- package/dist/update/version-check.js +2 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.js +1 -1
- package/package.json +1 -1
package/dist/logger/index.js
CHANGED
|
@@ -21,12 +21,12 @@ function todayStr(d) {
|
|
|
21
21
|
return `${y}-${m}-${day}`;
|
|
22
22
|
}
|
|
23
23
|
function timestampStr(d) {
|
|
24
|
-
//
|
|
25
|
-
// 2026-08-03T01:30:00.000+08:00.
|
|
26
|
-
//
|
|
27
|
-
// local midnight
|
|
28
|
-
//
|
|
29
|
-
//
|
|
24
|
+
// Local ISO 8601 with milliseconds and explicit offset, e.g.
|
|
25
|
+
// 2026-08-03T01:30:00.000+08:00. Must match the daily directory boundary
|
|
26
|
+
// (todayStr), which is local time: a UTC timestamp would put lines written
|
|
27
|
+
// after local midnight into the previous UTC day's file. Local offset keeps
|
|
28
|
+
// the timestamp's calendar date consistent with the directory date across
|
|
29
|
+
// all timezones.
|
|
30
30
|
const pad = (n) => String(n).padStart(2, '0');
|
|
31
31
|
const y = d.getFullYear();
|
|
32
32
|
const mo = pad(d.getMonth() + 1);
|
|
@@ -7,11 +7,6 @@ import type { SessionStore } from './session/index.js';
|
|
|
7
7
|
import type { WorkspaceStore } from './workspace/index.js';
|
|
8
8
|
import type { OwnerBinder } from './binder.js';
|
|
9
9
|
import type { CloneSession } from './clone.js';
|
|
10
|
-
/**
|
|
11
|
-
* /stop 命令别名单源:普通消息的停止分支与 clone 活跃期拦截的排除条件
|
|
12
|
-
* 共用同一判定,防止将来加别名时两处漂移。
|
|
13
|
-
*/
|
|
14
|
-
export declare function isStopCommand(text: string): boolean;
|
|
15
10
|
export interface MessageHandlerDeps {
|
|
16
11
|
connector: FeishuConnector;
|
|
17
12
|
router: CommandRouter;
|
package/dist/message-handlers.js
CHANGED
|
@@ -14,264 +14,281 @@ const SESSION_MUTATING_FLUSH_BUDGET_MS = 2_000;
|
|
|
14
14
|
* /stop 命令别名单源:普通消息的停止分支与 clone 活跃期拦截的排除条件
|
|
15
15
|
* 共用同一判定,防止将来加别名时两处漂移。
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
function isStopCommand(text) {
|
|
18
18
|
const t = text.trim().toLowerCase();
|
|
19
19
|
return t === '/stop' || t === '/t';
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
messageId,
|
|
51
|
-
messagePreview: prompt.slice(0, 3000),
|
|
52
|
-
binding,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
},
|
|
56
|
-
onReceipt: async (ctx, text) => {
|
|
57
|
-
await bridge.sendResult({ text }, ctx);
|
|
21
|
+
/** 入站统一装配器 commit:图/文组装成 turn → 进 agent 队列。 */
|
|
22
|
+
function handleTurnCommit(ctx, turn, prompt) {
|
|
23
|
+
const messageId = turn.messageIds[turn.messageIds.length - 1] ?? '';
|
|
24
|
+
const taskCtx = {
|
|
25
|
+
userId: turn.userId,
|
|
26
|
+
chatId: turn.chatId,
|
|
27
|
+
messageId,
|
|
28
|
+
turnMessageIds: turn.messageIds,
|
|
29
|
+
};
|
|
30
|
+
let cwd = ctx.sessionStore.getCwd(turn.userId) ?? '';
|
|
31
|
+
if (!cwd) {
|
|
32
|
+
const recent = [...ctx.workspaceStore.list()].sort((a, b) => b.lastUsedAt - a.lastUsedAt);
|
|
33
|
+
if (recent.length > 0)
|
|
34
|
+
cwd = recent[0].path;
|
|
35
|
+
}
|
|
36
|
+
const binding = ctx.bridge.currentBinding(turn.userId);
|
|
37
|
+
ctx.bridge.enqueue(cwd, async () => {
|
|
38
|
+
await ctx.router.handle(prompt, taskCtx, {
|
|
39
|
+
cwdOverride: cwd,
|
|
40
|
+
binding,
|
|
41
|
+
allowCommandPrefix: false,
|
|
42
|
+
});
|
|
43
|
+
}, {
|
|
44
|
+
taskMeta: {
|
|
45
|
+
userId: turn.userId,
|
|
46
|
+
chatId: turn.chatId,
|
|
47
|
+
messageId,
|
|
48
|
+
messagePreview: prompt.slice(0, 3000),
|
|
49
|
+
binding,
|
|
58
50
|
},
|
|
59
51
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
}
|
|
53
|
+
/** 入站回执(无文本纯附件 / rejected 项):面向用户的提示。 */
|
|
54
|
+
async function handleTurnReceipt(ctx, receiptCtx, text) {
|
|
55
|
+
await ctx.bridge.sendResult({ text }, receiptCtx);
|
|
56
|
+
}
|
|
57
|
+
/** owner 绑定成功后的 onboarding 消息(fire-and-forget)。 */
|
|
58
|
+
function handleBind(ctx, userId, chatId) {
|
|
59
|
+
let resolvedCwd;
|
|
60
|
+
try {
|
|
61
|
+
resolvedCwd = fs.realpathSync(process.cwd());
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
resolvedCwd = process.cwd();
|
|
65
|
+
}
|
|
66
|
+
if (!ctx.sessionStore.getCwd(userId)) {
|
|
67
|
+
ctx.sessionStore.setCwd(userId, resolvedCwd);
|
|
68
|
+
}
|
|
69
|
+
return (async () => {
|
|
70
|
+
try {
|
|
71
|
+
await ctx.connector.sendWithRetry(chatId, {
|
|
72
|
+
text: `✅ 已绑定到本账号,此后仅你可使用本应用\n` +
|
|
73
|
+
`📂 当前工作目录: \`${resolvedCwd}\`\n` +
|
|
74
|
+
`🤖 默认 Agent: ${agentDisplayName(ctx.config.defaultAgent)}\n` +
|
|
75
|
+
`💡 直接输入消息即可开始对话,或 /cd 切换目录`,
|
|
76
|
+
});
|
|
77
|
+
const helpResult = ctx.router.cmdHelp();
|
|
78
|
+
if (helpResult.card) {
|
|
79
|
+
await ctx.connector.sendWithRetry(chatId, { card: helpResult.card });
|
|
73
80
|
}
|
|
74
|
-
return (async () => {
|
|
75
|
-
try {
|
|
76
|
-
await connector.sendWithRetry(chatId, {
|
|
77
|
-
text: `✅ 已绑定到本账号,此后仅你可使用本应用\n` +
|
|
78
|
-
`📂 当前工作目录: \`${resolvedCwd}\`\n` +
|
|
79
|
-
`🤖 默认 Agent: ${agentDisplayName(config.defaultAgent)}\n` +
|
|
80
|
-
`💡 直接输入消息即可开始对话,或 /cd 切换目录`,
|
|
81
|
-
});
|
|
82
|
-
const helpResult = router.cmdHelp();
|
|
83
|
-
if (helpResult.card) {
|
|
84
|
-
await connector.sendWithRetry(chatId, { card: helpResult.card });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
logger.error('[binder] onboarding send failed:', err);
|
|
89
|
-
}
|
|
90
|
-
})();
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
connector.setInboundMediaDetectedHandler((msg) => {
|
|
94
|
-
const authorization = authorizer.authorize(msg.userId, msg.chatId);
|
|
95
|
-
if (authorization === 'rejected') {
|
|
96
|
-
logger.warn(`[media] rejected inbound media from non-owner ${msg.userId}`);
|
|
97
|
-
return;
|
|
98
81
|
}
|
|
99
|
-
|
|
100
|
-
logger.
|
|
101
|
-
return;
|
|
82
|
+
catch (err) {
|
|
83
|
+
ctx.logger.error('[binder] onboarding send failed:', err);
|
|
102
84
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
maxFileSizeMb: router.config.inboundMedia.maxFileSizeMb,
|
|
120
|
-
});
|
|
121
|
-
try {
|
|
122
|
-
return await bridge.saveInboundMedia(payload);
|
|
123
|
-
}
|
|
124
|
-
catch (err) {
|
|
125
|
-
for (const item of payload.media)
|
|
126
|
-
bestEffortUnlink(item.tempPath);
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
129
|
-
})();
|
|
130
|
-
assembler.ingest({
|
|
131
|
-
kind: 'media',
|
|
85
|
+
})();
|
|
86
|
+
}
|
|
87
|
+
/** 入站媒体(image/file)检测:鉴权 → clone 拦截 → 落盘或 rejected 回执。 */
|
|
88
|
+
function handleInboundMedia(ctx, msg) {
|
|
89
|
+
const authorization = ctx.authorizer.authorize(msg.userId, msg.chatId);
|
|
90
|
+
if (authorization === 'rejected') {
|
|
91
|
+
ctx.logger.warn(`[media] rejected inbound media from non-owner ${msg.userId}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (ctx.cloneSession.isActive()) {
|
|
95
|
+
ctx.logger.info(`[media] dropped inbound media during clone session ${msg.messageId}`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!ctx.router.config.inboundMedia.enabled) {
|
|
99
|
+
ctx.assembler.ingest({
|
|
100
|
+
kind: 'rejected',
|
|
132
101
|
userId: msg.userId,
|
|
133
102
|
chatId: msg.chatId,
|
|
134
103
|
messageId: msg.messageId,
|
|
135
104
|
replyToMessageId: msg.replyToMessageId,
|
|
136
105
|
rawContentType: msg.rawContentType,
|
|
137
|
-
|
|
106
|
+
rejectedKind: 'file',
|
|
107
|
+
reason: '入站媒体保存已关闭(inboundMedia.enabled: false),未保存文件。' +
|
|
108
|
+
'如需自动保存图片/文件,请开启后重试',
|
|
138
109
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
void cloneSession
|
|
148
|
-
.handleMessage(msg.content, {
|
|
149
|
-
userId: msg.userId,
|
|
150
|
-
chatId: msg.chatId,
|
|
151
|
-
messageId: msg.messageId,
|
|
152
|
-
})
|
|
153
|
-
.catch((err) => logger.error('[clone] message handling failed:', err));
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const content = router.expandAliasMessage(msg.content);
|
|
157
|
-
if (isStopCommand(content)) {
|
|
158
|
-
void (async () => {
|
|
159
|
-
const interrupt = await bridge.interruptCurrentRunDetailed({
|
|
160
|
-
userId: msg.userId,
|
|
161
|
-
chatId: msg.chatId,
|
|
162
|
-
});
|
|
163
|
-
const ctx = { userId: msg.userId, chatId: msg.chatId, messageId: msg.messageId };
|
|
164
|
-
if (!interrupt.stopped) {
|
|
165
|
-
await bridge.sendResult({ text: '当前没有运行中的进程' }, ctx);
|
|
166
|
-
}
|
|
167
|
-
else if ((interrupt.stoppedCount ?? 1) > 1) {
|
|
168
|
-
await bridge.sendResult({ text: `✅ 已停止 ${interrupt.stoppedCount} 个运行中的任务` }, ctx);
|
|
169
|
-
}
|
|
170
|
-
})().catch((err) => logger.error('[control] /stop failed:', err));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const stripped = stripPlaceholders(content);
|
|
174
|
-
if (stripped.unknownTags.length > 0) {
|
|
175
|
-
logger.warn(`[inbound] unknown placeholder tags stripped: ${stripped.unknownTags.join(', ')} ` +
|
|
176
|
-
`(msg_type=${msg.rawContentType})`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const outcome = (async () => {
|
|
113
|
+
const payload = await ctx.connector.downloadInboundMedia(msg, {
|
|
114
|
+
maxFileSizeMb: ctx.router.config.inboundMedia.maxFileSizeMb,
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
return await ctx.bridge.saveInboundMedia(payload);
|
|
177
118
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
await router.handle(stripped.clean, {
|
|
183
|
-
userId: msg.userId,
|
|
184
|
-
chatId: msg.chatId,
|
|
185
|
-
messageId: msg.messageId,
|
|
186
|
-
}, { allowCommandPrefix: true });
|
|
187
|
-
})().catch((err) => logger.error('[control] command failed:', err));
|
|
188
|
-
return;
|
|
119
|
+
catch (err) {
|
|
120
|
+
for (const item of payload.media)
|
|
121
|
+
bestEffortUnlink(item.tempPath);
|
|
122
|
+
throw err;
|
|
189
123
|
}
|
|
190
|
-
|
|
191
|
-
|
|
124
|
+
})();
|
|
125
|
+
ctx.assembler.ingest({
|
|
126
|
+
kind: 'media',
|
|
127
|
+
userId: msg.userId,
|
|
128
|
+
chatId: msg.chatId,
|
|
129
|
+
messageId: msg.messageId,
|
|
130
|
+
replyToMessageId: msg.replyToMessageId,
|
|
131
|
+
rawContentType: msg.rawContentType,
|
|
132
|
+
outcome,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** 普通入站消息:鉴权 → clone 拦截 → /stop → 命令前缀 → 装配器。 */
|
|
136
|
+
function handleInboundMessage(ctx, msg) {
|
|
137
|
+
const decision = ctx.authorizer.authorize(msg.userId, msg.chatId);
|
|
138
|
+
if (decision === 'rejected' || decision === 'bind_success')
|
|
139
|
+
return;
|
|
140
|
+
ctx.logger.info(`message from ${msg.userId} (${msg.rawContentType}): ${msg.content.slice(0, 100)}`);
|
|
141
|
+
void ctx.connector.addReaction(msg.messageId, 'Typing');
|
|
142
|
+
if (ctx.cloneSession.isActive() && !isStopCommand(msg.content)) {
|
|
143
|
+
void ctx.cloneSession
|
|
144
|
+
.handleMessage(msg.content, {
|
|
192
145
|
userId: msg.userId,
|
|
193
146
|
chatId: msg.chatId,
|
|
194
147
|
messageId: msg.messageId,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
148
|
+
})
|
|
149
|
+
.catch((err) => ctx.logger.error('[clone] message handling failed:', err));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const content = ctx.router.expandAliasMessage(msg.content);
|
|
153
|
+
if (isStopCommand(content)) {
|
|
154
|
+
void (async () => {
|
|
155
|
+
const interrupt = await ctx.bridge.interruptCurrentRunDetailed({
|
|
156
|
+
userId: msg.userId,
|
|
157
|
+
chatId: msg.chatId,
|
|
158
|
+
});
|
|
159
|
+
const stopCtx = { userId: msg.userId, chatId: msg.chatId, messageId: msg.messageId };
|
|
160
|
+
if (!interrupt.stopped) {
|
|
161
|
+
await ctx.bridge.sendResult({ text: '当前没有运行中的进程' }, stopCtx);
|
|
162
|
+
}
|
|
163
|
+
else if ((interrupt.stoppedCount ?? 1) > 1) {
|
|
164
|
+
await ctx.bridge.sendResult({ text: `✅ 已停止 ${interrupt.stoppedCount} 个运行中的任务` }, stopCtx);
|
|
165
|
+
}
|
|
166
|
+
})().catch((err) => ctx.logger.error('[control] /stop failed:', err));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const stripped = stripPlaceholders(content);
|
|
170
|
+
if (stripped.unknownTags.length > 0) {
|
|
171
|
+
ctx.logger.warn(`[inbound] unknown placeholder tags stripped: ${stripped.unknownTags.join(', ')} ` +
|
|
172
|
+
`(msg_type=${msg.rawContentType})`);
|
|
173
|
+
}
|
|
174
|
+
const commandEligible = msg.rawContentType === 'text' && stripped.kinds.length === 0 && stripped.clean !== '';
|
|
175
|
+
if (commandEligible && (stripped.clean.startsWith('/') || stripped.clean.startsWith('!'))) {
|
|
176
|
+
void (async () => {
|
|
177
|
+
await ctx.assembler.flush(msg.userId, msg.chatId, 'flush');
|
|
178
|
+
await ctx.router.handle(stripped.clean, {
|
|
179
|
+
userId: msg.userId,
|
|
180
|
+
chatId: msg.chatId,
|
|
181
|
+
messageId: msg.messageId,
|
|
182
|
+
}, { allowCommandPrefix: true });
|
|
183
|
+
})().catch((err) => ctx.logger.error('[control] command failed:', err));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
ctx.assembler.ingest({
|
|
187
|
+
kind: 'text',
|
|
188
|
+
userId: msg.userId,
|
|
189
|
+
chatId: msg.chatId,
|
|
190
|
+
messageId: msg.messageId,
|
|
191
|
+
replyToMessageId: msg.replyToMessageId,
|
|
192
|
+
rawContentType: msg.rawContentType,
|
|
193
|
+
text: stripped.clean,
|
|
194
|
+
placeholders: stripped.kinds,
|
|
195
|
+
unknownTags: stripped.unknownTags,
|
|
201
196
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
197
|
+
}
|
|
198
|
+
/** 卡片动作:鉴权 → stop/直返/order.exec → immediate 或普通队列。 */
|
|
199
|
+
async function handleCardAction(ctx, action) {
|
|
200
|
+
if (!ctx.binder.isOwner(action.operator.openId)) {
|
|
201
|
+
ctx.binder.recordRejectedCardAction(action.operator.openId);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const actionValue = action.action.value;
|
|
205
|
+
if (!actionValue?.cmd)
|
|
206
|
+
return;
|
|
207
|
+
ctx.logger.info(actionValue.cmd === 'stop'
|
|
208
|
+
? `card action: stop runId=${actionValue.runId ?? '(none)'} messageId=${action.messageId}`
|
|
209
|
+
: `card action: ${actionValue.cmd}`);
|
|
210
|
+
const userId = action.operator.openId;
|
|
211
|
+
const chatId = action.chatId;
|
|
212
|
+
const messageId = action.messageId;
|
|
213
|
+
void ctx.connector.addReaction(messageId, 'Typing');
|
|
214
|
+
if (actionValue.cmd === 'stop') {
|
|
215
|
+
void ctx.router
|
|
216
|
+
.handleCardAction({ ...actionValue, cmd: 'stop' }, { userId, chatId, messageId })
|
|
217
|
+
.catch((err) => ctx.logger.error('[control] card stop failed:', err));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const isImmediate = isImmediateAction(actionValue.cmd);
|
|
221
|
+
const cwd = ctx.sessionStore.getCwd(userId) ?? '';
|
|
222
|
+
const fullValue = buildCardActionFullValue(actionValue, action);
|
|
223
|
+
if (SESSION_MUTATING_ACTION_CMDS.has(actionValue.cmd)) {
|
|
224
|
+
const flushP = ctx.assembler
|
|
225
|
+
.flush(userId, chatId, 'flush')
|
|
226
|
+
.catch((err) => ctx.logger.error('[control] inbound flush failed:', err));
|
|
227
|
+
await Promise.race([flushP, sleep(SESSION_MUTATING_FLUSH_BUDGET_MS)]);
|
|
228
|
+
}
|
|
229
|
+
if (DIRECT_RETURN_CMDS.has(actionValue.cmd)) {
|
|
230
|
+
return ctx.router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
231
|
+
}
|
|
232
|
+
if (actionValue.cmd === 'order.exec') {
|
|
233
|
+
void dispatchOrderExecForQueue({
|
|
234
|
+
router: ctx.router,
|
|
235
|
+
bridge: ctx.bridge,
|
|
236
|
+
cwd,
|
|
237
|
+
orderId: actionValue.orderId,
|
|
238
|
+
ctx: { userId, chatId, messageId },
|
|
239
|
+
}).catch((err) => ctx.logger.error('[control] order.exec dispatch failed:', err));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const forwardActionFeedback = (res) => {
|
|
243
|
+
const text = actionFeedbackText(res);
|
|
244
|
+
if (!text)
|
|
243
245
|
return;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
246
|
+
void ctx.bridge
|
|
247
|
+
.sendResult({ text }, { userId, chatId, messageId })
|
|
248
|
+
.catch((err) => ctx.logger.error('[control] card action feedback failed:', err));
|
|
249
|
+
};
|
|
250
|
+
if (isImmediate) {
|
|
251
|
+
ctx.bridge.enqueueImmediate(cwd, async () => {
|
|
252
|
+
const res = await ctx.router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
253
|
+
forwardActionFeedback(res);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
ctx.bridge.enqueue(cwd, async () => {
|
|
258
|
+
const res = await ctx.router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
259
|
+
forwardActionFeedback(res);
|
|
260
|
+
}, {
|
|
261
|
+
taskMeta: {
|
|
262
|
+
userId,
|
|
263
|
+
chatId,
|
|
264
|
+
messageId: ctx.router.mintQueueTaskKey('card', actionValue.cmd),
|
|
265
|
+
feishuReplyTo: messageId,
|
|
266
|
+
messagePreview: `card action: ${actionValue.cmd}`,
|
|
267
|
+
kind: COMPACT_ACTION_CMDS.has(actionValue.cmd) ? 'compact' : 'command',
|
|
268
|
+
editable: !COMPACT_ACTION_CMDS.has(actionValue.cmd),
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
export function setupMessageHandlers(deps) {
|
|
274
|
+
// ctx 在 assembler/authorizer 创建后填充;转发箭头只在事件触发时读它。
|
|
275
|
+
// 所有决策逻辑都在模块级 handler 中,本函数只做注册,圈复杂度保持最低。
|
|
276
|
+
// 循环初始化:两个实例的回调捕获 ctx,而 ctx 又需要这两个实例,无法在
|
|
277
|
+
// 声明处一次性初始化;回调只在事件到达时执行,那时填充早已完成。
|
|
278
|
+
// eslint-disable-next-line prefer-const
|
|
279
|
+
let ctx;
|
|
280
|
+
const assembler = new InboundTurnAssembler({
|
|
281
|
+
onCommit: (turn, prompt) => handleTurnCommit(ctx, turn, prompt),
|
|
282
|
+
onReceipt: (receiptCtx, text) => handleTurnReceipt(ctx, receiptCtx, text),
|
|
283
|
+
});
|
|
284
|
+
deps.router.setInboundFlusher(() => assembler.flushAll('flush'));
|
|
285
|
+
const authorizer = new InboundAuthorizer({
|
|
286
|
+
binder: deps.binder,
|
|
287
|
+
onBind: (userId, chatId) => handleBind(ctx, userId, chatId),
|
|
275
288
|
});
|
|
289
|
+
ctx = { ...deps, assembler, authorizer };
|
|
290
|
+
deps.connector.setInboundMediaDetectedHandler((msg) => handleInboundMedia(ctx, msg));
|
|
291
|
+
deps.connector.setMessageHandler((msg) => handleInboundMessage(ctx, msg));
|
|
292
|
+
deps.connector.setCardActionHandler((action) => handleCardAction(ctx, action));
|
|
276
293
|
}
|
|
277
294
|
//# sourceMappingURL=message-handlers.js.map
|
package/dist/model-catalog.d.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* 「地基层被 runner 反向 import」;挪到这里后三方共同引用同一份。
|
|
7
7
|
*
|
|
8
8
|
* 表内容来自 Claude CLI 的模型别名(alias 是 CLI 侧的稳定名字,model ID 会随
|
|
9
|
-
*
|
|
9
|
+
* 版本变),新增一行只改 MODEL_ID_TO_ALIAS,反向表自动派生。
|
|
10
10
|
*/
|
|
11
11
|
/** 模型 ID → alias 映射。 */
|
|
12
12
|
export declare const MODEL_ID_TO_ALIAS: Record<string, string>;
|
|
13
|
-
/** alias → model ID
|
|
13
|
+
/** alias → model ID 反向映射(从正向表派生,新增别名只改上面一张表)。 */
|
|
14
14
|
export declare const MODEL_ALIAS_TO_ID: Record<string, string>;
|
package/dist/model-catalog.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 「地基层被 runner 反向 import」;挪到这里后三方共同引用同一份。
|
|
7
7
|
*
|
|
8
8
|
* 表内容来自 Claude CLI 的模型别名(alias 是 CLI 侧的稳定名字,model ID 会随
|
|
9
|
-
*
|
|
9
|
+
* 版本变),新增一行只改 MODEL_ID_TO_ALIAS,反向表自动派生。
|
|
10
10
|
*/
|
|
11
11
|
/** 模型 ID → alias 映射。 */
|
|
12
12
|
export const MODEL_ID_TO_ALIAS = {
|
|
@@ -15,11 +15,6 @@ export const MODEL_ID_TO_ALIAS = {
|
|
|
15
15
|
'claude-haiku-4-5-20250501': 'haiku',
|
|
16
16
|
'claude-fable-5': 'fable',
|
|
17
17
|
};
|
|
18
|
-
/** alias → model ID
|
|
19
|
-
export const MODEL_ALIAS_TO_ID =
|
|
20
|
-
opus: 'claude-opus-4-8',
|
|
21
|
-
sonnet: 'claude-sonnet-4-6',
|
|
22
|
-
haiku: 'claude-haiku-4-5-20250501',
|
|
23
|
-
fable: 'claude-fable-5',
|
|
24
|
-
};
|
|
18
|
+
/** alias → model ID 反向映射(从正向表派生,新增别名只改上面一张表)。 */
|
|
19
|
+
export const MODEL_ALIAS_TO_ID = Object.fromEntries(Object.entries(MODEL_ID_TO_ALIAS).map(([id, alias]) => [alias, id]));
|
|
25
20
|
//# sourceMappingURL=model-catalog.js.map
|
|
@@ -154,7 +154,7 @@ export function binaryName(token, platform = currentPlatform) {
|
|
|
154
154
|
return caseInsensitive(platform) ? name.toLowerCase() : name;
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
|
-
*
|
|
157
|
+
* 命令行与期望二进制是否属于同一程序。
|
|
158
158
|
*
|
|
159
159
|
* 1. 可执行位匹配:首 token 的 basename 相等——`/usr/local/bin/claude --verbose`
|
|
160
160
|
* 匹配 claude;native 单文件 CLI(claude 的 `bin/claude.exe`)也走这条;
|
package/dist/platform/path.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function samePath(a: string, b: string, opts?: PathOptions): bool
|
|
|
21
21
|
* cwd → 会话项目目录名(Claude/pi 的 on-disk 编码,lossy N-to-N)。
|
|
22
22
|
*
|
|
23
23
|
* 只可用于**定位**目录,绝不可反解回 cwd——cwd 必须从 JSONL 内容读取
|
|
24
|
-
*
|
|
24
|
+
* (否则 /resume & /cd 路径会错乱)。
|
|
25
25
|
*
|
|
26
26
|
* posix:`/` → `-`(`_` 同样归一,与 Claude Code 一致)。
|
|
27
27
|
* win32:`\` 与 `/` **都是**分隔符、`:` 是非法文件名字符,三者都必须归一,
|
package/dist/platform/path.js
CHANGED
|
@@ -63,7 +63,7 @@ export function samePath(a, b, opts) {
|
|
|
63
63
|
* cwd → 会话项目目录名(Claude/pi 的 on-disk 编码,lossy N-to-N)。
|
|
64
64
|
*
|
|
65
65
|
* 只可用于**定位**目录,绝不可反解回 cwd——cwd 必须从 JSONL 内容读取
|
|
66
|
-
*
|
|
66
|
+
* (否则 /resume & /cd 路径会错乱)。
|
|
67
67
|
*
|
|
68
68
|
* posix:`/` → `-`(`_` 同样归一,与 Claude Code 一致)。
|
|
69
69
|
* win32:`\` 与 `/` **都是**分隔符、`:` 是非法文件名字符,三者都必须归一,
|
|
@@ -6,7 +6,7 @@ import { getLogger } from '../logger/index.js';
|
|
|
6
6
|
* **必须按无符号 32 位取值**:`0x80000001` = 2147483649 超出 Int32 正数上界
|
|
7
7
|
* (2147483647),而 PowerShell 的数字字面量按 Int32 解析 —— 十六进制写法一旦
|
|
8
8
|
* 进了脚本就会被解析成 **-2147483647**,传给 P/Invoke 声明的 `uint` 形参时抛
|
|
9
|
-
* `MethodArgumentConversionInvalidCastArgument
|
|
9
|
+
* `MethodArgumentConversionInvalidCastArgument`。
|
|
10
10
|
*/
|
|
11
11
|
const ES_FLAGS_UINT32 = (0x80000000 | 0x00000001) >>> 0;
|
|
12
12
|
/**
|
|
@@ -42,8 +42,9 @@ export function createWin32Terminator(deps) {
|
|
|
42
42
|
stdio: 'ignore',
|
|
43
43
|
windowsHide: true,
|
|
44
44
|
});
|
|
45
|
-
killer?.on?.('error', () => {
|
|
45
|
+
killer?.on?.('error', (err) => {
|
|
46
46
|
// taskkill 拉起失败不致命:调用方仍可走 killOrphan / 陈旧锁自愈
|
|
47
|
+
log?.(`win32 树杀失败:taskkill error 事件 ${err?.message ?? err}`);
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
catch (error) {
|