@ouro.bot/cli 0.1.0-alpha.9 → 0.1.0-alpha.91

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 (128) 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 +536 -0
  7. package/dist/heart/active-work.js +251 -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/commitments.js +109 -0
  12. package/dist/heart/config.js +68 -23
  13. package/dist/heart/core.js +452 -93
  14. package/dist/heart/cross-chat-delivery.js +146 -0
  15. package/dist/heart/daemon/agent-discovery.js +81 -0
  16. package/dist/heart/daemon/auth-flow.js +430 -0
  17. package/dist/heart/daemon/daemon-cli.js +1738 -269
  18. package/dist/heart/daemon/daemon-entry.js +55 -6
  19. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  20. package/dist/heart/daemon/daemon.js +216 -10
  21. package/dist/heart/daemon/hatch-animation.js +10 -3
  22. package/dist/heart/daemon/hatch-flow.js +7 -82
  23. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  24. package/dist/heart/daemon/launchd.js +159 -0
  25. package/dist/heart/daemon/log-tailer.js +4 -3
  26. package/dist/heart/daemon/message-router.js +17 -8
  27. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  28. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  29. package/dist/heart/daemon/ouro-entry.js +0 -0
  30. package/dist/heart/daemon/ouro-path-installer.js +260 -0
  31. package/dist/heart/daemon/ouro-uti.js +11 -2
  32. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  33. package/dist/heart/daemon/process-manager.js +14 -1
  34. package/dist/heart/daemon/run-hooks.js +37 -0
  35. package/dist/heart/daemon/runtime-logging.js +58 -15
  36. package/dist/heart/daemon/runtime-metadata.js +219 -0
  37. package/dist/heart/daemon/runtime-mode.js +67 -0
  38. package/dist/heart/daemon/sense-manager.js +307 -0
  39. package/dist/heart/daemon/skill-management-installer.js +94 -0
  40. package/dist/heart/daemon/socket-client.js +202 -0
  41. package/dist/heart/daemon/specialist-orchestrator.js +53 -84
  42. package/dist/heart/daemon/specialist-prompt.js +63 -11
  43. package/dist/heart/daemon/specialist-tools.js +211 -60
  44. package/dist/heart/daemon/staged-restart.js +114 -0
  45. package/dist/heart/daemon/thoughts.js +507 -0
  46. package/dist/heart/daemon/update-checker.js +111 -0
  47. package/dist/heart/daemon/update-hooks.js +138 -0
  48. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  49. package/dist/heart/delegation.js +62 -0
  50. package/dist/heart/identity.js +126 -21
  51. package/dist/heart/kicks.js +1 -19
  52. package/dist/heart/model-capabilities.js +48 -0
  53. package/dist/heart/obligations.js +191 -0
  54. package/dist/heart/progress-story.js +42 -0
  55. package/dist/heart/providers/anthropic.js +74 -9
  56. package/dist/heart/providers/azure.js +86 -7
  57. package/dist/heart/providers/github-copilot.js +149 -0
  58. package/dist/heart/providers/minimax.js +4 -0
  59. package/dist/heart/providers/openai-codex.js +12 -3
  60. package/dist/heart/safe-workspace.js +362 -0
  61. package/dist/heart/sense-truth.js +61 -0
  62. package/dist/heart/session-activity.js +169 -0
  63. package/dist/heart/session-recall.js +116 -0
  64. package/dist/heart/streaming.js +100 -22
  65. package/dist/heart/target-resolution.js +123 -0
  66. package/dist/heart/turn-coordinator.js +28 -0
  67. package/dist/mind/associative-recall.js +14 -2
  68. package/dist/mind/bundle-manifest.js +70 -0
  69. package/dist/mind/context.js +57 -11
  70. package/dist/mind/first-impressions.js +16 -2
  71. package/dist/mind/friends/channel.js +35 -0
  72. package/dist/mind/friends/group-context.js +144 -0
  73. package/dist/mind/friends/store-file.js +19 -0
  74. package/dist/mind/friends/trust-explanation.js +74 -0
  75. package/dist/mind/friends/types.js +8 -0
  76. package/dist/mind/memory.js +27 -26
  77. package/dist/mind/obligation-steering.js +31 -0
  78. package/dist/mind/pending.js +76 -9
  79. package/dist/mind/phrases.js +1 -0
  80. package/dist/mind/prompt.js +467 -77
  81. package/dist/mind/token-estimate.js +8 -12
  82. package/dist/nerves/cli-logging.js +15 -2
  83. package/dist/nerves/coverage/run-artifacts.js +1 -1
  84. package/dist/nerves/index.js +12 -0
  85. package/dist/repertoire/ado-client.js +4 -2
  86. package/dist/repertoire/coding/feedback.js +180 -0
  87. package/dist/repertoire/coding/index.js +4 -1
  88. package/dist/repertoire/coding/manager.js +69 -4
  89. package/dist/repertoire/coding/spawner.js +21 -3
  90. package/dist/repertoire/coding/tools.js +105 -2
  91. package/dist/repertoire/data/ado-endpoints.json +188 -0
  92. package/dist/repertoire/guardrails.js +290 -0
  93. package/dist/repertoire/mcp-client.js +254 -0
  94. package/dist/repertoire/mcp-manager.js +195 -0
  95. package/dist/repertoire/skills.js +3 -26
  96. package/dist/repertoire/tasks/board.js +12 -0
  97. package/dist/repertoire/tasks/index.js +23 -9
  98. package/dist/repertoire/tasks/transitions.js +1 -2
  99. package/dist/repertoire/tools-base.js +714 -249
  100. package/dist/repertoire/tools-bluebubbles.js +93 -0
  101. package/dist/repertoire/tools-teams.js +58 -25
  102. package/dist/repertoire/tools.js +106 -53
  103. package/dist/senses/bluebubbles-client.js +210 -5
  104. package/dist/senses/bluebubbles-entry.js +2 -0
  105. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  106. package/dist/senses/bluebubbles-media.js +339 -0
  107. package/dist/senses/bluebubbles-model.js +12 -4
  108. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  109. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  110. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  111. package/dist/senses/bluebubbles.js +894 -45
  112. package/dist/senses/cli-layout.js +187 -0
  113. package/dist/senses/cli.js +400 -164
  114. package/dist/senses/continuity.js +94 -0
  115. package/dist/senses/debug-activity.js +154 -0
  116. package/dist/senses/inner-dialog-worker.js +47 -18
  117. package/dist/senses/inner-dialog.js +377 -83
  118. package/dist/senses/pipeline.js +307 -0
  119. package/dist/senses/teams.js +573 -129
  120. package/dist/senses/trust-gate.js +112 -2
  121. package/package.json +14 -3
  122. package/subagents/README.md +4 -70
  123. package/dist/heart/daemon/specialist-session.js +0 -142
  124. package/dist/heart/daemon/subagent-installer.js +0 -125
  125. package/dist/inner-worker-entry.js +0 -4
  126. package/subagents/work-doer.md +0 -233
  127. package/subagents/work-merger.md +0 -624
  128. package/subagents/work-planner.md +0 -373
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.__internal = void 0;
4
3
  exports.estimateTokensForMessage = estimateTokensForMessage;
