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.
Files changed (48) hide show
  1. package/dist/agent/agent.js +1 -1
  2. package/dist/agent/agent.js.map +1 -1
  3. package/dist/agent/agentLoop.js +77 -12
  4. package/dist/agent/agentLoop.js.map +1 -1
  5. package/dist/agent/agentWakeup.js +8 -3
  6. package/dist/agent/agentWakeup.js.map +1 -1
  7. package/dist/agent/commandPost.js +6 -1
  8. package/dist/agent/commandPost.js.map +1 -1
  9. package/dist/agent/heartbeatScheduler.js +36 -4
  10. package/dist/agent/heartbeatScheduler.js.map +1 -1
  11. package/dist/agent/toolRunner.js +30 -0
  12. package/dist/agent/toolRunner.js.map +1 -1
  13. package/dist/config/config.js +30 -8
  14. package/dist/config/config.js.map +1 -1
  15. package/dist/config/schema.js +10 -1
  16. package/dist/config/schema.js.map +1 -1
  17. package/dist/eval/record.js +1 -1
  18. package/dist/eval/record.js.map +1 -1
  19. package/dist/gateway/server.js +26 -0
  20. package/dist/gateway/server.js.map +1 -1
  21. package/dist/mesh/transport.js +60 -8
  22. package/dist/mesh/transport.js.map +1 -1
  23. package/dist/providers/anthropic.js +3 -2
  24. package/dist/providers/anthropic.js.map +1 -1
  25. package/dist/providers/base.js.map +1 -1
  26. package/dist/providers/google.js +94 -20
  27. package/dist/providers/google.js.map +1 -1
  28. package/dist/providers/modelCapabilities.js +59 -0
  29. package/dist/providers/modelCapabilities.js.map +1 -0
  30. package/dist/providers/ollama.js +3 -2
  31. package/dist/providers/ollama.js.map +1 -1
  32. package/dist/providers/openai.js +4 -3
  33. package/dist/providers/openai.js.map +1 -1
  34. package/dist/providers/openai_compat.js +3 -2
  35. package/dist/providers/openai_compat.js.map +1 -1
  36. package/dist/providers/router.js +63 -21
  37. package/dist/providers/router.js.map +1 -1
  38. package/dist/skills/registry.js +176 -163
  39. package/dist/skills/registry.js.map +1 -1
  40. package/dist/telemetry/activityLog.js +1 -1
  41. package/dist/telemetry/activityLog.js.map +1 -1
  42. package/dist/utils/constants.js +2 -2
  43. package/dist/utils/constants.js.map +1 -1
  44. package/docs/AGENT-HIERARCHY.md +154 -0
  45. package/docs/superpowers/plans/2026-04-29-titan-production-fix.md +241 -0
  46. package/package.json +2 -2
  47. package/scripts/start-workers.sh +39 -0
  48. package/scripts/task-feeder.ts +38 -0
