larkin 0.5.2 → 0.5.4

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.
@@ -36,12 +36,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.toUserRuntime = exports.runtimeOptionTarget = exports.runtimeOptionOf = exports.isUserRuntime = exports.isAdapterRuntime = exports.fromUserRuntime = exports.RUNTIME_OPTIONS = void 0;
39
+ exports.MAX_INBOX_AUDIT_INTERVAL_MS = exports.MIN_INBOX_AUDIT_INTERVAL_MS = exports.DEFAULT_INBOX_AUDIT_INTERVAL_MS = exports.toUserRuntime = exports.runtimeOptionTarget = exports.runtimeOptionOf = exports.isUserRuntime = exports.isAdapterRuntime = exports.fromUserRuntime = exports.RUNTIME_OPTIONS = void 0;
40
40
  exports.loadRuntimeModels = loadRuntimeModels;
41
41
  exports.defaultModelFor = defaultModelFor;
42
42
  exports.resolveConfigDir = resolveConfigDir;
43
43
  exports.resolveLarkConfigDir = resolveLarkConfigDir;
44
44
  exports.resolveRuntimeAuthority = resolveRuntimeAuthority;
45
+ exports.assertInboxAuditInterval = assertInboxAuditInterval;
46
+ exports.parseInboxAuditInterval = parseInboxAuditInterval;
47
+ exports.formatInboxAuditInterval = formatInboxAuditInterval;
48
+ exports.inboxAuditMutationFromCli = inboxAuditMutationFromCli;
45
49
  exports.hydrateAgent = hydrateAgent;
46
50
  exports.normalizeConfig = normalizeConfig;
47
51
  exports.assertPrivateConfigMetadata = assertPrivateConfigMetadata;
@@ -50,6 +54,7 @@ exports.selectAgent = selectAgent;
50
54
  exports.toStored = toStored;
51
55
  exports.resolveMentionPolicy = resolveMentionPolicy;
52
56
  exports.resolveAgentGlobalMentionPolicy = resolveAgentGlobalMentionPolicy;
57
+ exports.resolveInboxAuditSchedule = resolveInboxAuditSchedule;
53
58
  exports.runtimeConfigSignature = runtimeConfigSignature;
54
59
  exports.configApplyState = configApplyState;
55
60
  exports.markConfigApplied = markConfigApplied;
