@ouro.bot/cli 0.1.0-alpha.17 → 0.1.0-alpha.170
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 +1145 -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 +608 -123
- 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 +2473 -206
- package/dist/heart/daemon/daemon-entry.js +140 -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 +446 -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 +161 -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 -8
- package/dist/heart/daemon/specialist-tools.js +53 -4
- 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/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 +649 -89
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/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-CBueh0d3.css +1 -0
- package/dist/outlook-ui/assets/index-GtHoFpRM.js +61 -0
- package/dist/outlook-ui/index.html +15 -0
- package/dist/repertoire/ado-semantic.js +6 -0
- 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 +135 -2
- 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/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 +991 -272
- 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 +281 -122
- 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-entry.js +1 -1
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +419 -152
- 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 +502 -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 +13 -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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listTargetSessionCandidates = listTargetSessionCandidates;
|
|
4
|
+
exports.formatTargetSessionCandidates = formatTargetSessionCandidates;
|
|
5
|
+
const session_recall_1 = require("./session-recall");
|
|
6
|
+
const session_activity_1 = require("./session-activity");
|
|
7
|
+
const trust_explanation_1 = require("../mind/friends/trust-explanation");
|
|
8
|
+
const runtime_1 = require("../nerves/runtime");
|
|
9
|
+
function synthesizeFriendRecord(candidate) {
|
|
10
|
+
return {
|
|
11
|
+
id: candidate.friendId,
|
|
12
|
+
name: candidate.friendName,
|
|
13
|
+
role: "stranger",
|
|
14
|
+
trustLevel: "stranger",
|
|
15
|
+
connections: [],
|
|
16
|
+
externalIds: [],
|
|
17
|
+
tenantMemberships: [],
|
|
18
|
+
toolPreferences: {},
|
|
19
|
+
notes: {},
|
|
20
|
+
totalTokens: 0,
|
|
21
|
+
createdAt: new Date(0).toISOString(),
|
|
22
|
+
updatedAt: new Date(0).toISOString(),
|
|
23
|
+
schemaVersion: 1,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function deliveryPriority(mode) {
|
|
27
|
+
if (mode === "deliver_now")
|
|
28
|
+
return 0;
|
|
29
|
+
if (mode === "queue_only")
|
|
30
|
+
return 1;
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
function activityPriority(source) {
|
|
34
|
+
return source === "friend-facing" ? 0 : 1;
|
|
35
|
+
}
|
|
36
|
+
function describeDelivery(candidate) {
|
|
37
|
+
if (candidate.channel === "mcp") {
|
|
38
|
+
// MCP can't push (no proactive API), but can queue — dev tool picks up via check_response
|
|
39
|
+
return { mode: "queue_only", reason: "MCP session — message queued for dev tool to pick up via check_response" };
|
|
40
|
+
}
|
|
41
|
+
if (candidate.channel !== "bluebubbles" && candidate.channel !== "teams") {
|
|
42
|
+
return { mode: "blocked", reason: "this channel does not support proactive outward delivery yet" };
|
|
43
|
+
}
|
|
44
|
+
if (candidate.trust.level === "family" || candidate.trust.level === "friend") {
|
|
45
|
+
return { mode: "deliver_now", reason: "directly trusted target on a proactive-delivery channel" };
|
|
46
|
+
}
|
|
47
|
+
return { mode: "queue_only", reason: "visible as a live chat, but immediate delivery still needs explicit cross-chat authorization" };
|
|
48
|
+
}
|
|
49
|
+
async function listTargetSessionCandidates(input) {
|
|
50
|
+
(0, runtime_1.emitNervesEvent)({
|
|
51
|
+
component: "engine",
|
|
52
|
+
event: "engine.target_resolution_start",
|
|
53
|
+
message: "listing live target session candidates",
|
|
54
|
+
meta: {
|
|
55
|
+
sessionsDir: input.sessionsDir,
|
|
56
|
+
currentSession: input.currentSession
|
|
57
|
+
? `${input.currentSession.friendId}/${input.currentSession.channel}/${input.currentSession.key}`
|
|
58
|
+
: null,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const activity = (0, session_activity_1.listSessionActivity)({
|
|
62
|
+
sessionsDir: input.sessionsDir,
|
|
63
|
+
friendsDir: input.friendsDir,
|
|
64
|
+
agentName: input.agentName,
|
|
65
|
+
currentSession: input.currentSession ?? null,
|
|
66
|
+
}).filter((entry) => entry.channel !== "inner");
|
|
67
|
+
const candidates = [];
|
|
68
|
+
for (const entry of activity) {
|
|
69
|
+
const friend = await input.friendStore.get(entry.friendId) ?? synthesizeFriendRecord(entry);
|
|
70
|
+
const trust = (0, trust_explanation_1.describeTrustContext)({
|
|
71
|
+
friend,
|
|
72
|
+
channel: entry.channel,
|
|
73
|
+
});
|
|
74
|
+
const recall = await (0, session_recall_1.recallSession)({
|
|
75
|
+
sessionPath: entry.sessionPath,
|
|
76
|
+
friendId: entry.friendId,
|
|
77
|
+
channel: entry.channel,
|
|
78
|
+
key: entry.key,
|
|
79
|
+
messageCount: 6,
|
|
80
|
+
summarize: input.summarize,
|
|
81
|
+
trustLevel: trust.level,
|
|
82
|
+
});
|
|
83
|
+
const snapshot = recall.kind === "ok"
|
|
84
|
+
? recall.snapshot
|
|
85
|
+
: recall.kind === "empty"
|
|
86
|
+
? "recent focus: no recent visible messages"
|
|
87
|
+
: "recent focus: session transcript unavailable";
|
|
88
|
+
const delivery = describeDelivery({
|
|
89
|
+
channel: entry.channel,
|
|
90
|
+
trust,
|
|
91
|
+
});
|
|
92
|
+
candidates.push({
|
|
93
|
+
friendId: entry.friendId,
|
|
94
|
+
friendName: entry.friendName,
|
|
95
|
+
channel: entry.channel,
|
|
96
|
+
key: entry.key,
|
|
97
|
+
sessionPath: entry.sessionPath,
|
|
98
|
+
snapshot,
|
|
99
|
+
trust,
|
|
100
|
+
delivery,
|
|
101
|
+
lastActivityAt: entry.lastActivityAt,
|
|
102
|
+
lastActivityMs: entry.lastActivityMs,
|
|
103
|
+
activitySource: entry.activitySource,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return candidates.sort((a, b) => {
|
|
107
|
+
const deliveryDiff = deliveryPriority(a.delivery.mode) - deliveryPriority(b.delivery.mode);
|
|
108
|
+
if (deliveryDiff !== 0)
|
|
109
|
+
return deliveryDiff;
|
|
110
|
+
const sourceDiff = activityPriority(a.activitySource) - activityPriority(b.activitySource);
|
|
111
|
+
if (sourceDiff !== 0)
|
|
112
|
+
return sourceDiff;
|
|
113
|
+
return b.lastActivityMs - a.lastActivityMs;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function formatTargetSessionCandidates(candidates) {
|
|
117
|
+
if (candidates.length === 0)
|
|
118
|
+
return "";
|
|
119
|
+
const lines = ["## candidate target chats"];
|
|
120
|
+
for (const candidate of candidates) {
|
|
121
|
+
lines.push(`- ${candidate.friendName} [${candidate.friendId}] via ${candidate.channel}/${candidate.key}`);
|
|
122
|
+
lines.push(` trust: ${candidate.trust.level} (${candidate.trust.basis}) — ${candidate.trust.summary}`);
|
|
123
|
+
lines.push(` delivery: ${candidate.delivery.mode} — ${candidate.delivery.reason}`);
|
|
124
|
+
lines.push(` snapshot: ${candidate.snapshot}`);
|
|
125
|
+
}
|
|
126
|
+
return lines.join("\n");
|
|
127
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createToolActivityCallbacks = createToolActivityCallbacks;
|
|
4
|
+
const tool_description_1 = require("./tool-description");
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
function createToolActivityCallbacks(options) {
|
|
7
|
+
(0, runtime_1.emitNervesEvent)({
|
|
8
|
+
component: "engine",
|
|
9
|
+
event: "engine.tool_activity_callbacks_created",
|
|
10
|
+
message: "tool activity callbacks initialized",
|
|
11
|
+
meta: {},
|
|
12
|
+
});
|
|
13
|
+
// Track the last description so we can reference it in END messages
|
|
14
|
+
let lastDescription = null;
|
|
15
|
+
return {
|
|
16
|
+
onToolStart(name, args) {
|
|
17
|
+
const description = (0, tool_description_1.humanReadableToolDescription)(name, args);
|
|
18
|
+
if (description === null)
|
|
19
|
+
return; // hidden tool (settle, rest, descend)
|
|
20
|
+
lastDescription = description;
|
|
21
|
+
options.onDescription(description);
|
|
22
|
+
},
|
|
23
|
+
onToolEnd(name, summary, success) {
|
|
24
|
+
const desc = lastDescription ?? name;
|
|
25
|
+
// Strip trailing "..." from description for the result line
|
|
26
|
+
const cleanDesc = desc.endsWith("...") ? desc.slice(0, -3) : desc;
|
|
27
|
+
if (!success) {
|
|
28
|
+
options.onFailure(`\u2717 ${cleanDesc} — ${summary}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (options.isDebug()) {
|
|
32
|
+
options.onResult(`\u2713 ${cleanDesc}`);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.humanReadableToolDescription = humanReadableToolDescription;
|
|
4
|
+
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
function basename(filePath) {
|
|
6
|
+
const idx = filePath.lastIndexOf("/");
|
|
7
|
+
return idx >= 0 ? filePath.slice(idx + 1) : filePath;
|
|
8
|
+
}
|
|
9
|
+
function truncate(text, max) {
|
|
10
|
+
if (text.length <= max)
|
|
11
|
+
return text;
|
|
12
|
+
// Cut at last space before maxLen to avoid mid-word truncation
|
|
13
|
+
const slice = text.slice(0, max - 1);
|
|
14
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
15
|
+
if (lastSpace > 0)
|
|
16
|
+
return text.slice(0, lastSpace) + "\u2026";
|
|
17
|
+
// No space found (single long word) — hard truncate
|
|
18
|
+
return slice + "\u2026";
|
|
19
|
+
}
|
|
20
|
+
const TOOL_DESCRIPTIONS = {
|
|
21
|
+
// File operations
|
|
22
|
+
shell: (args) => {
|
|
23
|
+
const cmd = args.command;
|
|
24
|
+
if (!cmd)
|
|
25
|
+
return "running a command...";
|
|
26
|
+
return `running ${truncate(cmd, 50)}...`;
|
|
27
|
+
},
|
|
28
|
+
read_file: (args) => {
|
|
29
|
+
const fp = args.path || args.file_path;
|
|
30
|
+
if (!fp)
|
|
31
|
+
return "reading a file...";
|
|
32
|
+
return `reading ${basename(fp)}...`;
|
|
33
|
+
},
|
|
34
|
+
write_file: (args) => {
|
|
35
|
+
const fp = args.path || args.file_path;
|
|
36
|
+
if (!fp)
|
|
37
|
+
return "writing a file...";
|
|
38
|
+
return `writing ${basename(fp)}...`;
|
|
39
|
+
},
|
|
40
|
+
edit_file: (args) => {
|
|
41
|
+
const fp = args.path || args.file_path;
|
|
42
|
+
if (!fp)
|
|
43
|
+
return "editing a file...";
|
|
44
|
+
return `editing ${basename(fp)}...`;
|
|
45
|
+
},
|
|
46
|
+
glob: (args) => {
|
|
47
|
+
const p = args.pattern;
|
|
48
|
+
if (!p)
|
|
49
|
+
return "searching for files...";
|
|
50
|
+
return `searching for ${truncate(p, 40)}...`;
|
|
51
|
+
},
|
|
52
|
+
grep: (args) => {
|
|
53
|
+
const p = args.pattern;
|
|
54
|
+
if (!p)
|
|
55
|
+
return "searching code...";
|
|
56
|
+
return `searching code for '${truncate(p, 40)}'...`;
|
|
57
|
+
},
|
|
58
|
+
// Memory and knowledge
|
|
59
|
+
recall: (args) => {
|
|
60
|
+
const q = args.query;
|
|
61
|
+
if (!q)
|
|
62
|
+
return "searching memory...";
|
|
63
|
+
return `searching memory for '${truncate(q, 40)}'...`;
|
|
64
|
+
},
|
|
65
|
+
diary_write: (args) => {
|
|
66
|
+
const about = args.about;
|
|
67
|
+
return about ? `noting something about ${truncate(about, 30)}...` : "noting something down...";
|
|
68
|
+
},
|
|
69
|
+
save_friend_note: () => "making a note about you...",
|
|
70
|
+
get_friend_note: () => "checking my notes...",
|
|
71
|
+
load_skill: (args) => {
|
|
72
|
+
const name = args.name || args.skill;
|
|
73
|
+
return name ? `loading ${name} skill...` : "loading a skill...";
|
|
74
|
+
},
|
|
75
|
+
// Session and context
|
|
76
|
+
query_session: (args) => {
|
|
77
|
+
const mode = args.mode;
|
|
78
|
+
if (mode === "search")
|
|
79
|
+
return `searching session for '${truncate(args.query || "", 30)}'...`;
|
|
80
|
+
if (mode === "status")
|
|
81
|
+
return "checking inner dialog status...";
|
|
82
|
+
return "checking session history...";
|
|
83
|
+
},
|
|
84
|
+
web_search: (args) => {
|
|
85
|
+
const q = args.query;
|
|
86
|
+
return q ? `searching the web for '${truncate(q, 35)}'...` : "searching the web...";
|
|
87
|
+
},
|
|
88
|
+
coding_spawn: (args) => {
|
|
89
|
+
const runner = args.runner;
|
|
90
|
+
return runner ? `starting ${runner} coding session...` : "starting coding session...";
|
|
91
|
+
},
|
|
92
|
+
coding_status: () => "checking coding sessions...",
|
|
93
|
+
coding_tail: () => "reading coding output...",
|
|
94
|
+
coding_kill: () => "stopping coding session...",
|
|
95
|
+
bridge_manage: () => "managing conversation bridge...",
|
|
96
|
+
// Communication
|
|
97
|
+
send_message: (args) => {
|
|
98
|
+
const to = args.to;
|
|
99
|
+
return to ? `sending a message to ${to}...` : "sending a message...";
|
|
100
|
+
},
|
|
101
|
+
surface: () => "sharing a thought...",
|
|
102
|
+
// Metacognitive (agent's inner life)
|
|
103
|
+
ponder: (args) => {
|
|
104
|
+
const thought = args.thought;
|
|
105
|
+
return thought ? `thinking about ${truncate(thought, 40)}...` : "thinking deeper...";
|
|
106
|
+
},
|
|
107
|
+
observe: () => null,
|
|
108
|
+
claude: () => "reasoning...",
|
|
109
|
+
set_reasoning_effort: (args) => {
|
|
110
|
+
const level = args.level;
|
|
111
|
+
return level ? `setting thinking depth to ${level}...` : "adjusting thinking depth...";
|
|
112
|
+
},
|
|
113
|
+
// Hidden — these ARE the response or end-of-turn
|
|
114
|
+
settle: () => null,
|
|
115
|
+
rest: () => null,
|
|
116
|
+
descend: () => null,
|
|
117
|
+
};
|
|
118
|
+
function humanReadableToolDescription(name, args) {
|
|
119
|
+
(0, runtime_1.emitNervesEvent)({
|
|
120
|
+
component: "engine",
|
|
121
|
+
event: "engine.tool_description",
|
|
122
|
+
message: "generated human-readable tool description",
|
|
123
|
+
meta: { tool: name },
|
|
124
|
+
});
|
|
125
|
+
const builder = TOOL_DESCRIPTIONS[name];
|
|
126
|
+
if (builder)
|
|
127
|
+
return builder(args);
|
|
128
|
+
// MCP tools: mcp__server__toolname — extract last segment
|
|
129
|
+
if (name.startsWith("mcp__")) {
|
|
130
|
+
const parts = name.split("__");
|
|
131
|
+
const toolName = parts[parts.length - 1];
|
|
132
|
+
return `using ${toolName}...`;
|
|
133
|
+
}
|
|
134
|
+
return `using ${name}...`;
|
|
135
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.GLOBAL_CIRCUIT_BREAKER_LIMIT = exports.PING_PONG_WINDOW = exports.GENERIC_REPEAT_LIMIT = exports.POLL_NO_PROGRESS_LIMIT = exports.TOOL_LOOP_HISTORY_LIMIT = void 0;
|
|
37
|
+
exports.createToolLoopState = createToolLoopState;
|
|
38
|
+
exports.recordToolOutcome = recordToolOutcome;
|
|
39
|
+
exports.detectToolLoop = detectToolLoop;
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
exports.TOOL_LOOP_HISTORY_LIMIT = 30;
|
|
43
|
+
exports.POLL_NO_PROGRESS_LIMIT = 3;
|
|
44
|
+
exports.GENERIC_REPEAT_LIMIT = 4;
|
|
45
|
+
exports.PING_PONG_WINDOW = 6;
|
|
46
|
+
exports.GLOBAL_CIRCUIT_BREAKER_LIMIT = 24;
|
|
47
|
+
function stableStringify(value) {
|
|
48
|
+
if (value === null || typeof value !== "object") {
|
|
49
|
+
return JSON.stringify(value);
|
|
50
|
+
}
|
|
51
|
+
/* v8 ignore next -- stableStringify currently receives only objects/strings from normalized loop inputs @preserve */
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
54
|
+
}
|
|
55
|
+
const record = value;
|
|
56
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
57
|
+
}
|
|
58
|
+
function digest(value) {
|
|
59
|
+
return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
|
|
60
|
+
}
|
|
61
|
+
function normalizeArgs(toolName, args) {
|
|
62
|
+
if (toolName === "query_active_work") {
|
|
63
|
+
return { toolName };
|
|
64
|
+
}
|
|
65
|
+
if (toolName === "coding_status" || toolName === "coding_tail") {
|
|
66
|
+
return {
|
|
67
|
+
toolName,
|
|
68
|
+
sessionId: args.sessionId ?? "",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (toolName === "query_session") {
|
|
72
|
+
return {
|
|
73
|
+
toolName,
|
|
74
|
+
mode: args.mode ?? "",
|
|
75
|
+
friendId: args.friendId ?? "",
|
|
76
|
+
channel: args.channel ?? "",
|
|
77
|
+
key: args.key ?? "",
|
|
78
|
+
query: args.query ?? "",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
toolName,
|
|
83
|
+
args,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function normalizeOutcome(result, success) {
|
|
87
|
+
return {
|
|
88
|
+
success,
|
|
89
|
+
result: result.replace(/\s+/g, " ").trim(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function isKnownPollTool(toolName, args) {
|
|
93
|
+
return toolName === "query_active_work"
|
|
94
|
+
|| toolName === "coding_status"
|
|
95
|
+
|| toolName === "coding_tail"
|
|
96
|
+
|| (toolName === "query_session" && (args.mode ?? "").trim() === "status");
|
|
97
|
+
}
|
|
98
|
+
function emitDetection(detector, toolName, count, message, pairedToolName) {
|
|
99
|
+
(0, runtime_1.emitNervesEvent)({
|
|
100
|
+
level: "warn",
|
|
101
|
+
component: "engine",
|
|
102
|
+
event: "engine.tool_loop_detected",
|
|
103
|
+
message: "tool loop guard detected repeated non-progress work",
|
|
104
|
+
meta: {
|
|
105
|
+
detector,
|
|
106
|
+
toolName,
|
|
107
|
+
count,
|
|
108
|
+
pairedToolName: pairedToolName ?? null,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
stuck: true,
|
|
113
|
+
detector,
|
|
114
|
+
count,
|
|
115
|
+
message,
|
|
116
|
+
pairedToolName,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function countTrailingRepeats(history, toolName, callHash) {
|
|
120
|
+
let count = 0;
|
|
121
|
+
let outcomeHash;
|
|
122
|
+
for (let index = history.length - 1; index >= 0; index--) {
|
|
123
|
+
const record = history[index];
|
|
124
|
+
if (record.toolName !== toolName || record.callHash !== callHash) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
if (!outcomeHash) {
|
|
128
|
+
outcomeHash = record.outcomeHash;
|
|
129
|
+
}
|
|
130
|
+
if (record.outcomeHash !== outcomeHash) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
count += 1;
|
|
134
|
+
}
|
|
135
|
+
return { count, outcomeHash };
|
|
136
|
+
}
|
|
137
|
+
function detectPingPong(history, toolName, callHash) {
|
|
138
|
+
if (history.length < exports.PING_PONG_WINDOW) {
|
|
139
|
+
return { stuck: false };
|
|
140
|
+
}
|
|
141
|
+
const recent = history.slice(-exports.PING_PONG_WINDOW);
|
|
142
|
+
const first = recent[0];
|
|
143
|
+
const second = recent[1];
|
|
144
|
+
if (!first.isKnownPoll || !second.isKnownPoll) {
|
|
145
|
+
return { stuck: false };
|
|
146
|
+
}
|
|
147
|
+
if (first.toolName === second.toolName && first.callHash === second.callHash) {
|
|
148
|
+
return { stuck: false };
|
|
149
|
+
}
|
|
150
|
+
for (let index = 0; index < recent.length; index++) {
|
|
151
|
+
const expected = index % 2 === 0 ? first : second;
|
|
152
|
+
const actual = recent[index];
|
|
153
|
+
if (actual.toolName !== expected.toolName
|
|
154
|
+
|| actual.callHash !== expected.callHash
|
|
155
|
+
|| actual.outcomeHash !== expected.outcomeHash) {
|
|
156
|
+
return { stuck: false };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const matchesPair = (toolName === first.toolName && callHash === first.callHash)
|
|
160
|
+
|| (toolName === second.toolName && callHash === second.callHash);
|
|
161
|
+
if (!matchesPair) {
|
|
162
|
+
return { stuck: false };
|
|
163
|
+
}
|
|
164
|
+
const pairedToolName = toolName === first.toolName ? second.toolName : first.toolName;
|
|
165
|
+
return emitDetection("ping_pong", toolName, exports.PING_PONG_WINDOW, `repeated ${first.toolName}/${second.toolName} polling is not changing. stop cycling between status checks and either act on the current state, change approach, or answer truthfully with what is known.`, pairedToolName);
|
|
166
|
+
}
|
|
167
|
+
function createToolLoopState() {
|
|
168
|
+
return { history: [] };
|
|
169
|
+
}
|
|
170
|
+
function recordToolOutcome(state, toolName, args, result, success) {
|
|
171
|
+
state.history.push({
|
|
172
|
+
toolName,
|
|
173
|
+
callHash: digest(normalizeArgs(toolName, args)),
|
|
174
|
+
outcomeHash: digest(normalizeOutcome(result, success)),
|
|
175
|
+
isKnownPoll: isKnownPollTool(toolName, args),
|
|
176
|
+
});
|
|
177
|
+
if (state.history.length > exports.TOOL_LOOP_HISTORY_LIMIT) {
|
|
178
|
+
state.history.splice(0, state.history.length - exports.TOOL_LOOP_HISTORY_LIMIT);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Tools that must never be blocked by the circuit breaker.
|
|
182
|
+
// settle = end the turn, surface = deliver results outward.
|
|
183
|
+
// ponder = continue thinking (inner dialog) or hand off to inner dialog (outer).
|
|
184
|
+
// rest = end inner dialog turn (added in Unit 8b).
|
|
185
|
+
// Blocking these traps the agent: it can think all it wants but can never speak or stop.
|
|
186
|
+
const CIRCUIT_BREAKER_EXEMPT = new Set(["settle", "surface", "ponder", "rest"]);
|
|
187
|
+
function detectToolLoop(state, toolName, args) {
|
|
188
|
+
if (state.history.length >= exports.GLOBAL_CIRCUIT_BREAKER_LIMIT && !CIRCUIT_BREAKER_EXEMPT.has(toolName)) {
|
|
189
|
+
return emitDetection("global_circuit_breaker", toolName, state.history.length, `this turn has already made ${state.history.length} tool calls. stop thrashing, use the current evidence, and either change approach or answer truthfully with the best grounded status.`);
|
|
190
|
+
}
|
|
191
|
+
const callHash = digest(normalizeArgs(toolName, args));
|
|
192
|
+
const trailing = countTrailingRepeats(state.history, toolName, callHash);
|
|
193
|
+
if (isKnownPollTool(toolName, args) && trailing.count >= exports.POLL_NO_PROGRESS_LIMIT) {
|
|
194
|
+
return emitDetection("known_poll_no_progress", toolName, trailing.count, `repeated ${toolName} calls have returned the same state ${trailing.count} times. stop polling and either act on the current state, wait for a meaningful change, or answer truthfully with what is known.`);
|
|
195
|
+
}
|
|
196
|
+
if (trailing.count >= exports.GENERIC_REPEAT_LIMIT) {
|
|
197
|
+
return emitDetection("generic_repeat", toolName, trailing.count, `repeating ${toolName} with the same inputs is not producing new information. change approach, use the evidence already gathered, or answer truthfully with what is known.`);
|
|
198
|
+
}
|
|
199
|
+
return detectPingPong(state.history, toolName, callHash);
|
|
200
|
+
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createTurnCoordinator = createTurnCoordinator;
|
|
4
|
+
exports.withSharedTurnLock = withSharedTurnLock;
|
|
5
|
+
exports.tryBeginSharedTurn = tryBeginSharedTurn;
|
|
6
|
+
exports.endSharedTurn = endSharedTurn;
|
|
7
|
+
exports.isSharedTurnActive = isSharedTurnActive;
|
|
8
|
+
exports.enqueueSharedFollowUp = enqueueSharedFollowUp;
|
|
9
|
+
exports.drainSharedFollowUps = drainSharedFollowUps;
|
|
4
10
|
const runtime_1 = require("../nerves/runtime");
|
|
11
|
+
function scopedKey(scope, key) {
|
|
12
|
+
return `${scope}:${key}`;
|
|
13
|
+
}
|
|
5
14
|
function createTurnCoordinator() {
|
|
6
15
|
const turnLocks = new Map();
|
|
7
16
|
const activeTurns = new Set();
|
|
@@ -60,3 +69,22 @@ function createTurnCoordinator() {
|
|
|
60
69
|
},
|
|
61
70
|
};
|
|
62
71
|
}
|
|
72
|
+
const _sharedTurnCoordinator = createTurnCoordinator();
|
|
73
|
+
function withSharedTurnLock(scope, key, fn) {
|
|
74
|
+
return _sharedTurnCoordinator.withTurnLock(scopedKey(scope, key), fn);
|
|
75
|
+
}
|
|
76
|
+
function tryBeginSharedTurn(scope, key) {
|
|
77
|
+
return _sharedTurnCoordinator.tryBeginTurn(scopedKey(scope, key));
|
|
78
|
+
}
|
|
79
|
+
function endSharedTurn(scope, key) {
|
|
80
|
+
_sharedTurnCoordinator.endTurn(scopedKey(scope, key));
|
|
81
|
+
}
|
|
82
|
+
function isSharedTurnActive(scope, key) {
|
|
83
|
+
return _sharedTurnCoordinator.isTurnActive(scopedKey(scope, key));
|
|
84
|
+
}
|
|
85
|
+
function enqueueSharedFollowUp(scope, key, followUp) {
|
|
86
|
+
_sharedTurnCoordinator.enqueueFollowUp(scopedKey(scope, key), followUp);
|
|
87
|
+
}
|
|
88
|
+
function drainSharedFollowUps(scope, key) {
|
|
89
|
+
return _sharedTurnCoordinator.drainFollowUps(scopedKey(scope, key));
|
|
90
|
+
}
|