@ouro.bot/cli 0.1.0-alpha.8 → 0.1.0-alpha.81
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 +468 -0
- package/dist/heart/active-work.js +218 -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 +89 -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 +1779 -247
- 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 +164 -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 +141 -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 +228 -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 +27 -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/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +445 -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 +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -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 +686 -251
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +95 -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 +405 -156
- 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,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(
|
|
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.
|
|
455
|
-
stderrTail: normalizedSession.
|
|
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.
|
|
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
|
|
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) => {
|