@ouro.bot/cli 0.1.0-alpha.9 → 0.1.0-alpha.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/assets/ouroboros.png +0 -0
- package/changelog.json +529 -0
- package/dist/heart/active-work.js +251 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +109 -0
- package/dist/heart/config.js +68 -23
- package/dist/heart/core.js +452 -93
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +430 -0
- package/dist/heart/daemon/daemon-cli.js +1737 -269
- package/dist/heart/daemon/daemon-entry.js +55 -6
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +216 -10
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -82
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +260 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +14 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +307 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +53 -84
- package/dist/heart/daemon/specialist-prompt.js +63 -11
- package/dist/heart/daemon/specialist-tools.js +211 -60
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +126 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +191 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +74 -9
- package/dist/heart/providers/azure.js +86 -7
- package/dist/heart/providers/github-copilot.js +149 -0
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +12 -3
- package/dist/heart/safe-workspace.js +362 -0
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/streaming.js +100 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +57 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/obligation-steering.js +31 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +467 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +180 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +69 -4
- package/dist/repertoire/coding/spawner.js +21 -3
- package/dist/repertoire/coding/tools.js +105 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +195 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +714 -249
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +106 -53
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +894 -45
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +400 -164
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +377 -83
- package/dist/senses/pipeline.js +307 -0
- package/dist/senses/teams.js +573 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- 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(
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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, "-");
|
package/dist/nerves/index.js
CHANGED
|
@@ -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
|
-
|
|
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 = `${
|
|
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(
|
|
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.
|
|
455
|
-
stderrTail: normalizedSession.
|
|
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.
|
|
112
|
+
proc.stdin.end(`${prompt}\n`);
|
|
95
113
|
(0, runtime_1.emitNervesEvent)({
|
|
96
114
|
component: "repertoire",
|
|
97
115
|
event: "repertoire.coding_spawn_end",
|