nightshift-mcp 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/agent-spawner.d.ts +9 -0
  2. package/dist/agent-spawner.d.ts.map +1 -1
  3. package/dist/agent-spawner.js +88 -5
  4. package/dist/agent-spawner.js.map +1 -1
  5. package/dist/daemon.d.ts +19 -5
  6. package/dist/daemon.d.ts.map +1 -1
  7. package/dist/daemon.js +212 -35
  8. package/dist/daemon.js.map +1 -1
  9. package/dist/database.d.ts +2 -2
  10. package/dist/database.d.ts.map +1 -1
  11. package/dist/database.js +4 -4
  12. package/dist/database.js.map +1 -1
  13. package/dist/index.js +219 -1146
  14. package/dist/index.js.map +1 -1
  15. package/dist/orchestrator.d.ts +10 -1
  16. package/dist/orchestrator.d.ts.map +1 -1
  17. package/dist/orchestrator.js +190 -8
  18. package/dist/orchestrator.js.map +1 -1
  19. package/dist/pipeline.d.ts +61 -0
  20. package/dist/pipeline.d.ts.map +1 -0
  21. package/dist/pipeline.js +227 -0
  22. package/dist/pipeline.js.map +1 -0
  23. package/dist/project-context.d.ts +28 -0
  24. package/dist/project-context.d.ts.map +1 -0
  25. package/dist/project-context.js +45 -0
  26. package/dist/project-context.js.map +1 -0
  27. package/dist/run-logger.d.ts +61 -0
  28. package/dist/run-logger.d.ts.map +1 -0
  29. package/dist/run-logger.js +219 -0
  30. package/dist/run-logger.js.map +1 -0
  31. package/dist/tools/agents.d.ts +3 -0
  32. package/dist/tools/agents.d.ts.map +1 -1
  33. package/dist/tools/agents.js +148 -5
  34. package/dist/tools/agents.js.map +1 -1
  35. package/dist/tools/bugs.d.ts.map +1 -1
  36. package/dist/tools/bugs.js +48 -22
  37. package/dist/tools/bugs.js.map +1 -1
  38. package/dist/tools/chat.d.ts.map +1 -1
  39. package/dist/tools/chat.js +47 -20
  40. package/dist/tools/chat.js.map +1 -1
  41. package/dist/tools/prd.d.ts.map +1 -1
  42. package/dist/tools/prd.js +67 -31
  43. package/dist/tools/prd.js.map +1 -1
  44. package/dist/tools/progress.d.ts.map +1 -1
  45. package/dist/tools/progress.js +22 -7
  46. package/dist/tools/progress.js.map +1 -1
  47. package/dist/tools/utility.js +2 -2
  48. package/dist/tools/utility.js.map +1 -1
  49. package/package.json +1 -1
