@ouro.bot/cli 0.1.0-alpha.18 → 0.1.0-alpha.181
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 +3 -2
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +3 -3
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +187 -191
- package/changelog.json +1219 -0
- package/dist/heart/active-work.js +633 -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 +105 -0
- package/dist/heart/config.js +40 -30
- package/dist/heart/core.js +620 -124
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/auth-flow.js +477 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/daemon-cli.js +2476 -206
- package/dist/heart/daemon/daemon-entry.js +157 -4
- package/dist/heart/daemon/daemon-health.js +136 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon-tombstone.js +123 -0
- package/dist/heart/daemon/daemon.js +449 -17
- package/dist/heart/daemon/habit-migration.js +181 -0
- package/dist/heart/daemon/habit-parser.js +119 -0
- package/dist/heart/daemon/habit-scheduler.js +369 -0
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +30 -111
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/launchd.js +165 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/mcp-server.js +591 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +78 -34
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/outlook-http.js +399 -0
- package/dist/heart/daemon/outlook-read.js +1291 -0
- package/dist/heart/daemon/outlook-render.js +1032 -0
- package/dist/heart/daemon/outlook-types.js +7 -0
- package/dist/heart/daemon/outlook-view.js +194 -0
- package/dist/heart/daemon/process-manager.js +75 -0
- package/dist/heart/daemon/run-hooks.js +39 -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/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +50 -2
- package/dist/heart/daemon/session-id-resolver.js +131 -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 +2 -2
- package/dist/heart/daemon/specialist-prompt.js +13 -9
- package/dist/heart/daemon/specialist-tools.js +78 -9
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- 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 +136 -55
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/migrate-config.js +127 -0
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +197 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +172 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +196 -36
- package/dist/heart/providers/azure.js +116 -10
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +51 -16
- package/dist/heart/session-activity.js +172 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +62 -32
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +135 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +122 -32
- package/dist/mind/bundle-manifest.js +14 -0
- package/dist/mind/context.js +125 -14
- package/dist/mind/{memory.js → diary.js} +70 -56
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +56 -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/journal-index.js +161 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/obligations.js +134 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +721 -102
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/audit.js +1 -1
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +63 -3
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/outlook-ui/assets/index-DN6vVYPm.css +1 -0
- package/dist/outlook-ui/assets/index-M1l1tDTX.js +61 -0
- package/dist/outlook-ui/index.html +15 -0
- package/dist/repertoire/ado-semantic.js +11 -5
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +149 -2
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +170 -7
- package/dist/repertoire/guardrails.js +291 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +14 -23
- package/dist/repertoire/tasks/board.js +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +28 -10
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +11 -28
- package/dist/repertoire/tools-base.js +1111 -327
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-github.js +1 -6
- package/dist/repertoire/tools-teams.js +12 -59
- package/dist/repertoire/tools.js +318 -124
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +97 -0
- package/dist/senses/bluebubbles-client.js +102 -3
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +118 -23
- 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 +1008 -102
- package/dist/senses/cli/adapter.js +274 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +323 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +525 -0
- package/dist/senses/cli/tool-display.js +83 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +198 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +1 -1
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +593 -240
- package/dist/senses/commands.js +38 -1
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +112 -19
- package/dist/senses/inner-dialog.js +592 -84
- package/dist/senses/pipeline.js +545 -0
- package/dist/senses/shared-turn.js +196 -0
- package/dist/senses/surface-tool.js +95 -0
- package/dist/senses/teams.js +667 -120
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +16 -5
- package/skills/configure-dev-tools.md +81 -0
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/dist/senses/debug-activity.js +0 -107
- package/subagents/work-doer.md +0 -235
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -382
|
@@ -33,37 +33,306 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.restTool = exports.settleTool = exports.observeTool = exports.ponderTool = 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");
|
|
43
|
+
const shell_sessions_1 = require("./shell-sessions");
|
|
41
44
|
const config_1 = require("../heart/config");
|
|
42
45
|
const runtime_1 = require("../nerves/runtime");
|
|
46
|
+
const file_state_1 = require("../mind/file-state");
|
|
47
|
+
const scrutiny_1 = require("../mind/scrutiny");
|
|
43
48
|
const identity_1 = require("../heart/identity");
|
|
44
|
-
const
|
|
45
|
-
const
|
|
49
|
+
const identity_2 = require("../heart/identity");
|
|
50
|
+
const socket_client_1 = require("../heart/daemon/socket-client");
|
|
51
|
+
const thoughts_1 = require("../heart/daemon/thoughts");
|
|
52
|
+
const manager_1 = require("../heart/bridges/manager");
|
|
53
|
+
const session_recall_1 = require("../heart/session-recall");
|
|
54
|
+
const session_activity_1 = require("../heart/session-activity");
|
|
55
|
+
const active_work_1 = require("../heart/active-work");
|
|
46
56
|
const tools_1 = require("./coding/tools");
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
const coding_1 = require("./coding");
|
|
58
|
+
const diary_1 = require("../mind/diary");
|
|
59
|
+
const tasks_1 = require("./tasks");
|
|
60
|
+
const pending_1 = require("../mind/pending");
|
|
61
|
+
const obligations_1 = require("../mind/obligations");
|
|
62
|
+
const progress_story_1 = require("../heart/progress-story");
|
|
63
|
+
const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
|
|
64
|
+
const obligations_2 = require("../heart/obligations");
|
|
65
|
+
// Tracks which file paths have been read via read_file in this session.
|
|
66
|
+
// edit_file requires a file to be read first (must-read-first guard).
|
|
67
|
+
exports.editFileReadTracker = new Set();
|
|
68
|
+
function buildContextDiff(lines, changeStart, changeEnd, contextSize = 3) {
|
|
69
|
+
const start = Math.max(0, changeStart - contextSize);
|
|
70
|
+
const end = Math.min(lines.length, changeEnd + contextSize);
|
|
71
|
+
const result = [];
|
|
72
|
+
for (let i = start; i < end; i++) {
|
|
73
|
+
const lineNum = i + 1;
|
|
74
|
+
const prefix = (i >= changeStart && i < changeEnd) ? ">" : " ";
|
|
75
|
+
result.push(`${prefix} ${lineNum} | ${lines[i]}`);
|
|
76
|
+
}
|
|
77
|
+
return result.join("\n");
|
|
78
|
+
}
|
|
79
|
+
function resolveLocalToolPath(targetPath) {
|
|
80
|
+
if (!path.isAbsolute(targetPath)) {
|
|
81
|
+
return path.resolve((0, identity_2.getRepoRoot)(), targetPath);
|
|
82
|
+
}
|
|
83
|
+
return targetPath;
|
|
84
|
+
}
|
|
85
|
+
const NO_SESSION_FOUND_MESSAGE = "no session found for that friend/channel/key combination.";
|
|
86
|
+
const EMPTY_SESSION_MESSAGE = "session exists but has no non-system messages.";
|
|
87
|
+
function findDelegatingBridgeId(ctx) {
|
|
88
|
+
const currentSession = ctx?.currentSession;
|
|
89
|
+
if (!currentSession)
|
|
90
|
+
return undefined;
|
|
91
|
+
return ctx?.activeBridges?.find((bridge) => bridge.lifecycle === "active"
|
|
92
|
+
&& bridge.attachedSessions.some((session) => session.friendId === currentSession.friendId
|
|
93
|
+
&& session.channel === currentSession.channel
|
|
94
|
+
&& session.key === currentSession.key))?.id;
|
|
95
|
+
}
|
|
96
|
+
async function recallSessionSafely(options) {
|
|
97
|
+
try {
|
|
98
|
+
return await (0, session_recall_1.recallSession)(options);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (options.summarize) {
|
|
102
|
+
(0, runtime_1.emitNervesEvent)({
|
|
103
|
+
component: "daemon",
|
|
104
|
+
event: "daemon.session_recall_summary_fallback",
|
|
105
|
+
message: "session recall summarization failed; using raw transcript",
|
|
106
|
+
meta: {
|
|
107
|
+
friendId: options.friendId,
|
|
108
|
+
channel: options.channel,
|
|
109
|
+
key: options.key,
|
|
110
|
+
error: error instanceof Error ? error.message : String(error),
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
try {
|
|
114
|
+
return await (0, session_recall_1.recallSession)({
|
|
115
|
+
...options,
|
|
116
|
+
summarize: undefined,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return { kind: "missing" };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { kind: "missing" };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function searchSessionSafely(options) {
|
|
127
|
+
try {
|
|
128
|
+
return await (0, session_recall_1.searchSessionTranscript)(options);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return { kind: "missing" };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function normalizeProgressOutcome(text) {
|
|
135
|
+
const trimmed = text.trim();
|
|
136
|
+
/* v8 ignore next -- defensive: normalizeProgressOutcome null branch @preserve */
|
|
137
|
+
if (!trimmed || trimmed === "nothing yet" || trimmed === "nothing recent") {
|
|
51
138
|
return null;
|
|
52
|
-
|
|
53
|
-
|
|
139
|
+
}
|
|
140
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") && trimmed.length >= 2) {
|
|
141
|
+
return trimmed.slice(1, -1);
|
|
142
|
+
}
|
|
143
|
+
return trimmed;
|
|
54
144
|
}
|
|
55
|
-
function
|
|
145
|
+
function writePendingEnvelope(queueDir, message) {
|
|
146
|
+
fs.mkdirSync(queueDir, { recursive: true });
|
|
147
|
+
const fileName = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
148
|
+
const filePath = path.join(queueDir, fileName);
|
|
149
|
+
fs.writeFileSync(filePath, JSON.stringify(message, null, 2));
|
|
150
|
+
}
|
|
151
|
+
function renderCrossChatDeliveryStatus(target, result) {
|
|
152
|
+
const phase = result.status === "delivered_now"
|
|
153
|
+
? "completed"
|
|
154
|
+
: result.status === "queued_for_later"
|
|
155
|
+
? "queued"
|
|
156
|
+
: result.status === "blocked"
|
|
157
|
+
? "blocked"
|
|
158
|
+
: "errored";
|
|
159
|
+
const lead = result.status === "delivered_now"
|
|
160
|
+
? "delivered now"
|
|
161
|
+
: result.status === "queued_for_later"
|
|
162
|
+
? "queued for later"
|
|
163
|
+
: result.status === "blocked"
|
|
164
|
+
? "blocked"
|
|
165
|
+
: "failed";
|
|
166
|
+
return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
167
|
+
scope: "shared-work",
|
|
168
|
+
phase,
|
|
169
|
+
objective: `message to ${target}`,
|
|
170
|
+
outcomeText: `${lead}\n${result.detail}`,
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
function emptyTaskBoard() {
|
|
56
174
|
return {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
175
|
+
compact: "",
|
|
176
|
+
full: "",
|
|
177
|
+
byStatus: {
|
|
178
|
+
drafting: [],
|
|
179
|
+
processing: [],
|
|
180
|
+
validating: [],
|
|
181
|
+
collaborating: [],
|
|
182
|
+
paused: [],
|
|
183
|
+
blocked: [],
|
|
184
|
+
done: [],
|
|
185
|
+
cancelled: [],
|
|
186
|
+
},
|
|
187
|
+
issues: [],
|
|
188
|
+
actionRequired: [],
|
|
189
|
+
unresolvedDependencies: [],
|
|
190
|
+
activeSessions: [],
|
|
191
|
+
activeBridges: [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function isLiveCodingSessionStatus(status) {
|
|
195
|
+
return status === "spawning"
|
|
196
|
+
|| status === "running"
|
|
197
|
+
|| status === "waiting_input"
|
|
198
|
+
|| status === "stalled";
|
|
199
|
+
}
|
|
200
|
+
function readActiveWorkInnerState() {
|
|
201
|
+
const defaultJob = {
|
|
202
|
+
status: "idle",
|
|
203
|
+
content: null,
|
|
204
|
+
origin: null,
|
|
205
|
+
mode: "reflect",
|
|
206
|
+
obligationStatus: null,
|
|
207
|
+
surfacedResult: null,
|
|
208
|
+
queuedAt: null,
|
|
209
|
+
startedAt: null,
|
|
210
|
+
surfacedAt: null,
|
|
66
211
|
};
|
|
212
|
+
try {
|
|
213
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
214
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
215
|
+
const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
|
|
216
|
+
const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
|
|
217
|
+
const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
|
|
218
|
+
const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
|
|
219
|
+
const storeObligationPending = (0, obligations_2.readPendingObligations)(agentRoot).length > 0;
|
|
220
|
+
return {
|
|
221
|
+
status: dialogStatus.processing === "started" ? "running" : "idle",
|
|
222
|
+
hasPending: dialogStatus.queue !== "clear",
|
|
223
|
+
origin: dialogStatus.origin,
|
|
224
|
+
contentSnippet: dialogStatus.contentSnippet,
|
|
225
|
+
obligationPending: dialogStatus.obligationPending || storeObligationPending,
|
|
226
|
+
job,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return {
|
|
231
|
+
status: "idle",
|
|
232
|
+
hasPending: false,
|
|
233
|
+
job: defaultJob,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function buildToolActiveWorkFrame(ctx) {
|
|
238
|
+
const currentSession = ctx?.currentSession
|
|
239
|
+
? {
|
|
240
|
+
friendId: ctx.currentSession.friendId,
|
|
241
|
+
channel: ctx.currentSession.channel,
|
|
242
|
+
key: ctx.currentSession.key,
|
|
243
|
+
sessionPath: (0, config_1.resolveSessionPath)(ctx.currentSession.friendId, ctx.currentSession.channel, ctx.currentSession.key),
|
|
244
|
+
}
|
|
245
|
+
: null;
|
|
246
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
247
|
+
const bridges = currentSession
|
|
248
|
+
? (0, manager_1.createBridgeManager)().findBridgesForSession({
|
|
249
|
+
friendId: currentSession.friendId,
|
|
250
|
+
channel: currentSession.channel,
|
|
251
|
+
key: currentSession.key,
|
|
252
|
+
})
|
|
253
|
+
: [];
|
|
254
|
+
let friendActivity = [];
|
|
255
|
+
try {
|
|
256
|
+
friendActivity = (0, session_activity_1.listSessionActivity)({
|
|
257
|
+
sessionsDir: `${agentRoot}/state/sessions`,
|
|
258
|
+
friendsDir: `${agentRoot}/friends`,
|
|
259
|
+
agentName: (0, identity_1.getAgentName)(),
|
|
260
|
+
currentSession,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
friendActivity = [];
|
|
265
|
+
}
|
|
266
|
+
const pendingObligations = (() => {
|
|
267
|
+
try {
|
|
268
|
+
return (0, obligations_2.readPendingObligations)(agentRoot);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
})();
|
|
274
|
+
let codingSessions = [];
|
|
275
|
+
let otherCodingSessions = [];
|
|
276
|
+
try {
|
|
277
|
+
const liveCodingSessions = (0, coding_1.getCodingSessionManager)()
|
|
278
|
+
.listSessions()
|
|
279
|
+
.filter((session) => isLiveCodingSessionStatus(session.status) && Boolean(session.originSession));
|
|
280
|
+
if (currentSession) {
|
|
281
|
+
codingSessions = liveCodingSessions.filter((session) => session.originSession?.friendId === currentSession.friendId
|
|
282
|
+
&& session.originSession.channel === currentSession.channel
|
|
283
|
+
&& session.originSession.key === currentSession.key);
|
|
284
|
+
otherCodingSessions = liveCodingSessions.filter((session) => !(session.originSession?.friendId === currentSession.friendId
|
|
285
|
+
&& session.originSession.channel === currentSession.channel
|
|
286
|
+
&& session.originSession.key === currentSession.key));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
codingSessions = [];
|
|
290
|
+
otherCodingSessions = liveCodingSessions;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
codingSessions = [];
|
|
295
|
+
otherCodingSessions = [];
|
|
296
|
+
}
|
|
297
|
+
const currentObligation = currentSession
|
|
298
|
+
? pendingObligations.find((obligation) => obligation.status !== "fulfilled"
|
|
299
|
+
&& obligation.origin.friendId === currentSession.friendId
|
|
300
|
+
&& obligation.origin.channel === currentSession.channel
|
|
301
|
+
&& obligation.origin.key === currentSession.key)?.content ?? null
|
|
302
|
+
: null;
|
|
303
|
+
return (0, active_work_1.buildActiveWorkFrame)({
|
|
304
|
+
currentSession,
|
|
305
|
+
currentObligation,
|
|
306
|
+
mustResolveBeforeHandoff: false,
|
|
307
|
+
inner: readActiveWorkInnerState(),
|
|
308
|
+
bridges,
|
|
309
|
+
codingSessions,
|
|
310
|
+
otherCodingSessions,
|
|
311
|
+
pendingObligations,
|
|
312
|
+
taskBoard: (() => {
|
|
313
|
+
try {
|
|
314
|
+
return (0, tasks_1.getTaskModule)().getBoard();
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return emptyTaskBoard();
|
|
318
|
+
}
|
|
319
|
+
})(),
|
|
320
|
+
friendActivity,
|
|
321
|
+
targetCandidates: [],
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
function renderInnerProgressStatus(status) {
|
|
325
|
+
if (status.processing === "pending") {
|
|
326
|
+
return "i've queued this thought for private attention. it'll come up when my inner dialog is free.";
|
|
327
|
+
}
|
|
328
|
+
if (status.processing === "started") {
|
|
329
|
+
return "i'm working through this privately right now.";
|
|
330
|
+
}
|
|
331
|
+
// processed / completed
|
|
332
|
+
if (status.surfaced && status.surfaced !== "nothing recent" && status.surfaced !== "no outward result") {
|
|
333
|
+
return `i thought about this privately and came to something: ${status.surfaced}`;
|
|
334
|
+
}
|
|
335
|
+
return "i thought about this privately. i'll bring it back when the time is right.";
|
|
67
336
|
}
|
|
68
337
|
exports.baseToolDefinitions = [
|
|
69
338
|
{
|
|
@@ -71,22 +340,50 @@ exports.baseToolDefinitions = [
|
|
|
71
340
|
type: "function",
|
|
72
341
|
function: {
|
|
73
342
|
name: "read_file",
|
|
74
|
-
description: "read file
|
|
343
|
+
description: "Read file contents. Results include line numbers. Use offset/limit for large files -- don't read the whole thing if you only need a section. Use this tool before editing any file. When reading code, read enough context to understand the surrounding logic, not just the target line.",
|
|
75
344
|
parameters: {
|
|
76
345
|
type: "object",
|
|
77
|
-
properties: {
|
|
346
|
+
properties: {
|
|
347
|
+
path: { type: "string" },
|
|
348
|
+
offset: { type: "number", description: "1-based line number to start reading from" },
|
|
349
|
+
limit: { type: "number", description: "maximum number of lines to return" },
|
|
350
|
+
},
|
|
78
351
|
required: ["path"],
|
|
79
352
|
},
|
|
80
353
|
},
|
|
81
354
|
},
|
|
82
|
-
handler: (a) =>
|
|
355
|
+
handler: (a) => {
|
|
356
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
357
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
358
|
+
exports.editFileReadTracker.add(resolvedPath);
|
|
359
|
+
const offset = a.offset ? parseInt(a.offset, 10) : undefined;
|
|
360
|
+
const limit = a.limit ? parseInt(a.limit, 10) : undefined;
|
|
361
|
+
// Record in file state cache for staleness detection
|
|
362
|
+
try {
|
|
363
|
+
const mtime = fs.statSync(resolvedPath).mtimeMs;
|
|
364
|
+
const readContent = (offset === undefined && limit === undefined)
|
|
365
|
+
? content
|
|
366
|
+
: content.split("\n").slice(offset ? offset - 1 : 0, limit !== undefined ? (offset ? offset - 1 : 0) + limit : undefined).join("\n");
|
|
367
|
+
file_state_1.fileStateCache.record(resolvedPath, readContent, mtime, offset, limit);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// stat failed -- skip cache recording
|
|
371
|
+
}
|
|
372
|
+
if (offset === undefined && limit === undefined)
|
|
373
|
+
return content;
|
|
374
|
+
const lines = content.split("\n");
|
|
375
|
+
const start = offset ? offset - 1 : 0;
|
|
376
|
+
const end = limit !== undefined ? start + limit : lines.length;
|
|
377
|
+
return lines.slice(start, end).join("\n");
|
|
378
|
+
},
|
|
379
|
+
summaryKeys: ["path"],
|
|
83
380
|
},
|
|
84
381
|
{
|
|
85
382
|
tool: {
|
|
86
383
|
type: "function",
|
|
87
384
|
function: {
|
|
88
385
|
name: "write_file",
|
|
89
|
-
description: "
|
|
386
|
+
description: "Prefer this tool for creating new files or fully replacing existing ones. You MUST read an existing file with read_file before overwriting it. Prefer edit_file for modifying existing files -- it only sends the diff. Do not create documentation files (*.md, README) by default; only do so when explicitly asked or when documentation is clearly part of the requested change.",
|
|
90
387
|
parameters: {
|
|
91
388
|
type: "object",
|
|
92
389
|
properties: { path: { type: "string" }, content: { type: "string" } },
|
|
@@ -95,106 +392,339 @@ exports.baseToolDefinitions = [
|
|
|
95
392
|
},
|
|
96
393
|
},
|
|
97
394
|
handler: (a) => {
|
|
98
|
-
|
|
99
|
-
fs.
|
|
100
|
-
|
|
395
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
396
|
+
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
397
|
+
fs.writeFileSync(resolvedPath, a.content, "utf-8");
|
|
398
|
+
(0, scrutiny_1.trackModifiedFile)(resolvedPath);
|
|
399
|
+
const scrutiny = (0, scrutiny_1.getPostImplementationScrutiny)((0, scrutiny_1.getModifiedFileCount)());
|
|
400
|
+
/* v8 ignore next -- scrutiny appendix branch depends on session-level file count @preserve */
|
|
401
|
+
return scrutiny ? `ok\n\n${scrutiny}` : "ok";
|
|
101
402
|
},
|
|
403
|
+
summaryKeys: ["path"],
|
|
102
404
|
},
|
|
103
405
|
{
|
|
104
406
|
tool: {
|
|
105
407
|
type: "function",
|
|
106
408
|
function: {
|
|
107
|
-
name: "
|
|
108
|
-
description: "
|
|
409
|
+
name: "edit_file",
|
|
410
|
+
description: "Surgically edit a file by replacing an exact string. The file MUST have been read via read_file first -- this tool will reject the call otherwise. old_string must match EXACTLY ONE location in the file -- if it matches zero or multiple, the edit fails. To fix: provide more surrounding context to make the match unique. Preserve exact indentation (tabs/spaces) from the file. Prefer this over write_file for modifications -- it only sends the diff.",
|
|
109
411
|
parameters: {
|
|
110
412
|
type: "object",
|
|
111
|
-
properties: {
|
|
112
|
-
|
|
413
|
+
properties: {
|
|
414
|
+
path: { type: "string" },
|
|
415
|
+
old_string: { type: "string" },
|
|
416
|
+
new_string: { type: "string" },
|
|
417
|
+
},
|
|
418
|
+
required: ["path", "old_string", "new_string"],
|
|
113
419
|
},
|
|
114
420
|
},
|
|
115
421
|
},
|
|
116
|
-
handler: (a) =>
|
|
422
|
+
handler: (a) => {
|
|
423
|
+
const resolvedPath = resolveLocalToolPath(a.path);
|
|
424
|
+
if (!exports.editFileReadTracker.has(resolvedPath)) {
|
|
425
|
+
return `error: you must read the file with read_file before editing it. call read_file on ${a.path} first.`;
|
|
426
|
+
}
|
|
427
|
+
// Check staleness before editing
|
|
428
|
+
const stalenessCheck = file_state_1.fileStateCache.isStale(resolvedPath);
|
|
429
|
+
let content;
|
|
430
|
+
try {
|
|
431
|
+
content = fs.readFileSync(resolvedPath, "utf-8");
|
|
432
|
+
}
|
|
433
|
+
catch (e) {
|
|
434
|
+
return `error: could not read file: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
435
|
+
}
|
|
436
|
+
// Count occurrences
|
|
437
|
+
const occurrences = [];
|
|
438
|
+
let searchFrom = 0;
|
|
439
|
+
while (true) {
|
|
440
|
+
const idx = content.indexOf(a.old_string, searchFrom);
|
|
441
|
+
if (idx === -1)
|
|
442
|
+
break;
|
|
443
|
+
occurrences.push(idx);
|
|
444
|
+
searchFrom = idx + 1;
|
|
445
|
+
}
|
|
446
|
+
if (occurrences.length === 0) {
|
|
447
|
+
return `error: old_string not found in ${a.path}`;
|
|
448
|
+
}
|
|
449
|
+
if (occurrences.length > 1) {
|
|
450
|
+
return `error: old_string is ambiguous -- found ${occurrences.length} matches in ${a.path}. provide more context to make the match unique.`;
|
|
451
|
+
}
|
|
452
|
+
// Single unique match -- replace
|
|
453
|
+
const idx = occurrences[0];
|
|
454
|
+
const updated = content.slice(0, idx) + a.new_string + content.slice(idx + a.old_string.length);
|
|
455
|
+
fs.writeFileSync(resolvedPath, updated, "utf-8");
|
|
456
|
+
// Update file state cache with new content
|
|
457
|
+
try {
|
|
458
|
+
const newMtime = fs.statSync(resolvedPath).mtimeMs;
|
|
459
|
+
file_state_1.fileStateCache.record(resolvedPath, updated, newMtime);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// stat failed -- skip cache update
|
|
463
|
+
}
|
|
464
|
+
// Build contextual diff
|
|
465
|
+
const lines = updated.split("\n");
|
|
466
|
+
const prefixLines = content.slice(0, idx).split("\n");
|
|
467
|
+
const changeStartLine = prefixLines.length - 1;
|
|
468
|
+
const newStringLines = a.new_string.split("\n");
|
|
469
|
+
const changeEndLine = changeStartLine + newStringLines.length;
|
|
470
|
+
const diffResult = buildContextDiff(lines, changeStartLine, changeEndLine);
|
|
471
|
+
// Track modified file and compute scrutiny appendix
|
|
472
|
+
(0, scrutiny_1.trackModifiedFile)(resolvedPath);
|
|
473
|
+
const scrutiny = (0, scrutiny_1.getPostImplementationScrutiny)((0, scrutiny_1.getModifiedFileCount)());
|
|
474
|
+
// Append staleness warning if detected (do not block -- TTFA)
|
|
475
|
+
/* v8 ignore start -- staleness+diff+scrutiny combo not exercised in integration tests @preserve */
|
|
476
|
+
if (stalenessCheck.stale) {
|
|
477
|
+
const base = `${diffResult}\n\n⚠️ warning: file changed externally since last read -- re-read recommended`;
|
|
478
|
+
return scrutiny ? `${base}\n\n${scrutiny}` : base;
|
|
479
|
+
}
|
|
480
|
+
/* v8 ignore stop */
|
|
481
|
+
/* v8 ignore next -- scrutiny appendix branch depends on session-level file count @preserve */
|
|
482
|
+
return scrutiny ? `${diffResult}\n\n${scrutiny}` : diffResult;
|
|
483
|
+
},
|
|
484
|
+
summaryKeys: ["path"],
|
|
117
485
|
},
|
|
118
486
|
{
|
|
119
487
|
tool: {
|
|
120
488
|
type: "function",
|
|
121
489
|
function: {
|
|
122
|
-
name: "
|
|
123
|
-
description: "
|
|
490
|
+
name: "glob",
|
|
491
|
+
description: "Find files matching a glob pattern, sorted alphabetically. Use this instead of shell commands like `find` or `ls`. For broad exploratory searches that would require multiple rounds of globbing and grepping, consider using claude or coding_spawn.",
|
|
124
492
|
parameters: {
|
|
125
493
|
type: "object",
|
|
126
|
-
properties: {
|
|
127
|
-
|
|
494
|
+
properties: {
|
|
495
|
+
pattern: { type: "string", description: "glob pattern (e.g. **/*.ts)" },
|
|
496
|
+
cwd: { type: "string", description: "directory to search from (defaults to process.cwd())" },
|
|
497
|
+
},
|
|
498
|
+
required: ["pattern"],
|
|
128
499
|
},
|
|
129
500
|
},
|
|
130
501
|
},
|
|
131
|
-
handler: (a) =>
|
|
132
|
-
.
|
|
133
|
-
|
|
134
|
-
.join("\n")
|
|
502
|
+
handler: (a) => {
|
|
503
|
+
const cwd = a.cwd ? resolveLocalToolPath(a.cwd) : process.cwd();
|
|
504
|
+
const matches = fg.globSync(a.pattern, { cwd, dot: true });
|
|
505
|
+
return matches.sort().join("\n");
|
|
506
|
+
},
|
|
507
|
+
summaryKeys: ["pattern", "cwd"],
|
|
135
508
|
},
|
|
136
509
|
{
|
|
137
510
|
tool: {
|
|
138
511
|
type: "function",
|
|
139
512
|
function: {
|
|
140
|
-
name: "
|
|
141
|
-
description: "
|
|
513
|
+
name: "grep",
|
|
514
|
+
description: "Search file contents for lines matching a regex pattern. Searches recursively in directories. Use this instead of shell commands like `grep` or `rg`. Returns matching lines with file path and line numbers. Use context_lines for surrounding context. Use include to filter file types (e.g., '*.ts').",
|
|
142
515
|
parameters: {
|
|
143
516
|
type: "object",
|
|
144
517
|
properties: {
|
|
145
|
-
|
|
146
|
-
|
|
518
|
+
pattern: { type: "string", description: "regex pattern to search for" },
|
|
519
|
+
path: { type: "string", description: "file or directory to search" },
|
|
520
|
+
context_lines: { type: "number", description: "number of surrounding context lines (default 0)" },
|
|
521
|
+
include: { type: "string", description: "glob filter to limit searched files (e.g. *.ts)" },
|
|
147
522
|
},
|
|
148
|
-
required: ["
|
|
523
|
+
required: ["pattern", "path"],
|
|
149
524
|
},
|
|
150
525
|
},
|
|
151
526
|
},
|
|
152
527
|
handler: (a) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
528
|
+
const targetPath = resolveLocalToolPath(a.path);
|
|
529
|
+
const regex = new RegExp(a.pattern);
|
|
530
|
+
const contextLines = parseInt(a.context_lines || "0", 10);
|
|
531
|
+
const includeGlob = a.include || undefined;
|
|
532
|
+
function searchFile(filePath) {
|
|
533
|
+
let content;
|
|
534
|
+
try {
|
|
535
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
return [];
|
|
156
539
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
540
|
+
const lines = content.split("\n");
|
|
541
|
+
const matchIndices = new Set();
|
|
542
|
+
for (let i = 0; i < lines.length; i++) {
|
|
543
|
+
if (regex.test(lines[i])) {
|
|
544
|
+
matchIndices.add(i);
|
|
160
545
|
}
|
|
161
|
-
(0, child_process_1.execSync)(`git add ${p}`, { encoding: "utf-8" });
|
|
162
546
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
547
|
+
if (matchIndices.size === 0)
|
|
548
|
+
return [];
|
|
549
|
+
const outputIndices = new Set();
|
|
550
|
+
for (const idx of matchIndices) {
|
|
551
|
+
const start = Math.max(0, idx - contextLines);
|
|
552
|
+
const end = Math.min(lines.length - 1, idx + contextLines);
|
|
553
|
+
for (let i = start; i <= end; i++) {
|
|
554
|
+
outputIndices.add(i);
|
|
555
|
+
}
|
|
166
556
|
}
|
|
167
|
-
(
|
|
168
|
-
|
|
557
|
+
const sortedIndices = [...outputIndices].sort((a, b) => a - b);
|
|
558
|
+
const results = [];
|
|
559
|
+
for (const idx of sortedIndices) {
|
|
560
|
+
const lineNum = idx + 1;
|
|
561
|
+
if (matchIndices.has(idx)) {
|
|
562
|
+
results.push(`${filePath}:${lineNum}: ${lines[idx]}`);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
results.push(`-${filePath}:${lineNum}: ${lines[idx]}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return results;
|
|
169
569
|
}
|
|
170
|
-
|
|
171
|
-
|
|
570
|
+
function collectFiles(dirPath) {
|
|
571
|
+
const files = [];
|
|
572
|
+
function walk(dir) {
|
|
573
|
+
let entries;
|
|
574
|
+
try {
|
|
575
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
for (const entry of entries) {
|
|
581
|
+
const fullPath = path.join(dir, entry.name);
|
|
582
|
+
if (entry.isDirectory()) {
|
|
583
|
+
walk(fullPath);
|
|
584
|
+
}
|
|
585
|
+
else if (entry.isFile()) {
|
|
586
|
+
files.push(fullPath);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
walk(dirPath);
|
|
591
|
+
return files.sort();
|
|
592
|
+
}
|
|
593
|
+
function matchesGlob(filePath, glob) {
|
|
594
|
+
const escaped = glob
|
|
595
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
596
|
+
.replace(/\*/g, ".*")
|
|
597
|
+
.replace(/\?/g, ".");
|
|
598
|
+
return new RegExp(`(^|/)${escaped}$`).test(filePath);
|
|
599
|
+
}
|
|
600
|
+
const stat = fs.statSync(targetPath, { throwIfNoEntry: false });
|
|
601
|
+
if (!stat)
|
|
602
|
+
return "";
|
|
603
|
+
if (stat.isFile()) {
|
|
604
|
+
return searchFile(targetPath).join("\n");
|
|
605
|
+
}
|
|
606
|
+
let files = collectFiles(targetPath);
|
|
607
|
+
if (includeGlob) {
|
|
608
|
+
files = files.filter((f) => matchesGlob(f, includeGlob));
|
|
172
609
|
}
|
|
610
|
+
const allResults = [];
|
|
611
|
+
for (const file of files) {
|
|
612
|
+
allResults.push(...searchFile(file));
|
|
613
|
+
}
|
|
614
|
+
return allResults.join("\n");
|
|
173
615
|
},
|
|
616
|
+
summaryKeys: ["pattern", "path", "include"],
|
|
174
617
|
},
|
|
175
618
|
{
|
|
176
619
|
tool: {
|
|
177
620
|
type: "function",
|
|
178
621
|
function: {
|
|
179
|
-
name: "
|
|
180
|
-
description: "
|
|
622
|
+
name: "shell",
|
|
623
|
+
description: "Run a shell command and return stdout/stderr. Working directory persists between calls. Use dedicated tools instead of shell when available: read_file instead of cat, edit_file instead of sed, glob instead of find, grep instead of grep/rg. Reserve shell for operations that genuinely need the shell: installing packages, running builds/tests, git operations, process management. Be careful with destructive commands -- consider reversibility before running. If a command fails, read the error output before retrying with a different approach.",
|
|
181
624
|
parameters: {
|
|
182
625
|
type: "object",
|
|
183
626
|
properties: {
|
|
184
627
|
command: { type: "string" },
|
|
628
|
+
timeout_ms: {
|
|
629
|
+
type: "number",
|
|
630
|
+
description: "Timeout in milliseconds. Default: 30000. Max: 600000.",
|
|
631
|
+
},
|
|
632
|
+
background: {
|
|
633
|
+
type: "boolean",
|
|
634
|
+
description: "Run in background. Returns immediately with a process ID. Use shell_status/shell_tail to monitor.",
|
|
635
|
+
},
|
|
185
636
|
},
|
|
186
637
|
required: ["command"],
|
|
187
638
|
},
|
|
188
639
|
},
|
|
189
640
|
},
|
|
190
641
|
handler: (a) => {
|
|
642
|
+
// Destructive pattern detection (friction, not a block)
|
|
643
|
+
const destructivePatterns = (0, shell_sessions_1.detectDestructivePatterns)(a.command);
|
|
644
|
+
if (destructivePatterns.length > 0) {
|
|
645
|
+
(0, runtime_1.emitNervesEvent)({
|
|
646
|
+
level: "warn",
|
|
647
|
+
event: "tool.shell.destructive_detected",
|
|
648
|
+
component: "tools",
|
|
649
|
+
message: `destructive pattern detected: ${destructivePatterns.join(", ")}`,
|
|
650
|
+
meta: { command: a.command, patterns: destructivePatterns },
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
// Background mode: spawn and return immediately
|
|
654
|
+
if (a.background === "true") {
|
|
655
|
+
const session = (0, shell_sessions_1.spawnBackgroundShell)(a.command);
|
|
656
|
+
return JSON.stringify({ id: session.id, command: session.command, status: session.status });
|
|
657
|
+
}
|
|
658
|
+
const MAX_TIMEOUT = 600000;
|
|
659
|
+
const requestedTimeout = Number(a.timeout_ms) || 0;
|
|
660
|
+
let configDefault = 30000;
|
|
191
661
|
try {
|
|
192
|
-
|
|
662
|
+
configDefault = (0, identity_1.loadAgentConfig)().shell?.defaultTimeout ?? 30000;
|
|
193
663
|
}
|
|
194
|
-
catch
|
|
195
|
-
|
|
664
|
+
catch { /* test env: no --agent flag */ }
|
|
665
|
+
const baseTimeout = requestedTimeout > 0 ? requestedTimeout : configDefault;
|
|
666
|
+
const timeout = Math.min(baseTimeout, MAX_TIMEOUT);
|
|
667
|
+
const output = (0, child_process_1.execSync)(a.command, {
|
|
668
|
+
encoding: "utf-8",
|
|
669
|
+
timeout,
|
|
670
|
+
});
|
|
671
|
+
if (destructivePatterns.length > 0) {
|
|
672
|
+
return `${output}\n\n--- destructive pattern detected: ${destructivePatterns.join(", ")} ---`;
|
|
673
|
+
}
|
|
674
|
+
return output;
|
|
675
|
+
},
|
|
676
|
+
summaryKeys: ["command"],
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
tool: {
|
|
680
|
+
type: "function",
|
|
681
|
+
function: {
|
|
682
|
+
name: "shell_status",
|
|
683
|
+
description: "Check status of background shell processes. Omit id to list all.",
|
|
684
|
+
parameters: {
|
|
685
|
+
type: "object",
|
|
686
|
+
properties: {
|
|
687
|
+
id: { type: "string", description: "Background shell process ID" },
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
handler: (a) => {
|
|
693
|
+
if (!a.id) {
|
|
694
|
+
return JSON.stringify((0, shell_sessions_1.listShellSessions)());
|
|
196
695
|
}
|
|
696
|
+
const session = (0, shell_sessions_1.getShellSession)(a.id);
|
|
697
|
+
if (!session)
|
|
698
|
+
return `process not found: ${a.id}`;
|
|
699
|
+
return JSON.stringify(session);
|
|
197
700
|
},
|
|
701
|
+
summaryKeys: ["id"],
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
tool: {
|
|
705
|
+
type: "function",
|
|
706
|
+
function: {
|
|
707
|
+
name: "shell_tail",
|
|
708
|
+
description: "Show recent output from a background shell process.",
|
|
709
|
+
parameters: {
|
|
710
|
+
type: "object",
|
|
711
|
+
properties: {
|
|
712
|
+
id: { type: "string", description: "Background shell process ID" },
|
|
713
|
+
},
|
|
714
|
+
required: ["id"],
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
handler: (a) => {
|
|
719
|
+
/* v8 ignore next -- schema requires id, defensive guard @preserve */
|
|
720
|
+
if (!a.id)
|
|
721
|
+
return "id is required";
|
|
722
|
+
const output = (0, shell_sessions_1.tailShellSession)(a.id);
|
|
723
|
+
if (output === undefined)
|
|
724
|
+
return `process not found: ${a.id}`;
|
|
725
|
+
return output || "(no output yet)";
|
|
726
|
+
},
|
|
727
|
+
summaryKeys: ["id"],
|
|
198
728
|
},
|
|
199
729
|
{
|
|
200
730
|
tool: {
|
|
@@ -228,27 +758,14 @@ exports.baseToolDefinitions = [
|
|
|
228
758
|
return `error: ${e}`;
|
|
229
759
|
}
|
|
230
760
|
},
|
|
231
|
-
|
|
232
|
-
{
|
|
233
|
-
tool: {
|
|
234
|
-
type: "function",
|
|
235
|
-
function: {
|
|
236
|
-
name: "get_current_time",
|
|
237
|
-
description: "get the current date and time in America/Los_Angeles (Pacific Time)",
|
|
238
|
-
parameters: { type: "object", properties: {} },
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
handler: () => new Date().toLocaleString("en-US", {
|
|
242
|
-
timeZone: "America/Los_Angeles",
|
|
243
|
-
hour12: false,
|
|
244
|
-
}),
|
|
761
|
+
summaryKeys: ["name"],
|
|
245
762
|
},
|
|
246
763
|
{
|
|
247
764
|
tool: {
|
|
248
765
|
type: "function",
|
|
249
766
|
function: {
|
|
250
767
|
name: "claude",
|
|
251
|
-
description: "use claude code to query this codebase or get an outside perspective.
|
|
768
|
+
description: "use claude code to query this codebase or get an outside perspective. Use for code review, second opinions, or questions that benefit from a fresh perspective outside this conversation's context.",
|
|
252
769
|
parameters: {
|
|
253
770
|
type: "object",
|
|
254
771
|
properties: { prompt: { type: "string" } },
|
|
@@ -273,6 +790,7 @@ exports.baseToolDefinitions = [
|
|
|
273
790
|
return `error: ${e}`;
|
|
274
791
|
}
|
|
275
792
|
},
|
|
793
|
+
summaryKeys: ["prompt"],
|
|
276
794
|
},
|
|
277
795
|
{
|
|
278
796
|
tool: {
|
|
@@ -313,13 +831,14 @@ exports.baseToolDefinitions = [
|
|
|
313
831
|
return `error: ${e}`;
|
|
314
832
|
}
|
|
315
833
|
},
|
|
834
|
+
summaryKeys: ["query"],
|
|
316
835
|
},
|
|
317
836
|
{
|
|
318
837
|
tool: {
|
|
319
838
|
type: "function",
|
|
320
839
|
function: {
|
|
321
|
-
name: "
|
|
322
|
-
description: "
|
|
840
|
+
name: "recall",
|
|
841
|
+
description: "Search my diary and journal for facts, thoughts, and working notes matching a query. Uses semantic similarity -- phrasing matters. Try different angles if the first query doesn't find what you're looking for. Check recall before asking the human something you might already know.",
|
|
323
842
|
parameters: {
|
|
324
843
|
type: "object",
|
|
325
844
|
properties: { query: { type: "string" } },
|
|
@@ -332,44 +851,69 @@ exports.baseToolDefinitions = [
|
|
|
332
851
|
const query = (a.query || "").trim();
|
|
333
852
|
if (!query)
|
|
334
853
|
return "query is required";
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
.
|
|
854
|
+
const resultLines = [];
|
|
855
|
+
// Search diary entries
|
|
856
|
+
const hits = await (0, diary_1.searchDiaryEntries)(query, (0, diary_1.readDiaryEntries)());
|
|
857
|
+
for (const fact of hits) {
|
|
858
|
+
resultLines.push(`[diary] ${fact.text} (source=${fact.source}, createdAt=${fact.createdAt})`);
|
|
859
|
+
}
|
|
860
|
+
// Search journal index
|
|
861
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
862
|
+
const journalIndexPath = path.join(agentRoot, "journal", ".index.json");
|
|
863
|
+
try {
|
|
864
|
+
const raw = fs.readFileSync(journalIndexPath, "utf8");
|
|
865
|
+
const journalEntries = JSON.parse(raw);
|
|
866
|
+
if (Array.isArray(journalEntries) && journalEntries.length > 0) {
|
|
867
|
+
// Substring match on preview and filename
|
|
868
|
+
const lowerQuery = query.toLowerCase();
|
|
869
|
+
for (const entry of journalEntries) {
|
|
870
|
+
/* v8 ignore next 4 -- both sides tested (filename-only match in recall-journal.test.ts); v8 misreports || short-circuit @preserve */
|
|
871
|
+
if (entry.preview.toLowerCase().includes(lowerQuery) ||
|
|
872
|
+
entry.filename.toLowerCase().includes(lowerQuery)) {
|
|
873
|
+
resultLines.push(`[journal] ${entry.filename}: ${entry.preview}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
catch {
|
|
879
|
+
// No journal index or malformed — skip journal search
|
|
880
|
+
}
|
|
881
|
+
return resultLines.join("\n");
|
|
340
882
|
}
|
|
341
883
|
catch (e) {
|
|
342
884
|
return `error: ${e instanceof Error ? e.message : String(e)}`;
|
|
343
885
|
}
|
|
344
886
|
},
|
|
887
|
+
summaryKeys: ["query"],
|
|
345
888
|
},
|
|
346
889
|
{
|
|
347
890
|
tool: {
|
|
348
891
|
type: "function",
|
|
349
892
|
function: {
|
|
350
|
-
name: "
|
|
351
|
-
description: "
|
|
893
|
+
name: "diary_write",
|
|
894
|
+
description: "Write an entry in my diary -- something I learned, noticed, or concluded that I want to recall later. Use 'about' to tag the entry to a person, topic, or context. Write for my future self: include enough context that the entry makes sense without the surrounding conversation. Prefer durable conclusions over passing noise. Don't duplicate what already belongs in friend notes.",
|
|
352
895
|
parameters: {
|
|
353
896
|
type: "object",
|
|
354
897
|
properties: {
|
|
355
|
-
|
|
898
|
+
entry: { type: "string" },
|
|
356
899
|
about: { type: "string" },
|
|
357
900
|
},
|
|
358
|
-
required: ["
|
|
901
|
+
required: ["entry"],
|
|
359
902
|
},
|
|
360
903
|
},
|
|
361
904
|
},
|
|
362
905
|
handler: async (a) => {
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
365
|
-
return "
|
|
366
|
-
const result = await (0,
|
|
367
|
-
text,
|
|
368
|
-
source: "tool:
|
|
906
|
+
const entry = (a.entry || "").trim();
|
|
907
|
+
if (!entry)
|
|
908
|
+
return "entry is required";
|
|
909
|
+
const result = await (0, diary_1.saveDiaryEntry)({
|
|
910
|
+
text: entry,
|
|
911
|
+
source: "tool:diary_write",
|
|
369
912
|
about: typeof a.about === "string" ? a.about : undefined,
|
|
370
913
|
});
|
|
371
|
-
return `saved
|
|
914
|
+
return `saved diary entry (added=${result.added}, skipped=${result.skipped})`;
|
|
372
915
|
},
|
|
916
|
+
summaryKeys: ["entry", "about"],
|
|
373
917
|
},
|
|
374
918
|
{
|
|
375
919
|
tool: {
|
|
@@ -397,192 +941,7 @@ exports.baseToolDefinitions = [
|
|
|
397
941
|
return `friend not found: ${friendId}`;
|
|
398
942
|
return JSON.stringify(friend, null, 2);
|
|
399
943
|
},
|
|
400
|
-
|
|
401
|
-
{
|
|
402
|
-
tool: {
|
|
403
|
-
type: "function",
|
|
404
|
-
function: {
|
|
405
|
-
name: "task_board",
|
|
406
|
-
description: "show the task board grouped by status",
|
|
407
|
-
parameters: { type: "object", properties: {} },
|
|
408
|
-
},
|
|
409
|
-
},
|
|
410
|
-
handler: () => {
|
|
411
|
-
const board = (0, tasks_1.getTaskModule)().getBoard();
|
|
412
|
-
return board.full || board.compact || "no tasks found";
|
|
413
|
-
},
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
tool: {
|
|
417
|
-
type: "function",
|
|
418
|
-
function: {
|
|
419
|
-
name: "task_create",
|
|
420
|
-
description: "create a new task in the bundle task system. optionally set `scheduledAt` for a one-time reminder or `cadence` for recurring daemon-scheduled work.",
|
|
421
|
-
parameters: {
|
|
422
|
-
type: "object",
|
|
423
|
-
properties: {
|
|
424
|
-
title: { type: "string" },
|
|
425
|
-
type: { type: "string", enum: ["one-shot", "ongoing", "habit"] },
|
|
426
|
-
category: { type: "string" },
|
|
427
|
-
body: { type: "string" },
|
|
428
|
-
status: { type: "string" },
|
|
429
|
-
validator: { type: "string" },
|
|
430
|
-
requester: { type: "string" },
|
|
431
|
-
scheduledAt: { type: "string", description: "ISO timestamp for a one-time scheduled run/reminder" },
|
|
432
|
-
cadence: { type: "string", description: "recurrence like 30m, 1h, 1d, or cron" },
|
|
433
|
-
},
|
|
434
|
-
required: ["title", "type", "category", "body"],
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
handler: (a) => {
|
|
439
|
-
try {
|
|
440
|
-
const created = (0, tasks_1.getTaskModule)().createTask(buildTaskCreateInput(a));
|
|
441
|
-
return `created: ${created}`;
|
|
442
|
-
}
|
|
443
|
-
catch (error) {
|
|
444
|
-
return `error: ${error instanceof Error ? error.message : String(error)}`;
|
|
445
|
-
}
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
tool: {
|
|
450
|
-
type: "function",
|
|
451
|
-
function: {
|
|
452
|
-
name: "schedule_reminder",
|
|
453
|
-
description: "create a scheduled reminder or recurring daemon job. use `scheduledAt` for one-time reminders and `cadence` for recurring reminders. this writes canonical task fields that the daemon reconciles into OS-level jobs.",
|
|
454
|
-
parameters: {
|
|
455
|
-
type: "object",
|
|
456
|
-
properties: {
|
|
457
|
-
title: { type: "string" },
|
|
458
|
-
body: { type: "string" },
|
|
459
|
-
category: { type: "string" },
|
|
460
|
-
scheduledAt: { type: "string", description: "ISO timestamp for a one-time reminder" },
|
|
461
|
-
cadence: { type: "string", description: "recurrence like 30m, 1h, 1d, or cron" },
|
|
462
|
-
},
|
|
463
|
-
required: ["title", "body"],
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
|
-
},
|
|
467
|
-
handler: (a) => {
|
|
468
|
-
const scheduledAt = normalizeOptionalText(a.scheduledAt);
|
|
469
|
-
const cadence = normalizeOptionalText(a.cadence);
|
|
470
|
-
if (!scheduledAt && !cadence) {
|
|
471
|
-
return "error: provide scheduledAt or cadence";
|
|
472
|
-
}
|
|
473
|
-
try {
|
|
474
|
-
const created = (0, tasks_1.getTaskModule)().createTask({
|
|
475
|
-
title: a.title,
|
|
476
|
-
type: cadence ? "habit" : "one-shot",
|
|
477
|
-
category: normalizeOptionalText(a.category) ?? "reminder",
|
|
478
|
-
body: a.body,
|
|
479
|
-
scheduledAt,
|
|
480
|
-
cadence,
|
|
481
|
-
});
|
|
482
|
-
return `created: ${created}`;
|
|
483
|
-
}
|
|
484
|
-
catch (error) {
|
|
485
|
-
return `error: ${error instanceof Error ? error.message : String(error)}`;
|
|
486
|
-
}
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
{
|
|
490
|
-
tool: {
|
|
491
|
-
type: "function",
|
|
492
|
-
function: {
|
|
493
|
-
name: "task_update_status",
|
|
494
|
-
description: "update a task status using validated transitions",
|
|
495
|
-
parameters: {
|
|
496
|
-
type: "object",
|
|
497
|
-
properties: {
|
|
498
|
-
name: { type: "string" },
|
|
499
|
-
status: { type: "string" },
|
|
500
|
-
},
|
|
501
|
-
required: ["name", "status"],
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
},
|
|
505
|
-
handler: (a) => {
|
|
506
|
-
const result = (0, tasks_1.getTaskModule)().updateStatus(a.name, a.status);
|
|
507
|
-
if (!result.ok) {
|
|
508
|
-
return `error: ${result.reason ?? "status update failed"}`;
|
|
509
|
-
}
|
|
510
|
-
const archivedSuffix = result.archived && result.archived.length > 0
|
|
511
|
-
? ` | archived: ${result.archived.join(", ")}`
|
|
512
|
-
: "";
|
|
513
|
-
return `updated: ${a.name} -> ${result.to}${archivedSuffix}`;
|
|
514
|
-
},
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
tool: {
|
|
518
|
-
type: "function",
|
|
519
|
-
function: {
|
|
520
|
-
name: "task_board_status",
|
|
521
|
-
description: "show board detail for a specific status",
|
|
522
|
-
parameters: {
|
|
523
|
-
type: "object",
|
|
524
|
-
properties: {
|
|
525
|
-
status: { type: "string" },
|
|
526
|
-
},
|
|
527
|
-
required: ["status"],
|
|
528
|
-
},
|
|
529
|
-
},
|
|
530
|
-
},
|
|
531
|
-
handler: (a) => {
|
|
532
|
-
const lines = (0, tasks_1.getTaskModule)().boardStatus(a.status);
|
|
533
|
-
return lines.length > 0 ? lines.join("\n") : "no tasks in that status";
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
{
|
|
537
|
-
tool: {
|
|
538
|
-
type: "function",
|
|
539
|
-
function: {
|
|
540
|
-
name: "task_board_action",
|
|
541
|
-
description: "show tasks or validation issues that require action",
|
|
542
|
-
parameters: {
|
|
543
|
-
type: "object",
|
|
544
|
-
properties: {
|
|
545
|
-
scope: { type: "string" },
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
},
|
|
549
|
-
},
|
|
550
|
-
handler: (a) => {
|
|
551
|
-
const lines = (0, tasks_1.getTaskModule)().boardAction();
|
|
552
|
-
if (!a.scope) {
|
|
553
|
-
return lines.length > 0 ? lines.join("\n") : "no action required";
|
|
554
|
-
}
|
|
555
|
-
const filtered = lines.filter((line) => line.includes(a.scope));
|
|
556
|
-
return filtered.length > 0 ? filtered.join("\n") : "no matching action items";
|
|
557
|
-
},
|
|
558
|
-
},
|
|
559
|
-
{
|
|
560
|
-
tool: {
|
|
561
|
-
type: "function",
|
|
562
|
-
function: {
|
|
563
|
-
name: "task_board_deps",
|
|
564
|
-
description: "show unresolved task dependencies",
|
|
565
|
-
parameters: { type: "object", properties: {} },
|
|
566
|
-
},
|
|
567
|
-
},
|
|
568
|
-
handler: () => {
|
|
569
|
-
const lines = (0, tasks_1.getTaskModule)().boardDeps();
|
|
570
|
-
return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
tool: {
|
|
575
|
-
type: "function",
|
|
576
|
-
function: {
|
|
577
|
-
name: "task_board_sessions",
|
|
578
|
-
description: "show tasks with active coding or sub-agent sessions",
|
|
579
|
-
parameters: { type: "object", properties: {} },
|
|
580
|
-
},
|
|
581
|
-
},
|
|
582
|
-
handler: () => {
|
|
583
|
-
const lines = (0, tasks_1.getTaskModule)().boardSessions();
|
|
584
|
-
return lines.length > 0 ? lines.join("\n") : "no active sessions";
|
|
585
|
-
},
|
|
944
|
+
summaryKeys: ["friendId"],
|
|
586
945
|
},
|
|
587
946
|
{
|
|
588
947
|
tool: {
|
|
@@ -666,55 +1025,214 @@ exports.baseToolDefinitions = [
|
|
|
666
1025
|
return `error saving note: ${err instanceof Error ? err.message : String(err)}`;
|
|
667
1026
|
}
|
|
668
1027
|
},
|
|
1028
|
+
summaryKeys: ["type", "key", "content"],
|
|
669
1029
|
},
|
|
670
1030
|
// -- cross-session awareness --
|
|
1031
|
+
{
|
|
1032
|
+
tool: {
|
|
1033
|
+
type: "function",
|
|
1034
|
+
function: {
|
|
1035
|
+
name: "bridge_manage",
|
|
1036
|
+
description: "create and manage shared live-work bridges across already-active sessions.",
|
|
1037
|
+
parameters: {
|
|
1038
|
+
type: "object",
|
|
1039
|
+
properties: {
|
|
1040
|
+
action: {
|
|
1041
|
+
type: "string",
|
|
1042
|
+
enum: ["begin", "attach", "status", "promote_task", "complete", "cancel"],
|
|
1043
|
+
},
|
|
1044
|
+
bridgeId: { type: "string", description: "bridge id for all actions except begin" },
|
|
1045
|
+
objective: { type: "string", description: "objective for begin" },
|
|
1046
|
+
summary: { type: "string", description: "optional concise shared-work summary" },
|
|
1047
|
+
friendId: { type: "string", description: "target friend id for attach" },
|
|
1048
|
+
channel: { type: "string", description: "target channel for attach" },
|
|
1049
|
+
key: { type: "string", description: "target session key for attach (defaults to 'session')" },
|
|
1050
|
+
title: { type: "string", description: "task title override for promote_task" },
|
|
1051
|
+
category: { type: "string", description: "task category override for promote_task" },
|
|
1052
|
+
body: { type: "string", description: "task body override for promote_task" },
|
|
1053
|
+
},
|
|
1054
|
+
required: ["action"],
|
|
1055
|
+
},
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
handler: async (args, ctx) => {
|
|
1059
|
+
const manager = (0, manager_1.createBridgeManager)();
|
|
1060
|
+
const action = (args.action || "").trim();
|
|
1061
|
+
if (action === "begin") {
|
|
1062
|
+
if (!ctx?.currentSession) {
|
|
1063
|
+
return "bridge_manage begin requires an active session context.";
|
|
1064
|
+
}
|
|
1065
|
+
const objective = (args.objective || "").trim();
|
|
1066
|
+
if (!objective)
|
|
1067
|
+
return "objective is required for bridge begin.";
|
|
1068
|
+
return (0, manager_1.formatBridgeStatus)(manager.beginBridge({
|
|
1069
|
+
objective,
|
|
1070
|
+
summary: (args.summary || objective).trim(),
|
|
1071
|
+
session: ctx.currentSession,
|
|
1072
|
+
}));
|
|
1073
|
+
}
|
|
1074
|
+
const bridgeId = (args.bridgeId || "").trim();
|
|
1075
|
+
if (!bridgeId) {
|
|
1076
|
+
return "bridgeId is required for this bridge action.";
|
|
1077
|
+
}
|
|
1078
|
+
if (action === "attach") {
|
|
1079
|
+
const friendId = (args.friendId || "").trim();
|
|
1080
|
+
const channel = (args.channel || "").trim();
|
|
1081
|
+
const key = (args.key || "session").trim();
|
|
1082
|
+
if (!friendId || !channel) {
|
|
1083
|
+
return "friendId and channel are required for bridge attach.";
|
|
1084
|
+
}
|
|
1085
|
+
const sessionPath = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
1086
|
+
const recall = await recallSessionSafely({
|
|
1087
|
+
sessionPath,
|
|
1088
|
+
friendId,
|
|
1089
|
+
channel,
|
|
1090
|
+
key,
|
|
1091
|
+
messageCount: 20,
|
|
1092
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
1093
|
+
summarize: ctx?.summarize,
|
|
1094
|
+
});
|
|
1095
|
+
if (recall.kind === "missing") {
|
|
1096
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
1097
|
+
}
|
|
1098
|
+
return (0, manager_1.formatBridgeStatus)(manager.attachSession(bridgeId, {
|
|
1099
|
+
friendId,
|
|
1100
|
+
channel,
|
|
1101
|
+
key,
|
|
1102
|
+
sessionPath,
|
|
1103
|
+
snapshot: recall.kind === "ok" ? recall.snapshot : EMPTY_SESSION_MESSAGE,
|
|
1104
|
+
}));
|
|
1105
|
+
}
|
|
1106
|
+
if (action === "status") {
|
|
1107
|
+
const bridge = manager.getBridge(bridgeId);
|
|
1108
|
+
if (!bridge)
|
|
1109
|
+
return `bridge not found: ${bridgeId}`;
|
|
1110
|
+
return (0, manager_1.formatBridgeStatus)(bridge);
|
|
1111
|
+
}
|
|
1112
|
+
if (action === "promote_task") {
|
|
1113
|
+
return (0, manager_1.formatBridgeStatus)(manager.promoteBridgeToTask(bridgeId, {
|
|
1114
|
+
title: args.title,
|
|
1115
|
+
category: args.category,
|
|
1116
|
+
body: args.body,
|
|
1117
|
+
}));
|
|
1118
|
+
}
|
|
1119
|
+
if (action === "complete") {
|
|
1120
|
+
return (0, manager_1.formatBridgeStatus)(manager.completeBridge(bridgeId));
|
|
1121
|
+
}
|
|
1122
|
+
if (action === "cancel") {
|
|
1123
|
+
return (0, manager_1.formatBridgeStatus)(manager.cancelBridge(bridgeId));
|
|
1124
|
+
}
|
|
1125
|
+
return `unknown bridge action: ${action}`;
|
|
1126
|
+
},
|
|
1127
|
+
summaryKeys: ["action", "bridgeId", "objective", "friendId", "channel", "key"],
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
tool: {
|
|
1131
|
+
type: "function",
|
|
1132
|
+
function: {
|
|
1133
|
+
name: "query_active_work",
|
|
1134
|
+
description: "read the current live world-state across visible sessions, coding lanes, inner work, and return obligations. use this instead of piecing status together from separate session and coding tools.",
|
|
1135
|
+
parameters: {
|
|
1136
|
+
type: "object",
|
|
1137
|
+
properties: {},
|
|
1138
|
+
},
|
|
1139
|
+
},
|
|
1140
|
+
},
|
|
1141
|
+
handler: async (_args, ctx) => {
|
|
1142
|
+
const frame = await buildToolActiveWorkFrame(ctx);
|
|
1143
|
+
return `this is my current top-level live world-state.\nanswer whole-self status questions from this before drilling into individual sessions.\n\n${(0, active_work_1.formatActiveWorkFrame)(frame)}`;
|
|
1144
|
+
},
|
|
1145
|
+
},
|
|
671
1146
|
{
|
|
672
1147
|
tool: {
|
|
673
1148
|
type: "function",
|
|
674
1149
|
function: {
|
|
675
1150
|
name: "query_session",
|
|
676
|
-
description: "
|
|
1151
|
+
description: "inspect another session. use transcript for recent context, status for self/inner progress, or search to find older history by query.",
|
|
677
1152
|
parameters: {
|
|
678
1153
|
type: "object",
|
|
679
1154
|
properties: {
|
|
680
1155
|
friendId: { type: "string", description: "the friend UUID (or 'self')" },
|
|
681
|
-
channel: { type: "string", description: "the channel: cli, teams, or
|
|
1156
|
+
channel: { type: "string", description: "the channel: cli, teams, bluebubbles, inner, or mcp" },
|
|
682
1157
|
key: { type: "string", description: "session key (defaults to 'session')" },
|
|
683
1158
|
messageCount: { type: "string", description: "how many recent messages to return (default 20)" },
|
|
1159
|
+
mode: {
|
|
1160
|
+
type: "string",
|
|
1161
|
+
enum: ["transcript", "status", "search"],
|
|
1162
|
+
description: "transcript (default), lightweight status for self/inner checks, or search for older history",
|
|
1163
|
+
},
|
|
1164
|
+
query: { type: "string", description: "required when mode=search; search term for older session history" },
|
|
684
1165
|
},
|
|
685
1166
|
required: ["friendId", "channel"],
|
|
686
1167
|
},
|
|
687
1168
|
},
|
|
688
1169
|
},
|
|
689
1170
|
handler: async (args, ctx) => {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (tail.length === 0)
|
|
702
|
-
return "session exists but has no non-system messages.";
|
|
703
|
-
const transcript = tail.map((m) => `[${m.role}] ${m.content}`).join("\n");
|
|
704
|
-
// LLM summarization when summarize function is available
|
|
705
|
-
if (ctx?.summarize) {
|
|
706
|
-
const trustLevel = ctx.context?.friend?.trustLevel ?? "family";
|
|
707
|
-
const isSelfQuery = friendId === "self";
|
|
708
|
-
const instruction = isSelfQuery
|
|
709
|
-
? "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning."
|
|
710
|
-
: `summarize this session transcript. the person asking has trust level: ${trustLevel}. family=full transparency, friend=share work and general topics but protect other people's identities, acquaintance=very guarded minimal disclosure.`;
|
|
711
|
-
return await ctx.summarize(transcript, instruction);
|
|
1171
|
+
let friendId = args.friendId;
|
|
1172
|
+
const channel = args.channel;
|
|
1173
|
+
const key = args.key || "session";
|
|
1174
|
+
const count = parseInt(args.messageCount || "20", 10);
|
|
1175
|
+
const mode = args.mode || "transcript";
|
|
1176
|
+
// Resolve friend name → UUID if not already a UUID or "self"
|
|
1177
|
+
if (friendId && friendId !== "self" && !/^[0-9a-f]{8}-[0-9a-f]{4}-/.test(friendId) && ctx?.friendStore?.listAll) {
|
|
1178
|
+
const allFriends = await ctx.friendStore.listAll();
|
|
1179
|
+
const match = allFriends.find(f => f.name.toLowerCase() === friendId.toLowerCase());
|
|
1180
|
+
if (match) {
|
|
1181
|
+
friendId = match.id;
|
|
712
1182
|
}
|
|
713
|
-
return transcript;
|
|
714
1183
|
}
|
|
715
|
-
|
|
716
|
-
|
|
1184
|
+
if (mode === "status") {
|
|
1185
|
+
if (friendId !== "self" || channel !== "inner") {
|
|
1186
|
+
return "status mode is only available for self/inner dialog.";
|
|
1187
|
+
}
|
|
1188
|
+
const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)((0, identity_1.getAgentRoot)());
|
|
1189
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
1190
|
+
return renderInnerProgressStatus((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
|
|
1191
|
+
}
|
|
1192
|
+
if (mode === "search") {
|
|
1193
|
+
const query = (args.query || "").trim();
|
|
1194
|
+
if (!query) {
|
|
1195
|
+
return "search mode requires a non-empty query.";
|
|
1196
|
+
}
|
|
1197
|
+
const search = await searchSessionSafely({
|
|
1198
|
+
sessionPath: (0, config_1.resolveSessionPath)(friendId, channel, key),
|
|
1199
|
+
friendId,
|
|
1200
|
+
channel,
|
|
1201
|
+
key,
|
|
1202
|
+
query,
|
|
1203
|
+
});
|
|
1204
|
+
if (search.kind === "missing") {
|
|
1205
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
1206
|
+
}
|
|
1207
|
+
if (search.kind === "empty") {
|
|
1208
|
+
return EMPTY_SESSION_MESSAGE;
|
|
1209
|
+
}
|
|
1210
|
+
if (search.kind === "no_match") {
|
|
1211
|
+
return `no matches for "${search.query}" in that session.\n\n${search.snapshot}`;
|
|
1212
|
+
}
|
|
1213
|
+
return [
|
|
1214
|
+
`history search: "${search.query}"`,
|
|
1215
|
+
search.snapshot,
|
|
1216
|
+
...search.matches.map((match, index) => `match ${index + 1}\n${match}`),
|
|
1217
|
+
].join("\n\n");
|
|
1218
|
+
}
|
|
1219
|
+
const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
1220
|
+
const recall = await recallSessionSafely({
|
|
1221
|
+
sessionPath: sessFile,
|
|
1222
|
+
friendId,
|
|
1223
|
+
channel,
|
|
1224
|
+
key,
|
|
1225
|
+
messageCount: count,
|
|
1226
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
1227
|
+
summarize: ctx?.summarize,
|
|
1228
|
+
});
|
|
1229
|
+
if (recall.kind === "missing") {
|
|
1230
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
1231
|
+
}
|
|
1232
|
+
if (recall.kind === "empty") {
|
|
1233
|
+
return EMPTY_SESSION_MESSAGE;
|
|
717
1234
|
}
|
|
1235
|
+
return recall.summary;
|
|
718
1236
|
},
|
|
719
1237
|
},
|
|
720
1238
|
{
|
|
@@ -722,12 +1240,12 @@ exports.baseToolDefinitions = [
|
|
|
722
1240
|
type: "function",
|
|
723
1241
|
function: {
|
|
724
1242
|
name: "send_message",
|
|
725
|
-
description: "send a message to a friend's session. the
|
|
1243
|
+
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.",
|
|
726
1244
|
parameters: {
|
|
727
1245
|
type: "object",
|
|
728
1246
|
properties: {
|
|
729
1247
|
friendId: { type: "string", description: "the friend UUID (or 'self')" },
|
|
730
|
-
channel: { type: "string", description: "the channel: cli, teams, or
|
|
1248
|
+
channel: { type: "string", description: "the channel: cli, teams, bluebubbles, inner, or mcp" },
|
|
731
1249
|
key: { type: "string", description: "session key (defaults to 'session')" },
|
|
732
1250
|
content: { type: "string", description: "the message content to send" },
|
|
733
1251
|
},
|
|
@@ -735,44 +1253,310 @@ exports.baseToolDefinitions = [
|
|
|
735
1253
|
},
|
|
736
1254
|
},
|
|
737
1255
|
},
|
|
738
|
-
handler: async (args) => {
|
|
1256
|
+
handler: async (args, ctx) => {
|
|
739
1257
|
const friendId = args.friendId;
|
|
740
1258
|
const channel = args.channel;
|
|
741
1259
|
const key = args.key || "session";
|
|
742
1260
|
const content = args.content;
|
|
743
1261
|
const now = Date.now();
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
const
|
|
1262
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
1263
|
+
// Self-routing: messages to "self" always go to inner dialog pending dir,
|
|
1264
|
+
// regardless of the channel or key the agent specified.
|
|
1265
|
+
const isSelf = friendId === "self";
|
|
1266
|
+
const pendingDir = isSelf
|
|
1267
|
+
? (0, pending_1.getInnerDialogPendingDir)(agentName)
|
|
1268
|
+
: (0, pending_1.getPendingDir)(agentName, friendId, channel, key);
|
|
1269
|
+
const delegatingBridgeId = findDelegatingBridgeId(ctx);
|
|
1270
|
+
const delegatedFrom = isSelf
|
|
1271
|
+
&& ctx?.currentSession
|
|
1272
|
+
&& !(ctx.currentSession.friendId === "self" && ctx.currentSession.channel === "inner")
|
|
1273
|
+
? {
|
|
1274
|
+
friendId: ctx.currentSession.friendId,
|
|
1275
|
+
channel: ctx.currentSession.channel,
|
|
1276
|
+
key: ctx.currentSession.key,
|
|
1277
|
+
...(delegatingBridgeId ? { bridgeId: delegatingBridgeId } : {}),
|
|
1278
|
+
}
|
|
1279
|
+
: undefined;
|
|
1280
|
+
const obligationId = delegatedFrom ? (0, obligations_1.generateObligationId)(now) : undefined;
|
|
748
1281
|
const envelope = {
|
|
749
|
-
from:
|
|
1282
|
+
from: agentName,
|
|
750
1283
|
friendId,
|
|
751
1284
|
channel,
|
|
752
1285
|
key,
|
|
753
1286
|
content,
|
|
754
1287
|
timestamp: now,
|
|
1288
|
+
...(delegatedFrom ? { delegatedFrom, obligationStatus: "pending" } : {}),
|
|
1289
|
+
...(obligationId ? { obligationId } : {}),
|
|
755
1290
|
};
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1291
|
+
if (isSelf) {
|
|
1292
|
+
writePendingEnvelope(pendingDir, envelope);
|
|
1293
|
+
if (delegatedFrom) {
|
|
1294
|
+
try {
|
|
1295
|
+
(0, obligations_2.createObligation)((0, identity_1.getAgentRoot)(), {
|
|
1296
|
+
origin: {
|
|
1297
|
+
friendId: delegatedFrom.friendId,
|
|
1298
|
+
channel: delegatedFrom.channel,
|
|
1299
|
+
key: delegatedFrom.key,
|
|
1300
|
+
},
|
|
1301
|
+
...(delegatedFrom.bridgeId ? { bridgeId: delegatedFrom.bridgeId } : {}),
|
|
1302
|
+
content,
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
catch {
|
|
1306
|
+
/* v8 ignore next -- defensive: obligation store write failure should not break send_message @preserve */
|
|
1307
|
+
}
|
|
1308
|
+
/* v8 ignore next -- obligationId always set when delegatedFrom is set (see generateObligationId above) @preserve */
|
|
1309
|
+
if (obligationId) {
|
|
1310
|
+
(0, obligations_1.createObligation)(agentName, {
|
|
1311
|
+
id: obligationId,
|
|
1312
|
+
origin: delegatedFrom,
|
|
1313
|
+
status: "queued",
|
|
1314
|
+
delegatedContent: content.length > 120 ? `${content.slice(0, 117)}...` : content,
|
|
1315
|
+
createdAt: now,
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1319
|
+
event: "repertoire.obligation_created",
|
|
1320
|
+
component: "repertoire",
|
|
1321
|
+
message: "obligation created for inner dialog delegation",
|
|
1322
|
+
meta: {
|
|
1323
|
+
friendId: delegatedFrom.friendId,
|
|
1324
|
+
channel: delegatedFrom.channel,
|
|
1325
|
+
key: delegatedFrom.key,
|
|
1326
|
+
},
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
let wakeResponse = null;
|
|
1330
|
+
try {
|
|
1331
|
+
wakeResponse = await (0, socket_client_1.requestInnerWake)(agentName);
|
|
1332
|
+
}
|
|
1333
|
+
catch {
|
|
1334
|
+
wakeResponse = null;
|
|
1335
|
+
}
|
|
1336
|
+
if (!wakeResponse?.ok) {
|
|
1337
|
+
const { runInnerDialogTurn } = await Promise.resolve().then(() => __importStar(require("../senses/inner-dialog")));
|
|
1338
|
+
if (ctx?.context?.channel.channel === "inner") {
|
|
1339
|
+
queueMicrotask(() => {
|
|
1340
|
+
void runInnerDialogTurn({ reason: "instinct" });
|
|
1341
|
+
});
|
|
1342
|
+
return renderInnerProgressStatus({
|
|
1343
|
+
queue: "queued to inner/dialog",
|
|
1344
|
+
wake: "inline scheduled",
|
|
1345
|
+
processing: "pending",
|
|
1346
|
+
surfaced: "nothing yet",
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
else {
|
|
1350
|
+
const turnResult = await runInnerDialogTurn({ reason: "instinct" });
|
|
1351
|
+
const surfacedPreview = normalizeProgressOutcome((0, thoughts_1.formatSurfacedValue)((0, thoughts_1.extractThoughtResponseFromMessages)(turnResult?.messages ?? [])));
|
|
1352
|
+
return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
1353
|
+
scope: "inner-delegation",
|
|
1354
|
+
phase: "completed",
|
|
1355
|
+
objective: "queued to inner/dialog",
|
|
1356
|
+
outcomeText: `wake: inline fallback\n${surfacedPreview}`,
|
|
1357
|
+
}));
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return renderInnerProgressStatus({
|
|
1361
|
+
queue: "queued to inner/dialog",
|
|
1362
|
+
wake: "daemon requested",
|
|
1363
|
+
processing: "pending",
|
|
1364
|
+
surfaced: "nothing yet",
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
const deliveryResult = await (0, cross_chat_delivery_1.deliverCrossChatMessage)({
|
|
1368
|
+
friendId,
|
|
1369
|
+
channel,
|
|
1370
|
+
key,
|
|
1371
|
+
content,
|
|
1372
|
+
intent: ctx?.currentSession && ctx.currentSession.friendId !== "self"
|
|
1373
|
+
? "explicit_cross_chat"
|
|
1374
|
+
: "generic_outreach",
|
|
1375
|
+
...(ctx?.currentSession && ctx.currentSession.friendId !== "self"
|
|
1376
|
+
? {
|
|
1377
|
+
authorizingSession: {
|
|
1378
|
+
friendId: ctx.currentSession.friendId,
|
|
1379
|
+
channel: ctx.currentSession.channel,
|
|
1380
|
+
key: ctx.currentSession.key,
|
|
1381
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
1382
|
+
},
|
|
1383
|
+
}
|
|
1384
|
+
: {}),
|
|
1385
|
+
}, {
|
|
1386
|
+
agentName,
|
|
1387
|
+
queuePending: (message) => writePendingEnvelope(pendingDir, message),
|
|
1388
|
+
deliverers: {
|
|
1389
|
+
bluebubbles: async (request) => {
|
|
1390
|
+
const { sendProactiveBlueBubblesMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/bluebubbles")));
|
|
1391
|
+
const result = await sendProactiveBlueBubblesMessageToSession({
|
|
1392
|
+
friendId: request.friendId,
|
|
1393
|
+
sessionKey: request.key,
|
|
1394
|
+
text: request.content,
|
|
1395
|
+
intent: request.intent,
|
|
1396
|
+
authorizingSession: request.authorizingSession,
|
|
1397
|
+
});
|
|
1398
|
+
if (result.delivered) {
|
|
1399
|
+
return {
|
|
1400
|
+
status: "delivered_now",
|
|
1401
|
+
detail: "sent to the active bluebubbles chat now",
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
if (result.reason === "missing_target") {
|
|
1405
|
+
return {
|
|
1406
|
+
status: "blocked",
|
|
1407
|
+
detail: "bluebubbles could not resolve a routable target for that session",
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
if (result.reason === "send_error") {
|
|
1411
|
+
return {
|
|
1412
|
+
status: "failed",
|
|
1413
|
+
detail: "bluebubbles send failed",
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
return {
|
|
1417
|
+
status: "unavailable",
|
|
1418
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
1419
|
+
};
|
|
1420
|
+
},
|
|
1421
|
+
teams: async (request) => {
|
|
1422
|
+
if (!ctx?.botApi) {
|
|
1423
|
+
return {
|
|
1424
|
+
status: "unavailable",
|
|
1425
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
const { sendProactiveTeamsMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/teams")));
|
|
1429
|
+
const result = await sendProactiveTeamsMessageToSession({
|
|
1430
|
+
friendId: request.friendId,
|
|
1431
|
+
sessionKey: request.key,
|
|
1432
|
+
text: request.content,
|
|
1433
|
+
intent: request.intent,
|
|
1434
|
+
authorizingSession: request.authorizingSession,
|
|
1435
|
+
}, {
|
|
1436
|
+
botApi: ctx.botApi,
|
|
1437
|
+
});
|
|
1438
|
+
if (result.delivered) {
|
|
1439
|
+
return {
|
|
1440
|
+
status: "delivered_now",
|
|
1441
|
+
detail: "sent to the active teams chat now",
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
if (result.reason === "missing_target") {
|
|
1445
|
+
return {
|
|
1446
|
+
status: "blocked",
|
|
1447
|
+
detail: "teams could not resolve a routable target for that session",
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
if (result.reason === "send_error") {
|
|
1451
|
+
return {
|
|
1452
|
+
status: "failed",
|
|
1453
|
+
detail: "teams send failed",
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
return {
|
|
1457
|
+
status: "unavailable",
|
|
1458
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
1459
|
+
};
|
|
1460
|
+
},
|
|
1461
|
+
},
|
|
1462
|
+
});
|
|
1463
|
+
return renderCrossChatDeliveryStatus(`${friendId} on ${channel}/${key}`, deliveryResult);
|
|
759
1464
|
},
|
|
760
1465
|
},
|
|
1466
|
+
{
|
|
1467
|
+
tool: {
|
|
1468
|
+
type: "function",
|
|
1469
|
+
function: {
|
|
1470
|
+
name: "set_reasoning_effort",
|
|
1471
|
+
description: "adjust your own reasoning depth for subsequent turns. use higher effort for complex analysis, lower for simple tasks.",
|
|
1472
|
+
parameters: {
|
|
1473
|
+
type: "object",
|
|
1474
|
+
properties: {
|
|
1475
|
+
level: { type: "string", description: "the reasoning effort level to set" },
|
|
1476
|
+
},
|
|
1477
|
+
required: ["level"],
|
|
1478
|
+
},
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
handler: (args, ctx) => {
|
|
1482
|
+
if (!ctx?.supportedReasoningEfforts || !ctx.setReasoningEffort) {
|
|
1483
|
+
return "reasoning effort adjustment is not available in this context.";
|
|
1484
|
+
}
|
|
1485
|
+
const level = (args.level || "").trim();
|
|
1486
|
+
if (!ctx.supportedReasoningEfforts.includes(level)) {
|
|
1487
|
+
return `invalid reasoning effort level "${level}". accepted levels: ${ctx.supportedReasoningEfforts.join(", ")}`;
|
|
1488
|
+
}
|
|
1489
|
+
ctx.setReasoningEffort(level);
|
|
1490
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1491
|
+
component: "repertoire",
|
|
1492
|
+
event: "repertoire.reasoning_effort_changed",
|
|
1493
|
+
message: `reasoning effort set to ${level}`,
|
|
1494
|
+
meta: { level },
|
|
1495
|
+
});
|
|
1496
|
+
return `reasoning effort set to "${level}".`;
|
|
1497
|
+
},
|
|
1498
|
+
requiredCapability: "reasoning-effort",
|
|
1499
|
+
summaryKeys: ["level"],
|
|
1500
|
+
},
|
|
761
1501
|
...tools_1.codingToolDefinitions,
|
|
762
1502
|
];
|
|
763
|
-
// Backward-compat: extract just the OpenAI tool schemas
|
|
764
1503
|
exports.tools = exports.baseToolDefinitions.map((d) => d.tool);
|
|
765
|
-
|
|
766
|
-
exports.baseToolHandlers = Object.fromEntries(exports.baseToolDefinitions.map((d) => [d.tool.function.name, d.handler]));
|
|
767
|
-
exports.finalAnswerTool = {
|
|
1504
|
+
exports.ponderTool = {
|
|
768
1505
|
type: "function",
|
|
769
1506
|
function: {
|
|
770
|
-
name: "
|
|
771
|
-
description: "
|
|
1507
|
+
name: "ponder",
|
|
1508
|
+
description: "i need to sit with this. from a conversation, takes the thread inward with a thought and a parting word. from inner dialog, keeps the wheel turning for another pass. must be the only tool call in the turn. Use when a question deserves more thought than this turn allows. Don't ponder trivial questions.",
|
|
772
1509
|
parameters: {
|
|
773
1510
|
type: "object",
|
|
774
|
-
properties: {
|
|
1511
|
+
properties: {
|
|
1512
|
+
thought: {
|
|
1513
|
+
type: "string",
|
|
1514
|
+
description: "the question or thread that needs more thought — brief framing, not analysis. required from a conversation, ignored from inner dialog.",
|
|
1515
|
+
},
|
|
1516
|
+
say: {
|
|
1517
|
+
type: "string",
|
|
1518
|
+
description: "what you say before going quiet — speak to what caught your attention, not just that something did. required from a conversation, ignored from inner dialog.",
|
|
1519
|
+
},
|
|
1520
|
+
},
|
|
1521
|
+
},
|
|
1522
|
+
},
|
|
1523
|
+
};
|
|
1524
|
+
exports.observeTool = {
|
|
1525
|
+
type: "function",
|
|
1526
|
+
function: {
|
|
1527
|
+
name: "observe",
|
|
1528
|
+
description: "absorb what happened without responding — the moment doesn't call for words. must be the only tool call in the turn.",
|
|
1529
|
+
parameters: {
|
|
1530
|
+
type: "object",
|
|
1531
|
+
properties: {
|
|
1532
|
+
reason: { type: "string", description: "brief reason for staying silent (for logging)" },
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1535
|
+
},
|
|
1536
|
+
};
|
|
1537
|
+
exports.settleTool = {
|
|
1538
|
+
type: "function",
|
|
1539
|
+
function: {
|
|
1540
|
+
name: "settle",
|
|
1541
|
+
description: "respond to the user with your message. call this tool when you are ready to deliver your response. Only call when you have a substantive response. If you're settling with 'I'll look into that,' you probably should be using a tool instead.",
|
|
1542
|
+
parameters: {
|
|
1543
|
+
type: "object",
|
|
1544
|
+
properties: {
|
|
1545
|
+
answer: { type: "string" },
|
|
1546
|
+
intent: { type: "string", enum: ["complete", "blocked", "direct_reply"] },
|
|
1547
|
+
},
|
|
775
1548
|
required: ["answer"],
|
|
776
1549
|
},
|
|
777
1550
|
},
|
|
778
1551
|
};
|
|
1552
|
+
exports.restTool = {
|
|
1553
|
+
type: "function",
|
|
1554
|
+
function: {
|
|
1555
|
+
name: "rest",
|
|
1556
|
+
description: "put this down for now — the wheel stops until the next heartbeat. must be the only tool call in the turn.",
|
|
1557
|
+
parameters: {
|
|
1558
|
+
type: "object",
|
|
1559
|
+
properties: {},
|
|
1560
|
+
},
|
|
1561
|
+
},
|
|
1562
|
+
};
|