@ouro.bot/cli 0.1.0-alpha.6 → 0.1.0-alpha.60

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 (117) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +147 -205
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +325 -0
  7. package/dist/heart/active-work.js +178 -0
  8. package/dist/heart/bridges/manager.js +358 -0
  9. package/dist/heart/bridges/state-machine.js +135 -0
  10. package/dist/heart/bridges/store.js +123 -0
  11. package/dist/heart/config.js +57 -23
  12. package/dist/heart/core.js +236 -90
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-discovery.js +81 -0
  15. package/dist/heart/daemon/auth-flow.js +351 -0
  16. package/dist/heart/daemon/daemon-cli.js +1173 -227
  17. package/dist/heart/daemon/daemon-entry.js +55 -6
  18. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  19. package/dist/heart/daemon/daemon.js +189 -10
  20. package/dist/heart/daemon/hatch-animation.js +10 -3
  21. package/dist/heart/daemon/hatch-flow.js +4 -82
  22. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  23. package/dist/heart/daemon/launchd.js +159 -0
  24. package/dist/heart/daemon/log-tailer.js +4 -3
  25. package/dist/heart/daemon/message-router.js +17 -8
  26. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  27. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  28. package/dist/heart/daemon/ouro-entry.js +0 -0
  29. package/dist/heart/daemon/ouro-path-installer.js +178 -0
  30. package/dist/heart/daemon/ouro-uti.js +11 -2
  31. package/dist/heart/daemon/process-manager.js +14 -1
  32. package/dist/heart/daemon/run-hooks.js +37 -0
  33. package/dist/heart/daemon/runtime-logging.js +58 -15
  34. package/dist/heart/daemon/runtime-metadata.js +219 -0
  35. package/dist/heart/daemon/runtime-mode.js +67 -0
  36. package/dist/heart/daemon/sense-manager.js +307 -0
  37. package/dist/heart/daemon/socket-client.js +202 -0
  38. package/dist/heart/daemon/specialist-orchestrator.js +53 -84
  39. package/dist/heart/daemon/specialist-prompt.js +64 -5
  40. package/dist/heart/daemon/specialist-tools.js +213 -58
  41. package/dist/heart/daemon/staged-restart.js +114 -0
  42. package/dist/heart/daemon/subagent-installer.js +48 -7
  43. package/dist/heart/daemon/thoughts.js +379 -0
  44. package/dist/heart/daemon/update-checker.js +111 -0
  45. package/dist/heart/daemon/update-hooks.js +138 -0
  46. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  47. package/dist/heart/delegation.js +62 -0
  48. package/dist/heart/identity.js +122 -19
  49. package/dist/heart/kicks.js +1 -19
  50. package/dist/heart/model-capabilities.js +40 -0
  51. package/dist/heart/progress-story.js +42 -0
  52. package/dist/heart/providers/anthropic.js +74 -9
  53. package/dist/heart/providers/azure.js +86 -7
  54. package/dist/heart/providers/minimax.js +4 -0
  55. package/dist/heart/providers/openai-codex.js +12 -3
  56. package/dist/heart/safe-workspace.js +228 -0
  57. package/dist/heart/sense-truth.js +61 -0
  58. package/dist/heart/session-activity.js +169 -0
  59. package/dist/heart/session-recall.js +116 -0
  60. package/dist/heart/streaming.js +100 -22
  61. package/dist/heart/target-resolution.js +123 -0
  62. package/dist/heart/turn-coordinator.js +28 -0
  63. package/dist/mind/associative-recall.js +14 -2
  64. package/dist/mind/bundle-manifest.js +70 -0
  65. package/dist/mind/context.js +27 -11
  66. package/dist/mind/first-impressions.js +16 -2
  67. package/dist/mind/friends/channel.js +35 -0
  68. package/dist/mind/friends/group-context.js +144 -0
  69. package/dist/mind/friends/store-file.js +19 -0
  70. package/dist/mind/friends/trust-explanation.js +74 -0
  71. package/dist/mind/friends/types.js +8 -0
  72. package/dist/mind/memory.js +27 -26
  73. package/dist/mind/pending.js +72 -9
  74. package/dist/mind/phrases.js +1 -0
  75. package/dist/mind/prompt.js +299 -77
  76. package/dist/mind/token-estimate.js +8 -12
  77. package/dist/nerves/cli-logging.js +15 -2
  78. package/dist/nerves/coverage/run-artifacts.js +1 -1
  79. package/dist/repertoire/ado-client.js +4 -2
  80. package/dist/repertoire/coding/feedback.js +134 -0
  81. package/dist/repertoire/coding/index.js +4 -1
  82. package/dist/repertoire/coding/manager.js +62 -4
  83. package/dist/repertoire/coding/spawner.js +3 -3
  84. package/dist/repertoire/coding/tools.js +41 -2
  85. package/dist/repertoire/data/ado-endpoints.json +188 -0
  86. package/dist/repertoire/tasks/board.js +12 -0
  87. package/dist/repertoire/tasks/index.js +23 -9
  88. package/dist/repertoire/tasks/transitions.js +1 -2
  89. package/dist/repertoire/tools-base.js +629 -251
  90. package/dist/repertoire/tools-bluebubbles.js +93 -0
  91. package/dist/repertoire/tools-teams.js +58 -25
  92. package/dist/repertoire/tools.js +92 -48
  93. package/dist/senses/bluebubbles-client.js +210 -5
  94. package/dist/senses/bluebubbles-entry.js +2 -0
  95. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  96. package/dist/senses/bluebubbles-media.js +339 -0
  97. package/dist/senses/bluebubbles-model.js +12 -4
  98. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  99. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  100. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  101. package/dist/senses/bluebubbles.js +890 -45
  102. package/dist/senses/cli-layout.js +87 -0
  103. package/dist/senses/cli.js +345 -144
  104. package/dist/senses/continuity.js +94 -0
  105. package/dist/senses/debug-activity.js +148 -0
  106. package/dist/senses/inner-dialog-worker.js +47 -18
  107. package/dist/senses/inner-dialog.js +330 -84
  108. package/dist/senses/pipeline.js +278 -0
  109. package/dist/senses/teams.js +570 -129
  110. package/dist/senses/trust-gate.js +112 -2
  111. package/package.json +14 -3
  112. package/subagents/README.md +46 -33
  113. package/subagents/work-doer.md +28 -24
  114. package/subagents/work-merger.md +24 -30
  115. package/subagents/work-planner.md +44 -27
  116. package/dist/heart/daemon/specialist-session.js +0 -142
  117. package/dist/inner-worker-entry.js +0 -4
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatCodingTail = formatCodingTail;
4
+ exports.attachCodingSessionFeedback = attachCodingSessionFeedback;
5
+ const runtime_1 = require("../../nerves/runtime");
6
+ const TERMINAL_UPDATE_KINDS = new Set(["completed", "failed", "killed"]);
7
+ function clip(text, maxLength = 280) {
8
+ const trimmed = text.trim();
9
+ if (trimmed.length <= maxLength)
10
+ return trimmed;
11
+ return `${trimmed.slice(0, maxLength - 3)}...`;
12
+ }
13
+ function isNoiseLine(line) {
14
+ return (/^-+$/.test(line)
15
+ || /^Reading prompt from stdin/i.test(line)
16
+ || /^OpenAI Codex v/i.test(line)
17
+ || /^workdir:/i.test(line)
18
+ || /^model:/i.test(line)
19
+ || /^provider:/i.test(line)
20
+ || /^approval:/i.test(line)
21
+ || /^sandbox:/i.test(line)
22
+ || /^reasoning effort:/i.test(line)
23
+ || /^reasoning summaries:/i.test(line)
24
+ || /^session id:/i.test(line)
25
+ || /^mcp startup:/i.test(line)
26
+ || /^tokens used$/i.test(line)
27
+ || /^\d{1,3}(,\d{3})*$/.test(line)
28
+ || /^\d{4}-\d{2}-\d{2}T.*\bWARN\b/.test(line)
29
+ || line === "user"
30
+ || line === "codex");
31
+ }
32
+ function lastMeaningfulLine(text) {
33
+ if (!text)
34
+ return null;
35
+ const lines = text
36
+ .split(/\r?\n/)
37
+ .map((line) => line.trim())
38
+ .filter(Boolean)
39
+ .filter((line) => !isNoiseLine(line));
40
+ if (lines.length === 0)
41
+ return null;
42
+ return clip(lines.at(-1));
43
+ }
44
+ function formatSessionLabel(session) {
45
+ return `${session.runner} ${session.id}`;
46
+ }
47
+ function isSafeProgressSnippet(snippet) {
48
+ const wordCount = snippet.split(/\s+/).filter(Boolean).length;
49
+ return (snippet.length <= 80
50
+ && wordCount <= 8
51
+ && !snippet.includes(":")
52
+ && !snippet.startsWith("**")
53
+ && !/^Respond with\b/i.test(snippet)
54
+ && !/^Coding session metadata\b/i.test(snippet)
55
+ && !/^sessionId\b/i.test(snippet)
56
+ && !/^taskRef\b/i.test(snippet)
57
+ && !/^parentAgent\b/i.test(snippet));
58
+ }
59
+ function pickUpdateSnippet(update) {
60
+ return (lastMeaningfulLine(update.text)
61
+ ?? lastMeaningfulLine(update.session.stderrTail)
62
+ ?? lastMeaningfulLine(update.session.stdoutTail));
63
+ }
64
+ function formatUpdateMessage(update) {
65
+ const label = formatSessionLabel(update.session);
66
+ const snippet = pickUpdateSnippet(update);
67
+ switch (update.kind) {
68
+ case "progress":
69
+ return snippet && isSafeProgressSnippet(snippet) ? `${label}: ${snippet}` : null;
70
+ case "waiting_input":
71
+ return snippet ? `${label} waiting: ${snippet}` : `${label} waiting`;
72
+ case "stalled":
73
+ return snippet ? `${label} stalled: ${snippet}` : `${label} stalled`;
74
+ case "completed":
75
+ return snippet ? `${label} completed: ${snippet}` : `${label} completed`;
76
+ case "failed":
77
+ return snippet ? `${label} failed: ${snippet}` : `${label} failed`;
78
+ case "killed":
79
+ return `${label} killed`;
80
+ case "spawned":
81
+ return `${label} started`;
82
+ }
83
+ }
84
+ function formatCodingTail(session) {
85
+ const stdout = session.stdoutTail.trim() || "(empty)";
86
+ const stderr = session.stderrTail.trim() || "(empty)";
87
+ return [
88
+ `sessionId: ${session.id}`,
89
+ `runner: ${session.runner}`,
90
+ `status: ${session.status}`,
91
+ `workdir: ${session.workdir}`,
92
+ "",
93
+ "[stdout]",
94
+ stdout,
95
+ "",
96
+ "[stderr]",
97
+ stderr,
98
+ ].join("\n");
99
+ }
100
+ function attachCodingSessionFeedback(manager, session, target) {
101
+ let lastMessage = "";
102
+ let closed = false;
103
+ let unsubscribe = () => { };
104
+ const sendMessage = (message) => {
105
+ if (closed || !message || message === lastMessage) {
106
+ return;
107
+ }
108
+ lastMessage = message;
109
+ void Promise.resolve(target.send(message)).catch((error) => {
110
+ (0, runtime_1.emitNervesEvent)({
111
+ level: "warn",
112
+ component: "repertoire",
113
+ event: "repertoire.coding_feedback_error",
114
+ message: "coding feedback transport failed",
115
+ meta: {
116
+ sessionId: session.id,
117
+ reason: error instanceof Error ? error.message : String(error),
118
+ },
119
+ });
120
+ });
121
+ };
122
+ sendMessage(formatUpdateMessage({ kind: "spawned", session }));
123
+ unsubscribe = manager.subscribe(session.id, async (update) => {
124
+ sendMessage(formatUpdateMessage(update));
125
+ if (TERMINAL_UPDATE_KINDS.has(update.kind)) {
126
+ closed = true;
127
+ unsubscribe();
128
+ }
129
+ });
130
+ return () => {
131
+ closed = true;
132
+ unsubscribe();
133
+ };
134
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatCodingMonitorReport = exports.CodingSessionMonitor = exports.CodingSessionManager = void 0;
3
+ exports.formatCodingTail = exports.attachCodingSessionFeedback = exports.formatCodingMonitorReport = exports.CodingSessionMonitor = exports.CodingSessionManager = void 0;
4
4
  exports.getCodingSessionManager = getCodingSessionManager;
5
5
  exports.resetCodingSessionManager = resetCodingSessionManager;
6
6
  const runtime_1 = require("../../nerves/runtime");
@@ -34,3 +34,6 @@ var monitor_1 = require("./monitor");
34
34
  Object.defineProperty(exports, "CodingSessionMonitor", { enumerable: true, get: function () { return monitor_1.CodingSessionMonitor; } });
35
35
  var reporter_1 = require("./reporter");
36
36
  Object.defineProperty(exports, "formatCodingMonitorReport", { enumerable: true, get: function () { return reporter_1.formatCodingMonitorReport; } });
37
+ var feedback_1 = require("./feedback");
38
+ Object.defineProperty(exports, "attachCodingSessionFeedback", { enumerable: true, get: function () { return feedback_1.attachCodingSessionFeedback; } });
39
+ Object.defineProperty(exports, "formatCodingTail", { enumerable: true, get: function () { return feedback_1.formatCodingTail; } });
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.CodingSessionManager = void 0;
37
37
  const fs = __importStar(require("fs"));
38
- const os = __importStar(require("os"));
39
38
  const path = __importStar(require("path"));
40
39
  const identity_1 = require("../../heart/identity");
41
40
  const runtime_1 = require("../../nerves/runtime");
@@ -49,7 +48,7 @@ function safeAgentName() {
49
48
  }
50
49
  }
