@lightcone-ai/daemon 0.15.57 → 0.15.59
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.
|
@@ -4,6 +4,7 @@ export const DEFAULT_LIMIT = 200;
|
|
|
4
4
|
export const MAX_LIMIT = 1000;
|
|
5
5
|
|
|
6
6
|
const READ_QUERY_PREFIX = /^(SELECT|SHOW|DESCRIBE|DESC|EXPLAIN|WITH)\b/i;
|
|
7
|
+
const AUTO_LIMIT_PREFIX = /^(SELECT|WITH)\b/i;
|
|
7
8
|
const REQUIRED_DB_KEYS = ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME'];
|
|
8
9
|
|
|
9
10
|
function trimTrailingSemicolons(sql) {
|
|
@@ -27,6 +28,10 @@ export function isReadQuery(sql) {
|
|
|
27
28
|
return READ_QUERY_PREFIX.test(sql);
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export function supportsAutoLimit(sql) {
|
|
32
|
+
return AUTO_LIMIT_PREFIX.test(sql);
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
export function hasLimitClause(sql) {
|
|
31
36
|
return /\bLIMIT\b/i.test(sql);
|
|
32
37
|
}
|
|
@@ -36,7 +41,7 @@ export function prepareSql(sql, limit) {
|
|
|
36
41
|
if (!normalizedSql) throw new Error('sql must be a non-empty string');
|
|
37
42
|
|
|
38
43
|
const effectiveLimit = normalizeLimit(limit);
|
|
39
|
-
if (!
|
|
44
|
+
if (!supportsAutoLimit(normalizedSql) || hasLimitClause(normalizedSql)) {
|
|
40
45
|
return {
|
|
41
46
|
sql: normalizedSql,
|
|
42
47
|
limit: effectiveLimit,
|
|
@@ -138,13 +138,14 @@ export async function callOfficialTool({
|
|
|
138
138
|
toolName,
|
|
139
139
|
argumentsPayload = {},
|
|
140
140
|
timeoutMs = 10000,
|
|
141
|
+
env = {},
|
|
141
142
|
}) {
|
|
142
|
-
const entrypointPath = resolveMcpServerEntrypoint(serverId
|
|
143
|
+
const entrypointPath = resolveMcpServerEntrypoint(serverId);
|
|
143
144
|
if (!entrypointPath) {
|
|
144
145
|
throw new Error(`official_server_not_found:${serverId}`);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
const client = new StdioJsonRpcClient(process.execPath, [entrypointPath]);
|
|
148
|
+
const client = new StdioJsonRpcClient(process.execPath, [entrypointPath], env);
|
|
148
149
|
try {
|
|
149
150
|
await client.start();
|
|
150
151
|
await client.request('initialize', {
|
package/package.json
CHANGED
package/src/agent-manager.js
CHANGED
|
@@ -183,6 +183,7 @@ export class AgentManager {
|
|
|
183
183
|
switch (msg.type) {
|
|
184
184
|
case 'agent:start': return this._startAgent(msg, connection);
|
|
185
185
|
case 'agent:stop': return this._stopAgent(msg.agentId, msg.workspaceId, connection);
|
|
186
|
+
case 'agent:cancel_turn': return this._cancelAgentTurn(msg.agentId, msg.workspaceId, connection);
|
|
186
187
|
case 'agent:deliver': return this._deliverMessage(msg, connection);
|
|
187
188
|
case 'agent:reprobe': return this._reprobeCapability(msg, connection);
|
|
188
189
|
case 'publish:job': return this._handlePublishJob(msg, connection);
|
|
@@ -1714,6 +1715,61 @@ export class AgentManager {
|
|
|
1714
1715
|
// exit handler will report status
|
|
1715
1716
|
}
|
|
1716
1717
|
|
|
1718
|
+
_cancelAgentTurn(agentId, workspaceId, connection) {
|
|
1719
|
+
const key = this._key(agentId, workspaceId);
|
|
1720
|
+
const queued = this._pendingMessages?.get(key)?.length ?? 0;
|
|
1721
|
+
if (queued > 0) {
|
|
1722
|
+
this._pendingMessages.delete(key);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
const agent = this.agents.get(key);
|
|
1726
|
+
if (!agent?.proc) {
|
|
1727
|
+
console.log(`[AgentManager] Cancel turn requested for inactive agent ${agentId} workspace=${workspaceId ?? 'none'} queued_cleared=${queued}`);
|
|
1728
|
+
connection.send({
|
|
1729
|
+
type: 'agent:cancel_turn:ack',
|
|
1730
|
+
agentId,
|
|
1731
|
+
workspaceId,
|
|
1732
|
+
ok: queued > 0,
|
|
1733
|
+
queuedCleared: queued,
|
|
1734
|
+
running: false,
|
|
1735
|
+
});
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
console.log(`[AgentManager] Cancelling current turn for agent ${agentId} workspace=${workspaceId ?? 'none'} queued_cleared=${queued}`);
|
|
1740
|
+
agent.stopCause = 'user_cancelled';
|
|
1741
|
+
connection.send({
|
|
1742
|
+
type: 'agent:cancel_turn:ack',
|
|
1743
|
+
agentId,
|
|
1744
|
+
workspaceId,
|
|
1745
|
+
ok: true,
|
|
1746
|
+
queuedCleared: queued,
|
|
1747
|
+
running: true,
|
|
1748
|
+
});
|
|
1749
|
+
this._emitLifecycle(connection, {
|
|
1750
|
+
agentId,
|
|
1751
|
+
workspaceId,
|
|
1752
|
+
runtime: agent.runtime ?? 'claude',
|
|
1753
|
+
reachability: 'reachable',
|
|
1754
|
+
availability: 'available',
|
|
1755
|
+
runtimeState: 'running',
|
|
1756
|
+
reason: 'user_cancelled',
|
|
1757
|
+
sessionId: agent.sessionId ?? null,
|
|
1758
|
+
});
|
|
1759
|
+
|
|
1760
|
+
try {
|
|
1761
|
+
agent.proc.kill('SIGINT');
|
|
1762
|
+
} catch (err) {
|
|
1763
|
+
console.error(`[AgentManager] cancel SIGINT failed for ${key}:`, err?.message ?? err);
|
|
1764
|
+
}
|
|
1765
|
+
setTimeout(() => {
|
|
1766
|
+
if (this.agents.get(key) !== agent) return;
|
|
1767
|
+
try {
|
|
1768
|
+
agent.proc.kill('SIGTERM');
|
|
1769
|
+
} catch {}
|
|
1770
|
+
}, 1500);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1717
1773
|
_deliverMessage(msg, connection) {
|
|
1718
1774
|
const { agentId, workspaceId, seq, message } = msg;
|
|
1719
1775
|
const key = this._key(agentId, workspaceId);
|
package/src/chat-bridge.js
CHANGED
|
@@ -141,6 +141,7 @@ const DEFAULT_TOOL_CLASSIFICATION = {
|
|
|
141
141
|
write_governance_correction: 'local',
|
|
142
142
|
get_orchestrate_context: 'local',
|
|
143
143
|
complete_orchestrate_trigger: 'local',
|
|
144
|
+
propose_goal_candidate: 'local',
|
|
144
145
|
|
|
145
146
|
send_message: 'mandatory',
|
|
146
147
|
create_tasks: 'mandatory',
|
|
@@ -302,6 +303,7 @@ function inferToolForApi(method, apiPath, body) {
|
|
|
302
303
|
if (method === 'POST' && cleanPath === '/orchestrate/correction') return 'write_governance_correction';
|
|
303
304
|
if (method === 'GET' && cleanPath === '/orchestrate/context') return 'get_orchestrate_context';
|
|
304
305
|
if (method === 'POST' && cleanPath === '/orchestrate/complete') return 'complete_orchestrate_trigger';
|
|
306
|
+
if (method === 'POST' && cleanPath === '/orchestrate/propose-goal-candidate') return 'propose_goal_candidate';
|
|
305
307
|
if (method === 'POST' && cleanPath === '/context-proposals') return 'promote_context';
|
|
306
308
|
|
|
307
309
|
if (method === 'GET' && cleanPath === '/host/scenarios/search') return 'search_scenarios';
|
|
@@ -1747,6 +1749,49 @@ server.tool('get_orchestrate_context',
|
|
|
1747
1749
|
}
|
|
1748
1750
|
);
|
|
1749
1751
|
|
|
1752
|
+
// ── propose_goal_candidate ────────────────────────────────────────────────────
|
|
1753
|
+
server.tool('propose_goal_candidate',
|
|
1754
|
+
'Orchestrator-only: propose a goal_state_candidate for human review. Use when D1 (goal_extraction_opportunity) fires and you have extracted a goal/constraint shape from the user\'s recent messages. The candidate enters the existing candidate→confirm→promote flow; ONLY Human can promote it to active goal_states. Always include `source_excerpts` (verbatim user quotes the proposal is derived from) so the confirm card can show provenance.',
|
|
1755
|
+
{
|
|
1756
|
+
workspace_id: z.string().describe('Target workspace id; you must be the registered orchestrator for it.'),
|
|
1757
|
+
goal: z.record(z.any()).describe('Proposed goal object. Required: a non-empty object describing the workspace goal, e.g. { title, description, kpis, ... }. Be concrete — vague platitudes like "comply with brand" are useless.'),
|
|
1758
|
+
constraints: z.array(z.any()).optional().describe('Proposed constraints array (optional). Each item should be enforceable by detector A4 / worker review, not a slogan.'),
|
|
1759
|
+
current_phase: z.string().optional().describe('Current phase label (optional, e.g. "exploration" / "stable_publish").'),
|
|
1760
|
+
source_excerpts: z.array(z.string()).describe('Verbatim user quotes the proposal is derived from. REQUIRED for non-trivial proposals — the confirm card surfaces these to the user as provenance.'),
|
|
1761
|
+
source_risk: z.enum(['trusted', 'normal', 'untrusted', 'hostile']).optional().describe('Source risk classification: "trusted" if quoting user directly, "normal" if interpreting user intent, "untrusted" if drawing from worker output / external content. Default normal.'),
|
|
1762
|
+
trigger_id: z.string().optional().describe('Originating trigger id, e.g. "D1". Default "D1".'),
|
|
1763
|
+
detector_version: z.string().optional().describe('Detector version that fired, e.g. "rule-v1".'),
|
|
1764
|
+
reason: z.string().optional().describe('Human-readable rationale for the proposal.'),
|
|
1765
|
+
expires_in_minutes: z.number().int().optional().describe('Candidate TTL in minutes (default 30, max 7 days).'),
|
|
1766
|
+
},
|
|
1767
|
+
async ({ workspace_id, goal, constraints, current_phase, source_excerpts, source_risk, trigger_id, detector_version, reason, expires_in_minutes }) => {
|
|
1768
|
+
try {
|
|
1769
|
+
const data = await api('POST', '/orchestrate/propose-goal-candidate', {
|
|
1770
|
+
workspaceId: workspace_id,
|
|
1771
|
+
goal,
|
|
1772
|
+
constraints,
|
|
1773
|
+
currentPhase: current_phase,
|
|
1774
|
+
sourceExcerpts: source_excerpts,
|
|
1775
|
+
sourceRisk: source_risk,
|
|
1776
|
+
triggerId: trigger_id,
|
|
1777
|
+
detectorVersion: detector_version,
|
|
1778
|
+
reason,
|
|
1779
|
+
expiresInMinutes: expires_in_minutes,
|
|
1780
|
+
});
|
|
1781
|
+
const c = data?.candidate;
|
|
1782
|
+
return {
|
|
1783
|
+
content: [{
|
|
1784
|
+
type: 'text',
|
|
1785
|
+
text: `Goal candidate proposed. id=${c?.id} status=${c?.status} expires_at=${c?.expiresAt}\n`
|
|
1786
|
+
+ `Awaiting human confirm via UI; no active goal_states change yet.`,
|
|
1787
|
+
}],
|
|
1788
|
+
};
|
|
1789
|
+
} catch (err) {
|
|
1790
|
+
return { isError: true, content: [{ type: 'text', text: `Error: ${err.message}` }] };
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
);
|
|
1794
|
+
|
|
1750
1795
|
// ── complete_orchestrate_trigger ──────────────────────────────────────────────
|
|
1751
1796
|
server.tool('complete_orchestrate_trigger',
|
|
1752
1797
|
'Orchestrator-only: mark a processing orchestrator trigger as completed (status processing→completed) and reset its circuit breaker. Call this once you have written all governance actions for the current trigger.',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export function resolveExitOfflineDetail({ code, signal, stopCause }) {
|
|
2
2
|
if (stopCause === 'manual_stop') return '';
|
|
3
|
+
if (stopCause === 'user_cancelled') return '';
|
|
3
4
|
if (stopCause === 'credential_revoked') return 'credential_revoked';
|
|
4
5
|
if (stopCause === 'contract_violation') return 'contract_violation:visible_text_without_send_message';
|
|
5
6
|
if (stopCause === 'outbound_rate_limited') return 'outbound_rate_limited';
|
|
@@ -27,6 +28,14 @@ export function resolveLifecycleExitState({ runtime, code, signal, stopCause })
|
|
|
27
28
|
reason: 'manual_stop',
|
|
28
29
|
};
|
|
29
30
|
}
|
|
31
|
+
if (stopCause === 'user_cancelled') {
|
|
32
|
+
return {
|
|
33
|
+
reachability: 'reachable',
|
|
34
|
+
availability: 'available',
|
|
35
|
+
runtimeState: 'stopped',
|
|
36
|
+
reason: 'user_cancelled',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
30
39
|
if (stopCause === 'contract_violation') {
|
|
31
40
|
return {
|
|
32
41
|
reachability: 'reachable',
|
|
@@ -608,7 +608,14 @@ export async function runPublishJob({ serverUrl, machineApiKey, agentId, workspa
|
|
|
608
608
|
text,
|
|
609
609
|
tags,
|
|
610
610
|
payload,
|
|
611
|
-
callTool: callOfficialTool
|
|
611
|
+
callTool: (toolArgs) => callOfficialTool({
|
|
612
|
+
...toolArgs,
|
|
613
|
+
env: {
|
|
614
|
+
SERVER_URL: serverUrl,
|
|
615
|
+
MACHINE_API_KEY: machineApiKey,
|
|
616
|
+
AGENT_ID: agentId,
|
|
617
|
+
},
|
|
618
|
+
}),
|
|
612
619
|
});
|
|
613
620
|
} catch (error) {
|
|
614
621
|
emitPublishJobProgress(onProgress, 'precheck_error', {
|