@lightcone-ai/daemon 0.14.17 → 0.14.18
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/mcp-servers/official/keyword-research/index.js +95 -56
- package/mcp-servers/official/keyword-research/keyword-fixtures.json +58 -0
- package/mcp-servers/official/page-understanding/index.js +19 -0
- package/mcp-servers/official/page-understanding/manifest.json +8 -0
- package/mcp-servers/official/platform-policy-db/index.js +117 -163
- package/mcp-servers/official/platform-policy-db/policy-fixtures.json +170 -0
- package/mcp-servers/official/video-narration-planner/core.js +154 -116
- package/mcp-servers/official/video-narration-planner/index.js +29 -0
- package/mcp-servers/official/video-narration-planner/manifest.json +14 -0
- package/mcp-servers/official/video-narration-planner/planner-config.json +112 -0
- package/mcp-servers/official-common/tool-access-policy.js +90 -0
- package/package.json +1 -1
- package/src/agent-manager.js +205 -22
- package/src/chat-bridge.js +55 -12
- package/src/index.js +2 -1
- package/src/lifecycle-protocol.js +51 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
function isPlainObject(value) {
|
|
4
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeToken(value) {
|
|
8
|
+
return String(value ?? '').trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeId(value) {
|
|
12
|
+
return normalizeToken(value).toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeToolList(value) {
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value
|
|
18
|
+
.map(item => normalizeToken(item).toLowerCase())
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
}
|
|
21
|
+
const direct = normalizeToken(value).toLowerCase();
|
|
22
|
+
return direct ? [direct] : [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeRule(rawRule) {
|
|
26
|
+
if (!isPlainObject(rawRule)) return null;
|
|
27
|
+
|
|
28
|
+
const workspaceId = normalizeId(rawRule.workspace_id ?? rawRule.workspaceId);
|
|
29
|
+
const agentId = normalizeId(rawRule.agent_id ?? rawRule.agentId);
|
|
30
|
+
const tools = normalizeToolList(
|
|
31
|
+
rawRule.tools
|
|
32
|
+
?? rawRule.tool_names
|
|
33
|
+
?? rawRule.toolNames
|
|
34
|
+
?? rawRule.tool
|
|
35
|
+
);
|
|
36
|
+
const message = normalizeToken(rawRule.message ?? rawRule.error ?? rawRule.reason);
|
|
37
|
+
|
|
38
|
+
if (!workspaceId || !agentId || tools.length === 0 || !message) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
workspaceId,
|
|
44
|
+
agentId,
|
|
45
|
+
tools,
|
|
46
|
+
message,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function loadToolBlockRulesFromManifest(manifestUrl) {
|
|
51
|
+
if (!manifestUrl) return [];
|
|
52
|
+
let rawManifest = null;
|
|
53
|
+
try {
|
|
54
|
+
rawManifest = JSON.parse(readFileSync(manifestUrl, 'utf8'));
|
|
55
|
+
} catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
if (!isPlainObject(rawManifest)) return [];
|
|
59
|
+
|
|
60
|
+
const rawRules = Array.isArray(rawManifest.tool_block_rules)
|
|
61
|
+
? rawManifest.tool_block_rules
|
|
62
|
+
: Array.isArray(rawManifest.toolBlockRules)
|
|
63
|
+
? rawManifest.toolBlockRules
|
|
64
|
+
: [];
|
|
65
|
+
|
|
66
|
+
return rawRules
|
|
67
|
+
.map(rule => normalizeRule(rule))
|
|
68
|
+
.filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function findToolBlockRule(rules, {
|
|
72
|
+
toolName = '',
|
|
73
|
+
workspaceId = '',
|
|
74
|
+
agentId = '',
|
|
75
|
+
} = {}) {
|
|
76
|
+
if (!Array.isArray(rules) || rules.length === 0) return null;
|
|
77
|
+
|
|
78
|
+
const targetTool = normalizeToken(toolName).toLowerCase();
|
|
79
|
+
const targetWorkspaceId = normalizeId(workspaceId);
|
|
80
|
+
const targetAgentId = normalizeId(agentId);
|
|
81
|
+
if (!targetTool || !targetWorkspaceId || !targetAgentId) return null;
|
|
82
|
+
|
|
83
|
+
for (const rule of rules) {
|
|
84
|
+
if (!rule || rule.workspaceId !== targetWorkspaceId || rule.agentId !== targetAgentId) continue;
|
|
85
|
+
if (rule.tools.includes('*') || rule.tools.includes(targetTool)) {
|
|
86
|
+
return rule;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
package/package.json
CHANGED
package/src/agent-manager.js
CHANGED
|
@@ -16,6 +16,7 @@ import { injectWorkspaceContext } from './drivers/claude.js';
|
|
|
16
16
|
import { parseKimiLine, encodeKimiStdin } from './drivers/kimi.js';
|
|
17
17
|
import { startSession, stopSession, stopAllSessions } from './browser-login.js';
|
|
18
18
|
import { markInvalidatedLeases } from './governance-state.js';
|
|
19
|
+
import { resolveExitOfflineDetail, resolveLifecycleExitState } from './lifecycle-protocol.js';
|
|
19
20
|
import { runPublishJob } from './publish-job-runner.js';
|
|
20
21
|
|
|
21
22
|
const KIMI_SYSTEM_PROMPT_FILE = '.lightcone-kimi-system.md';
|
|
@@ -115,15 +116,6 @@ function runtimeMissingDetail(runtime) {
|
|
|
115
116
|
return `cli_missing:${runtime}`;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
function resolveExitOfflineDetail({ code, signal, stopCause }) {
|
|
119
|
-
if (stopCause === 'manual_stop') return '';
|
|
120
|
-
if (stopCause === 'credential_revoked') return 'credential_revoked';
|
|
121
|
-
if (code === 0) return '';
|
|
122
|
-
if (signal === 'SIGTERM') return '';
|
|
123
|
-
if (signal === 'SIGKILL') return 'agent_timeout';
|
|
124
|
-
return 'spawn_session_crashed';
|
|
125
|
-
}
|
|
126
|
-
|
|
127
119
|
function normalizeObject(value) {
|
|
128
120
|
return isPlainObject(value) ? value : {};
|
|
129
121
|
}
|
|
@@ -218,6 +210,31 @@ export class AgentManager {
|
|
|
218
210
|
return `New message in ${message.workspace_type === 'dm' ? 'dm from' : `#${message.workspace_name} from`} ${message.sender_name}: ${message.content}`;
|
|
219
211
|
}
|
|
220
212
|
|
|
213
|
+
_emitLifecycle(connection, {
|
|
214
|
+
agentId,
|
|
215
|
+
workspaceId,
|
|
216
|
+
runtime,
|
|
217
|
+
reachability,
|
|
218
|
+
availability,
|
|
219
|
+
runtimeState,
|
|
220
|
+
reason = null,
|
|
221
|
+
sessionId = null,
|
|
222
|
+
}) {
|
|
223
|
+
const normalizedRuntime = String(runtime ?? '').trim().toLowerCase() || 'claude';
|
|
224
|
+
const normalizedReason = String(reason ?? '').trim() || null;
|
|
225
|
+
connection.send({
|
|
226
|
+
type: 'agent:lifecycle',
|
|
227
|
+
agentId,
|
|
228
|
+
workspaceId,
|
|
229
|
+
reachability,
|
|
230
|
+
availability,
|
|
231
|
+
runtimeState,
|
|
232
|
+
reason: normalizedReason,
|
|
233
|
+
sessionId: sessionId ?? null,
|
|
234
|
+
runtime: normalizedRuntime,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
221
238
|
_takePendingMessage(key) {
|
|
222
239
|
if (!this._pendingMessages) return null;
|
|
223
240
|
const pending = this._pendingMessages.get(key);
|
|
@@ -227,6 +244,16 @@ export class AgentManager {
|
|
|
227
244
|
return msg;
|
|
228
245
|
}
|
|
229
246
|
|
|
247
|
+
_enqueuePendingMessage(key, msg, { front = false } = {}) {
|
|
248
|
+
if (!msg) return 0;
|
|
249
|
+
if (!this._pendingMessages) this._pendingMessages = new Map();
|
|
250
|
+
const pending = this._pendingMessages.get(key) ?? [];
|
|
251
|
+
if (front) pending.unshift(msg);
|
|
252
|
+
else pending.push(msg);
|
|
253
|
+
this._pendingMessages.set(key, pending);
|
|
254
|
+
return pending.length;
|
|
255
|
+
}
|
|
256
|
+
|
|
230
257
|
_prepareStartupMessage(key, runtime) {
|
|
231
258
|
if (runtime === 'codex') {
|
|
232
259
|
const startupMsg = this._takePendingMessage(key);
|
|
@@ -575,9 +602,21 @@ export class AgentManager {
|
|
|
575
602
|
this._workspaceRootDir(workspaceId);
|
|
576
603
|
const workspaceDir = this._workspaceDir(agentId, workspaceId);
|
|
577
604
|
const chatBridgePath = new URL('./chat-bridge.js', import.meta.url).pathname;
|
|
578
|
-
|
|
605
|
+
let rollbackStartupMessage = null;
|
|
606
|
+
const failStart = (reason, runtimeHint = requestedRuntime) => {
|
|
607
|
+
rollbackStartupMessage?.(reason ?? 'spawn_failed');
|
|
579
608
|
this._clearDirectiveRefresh(key);
|
|
580
609
|
this.starting.delete(key);
|
|
610
|
+
this._emitLifecycle(connection, {
|
|
611
|
+
agentId,
|
|
612
|
+
workspaceId,
|
|
613
|
+
runtime: runtimeHint,
|
|
614
|
+
reachability: 'unreachable',
|
|
615
|
+
availability: 'unreachable',
|
|
616
|
+
runtimeState: 'crashed',
|
|
617
|
+
reason: reason ?? 'spawn_failed',
|
|
618
|
+
sessionId: config.sessionId ?? null,
|
|
619
|
+
});
|
|
581
620
|
connection.send({ type: 'agent:status', agentId, workspaceId, status: 'inactive' });
|
|
582
621
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'offline', detail: reason ?? '', entries: [] });
|
|
583
622
|
};
|
|
@@ -620,6 +659,22 @@ export class AgentManager {
|
|
|
620
659
|
}
|
|
621
660
|
|
|
622
661
|
const { startupMsg } = this._prepareStartupMessage(key, runtime);
|
|
662
|
+
let startupMsgRestored = false;
|
|
663
|
+
const restoreStartupMessage = (reason) => {
|
|
664
|
+
if (runtime !== 'codex' || !startupMsg || startupMsgRestored) return;
|
|
665
|
+
startupMsgRestored = true;
|
|
666
|
+
const queueSize = this._enqueuePendingMessage(key, startupMsg, { front: true });
|
|
667
|
+
const seq = startupMsg.seq ?? 'n/a';
|
|
668
|
+
console.log(
|
|
669
|
+
`[AgentManager] Restored codex startup message seq=${seq} workspace=${workspaceName ?? workspaceId ?? 'none'} reason=${reason ?? 'unknown'} pending=${queueSize}`
|
|
670
|
+
);
|
|
671
|
+
};
|
|
672
|
+
if (runtime === 'codex' && startupMsg) {
|
|
673
|
+
console.log(
|
|
674
|
+
`[AgentManager] Claimed codex startup message seq=${startupMsg.seq ?? 'n/a'} workspace=${workspaceName ?? workspaceId ?? 'none'}`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
rollbackStartupMessage = restoreStartupMessage;
|
|
623
678
|
|
|
624
679
|
// Keep .skills materialization for runtime compatibility, but no longer use skills to build prompt/env/mcp.
|
|
625
680
|
let skills = [];
|
|
@@ -732,9 +787,20 @@ export class AgentManager {
|
|
|
732
787
|
const reportSpawnFailure = (detail) => {
|
|
733
788
|
if (spawnErrorReported) return;
|
|
734
789
|
spawnErrorReported = true;
|
|
790
|
+
restoreStartupMessage(detail ?? 'spawn_failed');
|
|
735
791
|
this._clearDirectiveRefresh(key);
|
|
736
792
|
this.starting.delete(key);
|
|
737
793
|
this.agents.delete(key);
|
|
794
|
+
this._emitLifecycle(connection, {
|
|
795
|
+
agentId,
|
|
796
|
+
workspaceId,
|
|
797
|
+
runtime,
|
|
798
|
+
reachability: 'unreachable',
|
|
799
|
+
availability: 'unreachable',
|
|
800
|
+
runtimeState: 'crashed',
|
|
801
|
+
reason: detail ?? 'spawn_failed',
|
|
802
|
+
sessionId: config.sessionId ?? null,
|
|
803
|
+
});
|
|
738
804
|
connection.send({ type: 'agent:status', agentId, workspaceId, status: 'inactive' });
|
|
739
805
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'offline', detail: detail ?? 'spawn_failed', entries: [] });
|
|
740
806
|
};
|
|
@@ -864,6 +930,10 @@ export class AgentManager {
|
|
|
864
930
|
this.agents.delete(key);
|
|
865
931
|
|
|
866
932
|
if (code === 0 && runtime === 'codex' && this._pendingMessages?.get(key)?.length) {
|
|
933
|
+
const pendingCount = this._pendingMessages?.get(key)?.length ?? 0;
|
|
934
|
+
console.log(
|
|
935
|
+
`[AgentManager] Codex exited cleanly with pending=${pendingCount}; restarting next turn for ${config.displayName ?? agentId}`
|
|
936
|
+
);
|
|
867
937
|
const restartConfig = { ...config, sessionId: agent?.sessionId ?? config.sessionId ?? null };
|
|
868
938
|
this._startAgent({ agentId, workspaceId, config: restartConfig }, connection);
|
|
869
939
|
return;
|
|
@@ -873,6 +943,7 @@ export class AgentManager {
|
|
|
873
943
|
if (code !== 0 && config.sessionId && !this._retried?.has(key)) {
|
|
874
944
|
if (!this._retried) this._retried = new Set();
|
|
875
945
|
this._retried.add(key);
|
|
946
|
+
restoreStartupMessage('session_retry_without_session');
|
|
876
947
|
console.log(`[AgentManager] Retrying ${agentId} workspace=${workspaceId} without session (session may not exist locally)`);
|
|
877
948
|
const retryConfig = { ...config, sessionId: null };
|
|
878
949
|
this._startAgent({ agentId, workspaceId, config: retryConfig }, connection);
|
|
@@ -885,6 +956,23 @@ export class AgentManager {
|
|
|
885
956
|
stopCause: agent?.stopCause ?? null,
|
|
886
957
|
});
|
|
887
958
|
|
|
959
|
+
const lifecycleExit = resolveLifecycleExitState({
|
|
960
|
+
runtime,
|
|
961
|
+
code,
|
|
962
|
+
signal,
|
|
963
|
+
stopCause: agent?.stopCause ?? null,
|
|
964
|
+
});
|
|
965
|
+
this._emitLifecycle(connection, {
|
|
966
|
+
agentId,
|
|
967
|
+
workspaceId,
|
|
968
|
+
runtime,
|
|
969
|
+
reachability: lifecycleExit.reachability,
|
|
970
|
+
availability: lifecycleExit.availability,
|
|
971
|
+
runtimeState: lifecycleExit.runtimeState,
|
|
972
|
+
reason: lifecycleExit.reason,
|
|
973
|
+
sessionId: agent?.sessionId ?? config.sessionId ?? null,
|
|
974
|
+
});
|
|
975
|
+
|
|
888
976
|
connection.send({ type: 'agent:status', agentId, workspaceId, status: 'inactive' });
|
|
889
977
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'offline', detail: offlineDetail, entries: [] });
|
|
890
978
|
});
|
|
@@ -897,6 +985,17 @@ export class AgentManager {
|
|
|
897
985
|
}
|
|
898
986
|
|
|
899
987
|
console.log(`[AgentManager] Agent ${config.displayName ?? agentId} is now active (workspace=${workspaceName ?? workspaceId ?? 'none'})`);
|
|
988
|
+
const startedAgent = this.agents.get(key);
|
|
989
|
+
this._emitLifecycle(connection, {
|
|
990
|
+
agentId,
|
|
991
|
+
workspaceId,
|
|
992
|
+
runtime,
|
|
993
|
+
reachability: 'reachable',
|
|
994
|
+
availability: 'available',
|
|
995
|
+
runtimeState: 'running',
|
|
996
|
+
reason: 'spawned',
|
|
997
|
+
sessionId: startedAgent?.sessionId ?? config.sessionId ?? null,
|
|
998
|
+
});
|
|
900
999
|
connection.send({ type: 'agent:status', agentId, workspaceId, status: 'active' });
|
|
901
1000
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
902
1001
|
|
|
@@ -1166,10 +1265,8 @@ export class AgentManager {
|
|
|
1166
1265
|
if (!this.agents.has(key) && !this.starting.has(key)) {
|
|
1167
1266
|
// Agent not running — queue the message and request config to spawn it
|
|
1168
1267
|
console.log(`[AgentManager] Agent ${agentId.slice(0,8)} workspace=${message.workspace_name ?? workspaceId} not running, requesting start for seq=${seq}`);
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
pending.push(msg);
|
|
1172
|
-
this._pendingMessages.set(key, pending);
|
|
1268
|
+
const queueSize = this._enqueuePendingMessage(key, msg);
|
|
1269
|
+
console.log(`[AgentManager] Queued seq=${seq} for start request pending=${queueSize}`);
|
|
1173
1270
|
connection.send({ type: 'agent:request_start', agentId, workspaceId });
|
|
1174
1271
|
return;
|
|
1175
1272
|
}
|
|
@@ -1177,10 +1274,8 @@ export class AgentManager {
|
|
|
1177
1274
|
if (this.starting.has(key)) {
|
|
1178
1275
|
// Spawn in progress — queue the message for delivery after start
|
|
1179
1276
|
console.log(`[AgentManager] Agent ${agentId.slice(0,8)} workspace=${message.workspace_name ?? workspaceId} still starting, queuing seq=${seq}`);
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
pending.push(msg);
|
|
1183
|
-
this._pendingMessages.set(key, pending);
|
|
1277
|
+
const queueSize = this._enqueuePendingMessage(key, msg);
|
|
1278
|
+
console.log(`[AgentManager] Queued seq=${seq} while starting pending=${queueSize}`);
|
|
1184
1279
|
return;
|
|
1185
1280
|
}
|
|
1186
1281
|
|
|
@@ -1188,10 +1283,8 @@ export class AgentManager {
|
|
|
1188
1283
|
const agent = this.agents.get(key);
|
|
1189
1284
|
console.log(`[AgentManager] Delivering seq=${seq} to agent ${agent?.config?.displayName ?? agentId.slice(0,8)} workspace=${message.workspace_name ?? workspaceId}`);
|
|
1190
1285
|
if (agent?.runtime === 'codex') {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
pending.push(msg);
|
|
1194
|
-
this._pendingMessages.set(key, pending);
|
|
1286
|
+
const queueSize = this._enqueuePendingMessage(key, msg);
|
|
1287
|
+
console.log(`[AgentManager] Queued seq=${seq} for codex next turn pending=${queueSize}`);
|
|
1195
1288
|
return;
|
|
1196
1289
|
}
|
|
1197
1290
|
this._write(key, text);
|
|
@@ -1253,13 +1346,43 @@ export class AgentManager {
|
|
|
1253
1346
|
}
|
|
1254
1347
|
break;
|
|
1255
1348
|
case 'thinking':
|
|
1349
|
+
this._emitLifecycle(connection, {
|
|
1350
|
+
agentId,
|
|
1351
|
+
workspaceId,
|
|
1352
|
+
runtime: 'kimi',
|
|
1353
|
+
reachability: 'reachable',
|
|
1354
|
+
availability: 'busy',
|
|
1355
|
+
runtimeState: 'running',
|
|
1356
|
+
reason: 'thinking',
|
|
1357
|
+
sessionId: agent.sessionId ?? null,
|
|
1358
|
+
});
|
|
1256
1359
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
1257
1360
|
break;
|
|
1258
1361
|
case 'tool_call':
|
|
1362
|
+
this._emitLifecycle(connection, {
|
|
1363
|
+
agentId,
|
|
1364
|
+
workspaceId,
|
|
1365
|
+
runtime: 'kimi',
|
|
1366
|
+
reachability: 'reachable',
|
|
1367
|
+
availability: 'busy',
|
|
1368
|
+
runtimeState: 'running',
|
|
1369
|
+
reason: `tool:${evt.name ?? 'unknown_tool'}`,
|
|
1370
|
+
sessionId: agent.sessionId ?? null,
|
|
1371
|
+
});
|
|
1259
1372
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: evt.name, entries: [] });
|
|
1260
1373
|
break;
|
|
1261
1374
|
case 'turn_end':
|
|
1262
1375
|
agent.kimiIdle = true;
|
|
1376
|
+
this._emitLifecycle(connection, {
|
|
1377
|
+
agentId,
|
|
1378
|
+
workspaceId,
|
|
1379
|
+
runtime: 'kimi',
|
|
1380
|
+
reachability: 'reachable',
|
|
1381
|
+
availability: 'available',
|
|
1382
|
+
runtimeState: 'running',
|
|
1383
|
+
reason: 'turn_end',
|
|
1384
|
+
sessionId: agent.sessionId ?? null,
|
|
1385
|
+
});
|
|
1263
1386
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
1264
1387
|
break;
|
|
1265
1388
|
case 'error':
|
|
@@ -1282,12 +1405,42 @@ export class AgentManager {
|
|
|
1282
1405
|
}
|
|
1283
1406
|
break;
|
|
1284
1407
|
case 'thinking':
|
|
1408
|
+
this._emitLifecycle(connection, {
|
|
1409
|
+
agentId,
|
|
1410
|
+
workspaceId,
|
|
1411
|
+
runtime: 'codex',
|
|
1412
|
+
reachability: 'reachable',
|
|
1413
|
+
availability: 'busy',
|
|
1414
|
+
runtimeState: 'running',
|
|
1415
|
+
reason: 'thinking',
|
|
1416
|
+
sessionId: agent.sessionId ?? null,
|
|
1417
|
+
});
|
|
1285
1418
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
1286
1419
|
break;
|
|
1287
1420
|
case 'tool_call':
|
|
1421
|
+
this._emitLifecycle(connection, {
|
|
1422
|
+
agentId,
|
|
1423
|
+
workspaceId,
|
|
1424
|
+
runtime: 'codex',
|
|
1425
|
+
reachability: 'reachable',
|
|
1426
|
+
availability: 'busy',
|
|
1427
|
+
runtimeState: 'running',
|
|
1428
|
+
reason: `tool:${evt.name ?? 'unknown_tool'}`,
|
|
1429
|
+
sessionId: agent.sessionId ?? null,
|
|
1430
|
+
});
|
|
1288
1431
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: evt.name, entries: [] });
|
|
1289
1432
|
break;
|
|
1290
1433
|
case 'turn_end':
|
|
1434
|
+
this._emitLifecycle(connection, {
|
|
1435
|
+
agentId,
|
|
1436
|
+
workspaceId,
|
|
1437
|
+
runtime: 'codex',
|
|
1438
|
+
reachability: 'reachable',
|
|
1439
|
+
availability: 'available',
|
|
1440
|
+
runtimeState: 'running',
|
|
1441
|
+
reason: 'turn_end',
|
|
1442
|
+
sessionId: agent.sessionId ?? null,
|
|
1443
|
+
});
|
|
1291
1444
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
1292
1445
|
break;
|
|
1293
1446
|
case 'error':
|
|
@@ -1322,10 +1475,30 @@ export class AgentManager {
|
|
|
1322
1475
|
console.log(`[AgentManager][${displayName}] <text> ${block.text?.slice(0, 500)}`);
|
|
1323
1476
|
} else if (block.type === 'tool_use') {
|
|
1324
1477
|
console.log(`[AgentManager][${displayName}] <tool_use> ${block.name} params=${JSON.stringify(block.input ?? {}).slice(0, 500)}`);
|
|
1478
|
+
this._emitLifecycle(connection, {
|
|
1479
|
+
agentId,
|
|
1480
|
+
workspaceId,
|
|
1481
|
+
runtime: 'claude',
|
|
1482
|
+
reachability: 'reachable',
|
|
1483
|
+
availability: 'busy',
|
|
1484
|
+
runtimeState: 'running',
|
|
1485
|
+
reason: `tool:${block.name ?? 'unknown_tool'}`,
|
|
1486
|
+
sessionId: this.agents.get(key)?.sessionId ?? null,
|
|
1487
|
+
});
|
|
1325
1488
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'working', detail: block.name, entries: [] });
|
|
1326
1489
|
}
|
|
1327
1490
|
}
|
|
1328
1491
|
if (!content.some(c => c.type === 'tool_use')) {
|
|
1492
|
+
this._emitLifecycle(connection, {
|
|
1493
|
+
agentId,
|
|
1494
|
+
workspaceId,
|
|
1495
|
+
runtime: 'claude',
|
|
1496
|
+
reachability: 'reachable',
|
|
1497
|
+
availability: 'busy',
|
|
1498
|
+
runtimeState: 'running',
|
|
1499
|
+
reason: 'thinking',
|
|
1500
|
+
sessionId: this.agents.get(key)?.sessionId ?? null,
|
|
1501
|
+
});
|
|
1329
1502
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'thinking', detail: '', entries: [] });
|
|
1330
1503
|
}
|
|
1331
1504
|
} else if (event.type === 'tool') {
|
|
@@ -1345,6 +1518,16 @@ export class AgentManager {
|
|
|
1345
1518
|
}
|
|
1346
1519
|
} else if (event.type === 'result') {
|
|
1347
1520
|
console.log(`[AgentManager][${displayName}] turn done (stop_reason=${event.stop_reason ?? '?'})`);
|
|
1521
|
+
this._emitLifecycle(connection, {
|
|
1522
|
+
agentId,
|
|
1523
|
+
workspaceId,
|
|
1524
|
+
runtime: 'claude',
|
|
1525
|
+
reachability: 'reachable',
|
|
1526
|
+
availability: 'available',
|
|
1527
|
+
runtimeState: 'running',
|
|
1528
|
+
reason: 'turn_end',
|
|
1529
|
+
sessionId: this.agents.get(key)?.sessionId ?? null,
|
|
1530
|
+
});
|
|
1348
1531
|
connection.send({ type: 'agent:activity', agentId, workspaceId, activity: 'online', detail: '', entries: [] });
|
|
1349
1532
|
}
|
|
1350
1533
|
}
|
package/src/chat-bridge.js
CHANGED
|
@@ -59,6 +59,14 @@ let currentWorkspaceId = WORKSPACE_ID;
|
|
|
59
59
|
const VOICEOVER_LOCAL_DIR = path.join(WORKSPACE_DIR, 'artifacts', 'audio');
|
|
60
60
|
const VIDEO_COMPOSE_LOCAL_DIR = path.join(WORKSPACE_DIR, 'artifacts', 'video');
|
|
61
61
|
const DEFAULT_OUTRO_PATH = path.join(homedir(), '.lightcone', 'assets', 'outros', 'default.mp4');
|
|
62
|
+
const CVMAX_WORKSPACE_ID = 'ae63cc9e-feff-4d7e-a62e-a7a7c5fd69d9';
|
|
63
|
+
const CVMAX_EDITOR_IN_CHIEF_AGENT_ID = '91a45fd7-ce5f-4da6-9b27-e34bf7b7c0e2';
|
|
64
|
+
const CVMAX_EDITOR_BLOCKED_VIDEO_TOOLS = new Set([
|
|
65
|
+
'generate_voiceover',
|
|
66
|
+
'record_url_narration',
|
|
67
|
+
'compose_video',
|
|
68
|
+
'submit_to_library',
|
|
69
|
+
]);
|
|
62
70
|
|
|
63
71
|
function dataUrlSummary(content) {
|
|
64
72
|
if (typeof content !== 'string' || !content.startsWith('data:')) return null;
|
|
@@ -83,6 +91,25 @@ function formatBytes(bytes) {
|
|
|
83
91
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
84
92
|
}
|
|
85
93
|
|
|
94
|
+
function isBlockedCvmaxEditorVideoTool(toolName) {
|
|
95
|
+
return currentWorkspaceId === CVMAX_WORKSPACE_ID
|
|
96
|
+
&& AGENT_ID === CVMAX_EDITOR_IN_CHIEF_AGENT_ID
|
|
97
|
+
&& CVMAX_EDITOR_BLOCKED_VIDEO_TOOLS.has(toolName);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cvmaxEditorVideoToolError(toolName) {
|
|
101
|
+
return {
|
|
102
|
+
isError: true,
|
|
103
|
+
content: [{
|
|
104
|
+
type: 'text',
|
|
105
|
+
text:
|
|
106
|
+
`Error: ${toolName} blocked for editor_in_chief in CvMax. ` +
|
|
107
|
+
'In this workspace, @short_video_scripter owns video production. ' +
|
|
108
|
+
'editor_in_chief may route, review, or assist with OCR/verification, but must not run video production tools directly.',
|
|
109
|
+
}],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
function normalizeVoiceFormat(value) {
|
|
87
114
|
const normalized = String(value ?? '').trim().toLowerCase();
|
|
88
115
|
if (!normalized) return 'mp3';
|
|
@@ -1345,6 +1372,9 @@ server.tool('generate_voiceover',
|
|
|
1345
1372
|
credential_id: z.string().optional().describe('Optional explicit credential id. If omitted, uses latest granted tts_provider credential.'),
|
|
1346
1373
|
},
|
|
1347
1374
|
async ({ workspace_id, text, voice_preset, speed, format, credential_id }) => {
|
|
1375
|
+
if (isBlockedCvmaxEditorVideoTool('generate_voiceover')) {
|
|
1376
|
+
return cvmaxEditorVideoToolError('generate_voiceover');
|
|
1377
|
+
}
|
|
1348
1378
|
const targetWorkspaceId = (workspace_id ?? currentWorkspaceId ?? WORKSPACE_ID ?? '').trim();
|
|
1349
1379
|
if (!targetWorkspaceId) {
|
|
1350
1380
|
return { isError: true, content: [{ type: 'text', text: 'workspace_id is required (no current workspace context).' }] };
|
|
@@ -1425,13 +1455,18 @@ server.tool('record_url_narration',
|
|
|
1425
1455
|
fps: z.number().optional().describe('Default 30. Do not change unless needed.'),
|
|
1426
1456
|
settle_ms: z.number().optional().describe('Default 4000. Settle wait after navigation before recording starts.'),
|
|
1427
1457
|
},
|
|
1428
|
-
async (args) =>
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1458
|
+
async (args) => {
|
|
1459
|
+
if (isBlockedCvmaxEditorVideoTool('record_url_narration')) {
|
|
1460
|
+
return cvmaxEditorVideoToolError('record_url_narration');
|
|
1461
|
+
}
|
|
1462
|
+
return runRecordUrlNarrationTool({
|
|
1463
|
+
args,
|
|
1464
|
+
currentWorkspaceId,
|
|
1465
|
+
workspaceDir: WORKSPACE_DIR,
|
|
1466
|
+
runMandatoryLocalToolFn: runMandatoryLocalTool,
|
|
1467
|
+
recordUrlNarrationFn: recordUrlNarration,
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1435
1470
|
);
|
|
1436
1471
|
|
|
1437
1472
|
// ── compose_video ───────────────────────────────────────────────────────────────
|
|
@@ -1449,6 +1484,9 @@ server.tool('compose_video',
|
|
|
1449
1484
|
target: z.enum(['short_video_cn', 'douyin', 'xhs']).optional().describe('Transcode target profile. Defaults to short_video_cn.'),
|
|
1450
1485
|
},
|
|
1451
1486
|
async ({ video_path, audio_segments, events_log, outro_path, target }) => {
|
|
1487
|
+
if (isBlockedCvmaxEditorVideoTool('compose_video')) {
|
|
1488
|
+
return cvmaxEditorVideoToolError('compose_video');
|
|
1489
|
+
}
|
|
1452
1490
|
const composeInput = { video_path, audio_segments, events_log, outro_path, target };
|
|
1453
1491
|
try {
|
|
1454
1492
|
const result = await runMandatoryLocalTool({
|
|
@@ -1547,11 +1585,16 @@ server.tool('submit_to_library',
|
|
|
1547
1585
|
understanding: z.record(z.any()).optional().describe('analyze_page 输出'),
|
|
1548
1586
|
plan: z.record(z.any()).optional().describe('plan_video / detail_sections 输出'),
|
|
1549
1587
|
},
|
|
1550
|
-
async (args) =>
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1588
|
+
async (args) => {
|
|
1589
|
+
if (isBlockedCvmaxEditorVideoTool('submit_to_library')) {
|
|
1590
|
+
return cvmaxEditorVideoToolError('submit_to_library');
|
|
1591
|
+
}
|
|
1592
|
+
return runSubmitToLibraryTool({
|
|
1593
|
+
args,
|
|
1594
|
+
currentWorkspaceId,
|
|
1595
|
+
apiFn: api,
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1555
1598
|
);
|
|
1556
1599
|
|
|
1557
1600
|
// ── register_data_source ───────────────────────────────────────────────────────
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createRequire } from 'module';
|
|
|
4
4
|
import { DaemonConnection } from './connection.js';
|
|
5
5
|
import { AgentManager } from './agent-manager.js';
|
|
6
6
|
import { releaseProfileLocksForProcess } from './profile-lock.js';
|
|
7
|
+
import { resolveLightconeServerUrl } from '../../src/runtime/config.js';
|
|
7
8
|
|
|
8
9
|
const { version } = createRequire(import.meta.url)('../package.json');
|
|
9
10
|
|
|
@@ -39,7 +40,7 @@ if (opts['--help'] || opts['-h']) {
|
|
|
39
40
|
process.exit(0);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const SERVER_URL = String(opts['--server-url'] ||
|
|
43
|
+
const SERVER_URL = String(opts['--server-url'] || resolveLightconeServerUrl()).trim();
|
|
43
44
|
const MACHINE_API_KEY = String(opts['--api-key'] || process.env.MACHINE_API_KEY || '').trim();
|
|
44
45
|
|
|
45
46
|
if (!MACHINE_API_KEY) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function resolveExitOfflineDetail({ code, signal, stopCause }) {
|
|
2
|
+
if (stopCause === 'manual_stop') return '';
|
|
3
|
+
if (stopCause === 'credential_revoked') return 'credential_revoked';
|
|
4
|
+
if (code === 0) return '';
|
|
5
|
+
if (signal === 'SIGTERM') return '';
|
|
6
|
+
if (signal === 'SIGKILL') return 'agent_timeout';
|
|
7
|
+
return 'spawn_session_crashed';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function resolveLifecycleExitState({ runtime, code, signal, stopCause }) {
|
|
11
|
+
const normalizedRuntime = String(runtime ?? '').trim().toLowerCase() || 'claude';
|
|
12
|
+
if (stopCause === 'credential_revoked') {
|
|
13
|
+
return {
|
|
14
|
+
reachability: 'reachable',
|
|
15
|
+
availability: 'paused',
|
|
16
|
+
runtimeState: 'stopped',
|
|
17
|
+
reason: 'credential_revoked',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (stopCause === 'manual_stop') {
|
|
21
|
+
return {
|
|
22
|
+
reachability: 'reachable',
|
|
23
|
+
availability: 'paused',
|
|
24
|
+
runtimeState: 'stopped',
|
|
25
|
+
reason: 'manual_stop',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (normalizedRuntime === 'codex' && code === 0) {
|
|
29
|
+
return {
|
|
30
|
+
reachability: 'reachable',
|
|
31
|
+
availability: 'available',
|
|
32
|
+
runtimeState: 'standby',
|
|
33
|
+
reason: 'normal_exit',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const offlineDetail = resolveExitOfflineDetail({ code, signal, stopCause });
|
|
37
|
+
if (!offlineDetail) {
|
|
38
|
+
return {
|
|
39
|
+
reachability: 'reachable',
|
|
40
|
+
availability: 'available',
|
|
41
|
+
runtimeState: 'stopped',
|
|
42
|
+
reason: signal === 'SIGTERM' ? 'terminated' : 'stopped',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
reachability: 'reachable',
|
|
47
|
+
availability: 'unreachable',
|
|
48
|
+
runtimeState: 'crashed',
|
|
49
|
+
reason: offlineDetail,
|
|
50
|
+
};
|
|
51
|
+
}
|