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
|
@@ -12,17 +12,19 @@ const COMPONENT = "AgentWakeup";
|
|
|
12
12
|
const wakeupQueue = /* @__PURE__ */ new Map();
|
|
13
13
|
const pendingResults = /* @__PURE__ */ new Map();
|
|
14
14
|
let cleanupInterval = null;
|
|
15
|
+
let wakeupListener = null;
|
|
15
16
|
let initialized = false;
|
|
16
17
|
function initWakeupSystem() {
|
|
17
18
|
if (initialized) return;
|
|
18
19
|
initialized = true;
|
|
19
|
-
|
|
20
|
+
wakeupListener = (data) => {
|
|
20
21
|
setImmediate(() => {
|
|
21
22
|
handleWakeup(data.wakeupRequestId).catch((err) => {
|
|
22
23
|
logger.error(COMPONENT, `Wakeup handler failed: ${err.message}`);
|
|
23
24
|
});
|
|
24
25
|
});
|
|
25
|
-
}
|
|
26
|
+
};
|
|
27
|
+
titanEvents.on("agent:wakeup", wakeupListener);
|
|
26
28
|
cleanupInterval = setInterval(() => {
|
|
27
29
|
const now = Date.now();
|
|
28
30
|
for (const [id, req] of wakeupQueue) {
|
|
@@ -128,7 +130,10 @@ function shutdownWakeupSystem() {
|
|
|
128
130
|
req.completedAt = Date.now();
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
|
-
|
|
133
|
+
if (wakeupListener) {
|
|
134
|
+
titanEvents.off("agent:wakeup", wakeupListener);
|
|
135
|
+
wakeupListener = null;
|
|
136
|
+
}
|
|
132
137
|
initialized = false;
|
|
133
138
|
logger.info(COMPONENT, "Agent wakeup system shut down");
|
|
134
139
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/agentWakeup.ts"],"sourcesContent":["/**\n * TITAN — Agent Wakeup System\n *\n * Async sub-agent delegation via Command Post. When `spawn_agent` is called\n * with Command Post enabled, instead of blocking the parent for 1-15s, we:\n * 1. Create a CP issue + queue a wakeup request\n * 2. Return immediately to the parent\n * 3. Execute the sub-agent in the background\n * 4. Post results as CP comment, store for parent injection, emit SSE\n *\n * Gated behind config.commandPost.enabled — when disabled, the sync path\n * in agent.ts is used instead (zero regression risk).\n */\nimport { v4 as uuid } from 'uuid';\nimport { titanEvents } from './daemon.js';\nimport { spawnSubAgent, SUB_AGENT_TEMPLATES, type SubAgentResult } from './subAgent.js';\nimport { routeMessage } from './multiAgent.js';\nimport { getAdapter } from './adapters/index.js';\nimport { addIssueComment, updateIssue, startRun, endRun, updateAgentStatus } from './commandPost.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { emitAgentEvent } from './agentEvents.js';\n\nconst COMPONENT = 'AgentWakeup';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport type WakeupStatus = 'queued' | 'running' | 'completed' | 'failed';\n\nexport type WakeupMode = 'sub-agent' | 'multi-agent' | 'external';\n\nexport interface WakeupRequest {\n id: string;\n issueId: string;\n issueIdentifier: string; // e.g. \"TIT-42\"\n agentId: string;\n agentName: string;\n parentSessionId: string | null;\n task: string;\n templateName: string;\n model?: string;\n mode: WakeupMode; // 'sub-agent' = spawnSubAgent, 'multi-agent' = routeMessage, 'external' = adapter\n adapterType?: string; // For external mode: 'claude-code', 'codex', 'bash'\n cwd?: string; // Working directory for external adapters\n status: WakeupStatus;\n createdAt: number;\n completedAt: number | null;\n error: string | null;\n}\n\nexport interface PendingResult {\n issueId: string;\n issueIdentifier: string;\n agentName: string;\n result: SubAgentResult;\n completedAt: number;\n}\n\n// ── In-Memory State ───────────────────────────────────────────────────\n\nconst wakeupQueue = new Map<string, WakeupRequest>();\nconst pendingResults = new Map<string, PendingResult[]>(); // key: parentSessionId\nlet cleanupInterval: ReturnType<typeof setInterval> | null = null;\nlet initialized = false;\n\n// ── Public API ────────────────────────────────────────────────────────\n\n/**\n * Initialize the wakeup system. Call once from gateway startup.\n * Registers the event listener on titanEvents.\n */\nexport function initWakeupSystem(): void {\n if (initialized) return;\n initialized = true;\n\n titanEvents.on('agent:wakeup', (data: { wakeupRequestId: string }) => {\n // Use setImmediate so queueWakeup() returns before we start executing\n setImmediate(() => {\n handleWakeup(data.wakeupRequestId).catch(err => {\n logger.error(COMPONENT, `Wakeup handler failed: ${(err as Error).message}`);\n });\n });\n });\n\n // TTL cleanup: sweep stale requests every 60s\n cleanupInterval = setInterval(() => {\n const now = Date.now();\n for (const [id, req] of wakeupQueue) {\n if (req.status === 'queued' && now - req.createdAt > 3600_000) {\n // Queued for over 1 hour — cancel\n req.status = 'failed';\n req.error = 'TTL expired (queued > 1 hour)';\n req.completedAt = now;\n logger.warn(COMPONENT, `[TTL] Wakeup ${id} expired (queued > 1h)`);\n }\n if (req.status === 'running' && now - req.createdAt > 300_000) {\n // Running for over 5 minutes — mark failed\n req.status = 'failed';\n req.error = 'Timeout (running > 5 minutes)';\n req.completedAt = now;\n logger.warn(COMPONENT, `[TTL] Wakeup ${id} timed out (running > 5m)`);\n }\n // Clean up completed/failed requests older than 30 minutes\n if ((req.status === 'completed' || req.status === 'failed') && req.completedAt && now - req.completedAt > 1800_000) {\n wakeupQueue.delete(id);\n }\n }\n // Clean up stale pendingResults (parent never drained them)\n for (const [sessionId, results] of pendingResults) {\n const stale = results.filter(r => now - r.completedAt > 3600_000);\n if (stale.length === results.length) {\n pendingResults.delete(sessionId);\n } else if (stale.length > 0) {\n pendingResults.set(sessionId, results.filter(r => now - r.completedAt <= 3600_000));\n }\n }\n }, 60_000);\n cleanupInterval.unref();\n\n logger.info(COMPONENT, 'Agent wakeup system initialized');\n}\n\n/**\n * Queue a wakeup request. Creates the request, stores it, and emits the event.\n * Returns immediately — actual execution happens in the background.\n */\nexport function queueWakeup(opts: {\n issueId: string;\n issueIdentifier: string;\n agentId: string;\n agentName: string;\n parentSessionId: string | null;\n task: string;\n templateName: string;\n model?: string;\n mode?: WakeupMode;\n adapterType?: string;\n cwd?: string;\n}): WakeupRequest {\n const request: WakeupRequest = {\n id: `wake_${uuid().slice(0, 8)}`,\n issueId: opts.issueId,\n issueIdentifier: opts.issueIdentifier,\n agentId: opts.agentId,\n agentName: opts.agentName,\n parentSessionId: opts.parentSessionId,\n task: opts.task,\n templateName: opts.templateName,\n model: opts.model,\n mode: opts.mode || 'sub-agent',\n adapterType: opts.adapterType,\n cwd: opts.cwd,\n status: 'queued',\n createdAt: Date.now(),\n completedAt: null,\n error: null,\n };\n\n wakeupQueue.set(request.id, request);\n logger.info(COMPONENT, `Queued wakeup ${request.id} for agent \"${opts.agentName}\" — issue ${opts.issueIdentifier}`);\n\n titanEvents.emit('agent:wakeup', { wakeupRequestId: request.id });\n titanEvents.emit('commandpost:activity', {\n id: uuid().slice(0, 8),\n timestamp: new Date().toISOString(),\n type: 'agent_status_change',\n agentId: opts.agentId,\n message: `Wakeup queued: ${opts.task.slice(0, 80)}`,\n metadata: { wakeupRequestId: request.id, issueId: opts.issueId },\n });\n\n return request;\n}\n\n/**\n * Drain completed async results for a session. Returns and clears them.\n * Called by agentLoop at the start of processMessage to inject context.\n */\nexport function drainPendingResults(sessionId: string): PendingResult[] {\n const results = pendingResults.get(sessionId);\n if (!results || results.length === 0) return [];\n pendingResults.delete(sessionId);\n return results;\n}\n\n/**\n * Get all wakeup requests for an agent (the \"inbox\").\n */\nexport function getAgentInbox(agentId: string): WakeupRequest[] {\n return [...wakeupQueue.values()].filter(\n r => r.agentId === agentId && (r.status === 'queued' || r.status === 'running')\n );\n}\n\n/**\n * Get a wakeup request by ID.\n */\nexport function getWakeupRequest(requestId: string): WakeupRequest | null {\n return wakeupQueue.get(requestId) || null;\n}\n\n/**\n * Claim a queued wakeup request (transition to running). Returns false if not queued.\n */\nexport function claimWakeupRequest(requestId: string): boolean {\n const req = wakeupQueue.get(requestId);\n if (!req || req.status !== 'queued') return false;\n req.status = 'running';\n logger.info(COMPONENT, `Claimed wakeup ${requestId}`);\n return true;\n}\n\n/**\n * Cancel a queued wakeup request. Returns false if already running/completed.\n */\nexport function cancelWakeup(requestId: string): boolean {\n const req = wakeupQueue.get(requestId);\n if (!req || req.status !== 'queued') return false;\n req.status = 'failed';\n req.error = 'Cancelled';\n req.completedAt = Date.now();\n logger.info(COMPONENT, `Cancelled wakeup ${requestId}`);\n return true;\n}\n\n/**\n * Shutdown: cancel all queued requests and clear state.\n */\nexport function shutdownWakeupSystem(): void {\n if (cleanupInterval) {\n clearInterval(cleanupInterval);\n cleanupInterval = null;\n }\n // Cancel all queued requests\n for (const [, req] of wakeupQueue) {\n if (req.status === 'queued') {\n req.status = 'failed';\n req.error = 'System shutdown';\n req.completedAt = Date.now();\n }\n }\n titanEvents.removeAllListeners('agent:wakeup');\n initialized = false;\n logger.info(COMPONENT, 'Agent wakeup system shut down');\n}\n\n// ── Internal ──────────────────────────────────────────────────────────\n\n/**\n * Handle a wakeup event. Claims the request, runs the sub-agent in\n * the background, and handles completion.\n */\nasync function handleWakeup(wakeupRequestId: string): Promise<void> {\n const req = wakeupQueue.get(wakeupRequestId);\n if (!req) {\n logger.warn(COMPONENT, `Wakeup ${wakeupRequestId} not found in queue`);\n return;\n }\n if (req.status !== 'queued') {\n logger.warn(COMPONENT, `Wakeup ${wakeupRequestId} already ${req.status}, skipping`);\n return;\n }\n\n // Claim\n req.status = 'running';\n logger.info(COMPONENT, `[Wakeup] Claimed ${wakeupRequestId} — running sub-agent \"${req.agentName}\" for issue ${req.issueIdentifier}`);\n\n // Activate agent in CP registry\n updateAgentStatus(req.agentId, 'active');\n\n // Transition CP issue to in_progress\n updateIssue(req.issueId, { status: 'in_progress' });\n\n // Start a CP run for tracking\n const run = startRun(req.agentId, 'assignment', req.issueId);\n\n // Emit SSE event\n titanEvents.emit('agent:wakeup:running', {\n wakeupRequestId,\n issueId: req.issueId,\n agentId: req.agentId,\n });\n\n try {\n let result: SubAgentResult;\n\n if (req.mode === 'multi-agent') {\n // ── Multi-agent path: route through the full agent system ──\n logger.info(COMPONENT, `[Wakeup] Multi-agent delegation to \"${req.agentName}\" (${req.agentId})`);\n const startMs = Date.now();\n const agentResponse = await routeMessage(req.task, 'delegation', req.agentId, { overrideAgentId: req.agentId });\n result = {\n content: agentResponse.content,\n toolsUsed: agentResponse.toolsUsed || [],\n success: !agentResponse.content.toLowerCase().includes('error'),\n durationMs: Date.now() - startMs,\n rounds: 1,\n validated: true,\n };\n } else if (req.mode === 'external') {\n // ── External adapter path: spawn CLI process ──\n const adapter = getAdapter(req.adapterType || 'bash');\n if (!adapter) throw new Error(`Unknown adapter type: ${req.adapterType}`);\n\n logger.info(COMPONENT, `[Wakeup] External adapter \"${adapter.displayName}\" for issue ${req.issueIdentifier}`);\n const config = loadConfig();\n const port = (config.gateway as Record<string, unknown>).port as number || 48420;\n const adapterResult = await adapter.execute({\n task: req.task,\n cwd: req.cwd,\n titanApiUrl: `http://localhost:${port}`,\n titanRunId: run.id,\n titanIssueId: req.issueId,\n timeoutMs: 300_000,\n });\n result = {\n content: adapterResult.content,\n toolsUsed: adapterResult.toolsUsed,\n success: adapterResult.success,\n durationMs: adapterResult.durationMs,\n rounds: 1,\n validated: true,\n };\n } else {\n // ── Sub-agent path: isolated sub-agent execution ──\n const template = SUB_AGENT_TEMPLATES[req.templateName] || {};\n const config = loadConfig();\n const modelAliases = (config.agent as Record<string, unknown>).modelAliases as Record<string, string> | undefined;\n const tier = (template as Record<string, unknown>).tier as string | undefined;\n let model = req.model;\n if (!model && modelAliases && tier) {\n model = modelAliases[tier] || modelAliases.fast;\n }\n result = await spawnSubAgent({\n name: req.agentName,\n task: req.task,\n tools: template.tools,\n systemPrompt: template.systemPrompt,\n model,\n depth: 0,\n streamCallbacks: {\n onToolCall: (name, args) => {\n emitAgentEvent({ type: 'tool_call', agentName: req.agentName, agentId: req.agentName, timestamp: Date.now(), data: { name, args } });\n },\n onToolResult: (name, r, durationMs, success) => {\n emitAgentEvent({ type: 'tool_end', agentName: req.agentName, agentId: req.agentName, timestamp: Date.now(), data: { name, result: r.slice(0, 500), durationMs, success } });\n },\n onRound: (round, maxRounds) => {\n emitAgentEvent({ type: 'round', agentName: req.agentName, agentId: req.agentName, timestamp: Date.now(), data: { round, maxRounds } });\n },\n },\n });\n }\n\n // Complete the CP run\n endRun(run.id, {\n status: result.success ? 'succeeded' : 'failed',\n toolsUsed: result.toolsUsed,\n });\n\n // Deactivate agent\n updateAgentStatus(req.agentId, 'idle');\n\n await handleCompletion(wakeupRequestId, result);\n } catch (err) {\n const error = (err as Error).message;\n req.status = 'failed';\n req.error = error;\n req.completedAt = Date.now();\n\n endRun(run.id, { status: 'error', error });\n\n // Deactivate agent\n updateAgentStatus(req.agentId, 'idle');\n\n // Post failure comment on issue\n addIssueComment(req.issueId, `**Sub-agent failed**: ${error}`, { agentId: req.agentId });\n updateIssue(req.issueId, { status: 'todo' }); // Back to todo for retry\n\n titanEvents.emit('agent:task:failed', {\n wakeupRequestId,\n issueId: req.issueId,\n agentId: req.agentId,\n error,\n });\n\n logger.error(COMPONENT, `[Wakeup] ${wakeupRequestId} failed: ${error}`);\n }\n}\n\n/**\n * Handle successful sub-agent completion.\n */\nasync function handleCompletion(wakeupRequestId: string, result: SubAgentResult): Promise<void> {\n const req = wakeupQueue.get(wakeupRequestId);\n if (!req) return;\n\n req.status = 'completed';\n req.completedAt = Date.now();\n\n const durationSec = ((req.completedAt - req.createdAt) / 1000).toFixed(1);\n logger.info(COMPONENT, `[Wakeup] ${wakeupRequestId} completed in ${durationSec}s — ${result.success ? 'SUCCESS' : 'FAILED'}`);\n\n // 1. Post result as CP issue comment\n const commentBody = [\n `**Sub-agent result** (${result.rounds} rounds, ${result.durationMs}ms)`,\n `Status: ${result.success ? 'SUCCESS' : 'FAILED'}${result.validated ? '' : ' [UNVALIDATED]'}`,\n `Tools: ${result.toolsUsed.join(', ') || 'none'}`,\n '',\n result.content,\n ].join('\\n');\n addIssueComment(req.issueId, commentBody, { agentId: req.agentId });\n\n // 2. Transition issue status\n updateIssue(req.issueId, { status: result.success ? 'done' : 'todo' });\n\n // 3. Store pending result for parent session injection\n if (req.parentSessionId) {\n const existing = pendingResults.get(req.parentSessionId) || [];\n existing.push({\n issueId: req.issueId,\n issueIdentifier: req.issueIdentifier,\n agentName: req.agentName,\n result,\n completedAt: Date.now(),\n });\n pendingResults.set(req.parentSessionId, existing);\n }\n\n // 4. Emit SSE event for UI\n titanEvents.emit('agent:task:completed', {\n wakeupRequestId,\n issueId: req.issueId,\n issueIdentifier: req.issueIdentifier,\n agentId: req.agentId,\n agentName: req.agentName,\n success: result.success,\n summary: result.content.slice(0, 200),\n durationMs: result.durationMs,\n rounds: result.rounds,\n });\n}\n"],"mappings":";AAaA,SAAS,MAAM,YAAY;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,eAAe,2BAAgD;AACxE,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB,aAAa,UAAU,QAAQ,yBAAyB;AAClF,SAAS,kBAAkB;AAC3B,OAAO,YAAY;AACnB,SAAS,sBAAsB;AAE/B,MAAM,YAAY;AAqClB,MAAM,cAAc,oBAAI,IAA2B;AACnD,MAAM,iBAAiB,oBAAI,IAA6B;AACxD,IAAI,kBAAyD;AAC7D,IAAI,cAAc;AAQX,SAAS,mBAAyB;AACrC,MAAI,YAAa;AACjB,gBAAc;AAEd,cAAY,GAAG,gBAAgB,CAAC,SAAsC;AAElE,iBAAa,MAAM;AACf,mBAAa,KAAK,eAAe,EAAE,MAAM,SAAO;AAC5C,eAAO,MAAM,WAAW,0BAA2B,IAAc,OAAO,EAAE;AAAA,MAC9E,CAAC;AAAA,IACL,CAAC;AAAA,EACL,CAAC;AAGD,oBAAkB,YAAY,MAAM;AAChC,UAAM,MAAM,KAAK,IAAI;AACrB,eAAW,CAAC,IAAI,GAAG,KAAK,aAAa;AACjC,UAAI,IAAI,WAAW,YAAY,MAAM,IAAI,YAAY,MAAU;AAE3D,YAAI,SAAS;AACb,YAAI,QAAQ;AACZ,YAAI,cAAc;AAClB,eAAO,KAAK,WAAW,gBAAgB,EAAE,wBAAwB;AAAA,MACrE;AACA,UAAI,IAAI,WAAW,aAAa,MAAM,IAAI,YAAY,KAAS;AAE3D,YAAI,SAAS;AACb,YAAI,QAAQ;AACZ,YAAI,cAAc;AAClB,eAAO,KAAK,WAAW,gBAAgB,EAAE,2BAA2B;AAAA,MACxE;AAEA,WAAK,IAAI,WAAW,eAAe,IAAI,WAAW,aAAa,IAAI,eAAe,MAAM,IAAI,cAAc,MAAU;AAChH,oBAAY,OAAO,EAAE;AAAA,MACzB;AAAA,IACJ;AAEA,eAAW,CAAC,WAAW,OAAO,KAAK,gBAAgB;AAC/C,YAAM,QAAQ,QAAQ,OAAO,OAAK,MAAM,EAAE,cAAc,IAAQ;AAChE,UAAI,MAAM,WAAW,QAAQ,QAAQ;AACjC,uBAAe,OAAO,SAAS;AAAA,MACnC,WAAW,MAAM,SAAS,GAAG;AACzB,uBAAe,IAAI,WAAW,QAAQ,OAAO,OAAK,MAAM,EAAE,eAAe,IAAQ,CAAC;AAAA,MACtF;AAAA,IACJ;AAAA,EACJ,GAAG,GAAM;AACT,kBAAgB,MAAM;AAEtB,SAAO,KAAK,WAAW,iCAAiC;AAC5D;AAMO,SAAS,YAAY,MAYV;AACd,QAAM,UAAyB;AAAA,IAC3B,IAAI,QAAQ,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAC9B,SAAS,KAAK;AAAA,IACd,iBAAiB,KAAK;AAAA,IACtB,SAAS,KAAK;AAAA,IACd,WAAW,KAAK;AAAA,IAChB,iBAAiB,KAAK;AAAA,IACtB,MAAM,KAAK;AAAA,IACX,cAAc,KAAK;AAAA,IACnB,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK,QAAQ;AAAA,IACnB,aAAa,KAAK;AAAA,IAClB,KAAK,KAAK;AAAA,IACV,QAAQ;AAAA,IACR,WAAW,KAAK,IAAI;AAAA,IACpB,aAAa;AAAA,IACb,OAAO;AAAA,EACX;AAEA,cAAY,IAAI,QAAQ,IAAI,OAAO;AACnC,SAAO,KAAK,WAAW,iBAAiB,QAAQ,EAAE,eAAe,KAAK,SAAS,kBAAa,KAAK,eAAe,EAAE;AAElH,cAAY,KAAK,gBAAgB,EAAE,iBAAiB,QAAQ,GAAG,CAAC;AAChE,cAAY,KAAK,wBAAwB;AAAA,IACrC,IAAI,KAAK,EAAE,MAAM,GAAG,CAAC;AAAA,IACrB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,MAAM;AAAA,IACN,SAAS,KAAK;AAAA,IACd,SAAS,kBAAkB,KAAK,KAAK,MAAM,GAAG,EAAE,CAAC;AAAA,IACjD,UAAU,EAAE,iBAAiB,QAAQ,IAAI,SAAS,KAAK,QAAQ;AAAA,EACnE,CAAC;AAED,SAAO;AACX;AAMO,SAAS,oBAAoB,WAAoC;AACpE,QAAM,UAAU,eAAe,IAAI,SAAS;AAC5C,MAAI,CAAC,WAAW,QAAQ,WAAW,EAAG,QAAO,CAAC;AAC9C,iBAAe,OAAO,SAAS;AAC/B,SAAO;AACX;AAKO,SAAS,cAAc,SAAkC;AAC5D,SAAO,CAAC,GAAG,YAAY,OAAO,CAAC,EAAE;AAAA,IAC7B,OAAK,EAAE,YAAY,YAAY,EAAE,WAAW,YAAY,EAAE,WAAW;AAAA,EACzE;AACJ;AAKO,SAAS,iBAAiB,WAAyC;AACtE,SAAO,YAAY,IAAI,SAAS,KAAK;AACzC;AAKO,SAAS,mBAAmB,WAA4B;AAC3D,QAAM,MAAM,YAAY,IAAI,SAAS;AACrC,MAAI,CAAC,OAAO,IAAI,WAAW,SAAU,QAAO;AAC5C,MAAI,SAAS;AACb,SAAO,KAAK,WAAW,kBAAkB,SAAS,EAAE;AACpD,SAAO;AACX;AAKO,SAAS,aAAa,WAA4B;AACrD,QAAM,MAAM,YAAY,IAAI,SAAS;AACrC,MAAI,CAAC,OAAO,IAAI,WAAW,SAAU,QAAO;AAC5C,MAAI,SAAS;AACb,MAAI,QAAQ;AACZ,MAAI,cAAc,KAAK,IAAI;AAC3B,SAAO,KAAK,WAAW,oBAAoB,SAAS,EAAE;AACtD,SAAO;AACX;AAKO,SAAS,uBAA6B;AACzC,MAAI,iBAAiB;AACjB,kBAAc,eAAe;AAC7B,sBAAkB;AAAA,EACtB;AAEA,aAAW,CAAC,EAAE,GAAG,KAAK,aAAa;AAC/B,QAAI,IAAI,WAAW,UAAU;AACzB,UAAI,SAAS;AACb,UAAI,QAAQ;AACZ,UAAI,cAAc,KAAK,IAAI;AAAA,IAC/B;AAAA,EACJ;AACA,cAAY,mBAAmB,cAAc;AAC7C,gBAAc;AACd,SAAO,KAAK,WAAW,+BAA+B;AAC1D;AAQA,eAAe,aAAa,iBAAwC;AAChE,QAAM,MAAM,YAAY,IAAI,eAAe;AAC3C,MAAI,CAAC,KAAK;AACN,WAAO,KAAK,WAAW,UAAU,eAAe,qBAAqB;AACrE;AAAA,EACJ;AACA,MAAI,IAAI,WAAW,UAAU;AACzB,WAAO,KAAK,WAAW,UAAU,eAAe,YAAY,IAAI,MAAM,YAAY;AAClF;AAAA,EACJ;AAGA,MAAI,SAAS;AACb,SAAO,KAAK,WAAW,oBAAoB,eAAe,8BAAyB,IAAI,SAAS,eAAe,IAAI,eAAe,EAAE;AAGpI,oBAAkB,IAAI,SAAS,QAAQ;AAGvC,cAAY,IAAI,SAAS,EAAE,QAAQ,cAAc,CAAC;AAGlD,QAAM,MAAM,SAAS,IAAI,SAAS,cAAc,IAAI,OAAO;AAG3D,cAAY,KAAK,wBAAwB;AAAA,IACrC;AAAA,IACA,SAAS,IAAI;AAAA,IACb,SAAS,IAAI;AAAA,EACjB,CAAC;AAED,MAAI;AACA,QAAI;AAEJ,QAAI,IAAI,SAAS,eAAe;AAE5B,aAAO,KAAK,WAAW,uCAAuC,IAAI,SAAS,MAAM,IAAI,OAAO,GAAG;AAC/F,YAAM,UAAU,KAAK,IAAI;AACzB,YAAM,gBAAgB,MAAM,aAAa,IAAI,MAAM,cAAc,IAAI,SAAS,EAAE,iBAAiB,IAAI,QAAQ,CAAC;AAC9G,eAAS;AAAA,QACL,SAAS,cAAc;AAAA,QACvB,WAAW,cAAc,aAAa,CAAC;AAAA,QACvC,SAAS,CAAC,cAAc,QAAQ,YAAY,EAAE,SAAS,OAAO;AAAA,QAC9D,YAAY,KAAK,IAAI,IAAI;AAAA,QACzB,QAAQ;AAAA,QACR,WAAW;AAAA,MACf;AAAA,IACJ,WAAW,IAAI,SAAS,YAAY;AAEhC,YAAM,UAAU,WAAW,IAAI,eAAe,MAAM;AACpD,UAAI,CAAC,QAAS,OAAM,IAAI,MAAM,yBAAyB,IAAI,WAAW,EAAE;AAExE,aAAO,KAAK,WAAW,8BAA8B,QAAQ,WAAW,eAAe,IAAI,eAAe,EAAE;AAC5G,YAAM,SAAS,WAAW;AAC1B,YAAM,OAAQ,OAAO,QAAoC,QAAkB;AAC3E,YAAM,gBAAgB,MAAM,QAAQ,QAAQ;AAAA,QACxC,MAAM,IAAI;AAAA,QACV,KAAK,IAAI;AAAA,QACT,aAAa,oBAAoB,IAAI;AAAA,QACrC,YAAY,IAAI;AAAA,QAChB,cAAc,IAAI;AAAA,QAClB,WAAW;AAAA,MACf,CAAC;AACD,eAAS;AAAA,QACL,SAAS,cAAc;AAAA,QACvB,WAAW,cAAc;AAAA,QACzB,SAAS,cAAc;AAAA,QACvB,YAAY,cAAc;AAAA,QAC1B,QAAQ;AAAA,QACR,WAAW;AAAA,MACf;AAAA,IACJ,OAAO;AAEH,YAAM,WAAW,oBAAoB,IAAI,YAAY,KAAK,CAAC;AAC3D,YAAM,SAAS,WAAW;AAC1B,YAAM,eAAgB,OAAO,MAAkC;AAC/D,YAAM,OAAQ,SAAqC;AACnD,UAAI,QAAQ,IAAI;AAChB,UAAI,CAAC,SAAS,gBAAgB,MAAM;AAChC,gBAAQ,aAAa,IAAI,KAAK,aAAa;AAAA,MAC/C;AACA,eAAS,MAAM,cAAc;AAAA,QACzB,MAAM,IAAI;AAAA,QACV,MAAM,IAAI;AAAA,QACV,OAAO,SAAS;AAAA,QAChB,cAAc,SAAS;AAAA,QACvB;AAAA,QACA,OAAO;AAAA,QACP,iBAAiB;AAAA,UACb,YAAY,CAAC,MAAM,SAAS;AACxB,2BAAe,EAAE,MAAM,aAAa,WAAW,IAAI,WAAW,SAAS,IAAI,WAAW,WAAW,KAAK,IAAI,GAAG,MAAM,EAAE,MAAM,KAAK,EAAE,CAAC;AAAA,UACvI;AAAA,UACA,cAAc,CAAC,MAAM,GAAG,YAAY,YAAY;AAC5C,2BAAe,EAAE,MAAM,YAAY,WAAW,IAAI,WAAW,SAAS,IAAI,WAAW,WAAW,KAAK,IAAI,GAAG,MAAM,EAAE,MAAM,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,YAAY,QAAQ,EAAE,CAAC;AAAA,UAC9K;AAAA,UACA,SAAS,CAAC,OAAO,cAAc;AAC3B,2BAAe,EAAE,MAAM,SAAS,WAAW,IAAI,WAAW,SAAS,IAAI,WAAW,WAAW,KAAK,IAAI,GAAG,MAAM,EAAE,OAAO,UAAU,EAAE,CAAC;AAAA,UACzI;AAAA,QACJ;AAAA,MACJ,CAAC;AAAA,IACL;AAGA,WAAO,IAAI,IAAI;AAAA,MACX,QAAQ,OAAO,UAAU,cAAc;AAAA,MACvC,WAAW,OAAO;AAAA,IACtB,CAAC;AAGD,sBAAkB,IAAI,SAAS,MAAM;AAErC,UAAM,iBAAiB,iBAAiB,MAAM;AAAA,EAClD,SAAS,KAAK;AACV,UAAM,QAAS,IAAc;AAC7B,QAAI,SAAS;AACb,QAAI,QAAQ;AACZ,QAAI,cAAc,KAAK,IAAI;AAE3B,WAAO,IAAI,IAAI,EAAE,QAAQ,SAAS,MAAM,CAAC;AAGzC,sBAAkB,IAAI,SAAS,MAAM;AAGrC,oBAAgB,IAAI,SAAS,yBAAyB,KAAK,IAAI,EAAE,SAAS,IAAI,QAAQ,CAAC;AACvF,gBAAY,IAAI,SAAS,EAAE,QAAQ,OAAO,CAAC;AAE3C,gBAAY,KAAK,qBAAqB;AAAA,MAClC;AAAA,MACA,SAAS,IAAI;AAAA,MACb,SAAS,IAAI;AAAA,MACb;AAAA,IACJ,CAAC;AAED,WAAO,MAAM,WAAW,YAAY,eAAe,YAAY,KAAK,EAAE;AAAA,EAC1E;AACJ;AAKA,eAAe,iBAAiB,iBAAyB,QAAuC;AAC5F,QAAM,MAAM,YAAY,IAAI,eAAe;AAC3C,MAAI,CAAC,IAAK;AAEV,MAAI,SAAS;AACb,MAAI,cAAc,KAAK,IAAI;AAE3B,QAAM,gBAAgB,IAAI,cAAc,IAAI,aAAa,KAAM,QAAQ,CAAC;AACxE,SAAO,KAAK,WAAW,YAAY,eAAe,iBAAiB,WAAW,YAAO,OAAO,UAAU,YAAY,QAAQ,EAAE;AAG5H,QAAM,cAAc;AAAA,IAChB,yBAAyB,OAAO,MAAM,YAAY,OAAO,UAAU;AAAA,IACnE,WAAW,OAAO,UAAU,YAAY,QAAQ,GAAG,OAAO,YAAY,KAAK,gBAAgB;AAAA,IAC3F,UAAU,OAAO,UAAU,KAAK,IAAI,KAAK,MAAM;AAAA,IAC/C;AAAA,IACA,OAAO;AAAA,EACX,EAAE,KAAK,IAAI;AACX,kBAAgB,IAAI,SAAS,aAAa,EAAE,SAAS,IAAI,QAAQ,CAAC;AAGlE,cAAY,IAAI,SAAS,EAAE,QAAQ,OAAO,UAAU,SAAS,OAAO,CAAC;AAGrE,MAAI,IAAI,iBAAiB;AACrB,UAAM,WAAW,eAAe,IAAI,IAAI,eAAe,KAAK,CAAC;AAC7D,aAAS,KAAK;AAAA,MACV,SAAS,IAAI;AAAA,MACb,iBAAiB,IAAI;AAAA,MACrB,WAAW,IAAI;AAAA,MACf;AAAA,MACA,aAAa,KAAK,IAAI;AAAA,IAC1B,CAAC;AACD,mBAAe,IAAI,IAAI,iBAAiB,QAAQ;AAAA,EACpD;AAGA,cAAY,KAAK,wBAAwB;AAAA,IACrC;AAAA,IACA,SAAS,IAAI;AAAA,IACb,iBAAiB,IAAI;AAAA,IACrB,SAAS,IAAI;AAAA,IACb,WAAW,IAAI;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,SAAS,OAAO,QAAQ,MAAM,GAAG,GAAG;AAAA,IACpC,YAAY,OAAO;AAAA,IACnB,QAAQ,OAAO;AAAA,EACnB,CAAC;AACL;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/agentWakeup.ts"],"sourcesContent":["/**\n * TITAN — Agent Wakeup System\n *\n * Async sub-agent delegation via Command Post. When `spawn_agent` is called\n * with Command Post enabled, instead of blocking the parent for 1-15s, we:\n * 1. Create a CP issue + queue a wakeup request\n * 2. Return immediately to the parent\n * 3. Execute the sub-agent in the background\n * 4. Post results as CP comment, store for parent injection, emit SSE\n *\n * Gated behind config.commandPost.enabled — when disabled, the sync path\n * in agent.ts is used instead (zero regression risk).\n */\nimport { v4 as uuid } from 'uuid';\nimport { titanEvents } from './daemon.js';\nimport { spawnSubAgent, SUB_AGENT_TEMPLATES, type SubAgentResult } from './subAgent.js';\nimport { routeMessage } from './multiAgent.js';\nimport { getAdapter } from './adapters/index.js';\nimport { addIssueComment, updateIssue, startRun, endRun, updateAgentStatus } from './commandPost.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { emitAgentEvent } from './agentEvents.js';\n\nconst COMPONENT = 'AgentWakeup';\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport type WakeupStatus = 'queued' | 'running' | 'completed' | 'failed';\n\nexport type WakeupMode = 'sub-agent' | 'multi-agent' | 'external';\n\nexport interface WakeupRequest {\n id: string;\n issueId: string;\n issueIdentifier: string; // e.g. \"TIT-42\"\n agentId: string;\n agentName: string;\n parentSessionId: string | null;\n task: string;\n templateName: string;\n model?: string;\n mode: WakeupMode; // 'sub-agent' = spawnSubAgent, 'multi-agent' = routeMessage, 'external' = adapter\n adapterType?: string; // For external mode: 'claude-code', 'codex', 'bash'\n cwd?: string; // Working directory for external adapters\n status: WakeupStatus;\n createdAt: number;\n completedAt: number | null;\n error: string | null;\n}\n\nexport interface PendingResult {\n issueId: string;\n issueIdentifier: string;\n agentName: string;\n result: SubAgentResult;\n completedAt: number;\n}\n\n// ── In-Memory State ───────────────────────────────────────────────────\n\nconst wakeupQueue = new Map<string, WakeupRequest>();\nconst pendingResults = new Map<string, PendingResult[]>(); // key: parentSessionId\nlet cleanupInterval: ReturnType<typeof setInterval> | null = null;\nlet wakeupListener: ((data: { wakeupRequestId: string }) => void) | null = null;\nlet initialized = false;\n\n// ── Public API ────────────────────────────────────────────────────────\n\n/**\n * Initialize the wakeup system. Call once from gateway startup.\n * Registers the event listener on titanEvents.\n */\nexport function initWakeupSystem(): void {\n if (initialized) return;\n initialized = true;\n\n wakeupListener = (data: { wakeupRequestId: string }) => {\n // Use setImmediate so queueWakeup() returns before we start executing\n setImmediate(() => {\n handleWakeup(data.wakeupRequestId).catch(err => {\n logger.error(COMPONENT, `Wakeup handler failed: ${(err as Error).message}`);\n });\n });\n };\n titanEvents.on('agent:wakeup', wakeupListener);\n\n // TTL cleanup: sweep stale requests every 60s\n cleanupInterval = setInterval(() => {\n const now = Date.now();\n for (const [id, req] of wakeupQueue) {\n if (req.status === 'queued' && now - req.createdAt > 3600_000) {\n // Queued for over 1 hour — cancel\n req.status = 'failed';\n req.error = 'TTL expired (queued > 1 hour)';\n req.completedAt = now;\n logger.warn(COMPONENT, `[TTL] Wakeup ${id} expired (queued > 1h)`);\n }\n if (req.status === 'running' && now - req.createdAt > 300_000) {\n // Running for over 5 minutes — mark failed\n req.status = 'failed';\n req.error = 'Timeout (running > 5 minutes)';\n req.completedAt = now;\n logger.warn(COMPONENT, `[TTL] Wakeup ${id} timed out (running > 5m)`);\n }\n // Clean up completed/failed requests older than 30 minutes\n if ((req.status === 'completed' || req.status === 'failed') && req.completedAt && now - req.completedAt > 1800_000) {\n wakeupQueue.delete(id);\n }\n }\n // Clean up stale pendingResults (parent never drained them)\n for (const [sessionId, results] of pendingResults) {\n const stale = results.filter(r => now - r.completedAt > 3600_000);\n if (stale.length === results.length) {\n pendingResults.delete(sessionId);\n } else if (stale.length > 0) {\n pendingResults.set(sessionId, results.filter(r => now - r.completedAt <= 3600_000));\n }\n }\n }, 60_000);\n cleanupInterval.unref();\n\n logger.info(COMPONENT, 'Agent wakeup system initialized');\n}\n\n/**\n * Queue a wakeup request. Creates the request, stores it, and emits the event.\n * Returns immediately — actual execution happens in the background.\n */\nexport function queueWakeup(opts: {\n issueId: string;\n issueIdentifier: string;\n agentId: string;\n agentName: string;\n parentSessionId: string | null;\n task: string;\n templateName: string;\n model?: string;\n mode?: WakeupMode;\n adapterType?: string;\n cwd?: string;\n}): WakeupRequest {\n const request: WakeupRequest = {\n id: `wake_${uuid().slice(0, 8)}`,\n issueId: opts.issueId,\n issueIdentifier: opts.issueIdentifier,\n agentId: opts.agentId,\n agentName: opts.agentName,\n parentSessionId: opts.parentSessionId,\n task: opts.task,\n templateName: opts.templateName,\n model: opts.model,\n mode: opts.mode || 'sub-agent',\n adapterType: opts.adapterType,\n cwd: opts.cwd,\n status: 'queued',\n createdAt: Date.now(),\n completedAt: null,\n error: null,\n };\n\n wakeupQueue.set(request.id, request);\n logger.info(COMPONENT, `Queued wakeup ${request.id} for agent \"${opts.agentName}\" — issue ${opts.issueIdentifier}`);\n\n titanEvents.emit('agent:wakeup', { wakeupRequestId: request.id });\n titanEvents.emit('commandpost:activity', {\n id: uuid().slice(0, 8),\n timestamp: new Date().toISOString(),\n type: 'agent_status_change',\n agentId: opts.agentId,\n message: `Wakeup queued: ${opts.task.slice(0, 80)}`,\n metadata: { wakeupRequestId: request.id, issueId: opts.issueId },\n });\n\n return request;\n}\n\n/**\n * Drain completed async results for a session. Returns and clears them.\n * Called by agentLoop at the start of processMessage to inject context.\n */\nexport function drainPendingResults(sessionId: string): PendingResult[] {\n const results = pendingResults.get(sessionId);\n if (!results || results.length === 0) return [];\n pendingResults.delete(sessionId);\n return results;\n}\n\n/**\n * Get all wakeup requests for an agent (the \"inbox\").\n */\nexport function getAgentInbox(agentId: string): WakeupRequest[] {\n return [...wakeupQueue.values()].filter(\n r => r.agentId === agentId && (r.status === 'queued' || r.status === 'running')\n );\n}\n\n/**\n * Get a wakeup request by ID.\n */\nexport function getWakeupRequest(requestId: string): WakeupRequest | null {\n return wakeupQueue.get(requestId) || null;\n}\n\n/**\n * Claim a queued wakeup request (transition to running). Returns false if not queued.\n */\nexport function claimWakeupRequest(requestId: string): boolean {\n const req = wakeupQueue.get(requestId);\n if (!req || req.status !== 'queued') return false;\n req.status = 'running';\n logger.info(COMPONENT, `Claimed wakeup ${requestId}`);\n return true;\n}\n\n/**\n * Cancel a queued wakeup request. Returns false if already running/completed.\n */\nexport function cancelWakeup(requestId: string): boolean {\n const req = wakeupQueue.get(requestId);\n if (!req || req.status !== 'queued') return false;\n req.status = 'failed';\n req.error = 'Cancelled';\n req.completedAt = Date.now();\n logger.info(COMPONENT, `Cancelled wakeup ${requestId}`);\n return true;\n}\n\n/**\n * Shutdown: cancel all queued requests and clear state.\n */\nexport function shutdownWakeupSystem(): void {\n if (cleanupInterval) {\n clearInterval(cleanupInterval);\n cleanupInterval = null;\n }\n // Cancel all queued requests\n for (const [, req] of wakeupQueue) {\n if (req.status === 'queued') {\n req.status = 'failed';\n req.error = 'System shutdown';\n req.completedAt = Date.now();\n }\n }\n if (wakeupListener) {\n titanEvents.off('agent:wakeup', wakeupListener);\n wakeupListener = null;\n }\n initialized = false;\n logger.info(COMPONENT, 'Agent wakeup system shut down');\n}\n\n// ── Internal ──────────────────────────────────────────────────────────\n\n/**\n * Handle a wakeup event. Claims the request, runs the sub-agent in\n * the background, and handles completion.\n */\nasync function handleWakeup(wakeupRequestId: string): Promise<void> {\n const req = wakeupQueue.get(wakeupRequestId);\n if (!req) {\n logger.warn(COMPONENT, `Wakeup ${wakeupRequestId} not found in queue`);\n return;\n }\n if (req.status !== 'queued') {\n logger.warn(COMPONENT, `Wakeup ${wakeupRequestId} already ${req.status}, skipping`);\n return;\n }\n\n // Claim\n req.status = 'running';\n logger.info(COMPONENT, `[Wakeup] Claimed ${wakeupRequestId} — running sub-agent \"${req.agentName}\" for issue ${req.issueIdentifier}`);\n\n // Activate agent in CP registry\n updateAgentStatus(req.agentId, 'active');\n\n // Transition CP issue to in_progress\n updateIssue(req.issueId, { status: 'in_progress' });\n\n // Start a CP run for tracking\n const run = startRun(req.agentId, 'assignment', req.issueId);\n\n // Emit SSE event\n titanEvents.emit('agent:wakeup:running', {\n wakeupRequestId,\n issueId: req.issueId,\n agentId: req.agentId,\n });\n\n try {\n let result: SubAgentResult;\n\n if (req.mode === 'multi-agent') {\n // ── Multi-agent path: route through the full agent system ──\n logger.info(COMPONENT, `[Wakeup] Multi-agent delegation to \"${req.agentName}\" (${req.agentId})`);\n const startMs = Date.now();\n const agentResponse = await routeMessage(req.task, 'delegation', req.agentId, { overrideAgentId: req.agentId });\n result = {\n content: agentResponse.content,\n toolsUsed: agentResponse.toolsUsed || [],\n success: !agentResponse.content.toLowerCase().includes('error'),\n durationMs: Date.now() - startMs,\n rounds: 1,\n validated: true,\n };\n } else if (req.mode === 'external') {\n // ── External adapter path: spawn CLI process ──\n const adapter = getAdapter(req.adapterType || 'bash');\n if (!adapter) throw new Error(`Unknown adapter type: ${req.adapterType}`);\n\n logger.info(COMPONENT, `[Wakeup] External adapter \"${adapter.displayName}\" for issue ${req.issueIdentifier}`);\n const config = loadConfig();\n const port = (config.gateway as Record<string, unknown>).port as number || 48420;\n const adapterResult = await adapter.execute({\n task: req.task,\n cwd: req.cwd,\n titanApiUrl: `http://localhost:${port}`,\n titanRunId: run.id,\n titanIssueId: req.issueId,\n timeoutMs: 300_000,\n });\n result = {\n content: adapterResult.content,\n toolsUsed: adapterResult.toolsUsed,\n success: adapterResult.success,\n durationMs: adapterResult.durationMs,\n rounds: 1,\n validated: true,\n };\n } else {\n // ── Sub-agent path: isolated sub-agent execution ──\n const template = SUB_AGENT_TEMPLATES[req.templateName] || {};\n const config = loadConfig();\n const modelAliases = (config.agent as Record<string, unknown>).modelAliases as Record<string, string> | undefined;\n const tier = (template as Record<string, unknown>).tier as string | undefined;\n let model = req.model;\n if (!model && modelAliases && tier) {\n model = modelAliases[tier] || modelAliases.fast;\n }\n result = await spawnSubAgent({\n name: req.agentName,\n task: req.task,\n tools: template.tools,\n systemPrompt: template.systemPrompt,\n model,\n depth: 0,\n streamCallbacks: {\n onToolCall: (name, args) => {\n emitAgentEvent({ type: 'tool_call', agentName: req.agentName, agentId: req.agentName, timestamp: Date.now(), data: { name, args } });\n },\n onToolResult: (name, r, durationMs, success) => {\n emitAgentEvent({ type: 'tool_end', agentName: req.agentName, agentId: req.agentName, timestamp: Date.now(), data: { name, result: r.slice(0, 500), durationMs, success } });\n },\n onRound: (round, maxRounds) => {\n emitAgentEvent({ type: 'round', agentName: req.agentName, agentId: req.agentName, timestamp: Date.now(), data: { round, maxRounds } });\n },\n },\n });\n }\n\n // Complete the CP run\n endRun(run.id, {\n status: result.success ? 'succeeded' : 'failed',\n toolsUsed: result.toolsUsed,\n });\n\n // Deactivate agent\n updateAgentStatus(req.agentId, 'idle');\n\n await handleCompletion(wakeupRequestId, result);\n } catch (err) {\n const error = (err as Error).message;\n req.status = 'failed';\n req.error = error;\n req.completedAt = Date.now();\n\n endRun(run.id, { status: 'error', error });\n\n // Deactivate agent\n updateAgentStatus(req.agentId, 'idle');\n\n // Post failure comment on issue\n addIssueComment(req.issueId, `**Sub-agent failed**: ${error}`, { agentId: req.agentId });\n updateIssue(req.issueId, { status: 'todo' }); // Back to todo for retry\n\n titanEvents.emit('agent:task:failed', {\n wakeupRequestId,\n issueId: req.issueId,\n agentId: req.agentId,\n error,\n });\n\n logger.error(COMPONENT, `[Wakeup] ${wakeupRequestId} failed: ${error}`);\n }\n}\n\n/**\n * Handle successful sub-agent completion.\n */\nasync function handleCompletion(wakeupRequestId: string, result: SubAgentResult): Promise<void> {\n const req = wakeupQueue.get(wakeupRequestId);\n if (!req) return;\n\n req.status = 'completed';\n req.completedAt = Date.now();\n\n const durationSec = ((req.completedAt - req.createdAt) / 1000).toFixed(1);\n logger.info(COMPONENT, `[Wakeup] ${wakeupRequestId} completed in ${durationSec}s — ${result.success ? 'SUCCESS' : 'FAILED'}`);\n\n // 1. Post result as CP issue comment\n const commentBody = [\n `**Sub-agent result** (${result.rounds} rounds, ${result.durationMs}ms)`,\n `Status: ${result.success ? 'SUCCESS' : 'FAILED'}${result.validated ? '' : ' [UNVALIDATED]'}`,\n `Tools: ${result.toolsUsed.join(', ') || 'none'}`,\n '',\n result.content,\n ].join('\\n');\n addIssueComment(req.issueId, commentBody, { agentId: req.agentId });\n\n // 2. Transition issue status\n updateIssue(req.issueId, { status: result.success ? 'done' : 'todo' });\n\n // 3. Store pending result for parent session injection\n if (req.parentSessionId) {\n const existing = pendingResults.get(req.parentSessionId) || [];\n existing.push({\n issueId: req.issueId,\n issueIdentifier: req.issueIdentifier,\n agentName: req.agentName,\n result,\n completedAt: Date.now(),\n });\n pendingResults.set(req.parentSessionId, existing);\n }\n\n // 4. Emit SSE event for UI\n titanEvents.emit('agent:task:completed', {\n wakeupRequestId,\n issueId: req.issueId,\n issueIdentifier: req.issueIdentifier,\n agentId: req.agentId,\n agentName: req.agentName,\n success: result.success,\n summary: result.content.slice(0, 200),\n durationMs: result.durationMs,\n rounds: result.rounds,\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -26,6 +26,7 @@ let issueCounter = 0;
|
|
|
26
26
|
let config = null;
|
|
27
27
|
let sweepInterval = null;
|
|
28
28
|
let heartbeatInterval = null;
|
|
29
|
+
let approvalSweepInterval = null;
|
|
29
30
|
let initialized = false;
|
|
30
31
|
const eventListeners = [];
|
|
31
32
|
function loadState() {
|
|
@@ -538,7 +539,7 @@ function initCommandPost(cfg) {
|
|
|
538
539
|
syncAgentRegistry();
|
|
539
540
|
sweepInterval = setInterval(sweepExpiredCheckouts, 6e4);
|
|
540
541
|
sweepInterval.unref();
|
|
541
|
-
|
|
542
|
+
approvalSweepInterval = setInterval(() => {
|
|
542
543
|
sweepStaleApprovals();
|
|
543
544
|
sweepAncientPendingApprovals();
|
|
544
545
|
}, 3e5);
|
|
@@ -1308,6 +1309,10 @@ function shutdownCommandPost() {
|
|
|
1308
1309
|
clearInterval(heartbeatInterval);
|
|
1309
1310
|
heartbeatInterval = null;
|
|
1310
1311
|
}
|
|
1312
|
+
if (approvalSweepInterval) {
|
|
1313
|
+
clearInterval(approvalSweepInterval);
|
|
1314
|
+
approvalSweepInterval = null;
|
|
1315
|
+
}
|
|
1311
1316
|
if (initialized) saveState();
|
|
1312
1317
|
for (const { event, handler } of eventListeners) {
|
|
1313
1318
|
titanEvents.removeListener(event, handler);
|