titan-agent 5.4.0 → 5.4.2
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/dist/agent/agent.js +1 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/agentLoop.js +77 -12
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/agentWakeup.js +8 -3
- package/dist/agent/agentWakeup.js.map +1 -1
- package/dist/agent/commandPost.js +6 -1
- package/dist/agent/commandPost.js.map +1 -1
- package/dist/agent/heartbeatScheduler.js +36 -4
- package/dist/agent/heartbeatScheduler.js.map +1 -1
- package/dist/agent/toolRunner.js +30 -0
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/config/config.js +30 -8
- package/dist/config/config.js.map +1 -1
- package/dist/config/schema.js +10 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/eval/record.js +1 -1
- package/dist/eval/record.js.map +1 -1
- package/dist/gateway/server.js +26 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/mesh/transport.js +60 -8
- package/dist/mesh/transport.js.map +1 -1
- package/dist/providers/anthropic.js +3 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/google.js +94 -20
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/modelCapabilities.js +59 -0
- package/dist/providers/modelCapabilities.js.map +1 -0
- package/dist/providers/ollama.js +3 -2
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.js +4 -3
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openai_compat.js +3 -2
- package/dist/providers/openai_compat.js.map +1 -1
- package/dist/providers/router.js +63 -21
- package/dist/providers/router.js.map +1 -1
- package/dist/skills/registry.js +176 -163
- package/dist/skills/registry.js.map +1 -1
- package/dist/telemetry/activityLog.js +1 -1
- package/dist/telemetry/activityLog.js.map +1 -1
- package/dist/utils/constants.js +2 -2
- package/dist/utils/constants.js.map +1 -1
- package/docs/AGENT-HIERARCHY.md +154 -0
- package/docs/superpowers/plans/2026-04-29-titan-production-fix.md +241 -0
- package/package.json +2 -2
- package/scripts/start-workers.sh +39 -0
- package/scripts/task-feeder.ts +38 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { titanEvents } from "./daemon.js";
|
|
3
3
|
import logger from "../utils/logger.js";
|
|
4
|
+
import { listAgents } from "./multiAgent.js";
|
|
4
5
|
const COMPONENT = "HeartbeatScheduler";
|
|
5
6
|
const schedules = /* @__PURE__ */ new Map();
|
|
6
7
|
let running = false;
|
|
7
8
|
let tickInterval = null;
|
|
9
|
+
let spawnListener = null;
|
|
8
10
|
function cronMatchesNow(expression) {
|
|
9
11
|
const parts = expression.trim().split(/\s+/);
|
|
10
12
|
if (parts.length < 5) return false;
|
|
@@ -63,6 +65,18 @@ function getNextFireTime(expression) {
|
|
|
63
65
|
function initHeartbeatScheduler() {
|
|
64
66
|
if (running) return;
|
|
65
67
|
running = true;
|
|
68
|
+
for (const agent of listAgents()) {
|
|
69
|
+
if (agent.status === "running" && !schedules.has(agent.id)) {
|
|
70
|
+
scheduleAgent(agent.id, "*/1 * * * *");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
spawnListener = (data) => {
|
|
74
|
+
if (!schedules.has(data.id)) {
|
|
75
|
+
scheduleAgent(data.id, "*/1 * * * *");
|
|
76
|
+
logger.info(COMPONENT, `Auto-scheduled newly spawned agent "${data.name}" (${data.id})`);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
titanEvents.on("agent:spawned", spawnListener);
|
|
66
80
|
tickInterval = setInterval(() => {
|
|
67
81
|
for (const [agentId, entry] of schedules) {
|
|
68
82
|
if (cronMatchesNow(entry.cronExpression)) {
|
|
@@ -108,7 +122,7 @@ function rescheduleAgent(agentId, cronExpression) {
|
|
|
108
122
|
async function fireHeartbeat(agentId) {
|
|
109
123
|
const entry = schedules.get(agentId);
|
|
110
124
|
logger.info(COMPONENT, `Firing heartbeat for agent "${agentId}"`);
|
|
111
|
-
const { listIssues, getRegisteredAgents, getIssueComments
|
|
125
|
+
const { listIssues, getRegisteredAgents, getIssueComments } = await import("./commandPost.js");
|
|
112
126
|
const { queueWakeup } = await import("./agentWakeup.js");
|
|
113
127
|
const agents = getRegisteredAgents();
|
|
114
128
|
const agent = agents.find((a) => a.id === agentId);
|
|
@@ -116,7 +130,22 @@ async function fireHeartbeat(agentId) {
|
|
|
116
130
|
logger.warn(COMPONENT, `Agent "${agentId}" not found in registry`);
|
|
117
131
|
return;
|
|
118
132
|
}
|
|
119
|
-
|
|
133
|
+
let assignedIssues = listIssues({ assigneeAgentId: agentId }).filter((i) => i.status === "todo" || i.status === "backlog" || i.status === "in_progress");
|
|
134
|
+
if (assignedIssues.length === 0) {
|
|
135
|
+
const { checkoutIssue } = await import("./commandPost.js");
|
|
136
|
+
const backlog = listIssues({ status: "backlog" }).filter((i) => !i.assigneeAgentId).sort((a, b) => {
|
|
137
|
+
const priorityOrder2 = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
138
|
+
return (priorityOrder2[a.priority] ?? 3) - (priorityOrder2[b.priority] ?? 3);
|
|
139
|
+
});
|
|
140
|
+
for (const issue2 of backlog) {
|
|
141
|
+
const checkedOut = checkoutIssue(issue2.id, agentId);
|
|
142
|
+
if (checkedOut) {
|
|
143
|
+
assignedIssues = [checkedOut];
|
|
144
|
+
logger.info(COMPONENT, `Auto-checked out issue ${issue2.identifier} to agent "${agentId}"`);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
120
149
|
if (assignedIssues.length === 0) {
|
|
121
150
|
logger.debug(COMPONENT, `No actionable issues for agent "${agentId}" \u2014 skipping heartbeat`);
|
|
122
151
|
return;
|
|
@@ -130,9 +159,7 @@ async function fireHeartbeat(agentId) {
|
|
|
130
159
|
const recentComments = issueComments.slice(-5);
|
|
131
160
|
contextFromComments = "\n\n## Previous Work on This Issue\n" + recentComments.map((c) => `[${c.createdAt}] ${c.authorAgentId || c.authorUser}: ${c.body.slice(0, 300)}`).join("\n");
|
|
132
161
|
}
|
|
133
|
-
const ancestryCtx = buildAncestryContext(issue.id);
|
|
134
162
|
const enrichedTask = [
|
|
135
|
-
ancestryCtx,
|
|
136
163
|
contextFromComments,
|
|
137
164
|
"",
|
|
138
165
|
"---",
|
|
@@ -149,6 +176,7 @@ Description: ${issue.description}` : ""
|
|
|
149
176
|
parentSessionId: null,
|
|
150
177
|
task: enrichedTask,
|
|
151
178
|
templateName: "",
|
|
179
|
+
model: agent.model,
|
|
152
180
|
mode: "sub-agent"
|
|
153
181
|
// Default; could be overridden per-agent in the future
|
|
154
182
|
});
|
|
@@ -176,6 +204,10 @@ function shutdownHeartbeatScheduler() {
|
|
|
176
204
|
for (const entry of schedules.values()) {
|
|
177
205
|
if (entry.task) clearTimeout(entry.task);
|
|
178
206
|
}
|
|
207
|
+
if (spawnListener) {
|
|
208
|
+
titanEvents.off("agent:spawned", spawnListener);
|
|
209
|
+
spawnListener = null;
|
|
210
|
+
}
|
|
179
211
|
schedules.clear();
|
|
180
212
|
running = false;
|
|
181
213
|
logger.info(COMPONENT, "Heartbeat scheduler shut down");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/heartbeatScheduler.ts"],"sourcesContent":["/**\n * TITAN — Heartbeat Scheduler\n * Wake external agents on schedule, resume their context from issue history.\n * Uses node-cron for scheduling. Each agent with a `schedule` field gets a cron job.\n */\nimport { titanEvents } from './daemon.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'HeartbeatScheduler';\n\ninterface ScheduleEntry {\n agentId: string;\n cronExpression: string;\n task: ReturnType<typeof setTimeout> | null;\n nextFireAt: string | null;\n lastFiredAt: string | null;\n fireCount: number;\n}\n\nconst schedules = new Map<string, ScheduleEntry>();\nlet running = false;\nlet tickInterval: ReturnType<typeof setInterval> | null = null;\n\n// ─── Cron Parser (simple, no external dependency) ────────────────────────\n\n/** Parse a simple cron expression (minute hour day month weekday) and check if it matches now */\nfunction cronMatchesNow(expression: string): boolean {\n const parts = expression.trim().split(/\\s+/);\n if (parts.length < 5) return false;\n\n const now = new Date();\n const fields = [\n now.getMinutes(), // minute\n now.getHours(), // hour\n now.getDate(), // day of month\n now.getMonth() + 1, // month (1-12)\n now.getDay(), // day of week (0=Sun)\n ];\n\n return parts.every((part, i) => matchesCronField(part, fields[i]));\n}\n\nfunction matchesCronField(pattern: string, value: number): boolean {\n if (pattern === '*') return true;\n\n // Handle step values: */5, 1-30/5\n if (pattern.includes('/')) {\n const [rangePart, stepStr] = pattern.split('/');\n const step = parseInt(stepStr, 10);\n if (isNaN(step) || step <= 0) return false;\n\n if (rangePart === '*') return value % step === 0;\n\n const [start] = rangePart.split('-').map(Number);\n return value >= start && (value - start) % step === 0;\n }\n\n // Handle ranges: 1-5\n if (pattern.includes('-')) {\n const [start, end] = pattern.split('-').map(Number);\n return value >= start && value <= end;\n }\n\n // Handle lists: 1,3,5\n if (pattern.includes(',')) {\n return pattern.split(',').map(Number).includes(value);\n }\n\n // Exact match\n return parseInt(pattern, 10) === value;\n}\n\n/** Calculate the next fire time for a cron expression (approximate — next minute check) */\nfunction getNextFireTime(expression: string): string {\n const now = new Date();\n // Check next 1440 minutes (24 hours)\n for (let i = 1; i <= 1440; i++) {\n const candidate = new Date(now.getTime() + i * 60000);\n // Temporarily check if expression matches this candidate\n const parts = expression.trim().split(/\\s+/);\n if (parts.length < 5) break;\n\n const fields = [\n candidate.getMinutes(),\n candidate.getHours(),\n candidate.getDate(),\n candidate.getMonth() + 1,\n candidate.getDay(),\n ];\n\n const matches = parts.every((part, idx) => matchesCronField(part, fields[idx]));\n if (matches) return candidate.toISOString();\n }\n return new Date(now.getTime() + 86400000).toISOString(); // fallback: 24h from now\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/** Initialize the heartbeat scheduler */\nexport function initHeartbeatScheduler(): void {\n if (running) return;\n running = true;\n\n // Tick every 60 seconds to check cron schedules\n tickInterval = setInterval(() => {\n for (const [agentId, entry] of schedules) {\n if (cronMatchesNow(entry.cronExpression)) {\n fireHeartbeat(agentId).catch(err => {\n logger.error(COMPONENT, `Heartbeat fire failed for ${agentId}: ${(err as Error).message}`);\n });\n }\n }\n }, 60_000);\n tickInterval.unref();\n\n logger.info(COMPONENT, `Heartbeat scheduler started — ${schedules.size} agent(s) scheduled`);\n}\n\n/** Schedule an agent with a cron expression */\nexport function scheduleAgent(agentId: string, cronExpression: string): ScheduleEntry {\n // Validate cron expression (basic check)\n const parts = cronExpression.trim().split(/\\s+/);\n if (parts.length < 5) {\n throw new Error(`Invalid cron expression: \"${cronExpression}\" (need 5 fields: min hour day month weekday)`);\n }\n\n const entry: ScheduleEntry = {\n agentId,\n cronExpression,\n task: null,\n nextFireAt: getNextFireTime(cronExpression),\n lastFiredAt: null,\n fireCount: 0,\n };\n\n schedules.set(agentId, entry);\n logger.info(COMPONENT, `Agent \"${agentId}\" scheduled: ${cronExpression} (next: ${entry.nextFireAt})`);\n\n titanEvents.emit('commandpost:agent:schedule', { agentId, cronExpression });\n return entry;\n}\n\n/** Unschedule an agent */\nexport function unscheduleAgent(agentId: string): boolean {\n const entry = schedules.get(agentId);\n if (!entry) return false;\n if (entry.task) clearTimeout(entry.task);\n schedules.delete(agentId);\n logger.info(COMPONENT, `Agent \"${agentId}\" unscheduled`);\n return true;\n}\n\n/** Reschedule an agent with a new cron expression */\nexport function rescheduleAgent(agentId: string, cronExpression: string): ScheduleEntry {\n unscheduleAgent(agentId);\n return scheduleAgent(agentId, cronExpression);\n}\n\n/** Fire a heartbeat for an agent — pick highest priority issue, build context, queue wakeup */\nexport async function fireHeartbeat(agentId: string): Promise<void> {\n const entry = schedules.get(agentId);\n\n logger.info(COMPONENT, `Firing heartbeat for agent \"${agentId}\"`);\n\n // Lazy import to avoid circular deps\n const { listIssues, getRegisteredAgents, getIssueComments, buildAncestryContext } = await import('./commandPost.js');\n const { queueWakeup } = await import('./agentWakeup.js');\n\n // Find the agent\n const agents = getRegisteredAgents();\n const agent = agents.find(a => a.id === agentId);\n if (!agent) {\n logger.warn(COMPONENT, `Agent \"${agentId}\" not found in registry`);\n return;\n }\n\n // Find highest priority assigned issue\n const assignedIssues = listIssues({ assigneeAgentId: agentId })\n .filter(i => i.status === 'todo' || i.status === 'backlog' || i.status === 'in_progress');\n\n if (assignedIssues.length === 0) {\n logger.debug(COMPONENT, `No actionable issues for agent \"${agentId}\" — skipping heartbeat`);\n return;\n }\n\n // Sort by priority (critical > high > medium > low)\n const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };\n assignedIssues.sort((a, b) => (priorityOrder[a.priority] ?? 3) - (priorityOrder[b.priority] ?? 3));\n\n const issue = assignedIssues[0];\n\n // Build context from issue comments (conversation history)\n const issueComments = getIssueComments(issue.id);\n let contextFromComments = '';\n if (issueComments.length > 0) {\n const recentComments = issueComments.slice(-5);\n contextFromComments = '\\n\\n## Previous Work on This Issue\\n' +\n recentComments.map(c => `[${c.createdAt}] ${c.authorAgentId || c.authorUser}: ${c.body.slice(0, 300)}`).join('\\n');\n }\n\n // Build ancestry context\n const ancestryCtx = buildAncestryContext(issue.id);\n\n // Build the enriched task\n const enrichedTask = [\n ancestryCtx,\n contextFromComments,\n '',\n '---',\n '',\n `Task: ${issue.title}`,\n issue.description ? `\\nDescription: ${issue.description}` : '',\n ].filter(Boolean).join('\\n');\n\n // Queue wakeup\n queueWakeup({\n issueId: issue.id,\n issueIdentifier: issue.identifier,\n agentId: agent.id,\n agentName: agent.name,\n parentSessionId: null,\n task: enrichedTask,\n templateName: '',\n mode: 'sub-agent', // Default; could be overridden per-agent in the future\n });\n\n // Update entry\n if (entry) {\n entry.lastFiredAt = new Date().toISOString();\n entry.fireCount++;\n entry.nextFireAt = getNextFireTime(entry.cronExpression);\n }\n\n logger.info(COMPONENT, `Heartbeat fired for \"${agent.name}\" — issue ${issue.identifier} \"${issue.title}\"`);\n}\n\n/** Get schedule status for all agents */\nexport function getScheduleStatus(): Array<{\n agentId: string;\n cronExpression: string;\n nextFireAt: string | null;\n lastFiredAt: string | null;\n fireCount: number;\n}> {\n return Array.from(schedules.values()).map(e => ({\n agentId: e.agentId,\n cronExpression: e.cronExpression,\n nextFireAt: e.nextFireAt,\n lastFiredAt: e.lastFiredAt,\n fireCount: e.fireCount,\n }));\n}\n\n/** Shutdown the scheduler */\nexport function shutdownHeartbeatScheduler(): void {\n if (tickInterval) {\n clearInterval(tickInterval);\n tickInterval = null;\n }\n for (const entry of schedules.values()) {\n if (entry.task) clearTimeout(entry.task);\n }\n schedules.clear();\n running = false;\n logger.info(COMPONENT, 'Heartbeat scheduler shut down');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/heartbeatScheduler.ts"],"sourcesContent":["/**\n * TITAN — Heartbeat Scheduler\n * Wake external agents on schedule, resume their context from issue history.\n * Uses node-cron for scheduling. Each agent with a `schedule` field gets a cron job.\n */\nimport { titanEvents } from './daemon.js';\nimport logger from '../utils/logger.js';\nimport { listAgents } from './multiAgent.js';\n\nconst COMPONENT = 'HeartbeatScheduler';\n\ninterface ScheduleEntry {\n agentId: string;\n cronExpression: string;\n task: ReturnType<typeof setTimeout> | null;\n nextFireAt: string | null;\n lastFiredAt: string | null;\n fireCount: number;\n}\n\nconst schedules = new Map<string, ScheduleEntry>();\nlet running = false;\nlet tickInterval: ReturnType<typeof setInterval> | null = null;\nlet spawnListener: ((data: { id: string; name: string; model: string }) => void) | null = null;\n\n// ─── Cron Parser (simple, no external dependency) ────────────────────────\n\n/** Parse a simple cron expression (minute hour day month weekday) and check if it matches now */\nfunction cronMatchesNow(expression: string): boolean {\n const parts = expression.trim().split(/\\s+/);\n if (parts.length < 5) return false;\n\n const now = new Date();\n const fields = [\n now.getMinutes(), // minute\n now.getHours(), // hour\n now.getDate(), // day of month\n now.getMonth() + 1, // month (1-12)\n now.getDay(), // day of week (0=Sun)\n ];\n\n return parts.every((part, i) => matchesCronField(part, fields[i]));\n}\n\nfunction matchesCronField(pattern: string, value: number): boolean {\n if (pattern === '*') return true;\n\n // Handle step values: */5, 1-30/5\n if (pattern.includes('/')) {\n const [rangePart, stepStr] = pattern.split('/');\n const step = parseInt(stepStr, 10);\n if (isNaN(step) || step <= 0) return false;\n\n if (rangePart === '*') return value % step === 0;\n\n const [start] = rangePart.split('-').map(Number);\n return value >= start && (value - start) % step === 0;\n }\n\n // Handle ranges: 1-5\n if (pattern.includes('-')) {\n const [start, end] = pattern.split('-').map(Number);\n return value >= start && value <= end;\n }\n\n // Handle lists: 1,3,5\n if (pattern.includes(',')) {\n return pattern.split(',').map(Number).includes(value);\n }\n\n // Exact match\n return parseInt(pattern, 10) === value;\n}\n\n/** Calculate the next fire time for a cron expression (approximate — next minute check) */\nfunction getNextFireTime(expression: string): string {\n const now = new Date();\n // Check next 1440 minutes (24 hours)\n for (let i = 1; i <= 1440; i++) {\n const candidate = new Date(now.getTime() + i * 60000);\n // Temporarily check if expression matches this candidate\n const parts = expression.trim().split(/\\s+/);\n if (parts.length < 5) break;\n\n const fields = [\n candidate.getMinutes(),\n candidate.getHours(),\n candidate.getDate(),\n candidate.getMonth() + 1,\n candidate.getDay(),\n ];\n\n const matches = parts.every((part, idx) => matchesCronField(part, fields[idx]));\n if (matches) return candidate.toISOString();\n }\n return new Date(now.getTime() + 86400000).toISOString(); // fallback: 24h from now\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/** Initialize the heartbeat scheduler */\nexport function initHeartbeatScheduler(): void {\n if (running) return;\n running = true;\n\n // Auto-schedule any existing running agents that aren't scheduled yet\n for (const agent of listAgents()) {\n if (agent.status === 'running' && !schedules.has(agent.id)) {\n scheduleAgent(agent.id, '*/1 * * * *'); // Every minute\n }\n }\n\n // Listen for new agents being spawned and auto-schedule them\n spawnListener = (data: { id: string; name: string; model: string }) => {\n if (!schedules.has(data.id)) {\n scheduleAgent(data.id, '*/1 * * * *');\n logger.info(COMPONENT, `Auto-scheduled newly spawned agent \"${data.name}\" (${data.id})`);\n }\n };\n titanEvents.on('agent:spawned', spawnListener);\n\n // Tick every 60 seconds to check cron schedules\n tickInterval = setInterval(() => {\n for (const [agentId, entry] of schedules) {\n if (cronMatchesNow(entry.cronExpression)) {\n fireHeartbeat(agentId).catch(err => {\n logger.error(COMPONENT, `Heartbeat fire failed for ${agentId}: ${(err as Error).message}`);\n });\n }\n }\n }, 60_000);\n tickInterval.unref();\n\n logger.info(COMPONENT, `Heartbeat scheduler started — ${schedules.size} agent(s) scheduled`);\n}\n\n/** Schedule an agent with a cron expression */\nexport function scheduleAgent(agentId: string, cronExpression: string): ScheduleEntry {\n // Validate cron expression (basic check)\n const parts = cronExpression.trim().split(/\\s+/);\n if (parts.length < 5) {\n throw new Error(`Invalid cron expression: \"${cronExpression}\" (need 5 fields: min hour day month weekday)`);\n }\n\n const entry: ScheduleEntry = {\n agentId,\n cronExpression,\n task: null,\n nextFireAt: getNextFireTime(cronExpression),\n lastFiredAt: null,\n fireCount: 0,\n };\n\n schedules.set(agentId, entry);\n logger.info(COMPONENT, `Agent \"${agentId}\" scheduled: ${cronExpression} (next: ${entry.nextFireAt})`);\n\n titanEvents.emit('commandpost:agent:schedule', { agentId, cronExpression });\n return entry;\n}\n\n/** Unschedule an agent */\nexport function unscheduleAgent(agentId: string): boolean {\n const entry = schedules.get(agentId);\n if (!entry) return false;\n if (entry.task) clearTimeout(entry.task);\n schedules.delete(agentId);\n logger.info(COMPONENT, `Agent \"${agentId}\" unscheduled`);\n return true;\n}\n\n/** Reschedule an agent with a new cron expression */\nexport function rescheduleAgent(agentId: string, cronExpression: string): ScheduleEntry {\n unscheduleAgent(agentId);\n return scheduleAgent(agentId, cronExpression);\n}\n\n/** Fire a heartbeat for an agent — pick highest priority issue, build context, queue wakeup */\nexport async function fireHeartbeat(agentId: string): Promise<void> {\n const entry = schedules.get(agentId);\n\n logger.info(COMPONENT, `Firing heartbeat for agent \"${agentId}\"`);\n\n // Lazy import to avoid circular deps\n const { listIssues, getRegisteredAgents, getIssueComments } = await import('./commandPost.js');\n const { queueWakeup } = await import('./agentWakeup.js');\n\n // Find the agent\n const agents = getRegisteredAgents();\n const agent = agents.find(a => a.id === agentId);\n if (!agent) {\n logger.warn(COMPONENT, `Agent \"${agentId}\" not found in registry`);\n return;\n }\n\n // Find highest priority assigned issue\n let assignedIssues = listIssues({ assigneeAgentId: agentId })\n .filter(i => i.status === 'todo' || i.status === 'backlog' || i.status === 'in_progress');\n\n // If no assigned issues, try to checkout an unassigned backlog issue\n if (assignedIssues.length === 0) {\n const { checkoutIssue } = await import('./commandPost.js');\n const backlog = listIssues({ status: 'backlog' })\n .filter(i => !i.assigneeAgentId)\n .sort((a, b) => {\n const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };\n return (priorityOrder[a.priority] ?? 3) - (priorityOrder[b.priority] ?? 3);\n });\n for (const issue of backlog) {\n const checkedOut = checkoutIssue(issue.id, agentId);\n if (checkedOut) {\n assignedIssues = [checkedOut];\n logger.info(COMPONENT, `Auto-checked out issue ${issue.identifier} to agent \"${agentId}\"`);\n break;\n }\n }\n }\n\n if (assignedIssues.length === 0) {\n logger.debug(COMPONENT, `No actionable issues for agent \"${agentId}\" — skipping heartbeat`);\n return;\n }\n\n // Sort by priority (critical > high > medium > low)\n const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };\n assignedIssues.sort((a, b) => (priorityOrder[a.priority] ?? 3) - (priorityOrder[b.priority] ?? 3));\n\n const issue = assignedIssues[0];\n\n // Build context from issue comments (conversation history)\n const issueComments = getIssueComments(issue.id);\n let contextFromComments = '';\n if (issueComments.length > 0) {\n const recentComments = issueComments.slice(-5);\n contextFromComments = '\\n\\n## Previous Work on This Issue\\n' +\n recentComments.map(c => `[${c.createdAt}] ${c.authorAgentId || c.authorUser}: ${c.body.slice(0, 300)}`).join('\\n');\n }\n\n // Build the enriched task\n const enrichedTask = [\n contextFromComments,\n '',\n '---',\n '',\n `Task: ${issue.title}`,\n issue.description ? `\\nDescription: ${issue.description}` : '',\n ].filter(Boolean).join('\\n');\n\n // Queue wakeup\n queueWakeup({\n issueId: issue.id,\n issueIdentifier: issue.identifier,\n agentId: agent.id,\n agentName: agent.name,\n parentSessionId: null,\n task: enrichedTask,\n templateName: '',\n model: agent.model,\n mode: 'sub-agent', // Default; could be overridden per-agent in the future\n });\n\n // Update entry\n if (entry) {\n entry.lastFiredAt = new Date().toISOString();\n entry.fireCount++;\n entry.nextFireAt = getNextFireTime(entry.cronExpression);\n }\n\n logger.info(COMPONENT, `Heartbeat fired for \"${agent.name}\" — issue ${issue.identifier} \"${issue.title}\"`);\n}\n\n/** Get schedule status for all agents */\nexport function getScheduleStatus(): Array<{\n agentId: string;\n cronExpression: string;\n nextFireAt: string | null;\n lastFiredAt: string | null;\n fireCount: number;\n}> {\n return Array.from(schedules.values()).map(e => ({\n agentId: e.agentId,\n cronExpression: e.cronExpression,\n nextFireAt: e.nextFireAt,\n lastFiredAt: e.lastFiredAt,\n fireCount: e.fireCount,\n }));\n}\n\n/** Shutdown the scheduler */\nexport function shutdownHeartbeatScheduler(): void {\n if (tickInterval) {\n clearInterval(tickInterval);\n tickInterval = null;\n }\n for (const entry of schedules.values()) {\n if (entry.task) clearTimeout(entry.task);\n }\n if (spawnListener) {\n titanEvents.off('agent:spawned', spawnListener);\n spawnListener = null;\n }\n schedules.clear();\n running = false;\n logger.info(COMPONENT, 'Heartbeat scheduler shut down');\n}\n"],"mappings":"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","names":["priorityOrder","issue"]}
|
package/dist/agent/toolRunner.js
CHANGED
|
@@ -414,6 +414,36 @@ Rewrite the path to live inside the self-mod target, OR retag the goal to remove
|
|
|
414
414
|
} catch (err) {
|
|
415
415
|
logger.warn(COMPONENT, `Invariant check failed (fail-open): ${err.message}`);
|
|
416
416
|
}
|
|
417
|
+
try {
|
|
418
|
+
const { requiresApproval, createApprovalRequest } = await import("../skills/builtin/approval_gates.js");
|
|
419
|
+
if (requiresApproval(handler.name)) {
|
|
420
|
+
logger.info(COMPONENT, `[ApprovalGate] Tool "${handler.name}" requires human approval \u2014 filing request`);
|
|
421
|
+
const request = createApprovalRequest(handler.name, args, sessionId || toolCall.id);
|
|
422
|
+
if (request.status === "pending") {
|
|
423
|
+
return {
|
|
424
|
+
toolCallId: toolCall.id,
|
|
425
|
+
name: handler.name,
|
|
426
|
+
content: `Awaiting approval: Tool "${handler.name}" requires human confirmation before execution. Request ID: ${request.id}. Approve with "approve ${request.id}" or deny with "deny ${request.id}".`,
|
|
427
|
+
success: false,
|
|
428
|
+
// v5.0: Signal to the loop that this is an approval pause, not a failure
|
|
429
|
+
approvalPending: true,
|
|
430
|
+
approvalRequestId: request.id,
|
|
431
|
+
durationMs: Date.now() - startTime
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
if (request.status === "denied") {
|
|
435
|
+
return {
|
|
436
|
+
toolCallId: toolCall.id,
|
|
437
|
+
name: handler.name,
|
|
438
|
+
content: `Error: Tool "${handler.name}" was auto-denied by approval policy. Check approval preferences or change the request decision.`,
|
|
439
|
+
success: false,
|
|
440
|
+
durationMs: Date.now() - startTime
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} catch (approvalErr) {
|
|
445
|
+
logger.warn(COMPONENT, `Approval gate check failed (fail-open): ${approvalErr.message}`);
|
|
446
|
+
}
|
|
417
447
|
for (; attempt <= (retryEnabled ? maxRetries : 0); attempt++) {
|
|
418
448
|
try {
|
|
419
449
|
const timeout = attempt > 0 && lastErrorClass === "timeout" ? baseTimeout * 2 : baseTimeout;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/toolRunner.ts"],"sourcesContent":["/**\n * TITAN — Tool Runner\n * Executes tool calls from the LLM with sandboxing, timeouts, and result formatting.\n */\nimport type { ToolCall, ToolDefinition } from '../providers/base.js';\nimport { appendFileSync, readFileSync, existsSync } from 'fs';\nimport { TELEMETRY_EVENTS_PATH } from '../utils/constants.js';\nimport { executeToolsParallel } from './parallelTools.js';\nimport { runPreTool, runPostTool } from '../plugins/contextEngine.js';\nimport type { ContextEnginePlugin } from '../plugins/contextEngine.js';\n\n/** Tool hook plugins — set during agent initialization */\nlet toolHookPlugins: ContextEnginePlugin[] = [];\nexport function setToolHookPlugins(plugins: ContextEnginePlugin[]): void {\n toolHookPlugins = plugins;\n}\nimport logger from '../utils/logger.js';\nimport { loadConfig } from '../config/config.js';\nimport { checkAutonomy } from './autonomy.js';\nimport { isToolSkillEnabled } from '../skills/registry.js';\nimport { redactSecrets } from '../security/secretGuard.js';\nimport { scanAndRedactPII, fullExfilScan } from '../security/exfilScan.js';\nimport { scanCommand, scanURL } from '../security/preExecScan.js';\nimport { runPreToolShellHooks, runPostToolShellHooks } from '../hooks/shellHooks.js';\nimport { createCheckpoint } from '../checkpoint/manager.js';\n\n/** Compute a lightweight unified diff between old and new file content */\nfunction computeUnifiedDiff(filePath: string, oldContent: string, newContent: string): string {\n if (oldContent === newContent) return `// No changes to ${filePath}`;\n const oldLines = oldContent.split('\\n');\n const newLines = newContent.split('\\n');\n const header = `--- ${filePath}\\n+++ ${filePath}`;\n const hunks: string[] = [];\n let i = 0, j = 0;\n while (i < oldLines.length || j < newLines.length) {\n if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {\n i++; j++; continue;\n }\n const startI = i, startJ = j;\n const removed: string[] = [];\n const added: string[] = [];\n while (i < oldLines.length && (j >= newLines.length || oldLines[i] !== newLines[j])) {\n removed.push(oldLines[i++]);\n }\n while (j < newLines.length && (i >= oldLines.length || oldLines[i] !== newLines[j])) {\n added.push(newLines[j++]);\n }\n if (removed.length || added.length) {\n const ctxBefore = oldLines.slice(Math.max(0, startI - 2), startI);\n const ctxAfter = oldLines.slice(i, Math.min(oldLines.length, i + 2));\n hunks.push([\n ...ctxBefore.map(l => ` ${l}`),\n ...removed.map(l => `-${l}`),\n ...added.map(l => `+${l}`),\n ...ctxAfter.map(l => ` ${l}`),\n ].join('\\n'));\n }\n }\n const body = hunks.join('\\n---\\n');\n return `${header}\\n${body}`;\n}\nimport { getCachedToolResult, cacheToolResult } from './trajectoryCompressor.js';\nimport { classifyProviderError, FailoverReason } from '../providers/errorTaxonomy.js';\nimport { snapshotBeforeWrite } from './shadowGit.js';\nimport { captureWrite, shouldCapture } from './selfProposals.js';\nimport { getSessionGoal } from './autonomyContext.js';\n\nconst COMPONENT = 'ToolRunner';\n\n/**\n * G1: Sanitize base64 image data from tool results (OpenClaw pattern).\n * Prevents token explosion when vision/screenshot tools return raw base64.\n * Replaces data URIs with a compact placeholder showing byte count.\n */\nfunction sanitizeBase64(content: string): string {\n return content.replace(\n /data:image\\/[^;]+;base64,[A-Za-z0-9+/=]{100,}/g,\n (match) => {\n const bytes = Math.ceil((match.length - match.indexOf(',') - 1) * 0.75);\n return `[image: ${(bytes / 1024).toFixed(1)}KB omitted]`;\n },\n );\n}\n\n/** Error classification for retry decisions */\nexport type ErrorClass = 'transient' | 'permanent' | 'timeout' | 'rate_limit';\n\n/** Classify an error to determine if retry is appropriate.\n * Delegates to the centralized error taxonomy, then maps back to ErrorClass\n * for backward compatibility with tool execution retry logic.\n */\nexport function classifyError(error: Error, _toolName: string): ErrorClass {\n const classified = classifyProviderError(error);\n switch (classified.reason) {\n case FailoverReason.TIMEOUT:\n return 'timeout';\n case FailoverReason.RATE_LIMIT:\n return 'rate_limit';\n case FailoverReason.SERVER_ERROR:\n case FailoverReason.NETWORK_ERROR:\n case FailoverReason.OVERLOADED:\n case FailoverReason.EMPTY_RESPONSE:\n return 'transient';\n default:\n return classified.retryable ? 'transient' : 'permanent';\n }\n}\n\n/** Tool execution result */\nexport interface ToolResult {\n toolCallId: string;\n name: string;\n content: string;\n success: boolean;\n durationMs: number;\n /** Number of retry attempts made (0 = first try succeeded/failed) */\n retryCount?: number;\n /** Error classification if the tool failed */\n errorClass?: ErrorClass;\n /** Inline unified diff for file-modifying tools (write_file, edit_file, apply_patch) */\n diff?: string;\n}\n\n/** A registered tool handler */\nexport interface ToolHandler {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (args: Record<string, unknown>) => Promise<string>;\n}\n\n/** Global tool registry */\nconst toolRegistry: Map<string, ToolHandler> = new Map();\n\n/** Register a tool */\nexport function registerTool(handler: ToolHandler): void {\n toolRegistry.set(handler.name, handler);\n logger.debug(COMPONENT, `Registered tool: ${handler.name}`);\n}\n\n/** Unregister a tool */\nexport function unregisterTool(name: string): void {\n toolRegistry.delete(name);\n}\n\n/** Get all registered tools */\nexport function getRegisteredTools(): ToolHandler[] {\n return Array.from(toolRegistry.values());\n}\n\n/** Convert registered tools to LLM tool definitions */\nexport function getToolDefinitions(): ToolDefinition[] {\n const config = loadConfig();\n const allowed = new Set(config.security.allowedTools);\n const denied = new Set(config.security.deniedTools);\n\n return Array.from(toolRegistry.values())\n .filter((tool) => {\n if (denied.has(tool.name)) return false;\n if (allowed.size > 0 && !allowed.has(tool.name)) return false;\n if (!isToolSkillEnabled(tool.name)) return false;\n return true;\n })\n .map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n },\n }));\n}\n\n/** Execute a single tool call */\nexport async function executeTool(toolCall: ToolCall, channel?: string): Promise<ToolResult> {\n const config = loadConfig();\n const startTime = Date.now();\n const handler = toolRegistry.get(toolCall.function.name);\n\n if (!handler) {\n // LangGraph pattern: tell the LLM which tools actually exist so it can self-correct\n const available = Array.from(toolRegistry.keys()).sort();\n const suggestions = available.filter(t => {\n const name = toolCall.function.name.toLowerCase();\n return t.toLowerCase().includes(name.slice(0, 4)) || name.includes(t.slice(0, 4));\n }).slice(0, 5);\n const hint = suggestions.length > 0\n ? `\\nDid you mean: ${suggestions.join(', ')}?`\n : `\\nAvailable tools include: ${available.slice(0, 20).join(', ')}${available.length > 20 ? ` (and ${available.length - 20} more)` : ''}`;\n return {\n toolCallId: toolCall.id,\n name: toolCall.function.name,\n content: `Error: \"${toolCall.function.name}\" is not a valid tool.${hint}\\nPlease use one of the available tools.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Check permissions\n if (config.security.deniedTools.includes(handler.name)) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" is denied by security policy`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Check if parent skill is enabled\n if (!isToolSkillEnabled(handler.name)) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" is disabled — its parent skill is turned off. Enable it in Mission Control.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Parse arguments\n let args: Record<string, unknown> = {};\n try {\n args = JSON.parse(toolCall.function.arguments);\n } catch (parseErr) {\n logger.warn('ToolRunner', `Malformed JSON args for ${handler.name}: ${(parseErr as Error).message} — raw: ${(toolCall.function.arguments || '').slice(0, 200)}`);\n // Try to salvage: if it looks like a truncated JSON, extract what we can\n const salvageMatch = (toolCall.function.arguments || '').match(/\\{[\\s\\S]*/);\n if (salvageMatch) {\n try {\n // Attempt to close the JSON and parse\n const fixed = salvageMatch[0].replace(/,?\\s*$/, '}');\n args = JSON.parse(fixed);\n logger.info('ToolRunner', `Salvaged partial JSON args for ${handler.name}`);\n } catch {\n // A5: Return error instead of executing with empty args (LangGraph pattern)\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Could not parse arguments for \"${handler.name}\". Raw: ${(toolCall.function.arguments || '').slice(0, 200)}. Please provide valid JSON arguments.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n }\n\n // Schema validation: check required parameters before execution (LangGraph pattern)\n if (handler.parameters && typeof handler.parameters === 'object') {\n const schema = handler.parameters as { required?: string[]; properties?: Record<string, unknown> };\n if (schema.required && Array.isArray(schema.required)) {\n const missing = schema.required.filter(key => args[key] === undefined || args[key] === null);\n if (missing.length > 0) {\n const available = schema.properties ? Object.keys(schema.properties) : [];\n logger.warn('ToolRunner', `[SchemaValidation] ${handler.name}: missing required params: ${missing.join(', ')}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Missing required parameter(s): ${missing.join(', ')}. ` +\n `Expected parameters: ${available.join(', ')}. Please provide all required arguments.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n }\n\n // Guardrails: validate tool call before execution\n try {\n const { guardToolCall } = await import('./guardrails.js');\n const guardResult = guardToolCall(handler.name, args);\n if (!guardResult.allowed) {\n logger.warn('ToolRunner', `[Guardrails] Blocked ${handler.name}: ${guardResult.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool call blocked by guardrails — ${guardResult.reason}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch { /* guardrails unavailable — continue */ }\n\n // Read-only tool result cache (60s TTL, helper self-gates to read-only allowlist)\n const cacheArgKey = toolCall.function.arguments || '{}';\n const cachedResult = getCachedToolResult(handler.name, cacheArgKey);\n if (cachedResult !== null) {\n logger.info(COMPONENT, `[Cache HIT] ${handler.name}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: cachedResult,\n success: true,\n durationMs: Date.now() - startTime,\n };\n }\n\n logger.info(COMPONENT, `Executing tool: ${handler.name}`);\n\n // v5.0: Pre-execution scanner for dangerous commands\n if (handler.name === 'shell' || handler.name === 'code_exec') {\n const cmdArg = (args.command || args.code || args.script || '') as string;\n const scan = scanCommand(cmdArg);\n if (!scan.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Pre-execution scan blocked this command\\n${scan.warnings.join('\\n')}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (scan.warnings.length > 0 && scan.level === 'warn') {\n logger.warn('ToolRunner', `Pre-exec warnings for ${handler.name}: ${scan.warnings.join('; ')}`);\n }\n }\n if (handler.name === 'browser_navigate' || handler.name === 'browser_auto_nav') {\n const urlArg = (args.url || args.target || '') as string;\n const scan = scanURL(urlArg);\n if (!scan.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Pre-execution scan blocked this URL\\n${scan.warnings.join('\\n')}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n\n // v5.0: Shell hooks — pre-tool\n const { getCurrentSessionId } = await import('./agent.js');\n const sessionId = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const shellPre = await runPreToolShellHooks(handler.name, args, sessionId || toolCall.id, 'default', 0);\n if (!shellPre.allow) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Blocked by shell hook: ' + (shellPre.reason || 'Hook denied execution'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (shellPre.modifiedArgs) args = shellPre.modifiedArgs;\n\n // Pre-tool hooks — plugins can block or modify args\n if (toolHookPlugins.length > 0) {\n const hookResult = await runPreTool(toolHookPlugins, handler.name, args);\n if (!hookResult.allow) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Blocked by hook: ' + (hookResult.reason || 'Plugin denied execution'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (hookResult.modifiedArgs) args = hookResult.modifiedArgs;\n }\n\n // Autonomy gate: check if the tool is permitted under current mode\n const autonomyResult = await checkAutonomy(handler.name, args, channel);\n if (!autonomyResult.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Action blocked by autonomy policy: ' + (autonomyResult.reason || 'Not permitted'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Shadow git checkpoint — snapshot files before mutation (fire-and-forget)\n const MUTATING_TOOLS = new Set(['write_file', 'edit_file', 'append_file', 'apply_patch']);\n\n // v4.9.0-local.7: kill-switch gate for file mutations. If the kill switch\n // is engaged, refuse write/edit/append/apply_patch so the initiative loop\n // can't keep accumulating fix-oscillations while the human hasn't resumed.\n // This closes the gap where `spawn_agent`, `autopilot`, and the pressure\n // cycle were gated but the main agent's tool path was not — meaning\n // initiative could keep rewriting the same files for hours after a kill.\n // See kill-switch.json `history` for the trigger; resume via\n // POST /api/safety/resume.\n if (MUTATING_TOOLS.has(handler.name)) {\n try {\n const { isKilled, getState } = await import('../safety/killSwitch.js');\n if (isKilled()) {\n const state = getState();\n const lastReason = state?.lastEvent?.reason ?? 'kill switch engaged';\n logger.warn(COMPONENT, `[KillSwitch] Refusing ${handler.name} — ${lastReason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: File mutation refused — kill switch is engaged (${lastReason}). ` +\n `Resume via POST /api/safety/resume after investigating the trigger, ` +\n `then retry. Do NOT retry this tool call until resumed.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch { /* kill switch module unavailable — fall through (fail-open on infra error) */ }\n }\n\n // v4.9.0-local.8: Self-mod scope lock + staging.\n //\n // Three-layer policy when the active session has a goal tagged as\n // self-modifying (see config.autonomy.selfMod.tags):\n // 1. Writes to paths OUTSIDE config.autonomy.selfMod.target are refused\n // (prevents LARPing self-improvement by writing to ~/titan-saas etc)\n // 2. When staging is enabled, writes INSIDE target are diverted to a\n // per-goal staging directory and a `self_mod_pr` approval is filed\n // 3. The original path is stored as `targetPath` on the staging entry\n // so the human sees what would land where if they approve the PR\n //\n // This is the deeper fix for the pattern observed 2026-04-18 where a\n // \"self-healing framework\" goal completed 100% by writing to an unrelated\n // Next.js app.\n let stagedRedirect: { stagedPath: string; targetPath: string } | null = null;\n if (MUTATING_TOOLS.has(handler.name)) {\n const rawFilePath = (args.path || args.file_path || args.filePath) as string | undefined;\n if (rawFilePath) {\n try {\n const { getCurrentSessionId } = await import('./agent.js');\n const { decideScope } = await import('./selfModStaging.js');\n const sid: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const decision = decideScope(sid, rawFilePath);\n if (decision.action === 'reject') {\n logger.warn(COMPONENT, `[ScopeLock] Refusing ${handler.name}: ${decision.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: ${decision.reason}\\n\\nRewrite the path to live inside the self-mod target, OR retag the goal to remove self-mod tags, OR pause the goal and create a properly-scoped one.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (decision.action === 'stage' && decision.stagedPath && decision.targetPath) {\n stagedRedirect = { stagedPath: decision.stagedPath, targetPath: decision.targetPath };\n // Rewrite the tool args so the handler writes to staging.\n // Preserve both `path` and `file_path` variants since\n // different tools use different field names.\n if (args.path !== undefined) args.path = decision.stagedPath;\n if (args.file_path !== undefined) args.file_path = decision.stagedPath;\n if (args.filePath !== undefined) args.filePath = decision.stagedPath;\n // Ensure the staged parent dir exists; write_file may not\n // mkdir -p on all code paths.\n try {\n const { mkdirSync } = await import('fs');\n const { dirname } = await import('path');\n mkdirSync(dirname(decision.stagedPath), { recursive: true });\n } catch { /* best-effort */ }\n logger.info(COMPONENT, `[SelfModStaging] Diverting ${handler.name} → ${decision.stagedPath} (would land at ${decision.targetPath} on approval)`);\n }\n } catch (err) {\n logger.debug(COMPONENT, `[ScopeLock] check failed (fail-open): ${(err as Error).message}`);\n }\n }\n }\n\n // v5.0: Filesystem checkpoint before destructive operations\n if (MUTATING_TOOLS.has(handler.name)) {\n const cpPaths: string[] = [];\n const cpPath = (args.path || args.file_path || args.filePath) as string;\n if (cpPath) cpPaths.push(cpPath);\n if (cpPaths.length > 0) {\n createCheckpoint(sessionId || toolCall.id, handler.name, args, cpPaths);\n }\n }\n\n if (MUTATING_TOOLS.has(handler.name)) {\n // Use the (potentially rewritten) path for shadow-git + fix-oscillation\n const filePath = (args.path || args.file_path || args.filePath) as string;\n if (filePath) {\n snapshotBeforeWrite(handler.name, filePath).catch(err =>\n logger.debug(COMPONENT, `Shadow checkpoint skipped: ${(err as Error).message}`),\n );\n // v4.9.0: fix-oscillation tracker. Same file written/edited\n // twice within 24h flags as oscillation, which feeds the\n // kill switch (3+ oscillations → kill). Best-effort — never\n // blocks the write.\n (async () => {\n try {\n const { recordFixEvent } = await import('../safety/fixOscillation.js');\n recordFixEvent({\n target: filePath,\n kind: 'file',\n detail: `${handler.name} via ${channel ?? 'unknown'}`,\n });\n } catch (err) {\n logger.debug(COMPONENT, `Fix-oscillation skipped: ${(err as Error).message}`);\n }\n })();\n }\n // v4.8.0: self-proposal capture — if this write is happening inside\n // an autonomous Soma-driven session, stash a copy for specialist\n // review. Fire-and-forget — never blocks tool execution.\n captureSelfProposalIfApplicable(handler.name, args).catch(err =>\n logger.debug(COMPONENT, `Self-proposal capture skipped: ${(err as Error).message}`),\n );\n }\n\n // Per-tool timeout lookup\n const toolTimeouts = (config.security as Record<string, unknown>).toolTimeouts as Record<string, number> | undefined;\n const baseTimeout = toolTimeouts?.[handler.name] || config.security.commandTimeout || 30000;\n\n // Retry config\n const retryConfig = (config.security as Record<string, unknown>).toolRetry as { enabled?: boolean; maxRetries?: number; backoffBaseMs?: number } | undefined;\n const retryEnabled = retryConfig?.enabled !== false;\n const maxRetries = retryConfig?.maxRetries ?? 3;\n const backoffBase = retryConfig?.backoffBaseMs ?? 1000;\n\n let lastError: Error | null = null;\n let lastErrorClass: ErrorClass = 'permanent';\n let attempt = 0;\n\n // Capture pre-execution file state for diff generation\n let preContent: string | undefined;\n let filePathForDiff: string | undefined;\n if (['write_file', 'edit_file', 'apply_patch'].includes(handler.name)) {\n const pathArg = args.path as string | undefined;\n if (pathArg) {\n filePathForDiff = pathArg;\n try {\n if (existsSync(pathArg)) preContent = readFileSync(pathArg, 'utf-8');\n } catch { /* ignore read errors */ }\n }\n }\n\n // Swarm invariants — hard safety rules (fail-open)\n try {\n const { checkInvariants } = await import('../safety/invariants.js');\n const invariant = checkInvariants(handler.name, args);\n if (!invariant.pass) {\n logger.warn(COMPONENT, `[Invariant] Blocked ${handler.name}: ${invariant.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `INVARIANT_VIOLATION: ${invariant.reason}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch (err) {\n logger.warn(COMPONENT, `Invariant check failed (fail-open): ${(err as Error).message}`);\n }\n\n for (; attempt <= (retryEnabled ? maxRetries : 0); attempt++) {\n try {\n // On timeout retry, double the timeout\n const timeout = (attempt > 0 && lastErrorClass === 'timeout') ? baseTimeout * 2 : baseTimeout;\n\n let result = await Promise.race([\n handler.execute(args),\n new Promise<string>((_, reject) =>\n setTimeout(() => reject(new Error(`Tool \"${handler.name}\" timed out after ${timeout}ms`)), timeout)\n ),\n ]);\n\n // Secret exfiltration guard — scan tool output before it leaves\n result = redactSecrets(result);\n\n // v5.0: PII redaction (privacy compliance)\n const config = loadConfig();\n if (config.security?.redactPII) {\n result = scanAndRedactPII(result);\n }\n\n // v5.0: Full exfiltration scan (layer 2-5) when configured\n if (config.security?.secretScan?.level === 'full') {\n const scan = fullExfilScan(result, 'tool_output');\n if (scan.blocked) {\n logger.warn('ToolRunner', `Exfiltration scan blocked ${handler.name}: ${scan.findings.map(f => f.type).join(', ')}`);\n }\n result = scan.redacted;\n }\n\n const durationMs = Date.now() - startTime;\n if (attempt > 0) {\n logger.info(COMPONENT, `Tool ${handler.name} succeeded on retry ${attempt} in ${durationMs}ms`);\n } else {\n logger.info(COMPONENT, `Tool ${handler.name} completed in ${durationMs}ms`);\n }\n\n // G1: Strip base64 image data before size check (prevents token explosion)\n let finalContent = sanitizeBase64(result);\n\n // Smart truncation — keep head + tail for large results (TITAN pattern)\n if (finalContent.length > 30000) {\n const head = finalContent.slice(0, 20000);\n const tail = finalContent.slice(-5000);\n finalContent = head + '\\n\\n[... ' + (finalContent.length - 25000) + ' chars omitted ...]\\n\\n' + tail;\n logger.info(COMPONENT, `Tool ${handler.name} output truncated: ${result.length} → ${finalContent.length} chars`);\n }\n\n // v5.0: Shell hooks — post-tool\n const shellPost = await runPostToolShellHooks(handler.name, args, finalContent, sessionId || toolCall.id, 'default', 0);\n if (shellPost !== undefined) finalContent = shellPost;\n\n // Post-tool hooks — plugins can modify result\n if (toolHookPlugins.length > 0) {\n const hookResult = await runPostTool(toolHookPlugins, handler.name, args, { content: finalContent, success: true, durationMs });\n if (hookResult.modifiedContent !== undefined) finalContent = hookResult.modifiedContent;\n }\n\n // Cache the result for read-only tools (helper self-gates)\n cacheToolResult(handler.name, cacheArgKey, finalContent);\n\n // v4.9.0-local.8: if this write was diverted to staging, record\n // it in the self_mod_pr bundle. Fire-and-forget — must NOT block\n // the tool's return value or the agent loop.\n if (stagedRedirect) {\n (async () => {\n try {\n const { getCurrentSessionId } = await import('./agent.js');\n const { recordStagedWrite } = await import('./selfModStaging.js');\n const sid: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n await recordStagedWrite({\n sessionId: sid,\n toolName: handler.name,\n stagedPath: stagedRedirect!.stagedPath,\n targetPath: stagedRedirect!.targetPath,\n });\n } catch (err) {\n logger.debug(COMPONENT, `[SelfModStaging] recordStagedWrite failed: ${(err as Error).message}`);\n }\n })();\n }\n\n // Fire-and-forget telemetry + activity log\n (async () => {\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'tool_call', tool: handler.name, channel: channel ?? 'unknown' });\n if (MUTATING_TOOLS.has(handler.name)) {\n logActivity({ event: 'file_edit', tool: handler.name, path: (args.path || args.file_path || args.filePath) as string | undefined });\n }\n if (handler.name === 'web_search') {\n logActivity({ event: 'web_search', query: (args.query || args.q) as string | undefined });\n }\n if (handler.name === 'web_fetch') {\n logActivity({ event: 'web_fetch', url: (args.url || args.target) as string | undefined });\n }\n if (handler.name === 'run_eval' || handler.name === 'eval_suite') {\n logActivity({ event: 'eval_run', suite: (args.suite || args.name) as string | undefined });\n }\n } catch { /* activity log non-critical */ }\n const cfg = loadConfig();\n if (cfg.telemetry?.enabled) {\n try {\n appendFileSync(TELEMETRY_EVENTS_PATH, JSON.stringify({\n event: 'tool_called',\n properties: { toolName: handler.name, success: true, durationMs, channel: channel ?? 'unknown' },\n timestamp: new Date().toISOString(),\n }) + '\\n', 'utf-8');\n } catch { /* non-critical */ }\n }\n // Remote analytics (PostHog + custom collector)\n const { trackToolCall } = await import('../analytics/featureTracker.js');\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sid = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n trackToolCall(handler.name, true, durationMs, undefined, sid ?? undefined);\n })();\n\n // Compute inline diff for file-modifying tools\n let diff: string | undefined;\n if (['write_file', 'edit_file', 'apply_patch'].includes(handler.name) && filePathForDiff && !stagedRedirect) {\n try {\n const postContent = existsSync(filePathForDiff) ? readFileSync(filePathForDiff, 'utf-8') : '';\n diff = computeUnifiedDiff(filePathForDiff, preContent ?? '', postContent);\n } catch { /* ignore diff errors */ }\n }\n\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: stagedRedirect\n ? `${finalContent}\\n\\n[SelfModStaging] Diverted to staging: ${stagedRedirect.stagedPath}. A human approval is pending before this lands at ${stagedRedirect.targetPath}.`\n : finalContent,\n success: true,\n durationMs,\n retryCount: attempt,\n diff,\n };\n } catch (error) {\n lastError = error as Error;\n lastErrorClass = classifyError(lastError, handler.name);\n\n // Don't retry permanent errors\n if (lastErrorClass === 'permanent') {\n break;\n }\n\n // Don't retry if this was the last attempt\n if (attempt >= maxRetries || !retryEnabled) {\n break;\n }\n\n // Exponential backoff: 1s, 2s, 4s (capped at 8s)\n const delay = Math.min(backoffBase * Math.pow(2, attempt), 8000);\n logger.warn(COMPONENT, `Tool ${handler.name} failed (${lastErrorClass}, attempt ${attempt + 1}/${maxRetries + 1}): ${lastError.message} — retrying in ${delay}ms`);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n // All retries exhausted or permanent error\n const durationMs = Date.now() - startTime;\n const errorMsg = lastError?.message || 'Unknown error';\n const retryCount = attempt; // actual number of retries performed (matches success path)\n logger.error(COMPONENT, `Tool ${handler.name} failed (${lastErrorClass}${retryCount > 0 ? `, ${retryCount} retries` : ''}): ${errorMsg}`);\n\n // Fire-and-forget telemetry\n (async () => {\n const cfg = loadConfig();\n if (cfg.telemetry?.enabled) {\n try {\n appendFileSync(TELEMETRY_EVENTS_PATH, JSON.stringify({\n event: 'tool_called',\n properties: { toolName: handler.name, success: false, durationMs, errorClass: lastErrorClass, channel: channel ?? 'unknown' },\n timestamp: new Date().toISOString(),\n }) + '\\n', 'utf-8');\n } catch { /* non-critical */ }\n }\n // Remote analytics (PostHog + custom collector)\n const { trackToolCall } = await import('../analytics/featureTracker.js');\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sid = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n trackToolCall(handler.name, false, durationMs, lastErrorClass, sid ?? undefined);\n })();\n\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: ${errorMsg}`,\n success: false,\n durationMs,\n retryCount,\n errorClass: lastErrorClass,\n };\n}\n\n/** Execute multiple tool calls (in parallel where possible, with write-conflict detection) */\nexport async function executeTools(toolCalls: ToolCall[], channel?: string): Promise<ToolResult[]> {\n // Single tool — fast path\n if (toolCalls.length <= 1) {\n return Promise.all(toolCalls.map(tc => executeTool(tc, channel)));\n }\n\n // Multiple tools — use parallelTools engine with write-conflict detection\n const parallelCalls = toolCalls.map(tc => {\n let args: Record<string, unknown> = {};\n try { args = JSON.parse(tc.function.arguments); } catch { /* use empty */ }\n return { id: tc.id, name: tc.function.name, args };\n });\n\n const executor = async (name: string, args: Record<string, unknown>): Promise<string> => {\n // Build a synthetic ToolCall for executeTool\n const syntheticTc: ToolCall = {\n id: '',\n type: 'function',\n function: { name, arguments: JSON.stringify(args) },\n };\n const result = await executeTool(syntheticTc, channel);\n return result.content;\n };\n\n const parallelResults = await executeToolsParallel(parallelCalls, executor);\n\n // Map back to ToolResult format with full metadata\n return parallelResults.map(pr => ({\n toolCallId: pr.toolCallId,\n name: pr.name,\n content: pr.content,\n success: !pr.content.startsWith('Error:'),\n durationMs: 0,\n }));\n}\n\n// ── Self-proposal capture helper (v4.8.0) ────────────────────────────────\n\n/**\n * If the current write is happening in an autonomous, Soma-driven session,\n * stash a copy of the written content for specialist review. Silent no-op\n * in all other cases (user-driven edits, non-autonomous mode, or when\n * selfMod.enabled is false in config).\n */\nasync function captureSelfProposalIfApplicable(\n toolName: string,\n args: Record<string, unknown>,\n): Promise<void> {\n // Resolve what we can from the current autonomous context\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sessionId: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const sessionGoal = getSessionGoal(sessionId);\n const config = loadConfig();\n const autonomous = (config.autonomy?.mode === 'autonomous');\n const goalProposedBy = sessionGoal?.proposedBy ?? null;\n\n if (!shouldCapture({ toolName, autonomous, goalProposedBy })) return;\n\n const filePath = (args.path || args.file_path || args.filePath) as string | undefined;\n const content = (args.content || args.new_text || args.data) as string | undefined;\n if (!filePath || !content) return;\n\n captureWrite({\n toolName,\n filePath,\n content,\n sessionId,\n agentId: null, // filled by downstream if needed\n goalId: sessionGoal?.goalId ?? null,\n goalTitle: sessionGoal?.goalTitle ?? null,\n goalProposedBy,\n });\n}\n"],"mappings":";AAKA,SAAS,gBAAgB,cAAc,kBAAkB;AACzD,SAAS,6BAA6B;AACtC,SAAS,4BAA4B;AACrC,SAAS,YAAY,mBAAmB;AAIxC,IAAI,kBAAyC,CAAC;AACvC,SAAS,mBAAmB,SAAsC;AACrE,oBAAkB;AACtB;AACA,OAAO,YAAY;AACnB,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AACnC,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,qBAAqB;AAChD,SAAS,aAAa,eAAe;AACrC,SAAS,sBAAsB,6BAA6B;AAC5D,SAAS,wBAAwB;AAGjC,SAAS,mBAAmB,UAAkB,YAAoB,YAA4B;AAC1F,MAAI,eAAe,WAAY,QAAO,oBAAoB,QAAQ;AAClE,QAAM,WAAW,WAAW,MAAM,IAAI;AACtC,QAAM,WAAW,WAAW,MAAM,IAAI;AACtC,QAAM,SAAS,OAAO,QAAQ;AAAA,MAAS,QAAQ;AAC/C,QAAM,QAAkB,CAAC;AACzB,MAAI,IAAI,GAAG,IAAI;AACf,SAAO,IAAI,SAAS,UAAU,IAAI,SAAS,QAAQ;AAC/C,QAAI,IAAI,SAAS,UAAU,IAAI,SAAS,UAAU,SAAS,CAAC,MAAM,SAAS,CAAC,GAAG;AAC3E;AAAK;AAAK;AAAA,IACd;AACA,UAAM,SAAS,GAAG,SAAS;AAC3B,UAAM,UAAoB,CAAC;AAC3B,UAAM,QAAkB,CAAC;AACzB,WAAO,IAAI,SAAS,WAAW,KAAK,SAAS,UAAU,SAAS,CAAC,MAAM,SAAS,CAAC,IAAI;AACjF,cAAQ,KAAK,SAAS,GAAG,CAAC;AAAA,IAC9B;AACA,WAAO,IAAI,SAAS,WAAW,KAAK,SAAS,UAAU,SAAS,CAAC,MAAM,SAAS,CAAC,IAAI;AACjF,YAAM,KAAK,SAAS,GAAG,CAAC;AAAA,IAC5B;AACA,QAAI,QAAQ,UAAU,MAAM,QAAQ;AAChC,YAAM,YAAY,SAAS,MAAM,KAAK,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM;AAChE,YAAM,WAAW,SAAS,MAAM,GAAG,KAAK,IAAI,SAAS,QAAQ,IAAI,CAAC,CAAC;AACnE,YAAM,KAAK;AAAA,QACP,GAAG,UAAU,IAAI,OAAK,IAAI,CAAC,EAAE;AAAA,QAC7B,GAAG,QAAQ,IAAI,OAAK,IAAI,CAAC,EAAE;AAAA,QAC3B,GAAG,MAAM,IAAI,OAAK,IAAI,CAAC,EAAE;AAAA,QACzB,GAAG,SAAS,IAAI,OAAK,IAAI,CAAC,EAAE;AAAA,MAChC,EAAE,KAAK,IAAI,CAAC;AAAA,IAChB;AAAA,EACJ;AACA,QAAM,OAAO,MAAM,KAAK,SAAS;AACjC,SAAO,GAAG,MAAM;AAAA,EAAK,IAAI;AAC7B;AACA,SAAS,qBAAqB,uBAAuB;AACrD,SAAS,uBAAuB,sBAAsB;AACtD,SAAS,2BAA2B;AACpC,SAAS,cAAc,qBAAqB;AAC5C,SAAS,sBAAsB;AAE/B,MAAM,YAAY;AAOlB,SAAS,eAAe,SAAyB;AAC7C,SAAO,QAAQ;AAAA,IACX;AAAA,IACA,CAAC,UAAU;AACP,YAAM,QAAQ,KAAK,MAAM,MAAM,SAAS,MAAM,QAAQ,GAAG,IAAI,KAAK,IAAI;AACtE,aAAO,YAAY,QAAQ,MAAM,QAAQ,CAAC,CAAC;AAAA,IAC/C;AAAA,EACJ;AACJ;AASO,SAAS,cAAc,OAAc,WAA+B;AACvE,QAAM,aAAa,sBAAsB,KAAK;AAC9C,UAAQ,WAAW,QAAQ;AAAA,IACvB,KAAK,eAAe;AAChB,aAAO;AAAA,IACX,KAAK,eAAe;AAChB,aAAO;AAAA,IACX,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAAA,IACpB,KAAK,eAAe;AAChB,aAAO;AAAA,IACX;AACI,aAAO,WAAW,YAAY,cAAc;AAAA,EACpD;AACJ;AA0BA,MAAM,eAAyC,oBAAI,IAAI;AAGhD,SAAS,aAAa,SAA4B;AACrD,eAAa,IAAI,QAAQ,MAAM,OAAO;AACtC,SAAO,MAAM,WAAW,oBAAoB,QAAQ,IAAI,EAAE;AAC9D;AAGO,SAAS,eAAe,MAAoB;AAC/C,eAAa,OAAO,IAAI;AAC5B;AAGO,SAAS,qBAAoC;AAChD,SAAO,MAAM,KAAK,aAAa,OAAO,CAAC;AAC3C;AAGO,SAAS,qBAAuC;AACnD,QAAM,SAAS,WAAW;AAC1B,QAAM,UAAU,IAAI,IAAI,OAAO,SAAS,YAAY;AACpD,QAAM,SAAS,IAAI,IAAI,OAAO,SAAS,WAAW;AAElD,SAAO,MAAM,KAAK,aAAa,OAAO,CAAC,EAClC,OAAO,CAAC,SAAS;AACd,QAAI,OAAO,IAAI,KAAK,IAAI,EAAG,QAAO;AAClC,QAAI,QAAQ,OAAO,KAAK,CAAC,QAAQ,IAAI,KAAK,IAAI,EAAG,QAAO;AACxD,QAAI,CAAC,mBAAmB,KAAK,IAAI,EAAG,QAAO;AAC3C,WAAO;AAAA,EACX,CAAC,EACA,IAAI,CAAC,UAAU;AAAA,IACZ,MAAM;AAAA,IACN,UAAU;AAAA,MACN,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,IACrB;AAAA,EACJ,EAAE;AACV;AAGA,eAAsB,YAAY,UAAoB,SAAuC;AACzF,QAAM,SAAS,WAAW;AAC1B,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,UAAU,aAAa,IAAI,SAAS,SAAS,IAAI;AAEvD,MAAI,CAAC,SAAS;AAEV,UAAM,YAAY,MAAM,KAAK,aAAa,KAAK,CAAC,EAAE,KAAK;AACvD,UAAM,cAAc,UAAU,OAAO,OAAK;AACtC,YAAM,OAAO,SAAS,SAAS,KAAK,YAAY;AAChD,aAAO,EAAE,YAAY,EAAE,SAAS,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,KAAK,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IACpF,CAAC,EAAE,MAAM,GAAG,CAAC;AACb,UAAM,OAAO,YAAY,SAAS,IAC5B;AAAA,gBAAmB,YAAY,KAAK,IAAI,CAAC,MACzC;AAAA,2BAA8B,UAAU,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,GAAG,UAAU,SAAS,KAAK,SAAS,UAAU,SAAS,EAAE,WAAW,EAAE;AAC3I,WAAO;AAAA,MACH,YAAY,SAAS;AAAA,MACrB,MAAM,SAAS,SAAS;AAAA,MACxB,SAAS,WAAW,SAAS,SAAS,IAAI,yBAAyB,IAAI;AAAA;AAAA,MACvE,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC7B;AAAA,EACJ;AAGA,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,IAAI,GAAG;AACpD,WAAO;AAAA,MACH,YAAY,SAAS;AAAA,MACrB,MAAM,QAAQ;AAAA,MACd,SAAS,gBAAgB,QAAQ,IAAI;AAAA,MACrC,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC7B;AAAA,EACJ;AAGA,MAAI,CAAC,mBAAmB,QAAQ,IAAI,GAAG;AACnC,WAAO;AAAA,MACH,YAAY,SAAS;AAAA,MACrB,MAAM,QAAQ;AAAA,MACd,SAAS,gBAAgB,QAAQ,IAAI;AAAA,MACrC,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC7B;AAAA,EACJ;AAGA,MAAI,OAAgC,CAAC;AACrC,MAAI;AACA,WAAO,KAAK,MAAM,SAAS,SAAS,SAAS;AAAA,EACjD,SAAS,UAAU;AACf,WAAO,KAAK,cAAc,2BAA2B,QAAQ,IAAI,KAAM,SAAmB,OAAO,iBAAY,SAAS,SAAS,aAAa,IAAI,MAAM,GAAG,GAAG,CAAC,EAAE;AAE/J,UAAM,gBAAgB,SAAS,SAAS,aAAa,IAAI,MAAM,WAAW;AAC1E,QAAI,cAAc;AACd,UAAI;AAEA,cAAM,QAAQ,aAAa,CAAC,EAAE,QAAQ,UAAU,GAAG;AACnD,eAAO,KAAK,MAAM,KAAK;AACvB,eAAO,KAAK,cAAc,kCAAkC,QAAQ,IAAI,EAAE;AAAA,MAC9E,QAAQ;AAEJ,eAAO;AAAA,UACH,YAAY,SAAS;AAAA,UACrB,MAAM,QAAQ;AAAA,UACd,SAAS,yCAAyC,QAAQ,IAAI,YAAY,SAAS,SAAS,aAAa,IAAI,MAAM,GAAG,GAAG,CAAC;AAAA,UAC1H,SAAS;AAAA,UACT,YAAY,KAAK,IAAI,IAAI;AAAA,QAC7B;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAGA,MAAI,QAAQ,cAAc,OAAO,QAAQ,eAAe,UAAU;AAC9D,UAAM,SAAS,QAAQ;AACvB,QAAI,OAAO,YAAY,MAAM,QAAQ,OAAO,QAAQ,GAAG;AACnD,YAAM,UAAU,OAAO,SAAS,OAAO,SAAO,KAAK,GAAG,MAAM,UAAa,KAAK,GAAG,MAAM,IAAI;AAC3F,UAAI,QAAQ,SAAS,GAAG;AACpB,cAAM,YAAY,OAAO,aAAa,OAAO,KAAK,OAAO,UAAU,IAAI,CAAC;AACxE,eAAO,KAAK,cAAc,sBAAsB,QAAQ,IAAI,8BAA8B,QAAQ,KAAK,IAAI,CAAC,EAAE;AAC9G,eAAO;AAAA,UACH,YAAY,SAAS;AAAA,UACrB,MAAM,QAAQ;AAAA,UACd,SAAS,yCAAyC,QAAQ,KAAK,IAAI,CAAC,0BACxC,UAAU,KAAK,IAAI,CAAC;AAAA,UAChD,SAAS;AAAA,UACT,YAAY,KAAK,IAAI,IAAI;AAAA,QAC7B;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAGA,MAAI;AACA,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,iBAAiB;AACxD,UAAM,cAAc,cAAc,QAAQ,MAAM,IAAI;AACpD,QAAI,CAAC,YAAY,SAAS;AACtB,aAAO,KAAK,cAAc,wBAAwB,QAAQ,IAAI,KAAK,YAAY,MAAM,EAAE;AACvF,aAAO;AAAA,QACH,YAAY,SAAS;AAAA,QACrB,MAAM,QAAQ;AAAA,QACd,SAAS,iDAA4C,YAAY,MAAM;AAAA,QACvE,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,MAC7B;AAAA,IACJ;AAAA,EACJ,QAAQ;AAAA,EAA0C;AAGlD,QAAM,cAAc,SAAS,SAAS,aAAa;AACnD,QAAM,eAAe,oBAAoB,QAAQ,MAAM,WAAW;AAClE,MAAI,iBAAiB,MAAM;AACvB,WAAO,KAAK,WAAW,eAAe,QAAQ,IAAI,EAAE;AACpD,WAAO;AAAA,MACH,YAAY,SAAS;AAAA,MACrB,MAAM,QAAQ;AAAA,MACd,SAAS;AAAA,MACT,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC7B;AAAA,EACJ;AAEA,SAAO,KAAK,WAAW,mBAAmB,QAAQ,IAAI,EAAE;AAGxD,MAAI,QAAQ,SAAS,WAAW,QAAQ,SAAS,aAAa;AAC1D,UAAM,SAAU,KAAK,WAAW,KAAK,QAAQ,KAAK,UAAU;AAC5D,UAAM,OAAO,YAAY,MAAM;AAC/B,QAAI,CAAC,KAAK,SAAS;AACf,aAAO;AAAA,QACH,YAAY,SAAS;AAAA,QACrB,MAAM,QAAQ;AAAA,QACd,SAAS;AAAA,EAAmD,KAAK,SAAS,KAAK,IAAI,CAAC;AAAA,QACpF,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,MAC7B;AAAA,IACJ;AACA,QAAI,KAAK,SAAS,SAAS,KAAK,KAAK,UAAU,QAAQ;AACnD,aAAO,KAAK,cAAc,yBAAyB,QAAQ,IAAI,KAAK,KAAK,SAAS,KAAK,IAAI,CAAC,EAAE;AAAA,IAClG;AAAA,EACJ;AACA,MAAI,QAAQ,SAAS,sBAAsB,QAAQ,SAAS,oBAAoB;AAC5E,UAAM,SAAU,KAAK,OAAO,KAAK,UAAU;AAC3C,UAAM,OAAO,QAAQ,MAAM;AAC3B,QAAI,CAAC,KAAK,SAAS;AACf,aAAO;AAAA,QACH,YAAY,SAAS;AAAA,QACrB,MAAM,QAAQ;AAAA,QACd,SAAS;AAAA,EAA+C,KAAK,SAAS,KAAK,IAAI,CAAC;AAAA,QAChF,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,MAC7B;AAAA,IACJ;AAAA,EACJ;AAGA,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,YAAY;AACzD,QAAM,YAAY,OAAO,wBAAwB,aAAa,oBAAoB,IAAI;AACtF,QAAM,WAAW,MAAM,qBAAqB,QAAQ,MAAM,MAAM,aAAa,SAAS,IAAI,WAAW,CAAC;AACtG,MAAI,CAAC,SAAS,OAAO;AACjB,WAAO;AAAA,MACH,YAAY,SAAS;AAAA,MACrB,MAAM,QAAQ;AAAA,MACd,SAAS,6BAA6B,SAAS,UAAU;AAAA,MACzD,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC7B;AAAA,EACJ;AACA,MAAI,SAAS,aAAc,QAAO,SAAS;AAG3C,MAAI,gBAAgB,SAAS,GAAG;AAC5B,UAAM,aAAa,MAAM,WAAW,iBAAiB,QAAQ,MAAM,IAAI;AACvE,QAAI,CAAC,WAAW,OAAO;AACnB,aAAO;AAAA,QACH,YAAY,SAAS;AAAA,QACrB,MAAM,QAAQ;AAAA,QACd,SAAS,uBAAuB,WAAW,UAAU;AAAA,QACrD,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,MAC7B;AAAA,IACJ;AACA,QAAI,WAAW,aAAc,QAAO,WAAW;AAAA,EACnD;AAGA,QAAM,iBAAiB,MAAM,cAAc,QAAQ,MAAM,MAAM,OAAO;AACtE,MAAI,CAAC,eAAe,SAAS;AACzB,WAAO;AAAA,MACH,YAAY,SAAS;AAAA,MACrB,MAAM,QAAQ;AAAA,MACd,SAAS,yCAAyC,eAAe,UAAU;AAAA,MAC3E,SAAS;AAAA,MACT,YAAY,KAAK,IAAI,IAAI;AAAA,IAC7B;AAAA,EACJ;AAGA,QAAM,iBAAiB,oBAAI,IAAI,CAAC,cAAc,aAAa,eAAe,aAAa,CAAC;AAUxF,MAAI,eAAe,IAAI,QAAQ,IAAI,GAAG;AAClC,QAAI;AACA,YAAM,EAAE,UAAU,SAAS,IAAI,MAAM,OAAO,yBAAyB;AACrE,UAAI,SAAS,GAAG;AACZ,cAAM,QAAQ,SAAS;AACvB,cAAM,aAAa,OAAO,WAAW,UAAU;AAC/C,eAAO,KAAK,WAAW,yBAAyB,QAAQ,IAAI,WAAM,UAAU,EAAE;AAC9E,eAAO;AAAA,UACH,YAAY,SAAS;AAAA,UACrB,MAAM,QAAQ;AAAA,UACd,SAAS,+DAA0D,UAAU;AAAA,UAG7E,SAAS;AAAA,UACT,YAAY,KAAK,IAAI,IAAI;AAAA,QAC7B;AAAA,MACJ;AAAA,IACJ,QAAQ;AAAA,IAAiF;AAAA,EAC7F;AAgBA,MAAI,iBAAoE;AACxE,MAAI,eAAe,IAAI,QAAQ,IAAI,GAAG;AAClC,UAAM,cAAe,KAAK,QAAQ,KAAK,aAAa,KAAK;AACzD,QAAI,aAAa;AACb,UAAI;AACA,cAAM,EAAE,qBAAAA,qBAAoB,IAAI,MAAM,OAAO,YAAY;AACzD,cAAM,EAAE,YAAY,IAAI,MAAM,OAAO,qBAAqB;AAC1D,cAAM,MAAqB,OAAOA,yBAAwB,aAAaA,qBAAoB,IAAI;AAC/F,cAAM,WAAW,YAAY,KAAK,WAAW;AAC7C,YAAI,SAAS,WAAW,UAAU;AAC9B,iBAAO,KAAK,WAAW,wBAAwB,QAAQ,IAAI,KAAK,SAAS,MAAM,EAAE;AACjF,iBAAO;AAAA,YACH,YAAY,SAAS;AAAA,YACrB,MAAM,QAAQ;AAAA,YACd,SAAS,UAAU,SAAS,MAAM;AAAA;AAAA;AAAA,YAClC,SAAS;AAAA,YACT,YAAY,KAAK,IAAI,IAAI;AAAA,UAC7B;AAAA,QACJ;AACA,YAAI,SAAS,WAAW,WAAW,SAAS,cAAc,SAAS,YAAY;AAC3E,2BAAiB,EAAE,YAAY,SAAS,YAAY,YAAY,SAAS,WAAW;AAIpF,cAAI,KAAK,SAAS,OAAW,MAAK,OAAO,SAAS;AAClD,cAAI,KAAK,cAAc,OAAW,MAAK,YAAY,SAAS;AAC5D,cAAI,KAAK,aAAa,OAAW,MAAK,WAAW,SAAS;AAG1D,cAAI;AACA,kBAAM,EAAE,UAAU,IAAI,MAAM,OAAO,IAAI;AACvC,kBAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,MAAM;AACvC,sBAAU,QAAQ,SAAS,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,UAC/D,QAAQ;AAAA,UAAoB;AAC5B,iBAAO,KAAK,WAAW,8BAA8B,QAAQ,IAAI,WAAM,SAAS,UAAU,mBAAmB,SAAS,UAAU,eAAe;AAAA,QACnJ;AAAA,MACJ,SAAS,KAAK;AACV,eAAO,MAAM,WAAW,yCAA0C,IAAc,OAAO,EAAE;AAAA,MAC7F;AAAA,IACJ;AAAA,EACJ;AAGA,MAAI,eAAe,IAAI,QAAQ,IAAI,GAAG;AAClC,UAAM,UAAoB,CAAC;AAC3B,UAAM,SAAU,KAAK,QAAQ,KAAK,aAAa,KAAK;AACpD,QAAI,OAAQ,SAAQ,KAAK,MAAM;AAC/B,QAAI,QAAQ,SAAS,GAAG;AACpB,uBAAiB,aAAa,SAAS,IAAI,QAAQ,MAAM,MAAM,OAAO;AAAA,IAC1E;AAAA,EACJ;AAEA,MAAI,eAAe,IAAI,QAAQ,IAAI,GAAG;AAElC,UAAM,WAAY,KAAK,QAAQ,KAAK,aAAa,KAAK;AACtD,QAAI,UAAU;AACV,0BAAoB,QAAQ,MAAM,QAAQ,EAAE;AAAA,QAAM,SAC9C,OAAO,MAAM,WAAW,8BAA+B,IAAc,OAAO,EAAE;AAAA,MAClF;AAKA,OAAC,YAAY;AACT,YAAI;AACA,gBAAM,EAAE,eAAe,IAAI,MAAM,OAAO,6BAA6B;AACrE,yBAAe;AAAA,YACX,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,QAAQ,GAAG,QAAQ,IAAI,QAAQ,WAAW,SAAS;AAAA,UACvD,CAAC;AAAA,QACL,SAAS,KAAK;AACV,iBAAO,MAAM,WAAW,4BAA6B,IAAc,OAAO,EAAE;AAAA,QAChF;AAAA,MACJ,GAAG;AAAA,IACP;AAIA,oCAAgC,QAAQ,MAAM,IAAI,EAAE;AAAA,MAAM,SACtD,OAAO,MAAM,WAAW,kCAAmC,IAAc,OAAO,EAAE;AAAA,IACtF;AAAA,EACJ;AAGA,QAAM,eAAgB,OAAO,SAAqC;AAClE,QAAM,cAAc,eAAe,QAAQ,IAAI,KAAK,OAAO,SAAS,kBAAkB;AAGtF,QAAM,cAAe,OAAO,SAAqC;AACjE,QAAM,eAAe,aAAa,YAAY;AAC9C,QAAM,aAAa,aAAa,cAAc;AAC9C,QAAM,cAAc,aAAa,iBAAiB;AAElD,MAAI,YAA0B;AAC9B,MAAI,iBAA6B;AACjC,MAAI,UAAU;AAGd,MAAI;AACJ,MAAI;AACJ,MAAI,CAAC,cAAc,aAAa,aAAa,EAAE,SAAS,QAAQ,IAAI,GAAG;AACnE,UAAM,UAAU,KAAK;AACrB,QAAI,SAAS;AACT,wBAAkB;AAClB,UAAI;AACA,YAAI,WAAW,OAAO,EAAG,cAAa,aAAa,SAAS,OAAO;AAAA,MACvE,QAAQ;AAAA,MAA2B;AAAA,IACvC;AAAA,EACJ;AAGA,MAAI;AACA,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,yBAAyB;AAClE,UAAM,YAAY,gBAAgB,QAAQ,MAAM,IAAI;AACpD,QAAI,CAAC,UAAU,MAAM;AACjB,aAAO,KAAK,WAAW,uBAAuB,QAAQ,IAAI,KAAK,UAAU,MAAM,EAAE;AACjF,aAAO;AAAA,QACH,YAAY,SAAS;AAAA,QACrB,MAAM,QAAQ;AAAA,QACd,SAAS,wBAAwB,UAAU,MAAM;AAAA,QACjD,SAAS;AAAA,QACT,YAAY,KAAK,IAAI,IAAI;AAAA,MAC7B;AAAA,IACJ;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,uCAAwC,IAAc,OAAO,EAAE;AAAA,EAC1F;AAEA,SAAO,YAAY,eAAe,aAAa,IAAI,WAAW;AAC1D,QAAI;AAEA,YAAM,UAAW,UAAU,KAAK,mBAAmB,YAAa,cAAc,IAAI;AAElF,UAAI,SAAS,MAAM,QAAQ,KAAK;AAAA,QAC5B,QAAQ,QAAQ,IAAI;AAAA,QACpB,IAAI;AAAA,UAAgB,CAAC,GAAG,WACpB,WAAW,MAAM,OAAO,IAAI,MAAM,SAAS,QAAQ,IAAI,qBAAqB,OAAO,IAAI,CAAC,GAAG,OAAO;AAAA,QACtG;AAAA,MACJ,CAAC;AAGD,eAAS,cAAc,MAAM;AAG7B,YAAMC,UAAS,WAAW;AAC1B,UAAIA,QAAO,UAAU,WAAW;AAC5B,iBAAS,iBAAiB,MAAM;AAAA,MACpC;AAGA,UAAIA,QAAO,UAAU,YAAY,UAAU,QAAQ;AAC/C,cAAM,OAAO,cAAc,QAAQ,aAAa;AAChD,YAAI,KAAK,SAAS;AACd,iBAAO,KAAK,cAAc,6BAA6B,QAAQ,IAAI,KAAK,KAAK,SAAS,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,QACvH;AACA,iBAAS,KAAK;AAAA,MAClB;AAEA,YAAMC,cAAa,KAAK,IAAI,IAAI;AAChC,UAAI,UAAU,GAAG;AACb,eAAO,KAAK,WAAW,QAAQ,QAAQ,IAAI,uBAAuB,OAAO,OAAOA,WAAU,IAAI;AAAA,MAClG,OAAO;AACH,eAAO,KAAK,WAAW,QAAQ,QAAQ,IAAI,iBAAiBA,WAAU,IAAI;AAAA,MAC9E;AAGA,UAAI,eAAe,eAAe,MAAM;AAGxC,UAAI,aAAa,SAAS,KAAO;AAC7B,cAAM,OAAO,aAAa,MAAM,GAAG,GAAK;AACxC,cAAM,OAAO,aAAa,MAAM,IAAK;AACrC,uBAAe,OAAO,eAAe,aAAa,SAAS,QAAS,4BAA4B;AAChG,eAAO,KAAK,WAAW,QAAQ,QAAQ,IAAI,sBAAsB,OAAO,MAAM,WAAM,aAAa,MAAM,QAAQ;AAAA,MACnH;AAGA,YAAM,YAAY,MAAM,sBAAsB,QAAQ,MAAM,MAAM,cAAc,aAAa,SAAS,IAAI,WAAW,CAAC;AACtH,UAAI,cAAc,OAAW,gBAAe;AAG5C,UAAI,gBAAgB,SAAS,GAAG;AAC5B,cAAM,aAAa,MAAM,YAAY,iBAAiB,QAAQ,MAAM,MAAM,EAAE,SAAS,cAAc,SAAS,MAAM,YAAAA,YAAW,CAAC;AAC9H,YAAI,WAAW,oBAAoB,OAAW,gBAAe,WAAW;AAAA,MAC5E;AAGA,sBAAgB,QAAQ,MAAM,aAAa,YAAY;AAKvD,UAAI,gBAAgB;AAChB,SAAC,YAAY;AACT,cAAI;AACA,kBAAM,EAAE,qBAAAF,qBAAoB,IAAI,MAAM,OAAO,YAAY;AACzD,kBAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,qBAAqB;AAChE,kBAAM,MAAqB,OAAOA,yBAAwB,aAAaA,qBAAoB,IAAI;AAC/F,kBAAM,kBAAkB;AAAA,cACpB,WAAW;AAAA,cACX,UAAU,QAAQ;AAAA,cAClB,YAAY,eAAgB;AAAA,cAC5B,YAAY,eAAgB;AAAA,YAChC,CAAC;AAAA,UACL,SAAS,KAAK;AACV,mBAAO,MAAM,WAAW,8CAA+C,IAAc,OAAO,EAAE;AAAA,UAClG;AAAA,QACJ,GAAG;AAAA,MACP;AAGA,OAAC,YAAY;AACT,YAAI;AACA,gBAAM,EAAE,YAAY,IAAI,MAAM,OAAO,6BAA6B;AAClE,sBAAY,EAAE,OAAO,aAAa,MAAM,QAAQ,MAAM,SAAS,WAAW,UAAU,CAAC;AACrF,cAAI,eAAe,IAAI,QAAQ,IAAI,GAAG;AAClC,wBAAY,EAAE,OAAO,aAAa,MAAM,QAAQ,MAAM,MAAO,KAAK,QAAQ,KAAK,aAAa,KAAK,SAAgC,CAAC;AAAA,UACtI;AACA,cAAI,QAAQ,SAAS,cAAc;AAC/B,wBAAY,EAAE,OAAO,cAAc,OAAQ,KAAK,SAAS,KAAK,EAAyB,CAAC;AAAA,UAC5F;AACA,cAAI,QAAQ,SAAS,aAAa;AAC9B,wBAAY,EAAE,OAAO,aAAa,KAAM,KAAK,OAAO,KAAK,OAA8B,CAAC;AAAA,UAC5F;AACA,cAAI,QAAQ,SAAS,cAAc,QAAQ,SAAS,cAAc;AAC9D,wBAAY,EAAE,OAAO,YAAY,OAAQ,KAAK,SAAS,KAAK,KAA4B,CAAC;AAAA,UAC7F;AAAA,QACJ,QAAQ;AAAA,QAAkC;AAC1C,cAAM,MAAM,WAAW;AACvB,YAAI,IAAI,WAAW,SAAS;AACxB,cAAI;AACA,2BAAe,uBAAuB,KAAK,UAAU;AAAA,cACjD,OAAO;AAAA,cACP,YAAY,EAAE,UAAU,QAAQ,MAAM,SAAS,MAAM,YAAAE,aAAY,SAAS,WAAW,UAAU;AAAA,cAC/F,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,YACtC,CAAC,IAAI,MAAM,OAAO;AAAA,UACtB,QAAQ;AAAA,UAAqB;AAAA,QACjC;AAEA,cAAM,EAAE,cAAc,IAAI,MAAM,OAAO,gCAAgC;AACvE,cAAM,EAAE,qBAAAF,qBAAoB,IAAI,MAAM,OAAO,YAAY,EAAE,MAAM,OAAO,EAAE,qBAAqB,MAAM,KAAK,EAAE;AAC5G,cAAM,MAAM,OAAOA,yBAAwB,aAAaA,qBAAoB,IAAI;AAChF,sBAAc,QAAQ,MAAM,MAAME,aAAY,QAAW,OAAO,MAAS;AAAA,MAC7E,GAAG;AAGH,UAAI;AACJ,UAAI,CAAC,cAAc,aAAa,aAAa,EAAE,SAAS,QAAQ,IAAI,KAAK,mBAAmB,CAAC,gBAAgB;AACzG,YAAI;AACA,gBAAM,cAAc,WAAW,eAAe,IAAI,aAAa,iBAAiB,OAAO,IAAI;AAC3F,iBAAO,mBAAmB,iBAAiB,cAAc,IAAI,WAAW;AAAA,QAC5E,QAAQ;AAAA,QAA2B;AAAA,MACvC;AAEA,aAAO;AAAA,QACH,YAAY,SAAS;AAAA,QACrB,MAAM,QAAQ;AAAA,QACd,SAAS,iBACH,GAAG,YAAY;AAAA;AAAA,wCAA6C,eAAe,UAAU,sDAAsD,eAAe,UAAU,MACpK;AAAA,QACN,SAAS;AAAA,QACT,YAAAA;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,MACJ;AAAA,IACJ,SAAS,OAAO;AACZ,kBAAY;AACZ,uBAAiB,cAAc,WAAW,QAAQ,IAAI;AAGtD,UAAI,mBAAmB,aAAa;AAChC;AAAA,MACJ;AAGA,UAAI,WAAW,cAAc,CAAC,cAAc;AACxC;AAAA,MACJ;AAGA,YAAM,QAAQ,KAAK,IAAI,cAAc,KAAK,IAAI,GAAG,OAAO,GAAG,GAAI;AAC/D,aAAO,KAAK,WAAW,QAAQ,QAAQ,IAAI,YAAY,cAAc,aAAa,UAAU,CAAC,IAAI,aAAa,CAAC,MAAM,UAAU,OAAO,uBAAkB,KAAK,IAAI;AACjK,YAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,KAAK,CAAC;AAAA,IAC3D;AAAA,EACJ;AAGA,QAAM,aAAa,KAAK,IAAI,IAAI;AAChC,QAAM,WAAW,WAAW,WAAW;AACvC,QAAM,aAAa;AACnB,SAAO,MAAM,WAAW,QAAQ,QAAQ,IAAI,YAAY,cAAc,GAAG,aAAa,IAAI,KAAK,UAAU,aAAa,EAAE,MAAM,QAAQ,EAAE;AAGxI,GAAC,YAAY;AACT,UAAM,MAAM,WAAW;AACvB,QAAI,IAAI,WAAW,SAAS;AACxB,UAAI;AACA,uBAAe,uBAAuB,KAAK,UAAU;AAAA,UACjD,OAAO;AAAA,UACP,YAAY,EAAE,UAAU,QAAQ,MAAM,SAAS,OAAO,YAAY,YAAY,gBAAgB,SAAS,WAAW,UAAU;AAAA,UAC5H,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACtC,CAAC,IAAI,MAAM,OAAO;AAAA,MACtB,QAAQ;AAAA,MAAqB;AAAA,IACjC;AAEA,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,gCAAgC;AACvE,UAAM,EAAE,qBAAAF,qBAAoB,IAAI,MAAM,OAAO,YAAY,EAAE,MAAM,OAAO,EAAE,qBAAqB,MAAM,KAAK,EAAE;AAC5G,UAAM,MAAM,OAAOA,yBAAwB,aAAaA,qBAAoB,IAAI;AAChF,kBAAc,QAAQ,MAAM,OAAO,YAAY,gBAAgB,OAAO,MAAS;AAAA,EACnF,GAAG;AAEH,SAAO;AAAA,IACH,YAAY,SAAS;AAAA,IACrB,MAAM,QAAQ;AAAA,IACd,SAAS,UAAU,QAAQ;AAAA,IAC3B,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,YAAY;AAAA,EAChB;AACJ;AAGA,eAAsB,aAAa,WAAuB,SAAyC;AAE/F,MAAI,UAAU,UAAU,GAAG;AACvB,WAAO,QAAQ,IAAI,UAAU,IAAI,QAAM,YAAY,IAAI,OAAO,CAAC,CAAC;AAAA,EACpE;AAGA,QAAM,gBAAgB,UAAU,IAAI,QAAM;AACtC,QAAI,OAAgC,CAAC;AACrC,QAAI;AAAE,aAAO,KAAK,MAAM,GAAG,SAAS,SAAS;AAAA,IAAG,QAAQ;AAAA,IAAkB;AAC1E,WAAO,EAAE,IAAI,GAAG,IAAI,MAAM,GAAG,SAAS,MAAM,KAAK;AAAA,EACrD,CAAC;AAED,QAAM,WAAW,OAAO,MAAc,SAAmD;AAErF,UAAM,cAAwB;AAAA,MAC1B,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,UAAU,EAAE,MAAM,WAAW,KAAK,UAAU,IAAI,EAAE;AAAA,IACtD;AACA,UAAM,SAAS,MAAM,YAAY,aAAa,OAAO;AACrD,WAAO,OAAO;AAAA,EAClB;AAEA,QAAM,kBAAkB,MAAM,qBAAqB,eAAe,QAAQ;AAG1E,SAAO,gBAAgB,IAAI,SAAO;AAAA,IAC9B,YAAY,GAAG;AAAA,IACf,MAAM,GAAG;AAAA,IACT,SAAS,GAAG;AAAA,IACZ,SAAS,CAAC,GAAG,QAAQ,WAAW,QAAQ;AAAA,IACxC,YAAY;AAAA,EAChB,EAAE;AACN;AAUA,eAAe,gCACX,UACA,MACa;AAEb,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,YAAY,EAAE,MAAM,OAAO,EAAE,qBAAqB,MAAM,KAAK,EAAE;AAC5G,QAAM,YAA2B,OAAO,wBAAwB,aAAa,oBAAoB,IAAI;AACrG,QAAM,cAAc,eAAe,SAAS;AAC5C,QAAM,SAAS,WAAW;AAC1B,QAAM,aAAc,OAAO,UAAU,SAAS;AAC9C,QAAM,iBAAiB,aAAa,cAAc;AAElD,MAAI,CAAC,cAAc,EAAE,UAAU,YAAY,eAAe,CAAC,EAAG;AAE9D,QAAM,WAAY,KAAK,QAAQ,KAAK,aAAa,KAAK;AACtD,QAAM,UAAW,KAAK,WAAW,KAAK,YAAY,KAAK;AACvD,MAAI,CAAC,YAAY,CAAC,QAAS;AAE3B,eAAa;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA;AAAA,IACT,QAAQ,aAAa,UAAU;AAAA,IAC/B,WAAW,aAAa,aAAa;AAAA,IACrC;AAAA,EACJ,CAAC;AACL;","names":["getCurrentSessionId","config","durationMs"]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/toolRunner.ts"],"sourcesContent":["/**\n * TITAN — Tool Runner\n * Executes tool calls from the LLM with sandboxing, timeouts, and result formatting.\n */\nimport type { ToolCall, ToolDefinition } from '../providers/base.js';\nimport { appendFileSync, readFileSync, existsSync } from 'fs';\nimport { TELEMETRY_EVENTS_PATH } from '../utils/constants.js';\nimport { executeToolsParallel } from './parallelTools.js';\nimport { runPreTool, runPostTool } from '../plugins/contextEngine.js';\nimport type { ContextEnginePlugin } from '../plugins/contextEngine.js';\n\n/** Tool hook plugins — set during agent initialization */\nlet toolHookPlugins: ContextEnginePlugin[] = [];\nexport function setToolHookPlugins(plugins: ContextEnginePlugin[]): void {\n toolHookPlugins = plugins;\n}\nimport logger from '../utils/logger.js';\nimport { loadConfig } from '../config/config.js';\nimport { checkAutonomy } from './autonomy.js';\nimport { isToolSkillEnabled } from '../skills/registry.js';\nimport { redactSecrets } from '../security/secretGuard.js';\nimport { scanAndRedactPII, fullExfilScan } from '../security/exfilScan.js';\nimport { scanCommand, scanURL } from '../security/preExecScan.js';\nimport { runPreToolShellHooks, runPostToolShellHooks } from '../hooks/shellHooks.js';\nimport { createCheckpoint } from '../checkpoint/manager.js';\n\n/** Compute a lightweight unified diff between old and new file content */\nfunction computeUnifiedDiff(filePath: string, oldContent: string, newContent: string): string {\n if (oldContent === newContent) return `// No changes to ${filePath}`;\n const oldLines = oldContent.split('\\n');\n const newLines = newContent.split('\\n');\n const header = `--- ${filePath}\\n+++ ${filePath}`;\n const hunks: string[] = [];\n let i = 0, j = 0;\n while (i < oldLines.length || j < newLines.length) {\n if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {\n i++; j++; continue;\n }\n const startI = i, startJ = j;\n const removed: string[] = [];\n const added: string[] = [];\n while (i < oldLines.length && (j >= newLines.length || oldLines[i] !== newLines[j])) {\n removed.push(oldLines[i++]);\n }\n while (j < newLines.length && (i >= oldLines.length || oldLines[i] !== newLines[j])) {\n added.push(newLines[j++]);\n }\n if (removed.length || added.length) {\n const ctxBefore = oldLines.slice(Math.max(0, startI - 2), startI);\n const ctxAfter = oldLines.slice(i, Math.min(oldLines.length, i + 2));\n hunks.push([\n ...ctxBefore.map(l => ` ${l}`),\n ...removed.map(l => `-${l}`),\n ...added.map(l => `+${l}`),\n ...ctxAfter.map(l => ` ${l}`),\n ].join('\\n'));\n }\n }\n const body = hunks.join('\\n---\\n');\n return `${header}\\n${body}`;\n}\nimport { getCachedToolResult, cacheToolResult } from './trajectoryCompressor.js';\nimport { classifyProviderError, FailoverReason } from '../providers/errorTaxonomy.js';\nimport { snapshotBeforeWrite } from './shadowGit.js';\nimport { captureWrite, shouldCapture } from './selfProposals.js';\nimport { getSessionGoal } from './autonomyContext.js';\n\nconst COMPONENT = 'ToolRunner';\n\n/**\n * G1: Sanitize base64 image data from tool results (OpenClaw pattern).\n * Prevents token explosion when vision/screenshot tools return raw base64.\n * Replaces data URIs with a compact placeholder showing byte count.\n */\nfunction sanitizeBase64(content: string): string {\n return content.replace(\n /data:image\\/[^;]+;base64,[A-Za-z0-9+/=]{100,}/g,\n (match) => {\n const bytes = Math.ceil((match.length - match.indexOf(',') - 1) * 0.75);\n return `[image: ${(bytes / 1024).toFixed(1)}KB omitted]`;\n },\n );\n}\n\n/** Error classification for retry decisions */\nexport type ErrorClass = 'transient' | 'permanent' | 'timeout' | 'rate_limit';\n\n/** Classify an error to determine if retry is appropriate.\n * Delegates to the centralized error taxonomy, then maps back to ErrorClass\n * for backward compatibility with tool execution retry logic.\n */\nexport function classifyError(error: Error, _toolName: string): ErrorClass {\n const classified = classifyProviderError(error);\n switch (classified.reason) {\n case FailoverReason.TIMEOUT:\n return 'timeout';\n case FailoverReason.RATE_LIMIT:\n return 'rate_limit';\n case FailoverReason.SERVER_ERROR:\n case FailoverReason.NETWORK_ERROR:\n case FailoverReason.OVERLOADED:\n case FailoverReason.EMPTY_RESPONSE:\n return 'transient';\n default:\n return classified.retryable ? 'transient' : 'permanent';\n }\n}\n\n/** Tool execution result */\nexport interface ToolResult {\n toolCallId: string;\n name: string;\n content: string;\n success: boolean;\n durationMs: number;\n /** Number of retry attempts made (0 = first try succeeded/failed) */\n retryCount?: number;\n /** Error classification if the tool failed */\n errorClass?: ErrorClass;\n /** Inline unified diff for file-modifying tools (write_file, edit_file, apply_patch) */\n diff?: string;\n /** v5.0: True when the tool is paused waiting for human approval */\n approvalPending?: boolean;\n /** v5.0: Approval request ID when approvalPending is true */\n approvalRequestId?: string;\n}\n\n/** A registered tool handler */\nexport interface ToolHandler {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n execute: (args: Record<string, unknown>) => Promise<string>;\n}\n\n/** Global tool registry */\nconst toolRegistry: Map<string, ToolHandler> = new Map();\n\n/** Register a tool */\nexport function registerTool(handler: ToolHandler): void {\n toolRegistry.set(handler.name, handler);\n logger.debug(COMPONENT, `Registered tool: ${handler.name}`);\n}\n\n/** Unregister a tool */\nexport function unregisterTool(name: string): void {\n toolRegistry.delete(name);\n}\n\n/** Get all registered tools */\nexport function getRegisteredTools(): ToolHandler[] {\n return Array.from(toolRegistry.values());\n}\n\n/** Convert registered tools to LLM tool definitions */\nexport function getToolDefinitions(): ToolDefinition[] {\n const config = loadConfig();\n const allowed = new Set(config.security.allowedTools);\n const denied = new Set(config.security.deniedTools);\n\n return Array.from(toolRegistry.values())\n .filter((tool) => {\n if (denied.has(tool.name)) return false;\n if (allowed.size > 0 && !allowed.has(tool.name)) return false;\n if (!isToolSkillEnabled(tool.name)) return false;\n return true;\n })\n .map((tool) => ({\n type: 'function' as const,\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters,\n },\n }));\n}\n\n/** Execute a single tool call */\nexport async function executeTool(toolCall: ToolCall, channel?: string): Promise<ToolResult> {\n const config = loadConfig();\n const startTime = Date.now();\n const handler = toolRegistry.get(toolCall.function.name);\n\n if (!handler) {\n // LangGraph pattern: tell the LLM which tools actually exist so it can self-correct\n const available = Array.from(toolRegistry.keys()).sort();\n const suggestions = available.filter(t => {\n const name = toolCall.function.name.toLowerCase();\n return t.toLowerCase().includes(name.slice(0, 4)) || name.includes(t.slice(0, 4));\n }).slice(0, 5);\n const hint = suggestions.length > 0\n ? `\\nDid you mean: ${suggestions.join(', ')}?`\n : `\\nAvailable tools include: ${available.slice(0, 20).join(', ')}${available.length > 20 ? ` (and ${available.length - 20} more)` : ''}`;\n return {\n toolCallId: toolCall.id,\n name: toolCall.function.name,\n content: `Error: \"${toolCall.function.name}\" is not a valid tool.${hint}\\nPlease use one of the available tools.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Check permissions\n if (config.security.deniedTools.includes(handler.name)) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" is denied by security policy`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Check if parent skill is enabled\n if (!isToolSkillEnabled(handler.name)) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" is disabled — its parent skill is turned off. Enable it in Mission Control.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Parse arguments\n let args: Record<string, unknown> = {};\n try {\n args = JSON.parse(toolCall.function.arguments);\n } catch (parseErr) {\n logger.warn('ToolRunner', `Malformed JSON args for ${handler.name}: ${(parseErr as Error).message} — raw: ${(toolCall.function.arguments || '').slice(0, 200)}`);\n // Try to salvage: if it looks like a truncated JSON, extract what we can\n const salvageMatch = (toolCall.function.arguments || '').match(/\\{[\\s\\S]*/);\n if (salvageMatch) {\n try {\n // Attempt to close the JSON and parse\n const fixed = salvageMatch[0].replace(/,?\\s*$/, '}');\n args = JSON.parse(fixed);\n logger.info('ToolRunner', `Salvaged partial JSON args for ${handler.name}`);\n } catch {\n // A5: Return error instead of executing with empty args (LangGraph pattern)\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Could not parse arguments for \"${handler.name}\". Raw: ${(toolCall.function.arguments || '').slice(0, 200)}. Please provide valid JSON arguments.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n }\n\n // Schema validation: check required parameters before execution (LangGraph pattern)\n if (handler.parameters && typeof handler.parameters === 'object') {\n const schema = handler.parameters as { required?: string[]; properties?: Record<string, unknown> };\n if (schema.required && Array.isArray(schema.required)) {\n const missing = schema.required.filter(key => args[key] === undefined || args[key] === null);\n if (missing.length > 0) {\n const available = schema.properties ? Object.keys(schema.properties) : [];\n logger.warn('ToolRunner', `[SchemaValidation] ${handler.name}: missing required params: ${missing.join(', ')}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Missing required parameter(s): ${missing.join(', ')}. ` +\n `Expected parameters: ${available.join(', ')}. Please provide all required arguments.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n }\n\n // Guardrails: validate tool call before execution\n try {\n const { guardToolCall } = await import('./guardrails.js');\n const guardResult = guardToolCall(handler.name, args);\n if (!guardResult.allowed) {\n logger.warn('ToolRunner', `[Guardrails] Blocked ${handler.name}: ${guardResult.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool call blocked by guardrails — ${guardResult.reason}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch { /* guardrails unavailable — continue */ }\n\n // Read-only tool result cache (60s TTL, helper self-gates to read-only allowlist)\n const cacheArgKey = toolCall.function.arguments || '{}';\n const cachedResult = getCachedToolResult(handler.name, cacheArgKey);\n if (cachedResult !== null) {\n logger.info(COMPONENT, `[Cache HIT] ${handler.name}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: cachedResult,\n success: true,\n durationMs: Date.now() - startTime,\n };\n }\n\n logger.info(COMPONENT, `Executing tool: ${handler.name}`);\n\n // v5.0: Pre-execution scanner for dangerous commands\n if (handler.name === 'shell' || handler.name === 'code_exec') {\n const cmdArg = (args.command || args.code || args.script || '') as string;\n const scan = scanCommand(cmdArg);\n if (!scan.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Pre-execution scan blocked this command\\n${scan.warnings.join('\\n')}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (scan.warnings.length > 0 && scan.level === 'warn') {\n logger.warn('ToolRunner', `Pre-exec warnings for ${handler.name}: ${scan.warnings.join('; ')}`);\n }\n }\n if (handler.name === 'browser_navigate' || handler.name === 'browser_auto_nav') {\n const urlArg = (args.url || args.target || '') as string;\n const scan = scanURL(urlArg);\n if (!scan.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Pre-execution scan blocked this URL\\n${scan.warnings.join('\\n')}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n\n // v5.0: Shell hooks — pre-tool\n const { getCurrentSessionId } = await import('./agent.js');\n const sessionId = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const shellPre = await runPreToolShellHooks(handler.name, args, sessionId || toolCall.id, 'default', 0);\n if (!shellPre.allow) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Blocked by shell hook: ' + (shellPre.reason || 'Hook denied execution'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (shellPre.modifiedArgs) args = shellPre.modifiedArgs;\n\n // Pre-tool hooks — plugins can block or modify args\n if (toolHookPlugins.length > 0) {\n const hookResult = await runPreTool(toolHookPlugins, handler.name, args);\n if (!hookResult.allow) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Blocked by hook: ' + (hookResult.reason || 'Plugin denied execution'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (hookResult.modifiedArgs) args = hookResult.modifiedArgs;\n }\n\n // Autonomy gate: check if the tool is permitted under current mode\n const autonomyResult = await checkAutonomy(handler.name, args, channel);\n if (!autonomyResult.allowed) {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: 'Action blocked by autonomy policy: ' + (autonomyResult.reason || 'Not permitted'),\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Shadow git checkpoint — snapshot files before mutation (fire-and-forget)\n const MUTATING_TOOLS = new Set(['write_file', 'edit_file', 'append_file', 'apply_patch']);\n\n // v4.9.0-local.7: kill-switch gate for file mutations. If the kill switch\n // is engaged, refuse write/edit/append/apply_patch so the initiative loop\n // can't keep accumulating fix-oscillations while the human hasn't resumed.\n // This closes the gap where `spawn_agent`, `autopilot`, and the pressure\n // cycle were gated but the main agent's tool path was not — meaning\n // initiative could keep rewriting the same files for hours after a kill.\n // See kill-switch.json `history` for the trigger; resume via\n // POST /api/safety/resume.\n if (MUTATING_TOOLS.has(handler.name)) {\n try {\n const { isKilled, getState } = await import('../safety/killSwitch.js');\n if (isKilled()) {\n const state = getState();\n const lastReason = state?.lastEvent?.reason ?? 'kill switch engaged';\n logger.warn(COMPONENT, `[KillSwitch] Refusing ${handler.name} — ${lastReason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: File mutation refused — kill switch is engaged (${lastReason}). ` +\n `Resume via POST /api/safety/resume after investigating the trigger, ` +\n `then retry. Do NOT retry this tool call until resumed.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch { /* kill switch module unavailable — fall through (fail-open on infra error) */ }\n }\n\n // v4.9.0-local.8: Self-mod scope lock + staging.\n //\n // Three-layer policy when the active session has a goal tagged as\n // self-modifying (see config.autonomy.selfMod.tags):\n // 1. Writes to paths OUTSIDE config.autonomy.selfMod.target are refused\n // (prevents LARPing self-improvement by writing to ~/titan-saas etc)\n // 2. When staging is enabled, writes INSIDE target are diverted to a\n // per-goal staging directory and a `self_mod_pr` approval is filed\n // 3. The original path is stored as `targetPath` on the staging entry\n // so the human sees what would land where if they approve the PR\n //\n // This is the deeper fix for the pattern observed 2026-04-18 where a\n // \"self-healing framework\" goal completed 100% by writing to an unrelated\n // Next.js app.\n let stagedRedirect: { stagedPath: string; targetPath: string } | null = null;\n if (MUTATING_TOOLS.has(handler.name)) {\n const rawFilePath = (args.path || args.file_path || args.filePath) as string | undefined;\n if (rawFilePath) {\n try {\n const { getCurrentSessionId } = await import('./agent.js');\n const { decideScope } = await import('./selfModStaging.js');\n const sid: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const decision = decideScope(sid, rawFilePath);\n if (decision.action === 'reject') {\n logger.warn(COMPONENT, `[ScopeLock] Refusing ${handler.name}: ${decision.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: ${decision.reason}\\n\\nRewrite the path to live inside the self-mod target, OR retag the goal to remove self-mod tags, OR pause the goal and create a properly-scoped one.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n if (decision.action === 'stage' && decision.stagedPath && decision.targetPath) {\n stagedRedirect = { stagedPath: decision.stagedPath, targetPath: decision.targetPath };\n // Rewrite the tool args so the handler writes to staging.\n // Preserve both `path` and `file_path` variants since\n // different tools use different field names.\n if (args.path !== undefined) args.path = decision.stagedPath;\n if (args.file_path !== undefined) args.file_path = decision.stagedPath;\n if (args.filePath !== undefined) args.filePath = decision.stagedPath;\n // Ensure the staged parent dir exists; write_file may not\n // mkdir -p on all code paths.\n try {\n const { mkdirSync } = await import('fs');\n const { dirname } = await import('path');\n mkdirSync(dirname(decision.stagedPath), { recursive: true });\n } catch { /* best-effort */ }\n logger.info(COMPONENT, `[SelfModStaging] Diverting ${handler.name} → ${decision.stagedPath} (would land at ${decision.targetPath} on approval)`);\n }\n } catch (err) {\n logger.debug(COMPONENT, `[ScopeLock] check failed (fail-open): ${(err as Error).message}`);\n }\n }\n }\n\n // v5.0: Filesystem checkpoint before destructive operations\n if (MUTATING_TOOLS.has(handler.name)) {\n const cpPaths: string[] = [];\n const cpPath = (args.path || args.file_path || args.filePath) as string;\n if (cpPath) cpPaths.push(cpPath);\n if (cpPaths.length > 0) {\n createCheckpoint(sessionId || toolCall.id, handler.name, args, cpPaths);\n }\n }\n\n if (MUTATING_TOOLS.has(handler.name)) {\n // Use the (potentially rewritten) path for shadow-git + fix-oscillation\n const filePath = (args.path || args.file_path || args.filePath) as string;\n if (filePath) {\n snapshotBeforeWrite(handler.name, filePath).catch(err =>\n logger.debug(COMPONENT, `Shadow checkpoint skipped: ${(err as Error).message}`),\n );\n // v4.9.0: fix-oscillation tracker. Same file written/edited\n // twice within 24h flags as oscillation, which feeds the\n // kill switch (3+ oscillations → kill). Best-effort — never\n // blocks the write.\n (async () => {\n try {\n const { recordFixEvent } = await import('../safety/fixOscillation.js');\n recordFixEvent({\n target: filePath,\n kind: 'file',\n detail: `${handler.name} via ${channel ?? 'unknown'}`,\n });\n } catch (err) {\n logger.debug(COMPONENT, `Fix-oscillation skipped: ${(err as Error).message}`);\n }\n })();\n }\n // v4.8.0: self-proposal capture — if this write is happening inside\n // an autonomous Soma-driven session, stash a copy for specialist\n // review. Fire-and-forget — never blocks tool execution.\n captureSelfProposalIfApplicable(handler.name, args).catch(err =>\n logger.debug(COMPONENT, `Self-proposal capture skipped: ${(err as Error).message}`),\n );\n }\n\n // Per-tool timeout lookup\n const toolTimeouts = (config.security as Record<string, unknown>).toolTimeouts as Record<string, number> | undefined;\n const baseTimeout = toolTimeouts?.[handler.name] || config.security.commandTimeout || 30000;\n\n // Retry config\n const retryConfig = (config.security as Record<string, unknown>).toolRetry as { enabled?: boolean; maxRetries?: number; backoffBaseMs?: number } | undefined;\n const retryEnabled = retryConfig?.enabled !== false;\n const maxRetries = retryConfig?.maxRetries ?? 3;\n const backoffBase = retryConfig?.backoffBaseMs ?? 1000;\n\n let lastError: Error | null = null;\n let lastErrorClass: ErrorClass = 'permanent';\n let attempt = 0;\n\n // Capture pre-execution file state for diff generation\n let preContent: string | undefined;\n let filePathForDiff: string | undefined;\n if (['write_file', 'edit_file', 'apply_patch'].includes(handler.name)) {\n const pathArg = args.path as string | undefined;\n if (pathArg) {\n filePathForDiff = pathArg;\n try {\n if (existsSync(pathArg)) preContent = readFileSync(pathArg, 'utf-8');\n } catch { /* ignore read errors */ }\n }\n }\n\n // Swarm invariants — hard safety rules (fail-open)\n try {\n const { checkInvariants } = await import('../safety/invariants.js');\n const invariant = checkInvariants(handler.name, args);\n if (!invariant.pass) {\n logger.warn(COMPONENT, `[Invariant] Blocked ${handler.name}: ${invariant.reason}`);\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `INVARIANT_VIOLATION: ${invariant.reason}`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n } catch (err) {\n logger.warn(COMPONENT, `Invariant check failed (fail-open): ${(err as Error).message}`);\n }\n\n // v5.0: Approval gates — human-in-the-loop before executing dangerous tools.\n // This wires the approval_gates.ts skill into the execution path, closing the\n // safety gap identified during the 2026-04-28 overnight audit.\n try {\n const { requiresApproval, createApprovalRequest } = await import('../skills/builtin/approval_gates.js');\n if (requiresApproval(handler.name)) {\n logger.info(COMPONENT, `[ApprovalGate] Tool \"${handler.name}\" requires human approval — filing request`);\n const request = createApprovalRequest(handler.name, args, sessionId || toolCall.id);\n if (request.status === 'pending') {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Awaiting approval: Tool \"${handler.name}\" requires human confirmation before execution. ` +\n `Request ID: ${request.id}. ` +\n `Approve with \"approve ${request.id}\" or deny with \"deny ${request.id}\".`,\n success: false,\n // v5.0: Signal to the loop that this is an approval pause, not a failure\n approvalPending: true,\n approvalRequestId: request.id,\n durationMs: Date.now() - startTime,\n };\n }\n // If request was auto-denied (e.g. preference set to 'always_deny'), 'createApprovalRequest'\n // returns a request with status 'denied' and we should abort.\n if (request.status === 'denied') {\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: Tool \"${handler.name}\" was auto-denied by approval policy. ` +\n `Check approval preferences or change the request decision.`,\n success: false,\n durationMs: Date.now() - startTime,\n };\n }\n }\n } catch (approvalErr) {\n // Approval gates module unavailable → fail-open so the agent doesn't deadlock\n logger.warn(COMPONENT, `Approval gate check failed (fail-open): ${(approvalErr as Error).message}`);\n }\n\n for (; attempt <= (retryEnabled ? maxRetries : 0); attempt++) {\n try {\n // On timeout retry, double the timeout\n const timeout = (attempt > 0 && lastErrorClass === 'timeout') ? baseTimeout * 2 : baseTimeout;\n\n let result = await Promise.race([\n handler.execute(args),\n new Promise<string>((_, reject) =>\n setTimeout(() => reject(new Error(`Tool \"${handler.name}\" timed out after ${timeout}ms`)), timeout)\n ),\n ]);\n\n // Secret exfiltration guard — scan tool output before it leaves\n result = redactSecrets(result);\n\n // v5.0: PII redaction (privacy compliance)\n const config = loadConfig();\n if (config.security?.redactPII) {\n result = scanAndRedactPII(result);\n }\n\n // v5.0: Full exfiltration scan (layer 2-5) when configured\n if (config.security?.secretScan?.level === 'full') {\n const scan = fullExfilScan(result, 'tool_output');\n if (scan.blocked) {\n logger.warn('ToolRunner', `Exfiltration scan blocked ${handler.name}: ${scan.findings.map(f => f.type).join(', ')}`);\n }\n result = scan.redacted;\n }\n\n const durationMs = Date.now() - startTime;\n if (attempt > 0) {\n logger.info(COMPONENT, `Tool ${handler.name} succeeded on retry ${attempt} in ${durationMs}ms`);\n } else {\n logger.info(COMPONENT, `Tool ${handler.name} completed in ${durationMs}ms`);\n }\n\n // G1: Strip base64 image data before size check (prevents token explosion)\n let finalContent = sanitizeBase64(result);\n\n // Smart truncation — keep head + tail for large results (TITAN pattern)\n if (finalContent.length > 30000) {\n const head = finalContent.slice(0, 20000);\n const tail = finalContent.slice(-5000);\n finalContent = head + '\\n\\n[... ' + (finalContent.length - 25000) + ' chars omitted ...]\\n\\n' + tail;\n logger.info(COMPONENT, `Tool ${handler.name} output truncated: ${result.length} → ${finalContent.length} chars`);\n }\n\n // v5.0: Shell hooks — post-tool\n const shellPost = await runPostToolShellHooks(handler.name, args, finalContent, sessionId || toolCall.id, 'default', 0);\n if (shellPost !== undefined) finalContent = shellPost;\n\n // Post-tool hooks — plugins can modify result\n if (toolHookPlugins.length > 0) {\n const hookResult = await runPostTool(toolHookPlugins, handler.name, args, { content: finalContent, success: true, durationMs });\n if (hookResult.modifiedContent !== undefined) finalContent = hookResult.modifiedContent;\n }\n\n // Cache the result for read-only tools (helper self-gates)\n cacheToolResult(handler.name, cacheArgKey, finalContent);\n\n // v4.9.0-local.8: if this write was diverted to staging, record\n // it in the self_mod_pr bundle. Fire-and-forget — must NOT block\n // the tool's return value or the agent loop.\n if (stagedRedirect) {\n (async () => {\n try {\n const { getCurrentSessionId } = await import('./agent.js');\n const { recordStagedWrite } = await import('./selfModStaging.js');\n const sid: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n await recordStagedWrite({\n sessionId: sid,\n toolName: handler.name,\n stagedPath: stagedRedirect!.stagedPath,\n targetPath: stagedRedirect!.targetPath,\n });\n } catch (err) {\n logger.debug(COMPONENT, `[SelfModStaging] recordStagedWrite failed: ${(err as Error).message}`);\n }\n })();\n }\n\n // Fire-and-forget telemetry + activity log\n (async () => {\n try {\n const { logActivity } = await import('../telemetry/activityLog.js');\n logActivity({ event: 'tool_call', tool: handler.name, channel: channel ?? 'unknown' });\n if (MUTATING_TOOLS.has(handler.name)) {\n logActivity({ event: 'file_edit', tool: handler.name, path: (args.path || args.file_path || args.filePath) as string | undefined });\n }\n if (handler.name === 'web_search') {\n logActivity({ event: 'web_search', query: (args.query || args.q) as string | undefined });\n }\n if (handler.name === 'web_fetch') {\n logActivity({ event: 'web_fetch', url: (args.url || args.target) as string | undefined });\n }\n if (handler.name === 'run_eval' || handler.name === 'eval_suite') {\n logActivity({ event: 'eval_run', suite: (args.suite || args.name) as string | undefined });\n }\n } catch { /* activity log non-critical */ }\n const cfg = loadConfig();\n if (cfg.telemetry?.enabled) {\n try {\n appendFileSync(TELEMETRY_EVENTS_PATH, JSON.stringify({\n event: 'tool_called',\n properties: { toolName: handler.name, success: true, durationMs, channel: channel ?? 'unknown' },\n timestamp: new Date().toISOString(),\n }) + '\\n', 'utf-8');\n } catch { /* non-critical */ }\n }\n // Remote analytics (PostHog + custom collector)\n const { trackToolCall } = await import('../analytics/featureTracker.js');\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sid = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n trackToolCall(handler.name, true, durationMs, undefined, sid ?? undefined);\n })();\n\n // Compute inline diff for file-modifying tools\n let diff: string | undefined;\n if (['write_file', 'edit_file', 'apply_patch'].includes(handler.name) && filePathForDiff && !stagedRedirect) {\n try {\n const postContent = existsSync(filePathForDiff) ? readFileSync(filePathForDiff, 'utf-8') : '';\n diff = computeUnifiedDiff(filePathForDiff, preContent ?? '', postContent);\n } catch { /* ignore diff errors */ }\n }\n\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: stagedRedirect\n ? `${finalContent}\\n\\n[SelfModStaging] Diverted to staging: ${stagedRedirect.stagedPath}. A human approval is pending before this lands at ${stagedRedirect.targetPath}.`\n : finalContent,\n success: true,\n durationMs,\n retryCount: attempt,\n diff,\n };\n } catch (error) {\n lastError = error as Error;\n lastErrorClass = classifyError(lastError, handler.name);\n\n // Don't retry permanent errors\n if (lastErrorClass === 'permanent') {\n break;\n }\n\n // Don't retry if this was the last attempt\n if (attempt >= maxRetries || !retryEnabled) {\n break;\n }\n\n // Exponential backoff: 1s, 2s, 4s (capped at 8s)\n const delay = Math.min(backoffBase * Math.pow(2, attempt), 8000);\n logger.warn(COMPONENT, `Tool ${handler.name} failed (${lastErrorClass}, attempt ${attempt + 1}/${maxRetries + 1}): ${lastError.message} — retrying in ${delay}ms`);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n // All retries exhausted or permanent error\n const durationMs = Date.now() - startTime;\n const errorMsg = lastError?.message || 'Unknown error';\n const retryCount = attempt; // actual number of retries performed (matches success path)\n logger.error(COMPONENT, `Tool ${handler.name} failed (${lastErrorClass}${retryCount > 0 ? `, ${retryCount} retries` : ''}): ${errorMsg}`);\n\n // Fire-and-forget telemetry\n (async () => {\n const cfg = loadConfig();\n if (cfg.telemetry?.enabled) {\n try {\n appendFileSync(TELEMETRY_EVENTS_PATH, JSON.stringify({\n event: 'tool_called',\n properties: { toolName: handler.name, success: false, durationMs, errorClass: lastErrorClass, channel: channel ?? 'unknown' },\n timestamp: new Date().toISOString(),\n }) + '\\n', 'utf-8');\n } catch { /* non-critical */ }\n }\n // Remote analytics (PostHog + custom collector)\n const { trackToolCall } = await import('../analytics/featureTracker.js');\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sid = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n trackToolCall(handler.name, false, durationMs, lastErrorClass, sid ?? undefined);\n })();\n\n return {\n toolCallId: toolCall.id,\n name: handler.name,\n content: `Error: ${errorMsg}`,\n success: false,\n durationMs,\n retryCount,\n errorClass: lastErrorClass,\n };\n}\n\n/** Execute multiple tool calls (in parallel where possible, with write-conflict detection) */\nexport async function executeTools(toolCalls: ToolCall[], channel?: string): Promise<ToolResult[]> {\n // Single tool — fast path\n if (toolCalls.length <= 1) {\n return Promise.all(toolCalls.map(tc => executeTool(tc, channel)));\n }\n\n // Multiple tools — use parallelTools engine with write-conflict detection\n const parallelCalls = toolCalls.map(tc => {\n let args: Record<string, unknown> = {};\n try { args = JSON.parse(tc.function.arguments); } catch { /* use empty */ }\n return { id: tc.id, name: tc.function.name, args };\n });\n\n const executor = async (name: string, args: Record<string, unknown>): Promise<string> => {\n // Build a synthetic ToolCall for executeTool\n const syntheticTc: ToolCall = {\n id: '',\n type: 'function',\n function: { name, arguments: JSON.stringify(args) },\n };\n const result = await executeTool(syntheticTc, channel);\n return result.content;\n };\n\n const parallelResults = await executeToolsParallel(parallelCalls, executor);\n\n // Map back to ToolResult format with full metadata\n return parallelResults.map(pr => ({\n toolCallId: pr.toolCallId,\n name: pr.name,\n content: pr.content,\n success: !pr.content.startsWith('Error:'),\n durationMs: 0,\n }));\n}\n\n// ── Self-proposal capture helper (v4.8.0) ────────────────────────────────\n\n/**\n * If the current write is happening in an autonomous, Soma-driven session,\n * stash a copy of the written content for specialist review. Silent no-op\n * in all other cases (user-driven edits, non-autonomous mode, or when\n * selfMod.enabled is false in config).\n */\nasync function captureSelfProposalIfApplicable(\n toolName: string,\n args: Record<string, unknown>,\n): Promise<void> {\n // Resolve what we can from the current autonomous context\n const { getCurrentSessionId } = await import('./agent.js').catch(() => ({ getCurrentSessionId: () => null }));\n const sessionId: string | null = typeof getCurrentSessionId === 'function' ? getCurrentSessionId() : null;\n const sessionGoal = getSessionGoal(sessionId);\n const config = loadConfig();\n const autonomous = (config.autonomy?.mode === 'autonomous');\n const goalProposedBy = sessionGoal?.proposedBy ?? null;\n\n if (!shouldCapture({ toolName, autonomous, goalProposedBy })) return;\n\n const filePath = (args.path || args.file_path || args.filePath) as string | undefined;\n const content = (args.content || args.new_text || args.data) as string | undefined;\n if (!filePath || !content) return;\n\n captureWrite({\n toolName,\n filePath,\n content,\n sessionId,\n agentId: null, // filled by downstream if needed\n goalId: sessionGoal?.goalId ?? null,\n goalTitle: sessionGoal?.goalTitle ?? null,\n goalProposedBy,\n });\n}\n"],"mappings":"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","names":["getCurrentSessionId","config","durationMs"]}
|
package/dist/config/config.js
CHANGED
|
@@ -37,14 +37,15 @@ function loadConfig() {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
const parsed = TitanConfigSchema.safeParse(rawConfig);
|
|
41
|
+
if (parsed.success) {
|
|
42
|
+
const droppedPaths = findDroppedKeys(rawConfig, parsed.data, "");
|
|
43
|
+
for (const path of droppedPaths) {
|
|
44
|
+
logger.warn(
|
|
45
|
+
COMPONENT,
|
|
46
|
+
`Unknown config key: ${path}. Will be ignored. If intentional, extend TitanConfigSchema in src/config/schema.ts.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
48
49
|
}
|
|
49
50
|
} catch {
|
|
50
51
|
}
|
|
@@ -197,6 +198,27 @@ function applyEnvOverrides(config) {
|
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
}
|
|
201
|
+
function findDroppedKeys(raw, parsed, pathPrefix, depth = 0) {
|
|
202
|
+
if (depth > 8) return [];
|
|
203
|
+
if (!isPlainObject(raw)) return [];
|
|
204
|
+
const dropped = [];
|
|
205
|
+
for (const key of Object.keys(raw)) {
|
|
206
|
+
const fullPath = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
207
|
+
const rawVal = raw[key];
|
|
208
|
+
const parsedVal = isPlainObject(parsed) ? parsed[key] : void 0;
|
|
209
|
+
if (parsedVal === void 0) {
|
|
210
|
+
if (rawVal !== void 0) dropped.push(fullPath);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (isPlainObject(rawVal) && isPlainObject(parsedVal)) {
|
|
214
|
+
dropped.push(...findDroppedKeys(rawVal, parsedVal, fullPath, depth + 1));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return dropped;
|
|
218
|
+
}
|
|
219
|
+
function isPlainObject(value) {
|
|
220
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
221
|
+
}
|
|
200
222
|
function setNested(obj, path, value) {
|
|
201
223
|
const parts = path.split(".");
|
|
202
224
|
let current = obj;
|