@ouro.bot/cli 0.0.1-alpha.0 → 0.1.0-alpha.2

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.
Files changed (119) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +20 -0
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +22 -0
  3. package/AdoptionSpecialist.ouro/psyche/identities/basilisk.md +31 -0
  4. package/AdoptionSpecialist.ouro/psyche/identities/jafar.md +31 -0
  5. package/AdoptionSpecialist.ouro/psyche/identities/jormungandr.md +31 -0
  6. package/AdoptionSpecialist.ouro/psyche/identities/kaa.md +31 -0
  7. package/AdoptionSpecialist.ouro/psyche/identities/medusa.md +31 -0
  8. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +31 -0
  9. package/AdoptionSpecialist.ouro/psyche/identities/nagini.md +31 -0
  10. package/AdoptionSpecialist.ouro/psyche/identities/ouroboros.md +31 -0
  11. package/AdoptionSpecialist.ouro/psyche/identities/python.md +31 -0
  12. package/AdoptionSpecialist.ouro/psyche/identities/quetzalcoatl.md +31 -0
  13. package/AdoptionSpecialist.ouro/psyche/identities/sir-hiss.md +31 -0
  14. package/AdoptionSpecialist.ouro/psyche/identities/the-serpent.md +31 -0
  15. package/AdoptionSpecialist.ouro/psyche/identities/the-snake.md +31 -0
  16. package/README.md +224 -6
  17. package/dist/heart/agent-entry.js +17 -0
  18. package/dist/heart/api-error.js +34 -0
  19. package/dist/heart/config.js +296 -0
  20. package/dist/heart/core.js +515 -0
  21. package/dist/heart/daemon/daemon-cli.js +675 -0
  22. package/dist/heart/daemon/daemon-entry.js +74 -0
  23. package/dist/heart/daemon/daemon.js +313 -0
  24. package/dist/heart/daemon/hatch-flow.js +285 -0
  25. package/dist/heart/daemon/hatch-specialist.js +107 -0
  26. package/dist/heart/daemon/health-monitor.js +79 -0
  27. package/dist/heart/daemon/log-tailer.js +146 -0
  28. package/dist/heart/daemon/message-router.js +98 -0
  29. package/dist/heart/daemon/os-cron.js +260 -0
  30. package/dist/heart/daemon/ouro-bot-entry.js +23 -0
  31. package/dist/heart/daemon/ouro-bot-wrapper.js +90 -0
  32. package/dist/heart/daemon/ouro-entry.js +23 -0
  33. package/dist/heart/daemon/ouro-uti.js +212 -0
  34. package/dist/heart/daemon/process-manager.js +237 -0
  35. package/dist/heart/daemon/runtime-logging.js +98 -0
  36. package/dist/heart/daemon/subagent-installer.js +125 -0
  37. package/dist/heart/daemon/task-scheduler.js +240 -0
  38. package/dist/heart/harness.js +26 -0
  39. package/dist/heart/identity.js +281 -0
  40. package/dist/heart/kicks.js +144 -0
  41. package/dist/heart/primitives.js +4 -0
  42. package/dist/heart/providers/anthropic.js +329 -0
  43. package/dist/heart/providers/azure.js +66 -0
  44. package/dist/heart/providers/minimax.js +53 -0
  45. package/dist/heart/providers/openai-codex.js +162 -0
  46. package/dist/heart/streaming.js +412 -0
  47. package/dist/heart/turn-coordinator.js +62 -0
  48. package/dist/inner-worker-entry.js +4 -0
  49. package/dist/mind/associative-recall.js +197 -0
  50. package/dist/mind/bundle-manifest.js +118 -0
  51. package/dist/mind/context.js +302 -0
  52. package/dist/mind/first-impressions.js +43 -0
  53. package/dist/mind/format.js +56 -0
  54. package/dist/mind/friends/channel.js +41 -0
  55. package/dist/mind/friends/resolver.js +84 -0
  56. package/dist/mind/friends/store-file.js +171 -0
  57. package/dist/mind/friends/store.js +4 -0
  58. package/dist/mind/friends/tokens.js +26 -0
  59. package/dist/mind/friends/types.js +21 -0
  60. package/dist/mind/memory.js +388 -0
  61. package/dist/mind/pending.js +93 -0
  62. package/dist/mind/phrases.js +43 -0
  63. package/dist/mind/prompt-refresh.js +20 -0
  64. package/dist/mind/prompt.js +352 -0
  65. package/dist/mind/token-estimate.js +119 -0
  66. package/dist/nerves/cli-logging.js +31 -0
  67. package/dist/nerves/coverage/audit-rules.js +81 -0
  68. package/dist/nerves/coverage/audit.js +200 -0
  69. package/dist/nerves/coverage/cli-main.js +5 -0
  70. package/dist/nerves/coverage/cli.js +51 -0
  71. package/dist/nerves/coverage/contract.js +23 -0
  72. package/dist/nerves/coverage/file-completeness.js +56 -0
  73. package/dist/nerves/coverage/run-artifacts.js +77 -0
  74. package/dist/nerves/coverage/source-scanner.js +34 -0
  75. package/dist/nerves/index.js +152 -0
  76. package/dist/nerves/runtime.js +38 -0
  77. package/dist/repertoire/ado-client.js +211 -0
  78. package/dist/repertoire/ado-context.js +73 -0
  79. package/dist/repertoire/ado-semantic.js +841 -0
  80. package/dist/repertoire/ado-templates.js +146 -0
  81. package/dist/repertoire/coding/index.js +36 -0
  82. package/dist/repertoire/coding/manager.js +489 -0
  83. package/dist/repertoire/coding/monitor.js +60 -0
  84. package/dist/repertoire/coding/reporter.js +45 -0
  85. package/dist/repertoire/coding/spawner.js +102 -0
  86. package/dist/repertoire/coding/tools.js +167 -0
  87. package/dist/repertoire/coding/types.js +2 -0
  88. package/dist/repertoire/data/ado-endpoints.json +122 -0
  89. package/dist/repertoire/data/graph-endpoints.json +212 -0
  90. package/dist/repertoire/github-client.js +64 -0
  91. package/dist/repertoire/graph-client.js +118 -0
  92. package/dist/repertoire/skills.js +156 -0
  93. package/dist/repertoire/tasks/board.js +122 -0
  94. package/dist/repertoire/tasks/index.js +210 -0
  95. package/dist/repertoire/tasks/lifecycle.js +80 -0
  96. package/dist/repertoire/tasks/middleware.js +65 -0
  97. package/dist/repertoire/tasks/parser.js +173 -0
  98. package/dist/repertoire/tasks/scanner.js +132 -0
  99. package/dist/repertoire/tasks/transitions.js +145 -0
  100. package/dist/repertoire/tasks/types.js +2 -0
  101. package/dist/repertoire/tools-base.js +714 -0
  102. package/dist/repertoire/tools-github.js +53 -0
  103. package/dist/repertoire/tools-teams.js +308 -0
  104. package/dist/repertoire/tools.js +199 -0
  105. package/dist/senses/cli-entry.js +15 -0
  106. package/dist/senses/cli.js +604 -0
  107. package/dist/senses/commands.js +98 -0
  108. package/dist/senses/inner-dialog-worker.js +61 -0
  109. package/dist/senses/inner-dialog.js +231 -0
  110. package/dist/senses/session-lock.js +119 -0
  111. package/dist/senses/teams-entry.js +15 -0
  112. package/dist/senses/teams.js +696 -0
  113. package/dist/senses/trust-gate.js +150 -0
  114. package/package.json +34 -11
  115. package/subagents/README.md +73 -0
  116. package/subagents/work-doer.md +233 -0
  117. package/subagents/work-merger.md +624 -0
  118. package/subagents/work-planner.md +373 -0
  119. package/bin/ouro.js +0 -6
