lark-remote 0.1.5 → 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 +39 -0
- package/README.en.md +5 -1
- package/README.md +10 -2
- package/dist/bridge/approval-coordinator.d.ts +131 -0
- package/dist/bridge/approval-coordinator.js +409 -0
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +138 -5
- package/dist/bridge/index.js +569 -65
- package/dist/bridge/queue-manager.d.ts +11 -1
- package/dist/bridge/queue-manager.js +20 -11
- package/dist/card/approval-render.d.ts +16 -0
- package/dist/card/approval-render.js +363 -0
- 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 +13 -10
- package/dist/card/card-shared.js +27 -24
- 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 +140 -202
- package/dist/card/run-state.d.ts +38 -3
- package/dist/card/run-state.js +221 -11
- 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/dir.js +1 -1
- package/dist/config/index.d.ts +116 -9
- package/dist/config/index.js +122 -29
- package/dist/connector/index.d.ts +74 -2
- package/dist/connector/index.js +119 -1
- package/dist/index.js +139 -99
- 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.d.ts +1 -1
- package/dist/router/config/codex.js +33 -5
- 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 +9 -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 +12 -3
- package/dist/router/index.d.ts +77 -17
- package/dist/router/index.js +455 -549
- package/dist/router/utils.d.ts +4 -0
- package/dist/router/utils.js +9 -2
- 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 +519 -0
- package/dist/runner/codex/app-server/protocol-types.js +56 -0
- package/dist/runner/codex/app-server/runner.d.ts +104 -0
- package/dist/runner/codex/app-server/runner.js +377 -0
- package/dist/runner/codex/app-server/translator.d.ts +89 -0
- package/dist/runner/codex/app-server/translator.js +716 -0
- package/dist/runner/codex/index.d.ts +1 -1
- package/dist/runner/codex/index.js +1 -1
- 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/common/jsonrpc/client.d.ts +89 -0
- package/dist/runner/common/jsonrpc/client.js +243 -0
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +205 -0
- package/dist/runner/common/jsonrpc/transport.d.ts +50 -0
- package/dist/runner/common/jsonrpc/transport.js +230 -0
- 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/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/index.d.ts +5 -5
- 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 +153 -2
- 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 +57 -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/argv.d.ts +0 -33
- package/dist/runner/codex/argv.js +0 -40
- 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 -63
- package/dist/runner/codex/runner.js +0 -124
- 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,76 +19,44 @@ 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 {
|
|
24
|
-
import { newSessionButton, agentDisplayName } from './card/card-shared.js';
|
|
22
|
+
import { buildSessionHistoryCard } from './router/card-helpers.js';
|
|
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
|
-
async function sendAutoResumeCard(connector, contact, cwd, session, state) {
|
|
28
|
+
async function sendAutoResumeCard(connector, contact, cwd, session, state, compactSupported = false) {
|
|
29
29
|
if (!contact.userId)
|
|
30
30
|
return;
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Add usage/stats at the end
|
|
63
|
-
if (state.usage) {
|
|
64
|
-
const usageStr = formatUsageStats(state.usage);
|
|
65
|
-
elements.push(markdownDiv(usageStr));
|
|
40
|
+
const actionButtons = [];
|
|
41
|
+
if (compactSupported) {
|
|
42
|
+
actionButtons.push(resumeCompactButton(session.sessionId, state.agentKind ?? 'codex'));
|
|
66
43
|
}
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
44
|
+
actionButtons.push(newSessionButton());
|
|
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,
|
|
78
59
|
});
|
|
79
|
-
const card = {
|
|
80
|
-
schema: '2.0',
|
|
81
|
-
config: {
|
|
82
|
-
wide_screen_mode: true,
|
|
83
|
-
},
|
|
84
|
-
header: {
|
|
85
|
-
title: {
|
|
86
|
-
tag: 'plain_text',
|
|
87
|
-
content: `🔁 自动恢复会话 · ${agentDisplayName(agentKind)}`,
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
body: { elements },
|
|
91
|
-
};
|
|
92
60
|
await connector.sendWithRetry(recipient, { card });
|
|
93
61
|
}
|
|
94
62
|
/** Initialize CLI args, config, and logger. Returns { config, configDir, logger, instanceLock }. */
|
|
@@ -161,28 +129,36 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
161
129
|
settings: cliArgs.settings,
|
|
162
130
|
pidDir: configDir,
|
|
163
131
|
workspace: ws,
|
|
132
|
+
permissionMode: claudeConfig.permissionMode,
|
|
133
|
+
idleTtlMs: claudeConfig.idleTtlMinutes != null ? claudeConfig.idleTtlMinutes * 60_000 : undefined,
|
|
164
134
|
});
|
|
165
135
|
});
|
|
166
|
-
// Register
|
|
167
|
-
//
|
|
168
|
-
//
|
|
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().
|
|
169
140
|
const codexSessionReader = new CodexSessionReader({ codexHome: process.env.CODEX_HOME });
|
|
170
|
-
agentRegistry.register('codex', (
|
|
141
|
+
agentRegistry.register('codex', (_ws) => {
|
|
171
142
|
const container = agentRegistry.getConfigContainer();
|
|
172
143
|
const latestConfig = container?.current ?? config;
|
|
173
144
|
const codexConfig = getAgentConfig(latestConfig, 'codex');
|
|
174
|
-
return new
|
|
145
|
+
return new CodexAppServerRunner({
|
|
146
|
+
kind: 'codex',
|
|
175
147
|
model: codexConfig?.model,
|
|
176
148
|
modelProvider: codexConfig?.modelProvider,
|
|
177
149
|
reasoningEffort: codexConfig?.reasoningEffort,
|
|
178
|
-
stopGraceMs: codexConfig?.stopGraceMs ?? latestConfig.claude?.stopGraceMs ?? DEFAULT_STOP_GRACE_MS,
|
|
179
|
-
pidDir: configDir,
|
|
180
|
-
workspace: ws,
|
|
181
150
|
sessionReader: codexSessionReader,
|
|
151
|
+
sandbox: codexConfig?.sandbox,
|
|
152
|
+
approvalPolicy: codexConfig?.approvalPolicy,
|
|
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,
|
|
182
159
|
});
|
|
183
160
|
});
|
|
184
|
-
// Register
|
|
185
|
-
// Using `opencode run --format json --auto` instead of HTTP+SSE server mode
|
|
161
|
+
// Register OpencodeAcpRunner (pure ACP mode, `opencode acp` JSON-RPC over stdio)
|
|
186
162
|
// Register session reader (same instance as used by runner)
|
|
187
163
|
const sessionReaderRegistry = new SessionReaderRegistry();
|
|
188
164
|
sessionReaderRegistry.register('claude', new ClaudeSessionReader());
|
|
@@ -190,7 +166,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
190
166
|
// Register OpencodeSessionReader (CLI version)
|
|
191
167
|
// Using `opencode session list` and `opencode export` instead of HTTP API
|
|
192
168
|
const opencodeSessionReader = new OpencodeSessionReader();
|
|
193
|
-
agentRegistry.register('opencode', (
|
|
169
|
+
agentRegistry.register('opencode', (_ws) => {
|
|
194
170
|
// Get latest config from container (set below for pi)
|
|
195
171
|
const container = agentRegistry.getConfigContainer();
|
|
196
172
|
const latestConfig = container?.current ?? config;
|
|
@@ -207,17 +183,15 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
207
183
|
model = `${provider}/${ocConfig.modelID}`;
|
|
208
184
|
}
|
|
209
185
|
}
|
|
210
|
-
return new
|
|
186
|
+
return new OpencodeAcpRunner({
|
|
187
|
+
kind: 'opencode',
|
|
211
188
|
model,
|
|
212
|
-
// P1-15: read stopGraceMs from latestConfig (not startup closure)
|
|
213
|
-
stopGraceMs: latestConfig.claude?.stopGraceMs ?? DEFAULT_STOP_GRACE_MS,
|
|
214
|
-
pidDir: configDir,
|
|
215
|
-
workspace: ws,
|
|
216
189
|
sessionReader: opencodeSessionReader,
|
|
190
|
+
mode: ocConfig?.mode ?? 'build',
|
|
217
191
|
});
|
|
218
192
|
});
|
|
219
193
|
sessionReaderRegistry.register('opencode', opencodeSessionReader);
|
|
220
|
-
// Register
|
|
194
|
+
// Register PiRpcRunner and PiSessionReader
|
|
221
195
|
// 2026-07-12: 修复 config.save 后 pi provider 不生效的问题
|
|
222
196
|
// 关键:factory 闭包必须能访问到最新的 config,而不是启动时的快照
|
|
223
197
|
// 用可变容器存储 config 引用,registry 持有容器引用,factory 从容器读取最新值
|
|
@@ -229,7 +203,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
229
203
|
const container = agentRegistry.getConfigContainer();
|
|
230
204
|
const latestConfig = container?.current ?? config;
|
|
231
205
|
const piConf = getAgentConfig(latestConfig, 'pi');
|
|
232
|
-
return new
|
|
206
|
+
return new PiRpcRunner({
|
|
233
207
|
provider: piConf?.provider ?? 'Volcano',
|
|
234
208
|
model: piConf?.model ?? 'glm-5.2',
|
|
235
209
|
thinking: piConf?.thinking ?? 'medium',
|
|
@@ -237,27 +211,30 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
237
211
|
.split(',')
|
|
238
212
|
.map((t) => t.trim())
|
|
239
213
|
.filter(Boolean),
|
|
240
|
-
pidDir: configDir,
|
|
241
214
|
workspace: ws,
|
|
242
215
|
sessionReader: piSessionReader,
|
|
243
216
|
});
|
|
244
217
|
});
|
|
245
218
|
sessionReaderRegistry.register('pi', piSessionReader);
|
|
246
|
-
// Register
|
|
219
|
+
// Register KimiAcpRunner (pure ACP mode) and KimiSessionReader
|
|
247
220
|
const kimiSessionReader = new KimiSessionReader();
|
|
248
|
-
agentRegistry.register('kimi', (
|
|
221
|
+
agentRegistry.register('kimi', (_ws) => {
|
|
249
222
|
// 每次 factory 调用时从 registry 获取最新 config
|
|
250
223
|
const container = agentRegistry.getConfigContainer();
|
|
251
224
|
const latestConfig = container?.current ?? config;
|
|
252
225
|
const kimiConf = getAgentConfig(latestConfig, 'kimi');
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// P1-15: read stopGraceMs from latestConfig (not startup closure)
|
|
257
|
-
stopGraceMs: latestConfig.claude?.stopGraceMs ?? DEFAULT_STOP_GRACE_MS,
|
|
258
|
-
pidDir: configDir,
|
|
259
|
-
workspace: ws,
|
|
226
|
+
const acpConf = kimiConf?.acp;
|
|
227
|
+
return new KimiAcpRunner({
|
|
228
|
+
kind: 'kimi',
|
|
260
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',
|
|
261
238
|
});
|
|
262
239
|
});
|
|
263
240
|
sessionReaderRegistry.register('kimi', kimiSessionReader);
|
|
@@ -288,6 +265,48 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
288
265
|
}
|
|
289
266
|
/** Setup message and card action handlers. */
|
|
290
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
|
+
});
|
|
291
310
|
connector.setMessageHandler((msg) => {
|
|
292
311
|
// 绑定/授权闸门:仅 owner 放行;非 owner 静默丢弃;未绑定时要求 PIN 认领
|
|
293
312
|
const decision = binder.classify(msg.userId, msg.content, msg.chatId);
|
|
@@ -345,9 +364,16 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
345
364
|
}
|
|
346
365
|
// decision.kind === 'owner':正常处理(不再每条覆盖 startup-contact)
|
|
347
366
|
logger.info(`message from ${msg.userId}: ${msg.content.slice(0, 100)}`);
|
|
367
|
+
// 文本到达先冲刷待合批的媒体保存提示(先图后文字时文字到达
|
|
368
|
+
// 先冲刷批次,避免合批提示被后续消息淹没/丢图)。
|
|
369
|
+
bridge.flushMediaNotifications(msg.userId, msg.chatId);
|
|
348
370
|
// Add Typing reaction to indicate "still alive"
|
|
349
371
|
void connector.addReaction(msg.messageId, 'Typing');
|
|
350
|
-
|
|
372
|
+
// 别名展开:命令分发前对消息做一次 $name 展开(不递归)。
|
|
373
|
+
// `!` / `/` 开头的消息不会进入展开路径($PATH、$HOME 等 shell 变量不受影响),
|
|
374
|
+
// 未知 $xxx 原样透传;展开结果若以 `/` 开头会自然落入命令路径。
|
|
375
|
+
const content = router.expandAliasMessage(msg.content);
|
|
376
|
+
const trimmedLower = content.trim().toLowerCase();
|
|
351
377
|
if (trimmedLower === '/stop' || trimmedLower === '/t') {
|
|
352
378
|
void (async () => {
|
|
353
379
|
const stopped = await bridge.interruptCurrentRun({
|
|
@@ -364,9 +390,9 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
364
390
|
})().catch((err) => logger.error('[control] /stop failed:', err));
|
|
365
391
|
return;
|
|
366
392
|
}
|
|
367
|
-
if (
|
|
393
|
+
if (content.trim().startsWith('/')) {
|
|
368
394
|
void router
|
|
369
|
-
.handle(
|
|
395
|
+
.handle(content, {
|
|
370
396
|
userId: msg.userId,
|
|
371
397
|
chatId: msg.chatId,
|
|
372
398
|
messageId: msg.messageId,
|
|
@@ -379,9 +405,9 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
379
405
|
// in the same workspace (design.md §9.6: the queue exists to prevent
|
|
380
406
|
// concurrent claude runs, not to serialize bash). router.handle dispatches
|
|
381
407
|
// `!` to bridge.executeBash, which tracks the run independently.
|
|
382
|
-
if (
|
|
408
|
+
if (content.trim().startsWith('!')) {
|
|
383
409
|
void router
|
|
384
|
-
.handle(
|
|
410
|
+
.handle(content, {
|
|
385
411
|
userId: msg.userId,
|
|
386
412
|
chatId: msg.chatId,
|
|
387
413
|
messageId: msg.messageId,
|
|
@@ -405,7 +431,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
405
431
|
// 捕获点 Bridge.currentBinding。
|
|
406
432
|
const binding = bridge.currentBinding(msg.userId);
|
|
407
433
|
bridge.enqueue(workspace, async () => {
|
|
408
|
-
await router.handle(
|
|
434
|
+
await router.handle(content, {
|
|
409
435
|
userId: msg.userId,
|
|
410
436
|
chatId: msg.chatId,
|
|
411
437
|
messageId: msg.messageId,
|
|
@@ -420,7 +446,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
420
446
|
userId: msg.userId,
|
|
421
447
|
chatId: msg.chatId,
|
|
422
448
|
messageId: msg.messageId,
|
|
423
|
-
messagePreview:
|
|
449
|
+
messagePreview: content.slice(0, 3000),
|
|
424
450
|
// D3/Step4: binding 随 taskMeta 存进 QueuedTask,供替换闭包
|
|
425
451
|
// (queue.edit/queue.immediate)复用,不重新快照。
|
|
426
452
|
binding,
|
|
@@ -479,11 +505,20 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
479
505
|
inputValue: action.raw?.action?.input_value ??
|
|
480
506
|
action.action.input_value,
|
|
481
507
|
};
|
|
482
|
-
// queue.input
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
|
|
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 被静默吞掉)。
|
|
513
|
+
// 审批响应尤其不能落串行队列:run 任务占用队列头直到 turn 结束,审批响应
|
|
514
|
+
// 排在后面会形成死锁(run 不结束不执行、run 结束 coordinator 已删响应空转)。
|
|
515
|
+
if (actionValue.cmd === 'queue.input' ||
|
|
516
|
+
actionValue.cmd === 'config.save' ||
|
|
517
|
+
actionValue.cmd === 'approval.respond' ||
|
|
518
|
+
actionValue.cmd === 'approval.toggle' ||
|
|
519
|
+
actionValue.cmd === 'approval.answer' ||
|
|
520
|
+
actionValue.cmd === 'approval.answerSubmit' ||
|
|
521
|
+
actionValue.cmd === 'approval.answerCustom') {
|
|
487
522
|
return router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
488
523
|
}
|
|
489
524
|
// order.exec → equivalent queued message (Plan A): resolve the order text
|
|
@@ -514,6 +549,8 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
514
549
|
chatId,
|
|
515
550
|
messageId,
|
|
516
551
|
messagePreview: `card action: ${actionValue.cmd}`,
|
|
552
|
+
// Compact 是单向操作,排队卡不允许编辑(编辑预览无意义)。
|
|
553
|
+
editable: actionValue.cmd !== 'codex.compact' && actionValue.cmd !== 'resume.compact',
|
|
517
554
|
},
|
|
518
555
|
});
|
|
519
556
|
}
|
|
@@ -599,6 +636,7 @@ async function main() {
|
|
|
599
636
|
configPath: path.join(configDir, 'config.yaml'),
|
|
600
637
|
workspacePath: path.join(configDir, 'workspace.json'),
|
|
601
638
|
ordersPath: path.join(configDir, 'orders.json'),
|
|
639
|
+
aliasesPath: path.join(configDir, 'aliases.json'),
|
|
602
640
|
restartSpawner: () => spawnReplacementBridge(path.join(configDir, 'logs')),
|
|
603
641
|
sessionReaderRegistry,
|
|
604
642
|
devMode: cliArgs.dev,
|
|
@@ -663,7 +701,9 @@ async function main() {
|
|
|
663
701
|
recap: content.recap,
|
|
664
702
|
displayTitle: content.displayTitle,
|
|
665
703
|
agentKind: config.defaultAgent,
|
|
666
|
-
}
|
|
704
|
+
},
|
|
705
|
+
// Compact 能力探测与 bridge 侧一致(runner 有 runCompact 才渲染)。
|
|
706
|
+
bridge.hasRunCompact(restoredCwd, config.defaultAgent));
|
|
667
707
|
}
|
|
668
708
|
catch (err) {
|
|
669
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;
|