@h-rig/server 0.0.6-alpha.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.
- package/README.md +14 -0
- package/dist/src/bootstrap.js +161 -0
- package/dist/src/index.js +13153 -0
- package/dist/src/inspector/agent-runtime.js +1077 -0
- package/dist/src/inspector/analysis.js +41 -0
- package/dist/src/inspector/discovery.js +137 -0
- package/dist/src/inspector/journal.js +518 -0
- package/dist/src/inspector/mission.js +562 -0
- package/dist/src/inspector/prompt.js +97 -0
- package/dist/src/inspector/provider-session.js +65 -0
- package/dist/src/inspector/reconcile.js +118 -0
- package/dist/src/inspector/review.js +13 -0
- package/dist/src/inspector/service.js +1759 -0
- package/dist/src/inspector/skills.js +155 -0
- package/dist/src/inspector/tools.js +1592 -0
- package/dist/src/inspector/types.js +1 -0
- package/dist/src/inspector/upstream-sync.js +479 -0
- package/dist/src/orchestration.js +402 -0
- package/dist/src/remote.js +123 -0
- package/dist/src/scheduler.js +84 -0
- package/dist/src/server-helpers/broadcasters.js +161 -0
- package/dist/src/server-helpers/conversation-snapshot.js +382 -0
- package/dist/src/server-helpers/event-emitter.js +41 -0
- package/dist/src/server-helpers/github-auth-store.js +155 -0
- package/dist/src/server-helpers/github-credentials.js +38 -0
- package/dist/src/server-helpers/github-project-status-sync.js +196 -0
- package/dist/src/server-helpers/github-projects.js +147 -0
- package/dist/src/server-helpers/github-reconciler.js +89 -0
- package/dist/src/server-helpers/http-router.js +3781 -0
- package/dist/src/server-helpers/http-utils.js +135 -0
- package/dist/src/server-helpers/inspector-agent-lifecycle.js +104 -0
- package/dist/src/server-helpers/inspector-jobs.js +4145 -0
- package/dist/src/server-helpers/issue-analysis.js +362 -0
- package/dist/src/server-helpers/normalizers.js +31 -0
- package/dist/src/server-helpers/notifications.js +96 -0
- package/dist/src/server-helpers/orchestration-ops.js +287 -0
- package/dist/src/server-helpers/orchestration.js +39 -0
- package/dist/src/server-helpers/plugin-host-cache.js +86 -0
- package/dist/src/server-helpers/project-fs-ops.js +194 -0
- package/dist/src/server-helpers/project-registry.js +124 -0
- package/dist/src/server-helpers/queue-state.js +78 -0
- package/dist/src/server-helpers/remote-checkout.js +140 -0
- package/dist/src/server-helpers/remote-snapshots.js +119 -0
- package/dist/src/server-helpers/run-io.js +262 -0
- package/dist/src/server-helpers/run-mutations.js +1784 -0
- package/dist/src/server-helpers/run-steering.js +176 -0
- package/dist/src/server-helpers/run-writers.js +75 -0
- package/dist/src/server-helpers/server-paths.js +27 -0
- package/dist/src/server-helpers/snapshot-orchestrator.js +832 -0
- package/dist/src/server-helpers/snapshot-service.js +1143 -0
- package/dist/src/server-helpers/summaries.js +126 -0
- package/dist/src/server-helpers/task-config.js +50 -0
- package/dist/src/server-helpers/task-projection.js +98 -0
- package/dist/src/server-helpers/terminal-runtime.js +156 -0
- package/dist/src/server-helpers/terminal-sessions.js +22 -0
- package/dist/src/server-helpers/validation-failure.js +31 -0
- package/dist/src/server-helpers/ws-router.js +1308 -0
- package/dist/src/server.js +12628 -0
- package/dist/src/websocket.js +63 -0
- package/package.json +33 -0
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/server/src/inspector/agent-runtime.ts
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { createHash } from "crypto";
|
|
5
|
+
import { accessSync, constants, realpathSync } from "fs";
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
|
|
8
|
+
// packages/server/src/inspector/prompt.ts
|
|
9
|
+
function renderSkills(skills) {
|
|
10
|
+
return skills.map((skill) => `- ${skill.name}: ${skill.rationale}`).join(`
|
|
11
|
+
`);
|
|
12
|
+
}
|
|
13
|
+
function stableJson(value) {
|
|
14
|
+
return JSON.stringify(value, null, 2);
|
|
15
|
+
}
|
|
16
|
+
function compactSnapshot(snapshot) {
|
|
17
|
+
const recentFindings = Array.isArray(snapshot.recentFindings) ? snapshot.recentFindings.filter((finding) => {
|
|
18
|
+
const kind = typeof finding.kind === "string" ? finding.kind : "";
|
|
19
|
+
const severity = typeof finding.severity === "string" ? finding.severity : "";
|
|
20
|
+
const source = typeof finding.source === "string" ? finding.source : "";
|
|
21
|
+
if (source === "inspector-agent") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return kind !== "run.observed" || severity === "warn" || severity === "error";
|
|
25
|
+
}) : [];
|
|
26
|
+
return {
|
|
27
|
+
activeRuns: snapshot.activeRuns ?? [],
|
|
28
|
+
recentFindings,
|
|
29
|
+
followups: snapshot.followups ?? [],
|
|
30
|
+
analysisReports: snapshot.analysisReports ?? [],
|
|
31
|
+
availableTools: snapshot.availableTools ?? []
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function buildInspectorBaseInstructions(options) {
|
|
35
|
+
return [
|
|
36
|
+
"You are Rig Global Inspector, the permanent supervisor for this workspace.",
|
|
37
|
+
"You operate as an out-of-band overlay around Rig execution, not inside the worker execution flow.",
|
|
38
|
+
"The lower runtime and provider layers are inspector-blind. Keep them that way.",
|
|
39
|
+
"App-server turns are transport boundaries only. They are not your operating model.",
|
|
40
|
+
"Stay broad, autonomous, and continuous in your judgment. Use turns only as a way to receive updates and emit progress.",
|
|
41
|
+
"Use full contextual judgment. There is no fixed intervention ladder.",
|
|
42
|
+
"You may patch task code or Rig itself, interrupt runs, relaunch runs, create follow-up tasks, run reviews, and improve the harness when confidence is high.",
|
|
43
|
+
"Be decisive, but stay surgical. Prefer the smallest action that materially improves the current situation.",
|
|
44
|
+
"Stay aware of all observable runs, the harness state, upstream drift tasks, and repeated failure patterns.",
|
|
45
|
+
"Document important findings and decisions through the inspector journal tools so the system retains a durable memory of what happened.",
|
|
46
|
+
"Treat TDD as the default for code and behavior changes unless a narrow recorded exception applies.",
|
|
47
|
+
"Before landing meaningful implementation work, run local review or another verification path that produces direct evidence.",
|
|
48
|
+
"When a human is not needed, do not wait. When a human is needed, create the right follow-up or journal note instead of stalling silently.",
|
|
49
|
+
"",
|
|
50
|
+
`Project root: ${options.projectRoot}`,
|
|
51
|
+
"",
|
|
52
|
+
"Required skills:",
|
|
53
|
+
renderSkills(options.requiredSkills),
|
|
54
|
+
"",
|
|
55
|
+
"Recommended skills:",
|
|
56
|
+
renderSkills(options.recommendedSkills)
|
|
57
|
+
].join(`
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
function buildInspectorDeveloperInstructions(options) {
|
|
61
|
+
return [
|
|
62
|
+
"Operate pragmatically and keep momentum.",
|
|
63
|
+
"Use the native Codex capabilities and the semantic inspector tools together.",
|
|
64
|
+
"You are free to inspect, edit, test, relaunch, review, and continue as far as the current situation warrants.",
|
|
65
|
+
"Yield when you reach a useful checkpoint, need fresher external state, or detect that continued looping would be wasteful.",
|
|
66
|
+
"Prefer semantic inspector tools for journal, follow-up task, upstream-sync, review, and run-inspection operations.",
|
|
67
|
+
"Use shell, read, write, edit, and git directly when that is the fastest reliable way to diagnose or repair the system.",
|
|
68
|
+
"When you create or modify code, keep the red-green-refactor discipline visible in your reasoning and actions.",
|
|
69
|
+
"Before claiming something is fixed, verify it directly.",
|
|
70
|
+
"Spawn specialist child agents when parallel work is useful, but remain the full-context owner and take over when they are not doing the job well enough.",
|
|
71
|
+
"You are responsible both for task oversight and for Rig self-improvement when a clear win-win fix is available.",
|
|
72
|
+
"",
|
|
73
|
+
`Workspace root: ${options.projectRoot}`,
|
|
74
|
+
"",
|
|
75
|
+
"Skill catalog in scope:",
|
|
76
|
+
renderSkills([...options.requiredSkills, ...options.recommendedSkills])
|
|
77
|
+
].join(`
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
function buildInspectorTurnPrompt(options) {
|
|
81
|
+
const compact = compactSnapshot(options.snapshot);
|
|
82
|
+
return [
|
|
83
|
+
"Rig inspector update.",
|
|
84
|
+
`reason: ${options.reason}`,
|
|
85
|
+
`timestamp: ${options.now}`,
|
|
86
|
+
"Review the current state and use broad judgment about whether intervention is warranted.",
|
|
87
|
+
"You may take multiple actions inside this turn if that is the most effective way to improve the situation.",
|
|
88
|
+
"Do not become noisy or repetitive when nothing material changed.",
|
|
89
|
+
"When you reach a useful checkpoint, emit one compact operator update in this form:",
|
|
90
|
+
"STATUS: <observed|acted|escalated|waiting>",
|
|
91
|
+
"NOTE: <one or two concise sentences>",
|
|
92
|
+
"Then yield so the server can deliver the next update when needed.",
|
|
93
|
+
"",
|
|
94
|
+
"Current snapshot:",
|
|
95
|
+
stableJson(compact)
|
|
96
|
+
].join(`
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// packages/server/src/bootstrap.ts
|
|
101
|
+
import { RIG_DEFINITION_DIRNAME, resolveMonorepoRoot } from "@rig/runtime";
|
|
102
|
+
|
|
103
|
+
// packages/server/src/inspector/tools.ts
|
|
104
|
+
var INSPECTOR_TOOL_DEFINITIONS = [
|
|
105
|
+
{
|
|
106
|
+
name: "read_inspector_snapshot",
|
|
107
|
+
description: "Read the current global inspector snapshot including active runs, recent findings, follow-ups, reports, and available semantic tools.",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {},
|
|
111
|
+
additionalProperties: false
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "discover_active_runs",
|
|
116
|
+
description: "List currently active Rig runs known to the inspector overlay.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {},
|
|
120
|
+
additionalProperties: false
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "inspect_run",
|
|
125
|
+
description: "Inspect one run in detail, including session paths, capabilities, and recent journal observations.",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
runId: { type: "string" }
|
|
130
|
+
},
|
|
131
|
+
required: ["runId"],
|
|
132
|
+
additionalProperties: false
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "record_inspector_note",
|
|
137
|
+
description: "Append a durable note to the inspector journal.",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
runId: { type: "string" },
|
|
142
|
+
summary: { type: "string" },
|
|
143
|
+
details: { type: "object" }
|
|
144
|
+
},
|
|
145
|
+
required: ["summary"],
|
|
146
|
+
additionalProperties: true
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "list_inspector_missions",
|
|
151
|
+
description: "List durable inspector missions with status, pending effects, posted results, and completion proof state.",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {},
|
|
155
|
+
additionalProperties: false
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "create_inspector_mission",
|
|
160
|
+
description: "Create a durable inspector-owned mission for a source task contract. The sourceTask is preserved in the mission JSON state.",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
sourceTask: { type: "object" },
|
|
165
|
+
owner: { type: "string" },
|
|
166
|
+
summary: { type: "string" }
|
|
167
|
+
},
|
|
168
|
+
required: ["sourceTask"],
|
|
169
|
+
additionalProperties: true
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "read_inspector_mission",
|
|
174
|
+
description: "Read a durable inspector mission, including sourceTask, pending effects, posted results, proof, and mission journal.",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
missionId: { type: "string" }
|
|
179
|
+
},
|
|
180
|
+
required: ["missionId"],
|
|
181
|
+
additionalProperties: false
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "request_inspector_effect",
|
|
186
|
+
description: "Request a provider-worker effect for an inspector mission. Pending effects gate acceptance until posted.",
|
|
187
|
+
inputSchema: {
|
|
188
|
+
type: "object",
|
|
189
|
+
properties: {
|
|
190
|
+
missionId: { type: "string" },
|
|
191
|
+
effectId: { type: "string" },
|
|
192
|
+
kind: { type: "string" },
|
|
193
|
+
executor: { type: "string" },
|
|
194
|
+
summary: { type: "string" },
|
|
195
|
+
input: { type: "object" }
|
|
196
|
+
},
|
|
197
|
+
required: ["missionId", "kind", "executor", "summary"],
|
|
198
|
+
additionalProperties: true
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "post_inspector_effect_result",
|
|
203
|
+
description: "Post a provider-worker effect result. Partial, blocked, or failed results block completion proof.",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
missionId: { type: "string" },
|
|
208
|
+
effectId: { type: "string" },
|
|
209
|
+
status: { type: "string", enum: ["completed", "partial", "blocked", "failed"] },
|
|
210
|
+
summary: { type: "string" },
|
|
211
|
+
result: { type: "object" },
|
|
212
|
+
postedBy: { type: "string" }
|
|
213
|
+
},
|
|
214
|
+
required: ["missionId", "effectId", "status", "summary"],
|
|
215
|
+
additionalProperties: true
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "accept_inspector_mission",
|
|
220
|
+
description: "Accept a mission closeout and emit completionProof when no pending/blocking effects remain.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
missionId: { type: "string" },
|
|
225
|
+
acceptedBy: { type: "string" },
|
|
226
|
+
summary: { type: "string" },
|
|
227
|
+
evidence: { type: "array", items: { type: "string" } }
|
|
228
|
+
},
|
|
229
|
+
required: ["missionId", "summary"],
|
|
230
|
+
additionalProperties: true
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: "reject_inspector_mission",
|
|
235
|
+
description: "Reject a mission closeout without emitting completionProof.",
|
|
236
|
+
inputSchema: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: {
|
|
239
|
+
missionId: { type: "string" },
|
|
240
|
+
rejectedBy: { type: "string" },
|
|
241
|
+
summary: { type: "string" },
|
|
242
|
+
rationale: { type: "string" }
|
|
243
|
+
},
|
|
244
|
+
required: ["missionId", "summary"],
|
|
245
|
+
additionalProperties: true
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "block_inspector_mission",
|
|
250
|
+
description: "Mark a mission blocked without emitting completionProof.",
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: {
|
|
254
|
+
missionId: { type: "string" },
|
|
255
|
+
blockedBy: { type: "string" },
|
|
256
|
+
summary: { type: "string" },
|
|
257
|
+
details: { type: "object" }
|
|
258
|
+
},
|
|
259
|
+
required: ["missionId", "summary"],
|
|
260
|
+
additionalProperties: true
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "complete_inspector_mission",
|
|
265
|
+
description: "Return the accepted mission completionProof. This fails until the mission has been accepted.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
missionId: { type: "string" }
|
|
270
|
+
},
|
|
271
|
+
required: ["missionId"],
|
|
272
|
+
additionalProperties: false
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: "create_followup_task",
|
|
277
|
+
description: "Create or dedupe a structured follow-up task for work the inspector discovered.",
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: "object",
|
|
280
|
+
properties: {
|
|
281
|
+
originRunId: { type: "string" },
|
|
282
|
+
summary: { type: "string" },
|
|
283
|
+
dedupeKey: { type: "string" },
|
|
284
|
+
details: { type: "object" }
|
|
285
|
+
},
|
|
286
|
+
required: ["summary"],
|
|
287
|
+
additionalProperties: true
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "provision_skill",
|
|
292
|
+
description: "Return the role-aware Rig skill set that should be in force for an agent or specialist lane.",
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: "object",
|
|
295
|
+
properties: {
|
|
296
|
+
runId: { type: "string" },
|
|
297
|
+
role: { type: "string" },
|
|
298
|
+
taskKind: { type: "string" },
|
|
299
|
+
exception: { type: "object" }
|
|
300
|
+
},
|
|
301
|
+
additionalProperties: true
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "run_local_review",
|
|
306
|
+
description: "Run the local Rig reviewer/validator flow for a task.",
|
|
307
|
+
inputSchema: {
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: {
|
|
310
|
+
runId: { type: "string" },
|
|
311
|
+
taskId: { type: "string" },
|
|
312
|
+
focus: { type: "array", items: { type: "string" } }
|
|
313
|
+
},
|
|
314
|
+
required: ["taskId"],
|
|
315
|
+
additionalProperties: false
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "scan_upstream_drift",
|
|
320
|
+
description: "Run the separate upstream-sync workflow and create follow-up tasks for needed ports.",
|
|
321
|
+
inputSchema: {
|
|
322
|
+
type: "object",
|
|
323
|
+
properties: {
|
|
324
|
+
scanId: { type: "string" }
|
|
325
|
+
},
|
|
326
|
+
additionalProperties: true
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "generate_analysis_report",
|
|
331
|
+
description: "Generate an inspector analysis report from journaled run and oversight data.",
|
|
332
|
+
inputSchema: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {
|
|
335
|
+
reportId: { type: "string" },
|
|
336
|
+
reportType: { type: "string" },
|
|
337
|
+
runId: { type: "string" }
|
|
338
|
+
},
|
|
339
|
+
additionalProperties: true
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
// packages/server/src/inspector/agent-runtime.ts
|
|
345
|
+
var DEFAULT_CLIENT_INFO = {
|
|
346
|
+
name: "rig_global_inspector",
|
|
347
|
+
title: "Rig Global Inspector",
|
|
348
|
+
version: "0.1.0"
|
|
349
|
+
};
|
|
350
|
+
function createInspectorAgentRuntime(options) {
|
|
351
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
352
|
+
const pollMs = Math.max(100, options.pollMs ?? 2000);
|
|
353
|
+
const startTimeoutMs = Math.max(1000, options.startTimeoutMs ?? 20000);
|
|
354
|
+
const model = options.model ?? process.env.RIG_INSPECTOR_MODEL ?? null;
|
|
355
|
+
const skills = [...options.requiredSkills, ...options.recommendedSkills];
|
|
356
|
+
const transport = options.transportFactory?.() ?? createCodexInspectorTransport({
|
|
357
|
+
model
|
|
358
|
+
});
|
|
359
|
+
const baseInstructions = buildInspectorBaseInstructions({
|
|
360
|
+
projectRoot: options.projectRoot,
|
|
361
|
+
requiredSkills: options.requiredSkills,
|
|
362
|
+
recommendedSkills: options.recommendedSkills
|
|
363
|
+
});
|
|
364
|
+
const developerInstructions = buildInspectorDeveloperInstructions({
|
|
365
|
+
projectRoot: options.projectRoot,
|
|
366
|
+
requiredSkills: options.requiredSkills,
|
|
367
|
+
recommendedSkills: options.recommendedSkills
|
|
368
|
+
});
|
|
369
|
+
let pollTimer = null;
|
|
370
|
+
let status = "stopped";
|
|
371
|
+
let threadId = null;
|
|
372
|
+
let processId = null;
|
|
373
|
+
let lastFingerprint = null;
|
|
374
|
+
let lastPromptAt = null;
|
|
375
|
+
let lastTurnCompletedAt = null;
|
|
376
|
+
let lastAssistantMessage = null;
|
|
377
|
+
let lastError = null;
|
|
378
|
+
let inFlight = null;
|
|
379
|
+
let pendingTurn = null;
|
|
380
|
+
const dispatchPrompt = async (turn) => {
|
|
381
|
+
status = "running";
|
|
382
|
+
lastPromptAt = now();
|
|
383
|
+
try {
|
|
384
|
+
const result = await transport.sendTurn(turn.prompt);
|
|
385
|
+
lastAssistantMessage = result.assistantMessage;
|
|
386
|
+
lastTurnCompletedAt = now();
|
|
387
|
+
lastError = result.error;
|
|
388
|
+
lastFingerprint = turn.fingerprint;
|
|
389
|
+
status = result.status === "completed" ? "idle" : "errored";
|
|
390
|
+
if (options.journal) {
|
|
391
|
+
if (result.assistantMessage) {
|
|
392
|
+
options.journal.appendObservation({
|
|
393
|
+
id: `inspector-agent-message:${Date.now()}`,
|
|
394
|
+
runId: null,
|
|
395
|
+
dedupeKey: null,
|
|
396
|
+
kind: "agent.message",
|
|
397
|
+
severity: "info",
|
|
398
|
+
source: "inspector-agent",
|
|
399
|
+
summary: result.assistantMessage.split(`
|
|
400
|
+
`)[0].slice(0, 240),
|
|
401
|
+
details: {
|
|
402
|
+
status: result.status,
|
|
403
|
+
message: result.assistantMessage
|
|
404
|
+
},
|
|
405
|
+
createdAt: now()
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (result.error) {
|
|
409
|
+
options.journal.appendObservation({
|
|
410
|
+
id: `inspector-agent-error:${Date.now()}`,
|
|
411
|
+
runId: null,
|
|
412
|
+
dedupeKey: null,
|
|
413
|
+
kind: "agent.error",
|
|
414
|
+
severity: "warn",
|
|
415
|
+
source: "inspector-agent",
|
|
416
|
+
summary: result.error,
|
|
417
|
+
details: {
|
|
418
|
+
status: result.status
|
|
419
|
+
},
|
|
420
|
+
createdAt: now()
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch (error) {
|
|
425
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
426
|
+
status = "errored";
|
|
427
|
+
if (options.journal) {
|
|
428
|
+
options.journal.appendObservation({
|
|
429
|
+
id: `inspector-agent-crash:${Date.now()}`,
|
|
430
|
+
runId: null,
|
|
431
|
+
dedupeKey: null,
|
|
432
|
+
kind: "agent.error",
|
|
433
|
+
severity: "warn",
|
|
434
|
+
source: "inspector-agent",
|
|
435
|
+
summary: lastError,
|
|
436
|
+
details: null,
|
|
437
|
+
createdAt: now()
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
} finally {
|
|
441
|
+
inFlight = null;
|
|
442
|
+
if (pendingTurn) {
|
|
443
|
+
const next = pendingTurn;
|
|
444
|
+
pendingTurn = null;
|
|
445
|
+
if (next.fingerprint === lastFingerprint) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
inFlight = dispatchPrompt(next);
|
|
449
|
+
await inFlight;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
const queuePrompt = async (turn) => {
|
|
454
|
+
if (inFlight) {
|
|
455
|
+
pendingTurn = turn;
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
inFlight = dispatchPrompt(turn);
|
|
459
|
+
await inFlight;
|
|
460
|
+
return true;
|
|
461
|
+
};
|
|
462
|
+
const tickNow = async (reason) => {
|
|
463
|
+
if (status === "stopped" || status === "starting") {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
const snapshot = options.snapshotProvider();
|
|
467
|
+
const prompt = buildInspectorTurnPrompt({
|
|
468
|
+
reason,
|
|
469
|
+
snapshot,
|
|
470
|
+
now: now()
|
|
471
|
+
});
|
|
472
|
+
const fingerprint = hashSnapshot(snapshot);
|
|
473
|
+
if (reason !== "startup" && fingerprint === lastFingerprint) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
return queuePrompt({
|
|
477
|
+
fingerprint,
|
|
478
|
+
prompt
|
|
479
|
+
});
|
|
480
|
+
};
|
|
481
|
+
return {
|
|
482
|
+
async start() {
|
|
483
|
+
if (status === "starting" || status === "idle" || status === "running") {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
if (status === "errored") {
|
|
487
|
+
await transport.stop().catch(() => {});
|
|
488
|
+
threadId = null;
|
|
489
|
+
processId = null;
|
|
490
|
+
}
|
|
491
|
+
status = "starting";
|
|
492
|
+
try {
|
|
493
|
+
const started = await withTimeout(transport.startSession({
|
|
494
|
+
projectRoot: options.projectRoot,
|
|
495
|
+
model,
|
|
496
|
+
baseInstructions,
|
|
497
|
+
developerInstructions,
|
|
498
|
+
dynamicTools: INSPECTOR_TOOL_DEFINITIONS,
|
|
499
|
+
invokeDynamicTool: options.invokeDynamicTool
|
|
500
|
+
}), startTimeoutMs, `Inspector agent start timed out after ${startTimeoutMs}ms`);
|
|
501
|
+
threadId = started.threadId;
|
|
502
|
+
processId = started.processId;
|
|
503
|
+
status = "idle";
|
|
504
|
+
pollTimer = setInterval(() => {
|
|
505
|
+
tickNow("poll").catch((error) => {
|
|
506
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
507
|
+
status = "errored";
|
|
508
|
+
});
|
|
509
|
+
}, pollMs);
|
|
510
|
+
await tickNow("startup");
|
|
511
|
+
return true;
|
|
512
|
+
} catch (error) {
|
|
513
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
514
|
+
status = "errored";
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
async stop() {
|
|
519
|
+
if (pollTimer) {
|
|
520
|
+
clearInterval(pollTimer);
|
|
521
|
+
}
|
|
522
|
+
pollTimer = null;
|
|
523
|
+
pendingTurn = null;
|
|
524
|
+
await transport.stop();
|
|
525
|
+
status = "stopped";
|
|
526
|
+
threadId = null;
|
|
527
|
+
processId = null;
|
|
528
|
+
},
|
|
529
|
+
isRunning() {
|
|
530
|
+
return status !== "stopped";
|
|
531
|
+
},
|
|
532
|
+
tickNow,
|
|
533
|
+
snapshot() {
|
|
534
|
+
return {
|
|
535
|
+
...transport.snapshot(),
|
|
536
|
+
status,
|
|
537
|
+
threadId,
|
|
538
|
+
processId,
|
|
539
|
+
queueDepth: pendingTurn ? 1 : 0,
|
|
540
|
+
lastFingerprint,
|
|
541
|
+
lastPromptAt,
|
|
542
|
+
lastTurnCompletedAt,
|
|
543
|
+
lastAssistantMessage,
|
|
544
|
+
lastError,
|
|
545
|
+
model,
|
|
546
|
+
skills
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function hashPrompt(prompt) {
|
|
552
|
+
return createHash("sha1").update(prompt).digest("hex");
|
|
553
|
+
}
|
|
554
|
+
function hashSnapshot(snapshot) {
|
|
555
|
+
const compact = {
|
|
556
|
+
activeRuns: snapshot.activeRuns ?? [],
|
|
557
|
+
recentFindings: (snapshot.recentFindings ?? []).filter((finding) => {
|
|
558
|
+
const kind = typeof finding.kind === "string" ? finding.kind : "";
|
|
559
|
+
const severity = typeof finding.severity === "string" ? finding.severity : "";
|
|
560
|
+
const source = typeof finding.source === "string" ? finding.source : "";
|
|
561
|
+
if (source === "inspector-agent") {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
return kind !== "run.observed" || severity === "warn" || severity === "error";
|
|
565
|
+
}),
|
|
566
|
+
followups: snapshot.followups ?? [],
|
|
567
|
+
analysisReports: snapshot.analysisReports ?? [],
|
|
568
|
+
availableTools: snapshot.availableTools ?? []
|
|
569
|
+
};
|
|
570
|
+
return hashPrompt(JSON.stringify(compact));
|
|
571
|
+
}
|
|
572
|
+
function createCodexInspectorTransport(options) {
|
|
573
|
+
const turnTimeoutMs = Math.max(5000, options.turnTimeoutMs ?? 45000);
|
|
574
|
+
let child = null;
|
|
575
|
+
let threadId = null;
|
|
576
|
+
let processId = null;
|
|
577
|
+
let status = "stopped";
|
|
578
|
+
let lastAssistantMessage = null;
|
|
579
|
+
let lastError = null;
|
|
580
|
+
const pendingResponses = new Map;
|
|
581
|
+
const assistantMessageText = new Map;
|
|
582
|
+
const inFlightToolCalls = new Set;
|
|
583
|
+
const recentProtocolEvents = [];
|
|
584
|
+
let nextRequestId = 1;
|
|
585
|
+
let sendQueue = Promise.resolve();
|
|
586
|
+
let currentTurn = null;
|
|
587
|
+
let invokeDynamicTool = null;
|
|
588
|
+
const rememberProtocolEvent = (event) => {
|
|
589
|
+
recentProtocolEvents.push(event);
|
|
590
|
+
if (recentProtocolEvents.length > 25) {
|
|
591
|
+
recentProtocolEvents.splice(0, recentProtocolEvents.length - 25);
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
const sendMessage = async (payload) => {
|
|
595
|
+
if (!child) {
|
|
596
|
+
throw new Error("Inspector agent session is not started.");
|
|
597
|
+
}
|
|
598
|
+
const activeChild = child;
|
|
599
|
+
if (activeChild.stdin.destroyed || !activeChild.stdin.writable) {
|
|
600
|
+
throw new Error(lastError ?? "Inspector agent transport is not writable.");
|
|
601
|
+
}
|
|
602
|
+
rememberProtocolEvent({
|
|
603
|
+
at: new Date().toISOString(),
|
|
604
|
+
direction: "send",
|
|
605
|
+
kind: payload.method ? "request" : "response",
|
|
606
|
+
method: typeof payload.method === "string" ? payload.method : `response:${String(payload.id ?? "unknown")}`
|
|
607
|
+
});
|
|
608
|
+
const line = `${JSON.stringify(payload)}
|
|
609
|
+
`;
|
|
610
|
+
sendQueue = sendQueue.then(() => writeChildLine(activeChild, line));
|
|
611
|
+
await sendQueue;
|
|
612
|
+
};
|
|
613
|
+
const sendRequest = async (method, params) => {
|
|
614
|
+
const id = nextRequestId;
|
|
615
|
+
nextRequestId += 1;
|
|
616
|
+
const response = new Promise((resolve, reject) => {
|
|
617
|
+
pendingResponses.set(id, { resolve, reject });
|
|
618
|
+
});
|
|
619
|
+
response.catch(() => {});
|
|
620
|
+
try {
|
|
621
|
+
await sendMessage({ id, method, params });
|
|
622
|
+
} catch (error) {
|
|
623
|
+
pendingResponses.delete(id);
|
|
624
|
+
throw error;
|
|
625
|
+
}
|
|
626
|
+
return response;
|
|
627
|
+
};
|
|
628
|
+
const sendResponse = async (id, result) => {
|
|
629
|
+
await sendMessage({ id, result });
|
|
630
|
+
};
|
|
631
|
+
const sendError = async (id, message) => {
|
|
632
|
+
await sendMessage({
|
|
633
|
+
id,
|
|
634
|
+
error: {
|
|
635
|
+
code: -32603,
|
|
636
|
+
message
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
};
|
|
640
|
+
const trackToolCall = (call) => {
|
|
641
|
+
inFlightToolCalls.add(call);
|
|
642
|
+
call.finally(() => inFlightToolCalls.delete(call)).catch((err) => {
|
|
643
|
+
console.warn("[inspector/agent-runtime] in-flight tool call failure:", err);
|
|
644
|
+
});
|
|
645
|
+
};
|
|
646
|
+
const handleServerRequest = (message) => {
|
|
647
|
+
const method = message.method;
|
|
648
|
+
const requestId = message.id;
|
|
649
|
+
if (!method || requestId === undefined) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (method === "item/tool/call") {
|
|
653
|
+
const params = message.params ?? {};
|
|
654
|
+
const toolName = normalizeString(params.tool);
|
|
655
|
+
const toolArgs = asRecord(params.arguments) ?? {};
|
|
656
|
+
const toolCall = (async () => {
|
|
657
|
+
if (!toolName || !invokeDynamicTool) {
|
|
658
|
+
rememberProtocolEvent({
|
|
659
|
+
at: new Date().toISOString(),
|
|
660
|
+
direction: "recv",
|
|
661
|
+
kind: "request",
|
|
662
|
+
method: "item/tool/call:malformed"
|
|
663
|
+
});
|
|
664
|
+
await sendError(requestId, "Malformed inspector dynamic tool call.");
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
const invocation = await invokeDynamicTool(toolName, toolArgs);
|
|
669
|
+
await sendResponse(requestId, {
|
|
670
|
+
contentItems: [
|
|
671
|
+
{
|
|
672
|
+
type: "inputText",
|
|
673
|
+
text: typeof invocation.details === "string" ? invocation.details : JSON.stringify({
|
|
674
|
+
status: invocation.status,
|
|
675
|
+
summary: invocation.summary,
|
|
676
|
+
details: invocation.details
|
|
677
|
+
}, null, 2)
|
|
678
|
+
}
|
|
679
|
+
],
|
|
680
|
+
success: invocation.status !== "failed" && invocation.status !== "missing"
|
|
681
|
+
});
|
|
682
|
+
} catch (error) {
|
|
683
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
684
|
+
await sendResponse(requestId, {
|
|
685
|
+
contentItems: [{ type: "inputText", text: messageText }],
|
|
686
|
+
success: false
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
})();
|
|
690
|
+
trackToolCall(toolCall);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval") {
|
|
694
|
+
trackToolCall(sendResponse(requestId, { decision: "approved_for_session" }));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (method === "item/permissions/requestApproval") {
|
|
698
|
+
trackToolCall(sendResponse(requestId, {
|
|
699
|
+
permissions: asRecord(message.params)?.permissions ?? {},
|
|
700
|
+
scope: "session"
|
|
701
|
+
}));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (method === "item/tool/requestUserInput") {
|
|
705
|
+
trackToolCall(sendResponse(requestId, { answers: {} }));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (method === "mcpServer/elicitation/request") {
|
|
709
|
+
trackToolCall(sendResponse(requestId, { action: "decline" }));
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (method === "applyPatchApproval" || method === "execCommandApproval") {
|
|
713
|
+
trackToolCall(sendResponse(requestId, { decision: "approved_for_session" }));
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
trackToolCall(sendError(requestId, `Unsupported inspector app-server request: ${method}`));
|
|
717
|
+
};
|
|
718
|
+
const handleNotification = (message) => {
|
|
719
|
+
const method = message.method;
|
|
720
|
+
const params = message.params ?? {};
|
|
721
|
+
if (!method) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (method === "thread/started") {
|
|
725
|
+
const thread = asRecord(params.thread);
|
|
726
|
+
threadId = normalizeString(thread?.id);
|
|
727
|
+
status = "idle";
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (method === "turn/started") {
|
|
731
|
+
status = "running";
|
|
732
|
+
currentTurn?.events.push({ method, params });
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (method === "item/agentMessage/delta") {
|
|
736
|
+
const itemId = normalizeString(params.itemId);
|
|
737
|
+
const delta = typeof params.delta === "string" ? params.delta : "";
|
|
738
|
+
if (itemId && delta) {
|
|
739
|
+
assistantMessageText.set(itemId, `${assistantMessageText.get(itemId) ?? ""}${delta}`);
|
|
740
|
+
}
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (method === "item/completed") {
|
|
744
|
+
const item = asRecord(params.item);
|
|
745
|
+
const itemType = normalizeString(item?.type);
|
|
746
|
+
const itemId = normalizeString(item?.id);
|
|
747
|
+
currentTurn?.events.push({ method, params });
|
|
748
|
+
if (itemType === "agentMessage" && itemId) {
|
|
749
|
+
const finalText = normalizeString(item?.text) ?? assistantMessageText.get(itemId) ?? "";
|
|
750
|
+
assistantMessageText.delete(itemId);
|
|
751
|
+
if (finalText.trim()) {
|
|
752
|
+
lastAssistantMessage = finalText.trim();
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (method === "error") {
|
|
758
|
+
lastError = normalizeString(params.message) ?? JSON.stringify(params);
|
|
759
|
+
currentTurn?.events.push({ method, params });
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (method === "turn/completed") {
|
|
763
|
+
const turn = asRecord(params.turn);
|
|
764
|
+
const error = asRecord(turn?.error);
|
|
765
|
+
const completion = {
|
|
766
|
+
status: normalizeString(turn?.status) ?? "failed",
|
|
767
|
+
error: normalizeString(error?.message),
|
|
768
|
+
assistantMessage: lastAssistantMessage,
|
|
769
|
+
events: currentTurn?.events ?? []
|
|
770
|
+
};
|
|
771
|
+
status = completion.status === "completed" ? "idle" : "errored";
|
|
772
|
+
currentTurn?.resolve(completion);
|
|
773
|
+
currentTurn = null;
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
const bootstrapChild = (config) => {
|
|
778
|
+
const codexExecutable = resolveCodexExecutable();
|
|
779
|
+
child = spawn(codexExecutable, ["app-server"], {
|
|
780
|
+
cwd: config.projectRoot,
|
|
781
|
+
env: process.env,
|
|
782
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
783
|
+
});
|
|
784
|
+
processId = child.pid ?? null;
|
|
785
|
+
const stdout = createInterface({ input: child.stdout });
|
|
786
|
+
const stderr = createInterface({ input: child.stderr });
|
|
787
|
+
stdout.on("line", (line) => {
|
|
788
|
+
const trimmed = line.trim();
|
|
789
|
+
if (!trimmed) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
let message;
|
|
793
|
+
try {
|
|
794
|
+
message = JSON.parse(trimmed);
|
|
795
|
+
} catch {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (message.method) {
|
|
799
|
+
if (message.id !== undefined) {
|
|
800
|
+
rememberProtocolEvent({
|
|
801
|
+
at: new Date().toISOString(),
|
|
802
|
+
direction: "recv",
|
|
803
|
+
kind: "request",
|
|
804
|
+
method: message.method
|
|
805
|
+
});
|
|
806
|
+
handleServerRequest(message);
|
|
807
|
+
} else {
|
|
808
|
+
rememberProtocolEvent({
|
|
809
|
+
at: new Date().toISOString(),
|
|
810
|
+
direction: "recv",
|
|
811
|
+
kind: "notification",
|
|
812
|
+
method: message.method
|
|
813
|
+
});
|
|
814
|
+
handleNotification(message);
|
|
815
|
+
}
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (message.id !== undefined) {
|
|
819
|
+
rememberProtocolEvent({
|
|
820
|
+
at: new Date().toISOString(),
|
|
821
|
+
direction: "recv",
|
|
822
|
+
kind: "response",
|
|
823
|
+
method: `response:${String(message.id)}`
|
|
824
|
+
});
|
|
825
|
+
const pending = pendingResponses.get(message.id);
|
|
826
|
+
if (!pending) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
pendingResponses.delete(message.id);
|
|
830
|
+
if (message.error) {
|
|
831
|
+
pending.reject(new Error(formatJsonRpcError(message.error)));
|
|
832
|
+
} else {
|
|
833
|
+
pending.resolve(message.result ?? {});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
stderr.on("line", (line) => {
|
|
838
|
+
if (line.trim()) {
|
|
839
|
+
rememberProtocolEvent({
|
|
840
|
+
at: new Date().toISOString(),
|
|
841
|
+
direction: "recv",
|
|
842
|
+
kind: "stderr",
|
|
843
|
+
method: line.trim().slice(0, 240)
|
|
844
|
+
});
|
|
845
|
+
lastError = line.trim();
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
child.once("error", (error) => {
|
|
849
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
850
|
+
lastError = message;
|
|
851
|
+
status = "errored";
|
|
852
|
+
for (const [id, pending] of pendingResponses.entries()) {
|
|
853
|
+
pending.reject(new Error(message));
|
|
854
|
+
pendingResponses.delete(id);
|
|
855
|
+
}
|
|
856
|
+
if (currentTurn) {
|
|
857
|
+
currentTurn.reject(new Error(message));
|
|
858
|
+
currentTurn = null;
|
|
859
|
+
}
|
|
860
|
+
threadId = null;
|
|
861
|
+
processId = null;
|
|
862
|
+
child = null;
|
|
863
|
+
});
|
|
864
|
+
child.once("close", (code, signal) => {
|
|
865
|
+
const exitMessage = `Inspector app-server exited (${String(code ?? signal ?? "unknown")})`;
|
|
866
|
+
for (const [id, pending] of pendingResponses.entries()) {
|
|
867
|
+
pending.reject(new Error(exitMessage));
|
|
868
|
+
pendingResponses.delete(id);
|
|
869
|
+
}
|
|
870
|
+
if (currentTurn) {
|
|
871
|
+
currentTurn.reject(new Error(exitMessage));
|
|
872
|
+
currentTurn = null;
|
|
873
|
+
}
|
|
874
|
+
status = "stopped";
|
|
875
|
+
lastError = exitMessage;
|
|
876
|
+
threadId = null;
|
|
877
|
+
processId = null;
|
|
878
|
+
child = null;
|
|
879
|
+
});
|
|
880
|
+
return child;
|
|
881
|
+
};
|
|
882
|
+
return {
|
|
883
|
+
async startSession(config) {
|
|
884
|
+
if (child) {
|
|
885
|
+
return { threadId, processId };
|
|
886
|
+
}
|
|
887
|
+
invokeDynamicTool = config.invokeDynamicTool;
|
|
888
|
+
status = "starting";
|
|
889
|
+
const activeChild = bootstrapChild(config);
|
|
890
|
+
await waitForChildSpawn(activeChild);
|
|
891
|
+
await sendRequest("initialize", {
|
|
892
|
+
clientInfo: DEFAULT_CLIENT_INFO,
|
|
893
|
+
capabilities: {
|
|
894
|
+
experimentalApi: true
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
const started = await sendRequest("thread/start", {
|
|
898
|
+
model: config.model ?? options.model ?? null,
|
|
899
|
+
cwd: config.projectRoot,
|
|
900
|
+
approvalPolicy: "never",
|
|
901
|
+
sandbox: "danger-full-access",
|
|
902
|
+
serviceName: "rig_global_inspector",
|
|
903
|
+
personality: "pragmatic",
|
|
904
|
+
ephemeral: false,
|
|
905
|
+
baseInstructions: config.baseInstructions,
|
|
906
|
+
developerInstructions: config.developerInstructions,
|
|
907
|
+
dynamicTools: config.dynamicTools
|
|
908
|
+
});
|
|
909
|
+
const thread = asRecord(started.thread);
|
|
910
|
+
threadId = normalizeString(thread?.id) ?? threadId;
|
|
911
|
+
if (!threadId) {
|
|
912
|
+
const preview = JSON.stringify(started).slice(0, 400);
|
|
913
|
+
throw new Error(`Codex thread/start response did not include thread.id (got: ${preview}). ` + `Likely codex protocol version mismatch \u2014 current Rig speaks codex 0.130 schema.`);
|
|
914
|
+
}
|
|
915
|
+
status = "idle";
|
|
916
|
+
return { threadId, processId };
|
|
917
|
+
},
|
|
918
|
+
async sendTurn(prompt) {
|
|
919
|
+
if (!child || !threadId) {
|
|
920
|
+
throw new Error("Inspector agent session is not ready.");
|
|
921
|
+
}
|
|
922
|
+
if (currentTurn) {
|
|
923
|
+
throw new Error("Inspector agent turn already in progress.");
|
|
924
|
+
}
|
|
925
|
+
lastAssistantMessage = null;
|
|
926
|
+
lastError = null;
|
|
927
|
+
const turnResult = new Promise((resolve, reject) => {
|
|
928
|
+
currentTurn = {
|
|
929
|
+
resolve,
|
|
930
|
+
reject,
|
|
931
|
+
events: []
|
|
932
|
+
};
|
|
933
|
+
});
|
|
934
|
+
try {
|
|
935
|
+
await sendRequest("turn/start", {
|
|
936
|
+
threadId,
|
|
937
|
+
approvalPolicy: "never",
|
|
938
|
+
input: [
|
|
939
|
+
{
|
|
940
|
+
type: "text",
|
|
941
|
+
text: prompt,
|
|
942
|
+
text_elements: []
|
|
943
|
+
}
|
|
944
|
+
]
|
|
945
|
+
});
|
|
946
|
+
} catch (error) {
|
|
947
|
+
currentTurn = null;
|
|
948
|
+
throw error;
|
|
949
|
+
}
|
|
950
|
+
let result;
|
|
951
|
+
try {
|
|
952
|
+
result = await withTimeout(turnResult, turnTimeoutMs, `Inspector agent turn timed out after ${turnTimeoutMs}ms`);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
currentTurn = null;
|
|
955
|
+
if (child) {
|
|
956
|
+
terminateChild(child);
|
|
957
|
+
child = null;
|
|
958
|
+
}
|
|
959
|
+
throw error;
|
|
960
|
+
}
|
|
961
|
+
if (inFlightToolCalls.size > 0) {
|
|
962
|
+
await Promise.allSettled(Array.from(inFlightToolCalls));
|
|
963
|
+
}
|
|
964
|
+
return result;
|
|
965
|
+
},
|
|
966
|
+
snapshot() {
|
|
967
|
+
return {
|
|
968
|
+
status,
|
|
969
|
+
threadId,
|
|
970
|
+
processId,
|
|
971
|
+
queueDepth: 0,
|
|
972
|
+
lastAssistantMessage,
|
|
973
|
+
lastError,
|
|
974
|
+
recentProtocolEvents: [...recentProtocolEvents]
|
|
975
|
+
};
|
|
976
|
+
},
|
|
977
|
+
async stop() {
|
|
978
|
+
if (child) {
|
|
979
|
+
terminateChild(child);
|
|
980
|
+
child = null;
|
|
981
|
+
}
|
|
982
|
+
status = "stopped";
|
|
983
|
+
threadId = null;
|
|
984
|
+
processId = null;
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function writeChildLine(child, line) {
|
|
989
|
+
return new Promise((resolve, reject) => {
|
|
990
|
+
child.stdin.write(line, (error) => {
|
|
991
|
+
if (error) {
|
|
992
|
+
reject(error);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
resolve();
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
function terminateChild(child) {
|
|
1000
|
+
if (child.killed) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
try {
|
|
1004
|
+
child.kill("SIGTERM");
|
|
1005
|
+
} catch {}
|
|
1006
|
+
}
|
|
1007
|
+
async function waitForChildSpawn(child) {
|
|
1008
|
+
await new Promise((resolve, reject) => {
|
|
1009
|
+
const onSpawn = () => {
|
|
1010
|
+
cleanup();
|
|
1011
|
+
resolve();
|
|
1012
|
+
};
|
|
1013
|
+
const onError = (error) => {
|
|
1014
|
+
cleanup();
|
|
1015
|
+
reject(error);
|
|
1016
|
+
};
|
|
1017
|
+
const cleanup = () => {
|
|
1018
|
+
child.off("spawn", onSpawn);
|
|
1019
|
+
child.off("error", onError);
|
|
1020
|
+
};
|
|
1021
|
+
child.once("spawn", onSpawn);
|
|
1022
|
+
child.once("error", onError);
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
function resolveCodexExecutable() {
|
|
1026
|
+
const candidate = process.env.RIG_INSPECTOR_CODEX_BIN ?? Bun.which("codex");
|
|
1027
|
+
if (!candidate) {
|
|
1028
|
+
throw new Error("Codex CLI is not available on PATH for the inspector app-server.");
|
|
1029
|
+
}
|
|
1030
|
+
let resolvedCandidate;
|
|
1031
|
+
try {
|
|
1032
|
+
resolvedCandidate = realpathSync(candidate);
|
|
1033
|
+
} catch {
|
|
1034
|
+
throw new Error(`Codex CLI is not executable for the inspector app-server: ${candidate}`);
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
accessSync(resolvedCandidate, constants.X_OK);
|
|
1038
|
+
} catch {
|
|
1039
|
+
throw new Error(`Codex CLI is not executable for the inspector app-server: ${candidate}`);
|
|
1040
|
+
}
|
|
1041
|
+
return resolvedCandidate;
|
|
1042
|
+
}
|
|
1043
|
+
function asRecord(value) {
|
|
1044
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1045
|
+
}
|
|
1046
|
+
function normalizeString(value) {
|
|
1047
|
+
if (typeof value !== "string") {
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
const trimmed = value.trim();
|
|
1051
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1052
|
+
}
|
|
1053
|
+
function formatJsonRpcError(error) {
|
|
1054
|
+
if (!error) {
|
|
1055
|
+
return "Unknown app-server error";
|
|
1056
|
+
}
|
|
1057
|
+
const parts = [error.message, error.data ? JSON.stringify(error.data) : null].filter(Boolean);
|
|
1058
|
+
return parts.join(" ");
|
|
1059
|
+
}
|
|
1060
|
+
async function withTimeout(promise, timeoutMs, message) {
|
|
1061
|
+
let timer = null;
|
|
1062
|
+
try {
|
|
1063
|
+
return await Promise.race([
|
|
1064
|
+
promise,
|
|
1065
|
+
new Promise((_, reject) => {
|
|
1066
|
+
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
1067
|
+
})
|
|
1068
|
+
]);
|
|
1069
|
+
} finally {
|
|
1070
|
+
if (timer) {
|
|
1071
|
+
clearTimeout(timer);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
export {
|
|
1076
|
+
createInspectorAgentRuntime
|
|
1077
|
+
};
|