@ouro.bot/cli 0.1.0-alpha.1 → 0.1.0-alpha.100
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 +596 -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 +102 -23
- package/dist/heart/core.js +512 -94
- 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 +1935 -185
- 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 +218 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +10 -83
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- 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 +147 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron.js +260 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- 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 +32 -2
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +61 -14
- 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 +129 -0
- package/dist/heart/daemon/specialist-prompt.js +99 -0
- package/dist/heart/daemon/specialist-tools.js +283 -0
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/task-scheduler.js +4 -1
- 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 +153 -23
- 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 +77 -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 +381 -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 +103 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +37 -4
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +141 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +43 -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 +9 -1
- package/dist/mind/memory.js +89 -26
- package/dist/mind/obligation-steering.js +31 -0
- package/dist/mind/pending.js +160 -0
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt-refresh.js +20 -0
- package/dist/mind/prompt.js +499 -8
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/file-completeness.js +14 -4
- 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 +210 -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 +770 -213
- 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 +484 -0
- package/dist/senses/bluebubbles-entry.js +13 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +261 -0
- package/dist/senses/bluebubbles-mutation-log.js +116 -0
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +1181 -0
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +452 -99
- 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 +387 -70
- package/dist/senses/pipeline.js +307 -0
- package/dist/senses/session-lock.js +119 -0
- package/dist/senses/teams.js +574 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +16 -4
- package/subagents/README.md +4 -68
- 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 -593
- package/subagents/work-planner.md +0 -373
|
@@ -33,18 +33,137 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.finalAnswerTool = exports.
|
|
36
|
+
exports.finalAnswerTool = exports.noResponseTool = exports.goInwardTool = exports.tools = exports.baseToolDefinitions = exports.editFileReadTracker = void 0;
|
|
37
|
+
exports.renderInnerProgressStatus = renderInnerProgressStatus;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
39
|
+
const fg = __importStar(require("fast-glob"));
|
|
38
40
|
const child_process_1 = require("child_process");
|
|
39
41
|
const path = __importStar(require("path"));
|
|
40
42
|
const skills_1 = require("./skills");
|
|
41
43
|
const config_1 = require("../heart/config");
|
|
42
44
|
const runtime_1 = require("../nerves/runtime");
|
|
43
45
|
const identity_1 = require("../heart/identity");
|
|
44
|
-
const
|
|
46
|
+
const safe_workspace_1 = require("../heart/safe-workspace");
|
|
47
|
+
const socket_client_1 = require("../heart/daemon/socket-client");
|
|
48
|
+
const thoughts_1 = require("../heart/daemon/thoughts");
|
|
49
|
+
const manager_1 = require("../heart/bridges/manager");
|
|
50
|
+
const session_recall_1 = require("../heart/session-recall");
|
|
45
51
|
const tools_1 = require("./coding/tools");
|
|
46
52
|
const memory_1 = require("../mind/memory");
|
|
47
|
-
const
|
|
53
|
+
const pending_1 = require("../mind/pending");
|
|
54
|
+
const progress_story_1 = require("../heart/progress-story");
|
|
55
|
+
const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
|
|
56
|
+
const obligations_1 = require("../heart/obligations");
|
|
57
|
+
// Tracks which file paths have been read via read_file in this session.
|
|
58
|
+
// edit_file requires a file to be read first (must-read-first guard).
|
|
59
|
+
exports.editFileReadTracker = new Set();
|
|
60
|
+
function buildContextDiff(lines, changeStart, changeEnd, contextSize = 3) {
|
|
61
|
+
const start = Math.max(0, changeStart - contextSize);
|
|
62
|
+
const end = Math.min(lines.length, changeEnd + contextSize);
|
|
63
|
+
const result = [];
|
|
64
|
+
for (let i = start; i < end; i++) {
|
|
65
|
+
const lineNum = i + 1;
|
|
66
|
+
const prefix = (i >= changeStart && i < changeEnd) ? ">" : " ";
|
|
67
|
+
result.push(`${prefix} ${lineNum} | ${lines[i]}`);
|
|
68
|
+
}
|
|
69
|
+
return result.join("\n");
|
|
70
|
+
}
|
|
71
|
+
function resolveLocalToolPath(targetPath) {
|
|
72
|
+
return (0, safe_workspace_1.resolveSafeRepoPath)({ requestedPath: targetPath }).resolvedPath;
|
|
73
|
+
}
|
|
74
|
+
const NO_SESSION_FOUND_MESSAGE = "no session found for that friend/channel/key combination.";
|
|
75
|
+
const EMPTY_SESSION_MESSAGE = "session exists but has no non-system messages.";
|
|
76
|
+
function findDelegatingBridgeId(ctx) {
|
|
77
|
+
const currentSession = ctx?.currentSession;
|
|
78
|
+
if (!currentSession)
|
|
79
|
+
return undefined;
|
|
80
|
+
return ctx?.activeBridges?.find((bridge) => bridge.lifecycle === "active"
|
|
81
|
+
&& bridge.attachedSessions.some((session) => session.friendId === currentSession.friendId
|
|
82
|
+
&& session.channel === currentSession.channel
|
|
83
|
+
&& session.key === currentSession.key))?.id;
|
|
84
|
+
}
|
|
85
|
+
async function recallSessionSafely(options) {
|
|
86
|
+
try {
|
|
87
|
+
return await (0, session_recall_1.recallSession)(options);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (options.summarize) {
|
|
91
|
+
(0, runtime_1.emitNervesEvent)({
|
|
92
|
+
component: "daemon",
|
|
93
|
+
event: "daemon.session_recall_summary_fallback",
|
|
94
|
+
message: "session recall summarization failed; using raw transcript",
|
|
95
|
+
meta: {
|
|
96
|
+
friendId: options.friendId,
|
|
97
|
+
channel: options.channel,
|
|
98
|
+
key: options.key,
|
|
99
|
+
error: error instanceof Error ? error.message : String(error),
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
return await (0, session_recall_1.recallSession)({
|
|
104
|
+
...options,
|
|
105
|
+
summarize: undefined,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return { kind: "missing" };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { kind: "missing" };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function normalizeProgressOutcome(text) {
|
|
116
|
+
const trimmed = text.trim();
|
|
117
|
+
/* v8 ignore next -- defensive: normalizeProgressOutcome null branch @preserve */
|
|
118
|
+
if (!trimmed || trimmed === "nothing yet" || trimmed === "nothing recent") {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") && trimmed.length >= 2) {
|
|
122
|
+
return trimmed.slice(1, -1);
|
|
123
|
+
}
|
|
124
|
+
return trimmed;
|
|
125
|
+
}
|
|
126
|
+
function writePendingEnvelope(queueDir, message) {
|
|
127
|
+
fs.mkdirSync(queueDir, { recursive: true });
|
|
128
|
+
const fileName = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
129
|
+
const filePath = path.join(queueDir, fileName);
|
|
130
|
+
fs.writeFileSync(filePath, JSON.stringify(message, null, 2));
|
|
131
|
+
}
|
|
132
|
+
function renderCrossChatDeliveryStatus(target, result) {
|
|
133
|
+
const phase = result.status === "delivered_now"
|
|
134
|
+
? "completed"
|
|
135
|
+
: result.status === "queued_for_later"
|
|
136
|
+
? "queued"
|
|
137
|
+
: result.status === "blocked"
|
|
138
|
+
? "blocked"
|
|
139
|
+
: "errored";
|
|
140
|
+
const lead = result.status === "delivered_now"
|
|
141
|
+
? "delivered now"
|
|
142
|
+
: result.status === "queued_for_later"
|
|
143
|
+
? "queued for later"
|
|
144
|
+
: result.status === "blocked"
|
|
145
|
+
? "blocked"
|
|
146
|
+
: "failed";
|
|
147
|
+
return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
148
|
+
scope: "shared-work",
|
|
149
|
+
phase,
|
|
150
|
+
objective: `message to ${target}`,
|
|
151
|
+
outcomeText: `${lead}\n${result.detail}`,
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
function renderInnerProgressStatus(status) {
|
|
155
|
+
if (status.processing === "pending") {
|
|
156
|
+
return "i've queued this thought for private attention. it'll come up when my inner dialog is free.";
|
|
157
|
+
}
|
|
158
|
+
if (status.processing === "started") {
|
|
159
|
+
return "i'm working through this privately right now.";
|
|
160
|
+
}
|
|
161
|
+
// processed / completed
|
|
162
|
+
if (status.surfaced && status.surfaced !== "nothing recent" && status.surfaced !== "no outward result") {
|
|
163
|
+
return `i thought about this privately and came to something: ${status.surfaced}`;
|
|
164
|
+
}
|
|
165
|
+
return "i thought about this privately. i'll bring it back when the time is right.";
|
|
166
|
+
}
|
|
48
167
|
exports.baseToolDefinitions = [
|
|
49
168
|
{
|
|
50
169
|
tool: {
|
|
@@ -54,12 +173,28 @@ exports.baseToolDefinitions = [
|
|
|
54
173
|
description: "read file contents",
|
|
55
174
|
parameters: {
|
|
56
175
|
type: "object",
|
|
57
|
-
properties: {
|
|
176
|
+
properties: {
|
|
177
|
+
path: { type: "string" },
|
|
178
|
+
offset: { type: "number", description: "1-based line number to start reading from" },
|
|
179
|
+
limit: { type: "number", description: "maximum number of lines to return" },
|
|
180
|
+
},
|
|
58
181
|
required: ["path"],
|
|
59
182
|
},
|
|
60
183
|
},
|
|
61
184
|
},
|
|
62
|
-
handler: (a) =>
|
|
185
|
+
handler: (a) => {
|
|
186
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
187
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
188
|
+
exports.editFileReadTracker.add(resolvedPath);
|
|
189
|
+
const offset = a.offset ? parseInt(a.offset, 10) : undefined;
|
|
190
|
+
const limit = a.limit ? parseInt(a.limit, 10) : undefined;
|
|
191
|
+
if (offset === undefined && limit === undefined)
|
|
192
|
+
return content;
|
|
193
|
+
const lines = content.split("\n");
|
|
194
|
+
const start = offset ? offset - 1 : 0;
|
|
195
|
+
const end = limit !== undefined ? start + limit : lines.length;
|
|
196
|
+
return lines.slice(start, end).join("\n");
|
|
197
|
+
},
|
|
63
198
|
},
|
|
64
199
|
{
|
|
65
200
|
tool: {
|
|
@@ -74,102 +209,244 @@ exports.baseToolDefinitions = [
|
|
|
74
209
|
},
|
|
75
210
|
},
|
|
76
211
|
},
|
|
77
|
-
handler: (a) =>
|
|
212
|
+
handler: (a) => {
|
|
213
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
214
|
+
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
215
|
+
fs.writeFileSync(resolvedPath, a.content, "utf-8");
|
|
216
|
+
return "ok";
|
|
217
|
+
},
|
|
78
218
|
},
|
|
79
219
|
{
|
|
80
220
|
tool: {
|
|
81
221
|
type: "function",
|
|
82
222
|
function: {
|
|
83
|
-
name: "
|
|
84
|
-
description: "
|
|
223
|
+
name: "edit_file",
|
|
224
|
+
description: "surgically edit a file by replacing an exact string. the file must have been read via read_file first. old_string must match exactly one location in the file.",
|
|
85
225
|
parameters: {
|
|
86
226
|
type: "object",
|
|
87
|
-
properties: {
|
|
88
|
-
|
|
227
|
+
properties: {
|
|
228
|
+
path: { type: "string" },
|
|
229
|
+
old_string: { type: "string" },
|
|
230
|
+
new_string: { type: "string" },
|
|
231
|
+
},
|
|
232
|
+
required: ["path", "old_string", "new_string"],
|
|
89
233
|
},
|
|
90
234
|
},
|
|
91
235
|
},
|
|
92
|
-
handler: (a) =>
|
|
236
|
+
handler: (a) => {
|
|
237
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
238
|
+
if (!exports.editFileReadTracker.has(resolvedPath)) {
|
|
239
|
+
return `error: you must read the file with read_file before editing it. call read_file on ${a.path} first.`;
|
|
240
|
+
}
|
|
241
|
+
let content;
|
|
242
|
+
try {
|
|
243
|
+
content = fs.readFileSync(resolvedPath, "utf-8");
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
return `error: could not read file: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
247
|
+
}
|
|
248
|
+
// Count occurrences
|
|
249
|
+
const occurrences = [];
|
|
250
|
+
let searchFrom = 0;
|
|
251
|
+
while (true) {
|
|
252
|
+
const idx = content.indexOf(a.old_string, searchFrom);
|
|
253
|
+
if (idx === -1)
|
|
254
|
+
break;
|
|
255
|
+
occurrences.push(idx);
|
|
256
|
+
searchFrom = idx + 1;
|
|
257
|
+
}
|
|
258
|
+
if (occurrences.length === 0) {
|
|
259
|
+
return `error: old_string not found in ${a.path}`;
|
|
260
|
+
}
|
|
261
|
+
if (occurrences.length > 1) {
|
|
262
|
+
return `error: old_string is ambiguous -- found ${occurrences.length} matches in ${a.path}. provide more context to make the match unique.`;
|
|
263
|
+
}
|
|
264
|
+
// Single unique match -- replace
|
|
265
|
+
const idx = occurrences[0];
|
|
266
|
+
const updated = content.slice(0, idx) + a.new_string + content.slice(idx + a.old_string.length);
|
|
267
|
+
fs.writeFileSync(resolvedPath, updated, "utf-8");
|
|
268
|
+
// Build contextual diff
|
|
269
|
+
const lines = updated.split("\n");
|
|
270
|
+
const prefixLines = content.slice(0, idx).split("\n");
|
|
271
|
+
const changeStartLine = prefixLines.length - 1;
|
|
272
|
+
const newStringLines = a.new_string.split("\n");
|
|
273
|
+
const changeEndLine = changeStartLine + newStringLines.length;
|
|
274
|
+
return buildContextDiff(lines, changeStartLine, changeEndLine);
|
|
275
|
+
},
|
|
93
276
|
},
|
|
94
277
|
{
|
|
95
278
|
tool: {
|
|
96
279
|
type: "function",
|
|
97
280
|
function: {
|
|
98
|
-
name: "
|
|
99
|
-
description: "
|
|
281
|
+
name: "glob",
|
|
282
|
+
description: "find files matching a glob pattern. returns matching paths sorted alphabetically, one per line.",
|
|
100
283
|
parameters: {
|
|
101
284
|
type: "object",
|
|
102
|
-
properties: {
|
|
103
|
-
|
|
285
|
+
properties: {
|
|
286
|
+
pattern: { type: "string", description: "glob pattern (e.g. **/*.ts)" },
|
|
287
|
+
cwd: { type: "string", description: "directory to search from (defaults to process.cwd())" },
|
|
288
|
+
},
|
|
289
|
+
required: ["pattern"],
|
|
104
290
|
},
|
|
105
291
|
},
|
|
106
292
|
},
|
|
107
|
-
handler: (a) =>
|
|
108
|
-
.
|
|
109
|
-
|
|
110
|
-
.join("\n")
|
|
293
|
+
handler: (a) => {
|
|
294
|
+
const cwd = a.cwd ? resolveLocalToolPath(a.cwd) : process.cwd();
|
|
295
|
+
const matches = fg.globSync(a.pattern, { cwd, dot: true });
|
|
296
|
+
return matches.sort().join("\n");
|
|
297
|
+
},
|
|
111
298
|
},
|
|
112
299
|
{
|
|
113
300
|
tool: {
|
|
114
301
|
type: "function",
|
|
115
302
|
function: {
|
|
116
|
-
name: "
|
|
117
|
-
description: "
|
|
303
|
+
name: "grep",
|
|
304
|
+
description: "search file contents for lines matching a regex pattern. searches recursively when given a directory. returns matching lines with file path and line numbers.",
|
|
118
305
|
parameters: {
|
|
119
306
|
type: "object",
|
|
120
307
|
properties: {
|
|
121
|
-
|
|
122
|
-
|
|
308
|
+
pattern: { type: "string", description: "regex pattern to search for" },
|
|
309
|
+
path: { type: "string", description: "file or directory to search" },
|
|
310
|
+
context_lines: { type: "number", description: "number of surrounding context lines (default 0)" },
|
|
311
|
+
include: { type: "string", description: "glob filter to limit searched files (e.g. *.ts)" },
|
|
123
312
|
},
|
|
124
|
-
required: ["
|
|
313
|
+
required: ["pattern", "path"],
|
|
125
314
|
},
|
|
126
315
|
},
|
|
127
316
|
},
|
|
128
317
|
handler: (a) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
318
|
+
const targetPath = resolveLocalToolPath(a.path);
|
|
319
|
+
const regex = new RegExp(a.pattern);
|
|
320
|
+
const contextLines = parseInt(a.context_lines || "0", 10);
|
|
321
|
+
const includeGlob = a.include || undefined;
|
|
322
|
+
function searchFile(filePath) {
|
|
323
|
+
let content;
|
|
324
|
+
try {
|
|
325
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
const lines = content.split("\n");
|
|
331
|
+
const matchIndices = new Set();
|
|
332
|
+
for (let i = 0; i < lines.length; i++) {
|
|
333
|
+
if (regex.test(lines[i])) {
|
|
334
|
+
matchIndices.add(i);
|
|
335
|
+
}
|
|
132
336
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
337
|
+
if (matchIndices.size === 0)
|
|
338
|
+
return [];
|
|
339
|
+
const outputIndices = new Set();
|
|
340
|
+
for (const idx of matchIndices) {
|
|
341
|
+
const start = Math.max(0, idx - contextLines);
|
|
342
|
+
const end = Math.min(lines.length - 1, idx + contextLines);
|
|
343
|
+
for (let i = start; i <= end; i++) {
|
|
344
|
+
outputIndices.add(i);
|
|
136
345
|
}
|
|
137
|
-
(0, child_process_1.execSync)(`git add ${p}`, { encoding: "utf-8" });
|
|
138
346
|
}
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
347
|
+
const sortedIndices = [...outputIndices].sort((a, b) => a - b);
|
|
348
|
+
const results = [];
|
|
349
|
+
for (const idx of sortedIndices) {
|
|
350
|
+
const lineNum = idx + 1;
|
|
351
|
+
if (matchIndices.has(idx)) {
|
|
352
|
+
results.push(`${filePath}:${lineNum}: ${lines[idx]}`);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
results.push(`-${filePath}:${lineNum}: ${lines[idx]}`);
|
|
356
|
+
}
|
|
142
357
|
}
|
|
143
|
-
|
|
144
|
-
return `${diff}\ncommitted`;
|
|
358
|
+
return results;
|
|
145
359
|
}
|
|
146
|
-
|
|
147
|
-
|
|
360
|
+
function collectFiles(dirPath) {
|
|
361
|
+
const files = [];
|
|
362
|
+
function walk(dir) {
|
|
363
|
+
let entries;
|
|
364
|
+
try {
|
|
365
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
for (const entry of entries) {
|
|
371
|
+
const fullPath = path.join(dir, entry.name);
|
|
372
|
+
if (entry.isDirectory()) {
|
|
373
|
+
walk(fullPath);
|
|
374
|
+
}
|
|
375
|
+
else if (entry.isFile()) {
|
|
376
|
+
files.push(fullPath);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
walk(dirPath);
|
|
381
|
+
return files.sort();
|
|
382
|
+
}
|
|
383
|
+
function matchesGlob(filePath, glob) {
|
|
384
|
+
const escaped = glob
|
|
385
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
386
|
+
.replace(/\*/g, ".*")
|
|
387
|
+
.replace(/\?/g, ".");
|
|
388
|
+
return new RegExp(`(^|/)${escaped}$`).test(filePath);
|
|
389
|
+
}
|
|
390
|
+
const stat = fs.statSync(targetPath, { throwIfNoEntry: false });
|
|
391
|
+
if (!stat)
|
|
392
|
+
return "";
|
|
393
|
+
if (stat.isFile()) {
|
|
394
|
+
return searchFile(targetPath).join("\n");
|
|
148
395
|
}
|
|
396
|
+
let files = collectFiles(targetPath);
|
|
397
|
+
if (includeGlob) {
|
|
398
|
+
files = files.filter((f) => matchesGlob(f, includeGlob));
|
|
399
|
+
}
|
|
400
|
+
const allResults = [];
|
|
401
|
+
for (const file of files) {
|
|
402
|
+
allResults.push(...searchFile(file));
|
|
403
|
+
}
|
|
404
|
+
return allResults.join("\n");
|
|
149
405
|
},
|
|
150
406
|
},
|
|
151
407
|
{
|
|
152
408
|
tool: {
|
|
153
409
|
type: "function",
|
|
154
410
|
function: {
|
|
155
|
-
name: "
|
|
156
|
-
description: "
|
|
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.",
|
|
157
413
|
parameters: {
|
|
158
414
|
type: "object",
|
|
159
|
-
properties: {
|
|
160
|
-
|
|
161
|
-
|
|
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
|
+
},
|
|
430
|
+
{
|
|
431
|
+
tool: {
|
|
432
|
+
type: "function",
|
|
433
|
+
function: {
|
|
434
|
+
name: "shell",
|
|
435
|
+
description: "run shell command",
|
|
436
|
+
parameters: {
|
|
437
|
+
type: "object",
|
|
438
|
+
properties: { command: { type: "string" } },
|
|
162
439
|
required: ["command"],
|
|
163
440
|
},
|
|
164
441
|
},
|
|
165
442
|
},
|
|
166
443
|
handler: (a) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
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
|
+
});
|
|
173
450
|
},
|
|
174
451
|
},
|
|
175
452
|
{
|
|
@@ -205,20 +482,6 @@ exports.baseToolDefinitions = [
|
|
|
205
482
|
}
|
|
206
483
|
},
|
|
207
484
|
},
|
|
208
|
-
{
|
|
209
|
-
tool: {
|
|
210
|
-
type: "function",
|
|
211
|
-
function: {
|
|
212
|
-
name: "get_current_time",
|
|
213
|
-
description: "get the current date and time in America/Los_Angeles (Pacific Time)",
|
|
214
|
-
parameters: { type: "object", properties: {} },
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
handler: () => new Date().toLocaleString("en-US", {
|
|
218
|
-
timeZone: "America/Los_Angeles",
|
|
219
|
-
hour12: false,
|
|
220
|
-
}),
|
|
221
|
-
},
|
|
222
485
|
{
|
|
223
486
|
tool: {
|
|
224
487
|
type: "function",
|
|
@@ -234,7 +497,7 @@ exports.baseToolDefinitions = [
|
|
|
234
497
|
},
|
|
235
498
|
handler: (a) => {
|
|
236
499
|
try {
|
|
237
|
-
const result = (0, child_process_1.spawnSync)("claude", ["-p", "--dangerously-skip-permissions", "--add-dir", "."], {
|
|
500
|
+
const result = (0, child_process_1.spawnSync)("claude", ["-p", "--no-session-persistence", "--dangerously-skip-permissions", "--add-dir", "."], {
|
|
238
501
|
input: a.prompt,
|
|
239
502
|
encoding: "utf-8",
|
|
240
503
|
timeout: 60000,
|
|
@@ -374,151 +637,6 @@ exports.baseToolDefinitions = [
|
|
|
374
637
|
return JSON.stringify(friend, null, 2);
|
|
375
638
|
},
|
|
376
639
|
},
|
|
377
|
-
{
|
|
378
|
-
tool: {
|
|
379
|
-
type: "function",
|
|
380
|
-
function: {
|
|
381
|
-
name: "task_board",
|
|
382
|
-
description: "show the task board grouped by status",
|
|
383
|
-
parameters: { type: "object", properties: {} },
|
|
384
|
-
},
|
|
385
|
-
},
|
|
386
|
-
handler: () => {
|
|
387
|
-
const board = (0, tasks_1.getTaskModule)().getBoard();
|
|
388
|
-
return board.full || board.compact || "no tasks found";
|
|
389
|
-
},
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
tool: {
|
|
393
|
-
type: "function",
|
|
394
|
-
function: {
|
|
395
|
-
name: "task_create",
|
|
396
|
-
description: "create a new task in the bundle task system",
|
|
397
|
-
parameters: {
|
|
398
|
-
type: "object",
|
|
399
|
-
properties: {
|
|
400
|
-
title: { type: "string" },
|
|
401
|
-
type: { type: "string", enum: ["one-shot", "ongoing", "habit"] },
|
|
402
|
-
category: { type: "string" },
|
|
403
|
-
body: { type: "string" },
|
|
404
|
-
},
|
|
405
|
-
required: ["title", "type", "category", "body"],
|
|
406
|
-
},
|
|
407
|
-
},
|
|
408
|
-
},
|
|
409
|
-
handler: (a) => {
|
|
410
|
-
try {
|
|
411
|
-
const created = (0, tasks_1.getTaskModule)().createTask({
|
|
412
|
-
title: a.title,
|
|
413
|
-
type: a.type,
|
|
414
|
-
category: a.category,
|
|
415
|
-
body: a.body,
|
|
416
|
-
});
|
|
417
|
-
return `created: ${created}`;
|
|
418
|
-
}
|
|
419
|
-
catch (error) {
|
|
420
|
-
return `error: ${error instanceof Error ? error.message : String(error)}`;
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
tool: {
|
|
426
|
-
type: "function",
|
|
427
|
-
function: {
|
|
428
|
-
name: "task_update_status",
|
|
429
|
-
description: "update a task status using validated transitions",
|
|
430
|
-
parameters: {
|
|
431
|
-
type: "object",
|
|
432
|
-
properties: {
|
|
433
|
-
name: { type: "string" },
|
|
434
|
-
status: { type: "string" },
|
|
435
|
-
},
|
|
436
|
-
required: ["name", "status"],
|
|
437
|
-
},
|
|
438
|
-
},
|
|
439
|
-
},
|
|
440
|
-
handler: (a) => {
|
|
441
|
-
const result = (0, tasks_1.getTaskModule)().updateStatus(a.name, a.status);
|
|
442
|
-
if (!result.ok) {
|
|
443
|
-
return `error: ${result.reason ?? "status update failed"}`;
|
|
444
|
-
}
|
|
445
|
-
const archivedSuffix = result.archived && result.archived.length > 0
|
|
446
|
-
? ` | archived: ${result.archived.join(", ")}`
|
|
447
|
-
: "";
|
|
448
|
-
return `updated: ${a.name} -> ${result.to}${archivedSuffix}`;
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
|
-
{
|
|
452
|
-
tool: {
|
|
453
|
-
type: "function",
|
|
454
|
-
function: {
|
|
455
|
-
name: "task_board_status",
|
|
456
|
-
description: "show board detail for a specific status",
|
|
457
|
-
parameters: {
|
|
458
|
-
type: "object",
|
|
459
|
-
properties: {
|
|
460
|
-
status: { type: "string" },
|
|
461
|
-
},
|
|
462
|
-
required: ["status"],
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
|
-
handler: (a) => {
|
|
467
|
-
const lines = (0, tasks_1.getTaskModule)().boardStatus(a.status);
|
|
468
|
-
return lines.length > 0 ? lines.join("\n") : "no tasks in that status";
|
|
469
|
-
},
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
tool: {
|
|
473
|
-
type: "function",
|
|
474
|
-
function: {
|
|
475
|
-
name: "task_board_action",
|
|
476
|
-
description: "show tasks or validation issues that require action",
|
|
477
|
-
parameters: {
|
|
478
|
-
type: "object",
|
|
479
|
-
properties: {
|
|
480
|
-
scope: { type: "string" },
|
|
481
|
-
},
|
|
482
|
-
},
|
|
483
|
-
},
|
|
484
|
-
},
|
|
485
|
-
handler: (a) => {
|
|
486
|
-
const lines = (0, tasks_1.getTaskModule)().boardAction();
|
|
487
|
-
if (!a.scope) {
|
|
488
|
-
return lines.length > 0 ? lines.join("\n") : "no action required";
|
|
489
|
-
}
|
|
490
|
-
const filtered = lines.filter((line) => line.includes(a.scope));
|
|
491
|
-
return filtered.length > 0 ? filtered.join("\n") : "no matching action items";
|
|
492
|
-
},
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
tool: {
|
|
496
|
-
type: "function",
|
|
497
|
-
function: {
|
|
498
|
-
name: "task_board_deps",
|
|
499
|
-
description: "show unresolved task dependencies",
|
|
500
|
-
parameters: { type: "object", properties: {} },
|
|
501
|
-
},
|
|
502
|
-
},
|
|
503
|
-
handler: () => {
|
|
504
|
-
const lines = (0, tasks_1.getTaskModule)().boardDeps();
|
|
505
|
-
return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
|
|
506
|
-
},
|
|
507
|
-
},
|
|
508
|
-
{
|
|
509
|
-
tool: {
|
|
510
|
-
type: "function",
|
|
511
|
-
function: {
|
|
512
|
-
name: "task_board_sessions",
|
|
513
|
-
description: "show tasks with active coding or sub-agent sessions",
|
|
514
|
-
parameters: { type: "object", properties: {} },
|
|
515
|
-
},
|
|
516
|
-
},
|
|
517
|
-
handler: () => {
|
|
518
|
-
const lines = (0, tasks_1.getTaskModule)().boardSessions();
|
|
519
|
-
return lines.length > 0 ? lines.join("\n") : "no active sessions";
|
|
520
|
-
},
|
|
521
|
-
},
|
|
522
640
|
{
|
|
523
641
|
tool: {
|
|
524
642
|
type: "function",
|
|
@@ -602,12 +720,448 @@ exports.baseToolDefinitions = [
|
|
|
602
720
|
}
|
|
603
721
|
},
|
|
604
722
|
},
|
|
723
|
+
// -- cross-session awareness --
|
|
724
|
+
{
|
|
725
|
+
tool: {
|
|
726
|
+
type: "function",
|
|
727
|
+
function: {
|
|
728
|
+
name: "bridge_manage",
|
|
729
|
+
description: "create and manage shared live-work bridges across already-active sessions.",
|
|
730
|
+
parameters: {
|
|
731
|
+
type: "object",
|
|
732
|
+
properties: {
|
|
733
|
+
action: {
|
|
734
|
+
type: "string",
|
|
735
|
+
enum: ["begin", "attach", "status", "promote_task", "complete", "cancel"],
|
|
736
|
+
},
|
|
737
|
+
bridgeId: { type: "string", description: "bridge id for all actions except begin" },
|
|
738
|
+
objective: { type: "string", description: "objective for begin" },
|
|
739
|
+
summary: { type: "string", description: "optional concise shared-work summary" },
|
|
740
|
+
friendId: { type: "string", description: "target friend id for attach" },
|
|
741
|
+
channel: { type: "string", description: "target channel for attach" },
|
|
742
|
+
key: { type: "string", description: "target session key for attach (defaults to 'session')" },
|
|
743
|
+
title: { type: "string", description: "task title override for promote_task" },
|
|
744
|
+
category: { type: "string", description: "task category override for promote_task" },
|
|
745
|
+
body: { type: "string", description: "task body override for promote_task" },
|
|
746
|
+
},
|
|
747
|
+
required: ["action"],
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
handler: async (args, ctx) => {
|
|
752
|
+
const manager = (0, manager_1.createBridgeManager)();
|
|
753
|
+
const action = (args.action || "").trim();
|
|
754
|
+
if (action === "begin") {
|
|
755
|
+
if (!ctx?.currentSession) {
|
|
756
|
+
return "bridge_manage begin requires an active session context.";
|
|
757
|
+
}
|
|
758
|
+
const objective = (args.objective || "").trim();
|
|
759
|
+
if (!objective)
|
|
760
|
+
return "objective is required for bridge begin.";
|
|
761
|
+
return (0, manager_1.formatBridgeStatus)(manager.beginBridge({
|
|
762
|
+
objective,
|
|
763
|
+
summary: (args.summary || objective).trim(),
|
|
764
|
+
session: ctx.currentSession,
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
767
|
+
const bridgeId = (args.bridgeId || "").trim();
|
|
768
|
+
if (!bridgeId) {
|
|
769
|
+
return "bridgeId is required for this bridge action.";
|
|
770
|
+
}
|
|
771
|
+
if (action === "attach") {
|
|
772
|
+
const friendId = (args.friendId || "").trim();
|
|
773
|
+
const channel = (args.channel || "").trim();
|
|
774
|
+
const key = (args.key || "session").trim();
|
|
775
|
+
if (!friendId || !channel) {
|
|
776
|
+
return "friendId and channel are required for bridge attach.";
|
|
777
|
+
}
|
|
778
|
+
const sessionPath = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
779
|
+
const recall = await recallSessionSafely({
|
|
780
|
+
sessionPath,
|
|
781
|
+
friendId,
|
|
782
|
+
channel,
|
|
783
|
+
key,
|
|
784
|
+
messageCount: 20,
|
|
785
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
786
|
+
summarize: ctx?.summarize,
|
|
787
|
+
});
|
|
788
|
+
if (recall.kind === "missing") {
|
|
789
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
790
|
+
}
|
|
791
|
+
return (0, manager_1.formatBridgeStatus)(manager.attachSession(bridgeId, {
|
|
792
|
+
friendId,
|
|
793
|
+
channel,
|
|
794
|
+
key,
|
|
795
|
+
sessionPath,
|
|
796
|
+
snapshot: recall.kind === "ok" ? recall.snapshot : EMPTY_SESSION_MESSAGE,
|
|
797
|
+
}));
|
|
798
|
+
}
|
|
799
|
+
if (action === "status") {
|
|
800
|
+
const bridge = manager.getBridge(bridgeId);
|
|
801
|
+
if (!bridge)
|
|
802
|
+
return `bridge not found: ${bridgeId}`;
|
|
803
|
+
return (0, manager_1.formatBridgeStatus)(bridge);
|
|
804
|
+
}
|
|
805
|
+
if (action === "promote_task") {
|
|
806
|
+
return (0, manager_1.formatBridgeStatus)(manager.promoteBridgeToTask(bridgeId, {
|
|
807
|
+
title: args.title,
|
|
808
|
+
category: args.category,
|
|
809
|
+
body: args.body,
|
|
810
|
+
}));
|
|
811
|
+
}
|
|
812
|
+
if (action === "complete") {
|
|
813
|
+
return (0, manager_1.formatBridgeStatus)(manager.completeBridge(bridgeId));
|
|
814
|
+
}
|
|
815
|
+
if (action === "cancel") {
|
|
816
|
+
return (0, manager_1.formatBridgeStatus)(manager.cancelBridge(bridgeId));
|
|
817
|
+
}
|
|
818
|
+
return `unknown bridge action: ${action}`;
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
tool: {
|
|
823
|
+
type: "function",
|
|
824
|
+
function: {
|
|
825
|
+
name: "query_session",
|
|
826
|
+
description: "read the last messages from another session. use this to check on a conversation with a friend or review your own inner dialog.",
|
|
827
|
+
parameters: {
|
|
828
|
+
type: "object",
|
|
829
|
+
properties: {
|
|
830
|
+
friendId: { type: "string", description: "the friend UUID (or 'self')" },
|
|
831
|
+
channel: { type: "string", description: "the channel: cli, teams, or inner" },
|
|
832
|
+
key: { type: "string", description: "session key (defaults to 'session')" },
|
|
833
|
+
messageCount: { type: "string", description: "how many recent messages to return (default 20)" },
|
|
834
|
+
mode: { type: "string", enum: ["transcript", "status"], description: "transcript (default) or lightweight status for self/inner checks" },
|
|
835
|
+
},
|
|
836
|
+
required: ["friendId", "channel"],
|
|
837
|
+
},
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
handler: async (args, ctx) => {
|
|
841
|
+
const friendId = args.friendId;
|
|
842
|
+
const channel = args.channel;
|
|
843
|
+
const key = args.key || "session";
|
|
844
|
+
const count = parseInt(args.messageCount || "20", 10);
|
|
845
|
+
const mode = args.mode || "transcript";
|
|
846
|
+
if (mode === "status") {
|
|
847
|
+
if (friendId !== "self" || channel !== "inner") {
|
|
848
|
+
return "status mode is only available for self/inner dialog.";
|
|
849
|
+
}
|
|
850
|
+
const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)((0, identity_1.getAgentRoot)());
|
|
851
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
852
|
+
return renderInnerProgressStatus((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
|
|
853
|
+
}
|
|
854
|
+
const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
855
|
+
const recall = await recallSessionSafely({
|
|
856
|
+
sessionPath: sessFile,
|
|
857
|
+
friendId,
|
|
858
|
+
channel,
|
|
859
|
+
key,
|
|
860
|
+
messageCount: count,
|
|
861
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
862
|
+
summarize: ctx?.summarize,
|
|
863
|
+
});
|
|
864
|
+
if (recall.kind === "missing") {
|
|
865
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
866
|
+
}
|
|
867
|
+
if (recall.kind === "empty") {
|
|
868
|
+
return EMPTY_SESSION_MESSAGE;
|
|
869
|
+
}
|
|
870
|
+
return recall.summary;
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
tool: {
|
|
875
|
+
type: "function",
|
|
876
|
+
function: {
|
|
877
|
+
name: "send_message",
|
|
878
|
+
description: "send a message to a friend's session. when the request is explicitly authorized from a trusted live chat, the harness will try to deliver immediately; otherwise it reports truthful queued/block/failure state.",
|
|
879
|
+
parameters: {
|
|
880
|
+
type: "object",
|
|
881
|
+
properties: {
|
|
882
|
+
friendId: { type: "string", description: "the friend UUID (or 'self')" },
|
|
883
|
+
channel: { type: "string", description: "the channel: cli, teams, or inner" },
|
|
884
|
+
key: { type: "string", description: "session key (defaults to 'session')" },
|
|
885
|
+
content: { type: "string", description: "the message content to send" },
|
|
886
|
+
},
|
|
887
|
+
required: ["friendId", "channel", "content"],
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
handler: async (args, ctx) => {
|
|
892
|
+
const friendId = args.friendId;
|
|
893
|
+
const channel = args.channel;
|
|
894
|
+
const key = args.key || "session";
|
|
895
|
+
const content = args.content;
|
|
896
|
+
const now = Date.now();
|
|
897
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
898
|
+
// Self-routing: messages to "self" always go to inner dialog pending dir,
|
|
899
|
+
// regardless of the channel or key the agent specified.
|
|
900
|
+
const isSelf = friendId === "self";
|
|
901
|
+
const pendingDir = isSelf
|
|
902
|
+
? (0, pending_1.getInnerDialogPendingDir)(agentName)
|
|
903
|
+
: (0, pending_1.getPendingDir)(agentName, friendId, channel, key);
|
|
904
|
+
const delegatingBridgeId = findDelegatingBridgeId(ctx);
|
|
905
|
+
const delegatedFrom = isSelf
|
|
906
|
+
&& ctx?.currentSession
|
|
907
|
+
&& !(ctx.currentSession.friendId === "self" && ctx.currentSession.channel === "inner")
|
|
908
|
+
? {
|
|
909
|
+
friendId: ctx.currentSession.friendId,
|
|
910
|
+
channel: ctx.currentSession.channel,
|
|
911
|
+
key: ctx.currentSession.key,
|
|
912
|
+
...(delegatingBridgeId ? { bridgeId: delegatingBridgeId } : {}),
|
|
913
|
+
}
|
|
914
|
+
: undefined;
|
|
915
|
+
const envelope = {
|
|
916
|
+
from: agentName,
|
|
917
|
+
friendId,
|
|
918
|
+
channel,
|
|
919
|
+
key,
|
|
920
|
+
content,
|
|
921
|
+
timestamp: now,
|
|
922
|
+
...(delegatedFrom ? { delegatedFrom, obligationStatus: "pending" } : {}),
|
|
923
|
+
};
|
|
924
|
+
if (isSelf) {
|
|
925
|
+
writePendingEnvelope(pendingDir, envelope);
|
|
926
|
+
if (delegatedFrom) {
|
|
927
|
+
try {
|
|
928
|
+
(0, obligations_1.createObligation)((0, identity_1.getAgentRoot)(), {
|
|
929
|
+
origin: {
|
|
930
|
+
friendId: delegatedFrom.friendId,
|
|
931
|
+
channel: delegatedFrom.channel,
|
|
932
|
+
key: delegatedFrom.key,
|
|
933
|
+
},
|
|
934
|
+
...(delegatedFrom.bridgeId ? { bridgeId: delegatedFrom.bridgeId } : {}),
|
|
935
|
+
content,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
catch {
|
|
939
|
+
/* v8 ignore next -- defensive: obligation store write failure should not break send_message @preserve */
|
|
940
|
+
}
|
|
941
|
+
(0, runtime_1.emitNervesEvent)({
|
|
942
|
+
event: "repertoire.obligation_created",
|
|
943
|
+
component: "repertoire",
|
|
944
|
+
message: "obligation created for inner dialog delegation",
|
|
945
|
+
meta: {
|
|
946
|
+
friendId: delegatedFrom.friendId,
|
|
947
|
+
channel: delegatedFrom.channel,
|
|
948
|
+
key: delegatedFrom.key,
|
|
949
|
+
},
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
let wakeResponse = null;
|
|
953
|
+
try {
|
|
954
|
+
wakeResponse = await (0, socket_client_1.requestInnerWake)(agentName);
|
|
955
|
+
}
|
|
956
|
+
catch {
|
|
957
|
+
wakeResponse = null;
|
|
958
|
+
}
|
|
959
|
+
if (!wakeResponse?.ok) {
|
|
960
|
+
const { runInnerDialogTurn } = await Promise.resolve().then(() => __importStar(require("../senses/inner-dialog")));
|
|
961
|
+
if (ctx?.context?.channel.channel === "inner") {
|
|
962
|
+
queueMicrotask(() => {
|
|
963
|
+
void runInnerDialogTurn({ reason: "instinct" });
|
|
964
|
+
});
|
|
965
|
+
return renderInnerProgressStatus({
|
|
966
|
+
queue: "queued to inner/dialog",
|
|
967
|
+
wake: "inline scheduled",
|
|
968
|
+
processing: "pending",
|
|
969
|
+
surfaced: "nothing yet",
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
const turnResult = await runInnerDialogTurn({ reason: "instinct" });
|
|
974
|
+
const surfacedPreview = normalizeProgressOutcome((0, thoughts_1.formatSurfacedValue)((0, thoughts_1.extractThoughtResponseFromMessages)(turnResult?.messages ?? [])));
|
|
975
|
+
return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
976
|
+
scope: "inner-delegation",
|
|
977
|
+
phase: "completed",
|
|
978
|
+
objective: "queued to inner/dialog",
|
|
979
|
+
outcomeText: `wake: inline fallback\n${surfacedPreview}`,
|
|
980
|
+
}));
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return renderInnerProgressStatus({
|
|
984
|
+
queue: "queued to inner/dialog",
|
|
985
|
+
wake: "daemon requested",
|
|
986
|
+
processing: "pending",
|
|
987
|
+
surfaced: "nothing yet",
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
const deliveryResult = await (0, cross_chat_delivery_1.deliverCrossChatMessage)({
|
|
991
|
+
friendId,
|
|
992
|
+
channel,
|
|
993
|
+
key,
|
|
994
|
+
content,
|
|
995
|
+
intent: ctx?.currentSession && ctx.currentSession.friendId !== "self"
|
|
996
|
+
? "explicit_cross_chat"
|
|
997
|
+
: "generic_outreach",
|
|
998
|
+
...(ctx?.currentSession && ctx.currentSession.friendId !== "self"
|
|
999
|
+
? {
|
|
1000
|
+
authorizingSession: {
|
|
1001
|
+
friendId: ctx.currentSession.friendId,
|
|
1002
|
+
channel: ctx.currentSession.channel,
|
|
1003
|
+
key: ctx.currentSession.key,
|
|
1004
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
1005
|
+
},
|
|
1006
|
+
}
|
|
1007
|
+
: {}),
|
|
1008
|
+
}, {
|
|
1009
|
+
agentName,
|
|
1010
|
+
queuePending: (message) => writePendingEnvelope(pendingDir, message),
|
|
1011
|
+
deliverers: {
|
|
1012
|
+
bluebubbles: async (request) => {
|
|
1013
|
+
const { sendProactiveBlueBubblesMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/bluebubbles")));
|
|
1014
|
+
const result = await sendProactiveBlueBubblesMessageToSession({
|
|
1015
|
+
friendId: request.friendId,
|
|
1016
|
+
sessionKey: request.key,
|
|
1017
|
+
text: request.content,
|
|
1018
|
+
intent: request.intent,
|
|
1019
|
+
authorizingSession: request.authorizingSession,
|
|
1020
|
+
});
|
|
1021
|
+
if (result.delivered) {
|
|
1022
|
+
return {
|
|
1023
|
+
status: "delivered_now",
|
|
1024
|
+
detail: "sent to the active bluebubbles chat now",
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
if (result.reason === "missing_target") {
|
|
1028
|
+
return {
|
|
1029
|
+
status: "blocked",
|
|
1030
|
+
detail: "bluebubbles could not resolve a routable target for that session",
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
if (result.reason === "send_error") {
|
|
1034
|
+
return {
|
|
1035
|
+
status: "failed",
|
|
1036
|
+
detail: "bluebubbles send failed",
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
status: "unavailable",
|
|
1041
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
1042
|
+
};
|
|
1043
|
+
},
|
|
1044
|
+
teams: async (request) => {
|
|
1045
|
+
if (!ctx?.botApi) {
|
|
1046
|
+
return {
|
|
1047
|
+
status: "unavailable",
|
|
1048
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
const { sendProactiveTeamsMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/teams")));
|
|
1052
|
+
const result = await sendProactiveTeamsMessageToSession({
|
|
1053
|
+
friendId: request.friendId,
|
|
1054
|
+
sessionKey: request.key,
|
|
1055
|
+
text: request.content,
|
|
1056
|
+
intent: request.intent,
|
|
1057
|
+
authorizingSession: request.authorizingSession,
|
|
1058
|
+
}, {
|
|
1059
|
+
botApi: ctx.botApi,
|
|
1060
|
+
});
|
|
1061
|
+
if (result.delivered) {
|
|
1062
|
+
return {
|
|
1063
|
+
status: "delivered_now",
|
|
1064
|
+
detail: "sent to the active teams chat now",
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
if (result.reason === "missing_target") {
|
|
1068
|
+
return {
|
|
1069
|
+
status: "blocked",
|
|
1070
|
+
detail: "teams could not resolve a routable target for that session",
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
if (result.reason === "send_error") {
|
|
1074
|
+
return {
|
|
1075
|
+
status: "failed",
|
|
1076
|
+
detail: "teams send failed",
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
return {
|
|
1080
|
+
status: "unavailable",
|
|
1081
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
1082
|
+
};
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
});
|
|
1086
|
+
return renderCrossChatDeliveryStatus(`${friendId} on ${channel}/${key}`, deliveryResult);
|
|
1087
|
+
},
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
tool: {
|
|
1091
|
+
type: "function",
|
|
1092
|
+
function: {
|
|
1093
|
+
name: "set_reasoning_effort",
|
|
1094
|
+
description: "adjust your own reasoning depth for subsequent turns. use higher effort for complex analysis, lower for simple tasks.",
|
|
1095
|
+
parameters: {
|
|
1096
|
+
type: "object",
|
|
1097
|
+
properties: {
|
|
1098
|
+
level: { type: "string", description: "the reasoning effort level to set" },
|
|
1099
|
+
},
|
|
1100
|
+
required: ["level"],
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
},
|
|
1104
|
+
handler: (args, ctx) => {
|
|
1105
|
+
if (!ctx?.supportedReasoningEfforts || !ctx.setReasoningEffort) {
|
|
1106
|
+
return "reasoning effort adjustment is not available in this context.";
|
|
1107
|
+
}
|
|
1108
|
+
const level = (args.level || "").trim();
|
|
1109
|
+
if (!ctx.supportedReasoningEfforts.includes(level)) {
|
|
1110
|
+
return `invalid reasoning effort level "${level}". accepted levels: ${ctx.supportedReasoningEfforts.join(", ")}`;
|
|
1111
|
+
}
|
|
1112
|
+
ctx.setReasoningEffort(level);
|
|
1113
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1114
|
+
component: "repertoire",
|
|
1115
|
+
event: "repertoire.reasoning_effort_changed",
|
|
1116
|
+
message: `reasoning effort set to ${level}`,
|
|
1117
|
+
meta: { level },
|
|
1118
|
+
});
|
|
1119
|
+
return `reasoning effort set to "${level}".`;
|
|
1120
|
+
},
|
|
1121
|
+
requiredCapability: "reasoning-effort",
|
|
1122
|
+
},
|
|
605
1123
|
...tools_1.codingToolDefinitions,
|
|
606
1124
|
];
|
|
607
|
-
// Backward-compat: extract just the OpenAI tool schemas
|
|
608
1125
|
exports.tools = exports.baseToolDefinitions.map((d) => d.tool);
|
|
609
|
-
|
|
610
|
-
|
|
1126
|
+
exports.goInwardTool = {
|
|
1127
|
+
type: "function",
|
|
1128
|
+
function: {
|
|
1129
|
+
name: "go_inward",
|
|
1130
|
+
description: "i need to think about this privately. this takes the current thread inward -- i'll sit with it, work through it, or carry it to where it needs to go. must be the only tool call in the turn.",
|
|
1131
|
+
parameters: {
|
|
1132
|
+
type: "object",
|
|
1133
|
+
properties: {
|
|
1134
|
+
content: {
|
|
1135
|
+
type: "string",
|
|
1136
|
+
description: "what i need to think about -- the question, the thread, the thing that needs private attention",
|
|
1137
|
+
},
|
|
1138
|
+
answer: {
|
|
1139
|
+
type: "string",
|
|
1140
|
+
description: "if i want to say something outward before going inward -- an acknowledgment, a 'let me think about that', whatever feels right",
|
|
1141
|
+
},
|
|
1142
|
+
mode: {
|
|
1143
|
+
type: "string",
|
|
1144
|
+
enum: ["reflect", "plan", "relay"],
|
|
1145
|
+
description: "reflect: something to sit with. plan: something to work through. relay: something to carry across.",
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
required: ["content"],
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
};
|
|
1152
|
+
exports.noResponseTool = {
|
|
1153
|
+
type: "function",
|
|
1154
|
+
function: {
|
|
1155
|
+
name: "no_response",
|
|
1156
|
+
description: "stay silent in this group chat — the moment doesn't call for a response. must be the only tool call in the turn.",
|
|
1157
|
+
parameters: {
|
|
1158
|
+
type: "object",
|
|
1159
|
+
properties: {
|
|
1160
|
+
reason: { type: "string", description: "brief reason for staying silent (for logging)" },
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
};
|
|
611
1165
|
exports.finalAnswerTool = {
|
|
612
1166
|
type: "function",
|
|
613
1167
|
function: {
|
|
@@ -615,7 +1169,10 @@ exports.finalAnswerTool = {
|
|
|
615
1169
|
description: "respond to the user with your message. call this tool when you are ready to deliver your response.",
|
|
616
1170
|
parameters: {
|
|
617
1171
|
type: "object",
|
|
618
|
-
properties: {
|
|
1172
|
+
properties: {
|
|
1173
|
+
answer: { type: "string" },
|
|
1174
|
+
intent: { type: "string", enum: ["complete", "blocked", "direct_reply"] },
|
|
1175
|
+
},
|
|
619
1176
|
required: ["answer"],
|
|
620
1177
|
},
|
|
621
1178
|
},
|