@@ -2,13 +2,13 @@
2
2
  import { existsSync, mkdirSync, readdirSync, writeFileSync, statSync, rmSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { createHash } from "crypto";
5
+ import { loadConfig } from "../config/config.js";
5
6
  let _retentionDaysOverride;
6
7
  const AUTO_DIR = join(process.cwd(), "tests", "fixtures", "tapes", "auto");
7
8
  const DEFAULT_RETENTION_DAYS = 30;
8
9
  function getRetentionDays() {
9
10
  if (_retentionDaysOverride !== void 0) return _retentionDaysOverride;
10
11
  try {
11
- const { loadConfig } = require("../config/config.js");
12
12
  const config = loadConfig();
13
13
  const days = config.eval?.autoCorpus?.retentionDays;
14
14
  if (typeof days === "number" && days >= 0) return days;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/eval/record.ts"],"sourcesContent":["/**\n * TITAN — Auto-Corpus Expansion (Phase 6)\n *\n * When a production trace fails eval, automatically add it to the tape corpus.\n * Deduplication prevents bloating. Configurable retention purges old auto-tapes.\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync, rmSync } from 'fs';\nimport { join } from 'path';\nimport { createHash } from 'crypto';\nimport type { EvalCase, EvalResult } from './harness.js';\n\nlet _retentionDaysOverride: number | undefined;\n\nconst AUTO_DIR = join(process.cwd(), 'tests', 'fixtures', 'tapes', 'auto');\nconst DEFAULT_RETENTION_DAYS = 30;\n\n/**\n * Read the auto-corpus retention days from titan.json config.\n * Falls back to DEFAULT_RETENTION_DAYS if config fails to load.\n */\nexport function getRetentionDays(): number {\n if (_retentionDaysOverride !== undefined) return _retentionDaysOverride;\n try {\n const { loadConfig } = require('../config/config.js');\n const config = loadConfig();\n const days = config.eval?.autoCorpus?.retentionDays;\n if (typeof days === 'number' && days >= 0) return days;\n } catch {\n // config module may not be available in test/browser contexts\n }\n return DEFAULT_RETENTION_DAYS;\n}\n\n/** Test-only: override the retention days without touching config files */\nexport function _setRetentionDaysOverride(days: number | undefined): void {\n _retentionDaysOverride = days;\n}\n\n/** Compute a stable hash of the input for deduplication */\nfunction hashInput(input: string): string {\n return createHash('sha256').update(input).digest('hex').slice(0, 16);\n}\n\n/** Ensure the auto-tape directory exists */\nfunction ensureAutoDir(): void {\n if (!existsSync(AUTO_DIR)) {\n mkdirSync(AUTO_DIR, { recursive: true });\n }\n}\n\n/** Build a tape filename from metadata */\nfunction buildTapeName(suite: string, name: string, timestamp: number, inputHash: string): string {\n const safeSuite = suite.replace(/[^a-z0-9_-]/gi, '_');\n const safeName = name.replace(/[^a-z0-9_-]/gi, '_');\n return `${timestamp}_${safeSuite}_${safeName}_${inputHash}.json`;\n}\n\n/** Check if an auto-tape with the same input hash already exists */\nfunction hasExistingTape(inputHash: string): boolean {\n if (!existsSync(AUTO_DIR)) return false;\n const files = readdirSync(AUTO_DIR);\n return files.some(f => f.includes(`_${inputHash}.json`));\n}\n\nexport interface RecordOptions {\n suite?: string;\n name?: string;\n retentionDays?: number;\n}\n\nexport interface RecordedTape {\n path: string;\n deduplicated: boolean;\n inputHash: string;\n}\n\n/**\n * Record a failed eval trace as a new auto-tape.\n *\n * Returns the path to the written file, or null if deduplicated.\n * Throws on I/O errors.\n */\nexport function recordFailedTrace(\n input: string,\n expected: EvalCase,\n actual: EvalResult,\n options: RecordOptions = {},\n): RecordedTape {\n ensureAutoDir();\n\n const inputHash = hashInput(input);\n\n if (hasExistingTape(inputHash)) {\n return { path: '', deduplicated: true, inputHash };\n }\n\n const timestamp = Date.now();\n const suite = options.suite || 'unknown';\n const name = options.name || expected.name || 'untitled';\n const filename = buildTapeName(suite, name, timestamp, inputHash);\n const filepath = join(AUTO_DIR, filename);\n\n const tape = {\n name,\n suite,\n model: 'auto-corpus',\n recorded_at: new Date(timestamp).toISOString(),\n titan_version: process.env.npm_package_version || '0.0.0',\n input,\n expected: {\n tools: expected.expectedTools,\n toolSequence: expected.expectedToolSequence,\n content: expected.expectedContent?.toString(),\n forbiddenTools: expected.forbiddenTools,\n },\n actual: {\n passed: actual.passed,\n errors: actual.errors,\n toolsUsed: actual.toolsUsed,\n content: actual.content,\n },\n exchanges: [],\n };\n\n writeFileSync(filepath, JSON.stringify(tape, null, 2), 'utf-8');\n\n return { path: filepath, deduplicated: false, inputHash };\n}\n\n/**\n * Purge auto-tapes older than the retention threshold.\n *\n * Returns the number of files removed.\n */\nexport function purgeOldAutoTapes(retentionDays?: number): number {\n const days = retentionDays ?? getRetentionDays();\n if (days === 0) return 0; // 0 = never purge\n if (!existsSync(AUTO_DIR)) return 0;\n\n const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;\n const files = readdirSync(AUTO_DIR);\n let removed = 0;\n\n for (const file of files) {\n const filepath = join(AUTO_DIR, file);\n try {\n const stats = statSync(filepath);\n if (stats.mtimeMs < cutoff) {\n rmSync(filepath);\n removed++;\n }\n } catch {\n // Ignore stat/rm errors on individual files\n }\n }\n\n return removed;\n}\n\n/**\n * List all auto-tapes with metadata.\n */\nexport function listAutoTapes(): Array<{ name: string; path: string; size: number; mtime: Date }> {\n if (!existsSync(AUTO_DIR)) return [];\n\n return readdirSync(AUTO_DIR)\n .filter(f => f.endsWith('.json'))\n .map(f => {\n const filepath = join(AUTO_DIR, f);\n const stats = statSync(filepath);\n return {\n name: f,\n path: filepath,\n size: stats.size,\n mtime: stats.mtime,\n };\n })\n .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/eval/record.ts"],"sourcesContent":["/**\n * TITAN — Auto-Corpus Expansion (Phase 6)\n *\n * When a production trace fails eval, automatically add it to the tape corpus.\n * Deduplication prevents bloating. Configurable retention purges old auto-tapes.\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync, rmSync } from 'fs';\nimport { join } from 'path';\nimport { createHash } from 'crypto';\nimport { loadConfig } from '../config/config.js';\nimport type { EvalCase, EvalResult } from './harness.js';\n\nlet _retentionDaysOverride: number | undefined;\n\nconst AUTO_DIR = join(process.cwd(), 'tests', 'fixtures', 'tapes', 'auto');\nconst DEFAULT_RETENTION_DAYS = 30;\n\n/**\n * Read the auto-corpus retention days from titan.json config.\n * Falls back to DEFAULT_RETENTION_DAYS if config fails to load.\n *\n * Note: pre-fix this used `require('../config/config.js')` inside the\n * function body to defer module load. Switched to a static ESM import\n * because (a) ESLint forbids `require()` style imports under\n * `@typescript-eslint/no-require-imports` and (b) there is no circular\n * dependency — config.ts does not import this module. The try/catch\n * still handles a thrown `loadConfig()` from a malformed titan.json.\n */\nexport function getRetentionDays(): number {\n if (_retentionDaysOverride !== undefined) return _retentionDaysOverride;\n try {\n const config = loadConfig();\n const days = config.eval?.autoCorpus?.retentionDays;\n if (typeof days === 'number' && days >= 0) return days;\n } catch {\n // loadConfig() can throw on malformed titan.json fall back to default.\n }\n return DEFAULT_RETENTION_DAYS;\n}\n\n/** Test-only: override the retention days without touching config files */\nexport function _setRetentionDaysOverride(days: number | undefined): void {\n _retentionDaysOverride = days;\n}\n\n/** Compute a stable hash of the input for deduplication */\nfunction hashInput(input: string): string {\n return createHash('sha256').update(input).digest('hex').slice(0, 16);\n}\n\n/** Ensure the auto-tape directory exists */\nfunction ensureAutoDir(): void {\n if (!existsSync(AUTO_DIR)) {\n mkdirSync(AUTO_DIR, { recursive: true });\n }\n}\n\n/** Build a tape filename from metadata */\nfunction buildTapeName(suite: string, name: string, timestamp: number, inputHash: string): string {\n const safeSuite = suite.replace(/[^a-z0-9_-]/gi, '_');\n const safeName = name.replace(/[^a-z0-9_-]/gi, '_');\n return `${timestamp}_${safeSuite}_${safeName}_${inputHash}.json`;\n}\n\n/** Check if an auto-tape with the same input hash already exists */\nfunction hasExistingTape(inputHash: string): boolean {\n if (!existsSync(AUTO_DIR)) return false;\n const files = readdirSync(AUTO_DIR);\n return files.some(f => f.includes(`_${inputHash}.json`));\n}\n\nexport interface RecordOptions {\n suite?: string;\n name?: string;\n retentionDays?: number;\n}\n\nexport interface RecordedTape {\n path: string;\n deduplicated: boolean;\n inputHash: string;\n}\n\n/**\n * Record a failed eval trace as a new auto-tape.\n *\n * Returns the path to the written file, or null if deduplicated.\n * Throws on I/O errors.\n */\nexport function recordFailedTrace(\n input: string,\n expected: EvalCase,\n actual: EvalResult,\n options: RecordOptions = {},\n): RecordedTape {\n ensureAutoDir();\n\n const inputHash = hashInput(input);\n\n if (hasExistingTape(inputHash)) {\n return { path: '', deduplicated: true, inputHash };\n }\n\n const timestamp = Date.now();\n const suite = options.suite || 'unknown';\n const name = options.name || expected.name || 'untitled';\n const filename = buildTapeName(suite, name, timestamp, inputHash);\n const filepath = join(AUTO_DIR, filename);\n\n const tape = {\n name,\n suite,\n model: 'auto-corpus',\n recorded_at: new Date(timestamp).toISOString(),\n titan_version: process.env.npm_package_version || '0.0.0',\n input,\n expected: {\n tools: expected.expectedTools,\n toolSequence: expected.expectedToolSequence,\n content: expected.expectedContent?.toString(),\n forbiddenTools: expected.forbiddenTools,\n },\n actual: {\n passed: actual.passed,\n errors: actual.errors,\n toolsUsed: actual.toolsUsed,\n content: actual.content,\n },\n exchanges: [],\n };\n\n writeFileSync(filepath, JSON.stringify(tape, null, 2), 'utf-8');\n\n return { path: filepath, deduplicated: false, inputHash };\n}\n\n/**\n * Purge auto-tapes older than the retention threshold.\n *\n * Returns the number of files removed.\n */\nexport function purgeOldAutoTapes(retentionDays?: number): number {\n const days = retentionDays ?? getRetentionDays();\n if (days === 0) return 0; // 0 = never purge\n if (!existsSync(AUTO_DIR)) return 0;\n\n const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;\n const files = readdirSync(AUTO_DIR);\n let removed = 0;\n\n for (const file of files) {\n const filepath = join(AUTO_DIR, file);\n try {\n const stats = statSync(filepath);\n if (stats.mtimeMs < cutoff) {\n rmSync(filepath);\n removed++;\n }\n } catch {\n // Ignore stat/rm errors on individual files\n }\n }\n\n return removed;\n}\n\n/**\n * List all auto-tapes with metadata.\n */\nexport function listAutoTapes(): Array<{ name: string; path: string; size: number; mtime: Date }> {\n if (!existsSync(AUTO_DIR)) return [];\n\n return readdirSync(AUTO_DIR)\n .filter(f => f.endsWith('.json'))\n .map(f => {\n const filepath = join(AUTO_DIR, f);\n const stats = statSync(filepath);\n return {\n name: f,\n path: filepath,\n size: stats.size,\n mtime: stats.mtime,\n };\n })\n .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n}\n"],"mappings":"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","names":[]}
@@ -66,6 +66,7 @@ import { initAutopilot, stopAutopilot, runAutopilotNow, getAutopilotStatus, getR
66
66
  import { initDaemon, stopDaemon, getDaemonStatus, pauseDaemonManual, resumeDaemon, titanEvents } from "../agent/daemon.js";
67
67
  import { initCommandPost, shutdownCommandPost, isCommandPostEnabled, getDashboard as getCPDashboard, getRegisteredAgents, reportHeartbeat, removeAgent, checkoutTask, checkinTask, getActiveCheckouts, getBudgetPolicies, createBudgetPolicy, updateBudgetPolicy, deleteBudgetPolicy, getActivity, getGoalTree, getAncestryChain, validateGoalAncestry, validateGoalParentAssignment, sweepExpiredCheckoutsManual, getStaleAgents, enforceBudgetForAgent, getBudgetPolicyForAgent, createIssue, updateIssue, getIssue, listIssues, searchIssues, checkoutIssue, deleteIssue, addIssueComment, getIssueComments, createApproval, approveApproval, rejectApproval, listApprovals, replyToApproval, snoozeApproval, unsnoozeApproval, batchApprove, batchReject, getAgentMessages, markAgentMessageRead, startRun, listRuns, getOrgTree, updateRegisteredAgent } from "../agent/commandPost.js";
68
68
  import { initWakeupSystem, getAgentInbox, queueWakeup, getWakeupRequest, cancelWakeup, drainPendingResults } from "../agent/agentWakeup.js";
69
+ import { initHeartbeatScheduler } from "../agent/heartbeatScheduler.js";
69
70
  import { queryAuditLog, getAuditStats } from "../agent/auditLog.js";
70
71
  import { listGoals, createGoal, getGoal, deleteGoal, updateGoal, completeSubtask, addSubtask, dedupeGoalsBulk } from "../agent/goals.js";
71
72
  import { startTunnel, stopTunnel, getTunnelStatus } from "../utils/tunnel.js";
@@ -3201,6 +3202,21 @@ data: ${JSON.stringify({ timestamp: Date.now() })}
3201
3202
  safeWrite(`event: round
3202
3203
  data: ${JSON.stringify({ round, maxRounds, timestamp: Date.now() })}
3203
3204
 
3205
+ `);
3206
+ },
3207
+ // Dedicated `retry` SSE event so the UI can show a status indicator.
3208
+ // Pre-fix the router yielded retry banners as `text` chunks which
3209
+ // ended up in the user-visible message; this isolates the signal.
3210
+ onRetry: (info) => {
3211
+ safeWrite(`event: retry
3212
+ data: ${JSON.stringify({ ...info, timestamp: Date.now() })}
3213
+
3214
+ `);
3215
+ },
3216
+ onFailover: (info) => {
3217
+ safeWrite(`event: failover
3218
+ data: ${JSON.stringify({ ...info, timestamp: Date.now() })}
3219
+
3204
3220
  `);
3205
3221
  }
3206
3222
  },
@@ -5122,6 +5138,15 @@ data: ${JSON.stringify(data)}
5122
5138
  const ok = reportHeartbeat(req.params.id);
5123
5139
  res.json({ success: ok });
5124
5140
  });
5141
+ app.post("/api/command-post/agents/:id/fire", async (req, res) => {
5142
+ try {
5143
+ const { fireHeartbeat } = await import("../agent/heartbeatScheduler.js");
5144
+ await fireHeartbeat(req.params.id);
5145
+ res.json({ success: true });
5146
+ } catch (err) {
5147
+ res.status(500).json({ success: false, error: err.message });
5148
+ }
5149
+ });
5125
5150
  app.delete("/api/command-post/agents/:id", (req, res) => {
5126
5151
  const ok = removeAgent(req.params.id);
5127
5152
  if (!ok) {
@@ -9062,6 +9087,7 @@ td{padding:10px 12px;font-size:14px;vertical-align:middle}
9062
9087
  if (config.commandPost?.enabled) {
9063
9088
  initCommandPost(config.commandPost);
9064
9089
  initWakeupSystem();
9090
+ initHeartbeatScheduler();
9065
9091
  logger.info(COMPONENT, "Command Post governance layer initialized (wakeup system active)");
9066
9092
  try {
9067
9093
  const { ensureSpecialistsRegistered } = await import("../agent/specialists.js");