@ouro.bot/cli 0.0.1-alpha.0 → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +20 -0
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +22 -0
  3. package/AdoptionSpecialist.ouro/psyche/identities/basilisk.md +31 -0
  4. package/AdoptionSpecialist.ouro/psyche/identities/jafar.md +31 -0
  5. package/AdoptionSpecialist.ouro/psyche/identities/jormungandr.md +31 -0
  6. package/AdoptionSpecialist.ouro/psyche/identities/kaa.md +31 -0
  7. package/AdoptionSpecialist.ouro/psyche/identities/medusa.md +31 -0
  8. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +31 -0
  9. package/AdoptionSpecialist.ouro/psyche/identities/nagini.md +31 -0
  10. package/AdoptionSpecialist.ouro/psyche/identities/ouroboros.md +31 -0
  11. package/AdoptionSpecialist.ouro/psyche/identities/python.md +31 -0
  12. package/AdoptionSpecialist.ouro/psyche/identities/quetzalcoatl.md +31 -0
  13. package/AdoptionSpecialist.ouro/psyche/identities/sir-hiss.md +31 -0
  14. package/AdoptionSpecialist.ouro/psyche/identities/the-serpent.md +31 -0
  15. package/AdoptionSpecialist.ouro/psyche/identities/the-snake.md +31 -0
  16. package/README.md +224 -6
  17. package/dist/heart/agent-entry.js +17 -0
  18. package/dist/heart/api-error.js +34 -0
  19. package/dist/heart/config.js +296 -0
  20. package/dist/heart/core.js +515 -0
  21. package/dist/heart/daemon/daemon-cli.js +675 -0
  22. package/dist/heart/daemon/daemon-entry.js +74 -0
  23. package/dist/heart/daemon/daemon.js +313 -0
  24. package/dist/heart/daemon/hatch-flow.js +285 -0
  25. package/dist/heart/daemon/hatch-specialist.js +107 -0
  26. package/dist/heart/daemon/health-monitor.js +79 -0
  27. package/dist/heart/daemon/log-tailer.js +146 -0
  28. package/dist/heart/daemon/message-router.js +98 -0
  29. package/dist/heart/daemon/os-cron.js +260 -0
  30. package/dist/heart/daemon/ouro-bot-entry.js +23 -0
  31. package/dist/heart/daemon/ouro-bot-wrapper.js +90 -0
  32. package/dist/heart/daemon/ouro-entry.js +23 -0
  33. package/dist/heart/daemon/ouro-uti.js +212 -0
  34. package/dist/heart/daemon/process-manager.js +237 -0
  35. package/dist/heart/daemon/runtime-logging.js +98 -0
  36. package/dist/heart/daemon/subagent-installer.js +125 -0
  37. package/dist/heart/daemon/task-scheduler.js +240 -0
  38. package/dist/heart/harness.js +26 -0
  39. package/dist/heart/identity.js +281 -0
  40. package/dist/heart/kicks.js +144 -0
  41. package/dist/heart/primitives.js +4 -0
  42. package/dist/heart/providers/anthropic.js +329 -0
  43. package/dist/heart/providers/azure.js +66 -0
  44. package/dist/heart/providers/minimax.js +53 -0
  45. package/dist/heart/providers/openai-codex.js +162 -0
  46. package/dist/heart/streaming.js +412 -0
  47. package/dist/heart/turn-coordinator.js +62 -0
  48. package/dist/inner-worker-entry.js +4 -0
  49. package/dist/mind/associative-recall.js +197 -0
  50. package/dist/mind/bundle-manifest.js +118 -0
  51. package/dist/mind/context.js +302 -0
  52. package/dist/mind/first-impressions.js +43 -0
  53. package/dist/mind/format.js +56 -0
  54. package/dist/mind/friends/channel.js +41 -0
  55. package/dist/mind/friends/resolver.js +84 -0
  56. package/dist/mind/friends/store-file.js +171 -0
  57. package/dist/mind/friends/store.js +4 -0
  58. package/dist/mind/friends/tokens.js +26 -0
  59. package/dist/mind/friends/types.js +21 -0
  60. package/dist/mind/memory.js +388 -0
  61. package/dist/mind/pending.js +93 -0
  62. package/dist/mind/phrases.js +43 -0
  63. package/dist/mind/prompt-refresh.js +20 -0
  64. package/dist/mind/prompt.js +352 -0
  65. package/dist/mind/token-estimate.js +119 -0
  66. package/dist/nerves/cli-logging.js +31 -0
  67. package/dist/nerves/coverage/audit-rules.js +81 -0
  68. package/dist/nerves/coverage/audit.js +200 -0
  69. package/dist/nerves/coverage/cli-main.js +5 -0
  70. package/dist/nerves/coverage/cli.js +51 -0
  71. package/dist/nerves/coverage/contract.js +23 -0
  72. package/dist/nerves/coverage/file-completeness.js +56 -0
  73. package/dist/nerves/coverage/run-artifacts.js +77 -0
  74. package/dist/nerves/coverage/source-scanner.js +34 -0
  75. package/dist/nerves/index.js +152 -0
  76. package/dist/nerves/runtime.js +38 -0
  77. package/dist/repertoire/ado-client.js +211 -0
  78. package/dist/repertoire/ado-context.js +73 -0
  79. package/dist/repertoire/ado-semantic.js +841 -0
  80. package/dist/repertoire/ado-templates.js +146 -0
  81. package/dist/repertoire/coding/index.js +36 -0
  82. package/dist/repertoire/coding/manager.js +489 -0
  83. package/dist/repertoire/coding/monitor.js +60 -0
  84. package/dist/repertoire/coding/reporter.js +45 -0
  85. package/dist/repertoire/coding/spawner.js +102 -0
  86. package/dist/repertoire/coding/tools.js +167 -0
  87. package/dist/repertoire/coding/types.js +2 -0
  88. package/dist/repertoire/data/ado-endpoints.json +122 -0
  89. package/dist/repertoire/data/graph-endpoints.json +212 -0
  90. package/dist/repertoire/github-client.js +64 -0
  91. package/dist/repertoire/graph-client.js +118 -0
  92. package/dist/repertoire/skills.js +156 -0
  93. package/dist/repertoire/tasks/board.js +122 -0
  94. package/dist/repertoire/tasks/index.js +210 -0
  95. package/dist/repertoire/tasks/lifecycle.js +80 -0
  96. package/dist/repertoire/tasks/middleware.js +65 -0
  97. package/dist/repertoire/tasks/parser.js +173 -0
  98. package/dist/repertoire/tasks/scanner.js +132 -0
  99. package/dist/repertoire/tasks/transitions.js +145 -0
  100. package/dist/repertoire/tasks/types.js +2 -0
  101. package/dist/repertoire/tools-base.js +714 -0
  102. package/dist/repertoire/tools-github.js +53 -0
  103. package/dist/repertoire/tools-teams.js +308 -0
  104. package/dist/repertoire/tools.js +199 -0
  105. package/dist/senses/cli-entry.js +15 -0
  106. package/dist/senses/cli.js +604 -0
  107. package/dist/senses/commands.js +98 -0
  108. package/dist/senses/inner-dialog-worker.js +61 -0
  109. package/dist/senses/inner-dialog.js +231 -0
  110. package/dist/senses/session-lock.js +119 -0
  111. package/dist/senses/teams-entry.js +15 -0
  112. package/dist/senses/teams.js +696 -0
  113. package/dist/senses/trust-gate.js +150 -0
  114. package/package.json +34 -11
  115. package/subagents/README.md +73 -0
  116. package/subagents/work-doer.md +233 -0
  117. package/subagents/work-merger.md +624 -0
  118. package/subagents/work-planner.md +373 -0
  119. package/bin/ouro.js +0 -6
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ // Process template awareness -- fetches process template definitions from ADO,
3
+ // derives hierarchy rules, and validates parent/child work item type relationships.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.fetchProcessTemplate = fetchProcessTemplate;
6
+ exports.deriveHierarchyRules = deriveHierarchyRules;
7
+ exports.validateParentChild = validateParentChild;
8
+ const ado_client_1 = require("./ado-client");
9
+ const runtime_1 = require("../nerves/runtime");
10
+ // Known hierarchy definitions for common ADO process templates.
11
+ // Maps template name to explicit parent -> allowed children mappings.
12
+ // Bug appears at multiple levels in Agile/Scrum (can be child of Feature, User Story, or PBI).
13
+ const KNOWN_HIERARCHIES = {
14
+ "Basic": {
15
+ "Epic": ["Issue"],
16
+ "Issue": ["Task"],
17
+ "Task": [],
18
+ },
19
+ "Agile": {
20
+ "Epic": ["Feature"],
21
+ "Feature": ["User Story", "Bug"],
22
+ "User Story": ["Task", "Bug"],
23
+ "Task": [],
24
+ "Bug": ["Task"],
25
+ },
26
+ "Scrum": {
27
+ "Epic": ["Feature"],
28
+ "Feature": ["Product Backlog Item", "Bug"],
29
+ "Product Backlog Item": ["Task", "Bug"],
30
+ "Task": [],
31
+ "Bug": ["Task"],
32
+ },
33
+ "CMMI": {
34
+ "Epic": ["Feature"],
35
+ "Feature": ["Requirement", "Bug"],
36
+ "Requirement": ["Task", "Bug"],
37
+ "Task": [],
38
+ "Bug": ["Task"],
39
+ },
40
+ };
41
+ /**
42
+ * Fetch the process template for an ADO project.
43
+ * Returns null if fetching fails (tools proceed without validation).
44
+ */
45
+ async function fetchProcessTemplate(token, organization, project) {
46
+ (0, runtime_1.emitNervesEvent)({
47
+ component: "repertoire",
48
+ event: "repertoire.ado_template_fetch",
49
+ message: "fetching process template",
50
+ meta: {},
51
+ });
52
+ try {
53
+ // Step 1: Get the process template type from project properties
54
+ const propsResult = await (0, ado_client_1.adoRequest)(token, "GET", organization, `/${project}/_apis/properties`);
55
+ let propsData;
56
+ try {
57
+ propsData = JSON.parse(propsResult);
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ const templateProp = propsData.value?.find(p => p.name === "System.ProcessTemplateType");
63
+ if (!templateProp)
64
+ return null;
65
+ // Step 2: Fetch process template details
66
+ const templateResult = await (0, ado_client_1.adoRequest)(token, "GET", organization, `/_apis/work/processes/${templateProp.value}`);
67
+ let templateData;
68
+ try {
69
+ templateData = JSON.parse(templateResult);
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ if (!templateData.name)
75
+ return null;
76
+ // Step 3: Fetch work item types for the project
77
+ const typesResult = await (0, ado_client_1.adoRequest)(token, "GET", organization, `/${project}/_apis/wit/workitemtypes`);
78
+ let typesData;
79
+ try {
80
+ typesData = JSON.parse(typesResult);
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ if (!typesData.value)
86
+ return null;
87
+ return {
88
+ templateName: templateData.name,
89
+ templateId: templateProp.value,
90
+ workItemTypes: typesData.value.map(t => t.name),
91
+ };
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+ /**
98
+ * Derive hierarchy rules from a process template name and available work item types.
99
+ * Returns a map of parent type -> allowed child types.
100
+ *
101
+ * For known templates (Basic, Agile, Scrum, CMMI), uses predefined hierarchies.
102
+ * For unknown templates, returns empty child arrays (no validation possible).
103
+ */
104
+ function deriveHierarchyRules(templateName, workItemTypes) {
105
+ const rules = {};
106
+ // Initialize all types with empty children
107
+ for (const type of workItemTypes) {
108
+ rules[type] = [];
109
+ }
110
+ const hierarchy = KNOWN_HIERARCHIES[templateName];
111
+ if (!hierarchy)
112
+ return rules;
113
+ // Apply known hierarchy rules, filtering to types actually present in the project
114
+ const typeSet = new Set(workItemTypes);
115
+ for (const [parentType, children] of Object.entries(hierarchy)) {
116
+ if (typeSet.has(parentType)) {
117
+ rules[parentType] = children.filter(t => typeSet.has(t));
118
+ }
119
+ }
120
+ return rules;
121
+ }
122
+ /**
123
+ * Validate whether a child work item type can be parented under a given parent type.
124
+ */
125
+ function validateParentChild(rules, parentType, childType) {
126
+ const allowedChildren = rules[parentType];
127
+ if (allowedChildren === undefined) {
128
+ return {
129
+ valid: false,
130
+ violations: [`Unknown parent type: ${parentType}. Not found in project's work item types.`],
131
+ };
132
+ }
133
+ if (allowedChildren.length === 0) {
134
+ return {
135
+ valid: false,
136
+ violations: [`${parentType} cannot have children (leaf type). ${childType} cannot be a child of ${parentType}.`],
137
+ };
138
+ }
139
+ if (!allowedChildren.includes(childType)) {
140
+ return {
141
+ valid: false,
142
+ violations: [`${childType} cannot be a child of ${parentType}. Allowed children: ${allowedChildren.join(", ")}.`],
143
+ };
144
+ }
145
+ return { valid: true, violations: [] };
146
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatCodingMonitorReport = exports.CodingSessionMonitor = exports.CodingSessionManager = void 0;
4
+ exports.getCodingSessionManager = getCodingSessionManager;
5
+ exports.resetCodingSessionManager = resetCodingSessionManager;
6
+ const runtime_1 = require("../../nerves/runtime");
7
+ const manager_1 = require("./manager");
8
+ let manager = null;
9
+ function getCodingSessionManager() {
10
+ if (!manager) {
11
+ manager = new manager_1.CodingSessionManager({});
12
+ (0, runtime_1.emitNervesEvent)({
13
+ component: "repertoire",
14
+ event: "repertoire.coding_manager_init",
15
+ message: "initialized coding session manager singleton",
16
+ meta: {},
17
+ });
18
+ }
19
+ return manager;
20
+ }
21
+ function resetCodingSessionManager() {
22
+ manager?.shutdown();
23
+ manager = null;
24
+ (0, runtime_1.emitNervesEvent)({
25
+ component: "repertoire",
26
+ event: "repertoire.coding_manager_reset",
27
+ message: "reset coding session manager singleton",
28
+ meta: {},
29
+ });
30
+ }
31
+ var manager_2 = require("./manager");
32
+ Object.defineProperty(exports, "CodingSessionManager", { enumerable: true, get: function () { return manager_2.CodingSessionManager; } });
33
+ var monitor_1 = require("./monitor");
34
+ Object.defineProperty(exports, "CodingSessionMonitor", { enumerable: true, get: function () { return monitor_1.CodingSessionMonitor; } });
35
+ var reporter_1 = require("./reporter");
36
+ Object.defineProperty(exports, "formatCodingMonitorReport", { enumerable: true, get: function () { return reporter_1.formatCodingMonitorReport; } });
@@ -0,0 +1,489 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CodingSessionManager = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const identity_1 = require("../../heart/identity");
41
+ const runtime_1 = require("../../nerves/runtime");
42
+ const spawner_1 = require("./spawner");
43
+ function safeAgentName() {
44
+ try {
45
+ return (0, identity_1.getAgentName)();
46
+ }
47
+ catch {
48
+ return "default";
49
+ }
50
+ }
51
+ function defaultStateFilePath(agentName) {
52
+ return path.join(os.homedir(), ".agentstate", agentName, "coding", "sessions.json");
53
+ }
54
+ function isPidAlive(pid) {
55
+ try {
56
+ process.kill(pid, 0);
57
+ return true;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ function cloneSession(session) {
64
+ return {
65
+ ...session,
66
+ failure: session.failure
67
+ ? {
68
+ ...session.failure,
69
+ args: [...session.failure.args],
70
+ }
71
+ : null,
72
+ };
73
+ }
74
+ function extractSequence(id) {
75
+ const match = /^coding-(\d+)$/.exec(id);
76
+ if (!match)
77
+ return 0;
78
+ return Number.parseInt(match[1], 10);
79
+ }
80
+ function isActiveSessionStatus(status) {
81
+ return status === "spawning" || status === "running" || status === "waiting_input" || status === "stalled";
82
+ }
83
+ function appendTail(existing, nextChunk, maxLength = 2000) {
84
+ const combined = `${existing}${nextChunk}`;
85
+ return combined.length <= maxLength ? combined : combined.slice(combined.length - maxLength);
86
+ }
87
+ function isSpawnCodingResult(value) {
88
+ return typeof value === "object" && value !== null && "process" in value;
89
+ }
90
+ function normalizeSpawnResult(result) {
91
+ if (isSpawnCodingResult(result)) {
92
+ return {
93
+ process: result.process,
94
+ command: result.command,
95
+ args: [...result.args],
96
+ prompt: result.prompt,
97
+ };
98
+ }
99
+ return {
100
+ process: result,
101
+ command: "unknown",
102
+ args: [],
103
+ prompt: "",
104
+ };
105
+ }
106
+ function defaultFailureDiagnostics(code, signal, command, args, stdoutTail, stderrTail) {
107
+ return {
108
+ command,
109
+ args: [...args],
110
+ code,
111
+ signal,
112
+ stdoutTail: stdoutTail.trim(),
113
+ stderrTail: stderrTail.trim(),
114
+ };
115
+ }
116
+ class CodingSessionManager {
117
+ records = new Map();
118
+ spawnProcess;
119
+ nowIso;
120
+ maxRestarts;
121
+ defaultStallThresholdMs;
122
+ stateFilePath;
123
+ existsSync;
124
+ readFileSync;
125
+ writeFileSync;
126
+ mkdirSync;
127
+ pidAlive;
128
+ agentName;
129
+ sequence = 0;
130
+ constructor(options) {
131
+ this.spawnProcess = options.spawnProcess ?? ((request) => (0, spawner_1.spawnCodingProcess)(request));
132
+ this.nowIso = options.nowIso ?? (() => new Date().toISOString());
133
+ this.maxRestarts = options.maxRestarts ?? 1;
134
+ this.defaultStallThresholdMs = options.defaultStallThresholdMs ?? 5 * 60_000;
135
+ this.existsSync = options.existsSync ?? fs.existsSync;
136
+ this.readFileSync = options.readFileSync ?? fs.readFileSync;
137
+ this.writeFileSync = options.writeFileSync ?? fs.writeFileSync;
138
+ this.mkdirSync = options.mkdirSync ?? fs.mkdirSync;
139
+ this.pidAlive = options.pidAlive ?? isPidAlive;
140
+ this.agentName = options.agentName ?? safeAgentName();
141
+ this.stateFilePath = options.stateFilePath ?? defaultStateFilePath(this.agentName);
142
+ this.loadPersistedState();
143
+ }
144
+ async spawnSession(request) {
145
+ this.sequence += 1;
146
+ const id = `coding-${String(this.sequence).padStart(3, "0")}`;
147
+ const now = this.nowIso();
148
+ const normalizedRequest = {
149
+ ...request,
150
+ sessionId: id,
151
+ parentAgent: request.parentAgent ?? this.agentName,
152
+ };
153
+ const session = {
154
+ id,
155
+ runner: normalizedRequest.runner,
156
+ workdir: normalizedRequest.workdir,
157
+ taskRef: normalizedRequest.taskRef,
158
+ scopeFile: normalizedRequest.scopeFile,
159
+ stateFile: normalizedRequest.stateFile,
160
+ status: "spawning",
161
+ pid: null,
162
+ startedAt: now,
163
+ lastActivityAt: now,
164
+ endedAt: null,
165
+ restartCount: 0,
166
+ lastExitCode: null,
167
+ lastSignal: null,
168
+ failure: null,
169
+ };
170
+ const spawned = normalizeSpawnResult(this.spawnProcess(normalizedRequest));
171
+ session.pid = spawned.process.pid ?? null;
172
+ session.status = "running";
173
+ const record = {
174
+ request: normalizedRequest,
175
+ session,
176
+ process: spawned.process,
177
+ command: spawned.command,
178
+ args: [...spawned.args],
179
+ stdoutTail: "",
180
+ stderrTail: "",
181
+ };
182
+ this.records.set(id, record);
183
+ this.attachProcessListeners(record);
184
+ (0, runtime_1.emitNervesEvent)({
185
+ component: "repertoire",
186
+ event: "repertoire.coding_session_spawned",
187
+ message: "coding session spawned",
188
+ meta: { id, runner: normalizedRequest.runner, pid: session.pid },
189
+ });
190
+ this.persistState();
191
+ return cloneSession(session);
192
+ }
193
+ listSessions() {
194
+ return [...this.records.values()]
195
+ .map((record) => cloneSession(record.session))
196
+ .sort((a, b) => a.id.localeCompare(b.id));
197
+ }
198
+ getSession(sessionId) {
199
+ const record = this.records.get(sessionId);
200
+ return record ? cloneSession(record.session) : null;
201
+ }
202
+ sendInput(sessionId, input) {
203
+ const record = this.records.get(sessionId);
204
+ if (!record || !record.process) {
205
+ return { ok: false, message: `session not found: ${sessionId}` };
206
+ }
207
+ record.process.stdin.write(`${input}\n`);
208
+ record.session.lastActivityAt = this.nowIso();
209
+ if (record.session.status === "waiting_input" || record.session.status === "stalled") {
210
+ record.session.status = "running";
211
+ }
212
+ (0, runtime_1.emitNervesEvent)({
213
+ component: "repertoire",
214
+ event: "repertoire.coding_session_input",
215
+ message: "forwarded input to coding session",
216
+ meta: { id: sessionId },
217
+ });
218
+ this.persistState();
219
+ return { ok: true, message: `input sent to ${sessionId}` };
220
+ }
221
+ killSession(sessionId) {
222
+ const record = this.records.get(sessionId);
223
+ if (!record || !record.process) {
224
+ return { ok: false, message: `session not found: ${sessionId}` };
225
+ }
226
+ record.process.kill("SIGTERM");
227
+ record.process = null;
228
+ record.session.status = "killed";
229
+ record.session.endedAt = this.nowIso();
230
+ (0, runtime_1.emitNervesEvent)({
231
+ component: "repertoire",
232
+ event: "repertoire.coding_session_killed",
233
+ message: "coding session terminated",
234
+ meta: { id: sessionId },
235
+ });
236
+ this.persistState();
237
+ return { ok: true, message: `killed ${sessionId}` };
238
+ }
239
+ checkStalls(nowMs = Date.now()) {
240
+ let stalled = 0;
241
+ for (const record of this.records.values()) {
242
+ if (record.session.status !== "running")
243
+ continue;
244
+ const threshold = record.request.stallThresholdMs ?? this.defaultStallThresholdMs;
245
+ const elapsed = nowMs - Date.parse(record.session.lastActivityAt);
246
+ if (elapsed <= threshold)
247
+ continue;
248
+ stalled += 1;
249
+ record.session.status = "stalled";
250
+ (0, runtime_1.emitNervesEvent)({
251
+ level: "warn",
252
+ component: "repertoire",
253
+ event: "repertoire.coding_session_stalled",
254
+ message: "coding session stalled",
255
+ meta: { id: record.session.id, elapsedMs: elapsed },
256
+ });
257
+ if (record.request.autoRestartOnStall !== false && record.session.restartCount < this.maxRestarts) {
258
+ this.restartSession(record, "stalled");
259
+ }
260
+ }
261
+ if (stalled > 0) {
262
+ this.persistState();
263
+ }
264
+ return stalled;
265
+ }
266
+ shutdown() {
267
+ for (const record of this.records.values()) {
268
+ if (!record.process)
269
+ continue;
270
+ record.process.kill("SIGTERM");
271
+ record.process = null;
272
+ if (record.session.status === "running" || record.session.status === "spawning") {
273
+ record.session.status = "killed";
274
+ record.session.endedAt = this.nowIso();
275
+ }
276
+ }
277
+ this.persistState();
278
+ (0, runtime_1.emitNervesEvent)({
279
+ component: "repertoire",
280
+ event: "repertoire.coding_manager_shutdown",
281
+ message: "coding session manager shutdown complete",
282
+ meta: { sessionCount: this.records.size },
283
+ });
284
+ }
285
+ attachProcessListeners(record) {
286
+ if (!record.process)
287
+ return;
288
+ record.process.stdout.on("data", (chunk) => {
289
+ this.onOutput(record, chunk.toString(), "stdout");
290
+ });
291
+ record.process.stderr.on("data", (chunk) => {
292
+ this.onOutput(record, chunk.toString(), "stderr");
293
+ });
294
+ record.process.on("exit", (code, signal) => {
295
+ this.onExit(record, code, signal);
296
+ });
297
+ }
298
+ onOutput(record, text, stream) {
299
+ record.session.lastActivityAt = this.nowIso();
300
+ if (stream === "stdout") {
301
+ record.stdoutTail = appendTail(record.stdoutTail, text);
302
+ }
303
+ else {
304
+ record.stderrTail = appendTail(record.stderrTail, text);
305
+ }
306
+ if (text.includes("status: NEEDS_REVIEW") || text.includes("❌ blocked")) {
307
+ record.session.status = "waiting_input";
308
+ }
309
+ if (text.includes("✅ all units complete")) {
310
+ record.session.status = "completed";
311
+ record.session.endedAt = this.nowIso();
312
+ }
313
+ (0, runtime_1.emitNervesEvent)({
314
+ component: "repertoire",
315
+ event: "repertoire.coding_session_output",
316
+ message: "received coding session output",
317
+ meta: { id: record.session.id, status: record.session.status },
318
+ });
319
+ this.persistState();
320
+ }
321
+ onExit(record, code, signal) {
322
+ if (!record.process)
323
+ return;
324
+ record.process = null;
325
+ record.session.pid = null;
326
+ record.session.lastExitCode = code;
327
+ record.session.lastSignal = signal;
328
+ if (record.session.status === "killed" || record.session.status === "completed") {
329
+ record.session.endedAt = this.nowIso();
330
+ this.persistState();
331
+ return;
332
+ }
333
+ if (code === 0) {
334
+ record.session.status = "completed";
335
+ record.session.endedAt = this.nowIso();
336
+ this.persistState();
337
+ return;
338
+ }
339
+ if (record.request.autoRestartOnCrash !== false && record.session.restartCount < this.maxRestarts) {
340
+ this.restartSession(record, "crash");
341
+ return;
342
+ }
343
+ record.session.status = "failed";
344
+ record.session.endedAt = this.nowIso();
345
+ record.session.failure = defaultFailureDiagnostics(code, signal, record.command, record.args, record.stdoutTail, record.stderrTail);
346
+ (0, runtime_1.emitNervesEvent)({
347
+ level: "error",
348
+ component: "repertoire",
349
+ event: "repertoire.coding_session_failed",
350
+ message: "coding session failed without restart",
351
+ meta: { id: record.session.id, code, signal, command: record.command },
352
+ });
353
+ this.persistState();
354
+ }
355
+ restartSession(record, reason) {
356
+ const replacement = normalizeSpawnResult(this.spawnProcess(record.request));
357
+ record.process = replacement.process;
358
+ record.command = replacement.command;
359
+ record.args = [...replacement.args];
360
+ record.stdoutTail = "";
361
+ record.stderrTail = "";
362
+ record.session.pid = replacement.process.pid ?? null;
363
+ record.session.restartCount += 1;
364
+ record.session.status = "running";
365
+ record.session.lastActivityAt = this.nowIso();
366
+ record.session.endedAt = null;
367
+ record.session.failure = null;
368
+ this.attachProcessListeners(record);
369
+ (0, runtime_1.emitNervesEvent)({
370
+ level: "warn",
371
+ component: "repertoire",
372
+ event: "repertoire.coding_session_restarted",
373
+ message: "coding session restarted",
374
+ meta: { id: record.session.id, reason, restartCount: record.session.restartCount },
375
+ });
376
+ this.persistState();
377
+ }
378
+ loadPersistedState() {
379
+ if (!this.existsSync(this.stateFilePath)) {
380
+ return;
381
+ }
382
+ let raw;
383
+ try {
384
+ raw = this.readFileSync(this.stateFilePath, "utf-8");
385
+ }
386
+ catch (error) {
387
+ (0, runtime_1.emitNervesEvent)({
388
+ level: "warn",
389
+ component: "repertoire",
390
+ event: "repertoire.coding_state_load_error",
391
+ message: "failed reading coding session state",
392
+ meta: { path: this.stateFilePath, reason: error instanceof Error ? error.message : String(error) },
393
+ });
394
+ return;
395
+ }
396
+ let parsed;
397
+ try {
398
+ parsed = JSON.parse(raw);
399
+ }
400
+ catch (error) {
401
+ (0, runtime_1.emitNervesEvent)({
402
+ level: "warn",
403
+ component: "repertoire",
404
+ event: "repertoire.coding_state_load_error",
405
+ message: "invalid coding session state JSON",
406
+ meta: { path: this.stateFilePath, reason: error instanceof Error ? error.message : String(error) },
407
+ });
408
+ return;
409
+ }
410
+ if (!Array.isArray(parsed.records)) {
411
+ (0, runtime_1.emitNervesEvent)({
412
+ level: "warn",
413
+ component: "repertoire",
414
+ event: "repertoire.coding_state_load_error",
415
+ message: "coding session state missing records array",
416
+ meta: { path: this.stateFilePath },
417
+ });
418
+ return;
419
+ }
420
+ this.sequence = Number.isInteger(parsed.sequence) && parsed.sequence >= 0 ? parsed.sequence : 0;
421
+ for (const item of parsed.records) {
422
+ const request = item?.request;
423
+ const session = item?.session;
424
+ if (!request || !session || typeof session.id !== "string") {
425
+ continue;
426
+ }
427
+ const normalizedRequest = {
428
+ ...request,
429
+ sessionId: request.sessionId ?? session.id,
430
+ parentAgent: request.parentAgent ?? this.agentName,
431
+ };
432
+ const normalizedSession = {
433
+ ...session,
434
+ taskRef: session.taskRef ?? normalizedRequest.taskRef,
435
+ failure: session.failure ?? null,
436
+ };
437
+ if (typeof normalizedSession.pid === "number") {
438
+ const alive = this.pidAlive(normalizedSession.pid);
439
+ if (!alive && isActiveSessionStatus(normalizedSession.status)) {
440
+ normalizedSession.status = "failed";
441
+ normalizedSession.endedAt = normalizedSession.endedAt ?? this.nowIso();
442
+ normalizedSession.failure =
443
+ normalizedSession.failure ??
444
+ defaultFailureDiagnostics(normalizedSession.lastExitCode, normalizedSession.lastSignal, "restored", [], "", "process not running during manager restore");
445
+ normalizedSession.pid = null;
446
+ }
447
+ }
448
+ this.records.set(normalizedSession.id, {
449
+ request: normalizedRequest,
450
+ session: normalizedSession,
451
+ process: null,
452
+ command: normalizedSession.failure?.command ?? "restored",
453
+ args: normalizedSession.failure ? [...normalizedSession.failure.args] : [],
454
+ stdoutTail: normalizedSession.failure?.stdoutTail ?? "",
455
+ stderrTail: normalizedSession.failure?.stderrTail ?? "",
456
+ });
457
+ this.sequence = Math.max(this.sequence, extractSequence(normalizedSession.id));
458
+ }
459
+ (0, runtime_1.emitNervesEvent)({
460
+ component: "repertoire",
461
+ event: "repertoire.coding_state_loaded",
462
+ message: "loaded persisted coding sessions",
463
+ meta: { count: this.records.size },
464
+ });
465
+ }
466
+ persistState() {
467
+ const payload = {
468
+ sequence: this.sequence,
469
+ records: [...this.records.values()].map((record) => ({
470
+ request: record.request,
471
+ session: record.session,
472
+ })),
473
+ };
474
+ try {
475
+ this.mkdirSync(path.dirname(this.stateFilePath), { recursive: true });
476
+ this.writeFileSync(this.stateFilePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
477
+ }
478
+ catch (error) {
479
+ (0, runtime_1.emitNervesEvent)({
480
+ level: "warn",
481
+ component: "repertoire",
482
+ event: "repertoire.coding_state_persist_error",
483
+ message: "failed persisting coding sessions",
484
+ meta: { path: this.stateFilePath, reason: error instanceof Error ? error.message : String(error) },
485
+ });
486
+ }
487
+ }
488
+ }
489
+ exports.CodingSessionManager = CodingSessionManager;