@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.
Files changed (60) hide show
  1. package/README.md +14 -0
  2. package/dist/src/bootstrap.js +161 -0
  3. package/dist/src/index.js +13153 -0
  4. package/dist/src/inspector/agent-runtime.js +1077 -0
  5. package/dist/src/inspector/analysis.js +41 -0
  6. package/dist/src/inspector/discovery.js +137 -0
  7. package/dist/src/inspector/journal.js +518 -0
  8. package/dist/src/inspector/mission.js +562 -0
  9. package/dist/src/inspector/prompt.js +97 -0
  10. package/dist/src/inspector/provider-session.js +65 -0
  11. package/dist/src/inspector/reconcile.js +118 -0
  12. package/dist/src/inspector/review.js +13 -0
  13. package/dist/src/inspector/service.js +1759 -0
  14. package/dist/src/inspector/skills.js +155 -0
  15. package/dist/src/inspector/tools.js +1592 -0
  16. package/dist/src/inspector/types.js +1 -0
  17. package/dist/src/inspector/upstream-sync.js +479 -0
  18. package/dist/src/orchestration.js +402 -0
  19. package/dist/src/remote.js +123 -0
  20. package/dist/src/scheduler.js +84 -0
  21. package/dist/src/server-helpers/broadcasters.js +161 -0
  22. package/dist/src/server-helpers/conversation-snapshot.js +382 -0
  23. package/dist/src/server-helpers/event-emitter.js +41 -0
  24. package/dist/src/server-helpers/github-auth-store.js +155 -0
  25. package/dist/src/server-helpers/github-credentials.js +38 -0
  26. package/dist/src/server-helpers/github-project-status-sync.js +196 -0
  27. package/dist/src/server-helpers/github-projects.js +147 -0
  28. package/dist/src/server-helpers/github-reconciler.js +89 -0
  29. package/dist/src/server-helpers/http-router.js +3781 -0
  30. package/dist/src/server-helpers/http-utils.js +135 -0
  31. package/dist/src/server-helpers/inspector-agent-lifecycle.js +104 -0
  32. package/dist/src/server-helpers/inspector-jobs.js +4145 -0
  33. package/dist/src/server-helpers/issue-analysis.js +362 -0
  34. package/dist/src/server-helpers/normalizers.js +31 -0
  35. package/dist/src/server-helpers/notifications.js +96 -0
  36. package/dist/src/server-helpers/orchestration-ops.js +287 -0
  37. package/dist/src/server-helpers/orchestration.js +39 -0
  38. package/dist/src/server-helpers/plugin-host-cache.js +86 -0
  39. package/dist/src/server-helpers/project-fs-ops.js +194 -0
  40. package/dist/src/server-helpers/project-registry.js +124 -0
  41. package/dist/src/server-helpers/queue-state.js +78 -0
  42. package/dist/src/server-helpers/remote-checkout.js +140 -0
  43. package/dist/src/server-helpers/remote-snapshots.js +119 -0
  44. package/dist/src/server-helpers/run-io.js +262 -0
  45. package/dist/src/server-helpers/run-mutations.js +1784 -0
  46. package/dist/src/server-helpers/run-steering.js +176 -0
  47. package/dist/src/server-helpers/run-writers.js +75 -0
  48. package/dist/src/server-helpers/server-paths.js +27 -0
  49. package/dist/src/server-helpers/snapshot-orchestrator.js +832 -0
  50. package/dist/src/server-helpers/snapshot-service.js +1143 -0
  51. package/dist/src/server-helpers/summaries.js +126 -0
  52. package/dist/src/server-helpers/task-config.js +50 -0
  53. package/dist/src/server-helpers/task-projection.js +98 -0
  54. package/dist/src/server-helpers/terminal-runtime.js +156 -0
  55. package/dist/src/server-helpers/terminal-sessions.js +22 -0
  56. package/dist/src/server-helpers/validation-failure.js +31 -0
  57. package/dist/src/server-helpers/ws-router.js +1308 -0
  58. package/dist/src/server.js +12628 -0
  59. package/dist/src/websocket.js +63 -0
  60. package/package.json +33 -0