51
50
  function defaultStateFilePath(agentName) {
52
- return path.join(os.homedir(), ".agentstate", agentName, "coding", "sessions.json");
51
+ return path.join((0, identity_1.getAgentRoot)(agentName), "state", "coding", "sessions.json");
53
52
  }
54
53
  function isPidAlive(pid) {
55
54
  try {
@@ -63,6 +62,8 @@ function isPidAlive(pid) {
63
62
  function cloneSession(session) {
64
63
  return {
65
64
  ...session,
65
+ stdoutTail: session.stdoutTail,
66
+ stderrTail: session.stderrTail,
66
67
  failure: session.failure
67
68
  ? {
68
69
  ...session.failure,
@@ -115,6 +116,7 @@ function defaultFailureDiagnostics(code, signal, command, args, stdoutTail, stde
115
116
  }
116
117
  class CodingSessionManager {
117
118
  records = new Map();
119
+ listeners = new Map();
118
120
  spawnProcess;
119
121
  nowIso;
120
122
  maxRestarts;
@@ -158,6 +160,8 @@ class CodingSessionManager {
158
160
  scopeFile: normalizedRequest.scopeFile,
159
161
  stateFile: normalizedRequest.stateFile,
160
162
  status: "spawning",
163
+ stdoutTail: "",
164
+ stderrTail: "",
161
165
  pid: null,
162
166
  startedAt: now,
163
167
  lastActivityAt: now,
@@ -188,6 +192,7 @@ class CodingSessionManager {
188
192
  meta: { id, runner: normalizedRequest.runner, pid: session.pid },
189
193
  });
190
194
  this.persistState();
195
+ this.notifyListeners(id, { kind: "spawned", session: cloneSession(session) });
191
196
  return cloneSession(session);
192
197
  }
193
198
  listSessions() {
@@ -199,6 +204,20 @@ class CodingSessionManager {
199
204
  const record = this.records.get(sessionId);
200
205
  return record ? cloneSession(record.session) : null;
201
206
  }
207
+ subscribe(sessionId, listener) {
208
+ const listeners = this.listeners.get(sessionId) ?? new Set();
209
+ listeners.add(listener);
210
+ this.listeners.set(sessionId, listeners);
211
+ return () => {
212
+ const current = this.listeners.get(sessionId);
213
+ if (!current)
214
+ return;
215
+ current.delete(listener);
216
+ if (current.size === 0) {
217
+ this.listeners.delete(sessionId);
218
+ }
219
+ };
220
+ }
202
221
  sendInput(sessionId, input) {
203
222
  const record = this.records.get(sessionId);
204
223
  if (!record || !record.process) {
@@ -234,6 +253,7 @@ class CodingSessionManager {
234
253
  meta: { id: sessionId },
235
254
  });
236
255
  this.persistState();
256
+ this.notifyListeners(sessionId, { kind: "killed", session: cloneSession(record.session) });
237
257
  return { ok: true, message: `killed ${sessionId}` };
238
258
  }
239
259
  checkStalls(nowMs = Date.now()) {
@@ -254,6 +274,7 @@ class CodingSessionManager {
254
274
  message: "coding session stalled",
255
275
  meta: { id: record.session.id, elapsedMs: elapsed },
256
276
  });
277
+ this.notifyListeners(record.session.id, { kind: "stalled", session: cloneSession(record.session) });
257
278
  if (record.request.autoRestartOnStall !== false && record.session.restartCount < this.maxRestarts) {
258
279
  this.restartSession(record, "stalled");
259
280
  }
@@ -297,18 +318,23 @@ class CodingSessionManager {
297
318
  }
298
319
  onOutput(record, text, stream) {
299
320
  record.session.lastActivityAt = this.nowIso();
321
+ let updateKind = "progress";
300
322
  if (stream === "stdout") {
301
323
  record.stdoutTail = appendTail(record.stdoutTail, text);
324
+ record.session.stdoutTail = record.stdoutTail;
302
325
  }
303
326
  else {
304
327
  record.stderrTail = appendTail(record.stderrTail, text);
328
+ record.session.stderrTail = record.stderrTail;
305
329
  }
306
330
  if (text.includes("status: NEEDS_REVIEW") || text.includes("❌ blocked")) {
307
331
  record.session.status = "waiting_input";
332
+ updateKind = "waiting_input";
308
333
  }
309
334
  if (text.includes("✅ all units complete")) {
310
335
  record.session.status = "completed";
311
336
  record.session.endedAt = this.nowIso();
337
+ updateKind = "completed";
312
338
  }
313
339
  (0, runtime_1.emitNervesEvent)({
314
340
  component: "repertoire",
@@ -317,6 +343,12 @@ class CodingSessionManager {
317
343
  meta: { id: record.session.id, status: record.session.status },
318
344
  });
319
345
  this.persistState();
346
+ this.notifyListeners(record.session.id, {
347
+ kind: updateKind,
348
+ session: cloneSession(record.session),
349
+ stream,
350
+ text,
351
+ });
320
352
  }
321
353
  onExit(record, code, signal) {
322
354
  if (!record.process)
@@ -334,6 +366,7 @@ class CodingSessionManager {
334
366
  record.session.status = "completed";
335
367
  record.session.endedAt = this.nowIso();
336
368
  this.persistState();
369
+ this.notifyListeners(record.session.id, { kind: "completed", session: cloneSession(record.session) });
337
370
  return;
338
371
  }
339
372
  if (record.request.autoRestartOnCrash !== false && record.session.restartCount < this.maxRestarts) {
@@ -351,6 +384,7 @@ class CodingSessionManager {
351
384
  meta: { id: record.session.id, code, signal, command: record.command },
352
385
  });
353
386
  this.persistState();
387
+ this.notifyListeners(record.session.id, { kind: "failed", session: cloneSession(record.session) });
354
388
  }
355
389
  restartSession(record, reason) {
356
390
  const replacement = normalizeSpawnResult(this.spawnProcess(record.request));
@@ -359,6 +393,8 @@ class CodingSessionManager {
359
393
  record.args = [...replacement.args];
360
394
  record.stdoutTail = "";
361
395
  record.stderrTail = "";
396
+ record.session.stdoutTail = "";
397
+ record.session.stderrTail = "";
362
398
  record.session.pid = replacement.process.pid ?? null;
363
399
  record.session.restartCount += 1;
364
400
  record.session.status = "running";
@@ -375,6 +411,26 @@ class CodingSessionManager {
375
411
  });
376
412
  this.persistState();
377
413
  }
414
+ notifyListeners(sessionId, update) {
415
+ const listeners = this.listeners.get(sessionId);
416
+ if (!listeners || listeners.size === 0)
417
+ return;
418
+ for (const listener of listeners) {
419
+ void Promise.resolve(listener(update)).catch((error) => {
420
+ (0, runtime_1.emitNervesEvent)({
421
+ level: "warn",
422
+ component: "repertoire",
423
+ event: "repertoire.coding_feedback_listener_error",
424
+ message: "coding session listener failed",
425
+ meta: {
426
+ sessionId,
427
+ kind: update.kind,
428
+ reason: error instanceof Error ? error.message : String(error),
429
+ },
430
+ });
431
+ });
432
+ }
433
+ }
378
434
  loadPersistedState() {
379
435
  if (!this.existsSync(this.stateFilePath)) {
380
436
  return;
@@ -433,6 +489,8 @@ class CodingSessionManager {
433
489
  ...session,
434
490
  taskRef: session.taskRef ?? normalizedRequest.taskRef,
435
491
  failure: session.failure ?? null,
492
+ stdoutTail: session.stdoutTail ?? session.failure?.stdoutTail ?? "",
493
+ stderrTail: session.stderrTail ?? session.failure?.stderrTail ?? "",
436
494
  };
437
495
  if (typeof normalizedSession.pid === "number") {
438
496
  const alive = this.pidAlive(normalizedSession.pid);
@@ -451,8 +509,8 @@ class CodingSessionManager {
451
509
  process: null,
452
510
  command: normalizedSession.failure?.command ?? "restored",
453
511
  args: normalizedSession.failure ? [...normalizedSession.failure.args] : [],
454
- stdoutTail: normalizedSession.failure?.stdoutTail ?? "",
455
- stderrTail: normalizedSession.failure?.stderrTail ?? "",
512
+ stdoutTail: normalizedSession.stdoutTail,
513
+ stderrTail: normalizedSession.stderrTail,
456
514
  });
457
515
  this.sequence = Math.max(this.sequence, extractSequence(normalizedSession.id));
458
516
  }
@@ -43,11 +43,11 @@ function buildCommandArgs(runner, workdir) {
43
43
  command: "claude",
44
44
  args: [
45
45
  "-p",
46
+ "--verbose",
47
+ "--no-session-persistence",
46
48
  "--dangerously-skip-permissions",
47
49
  "--add-dir",
48
50
  workdir,
49
- "--input-format",
50
- "stream-json",
51
51
  "--output-format",
52
52
  "stream-json",
53
53
  ],
@@ -91,7 +91,7 @@ function spawnCodingProcess(request, deps = {}) {
91
91
  cwd: request.workdir,
92
92
  stdio: ["pipe", "pipe", "pipe"],
93
93
  });
94
- proc.stdin.write(`${prompt}\n`);
94
+ proc.stdin.end(`${prompt}\n`);
95
95
  (0, runtime_1.emitNervesEvent)({
96
96
  component: "repertoire",
97
97
  event: "repertoire.coding_spawn_end",
@@ -61,6 +61,20 @@ const codingStatusTool = {
61
61
  },
62
62
  },
63
63
  };
64
+ const codingTailTool = {
65
+ type: "function",
66
+ function: {
67
+ name: "coding_tail",
68
+ description: "show recent stdout/stderr tail for a coding session in a readable format",
69
+ parameters: {
70
+ type: "object",
71
+ properties: {
72
+ sessionId: { type: "string" },
73
+ },
74
+ required: ["sessionId"],
75
+ },
76
+ },
77
+ };
64
78
  const codingSendInputTool = {
65
79
  type: "function",
66
80
  function: {
@@ -93,7 +107,7 @@ const codingKillTool = {
93
107
  exports.codingToolDefinitions = [
94
108
  {
95
109
  tool: codingSpawnTool,
96
- handler: async (args) => {
110
+ handler: async (args, ctx) => {
97
111
  emitCodingToolEvent("coding_spawn");
98
112
  const rawRunner = requireArg(args, "runner");
99
113
  if (!rawRunner)
@@ -122,7 +136,19 @@ exports.codingToolDefinitions = [
122
136
  const stateFile = optionalArg(args, "stateFile");
123
137
  if (stateFile)
124
138
  request.stateFile = stateFile;
125
- const session = await (0, index_1.getCodingSessionManager)().spawnSession(request);
139
+ const manager = (0, index_1.getCodingSessionManager)();
140
+ const session = await manager.spawnSession(request);
141
+ if (args.runner === "codex" && args.taskRef) {
142
+ (0, runtime_1.emitNervesEvent)({
143
+ component: "repertoire",
144
+ event: "repertoire.coding_codex_spawned",
145
+ message: "spawned codex coding session",
146
+ meta: { sessionId: session.id, taskRef: args.taskRef },
147
+ });
148
+ }
149
+ if (ctx?.codingFeedback) {
150
+ (0, index_1.attachCodingSessionFeedback)(manager, session, ctx.codingFeedback);
151
+ }
126
152
  return JSON.stringify(session);
127
153
  },
128
154
  },
@@ -141,6 +167,19 @@ exports.codingToolDefinitions = [
141
167
  return JSON.stringify(session);
142
168
  },
143
169
  },
170
+ {
171
+ tool: codingTailTool,
172
+ handler: (args) => {
173
+ emitCodingToolEvent("coding_tail");
174
+ const sessionId = requireArg(args, "sessionId");
175
+ if (!sessionId)
176
+ return "sessionId is required";
177
+ const session = (0, index_1.getCodingSessionManager)().getSession(sessionId);
178
+ if (!session)
179
+ return `session not found: ${sessionId}`;
180
+ return (0, index_1.formatCodingTail)(session);
181
+ },
182
+ },
144
183
  {
145
184
  tool: codingSendInputTool,
146
185
  handler: (args) => {
@@ -35,6 +35,12 @@
35
35
  "description": "Delete a work item (moves to recycle bin)",
36
36
  "params": "destroy (boolean, permanently delete)"
37
37
  },
38
+ {
39
+ "path": "/{project}/_apis/wit/workitemtypes",
40
+ "method": "GET",
41
+ "description": "List all work item types available in a project (Bug, Task, Epic, User Story, etc.)",
42
+ "params": ""
43
+ },
38
44
  {
39
45
  "path": "/_apis/git/repositories",
40
46
  "method": "GET",
@@ -118,5 +124,187 @@
118
124
  "method": "GET",
119
125
  "description": "List saved work item queries (shared and personal)",
120
126
  "params": "$depth, $expand"
127
+ },
128
+ {
129
+ "path": "/_apis/groupentitlements?api-version=7.1",
130
+ "method": "GET",
131
+ "host": "vsaex.dev.azure.com",
132
+ "description": "List group entitlements (group rules that auto-assign licenses). Use host vsaex.dev.azure.com.",
133
+ "params": ""
134
+ },
135
+ {
136
+ "path": "/_apis/groupentitlements?api-version=7.1",
137
+ "method": "POST",
138
+ "host": "vsaex.dev.azure.com",
139
+ "description": "Create a group entitlement rule — maps an AAD group to an access level (e.g. Basic) and project membership. All members of the AAD group automatically get the specified license. Use host vsaex.dev.azure.com. This is the best way to bulk-provision users.",
140
+ "params": "body: { group: { origin: 'aad', originId: '<AAD-group-object-id>', subjectKind: 'group' }, licenseRule: { licensingSource: 'account', accountLicenseType: 'express', licenseDisplayName: 'Basic' }, projectEntitlements: [{ group: { groupType: 'projectContributor' }, projectRef: { id: '<project-id>' } }] }"
141
+ },
142
+ {
143
+ "path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
144
+ "method": "GET",
145
+ "host": "vsaex.dev.azure.com",
146
+ "description": "Get a specific group entitlement by ID. Use host vsaex.dev.azure.com.",
147
+ "params": ""
148
+ },
149
+ {
150
+ "path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
151
+ "method": "PATCH",
152
+ "host": "vsaex.dev.azure.com",
153
+ "description": "Update a group entitlement (change license rule, project access). Use host vsaex.dev.azure.com.",
154
+ "params": "JSON Patch array: [{op, path, value}]"
155
+ },
156
+ {
157
+ "path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
158
+ "method": "DELETE",
159
+ "host": "vsaex.dev.azure.com",
160
+ "description": "Delete a group entitlement rule. Use host vsaex.dev.azure.com.",
161
+ "params": ""
162
+ },
163
+ {
164
+ "path": "/_apis/memberentitlementmanagement/memberentitlements?api-version=7.1-preview.3",
165
+ "method": "GET",
166
+ "host": "vsapm.dev.azure.com",
167
+ "description": "List individual member entitlements (users and their access levels). Use host vsapm.dev.azure.com. For bulk provisioning, prefer the Group Entitlements API on vsaex.dev.azure.com instead.",
168
+ "params": "$top, $skip, $filter, $orderBy, $select"
169
+ },
170
+ {
171
+ "path": "/_apis/memberentitlementmanagement/memberentitlements?api-version=7.1-preview.3",
172
+ "method": "POST",
173
+ "host": "vsapm.dev.azure.com",
174
+ "description": "Add a single member entitlement. Use host vsapm.dev.azure.com. For bulk provisioning, prefer the Group Entitlements API on vsaex.dev.azure.com instead.",
175
+ "params": "body: { accessLevel: { accountLicenseType: 'express'|'stakeholder', licensingSource: 'account' }, user: { principalName: 'user@domain.com', subjectKind: 'user' }, projectEntitlements: [{ group: { groupType: 'projectContributor' }, projectRef: { id: projectId } }] }"
176
+ },
177
+ {
178
+ "path": "/_apis/memberentitlementmanagement/memberentitlements/{memberId}?api-version=7.1-preview.3",
179
+ "method": "PATCH",
180
+ "host": "vsapm.dev.azure.com",
181
+ "description": "Update a member entitlement (change access level, project access). Use host vsapm.dev.azure.com.",
182
+ "params": "JSON Patch array: [{op, path, value}]"
183
+ },
184
+ {
185
+ "path": "/_apis/memberentitlementmanagement/memberentitlements/{memberId}?api-version=7.1-preview.3",
186
+ "method": "DELETE",
187
+ "host": "vsapm.dev.azure.com",
188
+ "description": "Remove a member entitlement (revoke user access). Use host vsapm.dev.azure.com.",
189
+ "params": ""
190
+ },
191
+ {
192
+ "path": "/_apis/graph/users?api-version=7.1-preview.1",
193
+ "method": "GET",
194
+ "host": "vssps.dev.azure.com",
195
+ "description": "List users in the organization (Graph API). Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
196
+ "params": "subjectTypes (aad, msa, etc.), continuationToken"
197
+ },
198
+ {
199
+ "path": "/_apis/graph/groups?api-version=7.1-preview.1",
200
+ "method": "GET",
201
+ "host": "vssps.dev.azure.com",
202
+ "description": "List groups in the organization. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
203
+ "params": "subjectTypes, continuationToken"
204
+ },
205
+ {
206
+ "path": "/_apis/graph/memberships/{subjectDescriptor}?api-version=7.1-preview.1",
207
+ "method": "GET",
208
+ "host": "vssps.dev.azure.com",
209
+ "description": "List group memberships for a user or group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
210
+ "params": "direction (up = groups user belongs to, down = members of group)"
211
+ },
212
+ {
213
+ "path": "/_apis/graph/memberships/{subjectDescriptor}/{containerDescriptor}?api-version=7.1-preview.1",
214
+ "method": "PUT",
215
+ "host": "vssps.dev.azure.com",
216
+ "description": "Add a user to a group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
217
+ "params": ""
218
+ },
219
+ {
220
+ "path": "/_apis/graph/memberships/{subjectDescriptor}/{containerDescriptor}?api-version=7.1-preview.1",
221
+ "method": "DELETE",
222
+ "host": "vssps.dev.azure.com",
223
+ "description": "Remove a user from a group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
224
+ "params": ""
225
+ },
226
+ {
227
+ "path": "/_apis/projects/{projectId}/teams",
228
+ "method": "GET",
229
+ "description": "List teams in a project",
230
+ "params": "$top, $skip"
231
+ },
232
+ {
233
+ "path": "/_apis/projects/{projectId}/teams/{teamId}",
234
+ "method": "GET",
235
+ "description": "Get a specific team by ID",
236
+ "params": ""
237
+ },
238
+ {
239
+ "path": "/_apis/projects/{projectId}/teams",
240
+ "method": "POST",
241
+ "description": "Create a new team in a project",
242
+ "params": "name, description"
243
+ },
244
+ {
245
+ "path": "/_apis/projects/{projectId}/teams/{teamId}/members",
246
+ "method": "GET",
247
+ "description": "List members of a team",
248
+ "params": "$top, $skip"
249
+ },
250
+ {
251
+ "path": "/{project}/{team}/_apis/work/teamsettings/iterations",
252
+ "method": "GET",
253
+ "description": "List iterations (sprints) for a team",
254
+ "params": "$timeframe (current, past, future)"
255
+ },
256
+ {
257
+ "path": "/{project}/{team}/_apis/work/teamsettings/iterations",
258
+ "method": "POST",
259
+ "description": "Add an iteration to a team's sprint schedule",
260
+ "params": "id (iteration node ID)"
261
+ },
262
+ {
263
+ "path": "/{project}/{team}/_apis/work/teamsettings/iterations/{iterationId}",
264
+ "method": "DELETE",
265
+ "description": "Remove an iteration from a team's sprint schedule",
266
+ "params": ""
267
+ },
268
+ {
269
+ "path": "/{project}/_apis/wit/classificationnodes/iterations",
270
+ "method": "GET",
271
+ "description": "List iteration path tree (project-level iteration nodes)",
272
+ "params": "$depth"
273
+ },
274
+ {
275
+ "path": "/{project}/_apis/wit/classificationnodes/iterations",
276
+ "method": "POST",
277
+ "description": "Create a new iteration node (sprint)",
278
+ "params": "name, attributes: { startDate, finishDate }"
279
+ },
280
+ {
281
+ "path": "/{project}/_apis/wit/classificationnodes/areas",
282
+ "method": "GET",
283
+ "description": "List area path tree (project-level area nodes)",
284
+ "params": "$depth"
285
+ },
286
+ {
287
+ "path": "/{project}/_apis/wit/classificationnodes/areas",
288
+ "method": "POST",
289
+ "description": "Create a new area path node",
290
+ "params": "name"
291
+ },
292
+ {
293
+ "path": "/{project}/_apis/wit/classificationnodes/{structureGroup}/{path}",
294
+ "method": "DELETE",
295
+ "description": "Delete a classification node (area or iteration). structureGroup is 'areas' or 'iterations'.",
296
+ "params": "$reclassifyId (move items to this node before deleting)"
297
+ },
298
+ {
299
+ "path": "/_apis/hooks/subscriptions",
300
+ "method": "GET",
301
+ "description": "List service hook subscriptions (webhooks for events)",
302
+ "params": ""
303
+ },
304
+ {
305
+ "path": "/_apis/hooks/subscriptions",
306
+ "method": "POST",
307
+ "description": "Create a service hook subscription (webhook)",
308
+ "params": "publisherId, eventType, consumerId, consumerActionId, publisherInputs, consumerInputs"
121
309
  }
122
310
  ]
@@ -59,6 +59,12 @@ function activeSessionLines(tasks) {
59
59
  });
60
60
  return active.map((task) => task.stem).sort();
61
61
  }
62
+ function activeBridgeLines(tasks) {
63
+ return tasks
64
+ .filter((task) => typeof task.frontmatter.active_bridge === "string" && String(task.frontmatter.active_bridge).trim())
65
+ .map((task) => `${task.stem} -> ${String(task.frontmatter.active_bridge).trim()}`)
66
+ .sort();
67
+ }
62
68
  function actionRequired(index, byStatus) {
63
69
  const actions = [...index.parseErrors, ...index.invalidFilenames.map((filePath) => `bad filename: ${filePath}`)];
64
70
  if (byStatus.blocked.length > 0) {
@@ -99,6 +105,11 @@ function buildTaskBoard(index) {
99
105
  fullLines.push("## active sessions");
100
106
  fullLines.push(active.map((line) => `- ${line}`).join("\n"));
101
107
  }
108
+ const activeBridges = activeBridgeLines(index.tasks);
109
+ if (activeBridges.length > 0) {
110
+ fullLines.push("## active bridges");
111
+ fullLines.push(activeBridges.map((line) => `- ${line}`).join("\n"));
112
+ }
102
113
  return {
103
114
  compact,
104
115
  full: fullLines.join("\n\n"),
@@ -106,6 +117,7 @@ function buildTaskBoard(index) {
106
117
  actionRequired: actionRequired(index, byStatus),
107
118
  unresolvedDependencies: unresolved,
108
119
  activeSessions: active,
120
+ activeBridges,
109
121
  };
110
122
  }
111
123
  function boardStatus(board, status) {