larkin 0.5.3 → 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.
- package/CHANGELOG.md +6 -0
- package/README.md +15 -0
- package/dist/agent/agent-cli-capabilities.cjs +1 -1
- package/dist/agent/agent-cli-capabilities.mjs +1 -1
- package/dist/agent/config-cli-contract.cjs +3 -1
- package/dist/agent/config-cli-contract.mjs +3 -1
- package/dist/agent/inbox-audit-heartbeat.cjs +157 -37
- package/dist/agent/inbox-audit-heartbeat.mjs +122 -36
- package/dist/agent/missed-outbound-scan.cjs +326 -54
- package/dist/agent/missed-outbound-scan.mjs +319 -53
- package/dist/app/agent-cli.mjs +37 -6
- package/dist/app/agent-config.mjs +36 -5
- package/dist/app/cli.mjs +1 -0
- package/dist/dashboard/dashboard-config-controller.mjs +15 -0
- package/dist/dashboard/web/assets/dashboard.css +1 -1
- package/dist/dashboard/web/assets/dashboard.js +15 -15
- package/dist/feishu/host-shell.cjs +63 -5
- package/dist/feishu/host-shell.mjs +66 -8
- package/dist/platform/config.cjs +173 -5
- package/dist/platform/config.mjs +167 -4
- package/package.json +1 -1
|
@@ -424,13 +424,57 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
424
424
|
for (const agent of agents)
|
|
425
425
|
prepareAgentState(agent);
|
|
426
426
|
const reminder = new host_reminder_orchestrator_js_1.HostReminderOrchestrator({ agents, stateStore, envelopeProjector, deliveryTarget: runtimeHost, log });
|
|
427
|
+
const auditRegistry = (0, missed_outbound_scan_js_1.inboxAuditRegistryFile)(larkinHome);
|
|
428
|
+
const auditRetryJournal = (0, missed_outbound_scan_js_1.inboxAuditRetryFile)(larkinHome);
|
|
429
|
+
let auditRetryTimer = null;
|
|
430
|
+
let auditRetryAttempt = 0;
|
|
431
|
+
const reconcileAuditRetries = () => {
|
|
432
|
+
try {
|
|
433
|
+
return (0, missed_outbound_scan_js_1.reconcileInboxAuditTargetRetries)(auditRegistry, auditRetryJournal).remaining;
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
log(`inbox audit retry 读取失败: ${(0, inbox_audit_heartbeat_js_1.boundedInboxAuditDiagnostic)(error)}`);
|
|
437
|
+
return 1;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const scheduleAuditRetry = () => {
|
|
441
|
+
if (auditRetryTimer || auditRetryAttempt >= 5)
|
|
442
|
+
return;
|
|
443
|
+
const delay = [100, 250, 1_000, 3_000, 10_000][auditRetryAttempt++];
|
|
444
|
+
auditRetryTimer = setTimeout(() => {
|
|
445
|
+
auditRetryTimer = null;
|
|
446
|
+
if (reconcileAuditRetries() > 0)
|
|
447
|
+
scheduleAuditRetry();
|
|
448
|
+
else
|
|
449
|
+
auditRetryAttempt = 0;
|
|
450
|
+
}, delay);
|
|
451
|
+
auditRetryTimer.unref?.();
|
|
452
|
+
};
|
|
427
453
|
const inboxAudit = new inbox_audit_heartbeat_js_1.InboxAuditHeartbeat({
|
|
428
454
|
agents,
|
|
429
455
|
stateStore,
|
|
430
456
|
runtimeHost,
|
|
431
457
|
log,
|
|
458
|
+
configFile: node_path_1.default.join(larkinHome, "config.json"),
|
|
459
|
+
schedule(agent) {
|
|
460
|
+
try {
|
|
461
|
+
return (0, config_js_1.resolveInboxAuditSchedule)((0, config_js_1.loadConfig)(env).config, agent.agentId);
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
log(`inbox audit schedule 读取失败 agent=${agent.agentId}: ${errorMessage(error)}`);
|
|
465
|
+
return { enabled: false, intervalMs: inbox_audit_heartbeat_js_1.INBOX_AUDIT_CADENCE_MS };
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
shouldDispatch(agent) {
|
|
469
|
+
try {
|
|
470
|
+
return (0, missed_outbound_scan_js_1.hasPendingInboxAuditTargets)(auditRegistry, agent.agentId);
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
log(`inbox audit pending 读取失败 agent=${agent.agentId}: ${errorMessage(error)}`);
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
},
|
|
432
477
|
});
|
|
433
|
-
const auditRegistry = (0, missed_outbound_scan_js_1.inboxAuditRegistryFile)(larkinHome);
|
|
434
478
|
const seenEventIds = new Set();
|
|
435
479
|
const inFlightEventIds = new Set();
|
|
436
480
|
const onFeishuMessage = async (agent, event, options) => {
|
|
@@ -477,14 +521,23 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
477
521
|
// append/dedupe decision is durable. Agent model-seen state is untouched.
|
|
478
522
|
if (event.event_id)
|
|
479
523
|
seenEventIds.add(eventKey);
|
|
524
|
+
if (append.status === "duplicate_consumed")
|
|
525
|
+
return null;
|
|
526
|
+
const auditSourceSeq = Number(append.envelope.target_seq);
|
|
480
527
|
try {
|
|
481
|
-
(0, missed_outbound_scan_js_1.observeInboxAuditTarget)(auditRegistry, agent.agentId, event);
|
|
528
|
+
(0, missed_outbound_scan_js_1.observeInboxAuditTarget)(auditRegistry, agent.agentId, { ...event, wake, source_seq: auditSourceSeq });
|
|
529
|
+
(0, missed_outbound_scan_js_1.discardInboxAuditTargetRetry)(auditRetryJournal, agent.agentId, { ...event, source_seq: auditSourceSeq });
|
|
482
530
|
}
|
|
483
531
|
catch (error) {
|
|
484
|
-
|
|
532
|
+
try {
|
|
533
|
+
(0, missed_outbound_scan_js_1.enqueueInboxAuditTargetRetry)(auditRetryJournal, agent.agentId, { ...event, wake, source_seq: auditSourceSeq });
|
|
534
|
+
scheduleAuditRetry();
|
|
535
|
+
log(`inbox audit target 延后重试: ${(0, inbox_audit_heartbeat_js_1.boundedInboxAuditDiagnostic)(error)}`);
|
|
536
|
+
}
|
|
537
|
+
catch (retryError) {
|
|
538
|
+
log(`inbox audit target 未持久化: ${(0, inbox_audit_heartbeat_js_1.boundedInboxAuditDiagnostic)(retryError)}`);
|
|
539
|
+
}
|
|
485
540
|
}
|
|
486
|
-
if (append.status === "duplicate_consumed")
|
|
487
|
-
return null;
|
|
488
541
|
const inboxEnvelope = append.envelope;
|
|
489
542
|
if (append.status === "appended")
|
|
490
543
|
hostState.appendConversation(agent, {
|
|
@@ -1452,6 +1505,9 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1452
1505
|
eventSourceStartTimer = null;
|
|
1453
1506
|
await Promise.resolve(eventSourceStop());
|
|
1454
1507
|
reminder.stopSync();
|
|
1508
|
+
if (auditRetryTimer)
|
|
1509
|
+
clearTimeout(auditRetryTimer);
|
|
1510
|
+
auditRetryTimer = null;
|
|
1455
1511
|
inboxAudit.stop();
|
|
1456
1512
|
interaction.stopSync();
|
|
1457
1513
|
await runtimeHost.shutdown(reason);
|
|
@@ -1670,6 +1726,8 @@ function createHostShell({ env = process.env, runtimeHost, channelPackage, event
|
|
|
1670
1726
|
previousSession: agentStates.get(agent.agentId)?.state.previousSessions?.[agent.runtime] ?? null,
|
|
1671
1727
|
})));
|
|
1672
1728
|
reminder.startSync();
|
|
1729
|
+
if (reconcileAuditRetries() > 0)
|
|
1730
|
+
scheduleAuditRetry();
|
|
1673
1731
|
inboxAudit.start();
|
|
1674
1732
|
interaction.startSync();
|
|
1675
1733
|
eventSourceStartTimer = setTimeout(() => { eventSourceStartTimer = null; startEventSource(); }, eventSourceStartDelayMs);
|
|
@@ -11,15 +11,15 @@ import { HostEnvelopeProjector, HostStateProjection, SenderIdentityCache, projec
|
|
|
11
11
|
import { ProcessingEyeOrchestrator } from "./host-processing-eye.mjs";
|
|
12
12
|
import { projectInboxEnvelope, targetKeyOfInboxEnvelope } from "../agent/inbox-projection.mjs";
|
|
13
13
|
import { HostReminderOrchestrator } from "../agent/host-reminder-orchestrator.mjs";
|
|
14
|
-
import { InboxAuditHeartbeat } from "../agent/inbox-audit-heartbeat.mjs";
|
|
15
|
-
import { inboxAuditRegistryFile, observeInboxAuditTarget } from "../agent/missed-outbound-scan.mjs";
|
|
14
|
+
import { boundedInboxAuditDiagnostic, InboxAuditHeartbeat, INBOX_AUDIT_CADENCE_MS } from "../agent/inbox-audit-heartbeat.mjs";
|
|
15
|
+
import { discardInboxAuditTargetRetry, enqueueInboxAuditTargetRetry, hasPendingInboxAuditTargets, inboxAuditRegistryFile, inboxAuditRetryFile, observeInboxAuditTarget, reconcileInboxAuditTargetRetries } from "../agent/missed-outbound-scan.mjs";
|
|
16
16
|
import { HostChannelBusiness } from "./host-channel-business.mjs";
|
|
17
17
|
import { HostInteractionOrchestrator } from "./interaction-orchestrator.mjs";
|
|
18
18
|
import { targetFor } from "./message-policy.mjs";
|
|
19
19
|
import { authFailureAppliesTo, parsePersistedAuthFailure, providerAuthenticationFailureReadiness, readinessForPersistedAuthFailure, RuntimePrerequisiteError, } from "../runtime/runtime-readiness.mjs";
|
|
20
20
|
import { safeProviderDiagnostic } from "../runtime/provider-error-classifier.mjs";
|
|
21
21
|
import { readDocumentCommentSubscription, verifyCallbackProbe } from "../platform/callback-capability.mjs";
|
|
22
|
-
import { loadConfig, resolveMentionPolicy } from "../platform/config.mjs";
|
|
22
|
+
import { loadConfig, resolveInboxAuditSchedule, resolveMentionPolicy } from "../platform/config.mjs";
|
|
23
23
|
import { processCommandToken } from "../app/internal-command.mjs";
|
|
24
24
|
import { managedOfficialLarkCli } from "../app/agent-lark-cli-workspace.mjs";
|
|
25
25
|
import { isChannelReconnecting, isRuntimeReadinessCurrent } from "../app/agent-readiness.mjs";
|
|
@@ -384,13 +384,57 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
384
384
|
for (const agent of agents)
|
|
385
385
|
prepareAgentState(agent);
|
|
386
386
|
const reminder = new HostReminderOrchestrator({ agents, stateStore, envelopeProjector, deliveryTarget: runtimeHost, log });
|
|
387
|
+
const auditRegistry = inboxAuditRegistryFile(larkinHome);
|
|
388
|
+
const auditRetryJournal = inboxAuditRetryFile(larkinHome);
|
|
389
|
+
let auditRetryTimer = null;
|
|
390
|
+
let auditRetryAttempt = 0;
|
|
391
|
+
const reconcileAuditRetries = () => {
|
|
392
|
+
try {
|
|
393
|
+
return reconcileInboxAuditTargetRetries(auditRegistry, auditRetryJournal).remaining;
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
log(`inbox audit retry 读取失败: ${boundedInboxAuditDiagnostic(error)}`);
|
|
397
|
+
return 1;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
const scheduleAuditRetry = () => {
|
|
401
|
+
if (auditRetryTimer || auditRetryAttempt >= 5)
|
|
402
|
+
return;
|
|
403
|
+
const delay = [100, 250, 1_000, 3_000, 10_000][auditRetryAttempt++];
|
|
404
|
+
auditRetryTimer = setTimeout(() => {
|
|
405
|
+
auditRetryTimer = null;
|
|
406
|
+
if (reconcileAuditRetries() > 0)
|
|
407
|
+
scheduleAuditRetry();
|
|
408
|
+
else
|
|
409
|
+
auditRetryAttempt = 0;
|
|
410
|
+
}, delay);
|
|
411
|
+
auditRetryTimer.unref?.();
|
|
412
|
+
};
|
|
387
413
|
const inboxAudit = new InboxAuditHeartbeat({
|
|
388
414
|
agents,
|
|
389
415
|
stateStore,
|
|
390
416
|
runtimeHost,
|
|
391
417
|
log,
|
|
418
|
+
configFile: path.join(larkinHome, "config.json"),
|
|
419
|
+
schedule(agent) {
|
|
420
|
+
try {
|
|
421
|
+
return resolveInboxAuditSchedule(loadConfig(env).config, agent.agentId);
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
log(`inbox audit schedule 读取失败 agent=${agent.agentId}: ${errorMessage(error)}`);
|
|
425
|
+
return { enabled: false, intervalMs: INBOX_AUDIT_CADENCE_MS };
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
shouldDispatch(agent) {
|
|
429
|
+
try {
|
|
430
|
+
return hasPendingInboxAuditTargets(auditRegistry, agent.agentId);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
log(`inbox audit pending 读取失败 agent=${agent.agentId}: ${errorMessage(error)}`);
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
},
|
|
392
437
|
});
|
|
393
|
-
const auditRegistry = inboxAuditRegistryFile(larkinHome);
|
|
394
438
|
const seenEventIds = new Set();
|
|
395
439
|
const inFlightEventIds = new Set();
|
|
396
440
|
const onFeishuMessage = async (agent, event, options) => {
|
|
@@ -437,14 +481,23 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
437
481
|
// append/dedupe decision is durable. Agent model-seen state is untouched.
|
|
438
482
|
if (event.event_id)
|
|
439
483
|
seenEventIds.add(eventKey);
|
|
484
|
+
if (append.status === "duplicate_consumed")
|
|
485
|
+
return null;
|
|
486
|
+
const auditSourceSeq = Number(append.envelope.target_seq);
|
|
440
487
|
try {
|
|
441
|
-
observeInboxAuditTarget(auditRegistry, agent.agentId, event);
|
|
488
|
+
observeInboxAuditTarget(auditRegistry, agent.agentId, { ...event, wake, source_seq: auditSourceSeq });
|
|
489
|
+
discardInboxAuditTargetRetry(auditRetryJournal, agent.agentId, { ...event, source_seq: auditSourceSeq });
|
|
442
490
|
}
|
|
443
491
|
catch (error) {
|
|
444
|
-
|
|
492
|
+
try {
|
|
493
|
+
enqueueInboxAuditTargetRetry(auditRetryJournal, agent.agentId, { ...event, wake, source_seq: auditSourceSeq });
|
|
494
|
+
scheduleAuditRetry();
|
|
495
|
+
log(`inbox audit target 延后重试: ${boundedInboxAuditDiagnostic(error)}`);
|
|
496
|
+
}
|
|
497
|
+
catch (retryError) {
|
|
498
|
+
log(`inbox audit target 未持久化: ${boundedInboxAuditDiagnostic(retryError)}`);
|
|
499
|
+
}
|
|
445
500
|
}
|
|
446
|
-
if (append.status === "duplicate_consumed")
|
|
447
|
-
return null;
|
|
448
501
|
const inboxEnvelope = append.envelope;
|
|
449
502
|
if (append.status === "appended")
|
|
450
503
|
hostState.appendConversation(agent, {
|
|
@@ -1412,6 +1465,9 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1412
1465
|
eventSourceStartTimer = null;
|
|
1413
1466
|
await Promise.resolve(eventSourceStop());
|
|
1414
1467
|
reminder.stopSync();
|
|
1468
|
+
if (auditRetryTimer)
|
|
1469
|
+
clearTimeout(auditRetryTimer);
|
|
1470
|
+
auditRetryTimer = null;
|
|
1415
1471
|
inboxAudit.stop();
|
|
1416
1472
|
interaction.stopSync();
|
|
1417
1473
|
await runtimeHost.shutdown(reason);
|
|
@@ -1630,6 +1686,8 @@ export function createHostShell({ env = process.env, runtimeHost, channelPackage
|
|
|
1630
1686
|
previousSession: agentStates.get(agent.agentId)?.state.previousSessions?.[agent.runtime] ?? null,
|
|
1631
1687
|
})));
|
|
1632
1688
|
reminder.startSync();
|
|
1689
|
+
if (reconcileAuditRetries() > 0)
|
|
1690
|
+
scheduleAuditRetry();
|
|
1633
1691
|
inboxAudit.start();
|
|
1634
1692
|
interaction.startSync();
|
|
1635
1693
|
eventSourceStartTimer = setTimeout(() => { eventSourceStartTimer = null; startEventSource(); }, eventSourceStartDelayMs);
|
package/dist/platform/config.cjs
CHANGED
|
@@ -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 = {
|
|
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
|
-
|
|
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
|
}
|