larkin 0.2.70
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/CONTRIBUTING.md +48 -0
- package/LICENSE +201 -0
- package/README.md +122 -0
- package/SECURITY.md +14 -0
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +5488 -0
- package/dist/agent/agent-cli-capabilities.cjs +43 -0
- package/dist/agent/agent-cli-capabilities.mjs +35 -0
- package/dist/agent/agent-state-store.cjs +961 -0
- package/dist/agent/agent-state-store.mjs +923 -0
- package/dist/agent/agent-transport.cjs +201 -0
- package/dist/agent/agent-transport.mjs +194 -0
- package/dist/agent/config-cli-contract.cjs +19 -0
- package/dist/agent/config-cli-contract.mjs +16 -0
- package/dist/agent/context-prompt.cjs +159 -0
- package/dist/agent/context-prompt.mjs +155 -0
- package/dist/agent/freshness-gate.mjs +24 -0
- package/dist/agent/history-projection.cjs +72 -0
- package/dist/agent/history-projection.mjs +65 -0
- package/dist/agent/host-reminder-orchestrator.cjs +289 -0
- package/dist/agent/host-reminder-orchestrator.mjs +252 -0
- package/dist/agent/inbox-projection.cjs +92 -0
- package/dist/agent/inbox-projection.mjs +85 -0
- package/dist/agent/interaction-state-machine.cjs +710 -0
- package/dist/agent/interaction-state-machine.mjs +705 -0
- package/dist/agent/reminder-routes.cjs +192 -0
- package/dist/agent/reminder-routes.mjs +156 -0
- package/dist/agent/reminder-store.cjs +220 -0
- package/dist/agent/reminder-store.mjs +205 -0
- package/dist/agent/transport-business-context.cjs +486 -0
- package/dist/agent/transport-business-context.mjs +446 -0
- package/dist/agent/transport-shell.cjs +45 -0
- package/dist/agent/transport-shell.mjs +39 -0
- package/dist/app/agent-cli.mjs +596 -0
- package/dist/app/agent-config.mjs +633 -0
- package/dist/app/agent-lark-cli-workspace.cjs +95 -0
- package/dist/app/agent-lark-cli-workspace.mjs +85 -0
- package/dist/app/agent-readiness.cjs +42 -0
- package/dist/app/agent-readiness.mjs +38 -0
- package/dist/app/binary-entry.mjs +86 -0
- package/dist/app/cli.mjs +176 -0
- package/dist/app/dashboard.mjs +205 -0
- package/dist/app/internal-command.cjs +57 -0
- package/dist/app/internal-command.mjs +48 -0
- package/dist/app/lark-channel-secret.mjs +22 -0
- package/dist/app/lark-cli.mjs +887 -0
- package/dist/app/lark.mjs +45 -0
- package/dist/app/local-control.mjs +728 -0
- package/dist/app/official-lark-cli.cjs +138 -0
- package/dist/app/official-lark-cli.mjs +128 -0
- package/dist/app/pi-auth-cli.mjs +67 -0
- package/dist/app/run.mjs +364 -0
- package/dist/app/runtime-agent-config.mjs +416 -0
- package/dist/app/runtime-model-directory.mjs +78 -0
- package/dist/app/runtime-process.mjs +113 -0
- package/dist/app/session-cli.mjs +83 -0
- package/dist/app/setup-dashboard.mjs +26 -0
- package/dist/app/setup.mjs +166 -0
- package/dist/app/telemetry.mjs +83 -0
- package/dist/dashboard/dashboard-assets.mjs +21 -0
- package/dist/dashboard/dashboard-avatar.mjs +128 -0
- package/dist/dashboard/dashboard-config-controller.mjs +525 -0
- package/dist/dashboard/dashboard-lifecycle.cjs +97 -0
- package/dist/dashboard/dashboard-lifecycle.mjs +88 -0
- package/dist/dashboard/dashboard-template.mjs +29 -0
- package/dist/dashboard/dashboard-view-model.mjs +556 -0
- package/dist/dashboard/dashboard-workspace.mjs +104 -0
- package/dist/dashboard/web/assets/dashboard.css +3 -0
- package/dist/dashboard/web/assets/dashboard.js +49 -0
- package/dist/feishu/channel-transport.cjs +151 -0
- package/dist/feishu/channel-transport.mjs +148 -0
- package/dist/feishu/document-comment.cjs +120 -0
- package/dist/feishu/document-comment.mjs +109 -0
- package/dist/feishu/host-business-state.cjs +477 -0
- package/dist/feishu/host-business-state.mjs +462 -0
- package/dist/feishu/host-channel-business.cjs +161 -0
- package/dist/feishu/host-channel-business.mjs +157 -0
- package/dist/feishu/host-processing-eye.cjs +185 -0
- package/dist/feishu/host-processing-eye.mjs +181 -0
- package/dist/feishu/host-shell.cjs +1425 -0
- package/dist/feishu/host-shell.mjs +1385 -0
- package/dist/feishu/im-freshness-adapter.mjs +89 -0
- package/dist/feishu/interaction-orchestrator.cjs +279 -0
- package/dist/feishu/interaction-orchestrator.mjs +242 -0
- package/dist/feishu/lark-passthrough.mjs +70 -0
- package/dist/feishu/message-policy.cjs +111 -0
- package/dist/feishu/message-policy.mjs +98 -0
- package/dist/feishu/outbound-transport.cjs +221 -0
- package/dist/feishu/outbound-transport.mjs +215 -0
- package/dist/platform/build-info.cjs +25 -0
- package/dist/platform/build-info.mjs +18 -0
- package/dist/platform/callback-capability.cjs +301 -0
- package/dist/platform/callback-capability.mjs +252 -0
- package/dist/platform/check-bun-version.cjs +12 -0
- package/dist/platform/config.cjs +751 -0
- package/dist/platform/config.mjs +694 -0
- package/dist/platform/process-inspect.cjs +89 -0
- package/dist/platform/process-state.cjs +237 -0
- package/dist/platform/process-state.mjs +223 -0
- package/dist/platform/release-artifacts.mjs +61 -0
- package/dist/platform/root-layout.cjs +111 -0
- package/dist/platform/root-layout.mjs +72 -0
- package/dist/platform/telemetry-config.cjs +64 -0
- package/dist/platform/telemetry-config.mjs +56 -0
- package/dist/platform/telemetry-spool.cjs +661 -0
- package/dist/platform/telemetry-spool.mjs +653 -0
- package/dist/platform/telemetry-tracing.cjs +602 -0
- package/dist/platform/telemetry-tracing.mjs +596 -0
- package/dist/platform/telemetry-uploader.cjs +150 -0
- package/dist/platform/telemetry-uploader.mjs +146 -0
- package/dist/platform/workspace-service.cjs +469 -0
- package/dist/platform/workspace-service.mjs +430 -0
- package/dist/runtime/builtin-pi-assets.mjs +33 -0
- package/dist/runtime/claude-model-catalog.cjs +102 -0
- package/dist/runtime/claude-model-catalog.mjs +99 -0
- package/dist/runtime/codex-model-catalog.cjs +108 -0
- package/dist/runtime/codex-model-catalog.mjs +105 -0
- package/dist/runtime/pi-bash-timeout-extension.mjs +60 -0
- package/dist/runtime/pi-bash-timeout-injection.mjs +60 -0
- package/dist/runtime/pi-bash-timeout.bundle.js +43 -0
- package/dist/runtime/pi-model-catalog.cjs +116 -0
- package/dist/runtime/pi-model-catalog.mjs +75 -0
- package/dist/runtime/pi-official-auth.mjs +480 -0
- package/dist/runtime/pi-provider-config.cjs +252 -0
- package/dist/runtime/pi-provider-config.mjs +239 -0
- package/dist/runtime/pi-rpc-client.cjs +255 -0
- package/dist/runtime/pi-rpc-client.mjs +251 -0
- package/dist/runtime/pi-subagent-injection.mjs +129 -0
- package/dist/runtime/pi-subagents.bundle.js +8990 -0
- package/dist/runtime/runtime-adapters.mjs +1019 -0
- package/dist/runtime/runtime-contracts.cjs +2 -0
- package/dist/runtime/runtime-contracts.mjs +1 -0
- package/dist/runtime/runtime-host.cjs +930 -0
- package/dist/runtime/runtime-host.mjs +922 -0
- package/dist/runtime/runtime-model-catalog.cjs +43 -0
- package/dist/runtime/runtime-model-catalog.mjs +40 -0
- package/dist/runtime/runtime-readiness.cjs +177 -0
- package/dist/runtime/runtime-readiness.mjs +133 -0
- package/dist/setup/bot-register.mjs +655 -0
- package/dist/setup/grant-scopes.mjs +133 -0
- package/dist/setup/run-credential-preflight.mjs +89 -0
- package/dist/setup/setup-agent-choice.mjs +172 -0
- package/dist/setup/setup-bind.mjs +337 -0
- package/dist/setup/setup-binding.mjs +52 -0
- package/package.json +112 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import "../platform/check-bun-version.cjs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { daemonHasAgent, readProcessState } from "../platform/process-state.mjs";
|
|
6
|
+
import { discoverClaudeModelCatalog } from "../runtime/claude-model-catalog.mjs";
|
|
7
|
+
import { discoverCodexModelCatalog } from "../runtime/codex-model-catalog.mjs";
|
|
8
|
+
import { discoverPiModelCatalog } from "../runtime/pi-model-catalog.mjs";
|
|
9
|
+
import { effectiveDocumentCommentSubscription, readCallbackCapability, readDocumentCommentCapability, readDocumentCommentReplyCapability, readDocumentCommentSubscription, } from "../platform/callback-capability.mjs";
|
|
10
|
+
import { isChannelReconnecting, projectAgentReadiness } from "./agent-readiness.mjs";
|
|
11
|
+
import { requestAgentUpsert } from "./local-control.mjs";
|
|
12
|
+
import * as larkinConfig from "../platform/config.mjs";
|
|
13
|
+
import { managedOfficialLarkCli } from "./agent-lark-cli-workspace.mjs";
|
|
14
|
+
function safeDocumentCommentCapabilities(configDir, profile) {
|
|
15
|
+
try {
|
|
16
|
+
return {
|
|
17
|
+
event: readDocumentCommentCapability(configDir, profile),
|
|
18
|
+
reply: readDocumentCommentReplyCapability(configDir, profile),
|
|
19
|
+
subscription: readDocumentCommentSubscription(configDir, profile),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { event: null, reply: null, subscription: effectiveDocumentCommentSubscription(null) };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function projectDocumentCommentDiagnostic(capability, status) {
|
|
27
|
+
const stableFailureCategories = new Set([
|
|
28
|
+
"permission_missing", "document_access_denied", "comment_unavailable_or_empty", "inbox_write_failure",
|
|
29
|
+
"pending_capacity_exhausted", "read_failure_unknown", "pending_state_failure",
|
|
30
|
+
]);
|
|
31
|
+
if (status?.documentCommentLastErrorCategory && stableFailureCategories.has(status.documentCommentLastErrorCategory)) {
|
|
32
|
+
return {
|
|
33
|
+
category: status.documentCommentLastErrorCategory,
|
|
34
|
+
reason: status.documentCommentLastError || status.documentCommentLastErrorCategory,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (status?.documentCommentEventAt)
|
|
38
|
+
return { category: "event_arrived", reason: "real_event_observed" };
|
|
39
|
+
if (capability)
|
|
40
|
+
return { category: "publish_or_event_unverified", reason: "publication_and_real_event_unverified" };
|
|
41
|
+
return { category: "not_requested", reason: "setup_required" };
|
|
42
|
+
}
|
|
43
|
+
const kind = process.argv[2];
|
|
44
|
+
const rest = process.argv.slice(3);
|
|
45
|
+
const say = (...args) => console.log(...args);
|
|
46
|
+
const die = (message) => {
|
|
47
|
+
console.error(`✗ ${message}`);
|
|
48
|
+
if (kind === "config" && !message.includes("larkin config --help"))
|
|
49
|
+
console.error(" 下一步: larkin config --help");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
};
|
|
52
|
+
if (!kind || !["agents", "model", "runtime", "effort", "chats", "config"].includes(kind)) {
|
|
53
|
+
die("用法: larkin agents | larkin config <show|mention|apply> | larkin model [<id>] | larkin runtime [<id>] [--model <id>] | larkin effort [<level>|clear] | larkin chats [free|strict <oc_id>]");
|
|
54
|
+
}
|
|
55
|
+
const allowedValueFlags = {
|
|
56
|
+
agents: new Set(), model: new Set(["--agent"]), runtime: new Set(["--agent", "--model"]),
|
|
57
|
+
effort: new Set(["--agent"]), chats: new Set(["--agent"]), config: new Set(["--agent", "--chat"]),
|
|
58
|
+
};
|
|
59
|
+
const allowedBooleanFlags = {
|
|
60
|
+
agents: new Set(["--json"]), model: new Set(), runtime: new Set(), effort: new Set(), chats: new Set(), config: new Set(["--json"]),
|
|
61
|
+
};
|
|
62
|
+
const parsedValues = new Map();
|
|
63
|
+
const parsedBooleans = new Set();
|
|
64
|
+
const positionals = [];
|
|
65
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
66
|
+
const argument = rest[index];
|
|
67
|
+
if (!argument.startsWith("-")) {
|
|
68
|
+
positionals.push(argument);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const equals = argument.indexOf("=");
|
|
72
|
+
const flag = equals >= 0 ? argument.slice(0, equals) : argument;
|
|
73
|
+
if (allowedBooleanFlags[kind].has(flag)) {
|
|
74
|
+
if (equals >= 0)
|
|
75
|
+
die(`${flag} 不接受值`);
|
|
76
|
+
if (parsedBooleans.has(flag))
|
|
77
|
+
die(`${flag} 只能指定一次`);
|
|
78
|
+
parsedBooleans.add(flag);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!allowedValueFlags[kind].has(flag))
|
|
82
|
+
die(`不支持参数 ${flag}`);
|
|
83
|
+
const flagValue = equals >= 0 ? argument.slice(equals + 1) : rest[++index];
|
|
84
|
+
if (!flagValue || flagValue.startsWith("-"))
|
|
85
|
+
die(`${flag} 需要值`);
|
|
86
|
+
if (parsedValues.has(flag))
|
|
87
|
+
die(`${flag} 只能指定一次`);
|
|
88
|
+
parsedValues.set(flag, flagValue);
|
|
89
|
+
}
|
|
90
|
+
const flagAgent = parsedValues.get("--agent");
|
|
91
|
+
const flagModel = parsedValues.get("--model");
|
|
92
|
+
const flagChat = parsedValues.get("--chat");
|
|
93
|
+
const flagJson = parsedBooleans.has("--json");
|
|
94
|
+
const value = positionals[0];
|
|
95
|
+
function assertOnlyFlags(valueFlags, booleanFlags = []) {
|
|
96
|
+
const allowedValues = new Set(valueFlags);
|
|
97
|
+
const allowedBooleans = new Set(booleanFlags);
|
|
98
|
+
const unsupported = [
|
|
99
|
+
...[...parsedValues.keys()].filter((flag) => !allowedValues.has(flag)),
|
|
100
|
+
...[...parsedBooleans].filter((flag) => !allowedBooleans.has(flag)),
|
|
101
|
+
];
|
|
102
|
+
if (unsupported.length)
|
|
103
|
+
die(`当前操作不支持参数 ${unsupported.join(", ")}`);
|
|
104
|
+
}
|
|
105
|
+
if (kind === "agents") {
|
|
106
|
+
assertOnlyFlags([], ["--json"]);
|
|
107
|
+
if (positionals.length)
|
|
108
|
+
die("用法: larkin agents [--json]");
|
|
109
|
+
}
|
|
110
|
+
else if (kind === "model") {
|
|
111
|
+
assertOnlyFlags(["--agent"]);
|
|
112
|
+
if (positionals.length > 1)
|
|
113
|
+
die("用法: larkin model [<model>] [--agent <App ID>]");
|
|
114
|
+
}
|
|
115
|
+
else if (kind === "runtime") {
|
|
116
|
+
assertOnlyFlags(["--agent", "--model"]);
|
|
117
|
+
if (positionals.length > 1 || (!positionals.length && flagModel))
|
|
118
|
+
die("用法: larkin runtime [<runtime>] [--model <model>] [--agent <App ID>]");
|
|
119
|
+
}
|
|
120
|
+
else if (kind === "effort") {
|
|
121
|
+
assertOnlyFlags(["--agent"]);
|
|
122
|
+
if (positionals.length > 1)
|
|
123
|
+
die("用法: larkin effort [<level>|clear|default] [--agent <App ID>]");
|
|
124
|
+
}
|
|
125
|
+
else if (kind === "chats") {
|
|
126
|
+
assertOnlyFlags(["--agent"]);
|
|
127
|
+
if (![0, 2].includes(positionals.length))
|
|
128
|
+
die("用法: larkin chats [free|strict <oc_id>] [--agent <App ID>]");
|
|
129
|
+
}
|
|
130
|
+
else if (kind === "config") {
|
|
131
|
+
const [operation = "show", scope] = positionals;
|
|
132
|
+
if (operation === "show") {
|
|
133
|
+
assertOnlyFlags(["--agent", "--chat"], ["--json"]);
|
|
134
|
+
if (positionals.length > 1)
|
|
135
|
+
die("用法: larkin config show [--agent <App ID>] [--chat <oc_id>] [--json]");
|
|
136
|
+
}
|
|
137
|
+
else if (operation === "mention" && scope === "global") {
|
|
138
|
+
assertOnlyFlags([]);
|
|
139
|
+
if (positionals.length !== 3)
|
|
140
|
+
die("用法: larkin config mention global <require|free>");
|
|
141
|
+
}
|
|
142
|
+
else if (operation === "mention" && scope === "agent") {
|
|
143
|
+
assertOnlyFlags(["--agent"]);
|
|
144
|
+
if (positionals.length !== 3)
|
|
145
|
+
die("用法: larkin config mention agent <inherit|require|free> [--agent <App ID>]");
|
|
146
|
+
}
|
|
147
|
+
else if (operation === "mention" && scope === "chat") {
|
|
148
|
+
assertOnlyFlags(["--agent"]);
|
|
149
|
+
if (positionals.length !== 4)
|
|
150
|
+
die("用法: larkin config mention chat <oc_id> <inherit|require|free> [--agent <App ID>]");
|
|
151
|
+
}
|
|
152
|
+
else if (operation === "apply") {
|
|
153
|
+
assertOnlyFlags(["--agent"]);
|
|
154
|
+
if (positionals.length !== 1)
|
|
155
|
+
die("用法: larkin config apply [--agent <App ID>]");
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
die("config 只支持 show/mention/apply;运行 larkin config --help");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const loaded = (() => {
|
|
162
|
+
try {
|
|
163
|
+
return larkinConfig.loadConfig(process.env);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return die("无法加载 Larkin 配置;请检查配置格式和文件权限,或运行 larkin setup");
|
|
167
|
+
}
|
|
168
|
+
})();
|
|
169
|
+
const { configDir, file, config } = loaded;
|
|
170
|
+
if (!fs.existsSync(file))
|
|
171
|
+
die("未找到 Larkin 配置,请运行 larkin setup");
|
|
172
|
+
if (kind === "agents") {
|
|
173
|
+
const list = Object.values(config.agents || {});
|
|
174
|
+
const daemon = readProcessState(config.larkinHome).daemon;
|
|
175
|
+
const daemonView = {
|
|
176
|
+
owned: daemon.state === "owned",
|
|
177
|
+
pid: daemon.state === "owned" && Number.isSafeInteger(Number(daemon.pid)) ? Number(daemon.pid) : null,
|
|
178
|
+
started_at: daemon.state === "owned" && typeof daemon.startedAt === "string" ? daemon.startedAt : null,
|
|
179
|
+
};
|
|
180
|
+
if (!list.length) {
|
|
181
|
+
if (flagJson)
|
|
182
|
+
say(JSON.stringify({ version: 1, daemon: daemonView, agents: [] }, null, 2));
|
|
183
|
+
else
|
|
184
|
+
say("还没有配置任何 agent,先跑 larkin setup");
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
if (flagJson) {
|
|
188
|
+
const agents = list.map((agent) => {
|
|
189
|
+
let status = null;
|
|
190
|
+
try {
|
|
191
|
+
status = JSON.parse(fs.readFileSync(path.join(agent.stateDir, "status.json"), "utf8"));
|
|
192
|
+
}
|
|
193
|
+
catch { /* absent */ }
|
|
194
|
+
const effectiveModel = status?.session?.runtime === agent.runtime && status.session.model ? status.session.model : agent.model;
|
|
195
|
+
const commentCapabilities = safeDocumentCommentCapabilities(configDir, agent.feishuProfile);
|
|
196
|
+
const commentCapability = commentCapabilities.event;
|
|
197
|
+
const commentSubscription = commentCapabilities.subscription;
|
|
198
|
+
const commentDiagnostic = projectDocumentCommentDiagnostic(commentCapability, status);
|
|
199
|
+
return {
|
|
200
|
+
agent_id: agent.agentId,
|
|
201
|
+
name: agent.name,
|
|
202
|
+
runtime: agent.runtime,
|
|
203
|
+
model: effectiveModel,
|
|
204
|
+
document_comment: {
|
|
205
|
+
event: "drive.notice.comment_add_v1",
|
|
206
|
+
category: commentDiagnostic.category,
|
|
207
|
+
reason: commentDiagnostic.reason,
|
|
208
|
+
requested_at: commentCapability?.requestedAt || null,
|
|
209
|
+
reply_scope: "docs:document.comment:create",
|
|
210
|
+
reply_requested_at: commentCapabilities.reply?.requestedAt || null,
|
|
211
|
+
event_verified_at: status?.documentCommentEventAt || null,
|
|
212
|
+
accepted_at: status?.documentCommentLastAcceptedAt || null,
|
|
213
|
+
last_error: status?.documentCommentLastError || null,
|
|
214
|
+
last_error_at: status?.documentCommentLastErrorAt || null,
|
|
215
|
+
subscription: {
|
|
216
|
+
mode: commentSubscription.mode,
|
|
217
|
+
status: commentSubscription.status,
|
|
218
|
+
source: commentSubscription.source,
|
|
219
|
+
dimension: commentSubscription.dimension,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
...projectAgentReadiness({ agentId: agent.agentId, daemon, status }),
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
say(JSON.stringify({
|
|
226
|
+
version: 1,
|
|
227
|
+
daemon: daemonView,
|
|
228
|
+
agents,
|
|
229
|
+
}, null, 2));
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
say(`共 ${list.length} 个 agent(daemon=${daemon.running ? `运行中 pid=${daemon.pid} agents=${(daemon.agents || []).join(",")}` : "未运行"}):\n`);
|
|
233
|
+
for (const agent of list) {
|
|
234
|
+
let bot = null;
|
|
235
|
+
try {
|
|
236
|
+
bot = JSON.parse(fs.readFileSync(path.join(agent.stateDir, "bot-identity.json"), "utf8"));
|
|
237
|
+
}
|
|
238
|
+
catch { /* absent */ }
|
|
239
|
+
let status = null;
|
|
240
|
+
try {
|
|
241
|
+
status = JSON.parse(fs.readFileSync(path.join(agent.stateDir, "status.json"), "utf8"));
|
|
242
|
+
}
|
|
243
|
+
catch { /* absent */ }
|
|
244
|
+
const credentialFile = path.join(configDir, "bots", `${agent.feishuProfile}.json`);
|
|
245
|
+
const cred = fs.existsSync(credentialFile);
|
|
246
|
+
let callbackStatus = "missing";
|
|
247
|
+
let commentCapability = null;
|
|
248
|
+
let commentReplyCapability = null;
|
|
249
|
+
let commentSubscription = effectiveDocumentCommentSubscription(null);
|
|
250
|
+
if (cred) {
|
|
251
|
+
try {
|
|
252
|
+
callbackStatus = readCallbackCapability(configDir, agent.feishuProfile)?.status || "missing";
|
|
253
|
+
const capabilities = safeDocumentCommentCapabilities(configDir, agent.feishuProfile);
|
|
254
|
+
commentCapability = capabilities.event;
|
|
255
|
+
commentReplyCapability = capabilities.reply;
|
|
256
|
+
commentSubscription = capabilities.subscription;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
callbackStatus = "invalid";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const connected = daemonHasAgent(daemon, agent.agentId)
|
|
263
|
+
&& !!status?.connectedAt
|
|
264
|
+
&& Date.parse(status.connectedAt) >= Date.parse(String(daemon.startedAt || 0)) - 1000;
|
|
265
|
+
const inboundVerified = connected
|
|
266
|
+
&& !!status?.inboundVerifiedAt
|
|
267
|
+
&& Date.parse(status.inboundVerifiedAt) >= Date.parse(String(daemon.startedAt || 0));
|
|
268
|
+
const commentDiagnostic = projectDocumentCommentDiagnostic(commentCapability, status);
|
|
269
|
+
say(` ${agent.name}${agent.name === config.activeAgent ? " [active]" : ""}`);
|
|
270
|
+
const effectiveModel = status?.session?.runtime === agent.runtime && status.session.model ? status.session.model : agent.model;
|
|
271
|
+
const effectiveEffort = status?.session?.runtime === agent.runtime && status.session.reasoningEffort ? status.session.reasoningEffort : agent.effort;
|
|
272
|
+
say(` runtime=${agent.runtime} model=${effectiveModel}${effectiveModel !== agent.model ? ` stored=${agent.model}` : ""}${effectiveEffort ? ` effort=${effectiveEffort}` : ""}`);
|
|
273
|
+
if (status?.runtimeReadiness)
|
|
274
|
+
say(` runtime readiness=${status.runtimeReadiness.state || "incompatible"}${status.runtimeReadiness.reason ? `:${status.runtimeReadiness.reason}` : ""}${status.runtimeReadiness.nextAction ? `;下一步:${status.runtimeReadiness.nextAction}` : ""}`);
|
|
275
|
+
if (status?.runtimeReadiness?.executable)
|
|
276
|
+
say(` runtime executable=${status.runtimeReadiness.executable}${status.runtimeReadiness.version ? ` version=${status.runtimeReadiness.version}` : ""}`);
|
|
277
|
+
say(` bot=${bot ? `${bot.name}(${bot.open_id})` : "(未连接过,无身份缓存)"}`);
|
|
278
|
+
const reconnecting = connected && isChannelReconnecting(status);
|
|
279
|
+
say(` 连接=${connected ? `已建立(${status?.connectedVia || "channel"},${status?.connectedAt})${reconnecting ? "(ws 重连中)" : ""}` : "未建立"}`);
|
|
280
|
+
// 三态:已验证 / 未证实(连上但长时间零事件=疑似僵尸,host 会预防性重连)/ 尚未验证(连接还年轻)。
|
|
281
|
+
const silenceMin = connected && status?.connectedAt ? Math.floor((Date.now() - Date.parse(status.connectedAt)) / 60_000) : 0;
|
|
282
|
+
say(` 入站=${inboundVerified
|
|
283
|
+
? `本次运行已验证(${status?.inboundVerifiedAt})`
|
|
284
|
+
: connected && silenceMin >= 2
|
|
285
|
+
? `未证实:已连接 ${silenceMin} 分钟无任何入站事件(疑似僵尸连接,daemon 会自动预防性重连${status?.droughtReconnectAt ? `;上次重连 ${status.droughtReconnectAt}` : ""})`
|
|
286
|
+
: "本次运行尚未收到消息验证"}`);
|
|
287
|
+
say(` 凭证=${cred ? `bots/${agent.feishuProfile}.json` : "无(不能建立专属 channel 长连接)"} state=${agent.stateDir}`);
|
|
288
|
+
say(` 卡片回调=${callbackStatus === "verified-effective" ? "已通过真实 callback probe 验证" : `${callbackStatus}(业务交互卡片 fail-closed;运行 interaction callback-probe)`}`);
|
|
289
|
+
say(` 文档评论=${commentDiagnostic.category} reason=${commentDiagnostic.reason}${status?.documentCommentEventAt
|
|
290
|
+
? `;真实 comment event 已到达(${status.documentCommentEventAt})${status.documentCommentLastAcceptedAt ? `;最近入箱 ${status.documentCommentLastAcceptedAt}` : ""}`
|
|
291
|
+
: commentCapability ? ";event/scope 已请求,但配置是否发布及事件是否生效均未验证" : ";重新运行 larkin setup 并发布"}${status?.documentCommentLastErrorAt ? `;最近失败时间=${status.documentCommentLastErrorAt}` : ""}`);
|
|
292
|
+
say(` 评论订阅=${commentSubscription.mode}/${commentSubscription.status} source=${commentSubscription.source} dimension=${commentSubscription.dimension || "none"}${commentSubscription.mode === "subscribed" ? ";所有实际送达的支持评论都会唤醒 Agent" : ";仅 @Bot 评论进入 Inbox"}`);
|
|
293
|
+
say(` 评论回复权限=${commentReplyCapability ? `${commentReplyCapability.scope} requested-unverified` : "missing(重跑 larkin setup;回复将由平台拒绝)"}`);
|
|
294
|
+
const recent = Array.isArray(status?.recentErrors) ? status.recentErrors.at(-1) : null;
|
|
295
|
+
if (recent)
|
|
296
|
+
say(` 最近错误=${recent.message || recent.error || JSON.stringify(recent)}`);
|
|
297
|
+
}
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
300
|
+
const keys = Object.keys(config.agents || {});
|
|
301
|
+
if (!keys.length)
|
|
302
|
+
die("还没有配置任何 agent,先跑 larkin setup");
|
|
303
|
+
let runtimeAgentId = null;
|
|
304
|
+
try {
|
|
305
|
+
runtimeAgentId = larkinConfig.resolveRuntimeAuthority(process.env);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
die(error instanceof Error ? error.message : String(error));
|
|
309
|
+
}
|
|
310
|
+
if (runtimeAgentId && !config.agents[runtimeAgentId])
|
|
311
|
+
die(`Runtime Agent 身份不存在:${runtimeAgentId}`);
|
|
312
|
+
if (kind === "config") {
|
|
313
|
+
const [operation, scope, first, second] = positionals;
|
|
314
|
+
const selected = flagAgent || runtimeAgentId;
|
|
315
|
+
const authority = runtimeAgentId ? { kind: "agent", agentId: runtimeAgentId } : { kind: "user" };
|
|
316
|
+
const requireTarget = () => {
|
|
317
|
+
if (selected) {
|
|
318
|
+
if (!config.agents[selected])
|
|
319
|
+
die(`agent ${selected} 不存在;可用: ${keys.join(", ")}`);
|
|
320
|
+
return selected;
|
|
321
|
+
}
|
|
322
|
+
if (keys.length === 1)
|
|
323
|
+
return keys[0];
|
|
324
|
+
return die(`有 ${keys.length} 个 agent,修改必须用 --agent <App ID> 指定;可用: ${keys.join(", ")}`);
|
|
325
|
+
};
|
|
326
|
+
if (!operation || operation === "show") {
|
|
327
|
+
const view = larkinConfig.safeConfigView(config, selected || undefined, flagChat, larkinConfig.configApplyState(process.env, config));
|
|
328
|
+
if (flagJson)
|
|
329
|
+
say(JSON.stringify(view, null, 2));
|
|
330
|
+
else {
|
|
331
|
+
say(`全局群消息策略=${String(view.mentionPolicy)}(free 会唤醒所有继承它的 Agent)`);
|
|
332
|
+
for (const item of view.agents) {
|
|
333
|
+
const mention = item.mention;
|
|
334
|
+
const apply = item.apply;
|
|
335
|
+
say(`agent=${item.agentId} runtime=${item.runtime} model=${item.model} effort=${item.effort || "default"} mention=${mention.override} apply=${apply.applyState || "unknown"}`);
|
|
336
|
+
if (mention.chat)
|
|
337
|
+
say(` chat=${mention.chat.chatId} override=${mention.chat.override} effective=${mention.chat.effective} source=${mention.chat.source}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
process.exit(0);
|
|
341
|
+
}
|
|
342
|
+
if (operation === "mention") {
|
|
343
|
+
let mutation = null;
|
|
344
|
+
if (scope === "global") {
|
|
345
|
+
if (first !== "require" && first !== "free")
|
|
346
|
+
die("用法: larkin config mention global <require|free>");
|
|
347
|
+
mutation = { kind: "set-global-mention", value: first };
|
|
348
|
+
}
|
|
349
|
+
else if (scope === "agent") {
|
|
350
|
+
if (!["inherit", "require", "free"].includes(first || ""))
|
|
351
|
+
die("用法: larkin config mention agent <inherit|require|free> [--agent <App ID>]");
|
|
352
|
+
mutation = { kind: "set-agent-mention", agentId: requireTarget(), value: first };
|
|
353
|
+
}
|
|
354
|
+
else if (scope === "chat") {
|
|
355
|
+
if (!first?.startsWith("oc_") || !["inherit", "require", "free"].includes(second || ""))
|
|
356
|
+
die("用法: larkin config mention chat <oc_id> <inherit|require|free> [--agent <App ID>]");
|
|
357
|
+
mutation = { kind: "set-chat-mention", agentId: requireTarget(), chatId: first, value: second };
|
|
358
|
+
}
|
|
359
|
+
else
|
|
360
|
+
die("mention scope 只支持 global/agent/chat");
|
|
361
|
+
if (!mutation)
|
|
362
|
+
die("mention mutation 无效");
|
|
363
|
+
const result = larkinConfig.mutateConfig(process.env, mutation, authority);
|
|
364
|
+
say(JSON.stringify({ ok: true, revision: result.revision, persisted: result.persisted, applyState: result.applyState, changedScope: result.changedScope }, null, 2));
|
|
365
|
+
process.exit(0);
|
|
366
|
+
}
|
|
367
|
+
if (operation === "apply") {
|
|
368
|
+
const agentId = requireTarget();
|
|
369
|
+
const expectedSignature = larkinConfig.runtimeConfigSignature(config, agentId);
|
|
370
|
+
try {
|
|
371
|
+
const result = await requestAgentUpsert({ larkinHome: config.larkinHome, agentId });
|
|
372
|
+
if (!result.ok) {
|
|
373
|
+
const readiness = result.readiness;
|
|
374
|
+
throw new Error([result.error || "daemon 拒绝应用配置",
|
|
375
|
+
readiness?.reason ? `runtime=${readiness.state}: ${readiness.reason}` : null,
|
|
376
|
+
readiness?.nextAction ? `下一步:${readiness.nextAction}` : null].filter(Boolean).join(";"));
|
|
377
|
+
}
|
|
378
|
+
larkinConfig.markConfigApplied(process.env, agentId, expectedSignature);
|
|
379
|
+
say(JSON.stringify({ ok: true, agentId, applyState: "applied", result }, null, 2));
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
die(`配置已保存但未应用:${error instanceof Error ? error.message : String(error)}`);
|
|
383
|
+
}
|
|
384
|
+
process.exit(0);
|
|
385
|
+
}
|
|
386
|
+
die("config 只支持 show/mention/apply");
|
|
387
|
+
}
|
|
388
|
+
let key = flagAgent;
|
|
389
|
+
if (!key && runtimeAgentId)
|
|
390
|
+
key = runtimeAgentId;
|
|
391
|
+
if (key && !config.agents[key])
|
|
392
|
+
die(`agent ${key} 不存在;可用: ${keys.join(", ")}`);
|
|
393
|
+
if (!key && keys.length === 1)
|
|
394
|
+
key = keys[0];
|
|
395
|
+
if (!key) {
|
|
396
|
+
if (!value) {
|
|
397
|
+
say(`共 ${keys.length} 个 agent(用 --agent <appId> 查看/修改指定一个):`);
|
|
398
|
+
for (const candidate of keys) {
|
|
399
|
+
const item = config.agents[candidate];
|
|
400
|
+
say(` ${candidate}${candidate === config.activeAgent ? " [active]" : ""} runtime=${item.runtime} model=${item.model}`);
|
|
401
|
+
}
|
|
402
|
+
process.exit(0);
|
|
403
|
+
}
|
|
404
|
+
die(`有 ${keys.length} 个 agent,修改必须用 --agent <appId> 指定;可用: ${keys.join(", ")}`);
|
|
405
|
+
}
|
|
406
|
+
const selectedKey = key;
|
|
407
|
+
const agent = config.agents[selectedKey];
|
|
408
|
+
const catalog = larkinConfig.loadRuntimeModels();
|
|
409
|
+
if ((["model", "effort"].includes(kind || "") && agent.runtime === "codex") || (kind === "runtime" && value === "codex")) {
|
|
410
|
+
try {
|
|
411
|
+
const discovered = await discoverCodexModelCatalog({ cwd: String(agent.workspaceDir), env: process.env });
|
|
412
|
+
catalog.codex = [{ id: "default", label: `default: ${discovered.effectiveModel}` }, ...discovered.models];
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
die(`Codex 模型目录加载失败:${error instanceof Error ? error.message : String(error)}。请确认当前 codex CLI 支持 app-server model/list。`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if ((["model", "effort"].includes(kind || "") && agent.runtime === "claude") || (kind === "runtime" && value === "claude")) {
|
|
419
|
+
try {
|
|
420
|
+
const discovered = await discoverClaudeModelCatalog({ cwd: String(agent.workspaceDir), env: process.env });
|
|
421
|
+
catalog.claude = [{ id: "default", label: `default: ${discovered.effectiveModel}` }, ...discovered.models];
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
die(`Claude 模型目录加载失败:${error instanceof Error ? error.message : String(error)}。请确认当前 Claude Code 支持 list_models control request。`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
let piCatalog = null;
|
|
428
|
+
if (agent.runtime === "pi" || (kind === "runtime" && value === "pi")) {
|
|
429
|
+
try {
|
|
430
|
+
piCatalog = await discoverPiModelCatalog({
|
|
431
|
+
cwd: String(agent.workspaceDir),
|
|
432
|
+
...(process.env.PI_CODING_AGENT_DIR ? { agentDir: process.env.PI_CODING_AGENT_DIR } : {}),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
die(`Pi 模型目录加载失败:${error instanceof Error ? error.message : String(error)}。请先用官方 pi 登录或检查 PI_CODING_AGENT_DIR。`);
|
|
437
|
+
}
|
|
438
|
+
if (!piCatalog)
|
|
439
|
+
die("Pi 模型目录加载失败");
|
|
440
|
+
const loadedPiCatalog = piCatalog;
|
|
441
|
+
for (const diagnostic of loadedPiCatalog.diagnostics)
|
|
442
|
+
console.error(`Pi diagnostic: ${diagnostic}`);
|
|
443
|
+
catalog.pi = [{ id: "default", label: `default: ${loadedPiCatalog.effectiveModel}` }, ...loadedPiCatalog.models];
|
|
444
|
+
}
|
|
445
|
+
function listModels(runtime, current) {
|
|
446
|
+
const items = catalog[runtime];
|
|
447
|
+
if (!items) {
|
|
448
|
+
say(` (无 ${runtime} 的目录数据)`);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
for (const model of items) {
|
|
452
|
+
const marks = [model.id === current && "← 当前"].filter(Boolean).join(",");
|
|
453
|
+
const levels = model.supportedReasoningEfforts?.length ? ` thinking=${model.supportedReasoningEfforts.join("/")}` : "";
|
|
454
|
+
say(` ${model.id.padEnd(32)} ${model.label || ""}${levels}${marks ? ` [${marks}]` : ""}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function writeAndHint(mutation) {
|
|
458
|
+
const authority = runtimeAgentId ? { kind: "agent", agentId: runtimeAgentId } : { kind: "user" };
|
|
459
|
+
const result = larkinConfig.mutateConfig(process.env, mutation, authority);
|
|
460
|
+
say("\n✓ 已持久化配置");
|
|
461
|
+
say(` revision=${result.revision} applyState=${result.applyState}`);
|
|
462
|
+
if (result.applyState === "saved_not_applied")
|
|
463
|
+
say(" 运行 larkin config apply --agent <App ID> 后应用到新 Runtime session");
|
|
464
|
+
}
|
|
465
|
+
function effortChoicesFor(runtime, model) {
|
|
466
|
+
const entry = catalog[runtime]?.find((candidate) => candidate.id === model);
|
|
467
|
+
if (!entry?.supportedReasoningEfforts?.length)
|
|
468
|
+
return null;
|
|
469
|
+
return { list: entry.supportedReasoningEfforts, note: "模型声明", def: entry.defaultReasoningEffort || null };
|
|
470
|
+
}
|
|
471
|
+
function dropEffortIfInvalid(next) {
|
|
472
|
+
if (!next.effort)
|
|
473
|
+
return next;
|
|
474
|
+
const choices = effortChoicesFor(next.runtime, next.model);
|
|
475
|
+
if (choices?.list.includes(next.effort))
|
|
476
|
+
return next;
|
|
477
|
+
say(choices
|
|
478
|
+
? `effort=${next.effort} 不适用于 ${next.runtime}/${next.model},已清除(恢复 runtime 默认;可用 larkin effort 重设)`
|
|
479
|
+
: `模型 ${next.runtime}/${next.model} 未显式声明 supportedReasoningEfforts,原 effort=${next.effort} 已清除`);
|
|
480
|
+
const { effort: _effort, ...withoutEffort } = next;
|
|
481
|
+
return withoutEffort;
|
|
482
|
+
}
|
|
483
|
+
if (kind === "model") {
|
|
484
|
+
const runtime = agent.runtime;
|
|
485
|
+
if (!value) {
|
|
486
|
+
say(`agent=${selectedKey} runtime=${runtime} model=${agent.model}`);
|
|
487
|
+
say(`\n${runtime} 可选模型:`);
|
|
488
|
+
listModels(runtime, agent.model);
|
|
489
|
+
say("\n切换: larkin model <id>");
|
|
490
|
+
process.exit(0);
|
|
491
|
+
}
|
|
492
|
+
const items = catalog[runtime];
|
|
493
|
+
if (items && !items.some((model) => model.id === value)) {
|
|
494
|
+
console.error(`✗ "${value}" 不是 ${runtime} 的合法模型。合法值:`);
|
|
495
|
+
for (const model of items)
|
|
496
|
+
console.error(` ${model.id}`);
|
|
497
|
+
console.error(" (Larkin 要求使用目录中的精确模型 ID,避免静默选择其它模型)");
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
if (value === agent.model) {
|
|
501
|
+
say(`model 已经是 ${value},无需修改`);
|
|
502
|
+
process.exit(0);
|
|
503
|
+
}
|
|
504
|
+
writeAndHint({ kind: "set-agent-model", agentId: selectedKey, model: value });
|
|
505
|
+
}
|
|
506
|
+
else if (kind === "effort") {
|
|
507
|
+
if (value === "clear" || value === "default") {
|
|
508
|
+
if (!agent.effort) {
|
|
509
|
+
say("effort 本来就未设置(runtime 默认),无需修改");
|
|
510
|
+
process.exit(0);
|
|
511
|
+
}
|
|
512
|
+
say(`effort 已清除: ${agent.effort} → runtime 默认`);
|
|
513
|
+
writeAndHint({ kind: "set-agent-effort", agentId: selectedKey, effort: null });
|
|
514
|
+
process.exit(0);
|
|
515
|
+
}
|
|
516
|
+
const choices = effortChoicesFor(agent.runtime, agent.model);
|
|
517
|
+
if (!choices) {
|
|
518
|
+
say(`agent=${selectedKey} runtime=${agent.runtime} model=${agent.model}`);
|
|
519
|
+
say(`模型 ${agent.runtime}/${agent.model} 未显式声明 supportedReasoningEfforts,不能设置 effort。`);
|
|
520
|
+
process.exit(value ? 1 : 0);
|
|
521
|
+
}
|
|
522
|
+
const { list, note, def } = choices;
|
|
523
|
+
if (!value) {
|
|
524
|
+
say(`agent=${selectedKey} runtime=${agent.runtime} model=${agent.model}`);
|
|
525
|
+
say(`effort=${agent.effort || `(未设置,runtime 默认${def ? `,该模型默认 ${def}` : ""})`}`);
|
|
526
|
+
say(`\n${agent.model} 可选档位(${note}):`);
|
|
527
|
+
for (const level of list)
|
|
528
|
+
say(` ${level.padEnd(10)}${level === agent.effort ? " [← 当前]" : ""}${level === def ? " [模型默认]" : ""}`);
|
|
529
|
+
say("\n设置: larkin effort <level>;恢复默认: larkin effort clear");
|
|
530
|
+
process.exit(0);
|
|
531
|
+
}
|
|
532
|
+
if (!list.includes(value)) {
|
|
533
|
+
console.error(`✗ "${value}" 不是 ${agent.runtime}/${agent.model} 的合法档位。合法值: ${list.join(", ")}`);
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
if (value === agent.effort) {
|
|
537
|
+
say(`effort 已经是 ${value},无需修改`);
|
|
538
|
+
process.exit(0);
|
|
539
|
+
}
|
|
540
|
+
writeAndHint({ kind: "set-agent-effort", agentId: selectedKey, effort: value });
|
|
541
|
+
}
|
|
542
|
+
else if (kind === "chats") {
|
|
543
|
+
const [action, chatArg] = positionals;
|
|
544
|
+
const overrides = { ...(agent.chatMentionPolicies || {}) };
|
|
545
|
+
if (!action) {
|
|
546
|
+
const known = new Set(Object.keys(overrides));
|
|
547
|
+
try {
|
|
548
|
+
const map = JSON.parse(fs.readFileSync(path.join(agent.stateDir, "feishu-map.json"), "utf8"));
|
|
549
|
+
for (const candidate of Object.values(map))
|
|
550
|
+
if (typeof candidate === "string" && candidate.startsWith("oc_"))
|
|
551
|
+
known.add(candidate);
|
|
552
|
+
}
|
|
553
|
+
catch { /* no mapping yet */ }
|
|
554
|
+
const names = {};
|
|
555
|
+
try {
|
|
556
|
+
if (!agent.larkConfigDir)
|
|
557
|
+
throw new Error("缺少 lark-cli 隔离目录");
|
|
558
|
+
fs.mkdirSync(agent.larkConfigDir, { recursive: true, mode: 0o700 });
|
|
559
|
+
const managed = managedOfficialLarkCli(agent, process.env);
|
|
560
|
+
const result = spawnSync(managed.command.command, [...managed.command.argsPrefix, "im", "+chat-list", "--json"], {
|
|
561
|
+
encoding: "utf8", timeout: 15000, env: managed.env,
|
|
562
|
+
});
|
|
563
|
+
const parsed = JSON.parse(result.stdout);
|
|
564
|
+
for (const chat of parsed.data?.chats || [])
|
|
565
|
+
names[chat.chat_id] = chat.name || "";
|
|
566
|
+
}
|
|
567
|
+
catch { /* group names are optional */ }
|
|
568
|
+
say(`agent=${selectedKey} 已知群与触发方式(p2p 私聊始终触发,不在此列):`);
|
|
569
|
+
if (!known.size)
|
|
570
|
+
say(" (还没有已知群——bot 收到过消息的群才会出现在这里)");
|
|
571
|
+
for (const id of known) {
|
|
572
|
+
const resolved = larkinConfig.resolveMentionPolicy(config, selectedKey, id);
|
|
573
|
+
say(` ${id} ${(names[id] || "(无名:私聊或已退出的群)").padEnd(16)} override=${overrides[id] || "inherit"} effective=${resolved.effective} source=${resolved.source}`);
|
|
574
|
+
}
|
|
575
|
+
say("\n免@: larkin chats free <oc_id>;恢复需@: larkin chats strict <oc_id>");
|
|
576
|
+
process.exit(0);
|
|
577
|
+
}
|
|
578
|
+
if (!chatArg || !chatArg.startsWith("oc_"))
|
|
579
|
+
die("用法: larkin chats [free|strict] <oc_群id>(群 id 可先用 larkin chats 查看)");
|
|
580
|
+
if (action === "free") {
|
|
581
|
+
if (overrides[chatArg] === "free") {
|
|
582
|
+
say(`该群已是免@: ${chatArg}`);
|
|
583
|
+
process.exit(0);
|
|
584
|
+
}
|
|
585
|
+
say(`✓ 免@已开: ${chatArg}(该群内不@也会触发)`);
|
|
586
|
+
}
|
|
587
|
+
else if (action === "strict") {
|
|
588
|
+
if (overrides[chatArg] === "require") {
|
|
589
|
+
say(`该群本来就需@: ${chatArg}`);
|
|
590
|
+
process.exit(0);
|
|
591
|
+
}
|
|
592
|
+
say(`✓ 已恢复需@: ${chatArg}`);
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
die(`未知动作 "${action}";用 free(免@)或 strict(需@)`);
|
|
596
|
+
}
|
|
597
|
+
writeAndHint({ kind: "set-chat-mention", agentId: selectedKey, chatId: chatArg, value: action === "free" ? "free" : "require" });
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
if (!value) {
|
|
601
|
+
say(`agent=${selectedKey} runtime=${agent.runtime} model=${agent.model}`);
|
|
602
|
+
say("\n可选 runtime:");
|
|
603
|
+
for (const runtime of Object.keys(catalog)) {
|
|
604
|
+
say(` ${runtime.padEnd(12)}${runtime === agent.runtime ? " [← 当前]" : ""} 默认模型=${larkinConfig.defaultModelFor(runtime)}`);
|
|
605
|
+
}
|
|
606
|
+
say("\n切换: larkin runtime <id> [--model <id>]");
|
|
607
|
+
process.exit(0);
|
|
608
|
+
}
|
|
609
|
+
if (!catalog[value]) {
|
|
610
|
+
console.error(`✗ "${value}" 不是合法 runtime。合法值: ${Object.keys(catalog).join(", ")}`);
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
let model = flagModel;
|
|
614
|
+
if (model) {
|
|
615
|
+
const items = catalog[value];
|
|
616
|
+
if (!items.some((candidate) => candidate.id === model)) {
|
|
617
|
+
console.error(`✗ "${model}" 不是 ${value} 的合法模型。合法值:`);
|
|
618
|
+
for (const item of items)
|
|
619
|
+
console.error(` ${item.id}`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
model = larkinConfig.defaultModelFor(value);
|
|
625
|
+
if (value !== agent.runtime)
|
|
626
|
+
say(`model 随 runtime 重置为默认: ${agent.model} → ${model}(可用 --model 显式指定)`);
|
|
627
|
+
}
|
|
628
|
+
if (value === agent.runtime && model === agent.model) {
|
|
629
|
+
say(`runtime 已经是 ${value},无需修改`);
|
|
630
|
+
process.exit(0);
|
|
631
|
+
}
|
|
632
|
+
writeAndHint({ kind: "set-agent-runtime", agentId: selectedKey, runtime: value, model });
|
|
633
|
+
}
|