@ouro.bot/cli 0.1.0-alpha.19 → 0.1.0-alpha.190
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/README.md +187 -191
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
- package/changelog.json +1280 -0
- package/dist/heart/active-work.js +832 -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/cares.js +174 -0
- package/dist/heart/commitments.js +111 -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 +2470 -210
- 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-flow.js +31 -112
- package/dist/heart/daemon/hatch-specialist.js +3 -3
- 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 +1331 -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-prompt.js +10 -7
- package/dist/heart/daemon/specialist-tools.js +49 -3
- 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/intentions.js +168 -0
- 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 +220 -0
- package/dist/heart/presence.js +185 -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 +187 -41
- 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 +36 -27
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -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/heart/wake-packet.js +226 -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/episodes.js +116 -0
- 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 +36 -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 +779 -102
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- 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 +330 -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 +1345 -320
- 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 +349 -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 +526 -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 +508 -243
- package/dist/senses/commands.js +65 -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 +633 -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 -6
- 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
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildWakePacket = buildWakePacket;
|
|
4
|
+
exports.renderWakePacket = renderWakePacket;
|
|
5
|
+
exports.renderCompactWakePacket = renderCompactWakePacket;
|
|
6
|
+
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
const tempo_1 = require("./tempo");
|
|
8
|
+
function estimateTokens(text) {
|
|
9
|
+
return Math.ceil(text.length / 4);
|
|
10
|
+
}
|
|
11
|
+
// --- Section builders (pure derivation from authored data, no embellishment) ---
|
|
12
|
+
function buildPlotLine(episodes, tempo) {
|
|
13
|
+
if (episodes.length === 0)
|
|
14
|
+
return "";
|
|
15
|
+
const limit = tempo === "crisis" ? 3 : tempo === "brief" ? 2 : 5;
|
|
16
|
+
const selected = episodes.slice(0, limit);
|
|
17
|
+
const lines = selected.map((ep) => {
|
|
18
|
+
const parts = [`- ${ep.summary}`];
|
|
19
|
+
if (ep.whyItMattered && tempo !== "brief") {
|
|
20
|
+
parts[0] += ` (${ep.whyItMattered})`;
|
|
21
|
+
}
|
|
22
|
+
return parts[0];
|
|
23
|
+
});
|
|
24
|
+
return lines.join("\n");
|
|
25
|
+
}
|
|
26
|
+
function buildObligationsSection(obligations) {
|
|
27
|
+
if (obligations.length === 0)
|
|
28
|
+
return "";
|
|
29
|
+
return obligations
|
|
30
|
+
.map((ob) => {
|
|
31
|
+
const parts = [`- ${ob.content}`];
|
|
32
|
+
if (ob.meaning?.resumeHint) {
|
|
33
|
+
parts.push(` [hint: ${ob.meaning.resumeHint}]`);
|
|
34
|
+
}
|
|
35
|
+
if (ob.meaning?.stalenessClass && ob.meaning.stalenessClass !== "fresh") {
|
|
36
|
+
parts.push(` (${ob.meaning.stalenessClass})`);
|
|
37
|
+
}
|
|
38
|
+
if (ob.meaning?.waitingOn) {
|
|
39
|
+
parts.push(` waiting on ${ob.meaning.waitingOn.kind}: ${ob.meaning.waitingOn.target}`);
|
|
40
|
+
}
|
|
41
|
+
return parts.join("");
|
|
42
|
+
})
|
|
43
|
+
.join("\n");
|
|
44
|
+
}
|
|
45
|
+
function buildCaresSection(cares) {
|
|
46
|
+
if (cares.length === 0)
|
|
47
|
+
return "";
|
|
48
|
+
return cares
|
|
49
|
+
.map((c) => {
|
|
50
|
+
const parts = [`- ${c.label}`];
|
|
51
|
+
if (c.salience !== "low") {
|
|
52
|
+
parts.push(` [${c.salience}]`);
|
|
53
|
+
}
|
|
54
|
+
if (c.currentRisk) {
|
|
55
|
+
parts.push(` risk: ${c.currentRisk}`);
|
|
56
|
+
}
|
|
57
|
+
return parts.join("");
|
|
58
|
+
})
|
|
59
|
+
.join("\n");
|
|
60
|
+
}
|
|
61
|
+
function buildPresenceSection(peers) {
|
|
62
|
+
if (peers.length === 0)
|
|
63
|
+
return "";
|
|
64
|
+
return peers
|
|
65
|
+
.map((p) => `- ${p.agentName}: ${p.availability}/${p.lane}`)
|
|
66
|
+
.join("\n");
|
|
67
|
+
}
|
|
68
|
+
function buildResumeHint(view) {
|
|
69
|
+
// Compose from authored obligation resumeHints and top intentions
|
|
70
|
+
const hints = [];
|
|
71
|
+
for (const ob of view.activeObligations) {
|
|
72
|
+
if (ob.meaning?.resumeHint) {
|
|
73
|
+
hints.push(ob.meaning.resumeHint);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const intent of view.openIntentions.slice(0, 3)) {
|
|
77
|
+
hints.push(intent.content);
|
|
78
|
+
}
|
|
79
|
+
if (hints.length === 0) {
|
|
80
|
+
// Fall back to top obligation content
|
|
81
|
+
if (view.activeObligations.length > 0) {
|
|
82
|
+
hints.push(view.activeObligations[0].content);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (hints.length === 0)
|
|
86
|
+
return "";
|
|
87
|
+
return hints.join("; ");
|
|
88
|
+
}
|
|
89
|
+
function buildWakePacket(view) {
|
|
90
|
+
const tempo = view.tempo;
|
|
91
|
+
const tokenBudget = tempo_1.TEMPO_BUDGETS[tempo];
|
|
92
|
+
const packet = {
|
|
93
|
+
plotLine: buildPlotLine(view.recentEpisodes, tempo),
|
|
94
|
+
obligations: buildObligationsSection(view.activeObligations),
|
|
95
|
+
cares: buildCaresSection(view.activeCares),
|
|
96
|
+
presence: buildPresenceSection(view.peerPresence),
|
|
97
|
+
resumeHint: buildResumeHint(view),
|
|
98
|
+
tempo,
|
|
99
|
+
tokenBudget,
|
|
100
|
+
assembledAt: new Date().toISOString(),
|
|
101
|
+
};
|
|
102
|
+
(0, runtime_1.emitNervesEvent)({
|
|
103
|
+
component: "heart",
|
|
104
|
+
event: "heart.wake_packet_built",
|
|
105
|
+
message: `wake packet built: tempo=${tempo}`,
|
|
106
|
+
meta: {
|
|
107
|
+
tempo,
|
|
108
|
+
plotLineTokens: estimateTokens(packet.plotLine),
|
|
109
|
+
obligationsTokens: estimateTokens(packet.obligations),
|
|
110
|
+
caresTokens: estimateTokens(packet.cares),
|
|
111
|
+
presenceTokens: estimateTokens(packet.presence),
|
|
112
|
+
resumeHintTokens: estimateTokens(packet.resumeHint),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
return packet;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Renders a wake packet to prompt text, respecting token budget.
|
|
119
|
+
* Truncation priority (last truncated first):
|
|
120
|
+
* resumeHint (PROTECTED) > obligations > cares > plotLine > presence
|
|
121
|
+
* So presence is truncated first, then plotLine, then cares, then obligations.
|
|
122
|
+
* resumeHint is never truncated.
|
|
123
|
+
*/
|
|
124
|
+
function renderWakePacket(packet) {
|
|
125
|
+
const budget = packet.tokenBudget;
|
|
126
|
+
// Assemble sections in priority order (highest priority first for budget allocation)
|
|
127
|
+
// Each section is { label, content, priority } where lower priority number = truncated first
|
|
128
|
+
const sections = [
|
|
129
|
+
{ label: "resume", content: packet.resumeHint, priority: 5 },
|
|
130
|
+
{ label: "obligations", content: packet.obligations, priority: 4 },
|
|
131
|
+
{ label: "cares", content: packet.cares, priority: 3 },
|
|
132
|
+
{ label: "plot", content: packet.plotLine, priority: 2 },
|
|
133
|
+
{ label: "presence", content: packet.presence, priority: 1 },
|
|
134
|
+
].filter((s) => s.content.length > 0);
|
|
135
|
+
if (sections.length === 0) {
|
|
136
|
+
(0, runtime_1.emitNervesEvent)({
|
|
137
|
+
component: "heart",
|
|
138
|
+
event: "heart.wake_packet_rendered",
|
|
139
|
+
message: "wake packet rendered: empty",
|
|
140
|
+
meta: { tokens: 0, tempo: packet.tempo },
|
|
141
|
+
});
|
|
142
|
+
return "";
|
|
143
|
+
}
|
|
144
|
+
// Build the rendered output, truncating lower-priority sections first
|
|
145
|
+
let rendered = formatSections(sections);
|
|
146
|
+
let tokens = estimateTokens(rendered);
|
|
147
|
+
// Truncate sections from lowest priority until we fit budget
|
|
148
|
+
const sortedByPriority = [...sections].sort((a, b) => a.priority - b.priority);
|
|
149
|
+
for (const section of sortedByPriority) {
|
|
150
|
+
if (tokens <= budget.max)
|
|
151
|
+
break;
|
|
152
|
+
// Skip resumeHint — it's PROTECTED
|
|
153
|
+
if (section.label === "resume")
|
|
154
|
+
continue;
|
|
155
|
+
// Remove this section entirely
|
|
156
|
+
const idx = sections.findIndex((s) => s.label === section.label);
|
|
157
|
+
sections.splice(idx, 1);
|
|
158
|
+
rendered = formatSections(sections);
|
|
159
|
+
tokens = estimateTokens(rendered);
|
|
160
|
+
}
|
|
161
|
+
// If still over budget after removing all non-protected sections, trim what's left
|
|
162
|
+
if (tokens > budget.max) {
|
|
163
|
+
const maxChars = budget.max * 4;
|
|
164
|
+
rendered = rendered.slice(0, maxChars);
|
|
165
|
+
}
|
|
166
|
+
(0, runtime_1.emitNervesEvent)({
|
|
167
|
+
component: "heart",
|
|
168
|
+
event: "heart.wake_packet_rendered",
|
|
169
|
+
message: `wake packet rendered: ${tokens} tokens`,
|
|
170
|
+
meta: { tokens, tempo: packet.tempo, sectionCount: sections.length },
|
|
171
|
+
});
|
|
172
|
+
return rendered;
|
|
173
|
+
}
|
|
174
|
+
function formatSections(sections) {
|
|
175
|
+
const parts = [];
|
|
176
|
+
for (const section of sections) {
|
|
177
|
+
switch (section.label) {
|
|
178
|
+
case "resume":
|
|
179
|
+
parts.push(`**Next:** ${section.content}`);
|
|
180
|
+
break;
|
|
181
|
+
case "obligations":
|
|
182
|
+
parts.push(`**Owed:**\n${section.content}`);
|
|
183
|
+
break;
|
|
184
|
+
case "cares":
|
|
185
|
+
parts.push(`**Cares:**\n${section.content}`);
|
|
186
|
+
break;
|
|
187
|
+
case "plot":
|
|
188
|
+
parts.push(`**Recent:**\n${section.content}`);
|
|
189
|
+
break;
|
|
190
|
+
case "presence":
|
|
191
|
+
parts.push(`**Peers:**\n${section.content}`);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return parts.join("\n\n");
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Ultra-compact version for coding context (max 200 tokens).
|
|
199
|
+
* Just resumeHint + top obligation + top care, single-line bullets.
|
|
200
|
+
*/
|
|
201
|
+
function renderCompactWakePacket(packet) {
|
|
202
|
+
const parts = [];
|
|
203
|
+
if (packet.resumeHint) {
|
|
204
|
+
parts.push(`next: ${packet.resumeHint}`);
|
|
205
|
+
}
|
|
206
|
+
if (packet.obligations) {
|
|
207
|
+
// Just first line of obligations
|
|
208
|
+
const firstOb = packet.obligations.split("\n")[0];
|
|
209
|
+
parts.push(`owed: ${firstOb.replace(/^- /, "")}`);
|
|
210
|
+
}
|
|
211
|
+
if (packet.cares) {
|
|
212
|
+
const firstCare = packet.cares.split("\n")[0];
|
|
213
|
+
parts.push(`care: ${firstCare.replace(/^- /, "")}`);
|
|
214
|
+
}
|
|
215
|
+
const compact = parts.join(" | ");
|
|
216
|
+
// Hard cap at 200 tokens (800 chars)
|
|
217
|
+
const maxChars = 200 * 4;
|
|
218
|
+
const result = compact.length > maxChars ? compact.slice(0, maxChars) : compact;
|
|
219
|
+
(0, runtime_1.emitNervesEvent)({
|
|
220
|
+
component: "heart",
|
|
221
|
+
event: "heart.wake_packet_compact_rendered",
|
|
222
|
+
message: `compact wake packet: ${estimateTokens(result)} tokens`,
|
|
223
|
+
meta: { tokens: estimateTokens(result) },
|
|
224
|
+
});
|
|
225
|
+
return result;
|
|
226
|
+
}
|