lark-remote 0.1.6 → 0.1.7
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 +23 -1
- package/README.en.md +9 -1
- package/README.md +14 -2
- package/dist/bridge/approval-coordinator.d.ts +57 -34
- package/dist/bridge/approval-coordinator.js +191 -40
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +78 -7
- package/dist/bridge/index.js +202 -79
- package/dist/bridge/queue-manager.d.ts +2 -1
- package/dist/bridge/queue-manager.js +1 -0
- package/dist/card/approval-render.js +152 -36
- package/dist/card/bash-card-session.d.ts +2 -24
- package/dist/card/bash-card-session.js +6 -73
- package/dist/card/bash-renderer.js +2 -3
- package/dist/card/card-budget.js +29 -75
- package/dist/card/card-session.d.ts +48 -0
- package/dist/card/card-session.js +86 -0
- package/dist/card/card-shared.d.ts +1 -12
- package/dist/card/card-shared.js +1 -26
- package/dist/card/run-card-session.d.ts +2 -55
- package/dist/card/run-card-session.js +6 -104
- package/dist/card/run-renderer.js +99 -232
- package/dist/card/run-state.d.ts +13 -0
- package/dist/card/run-state.js +25 -16
- package/dist/card/text-truncate.d.ts +13 -12
- package/dist/card/text-truncate.js +13 -63
- package/dist/common/fs.d.ts +8 -0
- package/dist/common/fs.js +17 -0
- package/dist/common/guards.d.ts +0 -20
- package/dist/common/guards.js +0 -43
- package/dist/common/sleep.d.ts +2 -0
- package/dist/common/sleep.js +5 -0
- package/dist/common/truncate.d.ts +39 -0
- package/dist/common/truncate.js +92 -0
- package/dist/config/codex-config.d.ts +1 -1
- package/dist/config/codex-config.js +1 -1
- package/dist/config/index.d.ts +88 -23
- package/dist/config/index.js +105 -34
- package/dist/connector/index.d.ts +69 -2
- package/dist/connector/index.js +107 -1
- package/dist/index.js +123 -105
- package/dist/instance-lock.js +4 -12
- package/dist/order/alias-resolve.d.ts +13 -0
- package/dist/order/alias-resolve.js +22 -0
- package/dist/order/alias-store.d.ts +38 -0
- package/dist/order/alias-store.js +106 -0
- package/dist/persistence/atomic-write.d.ts +8 -2
- package/dist/persistence/atomic-write.js +23 -7
- package/dist/restart.js +2 -1
- package/dist/router/card-helpers.d.ts +63 -0
- package/dist/router/card-helpers.js +146 -0
- package/dist/router/config/claude.js +25 -1
- package/dist/router/config/codex.js +29 -50
- package/dist/router/config/common/model-patch.d.ts +22 -0
- package/dist/router/config/common/model-patch.js +26 -0
- package/dist/router/config/common/render.js +5 -4
- package/dist/router/config/index.d.ts +12 -0
- package/dist/router/config/index.js +25 -1
- package/dist/router/config/kimi.d.ts +4 -1
- package/dist/router/config/kimi.js +27 -0
- package/dist/router/config/opencode.js +18 -5
- package/dist/router/config/pi.js +4 -5
- package/dist/router/config/types.d.ts +11 -2
- package/dist/router/index.d.ts +56 -21
- package/dist/router/index.js +373 -548
- package/dist/router/utils.d.ts +2 -0
- package/dist/router/utils.js +4 -0
- package/dist/runner/agent-choices-common.d.ts +16 -0
- package/dist/runner/agent-choices-common.js +31 -0
- package/dist/runner/claude/runner.d.ts +47 -10
- package/dist/runner/claude/runner.js +117 -46
- package/dist/runner/claude/session.d.ts +166 -0
- package/dist/runner/claude/session.js +749 -0
- package/dist/runner/codex/app-server/protocol-types.d.ts +31 -39
- package/dist/runner/codex/app-server/protocol-types.js +1 -12
- package/dist/runner/codex/app-server/runner.d.ts +42 -59
- package/dist/runner/codex/app-server/runner.js +160 -337
- package/dist/runner/codex/app-server/translator.d.ts +1 -21
- package/dist/runner/codex/app-server/translator.js +0 -18
- package/dist/runner/codex/index.d.ts +0 -2
- package/dist/runner/codex/index.js +0 -2
- package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
- package/dist/runner/common/acp/protocol-helpers.js +21 -0
- package/dist/runner/common/acp/protocol-types.d.ts +238 -0
- package/dist/runner/common/acp/protocol-types.js +75 -0
- package/dist/runner/common/connection-based-runner.d.ts +111 -0
- package/dist/runner/common/connection-based-runner.js +261 -0
- package/dist/runner/{codex/app-server → common/jsonrpc}/client.d.ts +14 -7
- package/dist/runner/{codex/app-server → common/jsonrpc}/client.js +27 -18
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
- package/dist/runner/{codex/app-server → common/jsonrpc}/connection-manager.js +63 -28
- package/dist/runner/{codex/app-server → common/jsonrpc}/transport.d.ts +5 -4
- package/dist/runner/{codex/app-server → common/jsonrpc}/transport.js +18 -19
- package/dist/runner/common/jsonrpc/types.d.ts +32 -0
- package/dist/runner/common/jsonrpc/types.js +9 -0
- package/dist/runner/common/process-stopper.d.ts +2 -1
- package/dist/runner/common/process-stopper.js +1 -0
- package/dist/runner/common/runner-utils.d.ts +6 -13
- package/dist/runner/common/runner-utils.js +6 -47
- package/dist/runner/common/spawning-runner.d.ts +24 -70
- package/dist/runner/common/spawning-runner.js +98 -152
- package/dist/runner/index.d.ts +4 -4
- package/dist/runner/index.js +4 -4
- package/dist/runner/kimi/acp/runner.d.ts +106 -0
- package/dist/runner/kimi/acp/runner.js +448 -0
- package/dist/runner/kimi/acp/translator.d.ts +124 -0
- package/dist/runner/kimi/acp/translator.js +336 -0
- package/dist/runner/kimi/index.d.ts +1 -1
- package/dist/runner/kimi/index.js +1 -1
- package/dist/runner/opencode/acp/index.d.ts +1 -0
- package/dist/runner/opencode/acp/index.js +2 -0
- package/dist/runner/opencode/acp/runner.d.ts +121 -0
- package/dist/runner/opencode/acp/runner.js +429 -0
- package/dist/runner/opencode/acp/translator.d.ts +119 -0
- package/dist/runner/opencode/acp/translator.js +345 -0
- package/dist/runner/opencode/index.d.ts +1 -1
- package/dist/runner/opencode/index.js +1 -1
- package/dist/runner/pi/index.d.ts +1 -1
- package/dist/runner/pi/index.js +1 -1
- package/dist/runner/pi/rpc/client.d.ts +52 -0
- package/dist/runner/pi/rpc/client.js +114 -0
- package/dist/runner/pi/rpc/index.d.ts +4 -0
- package/dist/runner/pi/rpc/index.js +4 -0
- package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
- package/dist/runner/pi/rpc/protocol-types.js +21 -0
- package/dist/runner/pi/rpc/runner.d.ts +80 -0
- package/dist/runner/pi/rpc/runner.js +199 -0
- package/dist/runner/pi/rpc/translator.d.ts +55 -0
- package/dist/runner/pi/rpc/translator.js +258 -0
- package/dist/runner/registry.d.ts +7 -8
- package/dist/runner/registry.js +3 -4
- package/dist/runner/resolve-agent-choices.js +18 -59
- package/dist/runner/sync-agent-choices.js +16 -67
- package/dist/runner/types.d.ts +43 -13
- package/dist/session/claude/session-index.js +2 -3
- package/dist/session/claude/session-reader.js +3 -6
- package/dist/session/claude/sessions.js +6 -13
- package/dist/session/codex/rollout-reader.d.ts +2 -2
- package/dist/session/codex/rollout-reader.js +14 -17
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +2 -2
- package/dist/session/common/content-blocks.d.ts +7 -0
- package/dist/session/common/content-blocks.js +13 -9
- package/dist/session/common/jsonl.d.ts +8 -25
- package/dist/session/common/jsonl.js +126 -261
- package/dist/session/common/pagination.d.ts +28 -0
- package/dist/session/common/pagination.js +33 -0
- package/dist/session/kimi/sessions.d.ts +34 -1
- package/dist/session/kimi/sessions.js +208 -98
- package/dist/session/opencode/sessions.d.ts +17 -3
- package/dist/session/opencode/sessions.js +29 -29
- package/dist/session/pi/sessions.d.ts +5 -1
- package/dist/session/pi/sessions.js +12 -16
- package/dist/session/session-store.d.ts +11 -1
- package/dist/session/session-store.js +75 -178
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/install.d.ts +20 -2
- package/dist/update/install.js +51 -7
- package/package.json +1 -1
- package/dist/runner/codex/app-server/connection-manager.d.ts +0 -56
- package/dist/runner/codex/argv.d.ts +0 -33
- package/dist/runner/codex/argv.js +0 -40
- package/dist/runner/codex/factory.d.ts +0 -29
- package/dist/runner/codex/factory.js +0 -39
- package/dist/runner/codex/jsonl.d.ts +0 -37
- package/dist/runner/codex/jsonl.js +0 -221
- package/dist/runner/codex/runner.d.ts +0 -66
- package/dist/runner/codex/runner.js +0 -129
- package/dist/runner/common/exec-translator.d.ts +0 -73
- package/dist/runner/common/exec-translator.js +0 -105
- package/dist/runner/kimi/jsonl.d.ts +0 -28
- package/dist/runner/kimi/jsonl.js +0 -143
- package/dist/runner/kimi/runner.d.ts +0 -49
- package/dist/runner/kimi/runner.js +0 -105
- package/dist/runner/opencode/argv.d.ts +0 -32
- package/dist/runner/opencode/argv.js +0 -35
- package/dist/runner/opencode/jsonl.d.ts +0 -68
- package/dist/runner/opencode/jsonl.js +0 -295
- package/dist/runner/opencode/runner.d.ts +0 -61
- package/dist/runner/opencode/runner.js +0 -93
- package/dist/runner/pi/jsonl.d.ts +0 -128
- package/dist/runner/pi/jsonl.js +0 -249
- package/dist/runner/pi/runner.d.ts +0 -36
- package/dist/runner/pi/runner.js +0 -76
- package/dist/session/common/catch-up.d.ts +0 -12
- package/dist/session/common/catch-up.js +0 -31
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { loadConfig, getAgentConfig
|
|
1
|
+
import { loadConfig, getAgentConfig } from './config/index.js';
|
|
2
2
|
import { resolveAgentChoices, } from './runner/index.js';
|
|
3
3
|
import { ensureConfig } from './config/wizard.js';
|
|
4
4
|
import { parseCliArgs, resolveConfigDir, setConfigDir, printHelp, printVersion, } from './config/dir.js';
|
|
5
5
|
import { FeishuConnector } from './connector/index.js';
|
|
6
|
-
import { ClaudeRunner,
|
|
6
|
+
import { ClaudeRunner, CodexAppServerRunner, OpencodeAcpRunner, PiRpcRunner, KimiAcpRunner, } from './runner/index.js';
|
|
7
7
|
import { AgentRegistry } from './runner/registry.js';
|
|
8
8
|
import { probeAllAgents } from './runner/probe.js';
|
|
9
9
|
import { SessionReaderRegistry, SessionStore } from './session/index.js';
|
|
10
10
|
import { ClaudeSessionReader, CodexSessionReader, OpencodeSessionReader, PiSessionReader, KimiSessionReader, } from './session/index.js';
|
|
11
|
-
import { CommandRouter, isImmediateAction
|
|
11
|
+
import { CommandRouter, isImmediateAction } from './router/index.js';
|
|
12
12
|
import { dispatchOrderExecForQueue } from './router/order-exec-dispatch.js';
|
|
13
13
|
import { Bridge } from './bridge/index.js';
|
|
14
14
|
import { initLogger, getLogger } from './logger/index.js';
|
|
@@ -19,11 +19,11 @@ import { spawnReplacementBridge, waitForPreviousInstance } from './restart.js';
|
|
|
19
19
|
import { checkLatestVersion, isNewer, runInstallLatest, formatUpdateHint } from './update/index.js';
|
|
20
20
|
import { classifyRejection } from './error-classification.js';
|
|
21
21
|
import { WorkspaceStore } from './workspace/index.js';
|
|
22
|
-
import {
|
|
23
|
-
import { sessionEventPanel } from './router/card-helpers.js';
|
|
22
|
+
import { buildSessionHistoryCard } from './router/card-helpers.js';
|
|
24
23
|
import { newSessionButton, resumeCompactButton, agentDisplayName } from './card/card-shared.js';
|
|
25
24
|
import path from 'node:path';
|
|
26
25
|
import fs from 'node:fs';
|
|
26
|
+
import { silentlyUnlink } from './common/fs.js';
|
|
27
27
|
/** Send auto-resume card for a restored session */
|
|
28
28
|
async function sendAutoResumeCard(connector, contact, cwd, session, state, compactSupported = false) {
|
|
29
29
|
if (!contact.userId)
|
|
@@ -31,68 +31,32 @@ async function sendAutoResumeCard(connector, contact, cwd, session, state, compa
|
|
|
31
31
|
const recipient = contact.chatId ?? contact.userId;
|
|
32
32
|
if (!recipient)
|
|
33
33
|
return;
|
|
34
|
-
// Build header with displayTitle (aiTitle or last user message) and recap
|
|
35
|
-
let header = `📂 \`${cwd}\`\n已恢复最近会话: **${session.sessionId}**`;
|
|
36
|
-
const sections = [];
|
|
37
|
-
if (state.displayTitle) {
|
|
38
|
-
const label = state.aiTitle ? 'AI 标题' : '最近输入';
|
|
39
|
-
sections.push(`🏷️ **${label}**\n${state.displayTitle}`);
|
|
40
|
-
}
|
|
41
|
-
if (state.recap) {
|
|
42
|
-
const recapPreview = state.recap.length > 200 ? state.recap.slice(0, 197) + '...' : state.recap;
|
|
43
|
-
sections.push(`📝 **Recap**\n${recapPreview}`);
|
|
44
|
-
}
|
|
45
|
-
if (sections.length > 0) {
|
|
46
|
-
header += '\n\n' + sections.join('\n\n──\n\n');
|
|
47
|
-
}
|
|
48
|
-
const elements = [markdownDiv(header), { tag: 'hr' }];
|
|
49
34
|
// Limit history events to prevent card size exceeded error (11310)
|
|
50
35
|
const MAX_HISTORY_EVENTS = 5;
|
|
51
36
|
const eventsToShow = state.events.slice(-MAX_HISTORY_EVENTS);
|
|
52
37
|
const hiddenCount = state.events.length - MAX_HISTORY_EVENTS;
|
|
53
|
-
//
|
|
54
|
-
if (hiddenCount > 0) {
|
|
55
|
-
elements.push(markdownDiv(`📜 还有 ${hiddenCount} 个更早的事件未显示`));
|
|
56
|
-
}
|
|
57
|
-
// Fold session history into collapsible panels: the last 2 events stay expanded
|
|
38
|
+
// Action buttons: Compact (runCompact-capable runner) + new session.
|
|
58
39
|
const agentKind = state.agentKind ?? 'claude';
|
|
59
|
-
eventsToShow.forEach((ev, i) => {
|
|
60
|
-
elements.push(sessionEventPanel(ev, i, eventsToShow.length, 2, agentKind));
|
|
61
|
-
});
|
|
62
|
-
// Add usage/stats at the end
|
|
63
|
-
if (state.usage) {
|
|
64
|
-
const usageStr = formatUsageStats(state.usage);
|
|
65
|
-
elements.push(markdownDiv(usageStr));
|
|
66
|
-
}
|
|
67
|
-
// Add action buttons - use column_set+column (div+elements not supported in 2.0)
|
|
68
40
|
const actionButtons = [];
|
|
69
|
-
// Compact 按钮:仅 codex app-server 模式(runner 有 runCompact)且未在跑。
|
|
70
|
-
// 启动自动恢复时不会存在该会话的 activeRun,直接渲染。
|
|
71
41
|
if (compactSupported) {
|
|
72
42
|
actionButtons.push(resumeCompactButton(session.sessionId, state.agentKind ?? 'codex'));
|
|
73
43
|
}
|
|
74
44
|
actionButtons.push(newSessionButton());
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
45
|
+
const card = buildSessionHistoryCard({
|
|
46
|
+
sessionId: session.sessionId,
|
|
47
|
+
cwd,
|
|
48
|
+
displayTitle: state.displayTitle,
|
|
49
|
+
aiTitle: state.aiTitle,
|
|
50
|
+
recap: state.recap,
|
|
51
|
+
events: eventsToShow,
|
|
52
|
+
usage: state.usage,
|
|
53
|
+
}, {
|
|
54
|
+
agentKind,
|
|
55
|
+
headerText: `📂 \`${cwd}\`\n已恢复最近会话: **${session.sessionId}**`,
|
|
56
|
+
title: `🔁 自动恢复会话 · ${agentDisplayName(agentKind)}`,
|
|
57
|
+
hiddenCount,
|
|
58
|
+
actions: actionButtons,
|
|
82
59
|
});
|
|
83
|
-
const card = {
|
|
84
|
-
schema: '2.0',
|
|
85
|
-
config: {
|
|
86
|
-
wide_screen_mode: true,
|
|
87
|
-
},
|
|
88
|
-
header: {
|
|
89
|
-
title: {
|
|
90
|
-
tag: 'plain_text',
|
|
91
|
-
content: `🔁 自动恢复会话 · ${agentDisplayName(agentKind)}`,
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
body: { elements },
|
|
95
|
-
};
|
|
96
60
|
await connector.sendWithRetry(recipient, { card });
|
|
97
61
|
}
|
|
98
62
|
/** Initialize CLI args, config, and logger. Returns { config, configDir, logger, instanceLock }. */
|
|
@@ -165,37 +129,36 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
165
129
|
settings: cliArgs.settings,
|
|
166
130
|
pidDir: configDir,
|
|
167
131
|
workspace: ws,
|
|
132
|
+
permissionMode: claudeConfig.permissionMode,
|
|
133
|
+
idleTtlMs: claudeConfig.idleTtlMinutes != null ? claudeConfig.idleTtlMinutes * 60_000 : undefined,
|
|
168
134
|
});
|
|
169
135
|
});
|
|
170
|
-
// Register Codex runner.
|
|
171
|
-
// so runtime config changes (
|
|
172
|
-
// reasoningEffort, etc.) take effect after bridge.setConfig()
|
|
173
|
-
//
|
|
174
|
-
// approval + sandbox); otherwise CodexExecRunner (codex exec --json).
|
|
136
|
+
// Register Codex runner (app-server only). The registry factory reads latest
|
|
137
|
+
// config from container.current so runtime config changes (model, sandbox,
|
|
138
|
+
// approvalPolicy, reasoningEffort, etc.) take effect after bridge.setConfig()
|
|
139
|
+
// + clearRunners().
|
|
175
140
|
const codexSessionReader = new CodexSessionReader({ codexHome: process.env.CODEX_HOME });
|
|
176
|
-
agentRegistry.register('codex', (
|
|
141
|
+
agentRegistry.register('codex', (_ws) => {
|
|
177
142
|
const container = agentRegistry.getConfigContainer();
|
|
178
143
|
const latestConfig = container?.current ?? config;
|
|
179
144
|
const codexConfig = getAgentConfig(latestConfig, 'codex');
|
|
180
|
-
return
|
|
145
|
+
return new CodexAppServerRunner({
|
|
146
|
+
kind: 'codex',
|
|
181
147
|
model: codexConfig?.model,
|
|
182
148
|
modelProvider: codexConfig?.modelProvider,
|
|
183
149
|
reasoningEffort: codexConfig?.reasoningEffort,
|
|
184
|
-
stopGraceMs: codexConfig?.stopGraceMs ?? latestConfig.claude?.stopGraceMs ?? DEFAULT_STOP_GRACE_MS,
|
|
185
|
-
pidDir: configDir,
|
|
186
|
-
workspace: ws,
|
|
187
150
|
sessionReader: codexSessionReader,
|
|
188
|
-
serviceMode: codexConfig?.serviceMode,
|
|
189
151
|
sandbox: codexConfig?.sandbox,
|
|
190
152
|
approvalPolicy: codexConfig?.approvalPolicy,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
153
|
+
binary: codexConfig?.appServer?.binary,
|
|
154
|
+
requestTimeoutMs: codexConfig?.appServer?.requestTimeoutMs,
|
|
155
|
+
idleTtlMs: codexConfig?.appServer?.idleTtlMs,
|
|
156
|
+
turnTimeoutMs: codexConfig?.appServer?.turnIdleTimeoutMinutes != null
|
|
157
|
+
? codexConfig.appServer.turnIdleTimeoutMinutes * 60_000
|
|
158
|
+
: undefined,
|
|
195
159
|
});
|
|
196
160
|
});
|
|
197
|
-
// Register
|
|
198
|
-
// Using `opencode run --format json --auto` instead of HTTP+SSE server mode
|
|
161
|
+
// Register OpencodeAcpRunner (pure ACP mode, `opencode acp` JSON-RPC over stdio)
|
|
199
162
|
// Register session reader (same instance as used by runner)
|
|
200
163
|
const sessionReaderRegistry = new SessionReaderRegistry();
|
|
201
164
|
sessionReaderRegistry.register('claude', new ClaudeSessionReader());
|
|
@@ -203,7 +166,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
203
166
|
// Register OpencodeSessionReader (CLI version)
|
|
204
167
|
// Using `opencode session list` and `opencode export` instead of HTTP API
|
|
205
168
|
const opencodeSessionReader = new OpencodeSessionReader();
|
|
206
|
-
agentRegistry.register('opencode', (
|
|
169
|
+
agentRegistry.register('opencode', (_ws) => {
|
|
207
170
|
// Get latest config from container (set below for pi)
|
|
208
171
|
const container = agentRegistry.getConfigContainer();
|
|
209
172
|
const latestConfig = container?.current ?? config;
|
|
@@ -220,17 +183,15 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
220
183
|
model = `${provider}/${ocConfig.modelID}`;
|
|
221
184
|
}
|
|
222
185
|
}
|
|
223
|
-
return new
|
|
186
|
+
return new OpencodeAcpRunner({
|
|
187
|
+
kind: 'opencode',
|
|
224
188
|
model,
|
|
225
|
-
// P1-15: read stopGraceMs from latestConfig (not startup closure)
|
|
226
|
-
stopGraceMs: latestConfig.claude?.stopGraceMs ?? DEFAULT_STOP_GRACE_MS,
|
|
227
|
-
pidDir: configDir,
|
|
228
|
-
workspace: ws,
|
|
229
189
|
sessionReader: opencodeSessionReader,
|
|
190
|
+
mode: ocConfig?.mode ?? 'build',
|
|
230
191
|
});
|
|
231
192
|
});
|
|
232
193
|
sessionReaderRegistry.register('opencode', opencodeSessionReader);
|
|
233
|
-
// Register
|
|
194
|
+
// Register PiRpcRunner and PiSessionReader
|
|
234
195
|
// 2026-07-12: 修复 config.save 后 pi provider 不生效的问题
|
|
235
196
|
// 关键:factory 闭包必须能访问到最新的 config,而不是启动时的快照
|
|
236
197
|
// 用可变容器存储 config 引用,registry 持有容器引用,factory 从容器读取最新值
|
|
@@ -242,7 +203,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
242
203
|
const container = agentRegistry.getConfigContainer();
|
|
243
204
|
const latestConfig = container?.current ?? config;
|
|
244
205
|
const piConf = getAgentConfig(latestConfig, 'pi');
|
|
245
|
-
return new
|
|
206
|
+
return new PiRpcRunner({
|
|
246
207
|
provider: piConf?.provider ?? 'Volcano',
|
|
247
208
|
model: piConf?.model ?? 'glm-5.2',
|
|
248
209
|
thinking: piConf?.thinking ?? 'medium',
|
|
@@ -250,27 +211,30 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
250
211
|
.split(',')
|
|
251
212
|
.map((t) => t.trim())
|
|
252
213
|
.filter(Boolean),
|
|
253
|
-
pidDir: configDir,
|
|
254
214
|
workspace: ws,
|
|
255
215
|
sessionReader: piSessionReader,
|
|
256
216
|
});
|
|
257
217
|
});
|
|
258
218
|
sessionReaderRegistry.register('pi', piSessionReader);
|
|
259
|
-
// Register
|
|
219
|
+
// Register KimiAcpRunner (pure ACP mode) and KimiSessionReader
|
|
260
220
|
const kimiSessionReader = new KimiSessionReader();
|
|
261
|
-
agentRegistry.register('kimi', (
|
|
221
|
+
agentRegistry.register('kimi', (_ws) => {
|
|
262
222
|
// 每次 factory 调用时从 registry 获取最新 config
|
|
263
223
|
const container = agentRegistry.getConfigContainer();
|
|
264
224
|
const latestConfig = container?.current ?? config;
|
|
265
225
|
const kimiConf = getAgentConfig(latestConfig, 'kimi');
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
// P1-15: read stopGraceMs from latestConfig (not startup closure)
|
|
270
|
-
stopGraceMs: latestConfig.claude?.stopGraceMs ?? DEFAULT_STOP_GRACE_MS,
|
|
271
|
-
pidDir: configDir,
|
|
272
|
-
workspace: ws,
|
|
226
|
+
const acpConf = kimiConf?.acp;
|
|
227
|
+
return new KimiAcpRunner({
|
|
228
|
+
kind: 'kimi',
|
|
273
229
|
sessionReader: kimiSessionReader,
|
|
230
|
+
binary: acpConf?.binary ?? 'kimi',
|
|
231
|
+
requestTimeoutMs: acpConf?.requestTimeoutMs,
|
|
232
|
+
idleTtlMs: acpConf?.idleTtlMs,
|
|
233
|
+
turnIdleTimeoutMs: acpConf?.turnIdleTimeoutMinutes != null
|
|
234
|
+
? acpConf.turnIdleTimeoutMinutes * 60_000
|
|
235
|
+
: undefined,
|
|
236
|
+
model: kimiConf?.model ?? 'kimi-code/k3',
|
|
237
|
+
permissionMode: kimiConf?.permissionMode ?? 'manual',
|
|
274
238
|
});
|
|
275
239
|
});
|
|
276
240
|
sessionReaderRegistry.register('kimi', kimiSessionReader);
|
|
@@ -301,6 +265,48 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
301
265
|
}
|
|
302
266
|
/** Setup message and card action handlers. */
|
|
303
267
|
function setupMessageHandlers(connector, router, bridge, sessionStore, workspaceStore, binder, logger, config) {
|
|
268
|
+
// 入站媒体(图片/文件):先认证后下载(P1 review 修复)。
|
|
269
|
+
// connector 只上报"媒体到达",这里先过 owner + enabled 闸门,通过后才
|
|
270
|
+
// 下载——未认证/关闭配置时不会发生任何网络下载或内存/磁盘占用。
|
|
271
|
+
connector.setInboundMediaDetectedHandler((msg) => {
|
|
272
|
+
if (!binder.isOwner(msg.userId)) {
|
|
273
|
+
logger.warn(`[media] rejected inbound media from non-owner ${msg.userId}`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// 读 router.config(活引用):/config 保存后 setConfigValues 返回新对象,
|
|
277
|
+
// 启动时捕获的 config 参数会过期(P2 review 修复)。
|
|
278
|
+
if (!router.config.inboundMedia.enabled) {
|
|
279
|
+
// 不静默(P3 review):关闭时给 owner 明确反馈,避免发图后无任何反应。
|
|
280
|
+
// 不下载、不落盘,只回一条说明。
|
|
281
|
+
void bridge
|
|
282
|
+
.sendResult({
|
|
283
|
+
text: '⚠️ 入站媒体保存已关闭(inboundMedia.enabled: false),未保存文件。' +
|
|
284
|
+
'如需自动保存图片/文件,请开启后重试',
|
|
285
|
+
}, {
|
|
286
|
+
userId: msg.userId,
|
|
287
|
+
chatId: msg.chatId,
|
|
288
|
+
messageId: msg.messageId,
|
|
289
|
+
})
|
|
290
|
+
.catch((err) => logger.error('[media] disabled feedback send failed:', err));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
void (async () => {
|
|
294
|
+
const payload = await connector.downloadInboundMedia(msg, {
|
|
295
|
+
maxFileSizeMb: router.config.inboundMedia.maxFileSizeMb,
|
|
296
|
+
});
|
|
297
|
+
try {
|
|
298
|
+
await bridge.onInboundMedia(payload);
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
// handle() 的正常路径会清理临时文件;这里兜底 handle 进入 mkdir
|
|
302
|
+
// try 之前意外抛错的情况,避免 os.tmpdir 残留。
|
|
303
|
+
for (const item of payload.media) {
|
|
304
|
+
silentlyUnlink(item.tempPath);
|
|
305
|
+
}
|
|
306
|
+
throw err;
|
|
307
|
+
}
|
|
308
|
+
})().catch((err) => logger.error('[media] inbound media download/save failed:', err));
|
|
309
|
+
});
|
|
304
310
|
connector.setMessageHandler((msg) => {
|
|
305
311
|
// 绑定/授权闸门:仅 owner 放行;非 owner 静默丢弃;未绑定时要求 PIN 认领
|
|
306
312
|
const decision = binder.classify(msg.userId, msg.content, msg.chatId);
|
|
@@ -358,9 +364,16 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
358
364
|
}
|
|
359
365
|
// decision.kind === 'owner':正常处理(不再每条覆盖 startup-contact)
|
|
360
366
|
logger.info(`message from ${msg.userId}: ${msg.content.slice(0, 100)}`);
|
|
367
|
+
// 文本到达先冲刷待合批的媒体保存提示(先图后文字时文字到达
|
|
368
|
+
// 先冲刷批次,避免合批提示被后续消息淹没/丢图)。
|
|
369
|
+
bridge.flushMediaNotifications(msg.userId, msg.chatId);
|
|
361
370
|
// Add Typing reaction to indicate "still alive"
|
|
362
371
|
void connector.addReaction(msg.messageId, 'Typing');
|
|
363
|
-
|
|
372
|
+
// 别名展开:命令分发前对消息做一次 $name 展开(不递归)。
|
|
373
|
+
// `!` / `/` 开头的消息不会进入展开路径($PATH、$HOME 等 shell 变量不受影响),
|
|
374
|
+
// 未知 $xxx 原样透传;展开结果若以 `/` 开头会自然落入命令路径。
|
|
375
|
+
const content = router.expandAliasMessage(msg.content);
|
|
376
|
+
const trimmedLower = content.trim().toLowerCase();
|
|
364
377
|
if (trimmedLower === '/stop' || trimmedLower === '/t') {
|
|
365
378
|
void (async () => {
|
|
366
379
|
const stopped = await bridge.interruptCurrentRun({
|
|
@@ -377,9 +390,9 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
377
390
|
})().catch((err) => logger.error('[control] /stop failed:', err));
|
|
378
391
|
return;
|
|
379
392
|
}
|
|
380
|
-
if (
|
|
393
|
+
if (content.trim().startsWith('/')) {
|
|
381
394
|
void router
|
|
382
|
-
.handle(
|
|
395
|
+
.handle(content, {
|
|
383
396
|
userId: msg.userId,
|
|
384
397
|
chatId: msg.chatId,
|
|
385
398
|
messageId: msg.messageId,
|
|
@@ -392,9 +405,9 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
392
405
|
// in the same workspace (design.md §9.6: the queue exists to prevent
|
|
393
406
|
// concurrent claude runs, not to serialize bash). router.handle dispatches
|
|
394
407
|
// `!` to bridge.executeBash, which tracks the run independently.
|
|
395
|
-
if (
|
|
408
|
+
if (content.trim().startsWith('!')) {
|
|
396
409
|
void router
|
|
397
|
-
.handle(
|
|
410
|
+
.handle(content, {
|
|
398
411
|
userId: msg.userId,
|
|
399
412
|
chatId: msg.chatId,
|
|
400
413
|
messageId: msg.messageId,
|
|
@@ -418,7 +431,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
418
431
|
// 捕获点 Bridge.currentBinding。
|
|
419
432
|
const binding = bridge.currentBinding(msg.userId);
|
|
420
433
|
bridge.enqueue(workspace, async () => {
|
|
421
|
-
await router.handle(
|
|
434
|
+
await router.handle(content, {
|
|
422
435
|
userId: msg.userId,
|
|
423
436
|
chatId: msg.chatId,
|
|
424
437
|
messageId: msg.messageId,
|
|
@@ -433,7 +446,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
433
446
|
userId: msg.userId,
|
|
434
447
|
chatId: msg.chatId,
|
|
435
448
|
messageId: msg.messageId,
|
|
436
|
-
messagePreview:
|
|
449
|
+
messagePreview: content.slice(0, 3000),
|
|
437
450
|
// D3/Step4: binding 随 taskMeta 存进 QueuedTask,供替换闭包
|
|
438
451
|
// (queue.edit/queue.immediate)复用,不重新快照。
|
|
439
452
|
binding,
|
|
@@ -492,15 +505,20 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
492
505
|
inputValue: action.raw?.action?.input_value ??
|
|
493
506
|
action.action.input_value,
|
|
494
507
|
};
|
|
495
|
-
// queue.input / config.save / approval.respond / approval.toggle
|
|
496
|
-
// CardActionResponse toast
|
|
497
|
-
// enqueueImmediate / enqueueConfigAction 是
|
|
508
|
+
// queue.input / config.save / approval.respond / approval.toggle /
|
|
509
|
+
// approval.answer 系列返回 CardActionResponse toast 给点击用户即时反馈。
|
|
510
|
+
// 必须直接返回值 -- enqueueImmediate / enqueueConfigAction 是
|
|
511
|
+
// fire-and-forget,会吞掉返回值(2026-08-17 review:answer 家族曾漏在
|
|
512
|
+
// 直返列表外,过期/重复 nonce/非法选项等错误 toast 被静默吞掉)。
|
|
498
513
|
// 审批响应尤其不能落串行队列:run 任务占用队列头直到 turn 结束,审批响应
|
|
499
514
|
// 排在后面会形成死锁(run 不结束不执行、run 结束 coordinator 已删响应空转)。
|
|
500
515
|
if (actionValue.cmd === 'queue.input' ||
|
|
501
516
|
actionValue.cmd === 'config.save' ||
|
|
502
517
|
actionValue.cmd === 'approval.respond' ||
|
|
503
|
-
actionValue.cmd === 'approval.toggle'
|
|
518
|
+
actionValue.cmd === 'approval.toggle' ||
|
|
519
|
+
actionValue.cmd === 'approval.answer' ||
|
|
520
|
+
actionValue.cmd === 'approval.answerSubmit' ||
|
|
521
|
+
actionValue.cmd === 'approval.answerCustom') {
|
|
504
522
|
return router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
505
523
|
}
|
|
506
524
|
// order.exec → equivalent queued message (Plan A): resolve the order text
|
|
@@ -618,6 +636,7 @@ async function main() {
|
|
|
618
636
|
configPath: path.join(configDir, 'config.yaml'),
|
|
619
637
|
workspacePath: path.join(configDir, 'workspace.json'),
|
|
620
638
|
ordersPath: path.join(configDir, 'orders.json'),
|
|
639
|
+
aliasesPath: path.join(configDir, 'aliases.json'),
|
|
621
640
|
restartSpawner: () => spawnReplacementBridge(path.join(configDir, 'logs')),
|
|
622
641
|
sessionReaderRegistry,
|
|
623
642
|
devMode: cliArgs.dev,
|
|
@@ -683,9 +702,8 @@ async function main() {
|
|
|
683
702
|
displayTitle: content.displayTitle,
|
|
684
703
|
agentKind: config.defaultAgent,
|
|
685
704
|
},
|
|
686
|
-
// Compact
|
|
687
|
-
config.defaultAgent
|
|
688
|
-
getAgentConfig(config, 'codex')?.serviceMode === 'app-server');
|
|
705
|
+
// Compact 能力探测与 bridge 侧一致(runner 有 runCompact 才渲染)。
|
|
706
|
+
bridge.hasRunCompact(restoredCwd, config.defaultAgent));
|
|
689
707
|
}
|
|
690
708
|
catch (err) {
|
|
691
709
|
getLogger().warn('[startup] sendAutoResumeCard failed:', err);
|
package/dist/instance-lock.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { silentlyUnlink } from './common/fs.js';
|
|
4
5
|
export class InstanceAlreadyRunningError extends Error {
|
|
5
6
|
pid;
|
|
6
7
|
lockPath;
|
|
@@ -68,10 +69,7 @@ export class InstanceLock {
|
|
|
68
69
|
throw new InstanceAlreadyRunningError(pid, this.lockPath);
|
|
69
70
|
}
|
|
70
71
|
// Stale lock: remove and retry once
|
|
71
|
-
|
|
72
|
-
fs.unlinkSync(this.lockPath);
|
|
73
|
-
}
|
|
74
|
-
catch { }
|
|
72
|
+
silentlyUnlink(this.lockPath);
|
|
75
73
|
try {
|
|
76
74
|
const fd = fs.openSync(this.lockPath, 'wx');
|
|
77
75
|
fs.writeSync(fd, this.lockContents(process.pid));
|
|
@@ -99,10 +97,7 @@ export class InstanceLock {
|
|
|
99
97
|
release() {
|
|
100
98
|
const { pid } = this.readPidAndComm();
|
|
101
99
|
if (pid === process.pid) {
|
|
102
|
-
|
|
103
|
-
fs.unlinkSync(this.lockPath);
|
|
104
|
-
}
|
|
105
|
-
catch { }
|
|
100
|
+
silentlyUnlink(this.lockPath);
|
|
106
101
|
}
|
|
107
102
|
}
|
|
108
103
|
registerExitHandlers() {
|
|
@@ -142,10 +137,7 @@ export class InstanceLock {
|
|
|
142
137
|
if (Number.isInteger(pid) && pid > 0 && comm)
|
|
143
138
|
return { pid, comm };
|
|
144
139
|
// Malformed or missing comm — treat as corrupt, unlink and return undefined
|
|
145
|
-
|
|
146
|
-
fs.unlinkSync(this.lockPath);
|
|
147
|
-
}
|
|
148
|
-
catch { }
|
|
140
|
+
silentlyUnlink(this.lockPath);
|
|
149
141
|
return { pid: undefined, comm: undefined };
|
|
150
142
|
}
|
|
151
143
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AliasEntry } from './alias-store.js';
|
|
2
|
+
/**
|
|
3
|
+
* 一次别名展开。
|
|
4
|
+
*
|
|
5
|
+
* - 仅匹配消息开头的 `$name`(`^$name`),`$name` 后接空格或行尾,全词精确匹配;
|
|
6
|
+
* - 名称必须 `[A-Za-z_][A-Za-z0-9_]*`(数字开头的 `$500` 永不匹配);
|
|
7
|
+
* - 未知别名返回 undefined(原样透传给 agent,不报错);
|
|
8
|
+
* - 单次不递归:展开结果不再二次展开,防死循环;
|
|
9
|
+
* - `!` / `/` 开头消息天然不匹配(正则要求 `$` 开头),bash 的 `$PATH` 不受影响。
|
|
10
|
+
*
|
|
11
|
+
* @returns 展开后的完整消息;无匹配时返回 undefined。
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveAlias(message: string, aliases: readonly AliasEntry[]): string | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次别名展开。
|
|
3
|
+
*
|
|
4
|
+
* - 仅匹配消息开头的 `$name`(`^$name`),`$name` 后接空格或行尾,全词精确匹配;
|
|
5
|
+
* - 名称必须 `[A-Za-z_][A-Za-z0-9_]*`(数字开头的 `$500` 永不匹配);
|
|
6
|
+
* - 未知别名返回 undefined(原样透传给 agent,不报错);
|
|
7
|
+
* - 单次不递归:展开结果不再二次展开,防死循环;
|
|
8
|
+
* - `!` / `/` 开头消息天然不匹配(正则要求 `$` 开头),bash 的 `$PATH` 不受影响。
|
|
9
|
+
*
|
|
10
|
+
* @returns 展开后的完整消息;无匹配时返回 undefined。
|
|
11
|
+
*/
|
|
12
|
+
export function resolveAlias(message, aliases) {
|
|
13
|
+
const match = /^\$([A-Za-z_][A-Za-z0-9_]*)(?=$|[ \t])/.exec(message);
|
|
14
|
+
if (!match)
|
|
15
|
+
return undefined;
|
|
16
|
+
const entry = aliases.find((a) => a.name === match[1]);
|
|
17
|
+
if (!entry)
|
|
18
|
+
return undefined;
|
|
19
|
+
// lookahead 不消费空格:match[0] 仅含 $name,参数(含前导空格)原样拼接在别名文本后。
|
|
20
|
+
return entry.text + message.slice(match[0].length);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=alias-resolve.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const MAX_ALIASES = 50;
|
|
2
|
+
export declare const MAX_ALIAS_NAME_LENGTH = 20;
|
|
3
|
+
export declare const MAX_ALIAS_TEXT_LENGTH = 200;
|
|
4
|
+
/**
|
|
5
|
+
* 别名名称规则:`[A-Za-z_][A-Za-z0-9_]*`,≤20 字符,不能数字开头
|
|
6
|
+
* —— `$500`、`$3d` 这类正常文本永远不匹配,不会误展开。
|
|
7
|
+
*/
|
|
8
|
+
export declare const ALIAS_NAME_PATTERN: RegExp;
|
|
9
|
+
export interface AliasEntry {
|
|
10
|
+
name: string;
|
|
11
|
+
text: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 全局别名存储。
|
|
16
|
+
*
|
|
17
|
+
* 独立于 OrderStore(aliases.json),避免破坏 orders.json 向后兼容。
|
|
18
|
+
* 原子写(tmp + rename)+ 损坏恢复,复用 OrderStore/WorkspaceStore 同一模式。
|
|
19
|
+
*/
|
|
20
|
+
export declare class AliasStore {
|
|
21
|
+
private filePath;
|
|
22
|
+
private data;
|
|
23
|
+
constructor(filePath?: string);
|
|
24
|
+
/** Reload data from disk (useful when another process may have written). */
|
|
25
|
+
reload(): void;
|
|
26
|
+
private load;
|
|
27
|
+
private persist;
|
|
28
|
+
has(name: string): boolean;
|
|
29
|
+
get(name: string): AliasEntry | undefined;
|
|
30
|
+
list(): AliasEntry[];
|
|
31
|
+
/**
|
|
32
|
+
* 注册/更新别名(同名覆盖文本,保留 createdAt)。返回写入后的条目。
|
|
33
|
+
* 名称/文本/数量超限时抛错,由调用方提示用户。
|
|
34
|
+
*/
|
|
35
|
+
set(name: string, text: string): AliasEntry;
|
|
36
|
+
/** 删除别名;不存在返回 false。 */
|
|
37
|
+
remove(name: string): boolean;
|
|
38
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { atomicWriteJson } from '../persistence/atomic-write.js';
|
|
4
|
+
import { loadJsonFile } from '../persistence/load-json-file.js';
|
|
5
|
+
export const MAX_ALIASES = 50;
|
|
6
|
+
export const MAX_ALIAS_NAME_LENGTH = 20;
|
|
7
|
+
export const MAX_ALIAS_TEXT_LENGTH = 200;
|
|
8
|
+
/**
|
|
9
|
+
* 别名名称规则:`[A-Za-z_][A-Za-z0-9_]*`,≤20 字符,不能数字开头
|
|
10
|
+
* —— `$500`、`$3d` 这类正常文本永远不匹配,不会误展开。
|
|
11
|
+
*/
|
|
12
|
+
export const ALIAS_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
13
|
+
/**
|
|
14
|
+
* 全局别名存储。
|
|
15
|
+
*
|
|
16
|
+
* 独立于 OrderStore(aliases.json),避免破坏 orders.json 向后兼容。
|
|
17
|
+
* 原子写(tmp + rename)+ 损坏恢复,复用 OrderStore/WorkspaceStore 同一模式。
|
|
18
|
+
*/
|
|
19
|
+
export class AliasStore {
|
|
20
|
+
filePath;
|
|
21
|
+
data;
|
|
22
|
+
constructor(filePath) {
|
|
23
|
+
this.filePath = filePath ?? path.join(os.homedir(), '.lark-remote', 'aliases.json');
|
|
24
|
+
this.data = [];
|
|
25
|
+
this.load();
|
|
26
|
+
}
|
|
27
|
+
/** Reload data from disk (useful when another process may have written). */
|
|
28
|
+
reload() {
|
|
29
|
+
this.load();
|
|
30
|
+
}
|
|
31
|
+
load() {
|
|
32
|
+
const parsed = loadJsonFile(this.filePath, undefined);
|
|
33
|
+
if (!parsed || !Array.isArray(parsed)) {
|
|
34
|
+
this.data = [];
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const valid = [];
|
|
38
|
+
for (const e of parsed) {
|
|
39
|
+
if (e &&
|
|
40
|
+
typeof e.name === 'string' &&
|
|
41
|
+
typeof e.text === 'string' &&
|
|
42
|
+
typeof e.createdAt === 'string' &&
|
|
43
|
+
ALIAS_NAME_PATTERN.test(e.name) &&
|
|
44
|
+
e.name.length <= MAX_ALIAS_NAME_LENGTH) {
|
|
45
|
+
valid.push({
|
|
46
|
+
name: e.name,
|
|
47
|
+
text: e.text.slice(0, MAX_ALIAS_TEXT_LENGTH),
|
|
48
|
+
createdAt: e.createdAt,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.data = valid;
|
|
53
|
+
}
|
|
54
|
+
persist() {
|
|
55
|
+
atomicWriteJson(this.filePath, this.data);
|
|
56
|
+
}
|
|
57
|
+
has(name) {
|
|
58
|
+
return this.data.some((a) => a.name === name);
|
|
59
|
+
}
|
|
60
|
+
get(name) {
|
|
61
|
+
return this.data.find((a) => a.name === name);
|
|
62
|
+
}
|
|
63
|
+
list() {
|
|
64
|
+
return [...this.data];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 注册/更新别名(同名覆盖文本,保留 createdAt)。返回写入后的条目。
|
|
68
|
+
* 名称/文本/数量超限时抛错,由调用方提示用户。
|
|
69
|
+
*/
|
|
70
|
+
set(name, text) {
|
|
71
|
+
if (!ALIAS_NAME_PATTERN.test(name) || name.length > MAX_ALIAS_NAME_LENGTH) {
|
|
72
|
+
throw new Error(`别名名称只能包含字母/数字/下划线,不能数字开头,且不超过 ${MAX_ALIAS_NAME_LENGTH} 字符`);
|
|
73
|
+
}
|
|
74
|
+
if (text.length > MAX_ALIAS_TEXT_LENGTH) {
|
|
75
|
+
throw new Error(`别名文本超过 ${MAX_ALIAS_TEXT_LENGTH} 字符限制`);
|
|
76
|
+
}
|
|
77
|
+
if (!this.has(name) && this.data.length >= MAX_ALIASES) {
|
|
78
|
+
throw new Error(`别名数量已达上限 ${MAX_ALIASES}`);
|
|
79
|
+
}
|
|
80
|
+
const existing = this.get(name);
|
|
81
|
+
const entry = {
|
|
82
|
+
name,
|
|
83
|
+
text,
|
|
84
|
+
createdAt: existing?.createdAt ?? new Date().toISOString(),
|
|
85
|
+
};
|
|
86
|
+
const idx = this.data.findIndex((a) => a.name === name);
|
|
87
|
+
if (idx >= 0) {
|
|
88
|
+
this.data[idx] = entry;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.data.push(entry);
|
|
92
|
+
}
|
|
93
|
+
this.persist();
|
|
94
|
+
return entry;
|
|
95
|
+
}
|
|
96
|
+
/** 删除别名;不存在返回 false。 */
|
|
97
|
+
remove(name) {
|
|
98
|
+
const before = this.data.length;
|
|
99
|
+
this.data = this.data.filter((a) => a.name !== name);
|
|
100
|
+
if (this.data.length === before)
|
|
101
|
+
return false;
|
|
102
|
+
this.persist();
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=alias-store.js.map
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Atomically write string
|
|
2
|
+
* Atomically write content (string or binary Buffer) to a file via tmp + rename.
|
|
3
3
|
* Falls back to copy+unlink when rename fails with EXDEV (cross-device).
|
|
4
4
|
*/
|
|
5
|
-
export declare function atomicWrite(filePath: string, content: string, encoding?: BufferEncoding): void;
|
|
5
|
+
export declare function atomicWrite(filePath: string, content: string | Buffer, encoding?: BufferEncoding): void;
|
|
6
6
|
/**
|
|
7
7
|
* Atomically write JSON data to a file via tmp + rename.
|
|
8
8
|
* Ensures a crash mid-write cannot leave a truncated file.
|
|
9
9
|
*/
|
|
10
10
|
export declare function atomicWriteJson(filePath: string, data: unknown): void;
|
|
11
|
+
/**
|
|
12
|
+
* 把已写好的文件原子移动到目标位置(rename 同设备即原子;EXDEV 跨设备
|
|
13
|
+
* 回退 copy + unlink)。失败时抛出,由调用方负责清理源文件(best-effort)。
|
|
14
|
+
* 用于入站媒体:下载已流式写入临时文件,落盘只需一次 move,不再复制进内存。
|
|
15
|
+
*/
|
|
16
|
+
export declare function atomicMoveFile(srcPath: string, destPath: string): void;
|