5
4
  exports.estimateTokensForMessages = estimateTokensForMessages;
6
5
  const runtime_1 = require("../nerves/runtime");
@@ -53,7 +52,7 @@ function countCharsInContent(content) {
53
52
  return c.text.length;
54
53
  if (typeof c.content === "string")
55
54
  return c.content.length;
56
- return safeStringify(content).length;
55
+ return safeStringify(c).length;
57
56
  }
58
57
  return 0;
59
58
  }
@@ -70,12 +69,13 @@ function countCharsInToolCalls(toolCalls) {
70
69
  if (typeof t.type === "string")
71
70
  total += t.type.length;
72
71
  if (t.function && typeof t.function === "object") {
73
- if (typeof t.function.name === "string")
74
- total += t.function.name.length;
75
- if (typeof t.function.arguments === "string")
76
- total += t.function.arguments.length;
77
- else if (t.function.arguments != null)
78
- total += safeStringify(t.function.arguments).length;
72
+ const fn = t.function;
73
+ if (typeof fn.name === "string")
74
+ total += fn.name.length;
75
+ if (typeof fn.arguments === "string")
76
+ total += fn.arguments.length;
77
+ else if (fn.arguments != null)
78
+ total += safeStringify(fn.arguments).length;
79
79
  }
80
80
  }
81
81
  return total;
@@ -113,7 +113,3 @@ function estimateTokensForMessages(msgs) {
113
113
  total += estimateTokensForMessage(msg);
114
114
  return total;
115
115
  }