@@ -76,11 +81,16 @@ Object.defineProperty(exports, "isUserRuntime", { enumerable: true, get: functio
76
81
  Object.defineProperty(exports, "runtimeOptionOf", { enumerable: true, get: function () { return user_runtime_js_2.runtimeOptionOf; } });
77
82
  Object.defineProperty(exports, "runtimeOptionTarget", { enumerable: true, get: function () { return user_runtime_js_2.runtimeOptionTarget; } });
78
83
  Object.defineProperty(exports, "toUserRuntime", { enumerable: true, get: function () { return user_runtime_js_2.toUserRuntime; } });
84
+ exports.DEFAULT_INBOX_AUDIT_INTERVAL_MS = 15 * 60_000;
85
+ exports.MIN_INBOX_AUDIT_INTERVAL_MS = 60_000;
86
+ exports.MAX_INBOX_AUDIT_INTERVAL_MS = 24 * 60 * 60_000;
79
87
  const TOP_FIELDS_V3 = new Set(["version", "serverId", "activeAgent", "agents"]);
80
88
  const TOP_FIELDS_V4 = new Set(["version", "serverId", "mentionPolicy", "activeAgent", "agents"]);
89
+ const OPTIONAL_TOP_FIELDS_V4 = new Set(["inboxAudit"]);
81
90
  const AGENT_FIELDS_V3 = new Set(["runtime", "model", "effort", "noMentionChats", "createdAt"]);
82
- const AGENT_FIELDS_V4 = new Set(["runtime", "model", "effort", "mentionPolicy", "chatMentionPolicies", "createdAt"]);
91
+ const AGENT_FIELDS_V4 = new Set(["runtime", "model", "effort", "mentionPolicy", "chatMentionPolicies", "inboxAudit", "createdAt"]);
83
92
  const LEGACY_AGENT_FIELDS = new Set(["piDistribution", "runtimeOption"]);
93
+ const INBOX_AUDIT_FIELDS = new Set(["enabled", "intervalMs"]);
84
94
  const APP_ID = /^cli_[A-Za-z0-9]+$/;
85
95
  const CHAT_ID = /^oc_[A-Za-z0-9_-]+$/;
86
96
  const PI_EFFORTS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
@@ -144,6 +154,85 @@ function assertPolicy(value, label) {
144
154
  if (value !== "require" && value !== "free")
145
155
  throw new Error(`${label} 只允许 require/free`);
146
156
  }
157
+ function assertInboxAuditInterval(value, label = "inbox-audit interval") {
158
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < exports.MIN_INBOX_AUDIT_INTERVAL_MS || value > exports.MAX_INBOX_AUDIT_INTERVAL_MS) {
159
+ throw new Error(`${label} 必须是 ${exports.MIN_INBOX_AUDIT_INTERVAL_MS}–${exports.MAX_INBOX_AUDIT_INTERVAL_MS} 毫秒`);
160
+ }
161
+ }
162
+ function parseInboxAuditInterval(value) {
163
+ if (typeof value !== "string" || !value)
164
+ throw new Error("inbox-audit interval 不能为空");
165
+ let milliseconds;
166
+ if (/^[1-9]\d*$/.test(value))
167
+ milliseconds = Number(value);
168
+ else {
169
+ const match = /^([1-9]\d*)(m|h)$/.exec(value);
170
+ if (!match)
171
+ throw new Error("inbox-audit interval 只允许 15m/1h 或 60000–86400000 毫秒");
172
+ milliseconds = Number(match[1]) * (match[2] === "h" ? 3_600_000 : 60_000);
173
+ }
174
+ assertInboxAuditInterval(milliseconds);
175
+ return milliseconds;
176
+ }
177
+ function formatInboxAuditInterval(intervalMs) {
178
+ if (intervalMs % 3_600_000 === 0)
179
+ return `${intervalMs / 3_600_000}h`;
180
+ if (intervalMs % 60_000 === 0)
181
+ return `${intervalMs / 60_000}m`;
182
+ return `${intervalMs}ms`;
183
+ }
184
+ function parseStoredInboxAudit(value, label) {
185
+ if (!isPlainObject(value))
186
+ throw new Error(`${label} 必须是普通 object`);
187
+ assertAllowedFields(value, INBOX_AUDIT_FIELDS, label);
188
+ const settings = {};
189
+ if (Object.hasOwn(value, "enabled")) {
190
+ if (typeof value.enabled !== "boolean")
191
+ throw new Error(`${label}.enabled 必须是 boolean`);
192
+ settings.enabled = value.enabled;
193
+ }
194
+ if (Object.hasOwn(value, "intervalMs")) {
195
+ assertInboxAuditInterval(value.intervalMs, `${label}.intervalMs`);
196
+ settings.intervalMs = value.intervalMs;
197
+ }
198
+ return settings;
199
+ }
200
+ function resolvedInboxAudit(settings) {
201
+ return {
202
+ enabled: settings?.enabled ?? false,
203
+ intervalMs: settings?.intervalMs ?? exports.DEFAULT_INBOX_AUDIT_INTERVAL_MS,
204
+ };
205
+ }
206
+ function inboxAuditMutationFromCli(input) {
207
+ if (input.scope === "global") {
208
+ if (input.enabled !== "on" && input.enabled !== "off") {
209
+ throw new Error("用法: larkin config inbox-audit global <on|off> [--interval <15m|1h>]");
210
+ }
211
+ const mutation = {
212
+ kind: "set-global-inbox-audit", enabled: input.enabled === "on",
213
+ };
214
+ if (input.interval !== undefined) {
215
+ if (input.interval === "inherit")
216
+ throw new Error("全局 inbox-audit 不支持 interval=inherit");
217
+ mutation.intervalMs = parseInboxAuditInterval(input.interval);
218
+ }
219
+ return mutation;
220
+ }
221
+ if (input.scope === "agent") {
222
+ if (input.enabled !== "on" && input.enabled !== "off" && input.enabled !== "inherit") {
223
+ throw new Error("用法: larkin config inbox-audit agent <inherit|on|off> [--agent <App ID>] [--interval <15m|inherit>]");
224
+ }
225
+ const mutation = {
226
+ kind: "set-agent-inbox-audit", agentId: input.agentId,
227
+ enabled: input.enabled === "inherit" ? "inherit" : input.enabled === "on",
228
+ };
229
+ if (input.interval !== undefined) {
230
+ mutation.intervalMs = input.interval === "inherit" ? "inherit" : parseInboxAuditInterval(input.interval);
231
+ }
232
+ return mutation;
233
+ }
234
+ throw new Error("inbox-audit scope 只支持 global/agent");
235
+ }
147
236
  function assertModel(runtime, model) {
148
237
  if (model === "default")
149
238
  return;
@@ -211,6 +300,8 @@ function validateStoredAgent(key, agent, version) {
211
300
  }
212
301
  if (version === 4 && Object.hasOwn(agent, "mentionPolicy"))
213
302
  assertPolicy(agent.mentionPolicy, `Agent ${key}.mentionPolicy`);
303
+ if (version === 4 && Object.hasOwn(agent, "inboxAudit"))
304
+ parseStoredInboxAudit(agent.inboxAudit, `Agent ${key}.inboxAudit`);
214
305
  if (version === 4 && Object.hasOwn(agent, "chatMentionPolicies")) {
215
306
  if (!isPlainObject(agent.chatMentionPolicies))
216
307
  throw new Error(`Agent ${key}.chatMentionPolicies 必须是普通 object`);
@@ -239,6 +330,7 @@ function hydratedStoredAgent(key, agent, version) {
239
330
  model: agent.model,
240
331
  ...(typeof agent.effort === "string" ? { effort: agent.effort } : {}),
241
332
  ...(version === 4 && (agent.mentionPolicy === "require" || agent.mentionPolicy === "free") ? { mentionPolicy: agent.mentionPolicy } : {}),
333
+ ...(version === 4 && Object.hasOwn(agent, "inboxAudit") ? { inboxAudit: parseStoredInboxAudit(agent.inboxAudit, `Agent ${key}.inboxAudit`) } : {}),
242
334
  ...(Object.keys(chatMentionPolicies).length ? { chatMentionPolicies, noMentionChats: Object.entries(chatMentionPolicies).filter(([, policy]) => policy === "free").map(([chatId]) => chatId) } : {}),
243
335
  ...(typeof agent.createdAt === "string" ? { createdAt: agent.createdAt } : {}),
244
336
  };
@@ -252,6 +344,7 @@ function hydrateAgent(key, agent, configDir) {
252
344
  larkConfigDir: path.join(layout.agentStateDir(key), "lark-cli-config"),
253
345
  ...(agent.effort ? { effort: agent.effort } : {}),
254
346
  ...(agent.mentionPolicy ? { mentionPolicy: agent.mentionPolicy } : {}),
347
+ ...(agent.inboxAudit ? { inboxAudit: { ...agent.inboxAudit } } : {}),
255
348
  ...(agent.chatMentionPolicies ? { chatMentionPolicies: { ...agent.chatMentionPolicies } } : {}),
256
349
  ...(agent.noMentionChats ? { noMentionChats: [...agent.noMentionChats] } : {}),
257
350
  ...(agent.createdAt ? { createdAt: agent.createdAt } : {}),
@@ -270,7 +363,7 @@ function normalizeConfig(raw, configDir, { mint } = {}) {
270
363
  if (version !== 3 && version !== 4)
271
364
  throw new Error("不支持该配置格式:larkin 只接受 version=3/4");
272
365
  const topFields = version === 3 ? TOP_FIELDS_V3 : TOP_FIELDS_V4;
273
- assertAllowedFields(raw, topFields, "config");
366
+ assertAllowedFields(raw, version === 4 ? new Set([...TOP_FIELDS_V4, ...OPTIONAL_TOP_FIELDS_V4]) : topFields, "config");
274
367
  for (const field of topFields)
275
368
  if (!Object.hasOwn(raw, field))
276
369
  throw new Error(`config 缺少必需字段 ${field}`);
@@ -289,8 +382,12 @@ function normalizeConfig(raw, configDir, { mint } = {}) {
289
382
  const agents = {};
290
383
  for (const [key, agent] of Object.entries(raw.agents))
291
384
  agents[key] = hydrateAgent(key, hydratedStoredAgent(key, agent, version), layout.root);
385
+ const inboxAudit = version === 4 && Object.hasOwn(raw, "inboxAudit")
386
+ ? resolvedInboxAudit(parseStoredInboxAudit(raw.inboxAudit, "config.inboxAudit"))
387
+ : undefined;
292
388
  return {
293
389
  version: 4, serverId: raw.serverId, mentionPolicy: version === 4 ? raw.mentionPolicy : "require",
390
+ ...(inboxAudit ? { inboxAudit } : {}),
294
391
  configDir: layout.configDir, larkinHome: layout.larkinHome, larkConfigDir: resolveLarkConfigDir({}, layout.root), activeAgent: raw.activeAgent, agents,
295
392
  };
296
393
  }
@@ -516,7 +613,11 @@ function selectAgent(config, env = process.env) {
516
613
  return agents[config.activeAgent];
517
614
  }
518
615
  function toStored(config) {
519
- const out = { version: 4, serverId: config.serverId, mentionPolicy: config.mentionPolicy, activeAgent: config.activeAgent, agents: {} };
616
+ const out = {
617
+ version: 4, serverId: config.serverId, mentionPolicy: config.mentionPolicy, activeAgent: config.activeAgent, agents: {},
618
+ };
619
+ if (config.inboxAudit)
620
+ out.inboxAudit = { enabled: config.inboxAudit.enabled, intervalMs: config.inboxAudit.intervalMs };
520
621
  for (const [key, agent] of Object.entries(config.agents || {})) {
521
622
  const stored = { runtime: agent.runtime, model: agent.model };
522
623
  if (typeof agent.effort === "string" && agent.effort)
@@ -525,6 +626,9 @@ function toStored(config) {
525
626
  stored.mentionPolicy = agent.mentionPolicy;
526
627
  if (agent.chatMentionPolicies && Object.keys(agent.chatMentionPolicies).length)
527
628
  stored.chatMentionPolicies = { ...agent.chatMentionPolicies };
629
+ if (agent.inboxAudit && (agent.inboxAudit.enabled !== undefined || agent.inboxAudit.intervalMs !== undefined)) {
630
+ stored.inboxAudit = { ...agent.inboxAudit };
631
+ }
528
632
  if (typeof agent.createdAt === "string" && agent.createdAt)
529
633
  stored.createdAt = agent.createdAt;
530
634
  out.agents[key] = stored;
@@ -551,6 +655,20 @@ function resolveAgentGlobalMentionPolicy(config, agentId) {
551
655
  return { effective: agent.mentionPolicy, source: "agent" };
552
656
  return { effective: config.mentionPolicy, source: "global" };
553
657
  }
658
+ function resolveInboxAuditSchedule(config, agentId) {
659
+ const agent = config.agents[agentId];
660
+ if (!agent)
661
+ throw new Error(`Agent 不存在:${agentId}`);
662
+ const enabled = agent.inboxAudit?.enabled ?? config.inboxAudit?.enabled ?? false;
663
+ const intervalMs = agent.inboxAudit?.intervalMs ?? config.inboxAudit?.intervalMs ?? exports.DEFAULT_INBOX_AUDIT_INTERVAL_MS;
664
+ assertInboxAuditInterval(intervalMs);
665
+ return {
666
+ enabled,
667
+ intervalMs,
668
+ enabledSource: agent.inboxAudit?.enabled !== undefined ? "agent" : config.inboxAudit ? "global" : "default",
669
+ intervalSource: agent.inboxAudit?.intervalMs !== undefined ? "agent" : config.inboxAudit?.intervalMs !== undefined ? "global" : "default",
670
+ };
671
+ }
554
672
  function revision(bytes) {
555
673
  return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
556
674
  }
@@ -563,6 +681,7 @@ function agentSignature(config, agentId) {
563
681
  schema: 2,
564
682
  runtime: agent.runtime, model: agent.model, effort: agent.effort ?? null,
565
683
  globalMentionPolicy: config.mentionPolicy, agentMentionPolicy: agent.mentionPolicy ?? null, chatMentionPolicies: chats,
684
+ globalInboxAudit: config.inboxAudit ?? null, agentInboxAudit: agent.inboxAudit ?? null,
566
685
  }));
567
686
  }
568
687
  function runtimeConfigSignature(config, agentId) {
@@ -700,6 +819,19 @@ function applyMutation(config, mutation) {
700
819
  config.mentionPolicy = mutation.value;
701
820
  return { scope: "global", runtimeChange: false, affectedAgentIds: Object.keys(config.agents) };
702
821
  }
822
+ if (mutation.kind === "set-global-inbox-audit") {
823
+ if (mutation.enabled === undefined && mutation.intervalMs === undefined)
824
+ throw new Error("inbox-audit 全局变更必须包含 enabled 或 interval");
825
+ const current = resolvedInboxAudit(config.inboxAudit);
826
+ if (mutation.enabled !== undefined)
827
+ current.enabled = mutation.enabled;
828
+ if (mutation.intervalMs !== undefined) {
829
+ assertInboxAuditInterval(mutation.intervalMs, "全局 inbox-audit interval");
830
+ current.intervalMs = mutation.intervalMs;
831
+ }
832
+ config.inboxAudit = current;
833
+ return { scope: "global", runtimeChange: false, affectedAgentIds: Object.keys(config.agents) };
834
+ }
703
835
  if (!APP_ID.test(mutation.agentId) || !config.agents[mutation.agentId])
704
836
  throw new Error(`Agent 不存在:${mutation.agentId}`);
705
837
  const agent = config.agents[mutation.agentId];
@@ -712,6 +844,26 @@ function applyMutation(config, mutation) {
712
844
  }
713
845
  return { scope: "agent", agentId: mutation.agentId, runtimeChange: false, affectedAgentIds: [mutation.agentId] };
714
846
  }
847
+ if (mutation.kind === "set-agent-inbox-audit") {
848
+ if (mutation.enabled === undefined && mutation.intervalMs === undefined)
849
+ throw new Error("inbox-audit Agent 变更必须包含 enabled 或 interval");
850
+ const current = { ...(agent.inboxAudit || {}) };
851
+ if (mutation.enabled === "inherit")
852
+ delete current.enabled;
853
+ else if (mutation.enabled !== undefined)
854
+ current.enabled = mutation.enabled;
855
+ if (mutation.intervalMs === "inherit")
856
+ delete current.intervalMs;
857
+ else if (mutation.intervalMs !== undefined) {
858
+ assertInboxAuditInterval(mutation.intervalMs, "Agent inbox-audit interval");
859
+ current.intervalMs = mutation.intervalMs;
860
+ }
861
+ if (current.enabled === undefined && current.intervalMs === undefined)
862
+ delete agent.inboxAudit;
863
+ else
864
+ agent.inboxAudit = current;
865
+ return { scope: "agent", agentId: mutation.agentId, runtimeChange: false, affectedAgentIds: [mutation.agentId] };
866
+ }
715
867
  if (mutation.kind === "set-chat-mention") {
716
868
  if (!CHAT_ID.test(mutation.chatId))
717
869
  throw new Error(`群 ID 格式无效:${mutation.chatId}`);
@@ -1198,8 +1350,24 @@ function safeConfigView(config, onlyAgentId, chatId, applyState) {
1198
1350
  source: agent.mentionPolicy ? "agent" : "global",
1199
1351
  ...(chatId ? { chat: { chatId, override: agent.chatMentionPolicies?.[chatId] ?? "inherit", ...resolveMentionPolicy(config, agentId, chatId) } } : {}),
1200
1352
  },
1353
+ inboxAudit: (() => {
1354
+ const resolved = resolveInboxAuditSchedule(config, agentId);
1355
+ return {
1356
+ override: {
1357
+ enabled: agent.inboxAudit?.enabled === undefined ? "inherit" : agent.inboxAudit.enabled ? "on" : "off",
1358
+ intervalMs: agent.inboxAudit?.intervalMs ?? "inherit",
1359
+ },
1360
+ effective: { enabled: resolved.enabled, intervalMs: resolved.intervalMs },
1361
+ source: { enabled: resolved.enabledSource, intervalMs: resolved.intervalSource },
1362
+ };
1363
+ })(),
1201
1364
  chatMentionPolicies: { ...(agent.chatMentionPolicies || {}) },
1202
1365
  apply: applyState?.agents?.[agentId] ?? { applyState: "unknown" },
1203
1366
  }));
1204
- return { version: 4, mentionPolicy: config.mentionPolicy, persistedRevision: applyState?.persistedRevision ?? "unknown", agents };
1367
+ const inboxAudit = resolvedInboxAudit(config.inboxAudit);
1368
+ return {
1369
+ version: 4, mentionPolicy: config.mentionPolicy,
1370
+ inboxAudit: { enabled: inboxAudit.enabled, intervalMs: inboxAudit.intervalMs },
1371
+ persistedRevision: applyState?.persistedRevision ?? "unknown", agents,
1372
+ };
1205
1373
  }
@@ -8,11 +8,16 @@ import { CURRENT_RUNTIME_MODELS } from "../runtime/runtime-model-catalog.mjs";
8
8
  import { fromUserRuntime, isAdapterRuntime, isUserRuntime, toUserRuntime } from "../runtime/user-runtime.mjs";
9
9
  import processInspect from "./process-inspect.cjs";
10
10
  export { RUNTIME_OPTIONS, fromUserRuntime, isAdapterRuntime, isUserRuntime, runtimeOptionOf, runtimeOptionTarget, toUserRuntime, } from "../runtime/user-runtime.mjs";
11
+ export const DEFAULT_INBOX_AUDIT_INTERVAL_MS = 15 * 60_000;
12
+ export const MIN_INBOX_AUDIT_INTERVAL_MS = 60_000;
13
+ export const MAX_INBOX_AUDIT_INTERVAL_MS = 24 * 60 * 60_000;
11
14
  const TOP_FIELDS_V3 = new Set(["version", "serverId", "activeAgent", "agents"]);
12
15
  const TOP_FIELDS_V4 = new Set(["version", "serverId", "mentionPolicy", "activeAgent", "agents"]);
16
+ const OPTIONAL_TOP_FIELDS_V4 = new Set(["inboxAudit"]);
13
17
  const AGENT_FIELDS_V3 = new Set(["runtime", "model", "effort", "noMentionChats", "createdAt"]);
14
- const AGENT_FIELDS_V4 = new Set(["runtime", "model", "effort", "mentionPolicy", "chatMentionPolicies", "createdAt"]);
18
+ const AGENT_FIELDS_V4 = new Set(["runtime", "model", "effort", "mentionPolicy", "chatMentionPolicies", "inboxAudit", "createdAt"]);
15
19
  const LEGACY_AGENT_FIELDS = new Set(["piDistribution", "runtimeOption"]);
20
+ const INBOX_AUDIT_FIELDS = new Set(["enabled", "intervalMs"]);
16
21
  const APP_ID = /^cli_[A-Za-z0-9]+$/;
17
22
  const CHAT_ID = /^oc_[A-Za-z0-9_-]+$/;
18
23
  const PI_EFFORTS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
@@ -76,6 +81,85 @@ function assertPolicy(value, label) {
76
81
  if (value !== "require" && value !== "free")
77
82
  throw new Error(`${label} 只允许 require/free`);
78
83
  }
84
+ export function assertInboxAuditInterval(value, label = "inbox-audit interval") {
85
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < MIN_INBOX_AUDIT_INTERVAL_MS || value > MAX_INBOX_AUDIT_INTERVAL_MS) {
86
+ throw new Error(`${label} 必须是 ${MIN_INBOX_AUDIT_INTERVAL_MS}–${MAX_INBOX_AUDIT_INTERVAL_MS} 毫秒`);
87
+ }
88
+ }
89
+ export function parseInboxAuditInterval(value) {
90
+ if (typeof value !== "string" || !value)
91
+ throw new Error("inbox-audit interval 不能为空");
92
+ let milliseconds;
93
+ if (/^[1-9]\d*$/.test(value))
94
+ milliseconds = Number(value);
95
+ else {
96
+ const match = /^([1-9]\d*)(m|h)$/.exec(value);
97
+ if (!match)
98
+ throw new Error("inbox-audit interval 只允许 15m/1h 或 60000–86400000 毫秒");
99
+ milliseconds = Number(match[1]) * (match[2] === "h" ? 3_600_000 : 60_000);
100
+ }
101
+ assertInboxAuditInterval(milliseconds);
102
+ return milliseconds;
103
+ }
104
+ export function formatInboxAuditInterval(intervalMs) {
105
+ if (intervalMs % 3_600_000 === 0)
106
+ return `${intervalMs / 3_600_000}h`;
107
+ if (intervalMs % 60_000 === 0)
108
+ return `${intervalMs / 60_000}m`;
109
+ return `${intervalMs}ms`;
110
+ }
111
+ function parseStoredInboxAudit(value, label) {
112
+ if (!isPlainObject(value))
113
+ throw new Error(`${label} 必须是普通 object`);
114
+ assertAllowedFields(value, INBOX_AUDIT_FIELDS, label);
115
+ const settings = {};
116
+ if (Object.hasOwn(value, "enabled")) {
117
+ if (typeof value.enabled !== "boolean")
118
+ throw new Error(`${label}.enabled 必须是 boolean`);
119
+ settings.enabled = value.enabled;
120
+ }
121
+ if (Object.hasOwn(value, "intervalMs")) {
122
+ assertInboxAuditInterval(value.intervalMs, `${label}.intervalMs`);
123
+ settings.intervalMs = value.intervalMs;
124
+ }
125
+ return settings;
126
+ }
127
+ function resolvedInboxAudit(settings) {
128
+ return {
129
+ enabled: settings?.enabled ?? false,
130
+ intervalMs: settings?.intervalMs ?? DEFAULT_INBOX_AUDIT_INTERVAL_MS,
131
+ };
132
+ }
133
+ export function inboxAuditMutationFromCli(input) {
134
+ if (input.scope === "global") {
135
+ if (input.enabled !== "on" && input.enabled !== "off") {
136
+ throw new Error("用法: larkin config inbox-audit global <on|off> [--interval <15m|1h>]");
137
+ }
138
+ const mutation = {
139
+ kind: "set-global-inbox-audit", enabled: input.enabled === "on",
140
+ };
141
+ if (input.interval !== undefined) {
142
+ if (input.interval === "inherit")
143
+ throw new Error("全局 inbox-audit 不支持 interval=inherit");
144
+ mutation.intervalMs = parseInboxAuditInterval(input.interval);
145
+ }
146
+ return mutation;
147
+ }
148
+ if (input.scope === "agent") {
149
+ if (input.enabled !== "on" && input.enabled !== "off" && input.enabled !== "inherit") {
150
+ throw new Error("用法: larkin config inbox-audit agent <inherit|on|off> [--agent <App ID>] [--interval <15m|inherit>]");
151
+ }
152
+ const mutation = {
153
+ kind: "set-agent-inbox-audit", agentId: input.agentId,
154
+ enabled: input.enabled === "inherit" ? "inherit" : input.enabled === "on",
155
+ };
156
+ if (input.interval !== undefined) {
157
+ mutation.intervalMs = input.interval === "inherit" ? "inherit" : parseInboxAuditInterval(input.interval);
158
+ }
159
+ return mutation;
160
+ }
161
+ throw new Error("inbox-audit scope 只支持 global/agent");
162
+ }
79
163
  function assertModel(runtime, model) {
80
164
  if (model === "default")
81
165
  return;
@@ -143,6 +227,8 @@ function validateStoredAgent(key, agent, version) {
143
227
  }
144
228
  if (version === 4 && Object.hasOwn(agent, "mentionPolicy"))
145
229
  assertPolicy(agent.mentionPolicy, `Agent ${key}.mentionPolicy`);
230
+ if (version === 4 && Object.hasOwn(agent, "inboxAudit"))
231
+ parseStoredInboxAudit(agent.inboxAudit, `Agent ${key}.inboxAudit`);
146
232
  if (version === 4 && Object.hasOwn(agent, "chatMentionPolicies")) {
147
233
  if (!isPlainObject(agent.chatMentionPolicies))
148
234
  throw new Error(`Agent ${key}.chatMentionPolicies 必须是普通 object`);
@@ -171,6 +257,7 @@ function hydratedStoredAgent(key, agent, version) {
171
257
  model: agent.model,
172
258
  ...(typeof agent.effort === "string" ? { effort: agent.effort } : {}),
173
259
  ...(version === 4 && (agent.mentionPolicy === "require" || agent.mentionPolicy === "free") ? { mentionPolicy: agent.mentionPolicy } : {}),
260
+ ...(version === 4 && Object.hasOwn(agent, "inboxAudit") ? { inboxAudit: parseStoredInboxAudit(agent.inboxAudit, `Agent ${key}.inboxAudit`) } : {}),
174
261
  ...(Object.keys(chatMentionPolicies).length ? { chatMentionPolicies, noMentionChats: Object.entries(chatMentionPolicies).filter(([, policy]) => policy === "free").map(([chatId]) => chatId) } : {}),
175
262
  ...(typeof agent.createdAt === "string" ? { createdAt: agent.createdAt } : {}),
176
263
  };
@@ -184,6 +271,7 @@ export function hydrateAgent(key, agent, configDir) {
184
271
  larkConfigDir: path.join(layout.agentStateDir(key), "lark-cli-config"),
185
272
  ...(agent.effort ? { effort: agent.effort } : {}),
186
273
  ...(agent.mentionPolicy ? { mentionPolicy: agent.mentionPolicy } : {}),
274
+ ...(agent.inboxAudit ? { inboxAudit: { ...agent.inboxAudit } } : {}),
187
275
  ...(agent.chatMentionPolicies ? { chatMentionPolicies: { ...agent.chatMentionPolicies } } : {}),
188
276
  ...(agent.noMentionChats ? { noMentionChats: [...agent.noMentionChats] } : {}),
189
277
  ...(agent.createdAt ? { createdAt: agent.createdAt } : {}),
@@ -202,7 +290,7 @@ export function normalizeConfig(raw, configDir, { mint } = {}) {
202
290
  if (version !== 3 && version !== 4)
203
291
  throw new Error("不支持该配置格式:larkin 只接受 version=3/4");
204
292
  const topFields = version === 3 ? TOP_FIELDS_V3 : TOP_FIELDS_V4;
205
- assertAllowedFields(raw, topFields, "config");
293
+ assertAllowedFields(raw, version === 4 ? new Set([...TOP_FIELDS_V4, ...OPTIONAL_TOP_FIELDS_V4]) : topFields, "config");
206
294
  for (const field of topFields)
207
295
  if (!Object.hasOwn(raw, field))
208
296
  throw new Error(`config 缺少必需字段 ${field}`);
@@ -221,8 +309,12 @@ export function normalizeConfig(raw, configDir, { mint } = {}) {
221
309
  const agents = {};
222
310
  for (const [key, agent] of Object.entries(raw.agents))
223
311
  agents[key] = hydrateAgent(key, hydratedStoredAgent(key, agent, version), layout.root);
312
+ const inboxAudit = version === 4 && Object.hasOwn(raw, "inboxAudit")
313
+ ? resolvedInboxAudit(parseStoredInboxAudit(raw.inboxAudit, "config.inboxAudit"))
314
+ : undefined;
224
315
  return {
225
316
  version: 4, serverId: raw.serverId, mentionPolicy: version === 4 ? raw.mentionPolicy : "require",
317
+ ...(inboxAudit ? { inboxAudit } : {}),
226
318
  configDir: layout.configDir, larkinHome: layout.larkinHome, larkConfigDir: resolveLarkConfigDir({}, layout.root), activeAgent: raw.activeAgent, agents,
227
319
  };
228
320
  }
@@ -448,7 +540,11 @@ export function selectAgent(config, env = process.env) {
448
540
  return agents[config.activeAgent];
449
541
  }
450
542
  export function toStored(config) {
451
- const out = { version: 4, serverId: config.serverId, mentionPolicy: config.mentionPolicy, activeAgent: config.activeAgent, agents: {} };
543
+ const out = {
544
+ version: 4, serverId: config.serverId, mentionPolicy: config.mentionPolicy, activeAgent: config.activeAgent, agents: {},
545
+ };
546
+ if (config.inboxAudit)
547
+ out.inboxAudit = { enabled: config.inboxAudit.enabled, intervalMs: config.inboxAudit.intervalMs };
452
548
  for (const [key, agent] of Object.entries(config.agents || {})) {
453
549
  const stored = { runtime: agent.runtime, model: agent.model };
454
550
  if (typeof agent.effort === "string" && agent.effort)
@@ -457,6 +553,9 @@ export function toStored(config) {
457
553
  stored.mentionPolicy = agent.mentionPolicy;
458
554
  if (agent.chatMentionPolicies && Object.keys(agent.chatMentionPolicies).length)
459
555
  stored.chatMentionPolicies = { ...agent.chatMentionPolicies };
556
+ if (agent.inboxAudit && (agent.inboxAudit.enabled !== undefined || agent.inboxAudit.intervalMs !== undefined)) {
557
+ stored.inboxAudit = { ...agent.inboxAudit };
558
+ }
460
559
  if (typeof agent.createdAt === "string" && agent.createdAt)
461
560
  stored.createdAt = agent.createdAt;
462
561
  out.agents[key] = stored;
@@ -483,6 +582,20 @@ export function resolveAgentGlobalMentionPolicy(config, agentId) {
483
582
  return { effective: agent.mentionPolicy, source: "agent" };
484
583
  return { effective: config.mentionPolicy, source: "global" };
485
584
  }
585
+ export function resolveInboxAuditSchedule(config, agentId) {
586
+ const agent = config.agents[agentId];
587
+ if (!agent)
588
+ throw new Error(`Agent 不存在:${agentId}`);
589
+ const enabled = agent.inboxAudit?.enabled ?? config.inboxAudit?.enabled ?? false;
590
+ const intervalMs = agent.inboxAudit?.intervalMs ?? config.inboxAudit?.intervalMs ?? DEFAULT_INBOX_AUDIT_INTERVAL_MS;
591
+ assertInboxAuditInterval(intervalMs);
592
+ return {
593
+ enabled,
594
+ intervalMs,
595
+ enabledSource: agent.inboxAudit?.enabled !== undefined ? "agent" : config.inboxAudit ? "global" : "default",
596
+ intervalSource: agent.inboxAudit?.intervalMs !== undefined ? "agent" : config.inboxAudit?.intervalMs !== undefined ? "global" : "default",
597
+ };
598
+ }
486
599
  function revision(bytes) {
487
600
  return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
488
601
  }
@@ -495,6 +608,7 @@ function agentSignature(config, agentId) {
495
608
  schema: 2,
496
609
  runtime: agent.runtime, model: agent.model, effort: agent.effort ?? null,
497
610
  globalMentionPolicy: config.mentionPolicy, agentMentionPolicy: agent.mentionPolicy ?? null, chatMentionPolicies: chats,
611
+ globalInboxAudit: config.inboxAudit ?? null, agentInboxAudit: agent.inboxAudit ?? null,
498
612
  }));
499
613
  }
500
614
  export function runtimeConfigSignature(config, agentId) {
@@ -632,6 +746,19 @@ function applyMutation(config, mutation) {
632
746
  config.mentionPolicy = mutation.value;
633
747
  return { scope: "global", runtimeChange: false, affectedAgentIds: Object.keys(config.agents) };
634
748
  }
749
+ if (mutation.kind === "set-global-inbox-audit") {
750
+ if (mutation.enabled === undefined && mutation.intervalMs === undefined)
751
+ throw new Error("inbox-audit 全局变更必须包含 enabled 或 interval");
752
+ const current = resolvedInboxAudit(config.inboxAudit);
753
+ if (mutation.enabled !== undefined)
754
+ current.enabled = mutation.enabled;
755
+ if (mutation.intervalMs !== undefined) {
756
+ assertInboxAuditInterval(mutation.intervalMs, "全局 inbox-audit interval");
757
+ current.intervalMs = mutation.intervalMs;
758
+ }
759
+ config.inboxAudit = current;
760
+ return { scope: "global", runtimeChange: false, affectedAgentIds: Object.keys(config.agents) };
761
+ }
635
762
  if (!APP_ID.test(mutation.agentId) || !config.agents[mutation.agentId])
636
763
  throw new Error(`Agent 不存在:${mutation.agentId}`);
637
764
  const agent = config.agents[mutation.agentId];
@@ -644,6 +771,26 @@ function applyMutation(config, mutation) {
644
771
  }
645
772
  return { scope: "agent", agentId: mutation.agentId, runtimeChange: false, affectedAgentIds: [mutation.agentId] };
646
773
  }
774
+ if (mutation.kind === "set-agent-inbox-audit") {
775
+ if (mutation.enabled === undefined && mutation.intervalMs === undefined)
776
+ throw new Error("inbox-audit Agent 变更必须包含 enabled 或 interval");
777
+ const current = { ...(agent.inboxAudit || {}) };
778
+ if (mutation.enabled === "inherit")
779
+ delete current.enabled;
780
+ else if (mutation.enabled !== undefined)
781
+ current.enabled = mutation.enabled;
782
+ if (mutation.intervalMs === "inherit")
783
+ delete current.intervalMs;
784
+ else if (mutation.intervalMs !== undefined) {
785
+ assertInboxAuditInterval(mutation.intervalMs, "Agent inbox-audit interval");
786
+ current.intervalMs = mutation.intervalMs;
787
+ }
788
+ if (current.enabled === undefined && current.intervalMs === undefined)
789
+ delete agent.inboxAudit;
790
+ else
791
+ agent.inboxAudit = current;
792
+ return { scope: "agent", agentId: mutation.agentId, runtimeChange: false, affectedAgentIds: [mutation.agentId] };
793
+ }
647
794
  if (mutation.kind === "set-chat-mention") {
648
795
  if (!CHAT_ID.test(mutation.chatId))
649
796
  throw new Error(`群 ID 格式无效:${mutation.chatId}`);
@@ -1130,8 +1277,24 @@ export function safeConfigView(config, onlyAgentId, chatId, applyState) {
1130
1277
  source: agent.mentionPolicy ? "agent" : "global",
1131
1278
  ...(chatId ? { chat: { chatId, override: agent.chatMentionPolicies?.[chatId] ?? "inherit", ...resolveMentionPolicy(config, agentId, chatId) } } : {}),
1132
1279
  },
1280
+ inboxAudit: (() => {
1281
+ const resolved = resolveInboxAuditSchedule(config, agentId);
1282
+ return {
1283
+ override: {
1284
+ enabled: agent.inboxAudit?.enabled === undefined ? "inherit" : agent.inboxAudit.enabled ? "on" : "off",
1285
+ intervalMs: agent.inboxAudit?.intervalMs ?? "inherit",
1286
+ },
1287
+ effective: { enabled: resolved.enabled, intervalMs: resolved.intervalMs },
1288
+ source: { enabled: resolved.enabledSource, intervalMs: resolved.intervalSource },
1289
+ };
1290
+ })(),
1133
1291
  chatMentionPolicies: { ...(agent.chatMentionPolicies || {}) },
1134
1292
  apply: applyState?.agents?.[agentId] ?? { applyState: "unknown" },
1135
1293
  }));
1136
- return { version: 4, mentionPolicy: config.mentionPolicy, persistedRevision: applyState?.persistedRevision ?? "unknown", agents };
1294
+ const inboxAudit = resolvedInboxAudit(config.inboxAudit);
1295
+ return {
1296
+ version: 4, mentionPolicy: config.mentionPolicy,
1297
+ inboxAudit: { enabled: inboxAudit.enabled, intervalMs: inboxAudit.intervalMs },
1298
+ persistedRevision: applyState?.persistedRevision ?? "unknown", agents,
1299
+ };
1137
1300
  }
@@ -1,11 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = exports.CANONICAL_CONTEXT_WINDOW_MESSAGE = exports.CODEX_ERROR_PREFIX = void 0;
4
+ exports.safeProviderDiagnostic = safeProviderDiagnostic;
4
5
  exports.buildStrictProviderErrorInput = buildStrictProviderErrorInput;
5
6
  exports.classifyStrictProviderError = classifyStrictProviderError;
6
7
  exports.CODEX_ERROR_PREFIX = "Codex error: ";
7
8
  exports.CANONICAL_CONTEXT_WINDOW_MESSAGE = "Your input exceeds the context window of this model. Please adjust your input and try again.";
8
9
  exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = "provider rejected the input because the context window was exceeded";
10
+ /** Redact credentials and local filesystem locations from provider diagnostics kept in status. */
11
+ function safeProviderDiagnostic(value, fallback, max = 2_000) {
12
+ const source = typeof value === "string" ? value : fallback;
13
+ return source
14
+ .replace(/\b(?:authorization|proxy-authorization)\s*[:=]\s*(?:Bearer\s+)?[^\s,;]+/gi, (match) => `${match.split(/[:=]/, 1)[0]}=[redacted]`)
15
+ .replace(/\b(?:cookie|set-cookie)\s*[:=]\s*(?:[^;\s,]+(?:\s*;\s*[^;\s,]+)*)/gi, (match) => `${match.split(/[:=]/, 1)[0]}=[redacted]`)
16
+ .replace(/\b(?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|token|secret|password)\s*[:=]\s*[^\s,;]+/gi, (match) => `${match.split(/[:=]/, 1)[0]}=[redacted]`)
17
+ .replace(/(["'](?:authorization|cookie|api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)["']\s*:\s*)["'][^"']*["']/gi, "$1\"[redacted]\"")
18
+ .replace(/Bearer\s+[A-Za-z0-9._~+\/-]+/gi, "Bearer [redacted]")
19
+ .replace(/\b(?:sk|ghp|github_pat)-?[A-Za-z0-9_-]{8,}\b/g, "[redacted]")
20
+ .replace(/(^|[\s"'=])(?:\/(?:Users|home|private|var|tmp|etc)\/[^\s"']+|[A-Za-z]:[\\/][^\s"']+)/g, "$1[redacted-path]")
21
+ .replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim().slice(0, max) || fallback;
22
+ }
9
23
  /** Build the one structured provider-error shape shared by ingestion and recovery. */
10
24
  function buildStrictProviderErrorInput(source) {
11
25
  const upstream = source.upstream && typeof source.upstream === "object" && !Array.isArray(source.upstream)
@@ -1,6 +1,19 @@
1
1
  export const CODEX_ERROR_PREFIX = "Codex error: ";
2
2
  export const CANONICAL_CONTEXT_WINDOW_MESSAGE = "Your input exceeds the context window of this model. Please adjust your input and try again.";
3
3
  export const INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = "provider rejected the input because the context window was exceeded";
4
+ /** Redact credentials and local filesystem locations from provider diagnostics kept in status. */
5
+ export function safeProviderDiagnostic(value, fallback, max = 2_000) {
6
+ const source = typeof value === "string" ? value : fallback;
7
+ return source
8
+ .replace(/\b(?:authorization|proxy-authorization)\s*[:=]\s*(?:Bearer\s+)?[^\s,;]+/gi, (match) => `${match.split(/[:=]/, 1)[0]}=[redacted]`)
9
+ .replace(/\b(?:cookie|set-cookie)\s*[:=]\s*(?:[^;\s,]+(?:\s*;\s*[^;\s,]+)*)/gi, (match) => `${match.split(/[:=]/, 1)[0]}=[redacted]`)
10
+ .replace(/\b(?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|token|secret|password)\s*[:=]\s*[^\s,;]+/gi, (match) => `${match.split(/[:=]/, 1)[0]}=[redacted]`)
11
+ .replace(/(["'](?:authorization|cookie|api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)["']\s*:\s*)["'][^"']*["']/gi, "$1\"[redacted]\"")
12
+ .replace(/Bearer\s+[A-Za-z0-9._~+\/-]+/gi, "Bearer [redacted]")
13
+ .replace(/\b(?:sk|ghp|github_pat)-?[A-Za-z0-9_-]{8,}\b/g, "[redacted]")
14
+ .replace(/(^|[\s"'=])(?:\/(?:Users|home|private|var|tmp|etc)\/[^\s"']+|[A-Za-z]:[\\/][^\s"']+)/g, "$1[redacted-path]")
15
+ .replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim().slice(0, max) || fallback;
16
+ }
4
17
  /** Build the one structured provider-error shape shared by ingestion and recovery. */
5
18
  export function buildStrictProviderErrorInput(source) {
6
19
  const upstream = source.upstream && typeof source.upstream === "object" && !Array.isArray(source.upstream)