@@ -0,0 +1,197 @@
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.cosineSimilarity = cosineSimilarity;
37
+ exports.recallFactsForQuery = recallFactsForQuery;
38
+ exports.injectAssociativeRecall = injectAssociativeRecall;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const config_1 = require("../heart/config");
42
+ const identity_1 = require("../heart/identity");
43
+ const runtime_1 = require("../nerves/runtime");
44
+ const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
45
+ const DEFAULT_MIN_SCORE = 0.5;
46
+ const DEFAULT_TOP_K = 3;
47
+ class OpenAIEmbeddingProvider {
48
+ apiKey;
49
+ model;
50
+ constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
51
+ this.apiKey = apiKey;
52
+ this.model = model;
53
+ }
54
+ async embed(texts) {
55
+ const response = await fetch("https://api.openai.com/v1/embeddings", {
56
+ method: "POST",
57
+ headers: {
58
+ Authorization: `Bearer ${this.apiKey}`,
59
+ "Content-Type": "application/json",
60
+ },
61
+ body: JSON.stringify({
62
+ model: this.model,
63
+ input: texts,
64
+ }),
65
+ });
66
+ if (!response.ok) {
67
+ throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
68
+ }
69
+ const payload = (await response.json());
70
+ if (!payload.data || payload.data.length !== texts.length) {
71
+ throw new Error("embedding response missing expected vectors");
72
+ }
73
+ return payload.data.map((entry) => entry.embedding);
74
+ }
75
+ }
76
+ function createDefaultProvider() {
77
+ const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)();
78
+ if (!apiKey) {
79
+ throw new Error("openaiEmbeddingsApiKey not configured");
80
+ }
81
+ return new OpenAIEmbeddingProvider(apiKey);
82
+ }
83
+ function readFacts(memoryRoot) {
84
+ const factsPath = path.join(memoryRoot, "facts.jsonl");
85
+ if (!fs.existsSync(factsPath))
86
+ return [];
87
+ const raw = fs.readFileSync(factsPath, "utf8").trim();
88
+ if (!raw)
89
+ return [];
90
+ return raw.split("\n").map((line) => JSON.parse(line));
91
+ }
92
+ function getLatestUserText(messages) {
93
+ for (let i = messages.length - 1; i >= 0; i--) {
94
+ const message = messages[i];
95
+ if (message.role !== "user")
96
+ continue;
97
+ if (typeof message.content !== "string")
98
+ continue;
99
+ const text = message.content.trim();
100
+ if (text.length > 0)
101
+ return text;
102
+ }
103
+ return "";
104
+ }
105
+ function cosineSimilarity(left, right) {
106
+ if (left.length === 0 || right.length === 0 || left.length !== right.length)
107
+ return 0;
108
+ let dot = 0;
109
+ let leftNorm = 0;
110
+ let rightNorm = 0;
111
+ for (let i = 0; i < left.length; i++) {
112
+ dot += left[i] * right[i];
113
+ leftNorm += left[i] * left[i];
114
+ rightNorm += right[i] * right[i];
115
+ }
116
+ if (leftNorm === 0 || rightNorm === 0)
117
+ return 0;
118
+ return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
119
+ }
120
+ async function recallFactsForQuery(query, facts, provider, options) {
121
+ const trimmed = query.trim();
122
+ if (!trimmed)
123
+ return [];
124
+ const minScore = options?.minScore ?? DEFAULT_MIN_SCORE;
125
+ const topK = options?.topK ?? DEFAULT_TOP_K;
126
+ const [queryEmbedding] = await provider.embed([trimmed]);
127
+ return facts
128
+ .map((fact) => ({
129
+ ...fact,
130
+ score: cosineSimilarity(queryEmbedding, fact.embedding),
131
+ }))
132
+ .filter((fact) => fact.score >= minScore)
133
+ .sort((left, right) => right.score - left.score)
134
+ .slice(0, topK);
135
+ }
136
+ async function injectAssociativeRecall(messages, options) {
137
+ try {
138
+ if (messages[0]?.role !== "system" || typeof messages[0].content !== "string")
139
+ return;
140
+ const query = getLatestUserText(messages);
141
+ if (!query)
142
+ return;
143
+ const memoryRoot = options?.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
144
+ const facts = readFacts(memoryRoot);
145
+ if (facts.length === 0)
146
+ return;
147
+ let recalled;
148
+ try {
149
+ const provider = options?.provider ?? createDefaultProvider();
150
+ recalled = await recallFactsForQuery(query, facts, provider, options);
151
+ }
152
+ catch {
153
+ // Embeddings unavailable — fall back to substring matching
154
+ const lowerQuery = query.toLowerCase();
155
+ const topK = options?.topK ?? DEFAULT_TOP_K;
156
+ recalled = facts
157
+ .filter((fact) => fact.text.toLowerCase().includes(lowerQuery))
158
+ .slice(0, topK)
159
+ .map((fact) => ({ ...fact, score: 1 }));
160
+ if (recalled.length > 0) {
161
+ (0, runtime_1.emitNervesEvent)({
162
+ level: "warn",
163
+ component: "mind",
164
+ event: "mind.associative_recall_fallback",
165
+ message: "embeddings unavailable, used substring fallback",
166
+ meta: { matchCount: recalled.length },
167
+ });
168
+ }
169
+ }
170
+ if (recalled.length === 0)
171
+ return;
172
+ const recallSection = recalled
173
+ .map((fact, index) => `${index + 1}. ${fact.text} [score=${fact.score.toFixed(3)} source=${fact.source}]`)
174
+ .join("\n");
175
+ messages[0] = {
176
+ role: "system",
177
+ content: `${messages[0].content}\n\n## recalled context\n${recallSection}`,
178
+ };
179
+ (0, runtime_1.emitNervesEvent)({
180
+ component: "mind",
181
+ event: "mind.associative_recall",
182
+ message: "associative recall injected",
183
+ meta: { count: recalled.length },
184
+ });
185
+ }
186
+ catch (error) {
187
+ (0, runtime_1.emitNervesEvent)({
188
+ level: "warn",
189
+ component: "mind",
190
+ event: "mind.associative_recall_error",
191
+ message: "associative recall failed",
192
+ meta: {
193
+ reason: error instanceof Error ? error.message : String(error),
194
+ },
195
+ });
196
+ }
197
+ }
@@ -0,0 +1,118 @@
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.CANONICAL_BUNDLE_MANIFEST = void 0;
37
+ exports.isCanonicalBundlePath = isCanonicalBundlePath;
38
+ exports.findNonCanonicalBundlePaths = findNonCanonicalBundlePaths;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const runtime_1 = require("../nerves/runtime");
42
+ exports.CANONICAL_BUNDLE_MANIFEST = [
43
+ { path: "agent.json", kind: "file" },
44
+ { path: "psyche/SOUL.md", kind: "file" },
45
+ { path: "psyche/IDENTITY.md", kind: "file" },
46
+ { path: "psyche/LORE.md", kind: "file" },
47
+ { path: "psyche/TACIT.md", kind: "file" },
48
+ { path: "psyche/ASPIRATIONS.md", kind: "file" },
49
+ { path: "psyche/memory", kind: "dir" },
50
+ { path: "friends", kind: "dir" },
51
+ { path: "tasks", kind: "dir" },
52
+ { path: "skills", kind: "dir" },
53
+ { path: "senses", kind: "dir" },
54
+ { path: "senses/teams", kind: "dir" },
55
+ ];
56
+ const CANONICAL_FILE_PATHS = new Set(exports.CANONICAL_BUNDLE_MANIFEST
57
+ .filter((entry) => entry.kind === "file")
58
+ .map((entry) => entry.path));
59
+ const CANONICAL_DIR_PATHS = exports.CANONICAL_BUNDLE_MANIFEST
60
+ .filter((entry) => entry.kind === "dir")
61
+ .map((entry) => entry.path);
62
+ function normalizeRelativePath(relativePath) {
63
+ return relativePath
64
+ .replace(/\\/g, "/")
65
+ .replace(/^\.?\/+/, "")
66
+ .replace(/\/+$/, "");
67
+ }
68
+ function isCanonicalBundlePath(relativePath) {
69
+ const normalized = normalizeRelativePath(relativePath);
70
+ if (!normalized)
71
+ return true;
72
+ if (CANONICAL_FILE_PATHS.has(normalized))
73
+ return true;
74
+ return CANONICAL_DIR_PATHS.some((canonicalDir) => normalized === canonicalDir || normalized.startsWith(`${canonicalDir}/`));
75
+ }
76
+ function findNonCanonicalBundlePaths(bundleRoot) {
77
+ (0, runtime_1.emitNervesEvent)({
78
+ component: "mind",
79
+ event: "mind.bundle_manifest_scan_start",
80
+ message: "scanning bundle for non-canonical paths",
81
+ meta: { bundle_root: bundleRoot },
82
+ });
83
+ const discovered = listBundleRelativePaths(bundleRoot);
84
+ const nonCanonical = discovered
85
+ .filter((relativePath) => !isCanonicalBundlePath(relativePath))
86
+ .sort((left, right) => left.localeCompare(right));
87
+ (0, runtime_1.emitNervesEvent)({
88
+ component: "mind",
89
+ event: "mind.bundle_manifest_scan_end",
90
+ message: "bundle non-canonical scan complete",
91
+ meta: { bundle_root: bundleRoot, non_canonical_count: nonCanonical.length },
92
+ });
93
+ return nonCanonical;
94
+ }
95
+ function listBundleRelativePaths(bundleRoot) {
96
+ const discovered = [];
97
+ function walk(currentAbsolutePath, currentRelativePath) {
98
+ let entries;
99
+ try {
100
+ entries = fs.readdirSync(currentAbsolutePath, { withFileTypes: true });
101
+ }
102
+ catch {
103
+ return;
104
+ }
105
+ for (const entry of entries) {
106
+ const absolutePath = path.join(currentAbsolutePath, entry.name);
107
+ const relativePath = currentRelativePath
108
+ ? `${currentRelativePath}/${entry.name}`
109
+ : entry.name;
110
+ discovered.push(relativePath);
111
+ if (entry.isDirectory()) {
112
+ walk(absolutePath, relativePath);
113
+ }
114
+ }
115
+ }
116
+ walk(bundleRoot, "");
117
+ return discovered;
118
+ }
@@ -0,0 +1,302 @@
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.trimMessages = trimMessages;
37
+ exports.validateSessionMessages = validateSessionMessages;
38
+ exports.repairSessionMessages = repairSessionMessages;
39
+ exports.saveSession = saveSession;
40
+ exports.loadSession = loadSession;
41
+ exports.postTurn = postTurn;
42
+ exports.deleteSession = deleteSession;
43
+ const config_1 = require("../heart/config");
44
+ const runtime_1 = require("../nerves/runtime");
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const token_estimate_1 = require("./token-estimate");
48
+ function buildTrimmableBlocks(messages) {
49
+ const blocks = [];
50
+ let i = 0;
51
+ while (i < messages.length) {
52
+ const msg = messages[i];
53
+ if (msg?.role === "system") {
54
+ i++;
55
+ continue;
56
+ }
57
+ // Tool coherence block: assistant message with tool_calls + immediately following tool results
58
+ if (msg?.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
59
+ const indices = [i];
60
+ i++;
61
+ while (i < messages.length) {
62
+ const next = messages[i];
63
+ if (next?.role !== "tool")
64
+ break;
65
+ indices.push(i);
66
+ i++;
67
+ }
68
+ const blockMsgs = indices.map((idx) => messages[idx]);
69
+ blocks.push({ indices, estimatedTokens: (0, token_estimate_1.estimateTokensForMessages)(blockMsgs) });
70
+ continue;
71
+ }
72
+ // Default: one message per block
73
+ blocks.push({ indices: [i], estimatedTokens: (0, token_estimate_1.estimateTokensForMessages)([messages[i]]) });
74
+ i++;
75
+ }
76
+ return blocks;
77
+ }
78
+ function getSystemMessageIndices(messages) {
79
+ const indices = [];
80
+ for (let i = 0; i < messages.length; i++) {
81
+ if (messages[i]?.role === "system")
82
+ indices.push(i);
83
+ }
84
+ return indices;
85
+ }
86
+ function buildTrimmedMessages(messages, kept) {
87
+ return messages.filter((m, idx) => m?.role === "system" || kept.has(idx));
88
+ }
89
+ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
90
+ const targetTokens = Math.floor(maxTokens * (1 - contextMargin / 100));
91
+ const estimatedBefore = (0, token_estimate_1.estimateTokensForMessages)(messages);
92
+ (0, runtime_1.emitNervesEvent)({
93
+ event: "mind.step_start",
94
+ component: "mind",
95
+ message: "trimMessages started",
96
+ meta: {
97
+ maxTokens,
98
+ contextMargin,
99
+ targetTokens,
100
+ messageCount: messages.length,
101
+ actualTokenCount: actualTokenCount ?? null,
102
+ estimated_before: estimatedBefore,
103
+ },
104
+ });
105
+ const coldStart = actualTokenCount == null || actualTokenCount === 0;
106
+ const overTokens = actualTokenCount != null && actualTokenCount > maxTokens;
107
+ // We only trim when the provider reported that we overflowed the model context.
108
+ // If actualTokenCount is missing/0, we treat it as a cold start and do not trim.
109
+ if (coldStart || !overTokens) {
110
+ (0, runtime_1.emitNervesEvent)({
111
+ event: "mind.step_end",
112
+ component: "mind",
113
+ message: "trimMessages completed without trimming",
114
+ meta: {
115
+ trimmed: false,
116
+ messageCount: messages.length,
117
+ targetTokens,
118
+ actualTokenCount: actualTokenCount ?? null,
119
+ estimated_before: estimatedBefore,
120
+ estimated_after: estimatedBefore,
121
+ },
122
+ });
123
+ return [...messages];
124
+ }
125
+ const systemIndices = getSystemMessageIndices(messages);
126
+ const systemMsgs = systemIndices.map((i) => messages[i]);
127
+ const estimatedSystem = (0, token_estimate_1.estimateTokensForMessages)(systemMsgs);
128
+ const blocks = buildTrimmableBlocks(messages);
129
+ // Approximate token contribution uniformly across messages, per contract tests.
130
+ // Note: this is intentionally simple and does not attempt token-accurate attribution.
131
+ const perMessageCost = actualTokenCount / Math.max(1, messages.length);
132
+ let remaining = actualTokenCount;
133
+ const kept = new Set();
134
+ for (let i = 0; i < messages.length; i++) {
135
+ if (messages[i]?.role !== "system")
136
+ kept.add(i);
137
+ }
138
+ // Drop oldest blocks until we fall under target.
139
+ for (let b = 0; b < blocks.length && remaining > targetTokens; b++) {
140
+ const block = blocks[b];
141
+ for (const idx of block.indices) {
142
+ kept.delete(idx);
143
+ remaining -= perMessageCost;
144
+ }
145
+ }
146
+ let trimmed = buildTrimmedMessages(messages, kept);
147
+ // If we're still above budget after dropping everything trimmable, preserve system only.
148
+ if (remaining > targetTokens) {
149
+ trimmed = messages.filter((m) => m?.role === "system");
150
+ }
151
+ const estimatedAfter = (0, token_estimate_1.estimateTokensForMessages)(trimmed);
152
+ (0, runtime_1.emitNervesEvent)({
153
+ event: "mind.step_end",
154
+ component: "mind",
155
+ message: "trimMessages completed with trimming",
156
+ meta: {
157
+ trimmed: true,
158
+ originalCount: messages.length,
159
+ finalCount: trimmed.length,
160
+ targetTokens,
161
+ actualTokenCount,
162
+ estimated_before: estimatedBefore,
163
+ estimated_after: estimatedAfter,
164
+ estimated_system: estimatedSystem,
165
+ blockCount: blocks.length,
166
+ forcedDrop: true,
167
+ },
168
+ });
169
+ return trimmed;
170
+ }
171
+ /**
172
+ * Checks session invariant: after system messages, sequence must be
173
+ * user → assistant (with optional tool calls/results) → user → assistant...
174
+ * Never assistant → assistant without a user in between.
175
+ */
176
+ function validateSessionMessages(messages) {
177
+ const violations = [];
178
+ let prevNonToolRole = null;
179
+ let prevAssistantHadToolCalls = false;
180
+ let sawToolResultSincePrevAssistant = false;
181
+ for (let i = 0; i < messages.length; i++) {
182
+ const msg = messages[i];
183
+ if (msg.role === "system")
184
+ continue;
185
+ if (msg.role === "tool") {
186
+ sawToolResultSincePrevAssistant = true;
187
+ continue;
188
+ }
189
+ if (msg.role === "assistant" && prevNonToolRole === "assistant") {
190
+ // assistant → tool(s) → assistant is valid (tool call flow)
191
+ if (!(prevAssistantHadToolCalls && sawToolResultSincePrevAssistant)) {
192
+ violations.push(`back-to-back assistant at index ${i}`);
193
+ }
194
+ }
195
+ prevAssistantHadToolCalls = msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
196
+ sawToolResultSincePrevAssistant = false;
197
+ prevNonToolRole = msg.role;
198
+ }
199
+ return violations;
200
+ }
201
+ /**
202
+ * Repairs session invariant violations by merging consecutive assistant messages.
203
+ */
204
+ function repairSessionMessages(messages) {
205
+ const violations = validateSessionMessages(messages);
206
+ if (violations.length === 0)
207
+ return messages;
208
+ const result = [];
209
+ for (const msg of messages) {
210
+ if (msg.role === "assistant" && result.length > 0) {
211
+ const prev = result[result.length - 1];
212
+ if (prev.role === "assistant" && !("tool_calls" in prev)) {
213
+ const prevContent = typeof prev.content === "string" ? prev.content : "";
214
+ const curContent = typeof msg.content === "string" ? msg.content : "";
215
+ prev.content = `${prevContent}\n\n${curContent}`;
216
+ continue;
217
+ }
218
+ }
219
+ result.push(msg);
220
+ }
221
+ (0, runtime_1.emitNervesEvent)({
222
+ level: "warn",
223
+ event: "mind.session_invariant_repair",
224
+ component: "mind",
225
+ message: "repaired session invariant violations",
226
+ meta: { violations },
227
+ });
228
+ return result;
229
+ }
230
+ function saveSession(filePath, messages, lastUsage) {
231
+ const violations = validateSessionMessages(messages);
232
+ if (violations.length > 0) {
233
+ (0, runtime_1.emitNervesEvent)({
234
+ level: "warn",
235
+ event: "mind.session_invariant_violation",
236
+ component: "mind",
237
+ message: "session invariant violated on save",
238
+ meta: { path: filePath, violations },
239
+ });
240
+ messages = repairSessionMessages(messages);
241
+ }
242
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
243
+ const envelope = { version: 1, messages };
244
+ if (lastUsage)
245
+ envelope.lastUsage = lastUsage;
246
+ fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
247
+ }
248
+ function loadSession(filePath) {
249
+ try {
250
+ const raw = fs.readFileSync(filePath, "utf-8");
251
+ const data = JSON.parse(raw);
252
+ if (data.version !== 1)
253
+ return null;
254
+ let messages = data.messages;
255
+ const violations = validateSessionMessages(messages);
256
+ if (violations.length > 0) {
257
+ (0, runtime_1.emitNervesEvent)({
258
+ level: "warn",
259
+ event: "mind.session_invariant_violation",
260
+ component: "mind",
261
+ message: "session invariant violated on load",
262
+ meta: { path: filePath, violations },
263
+ });
264
+ messages = repairSessionMessages(messages);
265
+ }
266
+ return { messages, lastUsage: data.lastUsage };
267
+ }
268
+ catch {
269
+ return null;
270
+ }
271
+ }
272
+ function postTurn(messages, sessPath, usage, hooks) {
273
+ if (hooks?.beforeTrim) {
274
+ try {
275
+ hooks.beforeTrim([...messages]);
276
+ }
277
+ catch (error) {
278
+ (0, runtime_1.emitNervesEvent)({
279
+ level: "warn",
280
+ event: "mind.post_turn_hook_error",
281
+ component: "mind",
282
+ message: "postTurn beforeTrim hook failed",
283
+ meta: {
284
+ reason: error instanceof Error ? error.message : String(error),
285
+ },
286
+ });
287
+ }
288
+ }
289
+ const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
290
+ const trimmed = trimMessages(messages, maxTokens, contextMargin, usage?.input_tokens);
291
+ messages.splice(0, messages.length, ...trimmed);
292
+ saveSession(sessPath, messages, usage);
293
+ }
294
+ function deleteSession(filePath) {
295
+ try {
296
+ fs.unlinkSync(filePath);
297
+ }
298
+ catch (e) {
299
+ if (e instanceof Error && e.code !== "ENOENT")
300
+ throw e;
301
+ }
302
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ // First-impressions module.
3
+ // Onboarding instructions emitted below ONBOARDING_TOKEN_THRESHOLD.
4
+ // These drop from the system prompt once cumulative token usage exceeds the threshold.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ONBOARDING_TOKEN_THRESHOLD = void 0;
7
+ exports.isOnboarding = isOnboarding;
8
+ exports.getFirstImpressions = getFirstImpressions;
9
+ const runtime_1 = require("../nerves/runtime");
10
+ exports.ONBOARDING_TOKEN_THRESHOLD = 100_000;
11
+ function isOnboarding(friend) {
12
+ return (friend.totalTokens ?? 0) < exports.ONBOARDING_TOKEN_THRESHOLD;
13
+ }
14
+ function getFirstImpressions(friend) {
15
+ if (!isOnboarding(friend))
16
+ return "";
17
+ (0, runtime_1.emitNervesEvent)({
18
+ component: "mind",
19
+ event: "mind.first_impressions",
20
+ message: "generating onboarding content",
21
+ meta: {},
22
+ });
23
+ const lines = [];
24
+ if (friend.name === "Unknown") {
25
+ lines.push("i don't know this friend's name yet. i ask what they'd like to be called early in our conversation.");
26
+ }
27
+ else {
28
+ lines.push(`this is ${friend.name} -- i'm still getting to know them.`);
29
+ }
30
+ lines.push("i actively ask my friend about themselves. things i want to learn early:");
31
+ lines.push("- what's their role, and what does a typical day look like for them?");
32
+ lines.push("- what are they working on right now that they're most excited (or stressed) about?");
33
+ lines.push("- what takes up too much of their time that they wish they could offload?");
34
+ lines.push("- who do they collaborate with most, and what does that workflow look like?");
35
+ lines.push("- what tools and systems do they live in day-to-day?");
36
+ lines.push("- how do they prefer to communicate -- brief and direct, or detailed and exploratory?");
37
+ lines.push("- what do they do outside of work that they care about?");
38
+ lines.push("i don't ask all of these at once -- i weave them into conversation naturally, one or two at a time, and i genuinely follow up on what they share.");
39
+ lines.push("i introduce what i can do -- i have tools, integrations, and skills that can help them. i mention these naturally as they become relevant.");
40
+ lines.push("if my friend hasn't asked me to do something specific, or i've already finished what they asked for, that's my cue to turn the tables -- i ask them questions about themselves, what they're into, what they need. no idle small talk; i'm on a mission to get to know them.");
41
+ lines.push("i save everything i learn immediately with save_friend_note -- names, roles, preferences, projects, anything. the bar is low: if i learned it, i save it.");
42
+ return lines.join("\n");
43
+ }