@@ -0,0 +1,219 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ // ============================================
4
+ // Token Parsing
5
+ // ============================================
6
+ /**
7
+ * Parse token usage from agent output. Each agent reports differently.
8
+ */
9
+ export function parseTokensFromOutput(agent, output) {
10
+ if (!output)
11
+ return undefined;
12
+ switch (agent) {
13
+ case "codex": {
14
+ // Codex reports tokens at the end of output, e.g. "108,712" or "tokens used: 14079"
15
+ // Also: "tokens used**" pattern seen in logs
16
+ const tokensUsedMatch = output.match(/tokens?\s*used[*:]*\s*([\d,]+)/i);
17
+ if (tokensUsedMatch) {
18
+ const total = parseInt(tokensUsedMatch[1].replace(/,/g, ""), 10);
19
+ if (!isNaN(total))
20
+ return { total, model: "gpt-5.4" };
21
+ }
22
+ // Bare large number at end of output (Codex sometimes just prints the count)
23
+ const bareNumberMatch = output.match(/\n([\d,]{4,})\s*$/);
24
+ if (bareNumberMatch) {
25
+ const total = parseInt(bareNumberMatch[1].replace(/,/g, ""), 10);
26
+ if (!isNaN(total) && total > 100)
27
+ return { total, model: "gpt-5.4" };
28
+ }
29
+ return undefined;
30
+ }
31
+ case "ollama": {
32
+ // Ollama API returns: prompt_eval_count, eval_count in JSON responses
33
+ const promptEvalMatch = output.match(/"prompt_eval_count"\s*:\s*(\d+)/);
34
+ const evalMatch = output.match(/"eval_count"\s*:\s*(\d+)/);
35
+ if (promptEvalMatch || evalMatch) {
36
+ const input = promptEvalMatch ? parseInt(promptEvalMatch[1], 10) : undefined;
37
+ const outputTokens = evalMatch ? parseInt(evalMatch[1], 10) : undefined;
38
+ const total = (input || 0) + (outputTokens || 0);
39
+ const model = process.env.NIGHTSHIFT_OLLAMA_MODEL || "qwen3:8b";
40
+ return { input, output: outputTokens, total: total || undefined, model };
41
+ }
42
+ return undefined;
43
+ }
44
+ case "gemini": {
45
+ // Gemini CLI may report: "Token count: N" or usage stats
46
+ const tokenCountMatch = output.match(/token[s]?\s*(?:count|usage|used)[:\s]*([\d,]+)/i);
47
+ if (tokenCountMatch) {
48
+ const total = parseInt(tokenCountMatch[1].replace(/,/g, ""), 10);
49
+ if (!isNaN(total))
50
+ return { total };
51
+ }
52
+ return undefined;
53
+ }
54
+ case "claude": {
55
+ // Claude Code --print mode may report token usage
56
+ const inputMatch = output.match(/input[_ ]tokens?[:\s]*([\d,]+)/i);
57
+ const outputMatch = output.match(/output[_ ]tokens?[:\s]*([\d,]+)/i);
58
+ if (inputMatch || outputMatch) {
59
+ const input = inputMatch ? parseInt(inputMatch[1].replace(/,/g, ""), 10) : undefined;
60
+ const outputTokens = outputMatch ? parseInt(outputMatch[1].replace(/,/g, ""), 10) : undefined;
61
+ const total = (input || 0) + (outputTokens || 0);
62
+ return { input, output: outputTokens, total: total || undefined };
63
+ }
64
+ return undefined;
65
+ }
66
+ default:
67
+ return undefined;
68
+ }
69
+ }
70
+ // ============================================
71
+ // Rate Limit Detection
72
+ // ============================================
73
+ /**
74
+ * Detect rate limiting from agent output.
75
+ */
76
+ export function detectRateLimit(output) {
77
+ if (!output)
78
+ return undefined;
79
+ // HTTP 429
80
+ const has429 = /\b429\b/.test(output) && /rate|limit|quota|capacity|exhausted/i.test(output);
81
+ // Explicit rate limit messages
82
+ const hasRateLimit = /rate.?limit|quota.?exceeded|too many requests|capacity.*exhausted|exhausted.*capacity|throttl/i.test(output);
83
+ // Retry-after header or message
84
+ const retryMatch = output.match(/retry.?after[:\s]*(\d+)/i)
85
+ || output.match(/reset after (\d+)s/i)
86
+ || output.match(/wait (\d+)\s*s/i);
87
+ if (!has429 && !hasRateLimit)
88
+ return undefined;
89
+ const retryAfter = retryMatch ? parseInt(retryMatch[1], 10) : undefined;
90
+ // Try to identify provider
91
+ let provider;
92
+ if (/openai|gpt/i.test(output))
93
+ provider = "openai";
94
+ else if (/anthropic|claude/i.test(output))
95
+ provider = "anthropic";
96
+ else if (/google|gemini/i.test(output))
97
+ provider = "google";
98
+ else if (/ollama/i.test(output))
99
+ provider = "ollama";
100
+ return { detected: true, retryAfter, provider };
101
+ }
102
+ // ============================================
103
+ // Tool Usage Parsing
104
+ // ============================================
105
+ /**
106
+ * Parse tool/command usage from agent output.
107
+ * Detects MCP tool calls, shell commands, file operations, etc.
108
+ */
109
+ export function parseToolsUsed(output) {
110
+ if (!output)
111
+ return undefined;
112
+ const tools = new Set();
113
+ // MCP tool calls (e.g., "nightshift - read_prd", "Tool: read_file")
114
+ const mcpToolMatches = output.matchAll(/(?:Tool|tool_use|nightshift)\s*[-:]?\s*(\w+)/g);
115
+ for (const match of mcpToolMatches) {
116
+ const toolName = match[1];
117
+ // Filter out noise words
118
+ if (toolName && toolName.length > 2 && !/^(the|and|for|was|use|has)$/i.test(toolName)) {
119
+ tools.add(toolName);
120
+ }
121
+ }
122
+ // Claude Code tool patterns: "Read(file)", "Edit(file)", "Bash(cmd)", "Write(file)"
123
+ const claudeToolMatches = output.matchAll(/\b(Read|Edit|Write|Bash|Glob|Grep|Agent|WebSearch|WebFetch)\s*\(/g);
124
+ for (const match of claudeToolMatches) {
125
+ tools.add(match[1]);
126
+ }
127
+ // Codex tool patterns: "exec", "read_file", "write_file", "shell"
128
+ const codexToolMatches = output.matchAll(/\b(exec|read_file|write_file|shell|list_dir|patch_file)\b/g);
129
+ for (const match of codexToolMatches) {
130
+ tools.add(match[1]);
131
+ }
132
+ // Gemini tool patterns: "ReadFile", "WriteFile", "ExecuteCommand"
133
+ const geminiToolMatches = output.matchAll(/\b(ReadFile|WriteFile|ExecuteCommand|SearchFiles|ListDirectory)\b/g);
134
+ for (const match of geminiToolMatches) {
135
+ tools.add(match[1]);
136
+ }
137
+ // Shell commands (common patterns)
138
+ const shellMatches = output.matchAll(/(?:^|\n)\s*\$?\s*(npm|npx|git|tsc|vitest|eslint|prettier|node)\b/gm);
139
+ for (const match of shellMatches) {
140
+ tools.add(`shell:${match[1]}`);
141
+ }
142
+ return tools.size > 0 ? Array.from(tools) : undefined;
143
+ }
144
+ // ============================================
145
+ // Run Logger
146
+ // ============================================
147
+ /**
148
+ * Append a run log entry to the project's runs.jsonl file.
149
+ */
150
+ export function appendRunLog(projectPath, entry) {
151
+ const robotChatDir = path.join(projectPath, ".robot-chat");
152
+ if (!fs.existsSync(robotChatDir)) {
153
+ fs.mkdirSync(robotChatDir, { recursive: true });
154
+ }
155
+ const logFile = path.join(robotChatDir, "runs.jsonl");
156
+ const line = JSON.stringify(entry) + "\n";
157
+ try {
158
+ fs.appendFileSync(logFile, line, "utf-8");
159
+ }
160
+ catch {
161
+ // Non-critical — best effort logging
162
+ }
163
+ }
164
+ /**
165
+ * Read all run log entries from the project's runs.jsonl file.
166
+ */
167
+ export function readRunLog(projectPath, limit) {
168
+ const logFile = path.join(projectPath, ".robot-chat", "runs.jsonl");
169
+ if (!fs.existsSync(logFile))
170
+ return [];
171
+ try {
172
+ const content = fs.readFileSync(logFile, "utf-8");
173
+ const lines = content.trim().split("\n").filter(Boolean);
174
+ const entries = lines.map((line) => JSON.parse(line));
175
+ return limit ? entries.slice(-limit) : entries;
176
+ }
177
+ catch {
178
+ return [];
179
+ }
180
+ }
181
+ /**
182
+ * Get aggregate stats from the run log.
183
+ */
184
+ export function getRunStats(projectPath) {
185
+ const entries = readRunLog(projectPath);
186
+ const byAgent = {};
187
+ let totalTokens = 0;
188
+ let totalDuration = 0;
189
+ let rateLimitCount = 0;
190
+ for (const entry of entries) {
191
+ if (!byAgent[entry.agent]) {
192
+ byAgent[entry.agent] = { runs: 0, totalTokens: 0, avgDuration: 0, totalDuration: 0 };
193
+ }
194
+ const agentStats = byAgent[entry.agent];
195
+ agentStats.runs++;
196
+ agentStats.totalDuration += entry.duration_seconds;
197
+ agentStats.avgDuration = Math.round(agentStats.totalDuration / agentStats.runs);
198
+ if (entry.tokens?.total) {
199
+ agentStats.totalTokens += entry.tokens.total;
200
+ totalTokens += entry.tokens.total;
201
+ }
202
+ totalDuration += entry.duration_seconds;
203
+ if (entry.status === "rate_limited")
204
+ rateLimitCount++;
205
+ }
206
+ // Clean up totalDuration from output
207
+ const result = {};
208
+ for (const [agent, stats] of Object.entries(byAgent)) {
209
+ result[agent] = { runs: stats.runs, totalTokens: stats.totalTokens, avgDuration: stats.avgDuration };
210
+ }
211
+ return {
212
+ totalRuns: entries.length,
213
+ byAgent: result,
214
+ totalTokens,
215
+ totalDuration,
216
+ rateLimitCount,
217
+ };
218
+ }
219
+ //# sourceMappingURL=run-logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-logger.js","sourceRoot":"","sources":["../src/run-logger.ts"],"names":[],"mappings":"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"}
@@ -8,5 +8,8 @@ export declare const getAgentStatusTool: import("../tool-registry.js").ToolDefin
8
8
  export declare const listRunningAgentsTool: import("../tool-registry.js").ToolDefinition;
9
9
  export declare const getDaemonStatusTool: import("../tool-registry.js").ToolDefinition;
10
10
  export declare const stopDaemonTool: import("../tool-registry.js").ToolDefinition;
11
+ export declare const getRunStatsTool: import("../tool-registry.js").ToolDefinition;
12
+ export declare const getPipelineTool: import("../tool-registry.js").ToolDefinition;
13
+ export declare const setPipelineTool: import("../tool-registry.js").ToolDefinition;
11
14
  export declare const agentTools: import("../tool-registry.js").ToolDefinition[];
12
15
  //# sourceMappingURL=agents.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/tools/agents.ts"],"names":[],"mappings":"AAkEA,eAAO,MAAM,mBAAmB,8CAsC9B,CAAC;AAEH,eAAO,MAAM,cAAc,8CAqFzB,CAAC;AAEH,eAAO,MAAM,wBAAwB,8CAoDnC,CAAC;AAEH,eAAO,MAAM,aAAa,8CAqLxB,CAAC;AAEH,eAAO,MAAM,gBAAgB,8CAoK3B,CAAC;AAEH,eAAO,MAAM,WAAW,8CAsJtB,CAAC;AAEH,eAAO,MAAM,kBAAkB,8CAwC7B,CAAC;AAEH,eAAO,MAAM,qBAAqB,8CAwChC,CAAC;AAEH,eAAO,MAAM,mBAAmB,8CAsB9B,CAAC;AAEH,eAAO,MAAM,cAAc,8CA4CzB,CAAC;AAGH,eAAO,MAAM,UAAU,gDAWtB,CAAC"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/tools/agents.ts"],"names":[],"mappings":"AA0EA,eAAO,MAAM,mBAAmB,8CAsC9B,CAAC;AAEH,eAAO,MAAM,cAAc,8CAiGzB,CAAC;AAEH,eAAO,MAAM,wBAAwB,8CAuDnC,CAAC;AAEH,eAAO,MAAM,aAAa,8CA6LxB,CAAC;AAEH,eAAO,MAAM,gBAAgB,8CAoK3B,CAAC;AAEH,eAAO,MAAM,WAAW,8CAsJtB,CAAC;AAEH,eAAO,MAAM,kBAAkB,8CAyC7B,CAAC;AAEH,eAAO,MAAM,qBAAqB,8CAyChC,CAAC;AAEH,eAAO,MAAM,mBAAmB,8CAsB9B,CAAC;AAEH,eAAO,MAAM,cAAc,8CA4CzB,CAAC;AAEH,eAAO,MAAM,eAAe,8CAgC1B,CAAC;AAEH,eAAO,MAAM,eAAe,8CA8B1B,CAAC;AAEH,eAAO,MAAM,eAAe,8CA0D1B,CAAC;AAGH,eAAO,MAAM,UAAU,gDActB,CAAC"}
@@ -2,6 +2,8 @@ import { z } from "zod";
2
2
  import { defineTool } from "../tool-registry.js";
3
3
  import { spawnAgent, spawnAgentBackground, getAvailableAgents, getAgentStatus, canAgentRun, getTrackedAgentStatus, listTrackedAgents, updateTrackedAgent, } from "../agent-spawner.js";
4
4
  import { getNightshiftCliCommand } from "../platform.js";
5
+ import { getRunStats, readRunLog } from "../run-logger.js";
6
+ import { savePipelineOverride, clearPipelineOverride, resolveExecutablePipeline, listPipelines, getTagRouting, } from "../pipeline.js";
5
7
  import { ProjectOrchestrator } from "../orchestrator.js";
6
8
  import { isDaemonRunning, ensureDaemonRunning, stopDaemon, } from "../daemon-manager.js";
7
9
  const AGENT_TYPES = ["claude", "codex", "gemini", "vibe", "goose", "ollama"];
@@ -106,8 +108,10 @@ Agent selection guide (only available agents will work — check list_available_
106
108
  agent: z.enum(AGENT_TYPES).describe("Which agent to spawn. IMPORTANT: only use agents confirmed available by list_available_agents or preflight check. Do NOT guess."),
107
109
  prompt: z.string().describe("The prompt/task to send to the agent"),
108
110
  timeout: z.number().optional().describe("Timeout in seconds (default: 300). Use 600 for research tasks, 900 for complex implementation. See tool description for guidance."),
111
+ role: z.string().optional().describe("Agent role for audit trail (e.g., 'planner', 'coder', 'fixer', 'verifier', 'reviewer')"),
112
+ storyId: z.string().optional().describe("Story ID being worked on (for run log tracking)"),
109
113
  }),
