@lightcone-ai/daemon 0.14.18 → 0.15.0
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/page-understanding/index.js +1 -1
- package/package.json +3 -1
- package/src/_vendor/video/understanding/analyze-page.js +684 -0
- package/src/_vendor/video/understanding/heuristics.js +826 -0
- package/src/_vendor/video/understanding/index.js +11 -0
- package/src/_vendor/video/understanding/llm-client.js +261 -0
- package/src/_vendor/video/understanding/schema.js +254 -0
- package/src/_vendor/video/understanding/site-selectors.js +47 -0
- package/src/agent-manager.js +371 -4
- package/src/capability-probe.js +113 -0
- package/src/chat-bridge.js +15 -1
- package/src/drivers/claude.js +27 -9
- package/src/drivers/codex.js +25 -14
- package/src/index.js +1 -1
- package/src/lifecycle-protocol.js +18 -0
- package/src/mcp-config.js +25 -14
- package/src/runtime-config.js +24 -0
package/src/drivers/claude.js
CHANGED
|
@@ -40,34 +40,46 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
40
40
|
|
|
41
41
|
CRITICAL RULES:
|
|
42
42
|
- Always communicate through ${t("send_message")}. This is your only output method.
|
|
43
|
+
- Plain assistant text is not a user-visible reply. If you need the human or another agent to see something, you must call ${t("send_message")}.
|
|
43
44
|
- Use only the provided MCP tools for messaging — they are already available and ready.
|
|
44
45
|
- Always claim a task via ${t("claim_tasks")} before starting work on it. If the claim fails, move on to a different task.
|
|
45
46
|
|
|
46
47
|
## Startup sequence
|
|
47
48
|
|
|
48
|
-
1. If this turn already includes a concrete incoming message
|
|
49
|
+
1. If this turn already includes a concrete incoming message with \`from=user\`, first decide whether it needs a visible acknowledgment, blocker question, or ownership signal. If it does, send it early with ${t("send_message")} before deep context gathering.
|
|
49
50
|
2. Read MEMORY.md (via ${t("read_memory")}) and then only the additional memory/notes files you need to handle the current turn well.
|
|
50
51
|
3. If there is no concrete incoming message to handle, stop and wait. New messages will be delivered to you automatically via stdin.
|
|
51
|
-
4. When you receive a message,
|
|
52
|
+
4. When you receive a message, first apply the audience-aware rules below before deciding whether to call ${t("send_message")}. Do not treat plain narrative output as a substitute for a visible reply.
|
|
52
53
|
5. **Complete ALL your work before stopping.** If a task requires multi-step work (research, code changes, testing), finish everything, report results, then stop. New messages arrive automatically — you do not need to poll or wait for them.
|
|
53
54
|
|
|
54
55
|
## Messaging
|
|
55
56
|
|
|
56
|
-
Messages you receive have a single
|
|
57
|
+
Messages you receive have a single structured data header followed by the sender/content payload:
|
|
57
58
|
|
|
58
59
|
\`\`\`
|
|
59
|
-
[target=#general msg=a1b2c3d4 time=2026-03-15T01:00:00] @richard: hello everyone
|
|
60
|
-
[target=#general msg=e5f6a7b8 time=2026-03-15T01:00:01
|
|
61
|
-
[target=dm:@richard msg=c9d0e1f2 time=2026-03-15T01:00:02] @richard: hey, can you help?
|
|
62
|
-
[target=#general
|
|
63
|
-
[target=dm:@richard:x9y8z7a0 msg=d7e8f9a0 time=2026-03-15T01:00:04] @richard: DM thread reply
|
|
60
|
+
[from=user user=richard target=#general msg=a1b2c3d4 time=2026-03-15T01:00:00] @richard: hello everyone
|
|
61
|
+
[from=peer-agent agent=Alice target=#general msg=e5f6a7b8 time=2026-03-15T01:00:01] @Alice: hi there
|
|
62
|
+
[from=user user=richard target=dm:@richard msg=c9d0e1f2 time=2026-03-15T01:00:02] @richard: hey, can you help?
|
|
63
|
+
[from=system reason=startup target=#general msg=f3a4b5c6 time=2026-03-15T01:00:03] Agent resumed
|
|
64
64
|
\`\`\`
|
|
65
65
|
|
|
66
66
|
Header fields:
|
|
67
|
+
- \`from=\` — one of \`user\` / \`peer-agent\` / \`system\`.
|
|
67
68
|
- \`target=\` — where the message came from. Reuse as the \`target\` parameter when replying.
|
|
68
69
|
- \`msg=\` — message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
|
|
69
70
|
- \`time=\` — timestamp.
|
|
70
|
-
|
|
71
|
+
|
|
72
|
+
## Audience-aware messaging
|
|
73
|
+
|
|
74
|
+
Each incoming message has a \`from=\` tag and should be handled as follows:
|
|
75
|
+
|
|
76
|
+
- **from=user**: this is a real user request. You MUST acknowledge with ${t("send_message")} before deep context-gathering, even if the acknowledgment is short.
|
|
77
|
+
- **from=peer-agent**: default is **no reply**. Reply only when at least one condition holds:
|
|
78
|
+
1. You were explicitly @mentioned.
|
|
79
|
+
2. You have materially new information that changes execution decisions.
|
|
80
|
+
3. The peer explicitly asked you a concrete question.
|
|
81
|
+
Pure receipts like "收到/已对齐/分工确认" are not requests and should not trigger a reply.
|
|
82
|
+
- **from=system**: internal lifecycle/governance/startup signal. Do not reply unless explicitly instructed.
|
|
71
83
|
|
|
72
84
|
### Sending messages
|
|
73
85
|
|
|
@@ -123,6 +135,12 @@ Only top-level workspace / DM messages can become tasks. Messages inside threads
|
|
|
123
135
|
4. When done, set status to \`in_review\` so a human can validate
|
|
124
136
|
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
125
137
|
|
|
138
|
+
**Primary-agent dispatch hard rule (fail-closed):**
|
|
139
|
+
- If your role is the workspace primary agent/owner and a user sends an execution request, you MUST call \`${t("create_tasks")}\` first and include an explicit \`scenario_type\` before routing work via \`${t("send_message")}\`.
|
|
140
|
+
- Execution requests include requests like content writing, short-video scripting, research, design/asset production, implementation, or any request that requires downstream execution instead of a simple answer.
|
|
141
|
+
- Use \`scenario_type\` values declared by your scenario manifest/dispatch protocol (for example: \`trend_scan\`, \`topic_research\`, \`research\`, \`graphic_writing\`, \`short_video_scripting\`, \`publish\`).
|
|
142
|
+
- Do not route execution work with only \`${t("send_message")}\`: skipping \`${t("create_tasks")}\` can cause downstream \`${t("claim_tasks")}\` failures and deadlock the workflow.
|
|
143
|
+
|
|
126
144
|
**What \`${t("create_tasks")}\` really means:**
|
|
127
145
|
- Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
|
|
128
146
|
- \`${t("create_tasks")}\` is a convenience helper for a specific sequence: create a brand-new message, then publish that new message as a task-message.
|
package/src/drivers/codex.js
CHANGED
|
@@ -39,6 +39,14 @@ function quote(value) {
|
|
|
39
39
|
return JSON.stringify(value);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function formatCodexServerKey(serverKey) {
|
|
43
|
+
const normalizedKey = String(serverKey ?? '').trim();
|
|
44
|
+
if (!normalizedKey) return null;
|
|
45
|
+
return /^[A-Za-z0-9_-]+$/.test(normalizedKey)
|
|
46
|
+
? normalizedKey
|
|
47
|
+
: quote(normalizedKey);
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
function normalizeCodexModel(model) {
|
|
43
51
|
if (!model) return 'gpt-5.2';
|
|
44
52
|
const normalized = String(model).trim().toLowerCase();
|
|
@@ -71,7 +79,6 @@ export function adaptCodexSystemPrompt(sourcePrompt) {
|
|
|
71
79
|
if (!basePrompt.trim()) return '';
|
|
72
80
|
|
|
73
81
|
let prompt = basePrompt
|
|
74
|
-
.replaceAll('mcp__chat__', '')
|
|
75
82
|
.replace(
|
|
76
83
|
'3. If there is no concrete incoming message to handle, stop and wait. New messages will be delivered to you automatically via stdin.',
|
|
77
84
|
'3. If there is no concrete incoming message to handle, stop. The daemon will restart you when new messages arrive.'
|
|
@@ -117,22 +124,24 @@ function normalizeDirectiveMcpServers(value) {
|
|
|
117
124
|
function buildDirectiveMcpFlags(mcpServers) {
|
|
118
125
|
const args = [];
|
|
119
126
|
for (const [serverKey, mc] of Object.entries(mcpServers)) {
|
|
127
|
+
const keyExpr = formatCodexServerKey(serverKey);
|
|
128
|
+
if (!keyExpr) continue;
|
|
120
129
|
const envPairs = Object.entries(mc.env ?? {}).map(([k, v]) => `${k}=${v ?? ''}`);
|
|
121
130
|
if (envPairs.length > 0) {
|
|
122
131
|
args.push(
|
|
123
|
-
'-c', `mcp_servers.${
|
|
124
|
-
'-c', `mcp_servers.${
|
|
125
|
-
'-c', `mcp_servers.${
|
|
132
|
+
'-c', `mcp_servers.${keyExpr}.command=${quote('env')}`,
|
|
133
|
+
'-c', `mcp_servers.${keyExpr}.args=${quote([...envPairs, mc.command, ...(mc.args ?? [])])}`,
|
|
134
|
+
'-c', `mcp_servers.${keyExpr}.enabled=true`
|
|
126
135
|
);
|
|
127
136
|
} else {
|
|
128
137
|
args.push(
|
|
129
|
-
'-c', `mcp_servers.${
|
|
130
|
-
'-c', `mcp_servers.${
|
|
131
|
-
'-c', `mcp_servers.${
|
|
138
|
+
'-c', `mcp_servers.${keyExpr}.command=${quote(mc.command)}`,
|
|
139
|
+
'-c', `mcp_servers.${keyExpr}.args=${quote(mc.args ?? [])}`,
|
|
140
|
+
'-c', `mcp_servers.${keyExpr}.enabled=true`
|
|
132
141
|
);
|
|
133
142
|
}
|
|
134
143
|
if (mc.required) {
|
|
135
|
-
args.push('-c', `mcp_servers.${
|
|
144
|
+
args.push('-c', `mcp_servers.${keyExpr}.required=true`);
|
|
136
145
|
}
|
|
137
146
|
}
|
|
138
147
|
return args;
|
|
@@ -206,19 +215,21 @@ export function buildCodexSpawn({
|
|
|
206
215
|
});
|
|
207
216
|
|
|
208
217
|
for (const [serverKey, mc] of Object.entries(skillMcpServers)) {
|
|
218
|
+
const keyExpr = formatCodexServerKey(serverKey);
|
|
219
|
+
if (!keyExpr) continue;
|
|
209
220
|
const envPairs = Object.entries(mc.env ?? {}).map(([k, v]) => `${k}=${v ?? ''}`);
|
|
210
221
|
if (envPairs.length > 0) {
|
|
211
222
|
args.push(
|
|
212
|
-
'-c', `mcp_servers.${
|
|
213
|
-
'-c', `mcp_servers.${
|
|
214
|
-
'-c', `mcp_servers.${
|
|
223
|
+
'-c', `mcp_servers.${keyExpr}.command=${quote('env')}`,
|
|
224
|
+
'-c', `mcp_servers.${keyExpr}.args=${quote([...envPairs, mc.command, ...(mc.args ?? [])])}`,
|
|
225
|
+
'-c', `mcp_servers.${keyExpr}.enabled=true`
|
|
215
226
|
);
|
|
216
227
|
continue;
|
|
217
228
|
}
|
|
218
229
|
args.push(
|
|
219
|
-
'-c', `mcp_servers.${
|
|
220
|
-
'-c', `mcp_servers.${
|
|
221
|
-
'-c', `mcp_servers.${
|
|
230
|
+
'-c', `mcp_servers.${keyExpr}.command=${quote(mc.command)}`,
|
|
231
|
+
'-c', `mcp_servers.${keyExpr}.args=${quote(mc.args ?? [])}`,
|
|
232
|
+
'-c', `mcp_servers.${keyExpr}.enabled=true`
|
|
222
233
|
);
|
|
223
234
|
}
|
|
224
235
|
|
package/src/index.js
CHANGED
|
@@ -4,7 +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 '
|
|
7
|
+
import { resolveLightconeServerUrl } from './runtime-config.js';
|
|
8
8
|
|
|
9
9
|
const { version } = createRequire(import.meta.url)('../package.json');
|
|
10
10
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export function resolveExitOfflineDetail({ code, signal, stopCause }) {
|
|
2
2
|
if (stopCause === 'manual_stop') return '';
|
|
3
3
|
if (stopCause === 'credential_revoked') return 'credential_revoked';
|
|
4
|
+
if (stopCause === 'contract_violation') return 'contract_violation:visible_text_without_send_message';
|
|
5
|
+
if (stopCause === 'outbound_rate_limited') return 'outbound_rate_limited';
|
|
4
6
|
if (code === 0) return '';
|
|
5
7
|
if (signal === 'SIGTERM') return '';
|
|
6
8
|
if (signal === 'SIGKILL') return 'agent_timeout';
|
|
@@ -25,6 +27,22 @@ export function resolveLifecycleExitState({ runtime, code, signal, stopCause })
|
|
|
25
27
|
reason: 'manual_stop',
|
|
26
28
|
};
|
|
27
29
|
}
|
|
30
|
+
if (stopCause === 'contract_violation') {
|
|
31
|
+
return {
|
|
32
|
+
reachability: 'reachable',
|
|
33
|
+
availability: 'available',
|
|
34
|
+
runtimeState: 'standby',
|
|
35
|
+
reason: 'contract_violation:visible_text_without_send_message',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (stopCause === 'outbound_rate_limited') {
|
|
39
|
+
return {
|
|
40
|
+
reachability: 'reachable',
|
|
41
|
+
availability: 'paused',
|
|
42
|
+
runtimeState: 'stopped',
|
|
43
|
+
reason: 'outbound_rate_limited',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
28
46
|
if (normalizedRuntime === 'codex' && code === 0) {
|
|
29
47
|
return {
|
|
30
48
|
reachability: 'reachable',
|
package/src/mcp-config.js
CHANGED
|
@@ -23,8 +23,14 @@ function resolveMcpPathToken(arg, mcpPaths = {}) {
|
|
|
23
23
|
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) return null;
|
|
24
24
|
|
|
25
25
|
const legacyId = LEGACY_MCP_PATH_TOKENS[trimmed] ?? null;
|
|
26
|
+
const legacyUnderscoreMatch = trimmed.match(/^\{([a-z0-9_]+)_mcp_path\}$/i);
|
|
27
|
+
const legacyUnderscoreId = legacyUnderscoreMatch
|
|
28
|
+
? legacyUnderscoreMatch[1].toLowerCase().replaceAll('_', '-')
|
|
29
|
+
: null;
|
|
26
30
|
const dynamicMatch = trimmed.match(/^\{mcp_path:([a-z0-9][a-z0-9_-]*)\}$/i);
|
|
27
|
-
const serverId = legacyId
|
|
31
|
+
const serverId = legacyId
|
|
32
|
+
?? legacyUnderscoreId
|
|
33
|
+
?? (dynamicMatch ? dynamicMatch[1].toLowerCase() : null);
|
|
28
34
|
if (!serverId) return null;
|
|
29
35
|
|
|
30
36
|
const runtimeMcpPath = typeof mcpPaths?.[serverId] === 'string'
|
|
@@ -99,20 +105,25 @@ export function buildSkillMcpServers({
|
|
|
99
105
|
const mc = skill.mcpConfig;
|
|
100
106
|
if (mcpServers[mc.server]) continue;
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
try {
|
|
109
|
+
const resolvedArgs = (mc.args ?? []).map(arg => resolveSkillArg(arg, config));
|
|
110
|
+
const resolvedEnv = {};
|
|
111
|
+
for (const envKey of (mc.env ?? [])) {
|
|
112
|
+
resolvedEnv[envKey] = agentEnv[envKey] ?? process.env[envKey] ?? '';
|
|
113
|
+
}
|
|
107
114
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
mcpServers[mc.server] = {
|
|
116
|
+
command: mc.command,
|
|
117
|
+
args: resolvedArgs,
|
|
118
|
+
env: {
|
|
119
|
+
...resolvedEnv,
|
|
120
|
+
...baseEnvForServer(mc.server, { serverUrl, authToken, agentId, workspaceId, workspaceDir }),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
} catch (error) {
|
|
124
|
+
const message = error instanceof Error ? error.message : String(error ?? 'unknown_error');
|
|
125
|
+
console.warn(`[mcp-config] Skipping MCP server '${mc.server}' due to invalid skill config: ${message}`);
|
|
126
|
+
}
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
return mcpServers;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const DEFAULT_LIGHTCONE_PORT = 9779;
|
|
2
|
+
const DEFAULT_OUTBOUND_WINDOW_MS = 60_000;
|
|
3
|
+
const DEFAULT_OUTBOUND_SOFT_CAP = 5;
|
|
4
|
+
const DEFAULT_OUTBOUND_HARD_CAP = 10;
|
|
5
|
+
const DEFAULT_OUTBOUND_COOLDOWN_MS = 5 * 60_000;
|
|
6
|
+
|
|
7
|
+
function resolvePositiveInt(value, fallback) {
|
|
8
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
9
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
10
|
+
return parsed;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function resolveLightconeServerUrl(env = process.env) {
|
|
14
|
+
return env.SERVER_URL || `http://localhost:${DEFAULT_LIGHTCONE_PORT}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveOutboundRateLimitConfig(env = process.env) {
|
|
18
|
+
return {
|
|
19
|
+
windowMs: resolvePositiveInt(env.AGENT_OUTBOUND_RATE_WINDOW_MS, DEFAULT_OUTBOUND_WINDOW_MS),
|
|
20
|
+
softCap: resolvePositiveInt(env.AGENT_OUTBOUND_RATE_SOFT_CAP, DEFAULT_OUTBOUND_SOFT_CAP),
|
|
21
|
+
hardCap: resolvePositiveInt(env.AGENT_OUTBOUND_RATE_HARD_CAP, DEFAULT_OUTBOUND_HARD_CAP),
|
|
22
|
+
cooldownMs: resolvePositiveInt(env.AGENT_OUTBOUND_RATE_COOLDOWN_MS, DEFAULT_OUTBOUND_COOLDOWN_MS),
|
|
23
|
+
};
|
|
24
|
+
}
|