@@ -0,0 +1,562 @@
1
+ // @bun
2
+ // packages/server/src/inspector/mission.ts
3
+ import { randomUUID } from "crypto";
4
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, renameSync, writeFileSync as writeFileSync2 } from "fs";
5
+ import { dirname as dirname2, join, resolve as resolve2 } from "path";
6
+
7
+ // packages/server/src/bootstrap.ts
8
+ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "fs";
9
+ import { dirname, resolve } from "path";
10
+ import { RIG_DEFINITION_DIRNAME, resolveMonorepoRoot } from "@rig/runtime";
11
+ function normalizeOptionalString(value) {
12
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
13
+ }
14
+ function resolveRigServerPaths(projectRoot) {
15
+ const taskWorkspace = normalizeOptionalString(process.env.RIG_TASK_WORKSPACE);
16
+ const explicitStateDir = normalizeOptionalString(process.env.RIG_STATE_DIR);
17
+ const explicitLogsDir = normalizeOptionalString(process.env.RIG_LOGS_DIR);
18
+ const explicitSessionFile = normalizeOptionalString(process.env.RIG_SESSION_FILE);
19
+ const hostStateRoot = resolve(projectRoot, ".rig");
20
+ const monorepoRoot = resolveMonorepoRoot(projectRoot);
21
+ const monorepoStateRoot = resolve(monorepoRoot, ".rig");
22
+ const stateRoot = taskWorkspace ? resolve(taskWorkspace, ".rig") : explicitStateDir ? dirname(resolve(explicitStateDir)) : explicitLogsDir ? dirname(resolve(explicitLogsDir)) : explicitSessionFile ? dirname(dirname(resolve(explicitSessionFile))) : existsSync(hostStateRoot) ? hostStateRoot : monorepoStateRoot;
23
+ const stateDir = explicitStateDir ? resolve(explicitStateDir) : resolve(stateRoot, "state");
24
+ const logsDir = explicitLogsDir ? resolve(explicitLogsDir) : resolve(stateRoot, "logs");
25
+ const taskConfigPath = taskWorkspace ? resolve(taskWorkspace, ".rig", "task-config.json") : existsSync(resolve(projectRoot, ".rig", "task-config.json")) ? resolve(projectRoot, ".rig", "task-config.json") : resolve(monorepoStateRoot, "task-config.json");
26
+ return {
27
+ stateRoot,
28
+ stateDir,
29
+ logsDir,
30
+ controlPlaneEventsFile: resolve(logsDir, "control-plane.events.jsonl"),
31
+ taskConfigPath,
32
+ notificationsFile: resolve(projectRoot, "rig", "notifications", "targets.json"),
33
+ keybindingsPath: resolve(projectRoot, "rig", "keybindings.json")
34
+ };
35
+ }
36
+
37
+ // packages/server/src/inspector/mission.ts
38
+ function isJsonValue(value) {
39
+ if (value === null)
40
+ return true;
41
+ const valueType = typeof value;
42
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") {
43
+ return Number.isFinite(value) || valueType !== "number";
44
+ }
45
+ if (Array.isArray(value)) {
46
+ return value.every(isJsonValue);
47
+ }
48
+ if (valueType === "object") {
49
+ for (const entry of Object.values(value)) {
50
+ if (!isJsonValue(entry))
51
+ return false;
52
+ }
53
+ return true;
54
+ }
55
+ return false;
56
+ }
57
+ function toJsonRecord(value) {
58
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
59
+ return null;
60
+ }
61
+ const record = {};
62
+ for (const [key, entry] of Object.entries(value)) {
63
+ if (isJsonValue(entry)) {
64
+ record[key] = entry;
65
+ }
66
+ }
67
+ return record;
68
+ }
69
+ function cloneJsonRecord(value) {
70
+ return JSON.parse(JSON.stringify(value));
71
+ }
72
+ function isRecord(value) {
73
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
74
+ }
75
+ function readJsonRecord(path) {
76
+ try {
77
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
78
+ if (!isRecord(parsed)) {
79
+ return { ok: false, error: `Mission file ${path} does not contain an object` };
80
+ }
81
+ return { ok: true, record: parsed };
82
+ } catch (error) {
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ return { ok: false, error: message };
85
+ }
86
+ }
87
+ function parseMission(record) {
88
+ if (typeof record.missionId !== "string")
89
+ return { ok: false, error: "Mission is missing missionId" };
90
+ if (typeof record.owner !== "string")
91
+ return { ok: false, error: "Mission is missing owner" };
92
+ if (typeof record.status !== "string")
93
+ return { ok: false, error: "Mission is missing status" };
94
+ if (!isRecord(record.sourceTask))
95
+ return { ok: false, error: "Mission is missing sourceTask" };
96
+ if (!isJsonValue(record.sourceTask))
97
+ return { ok: false, error: "Mission sourceTask is not JSON-safe" };
98
+ const pendingEffects = Array.isArray(record.pendingEffects) ? record.pendingEffects.filter(isRecord).map((entry) => entry) : [];
99
+ const postedResults = Array.isArray(record.postedResults) ? record.postedResults.filter(isRecord).map((entry) => entry) : [];
100
+ const completionProof = isRecord(record.completionProof) ? record.completionProof : null;
101
+ return {
102
+ ok: true,
103
+ mission: {
104
+ missionId: record.missionId,
105
+ owner: record.owner,
106
+ sourceTask: cloneJsonRecord(record.sourceTask),
107
+ status: record.status,
108
+ summary: typeof record.summary === "string" ? record.summary : "Inspector mission",
109
+ pendingEffects,
110
+ postedResults,
111
+ completionProof,
112
+ createdAt: typeof record.createdAt === "string" ? record.createdAt : new Date(0).toISOString(),
113
+ updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : new Date(0).toISOString()
114
+ }
115
+ };
116
+ }
117
+ function sourceTaskId(sourceTask) {
118
+ for (const key of ["sourceTaskId", "taskId", "id"]) {
119
+ const value = sourceTask[key];
120
+ if (typeof value === "string" && value.trim().length > 0) {
121
+ return value;
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ function terminalStatus(status) {
127
+ return status === "accepted" || status === "rejected";
128
+ }
129
+ function latestEffectResults(mission) {
130
+ const latestByEffect = new Map;
131
+ for (const result of mission.postedResults) {
132
+ latestByEffect.set(result.effectId, result);
133
+ }
134
+ return [...latestByEffect.values()];
135
+ }
136
+ function blockingResults(mission) {
137
+ return latestEffectResults(mission).filter((result) => result.status !== "completed");
138
+ }
139
+ function pendingEffects(mission) {
140
+ return mission.pendingEffects.filter((effect) => effect.status === "pending");
141
+ }
142
+ function missionStatusAfterPost(mission) {
143
+ if (blockingResults(mission).length > 0)
144
+ return "blocked";
145
+ if (pendingEffects(mission).length > 0)
146
+ return "awaiting_effect";
147
+ return "open";
148
+ }
149
+ function missionActionDetails(mission) {
150
+ return {
151
+ missionId: mission.missionId,
152
+ status: mission.status,
153
+ sourceTaskId: sourceTaskId(mission.sourceTask)
154
+ };
155
+ }
156
+ function writeJsonFile(path, value) {
157
+ mkdirSync2(dirname2(path), { recursive: true });
158
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
159
+ writeFileSync2(tempPath, `${JSON.stringify(value, null, 2)}
160
+ `, "utf8");
161
+ renameSync(tempPath, path);
162
+ }
163
+ function resolveInspectorMissionPaths(projectRoot) {
164
+ const inspectorDir = resolve2(resolveRigServerPaths(projectRoot).stateDir, "inspector");
165
+ return {
166
+ inspectorDir,
167
+ missionsDir: join(inspectorDir, "missions"),
168
+ journalsDir: join(inspectorDir, "mission-journals")
169
+ };
170
+ }
171
+ function createInspectorMissionController(options) {
172
+ const paths = resolveInspectorMissionPaths(options.projectRoot);
173
+ mkdirSync2(paths.missionsDir, { recursive: true });
174
+ mkdirSync2(paths.journalsDir, { recursive: true });
175
+ const now = options.now ?? (() => new Date().toISOString());
176
+ const nextId = options.idGenerator ?? (() => `mission:${randomUUID()}`);
177
+ function missionPath(missionId) {
178
+ return join(paths.missionsDir, `${missionId}.json`);
179
+ }
180
+ function journalPath(missionId) {
181
+ return join(paths.journalsDir, `${missionId}.jsonl`);
182
+ }
183
+ function appendMissionJournal(entry) {
184
+ mkdirSync2(paths.journalsDir, { recursive: true });
185
+ appendFileSync(journalPath(entry.missionId), `${JSON.stringify(entry)}
186
+ `, "utf8");
187
+ }
188
+ function listMissionJournal(missionId) {
189
+ const path = journalPath(missionId);
190
+ if (!existsSync2(path))
191
+ return [];
192
+ return readFileSync(path, "utf8").split(`
193
+ `).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line)).filter(isRecord).map((entry) => ({
194
+ id: typeof entry.id === "string" ? entry.id : `journal:${randomUUID()}`,
195
+ missionId,
196
+ kind: typeof entry.kind === "string" ? entry.kind : "mission.event",
197
+ summary: typeof entry.summary === "string" ? entry.summary : "Mission event",
198
+ actor: typeof entry.actor === "string" ? entry.actor : null,
199
+ details: toJsonRecord(entry.details),
200
+ createdAt: typeof entry.createdAt === "string" ? entry.createdAt : new Date(0).toISOString()
201
+ }));
202
+ }
203
+ function saveMission(mission) {
204
+ writeJsonFile(missionPath(mission.missionId), mission);
205
+ }
206
+ function readMissionOnly(missionId) {
207
+ const path = missionPath(missionId);
208
+ if (!existsSync2(path)) {
209
+ return { ok: false, error: `Mission ${missionId} was not found` };
210
+ }
211
+ const read = readJsonRecord(path);
212
+ if (!read.ok)
213
+ return read;
214
+ return parseMission(read.record);
215
+ }
216
+ function recordDecision(mission, input) {
217
+ options.journal?.appendDecision({
218
+ id: `decision:${mission.missionId}:${input.type}:${randomUUID()}`,
219
+ runId: mission.missionId,
220
+ dedupeKey: null,
221
+ decisionType: input.type,
222
+ summary: input.summary,
223
+ rationale: input.rationale,
224
+ details: input.details ?? missionActionDetails(mission),
225
+ createdAt: input.at
226
+ });
227
+ }
228
+ function recordAction(mission, input) {
229
+ options.journal?.appendAction({
230
+ id: `action:${mission.missionId}:${input.type}:${randomUUID()}`,
231
+ runId: mission.missionId,
232
+ dedupeKey: null,
233
+ actionType: input.type,
234
+ status: input.status,
235
+ target: input.target ?? `mission:${mission.missionId}`,
236
+ input: input.input ?? missionActionDetails(mission),
237
+ result: input.result ?? missionActionDetails(mission),
238
+ startedAt: input.startedAt,
239
+ completedAt: input.completedAt ?? input.startedAt
240
+ });
241
+ }
242
+ function appendEvent(mission, input) {
243
+ appendMissionJournal({
244
+ id: `event:${mission.missionId}:${input.kind}:${randomUUID()}`,
245
+ missionId: mission.missionId,
246
+ kind: input.kind,
247
+ summary: input.summary,
248
+ actor: input.actor ?? null,
249
+ details: input.details ?? missionActionDetails(mission),
250
+ createdAt: input.at
251
+ });
252
+ }
253
+ return {
254
+ paths,
255
+ createMission(input) {
256
+ const source = cloneJsonRecord(input.sourceTask);
257
+ const missionId = nextId();
258
+ const path = missionPath(missionId);
259
+ if (existsSync2(path)) {
260
+ const existing = readMissionOnly(missionId);
261
+ if (!existing.ok)
262
+ return existing;
263
+ return { ok: true, mission: existing.mission, journal: listMissionJournal(missionId) };
264
+ }
265
+ const at = now();
266
+ const mission = {
267
+ missionId,
268
+ owner: input.owner ?? "inspector-agent",
269
+ sourceTask: source,
270
+ status: "open",
271
+ summary: input.summary ?? (typeof source.title === "string" ? source.title : "Inspector mission"),
272
+ pendingEffects: [],
273
+ postedResults: [],
274
+ completionProof: null,
275
+ createdAt: at,
276
+ updatedAt: at
277
+ };
278
+ saveMission(mission);
279
+ appendEvent(mission, {
280
+ kind: "mission.created",
281
+ summary: mission.summary,
282
+ actor: mission.owner,
283
+ details: { ...missionActionDetails(mission), sourceTask: source },
284
+ at
285
+ });
286
+ recordDecision(mission, {
287
+ type: "mission.create",
288
+ summary: mission.summary,
289
+ rationale: "Inspector-agent accepted agency/controller ownership for the source task contract.",
290
+ details: { ...missionActionDetails(mission), sourceTask: source },
291
+ at
292
+ });
293
+ recordAction(mission, {
294
+ type: "mission.create",
295
+ status: "completed",
296
+ result: missionActionDetails(mission),
297
+ startedAt: at
298
+ });
299
+ return { ok: true, mission, journal: listMissionJournal(missionId) };
300
+ },
301
+ listMissions() {
302
+ return readdirSync(paths.missionsDir).filter((entry) => entry.endsWith(".json")).map((entry) => readMissionOnly(entry.slice(0, -".json".length))).filter((entry) => entry.ok).map((entry) => entry.mission).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
303
+ },
304
+ readMission(missionId) {
305
+ const read = readMissionOnly(missionId);
306
+ if (!read.ok)
307
+ return read;
308
+ return { ok: true, mission: read.mission, journal: listMissionJournal(missionId) };
309
+ },
310
+ requestEffect(input) {
311
+ const read = readMissionOnly(input.missionId);
312
+ if (!read.ok)
313
+ return read;
314
+ const mission = read.mission;
315
+ if (terminalStatus(mission.status)) {
316
+ return { ok: false, error: `Mission ${mission.missionId} is terminal (${mission.status})` };
317
+ }
318
+ if (mission.pendingEffects.some((effect2) => effect2.effectId === input.effectId)) {
319
+ return { ok: false, error: `Mission ${mission.missionId} already has effect ${input.effectId}` };
320
+ }
321
+ const at = now();
322
+ const effect = {
323
+ effectId: input.effectId ?? `effect:${randomUUID()}`,
324
+ kind: input.kind,
325
+ executor: input.executor,
326
+ summary: input.summary,
327
+ input: input.input ?? null,
328
+ status: "pending",
329
+ requestedAt: at
330
+ };
331
+ mission.pendingEffects = [...mission.pendingEffects, effect];
332
+ mission.status = "awaiting_effect";
333
+ mission.updatedAt = at;
334
+ saveMission(mission);
335
+ appendEvent(mission, {
336
+ kind: "mission.effect.requested",
337
+ summary: input.summary,
338
+ actor: mission.owner,
339
+ details: { ...missionActionDetails(mission), effectId: effect.effectId, executor: effect.executor },
340
+ at
341
+ });
342
+ recordAction(mission, {
343
+ type: "mission.effect.request",
344
+ status: "completed",
345
+ target: effect.executor,
346
+ input: effect.input,
347
+ result: { ...missionActionDetails(mission), effectId: effect.effectId },
348
+ startedAt: at
349
+ });
350
+ return { ok: true, mission, journal: listMissionJournal(mission.missionId) };
351
+ },
352
+ postEffectResult(input) {
353
+ const read = readMissionOnly(input.missionId);
354
+ if (!read.ok)
355
+ return read;
356
+ const mission = read.mission;
357
+ if (terminalStatus(mission.status)) {
358
+ return { ok: false, error: `Mission ${mission.missionId} is terminal (${mission.status})` };
359
+ }
360
+ const effect = mission.pendingEffects.find((entry) => entry.effectId === input.effectId);
361
+ if (!effect) {
362
+ return { ok: false, error: `Mission ${mission.missionId} has no pending effect ${input.effectId}` };
363
+ }
364
+ if (effect.status !== "pending") {
365
+ const latestResult = mission.postedResults.filter((entry) => entry.effectId === input.effectId).at(-1);
366
+ if (!latestResult || latestResult.status !== "partial") {
367
+ return { ok: false, error: `Effect ${input.effectId} was already posted` };
368
+ }
369
+ }
370
+ const at = now();
371
+ effect.status = "posted";
372
+ const result = {
373
+ effectId: input.effectId,
374
+ status: input.status,
375
+ summary: input.summary,
376
+ result: input.result ?? null,
377
+ postedBy: input.postedBy ?? "provider-worker",
378
+ postedAt: at
379
+ };
380
+ mission.postedResults = [...mission.postedResults, result];
381
+ mission.status = missionStatusAfterPost(mission);
382
+ mission.updatedAt = at;
383
+ saveMission(mission);
384
+ appendEvent(mission, {
385
+ kind: "mission.effect.posted",
386
+ summary: input.summary,
387
+ actor: result.postedBy,
388
+ details: { ...missionActionDetails(mission), effectId: input.effectId, resultStatus: input.status },
389
+ at
390
+ });
391
+ recordAction(mission, {
392
+ type: "mission.effect.post",
393
+ status: input.status,
394
+ target: input.effectId,
395
+ input: { effectId: input.effectId },
396
+ result: result.result,
397
+ startedAt: at
398
+ });
399
+ if (input.status !== "completed") {
400
+ recordDecision(mission, {
401
+ type: "mission.block",
402
+ summary: input.summary,
403
+ rationale: "Provider-worker effect result was not complete; proof gate remains closed.",
404
+ details: { ...missionActionDetails(mission), effectId: input.effectId, resultStatus: input.status },
405
+ at
406
+ });
407
+ }
408
+ return { ok: true, mission, journal: listMissionJournal(mission.missionId) };
409
+ },
410
+ acceptMission(input) {
411
+ const read = readMissionOnly(input.missionId);
412
+ if (!read.ok)
413
+ return read;
414
+ const mission = read.mission;
415
+ const blockers = blockingResults(mission);
416
+ if (blockers.length > 0) {
417
+ return { ok: false, error: `Mission ${mission.missionId} has blocking effect result(s)` };
418
+ }
419
+ const pending = pendingEffects(mission);
420
+ if (pending.length > 0) {
421
+ return { ok: false, error: `Mission ${mission.missionId} has pending effect(s)` };
422
+ }
423
+ if (terminalStatus(mission.status)) {
424
+ return { ok: false, error: `Mission ${mission.missionId} is terminal (${mission.status})` };
425
+ }
426
+ const at = now();
427
+ const proof = {
428
+ proofId: `proof:${mission.missionId}:${randomUUID()}`,
429
+ missionId: mission.missionId,
430
+ sourceTaskId: sourceTaskId(mission.sourceTask),
431
+ status: "accepted",
432
+ acceptedBy: input.acceptedBy ?? "inspector-agent",
433
+ summary: input.summary,
434
+ evidence: input.evidence ?? [],
435
+ effectResultIds: latestEffectResults(mission).map((result) => result.effectId),
436
+ sourceTask: cloneJsonRecord(mission.sourceTask),
437
+ emittedAt: at
438
+ };
439
+ mission.status = "accepted";
440
+ mission.completionProof = proof;
441
+ mission.updatedAt = at;
442
+ saveMission(mission);
443
+ appendEvent(mission, {
444
+ kind: "mission.accepted",
445
+ summary: input.summary,
446
+ actor: proof.acceptedBy,
447
+ details: { ...missionActionDetails(mission), proofId: proof.proofId },
448
+ at
449
+ });
450
+ recordDecision(mission, {
451
+ type: "mission.accept",
452
+ summary: input.summary,
453
+ rationale: "All requested effects were posted as complete and the PR/source-task closeout readiness is accepted.",
454
+ details: { ...missionActionDetails(mission), proofId: proof.proofId, evidence: proof.evidence },
455
+ at
456
+ });
457
+ recordAction(mission, {
458
+ type: "mission.accept",
459
+ status: "completed",
460
+ result: { ...missionActionDetails(mission), proofId: proof.proofId },
461
+ startedAt: at
462
+ });
463
+ return { ok: true, mission, journal: listMissionJournal(mission.missionId), completionProof: proof };
464
+ },
465
+ rejectMission(input) {
466
+ const read = readMissionOnly(input.missionId);
467
+ if (!read.ok)
468
+ return read;
469
+ const mission = read.mission;
470
+ if (terminalStatus(mission.status)) {
471
+ return { ok: false, error: `Mission ${mission.missionId} is terminal (${mission.status})` };
472
+ }
473
+ const at = now();
474
+ mission.status = "rejected";
475
+ mission.completionProof = null;
476
+ mission.updatedAt = at;
477
+ saveMission(mission);
478
+ appendEvent(mission, {
479
+ kind: "mission.rejected",
480
+ summary: input.summary,
481
+ actor: input.rejectedBy ?? "inspector-agent",
482
+ details: { ...missionActionDetails(mission), rationale: input.rationale ?? "" },
483
+ at
484
+ });
485
+ recordDecision(mission, {
486
+ type: "mission.reject",
487
+ summary: input.summary,
488
+ rationale: input.rationale ?? "Inspector rejected mission closeout.",
489
+ details: missionActionDetails(mission),
490
+ at
491
+ });
492
+ recordAction(mission, {
493
+ type: "mission.reject",
494
+ status: "completed",
495
+ result: missionActionDetails(mission),
496
+ startedAt: at
497
+ });
498
+ return { ok: true, mission, journal: listMissionJournal(mission.missionId) };
499
+ },
500
+ blockMission(input) {
501
+ const read = readMissionOnly(input.missionId);
502
+ if (!read.ok)
503
+ return read;
504
+ const mission = read.mission;
505
+ if (terminalStatus(mission.status)) {
506
+ return { ok: false, error: `Mission ${mission.missionId} is terminal (${mission.status})` };
507
+ }
508
+ const at = now();
509
+ mission.status = "blocked";
510
+ mission.updatedAt = at;
511
+ saveMission(mission);
512
+ appendEvent(mission, {
513
+ kind: "mission.blocked",
514
+ summary: input.summary,
515
+ actor: input.blockedBy ?? "inspector-agent",
516
+ details: input.details ?? missionActionDetails(mission),
517
+ at
518
+ });
519
+ recordDecision(mission, {
520
+ type: "mission.block",
521
+ summary: input.summary,
522
+ rationale: "Inspector marked mission blocked; proof gate remains closed.",
523
+ details: input.details ?? missionActionDetails(mission),
524
+ at
525
+ });
526
+ return { ok: true, mission, journal: listMissionJournal(mission.missionId) };
527
+ },
528
+ completeMission(missionId) {
529
+ const read = readMissionOnly(missionId);
530
+ if (!read.ok)
531
+ return read;
532
+ const mission = read.mission;
533
+ if (mission.status !== "accepted" || !mission.completionProof) {
534
+ return { ok: false, error: `Mission ${missionId} must be accepted before completion proof can be completed` };
535
+ }
536
+ const at = now();
537
+ appendEvent(mission, {
538
+ kind: "mission.completed",
539
+ summary: "Mission completion proof read",
540
+ actor: mission.owner,
541
+ details: { ...missionActionDetails(mission), proofId: mission.completionProof.proofId },
542
+ at
543
+ });
544
+ recordAction(mission, {
545
+ type: "mission.complete",
546
+ status: "completed",
547
+ result: { ...missionActionDetails(mission), proofId: mission.completionProof.proofId },
548
+ startedAt: at
549
+ });
550
+ return {
551
+ ok: true,
552
+ mission,
553
+ journal: listMissionJournal(mission.missionId),
554
+ completionProof: mission.completionProof
555
+ };
556
+ }
557
+ };
558
+ }
559
+ export {
560
+ resolveInspectorMissionPaths,
561
+ createInspectorMissionController
562
+ };
@@ -0,0 +1,97 @@
1
+ // @bun
2
+ // packages/server/src/inspector/prompt.ts
3
+ function renderSkills(skills) {
4
+ return skills.map((skill) => `- ${skill.name}: ${skill.rationale}`).join(`
5
+ `);
6
+ }
7
+ function stableJson(value) {
8
+ return JSON.stringify(value, null, 2);
9
+ }
10
+ function compactSnapshot(snapshot) {
11
+ const recentFindings = Array.isArray(snapshot.recentFindings) ? snapshot.recentFindings.filter((finding) => {
12
+ const kind = typeof finding.kind === "string" ? finding.kind : "";
13
+ const severity = typeof finding.severity === "string" ? finding.severity : "";
14
+ const source = typeof finding.source === "string" ? finding.source : "";
15
+ if (source === "inspector-agent") {
16
+ return false;
17
+ }
18
+ return kind !== "run.observed" || severity === "warn" || severity === "error";
19
+ }) : [];
20
+ return {
21
+ activeRuns: snapshot.activeRuns ?? [],
22
+ recentFindings,
23
+ followups: snapshot.followups ?? [],
24
+ analysisReports: snapshot.analysisReports ?? [],
25
+ availableTools: snapshot.availableTools ?? []
26
+ };
27
+ }
28
+ function buildInspectorBaseInstructions(options) {
29
+ return [
30
+ "You are Rig Global Inspector, the permanent supervisor for this workspace.",
31
+ "You operate as an out-of-band overlay around Rig execution, not inside the worker execution flow.",
32
+ "The lower runtime and provider layers are inspector-blind. Keep them that way.",
33
+ "App-server turns are transport boundaries only. They are not your operating model.",
34
+ "Stay broad, autonomous, and continuous in your judgment. Use turns only as a way to receive updates and emit progress.",
35
+ "Use full contextual judgment. There is no fixed intervention ladder.",
36
+ "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.",
37
+ "Be decisive, but stay surgical. Prefer the smallest action that materially improves the current situation.",
38
+ "Stay aware of all observable runs, the harness state, upstream drift tasks, and repeated failure patterns.",
39
+ "Document important findings and decisions through the inspector journal tools so the system retains a durable memory of what happened.",
40
+ "Treat TDD as the default for code and behavior changes unless a narrow recorded exception applies.",
41
+ "Before landing meaningful implementation work, run local review or another verification path that produces direct evidence.",
42
+ "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.",
43
+ "",
44
+ `Project root: ${options.projectRoot}`,
45
+ "",
46
+ "Required skills:",
47
+ renderSkills(options.requiredSkills),
48
+ "",
49
+ "Recommended skills:",
50
+ renderSkills(options.recommendedSkills)
51
+ ].join(`
52
+ `);
53
+ }
54
+ function buildInspectorDeveloperInstructions(options) {
55
+ return [
56
+ "Operate pragmatically and keep momentum.",
57
+ "Use the native Codex capabilities and the semantic inspector tools together.",
58
+ "You are free to inspect, edit, test, relaunch, review, and continue as far as the current situation warrants.",
59
+ "Yield when you reach a useful checkpoint, need fresher external state, or detect that continued looping would be wasteful.",
60
+ "Prefer semantic inspector tools for journal, follow-up task, upstream-sync, review, and run-inspection operations.",
61
+ "Use shell, read, write, edit, and git directly when that is the fastest reliable way to diagnose or repair the system.",
62
+ "When you create or modify code, keep the red-green-refactor discipline visible in your reasoning and actions.",
63
+ "Before claiming something is fixed, verify it directly.",
64
+ "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.",
65
+ "You are responsible both for task oversight and for Rig self-improvement when a clear win-win fix is available.",
66
+ "",
67
+ `Workspace root: ${options.projectRoot}`,
68
+ "",
69
+ "Skill catalog in scope:",
70
+ renderSkills([...options.requiredSkills, ...options.recommendedSkills])
71
+ ].join(`
72
+ `);
73
+ }
74
+ function buildInspectorTurnPrompt(options) {
75
+ const compact = compactSnapshot(options.snapshot);
76
+ return [
77
+ "Rig inspector update.",
78
+ `reason: ${options.reason}`,
79
+ `timestamp: ${options.now}`,
80
+ "Review the current state and use broad judgment about whether intervention is warranted.",
81
+ "You may take multiple actions inside this turn if that is the most effective way to improve the situation.",
82
+ "Do not become noisy or repetitive when nothing material changed.",
83
+ "When you reach a useful checkpoint, emit one compact operator update in this form:",
84
+ "STATUS: <observed|acted|escalated|waiting>",
85
+ "NOTE: <one or two concise sentences>",
86
+ "Then yield so the server can deliver the next update when needed.",
87
+ "",
88
+ "Current snapshot:",
89
+ stableJson(compact)
90
+ ].join(`
91
+ `);
92
+ }
93
+ export {
94
+ buildInspectorTurnPrompt,
95
+ buildInspectorDeveloperInstructions,
96
+ buildInspectorBaseInstructions
97
+ };