110
- handler: async ({ agent, prompt, timeout }, ctx) => {
114
+ handler: async ({ agent, prompt, timeout, role, storyId }, ctx) => {
111
115
  try {
112
116
  // Pre-flight: verify agent is available before attempting spawn
113
117
  const preflight = await preflightAgentCheck(agent);
@@ -128,18 +132,23 @@ Agent selection guide (only available agents will work — check list_available_
128
132
  prompt,
129
133
  projectPath: ctx.projectPath,
130
134
  timeout: timeout ? timeout * 1000 : undefined,
135
+ role,
136
+ storyId,
131
137
  });
132
138
  // End trace span
133
139
  if (spanId) {
134
140
  ctx.traceManager?.endSpan(spanId, result.success ? "completed" : "failed", {
135
141
  exitCode: result.exitCode,
142
+ tokens: result.tokens,
143
+ rateLimited: result.rateLimitInfo?.detected,
136
144
  });
137
145
  }
138
146
  // Post result to chat
147
+ const tokenSummary = result.tokens?.total ? ` (${result.tokens.total.toLocaleString()} tokens)` : "";
139
148
  ctx.chatManager.writeMessage({
140
149
  agent: "NightShift",
141
150
  type: result.success ? "INFO" : "ERROR",
142
- content: `${agent} agent ${result.success ? "completed" : "failed"}: ${result.output.substring(0, 500)}${result.output.length > 500 ? "..." : ""}`,
151
+ content: `${agent}${role ? ` [${role}]` : ""} agent ${result.success ? "completed" : "failed"}${tokenSummary}: ${result.output.substring(0, 500)}${result.output.length > 500 ? "..." : ""}`,
143
152
  });
144
153
  return {
145
154
  content: [{
@@ -147,8 +156,13 @@ Agent selection guide (only available agents will work — check list_available_
147
156
  text: JSON.stringify({
148
157
  success: result.success,
149
158
  agent,
159
+ role,
150
160
  output: result.output,
151
161
  exitCode: result.exitCode,
162
+ duration_seconds: result.duration_seconds,
163
+ tokens: result.tokens,
164
+ rateLimitInfo: result.rateLimitInfo,
165
+ toolsUsed: result.toolsUsed,
152
166
  error: result.error,
153
167
  }, null, 2),
154
168
  }],
@@ -171,8 +185,9 @@ export const spawnAgentBackgroundTool = defineTool({
171
185
  inputSchema: z.object({
172
186
  agent: z.enum(AGENT_TYPES).describe("Which agent to spawn. MUST be an available agent — check list_available_agents first."),
173
187
  prompt: z.string().describe("The prompt/task to send to the agent"),
188
+ role: z.string().optional().describe("Agent role for audit trail (e.g., 'planner', 'coder', 'fixer', 'verifier')"),
174
189
  }),
175
- handler: async ({ agent, prompt }, ctx) => {
190
+ handler: async ({ agent, prompt, role }, ctx) => {
176
191
  try {
177
192
  // Pre-flight: verify agent is available before attempting spawn
178
193
  const preflight = await preflightAgentCheck(agent);
@@ -187,18 +202,20 @@ export const spawnAgentBackgroundTool = defineTool({
187
202
  agent: agent,
188
203
  prompt,
189
204
  projectPath: ctx.projectPath,
205
+ role,
190
206
  });
191
207
  // Post to chat
192
208
  ctx.chatManager.writeMessage({
193
209
  agent: "NightShift",
194
210
  type: "INFO",
195
- content: `Spawned ${agent} in background (PID: ${result.pid})\nOutput: ${result.outputFile}`,
211
+ content: `Spawned ${agent}${role ? ` [${role}]` : ""} in background (PID: ${result.pid})\nOutput: ${result.outputFile}`,
196
212
  });
197
213
  return {
198
214
  content: [{
199
215
  type: "text",
200
216
  text: JSON.stringify({
201
217
  agent,
218
+ role,
202
219
  pid: result.pid,
203
220
  outputFile: result.outputFile,
204
221
  status: "running in background",
@@ -225,8 +242,9 @@ IMPORTANT: Only delegate to agents that are actually available. Use list_availab
225
242
  agent: z.enum(AGENT_TYPES).describe("Which agent to delegate to. MUST be an available agent — check list_available_agents first."),
226
243
  storyId: z.string().optional().describe("Specific story ID (defaults to next available)"),
227
244
  background: z.boolean().optional().describe("Run in background (default: false)"),
245
+ role: z.string().optional().describe("Agent role for audit trail (default: 'implementer')"),
228
246
  }),
229
- handler: async ({ agent, storyId, background }, ctx) => {
247
+ handler: async ({ agent, storyId, background, role }, ctx) => {
230
248
  try {
231
249
  // Pre-flight: verify agent is available before attempting delegation
232
250
  const preflight = await preflightAgentCheck(agent);
@@ -312,11 +330,14 @@ Begin implementation now.`;
312
330
  promptLength: delegationPrompt.length,
313
331
  background: !!background,
314
332
  });
333
+ const effectiveRole = role || "implementer";
315
334
  if (background) {
316
335
  const result = spawnAgentBackground({
317
336
  agent: agent,
318
337
  prompt: delegationPrompt,
319
338
  projectPath: ctx.projectPath,
339
+ role: effectiveRole,
340
+ storyId: story.id,
320
341
  });
321
342
  // Tag the tracked agent with the story ID
322
343
  if (result.pid) {
@@ -328,6 +349,7 @@ Begin implementation now.`;
328
349
  text: JSON.stringify({
329
350
  status: "delegated",
330
351
  agent,
352
+ role: effectiveRole,
331
353
  story: story.id,
332
354
  title: story.title,
333
355
  background: true,
@@ -344,6 +366,8 @@ Begin implementation now.`;
344
366
  prompt: delegationPrompt,
345
367
  projectPath: ctx.projectPath,
346
368
  timeout: 10 * 60 * 1000, // 10 minutes for story work
369
+ role: effectiveRole,
370
+ storyId: story.id,
347
371
  });
348
372
  // End trace span
349
373
  if (spanId) {
@@ -708,6 +732,7 @@ export const getAgentStatusTool = defineTool({
708
732
  pid,
709
733
  status: status.running ? "running" : "exited",
710
734
  agent: status.agent,
735
+ role: status.role,
711
736
  storyId: status.storyId,
712
737
  outputFile: status.outputFile,
713
738
  startTime: status.startTime ? new Date(status.startTime).toISOString() : undefined,
@@ -746,6 +771,7 @@ export const listRunningAgentsTool = defineTool({
746
771
  agents: agents.map((a) => ({
747
772
  pid: a.pid,
748
773
  agent: a.agent,
774
+ role: a.role,
749
775
  storyId: a.storyId,
750
776
  status: a.running ? "running" : "exited",
751
777
  elapsedSeconds: a.elapsedSeconds,
@@ -819,6 +845,120 @@ export const stopDaemonTool = defineTool({
819
845
  };
820
846
  },
821
847
  });
848
+ export const getRunStatsTool = defineTool({
849
+ name: "get_run_stats",
850
+ category: "agents",
851
+ description: "Get token usage and performance stats from the run log",
852
+ fullDescription: "Get aggregate statistics from the run log including token usage per agent, average durations, rate limit counts, and tools used. Use this to optimize your agent pipeline and track costs.",
853
+ inputSchema: z.object({
854
+ limit: z.number().optional().describe("Number of recent runs to show in detail (default: 10)"),
855
+ }),
856
+ handler: async ({ limit }, ctx) => {
857
+ const stats = getRunStats(ctx.projectPath);
858
+ const recentRuns = readRunLog(ctx.projectPath, limit ?? 10);
859
+ return {
860
+ content: [{
861
+ type: "text",
862
+ text: JSON.stringify({
863
+ summary: stats,
864
+ recentRuns: recentRuns.map((r) => ({
865
+ timestamp: r.timestamp,
866
+ agent: r.agent,
867
+ role: r.role,
868
+ storyId: r.storyId,
869
+ status: r.status,
870
+ duration_seconds: r.duration_seconds,
871
+ tokens: r.tokens,
872
+ toolsUsed: r.toolsUsed,
873
+ rateLimitInfo: r.rateLimitInfo,
874
+ })),
875
+ }, null, 2),
876
+ }],
877
+ };
878
+ },
879
+ });
880
+ export const getPipelineTool = defineTool({
881
+ name: "get_pipeline",
882
+ category: "agents",
883
+ description: "View configured pipelines and tag routing rules",
884
+ fullDescription: `View all configured agent pipelines and tag routing rules. Shows built-in defaults merged with any project config (nightshift.config.json) and runtime overrides.
885
+
886
+ Pipelines define the sequence of agents for different task types. Stories are automatically routed to pipelines based on their tags.`,
887
+ inputSchema: z.object({
888
+ storyTags: z.array(z.string()).optional().describe("Preview which pipeline would be selected for these tags"),
889
+ }),
890
+ handler: async ({ storyTags }, ctx) => {
891
+ const pipelines = listPipelines(ctx.projectPath);
892
+ const routing = getTagRouting(ctx.projectPath);
893
+ const result = { pipelines, tagRouting: routing };
894
+ if (storyTags && storyTags.length > 0) {
895
+ const resolved = await resolveExecutablePipeline(ctx.projectPath, storyTags);
896
+ result.preview = {
897
+ tags: storyTags,
898
+ selectedPipeline: resolved.name,
899
+ executableSteps: resolved.steps,
900
+ skippedSteps: resolved.skipped,
901
+ };
902
+ }
903
+ return {
904
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
905
+ };
906
+ },
907
+ });
908
+ export const setPipelineTool = defineTool({
909
+ name: "set_pipeline",
910
+ category: "agents",
911
+ description: "Configure or update an agent pipeline at runtime",
912
+ fullDescription: `Create or update an agent pipeline at runtime. Changes persist for this project session (stored in .robot-chat/pipeline-override.json).
913
+
914
+ Example: set_pipeline({ name: "default", steps: [{ agent: "gemini", role: "planner" }, { agent: "codex", role: "implementer" }] })
915
+
916
+ Use reset=true to clear all runtime overrides and revert to defaults + config file.`,
917
+ inputSchema: z.object({
918
+ name: z.string().optional().describe("Pipeline name to create/update (e.g., 'default', 'frontend', 'backend', or any custom name)"),
919
+ steps: z.array(z.object({
920
+ agent: z.enum(AGENT_TYPES).describe("Agent for this step"),
921
+ role: z.string().describe("Role name (planner, implementer, verifier, drafter, fixer, reviewer, researcher)"),
922
+ optional: z.boolean().optional().describe("Skip if agent unavailable (default: true)"),
923
+ timeout: z.number().optional().describe("Timeout in seconds for this step"),
924
+ })).optional().describe("Pipeline steps in execution order"),
925
+ tagRouting: z.record(z.string()).optional().describe("Map story tags to pipeline names, e.g. { 'frontend': 'frontend', 'api': 'backend' }"),
926
+ reset: z.boolean().optional().describe("Clear all runtime overrides, revert to defaults"),
927
+ }),
928
+ handler: async ({ name, steps, tagRouting, reset }, ctx) => {
929
+ if (reset) {
930
+ clearPipelineOverride(ctx.projectPath);
931
+ return {
932
+ content: [{ type: "text", text: "Pipeline overrides cleared. Using defaults + config file." }],
933
+ };
934
+ }
935
+ if (!name && !tagRouting) {
936
+ return {
937
+ content: [{ type: "text", text: "Provide a pipeline name + steps, tagRouting rules, or reset=true." }],
938
+ isError: true,
939
+ };
940
+ }
941
+ const overrides = {};
942
+ if (name && steps) {
943
+ overrides.pipelines = { [name]: steps };
944
+ }
945
+ if (tagRouting) {
946
+ overrides.tagRouting = tagRouting;
947
+ }
948
+ savePipelineOverride(ctx.projectPath, overrides);
949
+ // Show the resolved state after update
950
+ const allPipelines = listPipelines(ctx.projectPath);
951
+ return {
952
+ content: [{
953
+ type: "text",
954
+ text: JSON.stringify({
955
+ message: `Pipeline${name ? ` "${name}"` : ""} updated.`,
956
+ currentPipelines: allPipelines,
957
+ }, null, 2),
958
+ }],
959
+ };
960
+ },
961
+ });
822
962
  // Export all agent tools as an array
823
963
  export const agentTools = [
824
964
  listAvailableAgents,
@@ -831,5 +971,8 @@ export const agentTools = [
831
971
  listRunningAgentsTool,
832
972
  getDaemonStatusTool,
833
973
  stopDaemonTool,
974
+ getRunStatsTool,
975
+ getPipelineTool,
976
+ setPipelineTool,
834
977
  ];
835
978
  //# sourceMappingURL=agents.js.map