116
- exports.__internal = {
117
- CHARS_PER_TOKEN,
118
- PER_MESSAGE_OVERHEAD_TOKENS,
119
- };
@@ -5,20 +5,33 @@ const config_1 = require("../heart/config");
5
5
  const nerves_1 = require("../nerves");
6
6
  const runtime_1 = require("./runtime");
7
7
  const runtime_2 = require("./runtime");
8
+ const LEVEL_PRIORITY = { debug: 10, info: 20, warn: 30, error: 40 };
9
+ /** Wrap a sink so it only receives events at or above the given level. */
10
+ /* v8 ignore start -- internal filter plumbing, exercised via integration @preserve */
11
+ function filterSink(sink, minLevel) {
12
+ const minPriority = LEVEL_PRIORITY[minLevel] ?? 0;
13
+ return (entry) => {
14
+ if ((LEVEL_PRIORITY[entry.level] ?? 0) >= minPriority)
15
+ sink(entry);
16
+ };
17
+ }
8
18
  function resolveCliSinks(sinks) {
9
19
  const requested = sinks && sinks.length > 0 ? sinks : ["terminal", "ndjson"];
10
20
  return [...new Set(requested)];
11
21
  }
12
22
  function configureCliRuntimeLogger(_friendId, options = {}) {
13
23
  const sinkKinds = resolveCliSinks(options.sinks);
24
+ const level = options.level ?? "info";
14
25
  const sinks = sinkKinds.map((sinkKind) => {
15
26
  if (sinkKind === "terminal") {
16
- return (0, nerves_1.createTerminalSink)();
27
+ // Terminal only shows warnings and errors — INFO is too noisy
28
+ // for an interactive session. Full detail goes to the ndjson file.
29
+ return filterSink((0, nerves_1.createTerminalSink)(), "warn");
17
30
  }
18
31
  return (0, nerves_1.createNdjsonFileSink)((0, config_1.logPath)("cli", "runtime"));
19
32
  });
20
33
  const logger = (0, nerves_1.createLogger)({
21
- level: options.level ?? "info",
34
+ level,
22
35
  sinks,
23
36
  });
24
37
  (0, runtime_2.setRuntimeLogger)(logger);
@@ -14,7 +14,7 @@ const path_1 = require("path");
14
14
  const os_1 = require("os");
15
15
  exports.REPO_SLUG = "ouroboros-agent-harness";
16
16
  function getTestRunsRoot(repoSlug = exports.REPO_SLUG) {
17
- return (0, path_1.join)((0, os_1.homedir)(), ".agentstate", "test-runs", repoSlug);
17
+ return (0, path_1.join)((0, os_1.tmpdir)(), "ouroboros-test-runs", repoSlug);
18
18
  }
19
19
  function createRunId(now = new Date()) {
20
20
  return now.toISOString().replace(/[:.]/g, "-");
@@ -4,6 +4,7 @@ exports.createTraceId = createTraceId;
4
4
  exports.ensureTraceId = ensureTraceId;
5
5
  exports.createFanoutSink = createFanoutSink;
6
6
  exports.formatTerminalEntry = formatTerminalEntry;
7
+ exports.registerSpinnerHooks = registerSpinnerHooks;
7
8
  exports.createTerminalSink = createTerminalSink;
8
9
  exports.createStderrSink = createStderrSink;
9
10
  exports.createNdjsonFileSink = createNdjsonFileSink;
@@ -73,15 +74,26 @@ function formatTerminalEntry(entry) {
73
74
  const level = entry.level.toUpperCase();
74
75
  return `${formatTerminalTime(entry.ts)} ${level} [${entry.component}] ${entry.message}${formatTerminalMeta(entry.meta)}`;
75
76
  }
77
+ // Spinner coordination: the CLI sense registers these so log output
78
+ // doesn't interleave with the active spinner animation.
79
+ let _pauseSpinner = null;
80
+ let _resumeSpinner = null;
81
+ function registerSpinnerHooks(pause, resume) {
82
+ _pauseSpinner = pause;
83
+ _resumeSpinner = resume;
84
+ }
76
85
  function createTerminalSink(write = (chunk) => process.stderr.write(chunk), colorize = true) {
77
86
  return (entry) => {
87
+ _pauseSpinner?.();
78
88
  const line = formatTerminalEntry(entry);
79
89
  if (!colorize) {
80
90
  write(`${line}\n`);
91
+ _resumeSpinner?.();
81
92
  return;
82
93
  }
83
94
  const prefix = LEVEL_COLORS[entry.level];
84
95
  write(`${prefix}${line}\x1b[0m\n`);
96
+ _resumeSpinner?.();
85
97
  };
86
98
  }
87
99
  function createStderrSink(write = (chunk) => process.stderr.write(chunk)) {
@@ -28,8 +28,10 @@ function resolveContentType(method, path) {
28
28
  : "application/json";
29
29
  }
30
30
  // Generic ADO API request. Returns response body as pretty-printed JSON string.
31
- async function adoRequest(token, method, org, path, body) {
31
+ // `host` overrides the base URL for non-standard APIs (e.g. "vsapm.dev.azure.com", "vssps.dev.azure.com").
32
+ async function adoRequest(token, method, org, path, body, host) {
32
33
  try {
34
+ const base = host ? `https://${host}/${org}` : `${ADO_BASE}/${org}`;
33
35
  (0, runtime_1.emitNervesEvent)({
34
36
  event: "client.request_start",
35
37
  component: "clients",
@@ -37,7 +39,7 @@ async function adoRequest(token, method, org, path, body) {
37
39
  meta: { client: "ado", method, org, path },
38
40
  });
39
41
  const fullPath = ensureApiVersion(path);
40
- const url = `${ADO_BASE}/${org}${fullPath}`;
42
+ const url = `${base}${fullPath}`;
41
43
  const contentType = resolveContentType(method, path);
42
44
  const opts = {
43
45
  method,
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatCodingTail = formatCodingTail;
4
+ exports.attachCodingSessionFeedback = attachCodingSessionFeedback;
5
+ const identity_1 = require("../../heart/identity");
6
+ const obligations_1 = require("../../heart/obligations");
7
+ const runtime_1 = require("../../nerves/runtime");
8
+ const TERMINAL_UPDATE_KINDS = new Set(["completed", "failed", "killed"]);
9
+ function clip(text, maxLength = 280) {
10
+ const trimmed = text.trim();
11
+ if (trimmed.length <= maxLength)
12
+ return trimmed;
13
+ return `${trimmed.slice(0, maxLength - 3)}...`;
14
+ }
15
+ function isNoiseLine(line) {
16
+ return (/^-+$/.test(line)
17
+ || /^Reading prompt from stdin/i.test(line)
18
+ || /^OpenAI Codex v/i.test(line)
19
+ || /^workdir:/i.test(line)
20
+ || /^model:/i.test(line)
21
+ || /^provider:/i.test(line)
22
+ || /^approval:/i.test(line)
23
+ || /^sandbox:/i.test(line)
24
+ || /^reasoning effort:/i.test(line)
25
+ || /^reasoning summaries:/i.test(line)
26
+ || /^session id:/i.test(line)
27
+ || /^mcp startup:/i.test(line)
28
+ || /^tokens used$/i.test(line)
29
+ || /^\d{1,3}(,\d{3})*$/.test(line)
30
+ || /^\d{4}-\d{2}-\d{2}T.*\bWARN\b/.test(line)
31
+ || line === "user"
32
+ || line === "codex");
33
+ }
34
+ function lastMeaningfulLine(text) {
35
+ if (!text)
36
+ return null;
37
+ const lines = text
38
+ .split(/\r?\n/)
39
+ .map((line) => line.trim())
40
+ .filter(Boolean)
41
+ .filter((line) => !isNoiseLine(line));
42
+ if (lines.length === 0)
43
+ return null;
44
+ return clip(lines.at(-1));
45
+ }
46
+ function formatSessionLabel(session) {
47
+ const origin = session.originSession
48
+ ? ` for ${session.originSession.channel}/${session.originSession.key}`
49
+ : "";
50
+ return `${session.runner} ${session.id}${origin}`;
51
+ }
52
+ function isSafeProgressSnippet(snippet) {
53
+ const wordCount = snippet.split(/\s+/).filter(Boolean).length;
54
+ return (snippet.length <= 80
55
+ && wordCount <= 8
56
+ && !snippet.includes(":")
57
+ && !snippet.startsWith("**")
58
+ && !/^Respond with\b/i.test(snippet)
59
+ && !/^Coding session metadata\b/i.test(snippet)
60
+ && !/^sessionId\b/i.test(snippet)
61
+ && !/^taskRef\b/i.test(snippet)
62
+ && !/^parentAgent\b/i.test(snippet));
63
+ }
64
+ function pickUpdateSnippet(update) {
65
+ return (lastMeaningfulLine(update.text)
66
+ ?? lastMeaningfulLine(update.session.stderrTail)
67
+ ?? lastMeaningfulLine(update.session.stdoutTail));
68
+ }
69
+ function formatUpdateMessage(update) {
70
+ const label = formatSessionLabel(update.session);
71
+ const snippet = pickUpdateSnippet(update);
72
+ switch (update.kind) {
73
+ case "progress":
74
+ return snippet && isSafeProgressSnippet(snippet) ? `${label}: ${snippet}` : null;
75
+ case "waiting_input":
76
+ return snippet ? `${label} waiting: ${snippet}` : `${label} waiting`;
77
+ case "stalled":
78
+ return snippet ? `${label} stalled: ${snippet}` : `${label} stalled`;
79
+ case "completed":
80
+ return snippet ? `${label} completed: ${snippet}` : `${label} completed`;
81
+ case "failed":
82
+ return snippet ? `${label} failed: ${snippet}` : `${label} failed`;
83
+ case "killed":
84
+ return `${label} killed`;
85
+ case "spawned":
86
+ return `${label} started`;
87
+ }
88
+ }
89
+ function obligationNoteFromUpdate(update) {
90
+ const snippet = pickUpdateSnippet(update);
91
+ switch (update.kind) {
92
+ case "spawned":
93
+ return update.session.originSession
94
+ ? `coding session started for ${update.session.originSession.channel}/${update.session.originSession.key}`
95
+ : "coding session started";
96
+ case "progress":
97
+ return snippet ? `coding session progress: ${snippet}` : null;
98
+ case "waiting_input":
99
+ return snippet ? `coding session waiting: ${snippet}` : "coding session waiting for input";
100
+ case "stalled":
101
+ return snippet ? `coding session stalled: ${snippet}` : "coding session stalled";
102
+ case "completed":
103
+ return snippet
104
+ ? `coding session completed: ${snippet}; merge/update still pending`
105
+ : "coding session completed; merge/update still pending";
106
+ case "failed":
107
+ return snippet ? `coding session failed: ${snippet}` : "coding session failed";
108
+ case "killed":
109
+ return "coding session killed";
110
+ }
111
+ }
112
+ function syncObligationFromUpdate(update) {
113
+ const obligationId = update.session.obligationId;
114
+ if (!obligationId)
115
+ return;
116
+ try {
117
+ (0, obligations_1.advanceObligation)((0, identity_1.getAgentRoot)(), obligationId, {
118
+ status: "investigating",
119
+ currentSurface: { kind: "coding", label: `${update.session.runner} ${update.session.id}` },
120
+ latestNote: obligationNoteFromUpdate(update) ?? undefined,
121
+ });
122
+ }
123
+ catch {
124
+ // Detached feedback should still reach the human even if obligation sync is unavailable.
125
+ }
126
+ }
127
+ function formatCodingTail(session) {
128
+ const stdout = session.stdoutTail.trim() || "(empty)";
129
+ const stderr = session.stderrTail.trim() || "(empty)";
130
+ return [
131
+ `sessionId: ${session.id}`,
132
+ `runner: ${session.runner}`,
133
+ `status: ${session.status}`,
134
+ `workdir: ${session.workdir}`,
135
+ "",
136
+ "[stdout]",
137
+ stdout,
138
+ "",
139
+ "[stderr]",
140
+ stderr,
141
+ ].join("\n");
142
+ }
143
+ function attachCodingSessionFeedback(manager, session, target) {
144
+ let lastMessage = "";
145
+ let closed = false;
146
+ let unsubscribe = () => { };
147
+ const sendMessage = (message) => {
148
+ if (closed || !message || message === lastMessage) {
149
+ return;
150
+ }
151
+ lastMessage = message;
152
+ void Promise.resolve(target.send(message)).catch((error) => {
153
+ (0, runtime_1.emitNervesEvent)({
154
+ level: "warn",
155
+ component: "repertoire",
156
+ event: "repertoire.coding_feedback_error",
157
+ message: "coding feedback transport failed",
158
+ meta: {
159
+ sessionId: session.id,
160
+ reason: error instanceof Error ? error.message : String(error),
161
+ },
162
+ });
163
+ });
164
+ };
165
+ const spawnedUpdate = { kind: "spawned", session };
166
+ syncObligationFromUpdate(spawnedUpdate);
167
+ sendMessage(formatUpdateMessage(spawnedUpdate));
168
+ unsubscribe = manager.subscribe(session.id, async (update) => {
169
+ syncObligationFromUpdate(update);
170
+ sendMessage(formatUpdateMessage(update));
171
+ if (TERMINAL_UPDATE_KINDS.has(update.kind)) {
172
+ closed = true;
173
+ unsubscribe();
174
+ }
175
+ });
176
+ return () => {
177
+ closed = true;
178
+ unsubscribe();
179
+ };
180
+ }
@@ -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,9 @@ function isPidAlive(pid) {
63
62
  function cloneSession(session) {
64
63
  return {
65
64
  ...session,
65
+ originSession: session.originSession ? { ...session.originSession } : undefined,
66
+ stdoutTail: session.stdoutTail,
67
+ stderrTail: session.stderrTail,
66
68
  failure: session.failure
67
69
  ? {
68
70
  ...session.failure,
@@ -115,6 +117,7 @@ function defaultFailureDiagnostics(code, signal, command, args, stdoutTail, stde
115
117
  }
116
118
  class CodingSessionManager {
117
119
  records = new Map();
120
+ listeners = new Map();
118
121
  spawnProcess;
119
122
  nowIso;
120
123
  maxRestarts;
@@ -155,9 +158,13 @@ class CodingSessionManager {
155
158
  runner: normalizedRequest.runner,
156
159
  workdir: normalizedRequest.workdir,
157
160
  taskRef: normalizedRequest.taskRef,
161
+ originSession: normalizedRequest.originSession ? { ...normalizedRequest.originSession } : undefined,
162
+ obligationId: normalizedRequest.obligationId,
158
163
  scopeFile: normalizedRequest.scopeFile,
159
164
  stateFile: normalizedRequest.stateFile,
160
165
  status: "spawning",
166
+ stdoutTail: "",
167
+ stderrTail: "",
161
168
  pid: null,
162
169
  startedAt: now,
163
170
  lastActivityAt: now,
@@ -188,6 +195,7 @@ class CodingSessionManager {
188
195
  meta: { id, runner: normalizedRequest.runner, pid: session.pid },
189
196
  });
190
197
  this.persistState();
198
+ this.notifyListeners(id, { kind: "spawned", session: cloneSession(session) });
191
199
  return cloneSession(session);
192
200
  }
193
201
  listSessions() {
@@ -199,6 +207,20 @@ class CodingSessionManager {
199
207
  const record = this.records.get(sessionId);
200
208
  return record ? cloneSession(record.session) : null;
201
209
  }
210
+ subscribe(sessionId, listener) {
211
+ const listeners = this.listeners.get(sessionId) ?? new Set();
212
+ listeners.add(listener);
213
+ this.listeners.set(sessionId, listeners);
214
+ return () => {
215
+ const current = this.listeners.get(sessionId);
216
+ if (!current)
217
+ return;
218
+ current.delete(listener);
219
+ if (current.size === 0) {
220
+ this.listeners.delete(sessionId);
221
+ }
222
+ };
223
+ }
202
224
  sendInput(sessionId, input) {
203
225
  const record = this.records.get(sessionId);
204
226
  if (!record || !record.process) {
@@ -234,6 +256,7 @@ class CodingSessionManager {
234
256
  meta: { id: sessionId },
235
257
  });
236
258
  this.persistState();
259
+ this.notifyListeners(sessionId, { kind: "killed", session: cloneSession(record.session) });
237
260
  return { ok: true, message: `killed ${sessionId}` };
238
261
  }
239
262
  checkStalls(nowMs = Date.now()) {
@@ -254,6 +277,7 @@ class CodingSessionManager {
254
277
  message: "coding session stalled",
255
278
  meta: { id: record.session.id, elapsedMs: elapsed },
256
279
  });
280
+ this.notifyListeners(record.session.id, { kind: "stalled", session: cloneSession(record.session) });
257
281
  if (record.request.autoRestartOnStall !== false && record.session.restartCount < this.maxRestarts) {
258
282
  this.restartSession(record, "stalled");
259
283
  }
@@ -297,18 +321,23 @@ class CodingSessionManager {
297
321
  }
298
322
  onOutput(record, text, stream) {
299
323
  record.session.lastActivityAt = this.nowIso();
324
+ let updateKind = "progress";
300
325
  if (stream === "stdout") {
301
326
  record.stdoutTail = appendTail(record.stdoutTail, text);
327
+ record.session.stdoutTail = record.stdoutTail;
302
328
  }
303
329
  else {
304
330
  record.stderrTail = appendTail(record.stderrTail, text);
331
+ record.session.stderrTail = record.stderrTail;
305
332
  }
306
333
  if (text.includes("status: NEEDS_REVIEW") || text.includes("❌ blocked")) {
307
334
  record.session.status = "waiting_input";
335
+ updateKind = "waiting_input";
308
336
  }
309
337
  if (text.includes("✅ all units complete")) {
310
338
  record.session.status = "completed";
311
339
  record.session.endedAt = this.nowIso();
340
+ updateKind = "completed";
312
341
  }
313
342
  (0, runtime_1.emitNervesEvent)({
314
343
  component: "repertoire",
@@ -317,6 +346,12 @@ class CodingSessionManager {
317
346
  meta: { id: record.session.id, status: record.session.status },
318
347
  });
319
348
  this.persistState();
349
+ this.notifyListeners(record.session.id, {
350
+ kind: updateKind,
351
+ session: cloneSession(record.session),
352
+ stream,
353
+ text,
354
+ });
320
355
  }
321
356
  onExit(record, code, signal) {
322
357
  if (!record.process)
@@ -334,6 +369,7 @@ class CodingSessionManager {
334
369
  record.session.status = "completed";
335
370
  record.session.endedAt = this.nowIso();
336
371
  this.persistState();
372
+ this.notifyListeners(record.session.id, { kind: "completed", session: cloneSession(record.session) });
337
373
  return;
338
374
  }
339
375
  if (record.request.autoRestartOnCrash !== false && record.session.restartCount < this.maxRestarts) {
@@ -351,6 +387,7 @@ class CodingSessionManager {
351
387
  meta: { id: record.session.id, code, signal, command: record.command },
352
388
  });
353
389
  this.persistState();
390
+ this.notifyListeners(record.session.id, { kind: "failed", session: cloneSession(record.session) });
354
391
  }
355
392
  restartSession(record, reason) {
356
393
  const replacement = normalizeSpawnResult(this.spawnProcess(record.request));
@@ -359,6 +396,8 @@ class CodingSessionManager {
359
396
  record.args = [...replacement.args];
360
397
  record.stdoutTail = "";
361
398
  record.stderrTail = "";
399
+ record.session.stdoutTail = "";
400
+ record.session.stderrTail = "";
362
401
  record.session.pid = replacement.process.pid ?? null;
363
402
  record.session.restartCount += 1;
364
403
  record.session.status = "running";
@@ -375,6 +414,26 @@ class CodingSessionManager {
375
414
  });
376
415
  this.persistState();
377
416
  }
417
+ notifyListeners(sessionId, update) {
418
+ const listeners = this.listeners.get(sessionId);
419
+ if (!listeners || listeners.size === 0)
420
+ return;
421
+ for (const listener of listeners) {
422
+ void Promise.resolve(listener(update)).catch((error) => {
423
+ (0, runtime_1.emitNervesEvent)({
424
+ level: "warn",
425
+ component: "repertoire",
426
+ event: "repertoire.coding_feedback_listener_error",
427
+ message: "coding session listener failed",
428
+ meta: {
429
+ sessionId,
430
+ kind: update.kind,
431
+ reason: error instanceof Error ? error.message : String(error),
432
+ },
433
+ });
434
+ });
435
+ }
436
+ }
378
437
  loadPersistedState() {
379
438
  if (!this.existsSync(this.stateFilePath)) {
380
439
  return;
@@ -426,13 +485,19 @@ class CodingSessionManager {
426
485
  }
427
486
  const normalizedRequest = {
428
487
  ...request,
488
+ originSession: request.originSession ? { ...request.originSession } : undefined,
429
489
  sessionId: request.sessionId ?? session.id,
490
+ obligationId: request.obligationId,
430
491
  parentAgent: request.parentAgent ?? this.agentName,
431
492
  };
432
493
  const normalizedSession = {
433
494
  ...session,
434
495
  taskRef: session.taskRef ?? normalizedRequest.taskRef,
496
+ originSession: session.originSession ?? normalizedRequest.originSession,
497
+ obligationId: session.obligationId ?? normalizedRequest.obligationId,
435
498
  failure: session.failure ?? null,
499
+ stdoutTail: session.stdoutTail ?? session.failure?.stdoutTail ?? "",
500
+ stderrTail: session.stderrTail ?? session.failure?.stderrTail ?? "",
436
501
  };
437
502
  if (typeof normalizedSession.pid === "number") {
438
503
  const alive = this.pidAlive(normalizedSession.pid);
@@ -451,8 +516,8 @@ class CodingSessionManager {
451
516
  process: null,
452
517
  command: normalizedSession.failure?.command ?? "restored",
453
518
  args: normalizedSession.failure ? [...normalizedSession.failure.args] : [],
454
- stdoutTail: normalizedSession.failure?.stdoutTail ?? "",
455
- stderrTail: normalizedSession.failure?.stderrTail ?? "",
519
+ stdoutTail: normalizedSession.stdoutTail,
520
+ stderrTail: normalizedSession.stderrTail,
456
521
  });
457
522
  this.sequence = Math.max(this.sequence, extractSequence(normalizedSession.id));
458
523
  }
@@ -36,6 +36,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.spawnCodingProcess = spawnCodingProcess;
37
37
  const child_process_1 = require("child_process");
38
38
  const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
39
41
  const runtime_1 = require("../../nerves/runtime");
40
42
  function buildCommandArgs(runner, workdir) {
41
43
  if (runner === "claude") {
@@ -43,11 +45,11 @@ function buildCommandArgs(runner, workdir) {
43
45
  command: "claude",
44
46
  args: [
45
47
  "-p",
48
+ "--verbose",
49
+ "--no-session-persistence",
46
50
  "--dangerously-skip-permissions",
47
51
  "--add-dir",
48
52
  workdir,
49
- "--input-format",
50
- "stream-json",
51
53
  "--output-format",
52
54
  "stream-json",
53
55
  ],
@@ -58,6 +60,18 @@ function buildCommandArgs(runner, workdir) {
58
60
  args: ["exec", "--skip-git-repo-check", "--cd", workdir],
59
61
  };
60
62
  }
63
+ function buildSpawnEnv(baseEnv, homeDir) {
64
+ const binDir = path.join(homeDir, ".ouro-cli", "bin");
65
+ const existingPath = baseEnv.PATH ?? "";
66
+ const pathEntries = existingPath.split(path.delimiter).filter((entry) => entry.length > 0);
67
+ if (!pathEntries.includes(binDir)) {
68
+ pathEntries.unshift(binDir);
69
+ }
70
+ return {
71
+ ...baseEnv,
72
+ PATH: pathEntries.join(path.delimiter),
73
+ };
74
+ }
61
75
  function buildPrompt(request, deps) {
62
76
  const sections = [];
63
77
  sections.push([
@@ -79,8 +93,11 @@ function spawnCodingProcess(request, deps = {}) {
79
93
  const spawnFn = deps.spawnFn ?? ((command, args, options) => (0, child_process_1.spawn)(command, args, options));
80
94
  const existsSync = deps.existsSync ?? fs.existsSync;
81
95
  const readFileSync = deps.readFileSync ?? fs.readFileSync;
96
+ const homeDir = deps.homeDir ?? os.homedir();
97
+ const baseEnv = deps.baseEnv ?? process.env;
82
98
  const prompt = buildPrompt(request, { existsSync, readFileSync });
83
99
  const { command, args } = buildCommandArgs(request.runner, request.workdir);
100
+ const env = buildSpawnEnv(baseEnv, homeDir);
84
101
  (0, runtime_1.emitNervesEvent)({
85
102
  component: "repertoire",
86
103
  event: "repertoire.coding_spawn_start",
@@ -89,9 +106,10 @@ function spawnCodingProcess(request, deps = {}) {
89
106
  });
90
107
  const proc = spawnFn(command, args, {
91
108
  cwd: request.workdir,
109
+ env,
92
110
  stdio: ["pipe", "pipe", "pipe"],
93
111
  });
94
- proc.stdin.write(`${prompt}\n`);
112
+ proc.stdin.end(`${prompt}\n`);
95
113
  (0, runtime_1.emitNervesEvent)({
96
114
  component: "repertoire",
97
115
  event: "repertoire.coding_spawn_end",