@ouro.bot/cli 0.1.0-alpha.82 → 0.1.0-alpha.84
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/changelog.json +15 -0
- package/dist/heart/active-work.js +34 -1
- package/dist/heart/commitments.js +21 -1
- package/dist/heart/daemon/daemon-cli.js +10 -0
- package/dist/heart/daemon/ouro-version-manager.js +7 -0
- package/dist/heart/obligations.js +55 -5
- package/dist/heart/safe-workspace.js +35 -2
- package/dist/mind/obligation-steering.js +31 -0
- package/dist/mind/prompt.js +22 -0
- package/dist/repertoire/coding/feedback.js +48 -2
- package/dist/repertoire/coding/manager.js +7 -0
- package/dist/repertoire/coding/tools.js +22 -0
- package/dist/repertoire/tools-base.js +31 -1
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.84",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Safe workspace routing now covers repo-local shell commands too, so agents that discover a harness friction through `shell` land in the dedicated worktree instead of wandering in the shared checkout.",
|
|
8
|
+
"The new `safe_workspace` tool and prompt guidance make the chosen workspace path, branch, and first concrete repo action explicit before the first edit, tightening the visible OODA loop."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"version": "0.1.0-alpha.83",
|
|
13
|
+
"changes": [
|
|
14
|
+
"Visible OODA loop substrate: persistent return obligations now surface where work is happening, whether the agent is still investigating, and what still needs to come back to the originating session.",
|
|
15
|
+
"Coding-session provenance now carries the originating human-facing session and obligation through spawn, persistence, and feedback, so hidden coding work is legible in active work instead of disappearing behind a tool session.",
|
|
16
|
+
"Runtime closure is explicit after self-fix updates: `ouro up` now prints a changelog follow-up command, and the runtime prompt reminds the agent to report that it updated and review what changed before treating the loop as closed."
|
|
17
|
+
]
|
|
18
|
+
},
|
|
4
19
|
{
|
|
5
20
|
"version": "0.1.0-alpha.82",
|
|
6
21
|
"changes": [
|
|
@@ -5,6 +5,7 @@ exports.buildActiveWorkFrame = buildActiveWorkFrame;
|
|
|
5
5
|
exports.formatActiveWorkFrame = formatActiveWorkFrame;
|
|
6
6
|
const runtime_1 = require("../nerves/runtime");
|
|
7
7
|
const state_machine_1 = require("./bridges/state-machine");
|
|
8
|
+
const obligations_1 = require("./obligations");
|
|
8
9
|
const target_resolution_1 = require("./target-resolution");
|
|
9
10
|
function activityPriority(source) {
|
|
10
11
|
return source === "friend-facing" ? 0 : 1;
|
|
@@ -31,6 +32,23 @@ function hasSharedObligationPressure(input) {
|
|
|
31
32
|
&& input.currentObligation.trim().length > 0) || input.mustResolveBeforeHandoff
|
|
32
33
|
|| summarizeLiveTasks(input.taskBoard).length > 0;
|
|
33
34
|
}
|
|
35
|
+
function activeObligationCount(obligations) {
|
|
36
|
+
return (obligations ?? []).filter((ob) => (0, obligations_1.isOpenObligationStatus)(ob.status)).length;
|
|
37
|
+
}
|
|
38
|
+
function formatObligationSurface(obligation) {
|
|
39
|
+
if (!obligation.currentSurface?.label)
|
|
40
|
+
return "";
|
|
41
|
+
switch (obligation.status) {
|
|
42
|
+
case "investigating":
|
|
43
|
+
return ` (working in ${obligation.currentSurface.label})`;
|
|
44
|
+
case "waiting_for_merge":
|
|
45
|
+
return ` (waiting at ${obligation.currentSurface.label})`;
|
|
46
|
+
case "updating_runtime":
|
|
47
|
+
return ` (updating via ${obligation.currentSurface.label})`;
|
|
48
|
+
default:
|
|
49
|
+
return ` (${obligation.currentSurface.label})`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
34
52
|
function suggestBridgeForActiveWork(input) {
|
|
35
53
|
const targetCandidates = (input.targetCandidates ?? [])
|
|
36
54
|
.filter((candidate) => {
|
|
@@ -87,9 +105,10 @@ function buildActiveWorkFrame(input) {
|
|
|
87
105
|
: [];
|
|
88
106
|
const liveTaskNames = summarizeLiveTasks(input.taskBoard);
|
|
89
107
|
const activeBridgePresent = input.bridges.some(isActiveBridge);
|
|
108
|
+
const openObligations = activeObligationCount(input.pendingObligations);
|
|
90
109
|
const centerOfGravity = activeBridgePresent
|
|
91
110
|
? "shared-work"
|
|
92
|
-
: (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff)
|
|
111
|
+
: (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff || openObligations > 0)
|
|
93
112
|
? "inward-work"
|
|
94
113
|
: "local-turn";
|
|
95
114
|
const frame = {
|
|
@@ -129,6 +148,7 @@ function buildActiveWorkFrame(input) {
|
|
|
129
148
|
bridges: frame.bridges.length,
|
|
130
149
|
liveTasks: frame.taskPressure.liveTaskNames.length,
|
|
131
150
|
liveSessions: frame.friendActivity.otherLiveSessionsForCurrentFriend.length,
|
|
151
|
+
pendingObligations: openObligations,
|
|
132
152
|
hasBridgeSuggestion: frame.bridgeSuggestion !== null,
|
|
133
153
|
},
|
|
134
154
|
});
|
|
@@ -204,6 +224,19 @@ function formatActiveWorkFrame(frame) {
|
|
|
204
224
|
lines.push("");
|
|
205
225
|
lines.push(targetCandidatesBlock);
|
|
206
226
|
}
|
|
227
|
+
if ((frame.pendingObligations ?? []).length > 0) {
|
|
228
|
+
lines.push("");
|
|
229
|
+
lines.push("## return obligations");
|
|
230
|
+
for (const obligation of frame.pendingObligations) {
|
|
231
|
+
if (!(0, obligations_1.isOpenObligationStatus)(obligation.status))
|
|
232
|
+
continue;
|
|
233
|
+
let obligationLine = `- [${obligation.status}] ${obligation.origin.friendId}/${obligation.origin.channel}/${obligation.origin.key}: ${obligation.content}${formatObligationSurface(obligation)}`;
|
|
234
|
+
if (obligation.latestNote?.trim()) {
|
|
235
|
+
obligationLine += `\n latest: ${obligation.latestNote.trim()}`;
|
|
236
|
+
}
|
|
237
|
+
lines.push(obligationLine);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
207
240
|
// Bridge suggestion
|
|
208
241
|
if (frame.bridgeSuggestion) {
|
|
209
242
|
lines.push("");
|
|
@@ -2,17 +2,37 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.deriveCommitments = deriveCommitments;
|
|
4
4
|
exports.formatCommitments = formatCommitments;
|
|
5
|
+
const obligations_1 = require("./obligations");
|
|
5
6
|
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
function describeActiveObligation(obligation) {
|
|
8
|
+
if (obligation.status === "pending") {
|
|
9
|
+
return `i owe ${obligation.origin.friendId}: ${obligation.content}`;
|
|
10
|
+
}
|
|
11
|
+
const surface = obligation.currentSurface?.label;
|
|
12
|
+
const statusText = obligation.status.replaceAll("_", " ");
|
|
13
|
+
if (surface) {
|
|
14
|
+
return `i owe ${obligation.origin.friendId}: ${obligation.content} (${statusText} in ${surface})`;
|
|
15
|
+
}
|
|
16
|
+
return `i owe ${obligation.origin.friendId}: ${obligation.content} (${statusText})`;
|
|
17
|
+
}
|
|
6
18
|
function deriveCommitments(activeWorkFrame, innerJob, pendingObligations) {
|
|
7
19
|
const committedTo = [];
|
|
8
20
|
const completionCriteria = [];
|
|
9
21
|
const safeToIgnore = [];
|
|
10
22
|
// Persistent obligations from the obligation store
|
|
11
23
|
if (pendingObligations && pendingObligations.length > 0) {
|
|
24
|
+
let hasAdvancedObligation = false;
|
|
12
25
|
for (const ob of pendingObligations) {
|
|
13
|
-
|
|
26
|
+
if (!(0, obligations_1.isOpenObligationStatus)(ob.status))
|
|
27
|
+
continue;
|
|
28
|
+
committedTo.push(describeActiveObligation(ob));
|
|
29
|
+
if (ob.status !== "pending")
|
|
30
|
+
hasAdvancedObligation = true;
|
|
14
31
|
}
|
|
15
32
|
completionCriteria.push("fulfill my outstanding obligations");
|
|
33
|
+
if (hasAdvancedObligation) {
|
|
34
|
+
completionCriteria.push("close my active obligation loops");
|
|
35
|
+
}
|
|
16
36
|
}
|
|
17
37
|
// Obligation (from current turn -- kept for backward compat)
|
|
18
38
|
if (typeof activeWorkFrame.currentObligation === "string" && activeWorkFrame.currentObligation.trim().length > 0) {
|
|
@@ -1828,6 +1828,11 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1828
1828
|
await deps.installCliVersion(updateResult.latestVersion);
|
|
1829
1829
|
deps.activateCliVersion(updateResult.latestVersion);
|
|
1830
1830
|
deps.writeStdout(`ouro updated to ${updateResult.latestVersion} (was ${currentVersion})`);
|
|
1831
|
+
const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(currentVersion, updateResult.latestVersion);
|
|
1832
|
+
/* v8 ignore next -- buildChangelogCommand is non-null when an actual newer version is installed @preserve */
|
|
1833
|
+
if (changelogCommand) {
|
|
1834
|
+
deps.writeStdout(`review changes with: ${changelogCommand}`);
|
|
1835
|
+
}
|
|
1831
1836
|
pendingReExec = true;
|
|
1832
1837
|
}
|
|
1833
1838
|
/* v8 ignore start -- update check error: tested via daemon-cli-update-flow.test.ts @preserve */
|
|
@@ -1873,6 +1878,11 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1873
1878
|
/* v8 ignore start -- CLI update detection: tested via daemon-cli-version-detect.test.ts @preserve */
|
|
1874
1879
|
if (previousCliVersion && previousCliVersion !== currentVersion) {
|
|
1875
1880
|
deps.writeStdout(`ouro updated to ${currentVersion} (was ${previousCliVersion})`);
|
|
1881
|
+
const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(previousCliVersion, currentVersion);
|
|
1882
|
+
/* v8 ignore next -- buildChangelogCommand is non-null when previous/current runtime versions differ @preserve */
|
|
1883
|
+
if (changelogCommand) {
|
|
1884
|
+
deps.writeStdout(`review changes with: ${changelogCommand}`);
|
|
1885
|
+
}
|
|
1876
1886
|
}
|
|
1877
1887
|
/* v8 ignore stop */
|
|
1878
1888
|
if (updateSummary.updated.length > 0) {
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.getOuroCliHome = getOuroCliHome;
|
|
37
37
|
exports.getCurrentVersion = getCurrentVersion;
|
|
38
38
|
exports.getPreviousVersion = getPreviousVersion;
|
|
39
|
+
exports.buildChangelogCommand = buildChangelogCommand;
|
|
39
40
|
exports.listInstalledVersions = listInstalledVersions;
|
|
40
41
|
exports.installVersion = installVersion;
|
|
41
42
|
exports.activateVersion = activateVersion;
|
|
@@ -73,6 +74,12 @@ function getPreviousVersion(deps) {
|
|
|
73
74
|
return null;
|
|
74
75
|
}
|
|
75
76
|
}
|
|
77
|
+
function buildChangelogCommand(previousVersion, currentVersion) {
|
|
78
|
+
if (!previousVersion || !currentVersion || previousVersion === currentVersion) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return `ouro changelog --from ${previousVersion}`;
|
|
82
|
+
}
|
|
76
83
|
function listInstalledVersions(deps) {
|
|
77
84
|
const cliHome = getOuroCliHome(deps.homeDir);
|
|
78
85
|
/* v8 ignore next -- dep default: tests always inject @preserve */
|
|
@@ -33,9 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isOpenObligationStatus = isOpenObligationStatus;
|
|
37
|
+
exports.isOpenObligation = isOpenObligation;
|
|
36
38
|
exports.createObligation = createObligation;
|
|
37
39
|
exports.readObligations = readObligations;
|
|
38
40
|
exports.readPendingObligations = readPendingObligations;
|
|
41
|
+
exports.advanceObligation = advanceObligation;
|
|
39
42
|
exports.fulfillObligation = fulfillObligation;
|
|
40
43
|
exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
|
|
41
44
|
const fs = __importStar(require("fs"));
|
|
@@ -52,7 +55,14 @@ function generateId() {
|
|
|
52
55
|
const random = Math.random().toString(36).slice(2, 10);
|
|
53
56
|
return `${timestamp}-${random}`;
|
|
54
57
|
}
|
|
58
|
+
function isOpenObligationStatus(status) {
|
|
59
|
+
return status !== "fulfilled";
|
|
60
|
+
}
|
|
61
|
+
function isOpenObligation(obligation) {
|
|
62
|
+
return isOpenObligationStatus(obligation.status);
|
|
63
|
+
}
|
|
55
64
|
function createObligation(agentRoot, input) {
|
|
65
|
+
const now = new Date().toISOString();
|
|
56
66
|
const id = generateId();
|
|
57
67
|
const obligation = {
|
|
58
68
|
id,
|
|
@@ -60,7 +70,8 @@ function createObligation(agentRoot, input) {
|
|
|
60
70
|
...(input.bridgeId ? { bridgeId: input.bridgeId } : {}),
|
|
61
71
|
content: input.content,
|
|
62
72
|
status: "pending",
|
|
63
|
-
createdAt:
|
|
73
|
+
createdAt: now,
|
|
74
|
+
updatedAt: now,
|
|
64
75
|
};
|
|
65
76
|
const dir = obligationsDir(agentRoot);
|
|
66
77
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -107,9 +118,9 @@ function readObligations(agentRoot) {
|
|
|
107
118
|
return obligations;
|
|
108
119
|
}
|
|
109
120
|
function readPendingObligations(agentRoot) {
|
|
110
|
-
return readObligations(agentRoot).filter(
|
|
121
|
+
return readObligations(agentRoot).filter(isOpenObligation);
|
|
111
122
|
}
|
|
112
|
-
function
|
|
123
|
+
function advanceObligation(agentRoot, obligationId, update) {
|
|
113
124
|
const filePath = obligationFilePath(agentRoot, obligationId);
|
|
114
125
|
let obligation;
|
|
115
126
|
try {
|
|
@@ -119,9 +130,48 @@ function fulfillObligation(agentRoot, obligationId) {
|
|
|
119
130
|
catch {
|
|
120
131
|
return;
|
|
121
132
|
}
|
|
122
|
-
obligation.status
|
|
123
|
-
|
|
133
|
+
const previousStatus = obligation.status;
|
|
134
|
+
if (update.status) {
|
|
135
|
+
obligation.status = update.status;
|
|
136
|
+
if (update.status === "fulfilled") {
|
|
137
|
+
obligation.fulfilledAt = new Date().toISOString();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (update.currentSurface) {
|
|
141
|
+
obligation.currentSurface = update.currentSurface;
|
|
142
|
+
}
|
|
143
|
+
if (typeof update.latestNote === "string") {
|
|
144
|
+
obligation.latestNote = update.latestNote;
|
|
145
|
+
}
|
|
146
|
+
obligation.updatedAt = new Date().toISOString();
|
|
124
147
|
fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf-8");
|
|
148
|
+
(0, runtime_1.emitNervesEvent)({
|
|
149
|
+
component: "engine",
|
|
150
|
+
event: "engine.obligation_advanced",
|
|
151
|
+
message: "obligation advanced",
|
|
152
|
+
meta: {
|
|
153
|
+
obligationId,
|
|
154
|
+
previousStatus,
|
|
155
|
+
status: obligation.status,
|
|
156
|
+
friendId: obligation.origin.friendId,
|
|
157
|
+
channel: obligation.origin.channel,
|
|
158
|
+
key: obligation.origin.key,
|
|
159
|
+
surfaceKind: obligation.currentSurface?.kind ?? null,
|
|
160
|
+
surfaceLabel: obligation.currentSurface?.label ?? null,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function fulfillObligation(agentRoot, obligationId) {
|
|
165
|
+
advanceObligation(agentRoot, obligationId, { status: "fulfilled" });
|
|
166
|
+
const filePath = obligationFilePath(agentRoot, obligationId);
|
|
167
|
+
let obligation;
|
|
168
|
+
try {
|
|
169
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
170
|
+
obligation = JSON.parse(raw);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
125
175
|
(0, runtime_1.emitNervesEvent)({
|
|
126
176
|
component: "engine",
|
|
127
177
|
event: "engine.obligation_fulfilled",
|
|
@@ -37,6 +37,7 @@ exports.resetSafeWorkspaceSelection = resetSafeWorkspaceSelection;
|
|
|
37
37
|
exports.getActiveSafeWorkspaceSelection = getActiveSafeWorkspaceSelection;
|
|
38
38
|
exports.ensureSafeRepoWorkspace = ensureSafeRepoWorkspace;
|
|
39
39
|
exports.resolveSafeRepoPath = resolveSafeRepoPath;
|
|
40
|
+
exports.resolveSafeShellExecution = resolveSafeShellExecution;
|
|
40
41
|
const fs = __importStar(require("fs"));
|
|
41
42
|
const path = __importStar(require("path"));
|
|
42
43
|
const child_process_1 = require("child_process");
|
|
@@ -96,7 +97,7 @@ function createDedicatedWorktree(repoRoot, workspaceRoot, branchSuffix, existsSy
|
|
|
96
97
|
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
97
98
|
}
|
|
98
99
|
assertGitOk(runGit(repoRoot, ["worktree", "add", "-B", branchName, workspaceRoot, "origin/main"], spawnSync), "git worktree add");
|
|
99
|
-
return { workspaceRoot, created: true };
|
|
100
|
+
return { workspaceRoot, created: true, branchName };
|
|
100
101
|
}
|
|
101
102
|
function createScratchClone(workspaceRoot, cloneUrl, existsSync, mkdirSync, rmSync, spawnSync) {
|
|
102
103
|
mkdirSync(path.dirname(workspaceRoot), { recursive: true });
|
|
@@ -107,7 +108,11 @@ function createScratchClone(workspaceRoot, cloneUrl, existsSync, mkdirSync, rmSy
|
|
|
107
108
|
stdio: ["ignore", "pipe", "pipe"],
|
|
108
109
|
});
|
|
109
110
|
assertGitOk(result, "git clone");
|
|
110
|
-
return { workspaceRoot, created: true };
|
|
111
|
+
return { workspaceRoot, created: true, branchName: "main" };
|
|
112
|
+
}
|
|
113
|
+
const REPO_LOCAL_SHELL_COMMAND = /^(?:[A-Za-z_][A-Za-z0-9_]*=\S+\s+)*(git|npm|npx|node|pnpm|yarn|bun|rg|sed|cat|ls|find|grep|vitest|tsc|eslint)\b/;
|
|
114
|
+
function looksRepoLocalShellCommand(command) {
|
|
115
|
+
return REPO_LOCAL_SHELL_COMMAND.test(command.trim());
|
|
111
116
|
}
|
|
112
117
|
function registerCleanupHook(options) {
|
|
113
118
|
if (cleanupHookRegistered)
|
|
@@ -160,6 +165,7 @@ function ensureSafeRepoWorkspace(options = {}) {
|
|
|
160
165
|
runtimeKind: "clone-main",
|
|
161
166
|
repoRoot,
|
|
162
167
|
workspaceRoot: created.workspaceRoot,
|
|
168
|
+
workspaceBranch: created.branchName,
|
|
163
169
|
sourceBranch: branch,
|
|
164
170
|
sourceCloneUrl: canonicalRepoUrl,
|
|
165
171
|
cleanupAfterMerge: false,
|
|
@@ -174,6 +180,7 @@ function ensureSafeRepoWorkspace(options = {}) {
|
|
|
174
180
|
runtimeKind: "clone-non-main",
|
|
175
181
|
repoRoot,
|
|
176
182
|
workspaceRoot: created.workspaceRoot,
|
|
183
|
+
workspaceBranch: created.branchName,
|
|
177
184
|
sourceBranch: branch,
|
|
178
185
|
sourceCloneUrl: canonicalRepoUrl,
|
|
179
186
|
cleanupAfterMerge: false,
|
|
@@ -189,6 +196,7 @@ function ensureSafeRepoWorkspace(options = {}) {
|
|
|
189
196
|
runtimeKind: "installed-runtime",
|
|
190
197
|
repoRoot,
|
|
191
198
|
workspaceRoot: created.workspaceRoot,
|
|
199
|
+
workspaceBranch: created.branchName,
|
|
192
200
|
sourceBranch: null,
|
|
193
201
|
sourceCloneUrl: canonicalRepoUrl,
|
|
194
202
|
cleanupAfterMerge: true,
|
|
@@ -205,6 +213,7 @@ function ensureSafeRepoWorkspace(options = {}) {
|
|
|
205
213
|
runtimeKind: selection.runtimeKind,
|
|
206
214
|
repoRoot: selection.repoRoot,
|
|
207
215
|
workspaceRoot: selection.workspaceRoot,
|
|
216
|
+
workspaceBranch: selection.workspaceBranch,
|
|
208
217
|
sourceBranch: selection.sourceBranch,
|
|
209
218
|
sourceCloneUrl: selection.sourceCloneUrl,
|
|
210
219
|
cleanupAfterMerge: selection.cleanupAfterMerge,
|
|
@@ -226,3 +235,27 @@ function resolveSafeRepoPath(options) {
|
|
|
226
235
|
const resolvedPath = relativePath ? path.join(selection.workspaceRoot, relativePath) : selection.workspaceRoot;
|
|
227
236
|
return { selection, resolvedPath };
|
|
228
237
|
}
|
|
238
|
+
function resolveSafeShellExecution(command, options = {}) {
|
|
239
|
+
const trimmed = command.trim();
|
|
240
|
+
if (!trimmed) {
|
|
241
|
+
return { selection: activeSelection, command };
|
|
242
|
+
}
|
|
243
|
+
if (activeSelection && command.includes(activeSelection.workspaceRoot)) {
|
|
244
|
+
return { selection: activeSelection, command, cwd: activeSelection.workspaceRoot };
|
|
245
|
+
}
|
|
246
|
+
const repoRoot = path.resolve(options.repoRoot ?? (0, identity_1.getRepoRoot)());
|
|
247
|
+
const mentionsRepoRoot = command.includes(repoRoot);
|
|
248
|
+
const shouldRoute = mentionsRepoRoot || looksRepoLocalShellCommand(trimmed);
|
|
249
|
+
if (!shouldRoute) {
|
|
250
|
+
return { selection: activeSelection, command };
|
|
251
|
+
}
|
|
252
|
+
const selection = ensureSafeRepoWorkspace(options);
|
|
253
|
+
const rewrittenCommand = mentionsRepoRoot
|
|
254
|
+
? command.split(repoRoot).join(selection.workspaceRoot)
|
|
255
|
+
: command;
|
|
256
|
+
return {
|
|
257
|
+
selection,
|
|
258
|
+
command: rewrittenCommand,
|
|
259
|
+
cwd: selection.workspaceRoot,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findActivePersistentObligation = findActivePersistentObligation;
|
|
4
|
+
exports.renderActiveObligationSteering = renderActiveObligationSteering;
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
function findActivePersistentObligation(frame) {
|
|
7
|
+
if (!frame)
|
|
8
|
+
return null;
|
|
9
|
+
return (frame.pendingObligations ?? []).find((ob) => ob.status !== "pending" && ob.status !== "fulfilled") ?? null;
|
|
10
|
+
}
|
|
11
|
+
function renderActiveObligationSteering(obligation) {
|
|
12
|
+
(0, runtime_1.emitNervesEvent)({
|
|
13
|
+
component: "mind",
|
|
14
|
+
event: "mind.obligation_steering_rendered",
|
|
15
|
+
message: "rendered active obligation steering",
|
|
16
|
+
meta: {
|
|
17
|
+
hasObligation: Boolean(obligation),
|
|
18
|
+
hasSurface: Boolean(obligation?.currentSurface?.label),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
if (!obligation)
|
|
22
|
+
return "";
|
|
23
|
+
const name = obligation.origin.friendId;
|
|
24
|
+
const surfaceLine = obligation.currentSurface?.label
|
|
25
|
+
? `\nright now that work is happening in ${obligation.currentSurface.label}.`
|
|
26
|
+
: "";
|
|
27
|
+
return `## where my attention is
|
|
28
|
+
i'm already working on something i owe ${name}.${surfaceLine}
|
|
29
|
+
|
|
30
|
+
i should close that loop before i act like this is a fresh blank turn.`;
|
|
31
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -52,6 +52,7 @@ exports.buildSystem = buildSystem;
|
|
|
52
52
|
const fs = __importStar(require("fs"));
|
|
53
53
|
const path = __importStar(require("path"));
|
|
54
54
|
const core_1 = require("../heart/core");
|
|
55
|
+
const ouro_version_manager_1 = require("../heart/daemon/ouro-version-manager");
|
|
55
56
|
const tools_1 = require("../repertoire/tools");
|
|
56
57
|
const skills_1 = require("../repertoire/skills");
|
|
57
58
|
const identity_1 = require("../heart/identity");
|
|
@@ -65,6 +66,7 @@ const tasks_1 = require("../repertoire/tasks");
|
|
|
65
66
|
const session_activity_1 = require("../heart/session-activity");
|
|
66
67
|
const active_work_1 = require("../heart/active-work");
|
|
67
68
|
const commitments_1 = require("../heart/commitments");
|
|
69
|
+
const obligation_steering_1 = require("./obligation-steering");
|
|
68
70
|
// Lazy-loaded psyche text cache
|
|
69
71
|
let _psycheCache = null;
|
|
70
72
|
let _senseStatusLinesCache = null;
|
|
@@ -253,6 +255,11 @@ function runtimeInfoSection(channel) {
|
|
|
253
255
|
const bundleMeta = readBundleMeta();
|
|
254
256
|
if (bundleMeta?.previousRuntimeVersion && bundleMeta.previousRuntimeVersion !== currentVersion) {
|
|
255
257
|
lines.push(`previously: ${bundleMeta.previousRuntimeVersion}`);
|
|
258
|
+
const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(bundleMeta.previousRuntimeVersion, currentVersion);
|
|
259
|
+
/* v8 ignore next -- buildChangelogCommand is non-null when previous/current runtime versions differ @preserve */
|
|
260
|
+
if (changelogCommand) {
|
|
261
|
+
lines.push(`if i'm closing a self-fix loop, i should tell them i updated and review changes with \`${changelogCommand}\`.`);
|
|
262
|
+
}
|
|
256
263
|
}
|
|
257
264
|
lines.push(`changelog available at: ${(0, bundle_manifest_1.getChangelogPath)()}`);
|
|
258
265
|
lines.push(`cwd: ${process.cwd()}`);
|
|
@@ -460,7 +467,11 @@ function centerOfGravitySteeringSection(channel, options) {
|
|
|
460
467
|
if (cog === "local-turn")
|
|
461
468
|
return "";
|
|
462
469
|
const job = frame.inner?.job;
|
|
470
|
+
const activeObligation = (0, obligation_steering_1.findActivePersistentObligation)(frame);
|
|
463
471
|
if (cog === "inward-work") {
|
|
472
|
+
if (activeObligation) {
|
|
473
|
+
return (0, obligation_steering_1.renderActiveObligationSteering)(activeObligation);
|
|
474
|
+
}
|
|
464
475
|
if (job?.status === "queued" || job?.status === "running") {
|
|
465
476
|
const originClause = job.origin
|
|
466
477
|
? ` ${job.origin.friendName ?? job.origin.friendId} asked about something and i wanted to give it real thought before responding.`
|
|
@@ -554,6 +565,16 @@ tool_choice is set to "required" -- i must call a tool on every turn.
|
|
|
554
565
|
\`final_answer\` must be the ONLY tool call in that turn. do not combine it with other tool calls.
|
|
555
566
|
do NOT call no-op tools just before \`final_answer\`. if i am done, i call \`final_answer\` directly.`;
|
|
556
567
|
}
|
|
568
|
+
function workspaceDisciplineSection() {
|
|
569
|
+
return `## repo workspace discipline
|
|
570
|
+
when a shared-harness or local code fix needs repo work, i get the real workspace first with \`safe_workspace\`.
|
|
571
|
+
\`read_file\`, \`write_file\`, and \`edit_file\` already map repo paths into that workspace. shell commands that target the harness run there too.
|
|
572
|
+
|
|
573
|
+
before the first repo edit, i tell the user in 1-2 short lines:
|
|
574
|
+
- the friction i'm fixing
|
|
575
|
+
- the workspace path/branch i'm using
|
|
576
|
+
- the first concrete action i'm taking`;
|
|
577
|
+
}
|
|
557
578
|
function contextSection(context, options) {
|
|
558
579
|
if (!context)
|
|
559
580
|
return "";
|
|
@@ -688,6 +709,7 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
688
709
|
toolsSection(channel, options, context),
|
|
689
710
|
mcpToolsSection(options?.mcpManager),
|
|
690
711
|
reasoningEffortSection(options),
|
|
712
|
+
workspaceDisciplineSection(),
|
|
691
713
|
toolRestrictionSection(context),
|
|
692
714
|
trustContextSection(context),
|
|
693
715
|
mixedTrustGroupSection(context),
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatCodingTail = formatCodingTail;
|
|
4
4
|
exports.attachCodingSessionFeedback = attachCodingSessionFeedback;
|
|
5
|
+
const identity_1 = require("../../heart/identity");
|
|
6
|
+
const obligations_1 = require("../../heart/obligations");
|
|
5
7
|
const runtime_1 = require("../../nerves/runtime");
|
|
6
8
|
const TERMINAL_UPDATE_KINDS = new Set(["completed", "failed", "killed"]);
|
|
7
9
|
function clip(text, maxLength = 280) {
|
|
@@ -42,7 +44,10 @@ function lastMeaningfulLine(text) {
|
|
|
42
44
|
return clip(lines.at(-1));
|
|
43
45
|
}
|
|
44
46
|
function formatSessionLabel(session) {
|
|
45
|
-
|
|
47
|
+
const origin = session.originSession
|
|
48
|
+
? ` for ${session.originSession.channel}/${session.originSession.key}`
|
|
49
|
+
: "";
|
|
50
|
+
return `${session.runner} ${session.id}${origin}`;
|
|
46
51
|
}
|
|
47
52
|
function isSafeProgressSnippet(snippet) {
|
|
48
53
|
const wordCount = snippet.split(/\s+/).filter(Boolean).length;
|
|
@@ -81,6 +86,44 @@ function formatUpdateMessage(update) {
|
|
|
81
86
|
return `${label} started`;
|
|
82
87
|
}
|
|
83
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
|
+
}
|
|
84
127
|
function formatCodingTail(session) {
|
|
85
128
|
const stdout = session.stdoutTail.trim() || "(empty)";
|
|
86
129
|
const stderr = session.stderrTail.trim() || "(empty)";
|
|
@@ -119,8 +162,11 @@ function attachCodingSessionFeedback(manager, session, target) {
|
|
|
119
162
|
});
|
|
120
163
|
});
|
|
121
164
|
};
|
|
122
|
-
|
|
165
|
+
const spawnedUpdate = { kind: "spawned", session };
|
|
166
|
+
syncObligationFromUpdate(spawnedUpdate);
|
|
167
|
+
sendMessage(formatUpdateMessage(spawnedUpdate));
|
|
123
168
|
unsubscribe = manager.subscribe(session.id, async (update) => {
|
|
169
|
+
syncObligationFromUpdate(update);
|
|
124
170
|
sendMessage(formatUpdateMessage(update));
|
|
125
171
|
if (TERMINAL_UPDATE_KINDS.has(update.kind)) {
|
|
126
172
|
closed = true;
|
|
@@ -62,6 +62,7 @@ function isPidAlive(pid) {
|
|
|
62
62
|
function cloneSession(session) {
|
|
63
63
|
return {
|
|
64
64
|
...session,
|
|
65
|
+
originSession: session.originSession ? { ...session.originSession } : undefined,
|
|
65
66
|
stdoutTail: session.stdoutTail,
|
|
66
67
|
stderrTail: session.stderrTail,
|
|
67
68
|
failure: session.failure
|
|
@@ -157,6 +158,8 @@ class CodingSessionManager {
|
|
|
157
158
|
runner: normalizedRequest.runner,
|
|
158
159
|
workdir: normalizedRequest.workdir,
|
|
159
160
|
taskRef: normalizedRequest.taskRef,
|
|
161
|
+
originSession: normalizedRequest.originSession ? { ...normalizedRequest.originSession } : undefined,
|
|
162
|
+
obligationId: normalizedRequest.obligationId,
|
|
160
163
|
scopeFile: normalizedRequest.scopeFile,
|
|
161
164
|
stateFile: normalizedRequest.stateFile,
|
|
162
165
|
status: "spawning",
|
|
@@ -482,12 +485,16 @@ class CodingSessionManager {
|
|
|
482
485
|
}
|
|
483
486
|
const normalizedRequest = {
|
|
484
487
|
...request,
|
|
488
|
+
originSession: request.originSession ? { ...request.originSession } : undefined,
|
|
485
489
|
sessionId: request.sessionId ?? session.id,
|
|
490
|
+
obligationId: request.obligationId,
|
|
486
491
|
parentAgent: request.parentAgent ?? this.agentName,
|
|
487
492
|
};
|
|
488
493
|
const normalizedSession = {
|
|
489
494
|
...session,
|
|
490
495
|
taskRef: session.taskRef ?? normalizedRequest.taskRef,
|
|
496
|
+
originSession: session.originSession ?? normalizedRequest.originSession,
|
|
497
|
+
obligationId: session.obligationId ?? normalizedRequest.obligationId,
|
|
491
498
|
failure: session.failure ?? null,
|
|
492
499
|
stdoutTail: session.stdoutTail ?? session.failure?.stdoutTail ?? "",
|
|
493
500
|
stderrTail: session.stderrTail ?? session.failure?.stderrTail ?? "",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.codingToolDefinitions = void 0;
|
|
4
4
|
const index_1 = require("./index");
|
|
5
|
+
const identity_1 = require("../../heart/identity");
|
|
6
|
+
const obligations_1 = require("../../heart/obligations");
|
|
5
7
|
const runtime_1 = require("../../nerves/runtime");
|
|
6
8
|
const RUNNERS = ["claude", "codex"];
|
|
7
9
|
function requireArg(args, key) {
|
|
@@ -130,6 +132,17 @@ exports.codingToolDefinitions = [
|
|
|
130
132
|
prompt,
|
|
131
133
|
taskRef,
|
|
132
134
|
};
|
|
135
|
+
if (ctx?.currentSession && ctx.currentSession.channel !== "inner") {
|
|
136
|
+
request.originSession = {
|
|
137
|
+
friendId: ctx.currentSession.friendId,
|
|
138
|
+
channel: ctx.currentSession.channel,
|
|
139
|
+
key: ctx.currentSession.key,
|
|
140
|
+
};
|
|
141
|
+
const obligation = (0, obligations_1.findPendingObligationForOrigin)((0, identity_1.getAgentRoot)(), request.originSession);
|
|
142
|
+
if (obligation) {
|
|
143
|
+
request.obligationId = obligation.id;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
133
146
|
const scopeFile = optionalArg(args, "scopeFile");
|
|
134
147
|
if (scopeFile)
|
|
135
148
|
request.scopeFile = scopeFile;
|
|
@@ -138,6 +151,15 @@ exports.codingToolDefinitions = [
|
|
|
138
151
|
request.stateFile = stateFile;
|
|
139
152
|
const manager = (0, index_1.getCodingSessionManager)();
|
|
140
153
|
const session = await manager.spawnSession(request);
|
|
154
|
+
if (session.obligationId) {
|
|
155
|
+
(0, obligations_1.advanceObligation)((0, identity_1.getAgentRoot)(), session.obligationId, {
|
|
156
|
+
status: "investigating",
|
|
157
|
+
currentSurface: { kind: "coding", label: `${session.runner} ${session.id}` },
|
|
158
|
+
latestNote: session.originSession
|
|
159
|
+
? `coding session started for ${session.originSession.channel}/${session.originSession.key}`
|
|
160
|
+
: "coding session started",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
141
163
|
if (args.runner === "codex" && args.taskRef) {
|
|
142
164
|
(0, runtime_1.emitNervesEvent)({
|
|
143
165
|
component: "repertoire",
|
|
@@ -404,6 +404,29 @@ exports.baseToolDefinitions = [
|
|
|
404
404
|
return allResults.join("\n");
|
|
405
405
|
},
|
|
406
406
|
},
|
|
407
|
+
{
|
|
408
|
+
tool: {
|
|
409
|
+
type: "function",
|
|
410
|
+
function: {
|
|
411
|
+
name: "safe_workspace",
|
|
412
|
+
description: "acquire or inspect the safe harness repo workspace for local edits. returns the real workspace path, branch, and why it was chosen.",
|
|
413
|
+
parameters: {
|
|
414
|
+
type: "object",
|
|
415
|
+
properties: {},
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
handler: () => {
|
|
420
|
+
const selection = (0, safe_workspace_1.ensureSafeRepoWorkspace)();
|
|
421
|
+
return [
|
|
422
|
+
`workspace: ${selection.workspaceRoot}`,
|
|
423
|
+
`branch: ${selection.workspaceBranch}`,
|
|
424
|
+
`runtime: ${selection.runtimeKind}`,
|
|
425
|
+
`cleanup_after_merge: ${selection.cleanupAfterMerge ? "yes" : "no"}`,
|
|
426
|
+
`note: ${selection.note}`,
|
|
427
|
+
].join("\n");
|
|
428
|
+
},
|
|
429
|
+
},
|
|
407
430
|
{
|
|
408
431
|
tool: {
|
|
409
432
|
type: "function",
|
|
@@ -417,7 +440,14 @@ exports.baseToolDefinitions = [
|
|
|
417
440
|
},
|
|
418
441
|
},
|
|
419
442
|
},
|
|
420
|
-
handler: (a) =>
|
|
443
|
+
handler: (a) => {
|
|
444
|
+
const prepared = (0, safe_workspace_1.resolveSafeShellExecution)(a.command);
|
|
445
|
+
return (0, child_process_1.execSync)(prepared.command, {
|
|
446
|
+
encoding: "utf-8",
|
|
447
|
+
timeout: 30000,
|
|
448
|
+
...(prepared.cwd ? { cwd: prepared.cwd } : {}),
|
|
449
|
+
});
|
|
450
|
+
},
|
|
421
451
|
},
|
|
422
452
|
{
|
|
423
453
|
tool: {
|