iranti 0.2.51 → 0.3.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 (163) hide show
  1. package/README.md +30 -17
  2. package/dist/scripts/api-key-create.js +1 -1
  3. package/dist/scripts/api-key-list.js +1 -1
  4. package/dist/scripts/api-key-revoke.js +1 -1
  5. package/dist/scripts/claude-code-memory-hook.js +116 -30
  6. package/dist/scripts/codex-setup.js +86 -4
  7. package/dist/scripts/iranti-cli.js +1359 -57
  8. package/dist/scripts/iranti-mcp.js +578 -75
  9. package/dist/scripts/seed.js +11 -6
  10. package/dist/scripts/setup.js +1 -1
  11. package/dist/src/api/healthChecks.d.ts +29 -0
  12. package/dist/src/api/healthChecks.d.ts.map +1 -0
  13. package/dist/src/api/healthChecks.js +72 -0
  14. package/dist/src/api/healthChecks.js.map +1 -0
  15. package/dist/src/api/middleware/validation.d.ts +22 -0
  16. package/dist/src/api/middleware/validation.d.ts.map +1 -1
  17. package/dist/src/api/middleware/validation.js +93 -3
  18. package/dist/src/api/middleware/validation.js.map +1 -1
  19. package/dist/src/api/routes/knowledge.d.ts.map +1 -1
  20. package/dist/src/api/routes/knowledge.js +53 -0
  21. package/dist/src/api/routes/knowledge.js.map +1 -1
  22. package/dist/src/api/routes/memory.d.ts.map +1 -1
  23. package/dist/src/api/routes/memory.js +73 -9
  24. package/dist/src/api/routes/memory.js.map +1 -1
  25. package/dist/src/api/server.js +38 -43
  26. package/dist/src/api/server.js.map +1 -1
  27. package/dist/src/attendant/AttendantInstance.d.ts +135 -2
  28. package/dist/src/attendant/AttendantInstance.d.ts.map +1 -1
  29. package/dist/src/attendant/AttendantInstance.js +1836 -93
  30. package/dist/src/attendant/AttendantInstance.js.map +1 -1
  31. package/dist/src/attendant/index.d.ts +1 -1
  32. package/dist/src/attendant/index.d.ts.map +1 -1
  33. package/dist/src/attendant/index.js +1 -1
  34. package/dist/src/attendant/index.js.map +1 -1
  35. package/dist/src/attendant/registry.d.ts.map +1 -1
  36. package/dist/src/attendant/registry.js +2 -0
  37. package/dist/src/attendant/registry.js.map +1 -1
  38. package/dist/src/chat/index.d.ts +23 -0
  39. package/dist/src/chat/index.d.ts.map +1 -1
  40. package/dist/src/chat/index.js +111 -22
  41. package/dist/src/chat/index.js.map +1 -1
  42. package/dist/src/generated/prisma/browser.d.ts +5 -0
  43. package/dist/src/generated/prisma/browser.d.ts.map +1 -1
  44. package/dist/src/generated/prisma/client.d.ts +5 -0
  45. package/dist/src/generated/prisma/client.d.ts.map +1 -1
  46. package/dist/src/generated/prisma/commonInputTypes.d.ts +48 -0
  47. package/dist/src/generated/prisma/commonInputTypes.d.ts.map +1 -1
  48. package/dist/src/generated/prisma/internal/class.d.ts +11 -0
  49. package/dist/src/generated/prisma/internal/class.d.ts.map +1 -1
  50. package/dist/src/generated/prisma/internal/class.js +4 -4
  51. package/dist/src/generated/prisma/internal/class.js.map +1 -1
  52. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +92 -1
  53. package/dist/src/generated/prisma/internal/prismaNamespace.d.ts.map +1 -1
  54. package/dist/src/generated/prisma/internal/prismaNamespace.js +17 -2
  55. package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -1
  56. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +16 -0
  57. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  58. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +17 -2
  59. package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -1
  60. package/dist/src/generated/prisma/models/StaffEvent.d.ts +1184 -0
  61. package/dist/src/generated/prisma/models/StaffEvent.d.ts.map +1 -0
  62. package/dist/src/generated/prisma/models/StaffEvent.js +3 -0
  63. package/dist/src/generated/prisma/models/StaffEvent.js.map +1 -0
  64. package/dist/src/generated/prisma/models.d.ts +1 -0
  65. package/dist/src/generated/prisma/models.d.ts.map +1 -1
  66. package/dist/src/lib/assistantCheckpoint.d.ts +21 -0
  67. package/dist/src/lib/assistantCheckpoint.d.ts.map +1 -0
  68. package/dist/src/lib/assistantCheckpoint.js +143 -0
  69. package/dist/src/lib/assistantCheckpoint.js.map +1 -0
  70. package/dist/src/lib/autoRemember.d.ts +15 -0
  71. package/dist/src/lib/autoRemember.d.ts.map +1 -1
  72. package/dist/src/lib/autoRemember.js +433 -71
  73. package/dist/src/lib/autoRemember.js.map +1 -1
  74. package/dist/src/lib/cliHelpCatalog.d.ts.map +1 -1
  75. package/dist/src/lib/cliHelpCatalog.js +23 -11
  76. package/dist/src/lib/cliHelpCatalog.js.map +1 -1
  77. package/dist/src/lib/cliHelpRenderer.d.ts +1 -0
  78. package/dist/src/lib/cliHelpRenderer.d.ts.map +1 -1
  79. package/dist/src/lib/cliHelpRenderer.js +4 -0
  80. package/dist/src/lib/cliHelpRenderer.js.map +1 -1
  81. package/dist/src/lib/commandErrors.d.ts +5 -1
  82. package/dist/src/lib/commandErrors.d.ts.map +1 -1
  83. package/dist/src/lib/commandErrors.js +250 -17
  84. package/dist/src/lib/commandErrors.js.map +1 -1
  85. package/dist/src/lib/createFirstPartyIranti.d.ts.map +1 -1
  86. package/dist/src/lib/createFirstPartyIranti.js +1 -0
  87. package/dist/src/lib/createFirstPartyIranti.js.map +1 -1
  88. package/dist/src/lib/dbStaffEventEmitter.d.ts +2 -0
  89. package/dist/src/lib/dbStaffEventEmitter.d.ts.map +1 -1
  90. package/dist/src/lib/dbStaffEventEmitter.js +15 -0
  91. package/dist/src/lib/dbStaffEventEmitter.js.map +1 -1
  92. package/dist/src/lib/hostMemoryFormatting.d.ts +25 -0
  93. package/dist/src/lib/hostMemoryFormatting.d.ts.map +1 -0
  94. package/dist/src/lib/hostMemoryFormatting.js +55 -0
  95. package/dist/src/lib/hostMemoryFormatting.js.map +1 -0
  96. package/dist/src/lib/issueFacts.d.ts +37 -0
  97. package/dist/src/lib/issueFacts.d.ts.map +1 -0
  98. package/dist/src/lib/issueFacts.js +72 -0
  99. package/dist/src/lib/issueFacts.js.map +1 -0
  100. package/dist/src/lib/llm.d.ts +8 -0
  101. package/dist/src/lib/llm.d.ts.map +1 -1
  102. package/dist/src/lib/llm.js +33 -0
  103. package/dist/src/lib/llm.js.map +1 -1
  104. package/dist/src/lib/packageRoot.d.ts +2 -0
  105. package/dist/src/lib/packageRoot.d.ts.map +1 -0
  106. package/dist/src/lib/packageRoot.js +22 -0
  107. package/dist/src/lib/packageRoot.js.map +1 -0
  108. package/dist/src/lib/projectLearning.d.ts +21 -0
  109. package/dist/src/lib/projectLearning.d.ts.map +1 -0
  110. package/dist/src/lib/projectLearning.js +357 -0
  111. package/dist/src/lib/projectLearning.js.map +1 -0
  112. package/dist/src/lib/protocolEnforcement.d.ts +29 -0
  113. package/dist/src/lib/protocolEnforcement.d.ts.map +1 -0
  114. package/dist/src/lib/protocolEnforcement.js +124 -0
  115. package/dist/src/lib/protocolEnforcement.js.map +1 -0
  116. package/dist/src/lib/providers/claude.js +1 -1
  117. package/dist/src/lib/providers/claude.js.map +1 -1
  118. package/dist/src/lib/router.js +1 -1
  119. package/dist/src/lib/router.js.map +1 -1
  120. package/dist/src/lib/runtimeEnv.d.ts.map +1 -1
  121. package/dist/src/lib/runtimeEnv.js +8 -3
  122. package/dist/src/lib/runtimeEnv.js.map +1 -1
  123. package/dist/src/lib/scaffoldCloseout.d.ts +27 -0
  124. package/dist/src/lib/scaffoldCloseout.d.ts.map +1 -0
  125. package/dist/src/lib/scaffoldCloseout.js +139 -0
  126. package/dist/src/lib/scaffoldCloseout.js.map +1 -0
  127. package/dist/src/lib/semanticFactTags.d.ts +10 -0
  128. package/dist/src/lib/semanticFactTags.d.ts.map +1 -0
  129. package/dist/src/lib/semanticFactTags.js +166 -0
  130. package/dist/src/lib/semanticFactTags.js.map +1 -0
  131. package/dist/src/lib/sessionLedger.d.ts +94 -0
  132. package/dist/src/lib/sessionLedger.d.ts.map +1 -0
  133. package/dist/src/lib/sessionLedger.js +997 -0
  134. package/dist/src/lib/sessionLedger.js.map +1 -0
  135. package/dist/src/lib/sharedStateInvalidation.d.ts +10 -0
  136. package/dist/src/lib/sharedStateInvalidation.d.ts.map +1 -0
  137. package/dist/src/lib/sharedStateInvalidation.js +184 -0
  138. package/dist/src/lib/sharedStateInvalidation.js.map +1 -0
  139. package/dist/src/lib/staffEventsTable.d.ts +3 -0
  140. package/dist/src/lib/staffEventsTable.d.ts.map +1 -0
  141. package/dist/src/lib/staffEventsTable.js +58 -0
  142. package/dist/src/lib/staffEventsTable.js.map +1 -0
  143. package/dist/src/librarian/index.d.ts.map +1 -1
  144. package/dist/src/librarian/index.js +113 -2
  145. package/dist/src/librarian/index.js.map +1 -1
  146. package/dist/src/library/client.d.ts +6 -1
  147. package/dist/src/library/client.d.ts.map +1 -1
  148. package/dist/src/library/client.js +21 -7
  149. package/dist/src/library/client.js.map +1 -1
  150. package/dist/src/library/embeddings.d.ts +9 -1
  151. package/dist/src/library/embeddings.d.ts.map +1 -1
  152. package/dist/src/library/embeddings.js +28 -3
  153. package/dist/src/library/embeddings.js.map +1 -1
  154. package/dist/src/library/queries.d.ts.map +1 -1
  155. package/dist/src/library/queries.js +263 -46
  156. package/dist/src/library/queries.js.map +1 -1
  157. package/dist/src/sdk/index.d.ts +52 -1
  158. package/dist/src/sdk/index.d.ts.map +1 -1
  159. package/dist/src/sdk/index.js +546 -98
  160. package/dist/src/sdk/index.js.map +1 -1
  161. package/package.json +24 -3
  162. package/prisma/migrations/20260331101500_add_staff_events_ledger/migration.sql +24 -0
  163. package/prisma/schema.prisma +22 -0
@@ -32,15 +32,47 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ const node_fs_1 = __importDefault(require("node:fs"));
40
+ const node_path_1 = __importDefault(require("node:path"));
36
41
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
37
42
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
38
43
  const z = __importStar(require("zod/v4"));
44
+ const sdk_1 = require("../src/sdk");
39
45
  const commandErrors_1 = require("../src/lib/commandErrors");
40
46
  const createFirstPartyIranti_1 = require("../src/lib/createFirstPartyIranti");
41
47
  const runtimeEnv_1 = require("../src/lib/runtimeEnv");
42
48
  const autoRemember_1 = require("../src/lib/autoRemember");
49
+ const staffEventRegistry_1 = require("../src/lib/staffEventRegistry");
50
+ const client_1 = require("../src/library/client");
51
+ const attendant_1 = require("../src/attendant");
52
+ const assistantCheckpoint_1 = require("../src/lib/assistantCheckpoint");
53
+ const hostMemoryFormatting_1 = require("../src/lib/hostMemoryFormatting");
43
54
  (0, runtimeEnv_1.loadRuntimeEnv)();
55
+ let runtimeHostOverride = null;
56
+ let shutdownPromise = null;
57
+ let processIranti = null;
58
+ const HOST_SETUP_CHECKS = {
59
+ claude_code: { file: 'CLAUDE.md', command: 'iranti claude-setup .' },
60
+ codex: { file: 'AGENTS.md', command: 'iranti codex-setup' },
61
+ codex_cli: { file: 'AGENTS.md', command: 'iranti codex-setup' },
62
+ codex_vscode: { file: 'AGENTS.md', command: 'iranti codex-setup' },
63
+ };
64
+ function checkHostSetup(host, cwd) {
65
+ if (!host)
66
+ return [];
67
+ const check = HOST_SETUP_CHECKS[host];
68
+ if (!check)
69
+ return [];
70
+ const filePath = node_path_1.default.join(cwd, check.file);
71
+ if (!node_fs_1.default.existsSync(filePath)) {
72
+ return [`⚠ ${check.file} not found in ${cwd}. Run \`${check.command}\` to complete host setup for this project. Until then, operating rules will not be enforced across sessions.`];
73
+ }
74
+ return [];
75
+ }
44
76
  function printHelp() {
45
77
  console.log([
46
78
  'Iranti MCP Server',
@@ -59,7 +91,8 @@ function printHelp() {
59
91
  ' IRANTI_MCP_AGENT_DESCRIPTION Default agent description',
60
92
  ' IRANTI_MCP_AGENT_MODEL Default agent model label',
61
93
  ' IRANTI_MCP_DEFAULT_SOURCE Default write source (default: ClaudeCode)',
62
- ' IRANTI_AUTO_REMEMBER Opt-in explicit prompt auto-save before attend()',
94
+ ' IRANTI_MCP_HOST Host label for session ledger events (default: generic_mcp)',
95
+ ' IRANTI_AUTO_REMEMBER Opt-in prompt-side auto-save before pre-response attend()',
63
96
  '',
64
97
  'This server is intended for Claude Code and other MCP clients over stdio.',
65
98
  'If you run `iranti mcp` directly in a terminal, it will stay running and wait for an MCP client.',
@@ -72,13 +105,39 @@ function requireConnectionString() {
72
105
  }
73
106
  return connectionString;
74
107
  }
75
- function defaultAgentId() {
76
- return process.env.IRANTI_MCP_DEFAULT_AGENT?.trim()
77
- || process.env.IRANTI_AGENT_ID?.trim()
108
+ function currentHost(host) {
109
+ return host?.trim()
110
+ || runtimeHostOverride?.trim()
111
+ || process.env.IRANTI_MCP_HOST?.trim();
112
+ }
113
+ function defaultAgentId(host) {
114
+ const resolvedHost = currentHost(host);
115
+ if (resolvedHost === 'claude_code') {
116
+ return process.env.IRANTI_CLAUDE_AGENT_ID?.trim()
117
+ || process.env.IRANTI_AGENT_ID?.trim()
118
+ || 'claude_code';
119
+ }
120
+ const explicit = process.env.IRANTI_MCP_DEFAULT_AGENT?.trim();
121
+ if (explicit)
122
+ return explicit;
123
+ if (resolvedHost === 'codex' || resolvedHost === 'codex_cli' || resolvedHost === 'codex_vscode') {
124
+ return 'codex_code';
125
+ }
126
+ return process.env.IRANTI_AGENT_ID?.trim()
78
127
  || 'claude_code';
79
128
  }
80
- function defaultWriteSource() {
81
- return process.env.IRANTI_MCP_DEFAULT_SOURCE?.trim() || 'ClaudeCode';
129
+ function defaultWriteSource(host) {
130
+ const resolvedHost = currentHost(host);
131
+ if (resolvedHost === 'claude_code') {
132
+ return 'ClaudeCode';
133
+ }
134
+ const explicit = process.env.IRANTI_MCP_DEFAULT_SOURCE?.trim();
135
+ if (explicit)
136
+ return explicit;
137
+ if (resolvedHost === 'codex' || resolvedHost === 'codex_cli' || resolvedHost === 'codex_vscode') {
138
+ return 'Codex';
139
+ }
140
+ return 'MCP';
82
141
  }
83
142
  function safeJsonParse(raw) {
84
143
  try {
@@ -115,18 +174,57 @@ function parseIsoDate(raw) {
115
174
  }
116
175
  return parsed;
117
176
  }
118
- async function ensureDefaultAgent(iranti) {
119
- const agentId = defaultAgentId();
177
+ async function ensureDefaultAgent(iranti, host) {
178
+ const agentId = defaultAgentId(host);
179
+ const resolvedHost = currentHost(host);
180
+ const isCodexHost = resolvedHost === 'codex' || resolvedHost === 'codex_cli' || resolvedHost === 'codex_vscode';
120
181
  await iranti.registerAgent({
121
182
  agentId,
122
- name: process.env.IRANTI_MCP_AGENT_NAME?.trim() || 'Claude Code',
123
- description: process.env.IRANTI_MCP_AGENT_DESCRIPTION?.trim() || 'Claude Code MCP client',
183
+ name: process.env.IRANTI_MCP_AGENT_NAME?.trim() || (isCodexHost ? 'Codex' : 'Claude Code'),
184
+ description: process.env.IRANTI_MCP_AGENT_DESCRIPTION?.trim() || (isCodexHost ? 'Codex MCP client' : 'Claude Code MCP client'),
124
185
  capabilities: ['memory_read', 'memory_write', 'hybrid_search', 'working_memory'],
125
- model: process.env.IRANTI_MCP_AGENT_MODEL?.trim() || process.env.ANTHROPIC_MODEL || 'claude-code',
186
+ model: process.env.IRANTI_MCP_AGENT_MODEL?.trim()
187
+ || process.env.ANTHROPIC_MODEL
188
+ || (isCodexHost ? 'codex' : 'claude-code'),
189
+ });
190
+ }
191
+ function syncRuntimeLedgerContext(iranti, host, agent) {
192
+ iranti.setSessionLedgerContext({
193
+ source: 'mcp',
194
+ host: currentHost(host) ?? null,
195
+ agentId: agent?.trim() || defaultAgentId(host),
126
196
  });
127
197
  }
128
- function withDefaultAgent(agent) {
129
- return agent?.trim() || defaultAgentId();
198
+ function withDefaultAgent(agent, host) {
199
+ return agent?.trim() || defaultAgentId(host);
200
+ }
201
+ function resolveToolAgent(agent, agentId, host) {
202
+ return withDefaultAgent(agentId ?? agent, host);
203
+ }
204
+ function resolveToolHost(host) {
205
+ const trimmed = host?.trim();
206
+ if (trimmed) {
207
+ runtimeHostOverride = trimmed;
208
+ return trimmed;
209
+ }
210
+ return currentHost();
211
+ }
212
+ function protocolViolationResult(error) {
213
+ return {
214
+ content: [
215
+ {
216
+ type: 'text',
217
+ text: `${error.protocolViolation.message}\n\n${JSON.stringify({
218
+ blocked: true,
219
+ protocolViolation: error.protocolViolation,
220
+ }, null, 2)}`,
221
+ },
222
+ ],
223
+ structuredContent: {
224
+ blocked: true,
225
+ protocolViolation: error.protocolViolation,
226
+ },
227
+ };
130
228
  }
131
229
  function normalizeRecentMessages(messages) {
132
230
  if (!Array.isArray(messages))
@@ -139,6 +237,95 @@ function normalizeRecentMessages(messages) {
139
237
  function isInteractiveTerminalLaunch() {
140
238
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
141
239
  }
240
+ function clearProcessAttendants() {
241
+ for (const agentId of (0, attendant_1.activeAttendants)()) {
242
+ (0, attendant_1.clearAttendant)(agentId);
243
+ }
244
+ }
245
+ async function checkpointActiveAttendants(iranti, reason) {
246
+ const agents = (0, attendant_1.activeAttendants)();
247
+ for (const agentId of agents) {
248
+ const brief = (0, attendant_1.getAttendant)(agentId).getBrief();
249
+ const existing = brief?.sessionCheckpoint;
250
+ if (!existing?.task)
251
+ continue;
252
+ const existingCheckpoint = existing.checkpoint ?? {};
253
+ await iranti.checkpoint({
254
+ agentId,
255
+ task: existing.task,
256
+ recentMessages: [],
257
+ checkpoint: {
258
+ currentStep: existingCheckpoint.currentStep ?? 'in progress',
259
+ nextStep: existingCheckpoint.nextStep,
260
+ openRisks: existingCheckpoint.openRisks,
261
+ recentOutputs: existingCheckpoint.recentOutputs,
262
+ actions: existingCheckpoint.actions,
263
+ fileChanges: existingCheckpoint.fileChanges,
264
+ entityTargets: existingCheckpoint.entityTargets,
265
+ notes: `Host exited gracefully (${reason}). Resume from next step if applicable.`,
266
+ },
267
+ sessionId: existing.sessionId,
268
+ }).catch(() => undefined);
269
+ }
270
+ }
271
+ function resolveAttendLatestMessage(input) {
272
+ const latestMessage = input.latestMessage?.trim();
273
+ if (latestMessage)
274
+ return latestMessage;
275
+ const message = input.message?.trim();
276
+ if (message)
277
+ return message;
278
+ throw new Error('iranti_attend requires latestMessage or message.');
279
+ }
280
+ async function shutdownProcess(code, reason) {
281
+ if (shutdownPromise) {
282
+ return shutdownPromise;
283
+ }
284
+ shutdownPromise = (async () => {
285
+ try {
286
+ if (processIranti) {
287
+ await checkpointActiveAttendants(processIranti, reason).catch(() => undefined);
288
+ }
289
+ clearProcessAttendants();
290
+ await (0, staffEventRegistry_1.flushStaffEventEmitter)().catch(() => undefined);
291
+ await (0, client_1.disconnectDb)().catch(() => undefined);
292
+ }
293
+ finally {
294
+ if (isInteractiveTerminalLaunch()) {
295
+ console.error(`[iranti-mcp] shutting down (${reason}).`);
296
+ }
297
+ process.exitCode = code;
298
+ }
299
+ })();
300
+ return shutdownPromise;
301
+ }
302
+ function installShutdownHooks(transport) {
303
+ let shutdownRequested = false;
304
+ const requestShutdown = (code, reason) => {
305
+ if (shutdownRequested)
306
+ return;
307
+ shutdownRequested = true;
308
+ void shutdownProcess(code, reason);
309
+ };
310
+ transport.onclose = () => {
311
+ requestShutdown(0, 'transport_closed');
312
+ };
313
+ // When stdin is already ending or closing, asking the SDK transport to close
314
+ // can re-touch the same handle. On Windows that has been reproducing a
315
+ // UV_HANDLE_CLOSING assertion during shutdown, so we exit directly here.
316
+ process.stdin.once('end', () => requestShutdown(0, 'stdin_end'));
317
+ process.stdin.once('close', () => requestShutdown(0, 'stdin_close'));
318
+ process.stdin.once('error', () => requestShutdown(1, 'stdin_error'));
319
+ process.stdout.once('error', (error) => {
320
+ requestShutdown(error?.code === 'EPIPE' ? 0 : 1, error?.code === 'EPIPE' ? 'stdout_epipe' : 'stdout_error');
321
+ });
322
+ process.once('SIGINT', () => {
323
+ requestShutdown(130, 'sigint');
324
+ });
325
+ process.once('SIGTERM', () => {
326
+ requestShutdown(143, 'sigterm');
327
+ });
328
+ }
142
329
  async function main() {
143
330
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
144
331
  printHelp();
@@ -147,11 +334,16 @@ async function main() {
147
334
  const iranti = (0, createFirstPartyIranti_1.createFirstPartyIranti)({
148
335
  connectionString: requireConnectionString(),
149
336
  llmProvider: process.env.LLM_PROVIDER,
337
+ sessionLedgerSource: 'mcp',
338
+ sessionLedgerHost: currentHost() || 'generic_mcp',
339
+ sessionLedgerAgentId: defaultAgentId(),
340
+ dbPoolMax: 3,
150
341
  });
342
+ processIranti = iranti;
151
343
  await ensureDefaultAgent(iranti);
152
344
  const server = new mcp_js_1.McpServer({
153
345
  name: 'iranti-mcp',
154
- version: '0.2.51',
346
+ version: '0.3.2',
155
347
  });
156
348
  server.registerTool('iranti_handshake', {
157
349
  description: `Initialize or refresh an agent's working-memory brief for the current task.
@@ -166,13 +358,23 @@ Do not use this as a per-turn retrieval tool; use iranti_attend.`,
166
358
  task: z.string().min(1).describe('The current task or objective.'),
167
359
  recentMessages: z.array(z.string()).optional().describe('Recent conversation messages.'),
168
360
  agent: z.string().optional().describe('Override the default agent id.'),
361
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id.'),
362
+ host: z.string().optional().describe('Host identifier (e.g. claude_code, codex). Used to verify host setup has been run for this project.'),
169
363
  },
170
- }, async ({ task, recentMessages, agent }) => {
364
+ }, async ({ task, recentMessages, agent, agentId, host }) => {
365
+ const resolvedHost = resolveToolHost(host);
366
+ const resolvedAgent = resolveToolAgent(agent, agentId, resolvedHost);
367
+ syncRuntimeLedgerContext(iranti, resolvedHost, resolvedAgent);
368
+ await ensureDefaultAgent(iranti, resolvedHost);
171
369
  const result = await iranti.handshake({
172
- agent: withDefaultAgent(agent),
370
+ agent: resolvedAgent,
173
371
  task,
174
372
  recentMessages: normalizeRecentMessages(recentMessages),
175
373
  });
374
+ const setupWarnings = checkHostSetup(resolvedHost, process.cwd());
375
+ if (setupWarnings.length > 0) {
376
+ return textResult({ ...result, setupWarnings });
377
+ }
176
378
  return textResult(result);
177
379
  });
178
380
  server.registerTool('iranti_attend', {
@@ -186,33 +388,140 @@ be added to context if relevant memory is missing. If no handshake has been
186
388
  performed yet for this agent in the current process, attend will auto-bootstrap
187
389
  the session first and report that in the result metadata.
188
390
  This is the minimum safe pre-reply call even when the host skipped handshake.
189
- Omitting currentContext falls back to latestMessage only; pass the
190
- full visible context when available.`,
391
+ Omitting currentContext falls back to the latest message only; pass the
392
+ full visible context when available. For host compatibility, message is
393
+ accepted as an alias for latestMessage. When phase='post-response', pass the
394
+ assistant response so Iranti can persist strict continuity facts and shared
395
+ checkpoint breadcrumbs before closing the turn.`,
191
396
  inputSchema: {
192
- latestMessage: z.string().min(1).describe('The latest user or assistant message.'),
397
+ latestMessage: z.string().min(1).optional().describe('The latest user or assistant message.'),
398
+ message: z.string().min(1).optional().describe('Alias for latestMessage, accepted for host compatibility.'),
193
399
  currentContext: z.string().optional().describe('Current visible context window.'),
194
400
  entityHints: z.array(z.string()).optional().describe('Optional entity hints in entityType/entityId format.'),
195
401
  maxFacts: z.number().int().min(1).max(20).optional().describe('Maximum facts to inject.'),
196
402
  forceInject: z.boolean().optional().describe('Force a memory injection decision.'),
403
+ phase: z.enum(['pre-response', 'post-response', 'mid-turn']).optional().describe("Call phase: 'pre-response' before replying, 'post-response' after replying. Enables precise compliance tracking."),
197
404
  agent: z.string().optional().describe('Override the default agent id.'),
405
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id.'),
198
406
  },
199
- }, async ({ latestMessage, currentContext, entityHints, maxFacts, forceInject, agent }) => {
200
- const resolvedAgent = withDefaultAgent(agent);
201
- if ((0, autoRemember_1.isAutoRememberEnabled)()) {
202
- await (0, autoRemember_1.autoRememberPromptFacts)({
203
- iranti,
204
- prompt: latestMessage,
407
+ }, async ({ latestMessage, message, currentContext, entityHints, maxFacts, forceInject, phase, agent, agentId }) => {
408
+ const resolvedAgent = resolveToolAgent(agent, agentId);
409
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
410
+ const resolvedLatestMessage = resolveAttendLatestMessage({ latestMessage, message });
411
+ try {
412
+ if (phase === 'post-response') {
413
+ await (0, autoRemember_1.rememberAssistantResponseFacts)({
414
+ iranti,
415
+ response: resolvedLatestMessage,
416
+ agent: resolvedAgent,
417
+ source: defaultWriteSource(),
418
+ ledgerContext: {
419
+ source: 'mcp',
420
+ host: currentHost() || 'generic_mcp',
421
+ },
422
+ });
423
+ const checkpoint = (0, assistantCheckpoint_1.extractAssistantCheckpointPayload)(resolvedLatestMessage);
424
+ const projectEntity = process.env.IRANTI_MEMORY_ENTITY?.trim();
425
+ if (checkpoint && projectEntity) {
426
+ await iranti.checkpoint({
427
+ agent: resolvedAgent,
428
+ task: `Post-response checkpoint for ${resolvedAgent}`,
429
+ recentMessages: [],
430
+ checkpoint: {
431
+ ...checkpoint,
432
+ entityTargets: [projectEntity],
433
+ },
434
+ });
435
+ }
436
+ }
437
+ else if ((0, autoRemember_1.isAutoRememberEnabled)()) {
438
+ await (0, autoRemember_1.autoRememberPromptFacts)({
439
+ iranti,
440
+ prompt: resolvedLatestMessage,
441
+ agent: resolvedAgent,
442
+ source: defaultWriteSource(),
443
+ });
444
+ }
445
+ const result = await iranti.attend({
205
446
  agent: resolvedAgent,
206
- source: defaultWriteSource(),
447
+ latestMessage: resolvedLatestMessage,
448
+ currentContext: currentContext ?? resolvedLatestMessage,
449
+ entityHints,
450
+ maxFacts,
451
+ forceInject,
452
+ phase,
207
453
  });
454
+ const injectionBlock = result.shouldInject
455
+ ? (0, hostMemoryFormatting_1.formatStructuredFactBlock)(result.facts, {
456
+ title: 'Iranti Retrieved Memory',
457
+ includeValues: true,
458
+ })
459
+ : '';
460
+ return textResult({
461
+ ...result,
462
+ injectionBlock,
463
+ });
464
+ }
465
+ catch (error) {
466
+ if (error instanceof sdk_1.ProtocolViolationError) {
467
+ return protocolViolationResult(error);
468
+ }
469
+ throw error;
208
470
  }
209
- const result = await iranti.attend({
471
+ });
472
+ server.registerTool('iranti_checkpoint', {
473
+ description: `Persist a shared progress checkpoint while you work.
474
+ Use this at meaningful milestones so current step, next step, open risks,
475
+ recent outputs, structured actions, and shared entity breadcrumbs survive across turns,
476
+ sessions, and agents. This is the strongest shared-RAM tool for active work:
477
+ prefer it over ad-hoc prose when you need another session or another agent
478
+ to pick up where you left off. If entityTargets are supplied, Iranti also
479
+ writes canonical shared breadcrumbs such as current_step, next_step,
480
+ open_risks, recent_actions, and recent_file_changes to those entities for handoff.`,
481
+ inputSchema: {
482
+ task: z.string().min(1).describe('Current task or objective for the active checkpoint.'),
483
+ recentMessages: z.array(z.string()).optional().describe('Recent messages that help fingerprint the active task.'),
484
+ currentStep: z.string().optional().describe('What is being worked on right now.'),
485
+ nextStep: z.string().optional().describe('The next step another session or agent should take.'),
486
+ openRisks: z.array(z.string()).optional().describe('Open risks or blockers that still matter.'),
487
+ recentOutputs: z.array(z.string()).optional().describe('Important outputs or artifacts produced so far.'),
488
+ actions: z.array(z.object({
489
+ kind: z.string().min(1).describe('Action kind such as command, test, edit, search, validation, or decision.'),
490
+ summary: z.string().min(1).describe('Compact description of what action happened.'),
491
+ status: z.string().optional().describe('Optional outcome such as passed, failed, succeeded, or skipped.'),
492
+ target: z.string().optional().describe('Optional command, file, test, or subsystem the action touched.'),
493
+ detail: z.string().optional().describe('Optional compact supporting detail for later recovery.'),
494
+ })).optional().describe('Structured actions completed so far, such as commands, tests, searches, or validations.'),
495
+ fileChanges: z.array(z.object({
496
+ action: z.string().min(1).describe('Change action such as created, updated, moved, renamed, or deleted.'),
497
+ path: z.string().min(1).describe('Primary file path affected by the action.'),
498
+ toPath: z.string().optional().describe('Destination path for move/rename actions.'),
499
+ purpose: z.string().optional().describe('Why the file changed or what role it now serves.'),
500
+ })).optional().describe('Structured file actions produced so far.'),
501
+ entityTargets: z.array(z.string()).optional().describe('Shared entities that should receive checkpoint breadcrumbs, in entityType/entityId format.'),
502
+ notes: z.string().optional().describe('Compact extra checkpoint notes that aid handoff.'),
503
+ sessionId: z.string().optional().describe('Optional existing session id to refresh.'),
504
+ agent: z.string().optional().describe('Override the default agent id.'),
505
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id.'),
506
+ },
507
+ }, async ({ task, recentMessages, currentStep, nextStep, openRisks, recentOutputs, actions, fileChanges, entityTargets, notes, sessionId, agent, agentId }) => {
508
+ const resolvedAgent = resolveToolAgent(agent, agentId);
509
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
510
+ const result = await iranti.checkpoint({
210
511
  agent: resolvedAgent,
211
- latestMessage,
212
- currentContext: currentContext ?? latestMessage,
213
- entityHints,
214
- maxFacts,
215
- forceInject,
512
+ task,
513
+ recentMessages: normalizeRecentMessages(recentMessages),
514
+ sessionId,
515
+ checkpoint: {
516
+ currentStep,
517
+ nextStep,
518
+ openRisks,
519
+ recentOutputs,
520
+ actions,
521
+ fileChanges,
522
+ entityTargets,
523
+ notes,
524
+ },
216
525
  });
217
526
  return textResult(result);
218
527
  });
@@ -223,18 +532,31 @@ full visible context when available.`,
223
532
  entityHints: z.array(z.string()).optional().describe('Optional entity hints in entityType/entityId format.'),
224
533
  maxFacts: z.number().int().min(1).max(20).optional().describe('Maximum facts to recover.'),
225
534
  agent: z.string().optional().describe('Override the default agent id.'),
535
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id.'),
226
536
  },
227
- }, async ({ currentContext, entityHints, maxFacts, agent }) => {
228
- const result = await iranti.observe({
229
- agent: withDefaultAgent(agent),
230
- currentContext,
231
- entityHints,
232
- maxFacts,
233
- });
234
- return textResult(result);
537
+ }, async ({ currentContext, entityHints, maxFacts, agent, agentId }) => {
538
+ const resolvedAgent = resolveToolAgent(agent, agentId);
539
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
540
+ try {
541
+ const result = await iranti.observe({
542
+ agent: resolvedAgent,
543
+ currentContext,
544
+ entityHints,
545
+ maxFacts,
546
+ });
547
+ return textResult(result);
548
+ }
549
+ catch (error) {
550
+ if (error instanceof sdk_1.ProtocolViolationError) {
551
+ return protocolViolationResult(error);
552
+ }
553
+ throw error;
554
+ }
235
555
  });
236
556
  server.registerTool('iranti_query', {
237
557
  description: `Retrieve the current fact for an exact entity+key lookup.
558
+ REQUIRED: call iranti_attend before this discovery tool so Iranti can decide
559
+ whether memory should be injected before exact lookup.
238
560
  Use this when you already know both the entity and the key.
239
561
  Returns the current value, summary, confidence, source, and
240
562
  temporal metadata when available. Prefer this over iranti_search
@@ -243,15 +565,62 @@ memory alone before checking Iranti.`,
243
565
  inputSchema: {
244
566
  entity: z.string().min(1).describe('Entity in entityType/entityId format.'),
245
567
  key: z.string().min(1).describe('Fact key to retrieve.'),
568
+ agent: z.string().optional().describe('Override the default agent id for protocol tracking.'),
569
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id for protocol tracking.'),
246
570
  },
247
- }, async ({ entity, key }) => {
248
- const result = await iranti.query(entity, key);
249
- return textResult(result);
571
+ }, async ({ entity, key, agent, agentId }) => {
572
+ const resolvedAgent = resolveToolAgent(agent, agentId);
573
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
574
+ try {
575
+ const result = await iranti.query(entity, key);
576
+ return textResult(result);
577
+ }
578
+ catch (error) {
579
+ if (error instanceof sdk_1.ProtocolViolationError) {
580
+ return protocolViolationResult(error);
581
+ }
582
+ throw error;
583
+ }
584
+ });
585
+ server.registerTool('iranti_history', {
586
+ description: `Retrieve the full version history of a fact for an exact entity+key pair.
587
+ Returns all archived past values plus the current value, ordered oldest-first.
588
+ Each entry includes value, summary, confidence, source, validFrom, validUntil,
589
+ isCurrent, archivedReason, and resolutionState.
590
+ REQUIRED: call iranti_attend before this discovery tool so Iranti can decide
591
+ whether memory should be injected first.
592
+ Use this to understand how a fact evolved over time — decisions that changed,
593
+ blockers that were resolved, values that were contested or superseded.`,
594
+ inputSchema: {
595
+ entity: z.string().min(1).describe('Entity in entityType/entityId format.'),
596
+ key: z.string().min(1).describe('Fact key to retrieve history for.'),
597
+ limit: z.number().int().min(1).optional().describe('Maximum number of entries to return (applied after sorting oldest-first).'),
598
+ includeExpired: z.boolean().optional().describe('Include entries that expired without being superseded.'),
599
+ includeContested: z.boolean().optional().describe('Include entries that were contested or escalated.'),
600
+ agent: z.string().optional().describe('Override the default agent id for protocol tracking.'),
601
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id for protocol tracking.'),
602
+ },
603
+ }, async ({ entity, key, limit, includeExpired, includeContested, agent, agentId }) => {
604
+ const resolvedAgent = resolveToolAgent(agent, agentId);
605
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
606
+ try {
607
+ const history = await iranti.history(entity, key, { includeExpired, includeContested });
608
+ const result = limit != null ? history.slice(0, limit) : history;
609
+ return textResult(result);
610
+ }
611
+ catch (error) {
612
+ if (error instanceof sdk_1.ProtocolViolationError) {
613
+ return protocolViolationResult(error);
614
+ }
615
+ throw error;
616
+ }
250
617
  });
251
618
  server.registerTool('iranti_search', {
252
619
  description: `Search shared memory with natural language when the exact entity
253
620
  or key is unknown. Uses hybrid lexical and vector search across
254
621
  stored facts. Use this for discovery and recall, not exact lookup.
622
+ REQUIRED: call iranti_attend before this discovery tool so Iranti can decide
623
+ whether memory should be injected before search.
255
624
  If the user asks what they previously told you and you do not know
256
625
  the exact key, use this before saying you do not know.`,
257
626
  inputSchema: {
@@ -262,18 +631,30 @@ the exact key, use this before saying you do not know.`,
262
631
  lexicalWeight: z.number().min(0).max(1).optional().describe('Lexical ranking weight.'),
263
632
  vectorWeight: z.number().min(0).max(1).optional().describe('Vector similarity weight.'),
264
633
  minScore: z.number().min(0).max(1).optional().describe('Minimum final score threshold.'),
634
+ agent: z.string().optional().describe('Override the default agent id for protocol tracking.'),
635
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id for protocol tracking.'),
265
636
  },
266
- }, async ({ query, entityType, entityId, limit, lexicalWeight, vectorWeight, minScore }) => {
267
- const result = await iranti.search({
268
- query,
269
- entityType,
270
- entityId,
271
- limit,
272
- lexicalWeight,
273
- vectorWeight,
274
- minScore,
275
- });
276
- return textResult(result);
637
+ }, async ({ query, entityType, entityId, limit, lexicalWeight, vectorWeight, minScore, agent, agentId }) => {
638
+ const resolvedAgent = resolveToolAgent(agent, agentId);
639
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
640
+ try {
641
+ const result = await iranti.search({
642
+ query,
643
+ entityType,
644
+ entityId,
645
+ limit,
646
+ lexicalWeight,
647
+ vectorWeight,
648
+ minScore,
649
+ });
650
+ return textResult(result);
651
+ }
652
+ catch (error) {
653
+ if (error instanceof sdk_1.ProtocolViolationError) {
654
+ return protocolViolationResult(error);
655
+ }
656
+ throw error;
657
+ }
277
658
  });
278
659
  server.registerTool('iranti_write', {
279
660
  description: `Write one durable fact to shared memory for a specific entity.
@@ -282,7 +663,9 @@ agents, or sessions should retain. Requires: entity ("type/id"),
282
663
  key, value JSON, and summary. Confidence is optional and defaults
283
664
  to 85. Conflicts on the same entity+key are detected automatically
284
665
  and may be resolved or escalated. Personal-memory keys honor the
285
- configured canonical personal entity for this project/session.`,
666
+ configured canonical personal entity for this project/session.
667
+ Use properties JSON when you need structured issue or workflow metadata
668
+ such as issueStatus=open|resolved, severity, or resolution notes.`,
286
669
  inputSchema: {
287
670
  entity: z.string().min(1).describe('Entity in entityType/entityId format.'),
288
671
  key: z.string().min(1).describe('Fact key.'),
@@ -290,12 +673,16 @@ configured canonical personal entity for this project/session.`,
290
673
  summary: z.string().min(1).describe('Short retrieval-safe summary.'),
291
674
  confidence: z.number().int().min(0).max(100).optional().describe('Raw confidence score.'),
292
675
  source: z.string().optional().describe('Source label for provenance.'),
676
+ propertiesJson: z.string().optional().describe('Optional JSON-serialized fact properties for metadata such as issueStatus or severity.'),
293
677
  validFrom: z.string().optional().describe('Optional ISO timestamp for when the fact became true/current.'),
294
678
  requestId: z.string().optional().describe('Optional idempotency key.'),
295
679
  agent: z.string().optional().describe('Override the default agent id.'),
680
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id.'),
296
681
  },
297
- }, async ({ entity, key, valueJson, summary, confidence, source, validFrom, requestId, agent }) => {
682
+ }, async ({ entity, key, valueJson, summary, confidence, source, propertiesJson, validFrom, requestId, agent, agentId }) => {
298
683
  const target = (0, autoRemember_1.resolvePersonalWriteTarget)({ entity, key });
684
+ const resolvedAgent = resolveToolAgent(agent, agentId);
685
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
299
686
  const result = await iranti.write({
300
687
  entity: target.entity,
301
688
  key,
@@ -303,7 +690,8 @@ configured canonical personal entity for this project/session.`,
303
690
  summary,
304
691
  confidence: confidence ?? 85,
305
692
  source: source?.trim() || defaultWriteSource(),
306
- agent: withDefaultAgent(agent),
693
+ agent: resolvedAgent,
694
+ properties: propertiesJson ? safeJsonParse(propertiesJson) : undefined,
307
695
  validFrom: parseIsoDate(validFrom),
308
696
  requestId,
309
697
  });
@@ -313,6 +701,54 @@ configured canonical personal entity for this project/session.`,
313
701
  ...(target.rerouted ? { originalEntity: target.originalEntity, canonicalizedPersonalEntity: true } : {}),
314
702
  });
315
703
  });
704
+ server.registerTool('iranti_write_issue', {
705
+ description: `Write a canonical open or resolved issue fact on a stable key.
706
+ Use this when you want defects, bugs, or chores to remain first-class shared
707
+ memory instead of loose prose. The same issueId always maps to the same
708
+ issue_<id> key, so changing status from open to resolved archives the prior
709
+ state automatically while preserving history. Prefer this over hand-rolling
710
+ issueStatus properties through iranti_write when the fact is specifically a
711
+ trackable issue lifecycle entry.`,
712
+ inputSchema: {
713
+ entity: z.string().min(1).describe('Owner entity in entityType/entityId format, usually a project entity.'),
714
+ issueId: z.string().min(1).describe('Stable issue identifier that becomes issue_<normalized_id>.'),
715
+ title: z.string().min(1).describe('Short human-readable issue title.'),
716
+ status: z.enum(['open', 'resolved']).describe('Issue lifecycle status.'),
717
+ summary: z.string().min(1).describe('Short retrieval-safe summary of the issue state.'),
718
+ confidence: z.number().int().min(0).max(100).optional().describe('Raw confidence score.'),
719
+ source: z.string().optional().describe('Source label for provenance.'),
720
+ severity: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Optional issue severity.'),
721
+ detailsJson: z.string().optional().describe('Optional JSON-serialized structured issue details.'),
722
+ discoveredAt: z.string().optional().describe('Optional ISO timestamp for when the issue was first observed.'),
723
+ resolvedAt: z.string().optional().describe('Optional ISO timestamp for when the issue was resolved.'),
724
+ resolution: z.string().optional().describe('Optional resolution note for resolved issues.'),
725
+ tags: z.array(z.string()).optional().describe('Optional issue tags.'),
726
+ requestId: z.string().optional().describe('Optional idempotency key.'),
727
+ agent: z.string().optional().describe('Override the default agent id.'),
728
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id.'),
729
+ },
730
+ }, async ({ entity, issueId, title, status, summary, confidence, source, severity, detailsJson, discoveredAt, resolvedAt, resolution, tags, requestId, agent, agentId }) => {
731
+ const resolvedAgent = resolveToolAgent(agent, agentId);
732
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
733
+ const result = await iranti.writeIssue({
734
+ entity,
735
+ issueId,
736
+ title,
737
+ status,
738
+ summary,
739
+ confidence: confidence ?? 85,
740
+ source: source?.trim() || defaultWriteSource(),
741
+ agent: resolvedAgent,
742
+ severity,
743
+ details: detailsJson ? safeJsonParse(detailsJson) : undefined,
744
+ discoveredAt,
745
+ resolvedAt,
746
+ resolution,
747
+ tags,
748
+ requestId,
749
+ });
750
+ return textResult(result);
751
+ });
316
752
  server.registerTool('iranti_remember_response', {
317
753
  description: `Persist a strict durable summary from your own response.
318
754
  Use this after you decide to say something like "the next step is ...",
@@ -327,16 +763,23 @@ this for arbitrary prose or every turn.`,
327
763
  source: z.string().optional().describe('Optional provenance label override.'),
328
764
  confidence: z.number().int().min(0).max(100).optional().describe('Raw confidence score for remembered summaries.'),
329
765
  agent: z.string().optional().describe('Override the default agent id.'),
766
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id.'),
330
767
  },
331
- }, async ({ response, projectEntity, personalEntity, source, confidence, agent }) => {
768
+ }, async ({ response, projectEntity, personalEntity, source, confidence, agent, agentId }) => {
769
+ const resolvedAgent = resolveToolAgent(agent, agentId);
770
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
332
771
  const result = await (0, autoRemember_1.rememberAssistantResponseFacts)({
333
772
  iranti,
334
773
  response,
335
- agent: withDefaultAgent(agent),
774
+ agent: resolvedAgent,
336
775
  source: source?.trim() || defaultWriteSource(),
337
776
  projectEntity,
338
777
  personalEntity,
339
778
  confidence: confidence ?? 90,
779
+ ledgerContext: {
780
+ source: 'mcp',
781
+ host: currentHost() || 'generic_mcp',
782
+ },
340
783
  });
341
784
  return textResult(result);
342
785
  });
@@ -348,14 +791,17 @@ this for arbitrary prose or every turn.`,
348
791
  confidence: z.number().int().min(0).max(100).optional().describe('Raw confidence score.'),
349
792
  source: z.string().optional().describe('Source label for provenance.'),
350
793
  agent: z.string().optional().describe('Override the default agent id.'),
794
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id.'),
351
795
  },
352
- }, async ({ entity, content, confidence, source, agent }) => {
796
+ }, async ({ entity, content, confidence, source, agent, agentId }) => {
797
+ const resolvedAgent = resolveToolAgent(agent, agentId);
798
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
353
799
  const result = await iranti.ingest({
354
800
  entity,
355
801
  content,
356
802
  confidence: confidence ?? 80,
357
803
  source: source?.trim() || defaultWriteSource(),
358
- agent: withDefaultAgent(agent),
804
+ agent: resolvedAgent,
359
805
  });
360
806
  return textResult(result);
361
807
  });
@@ -370,49 +816,106 @@ this for arbitrary prose or every turn.`,
370
816
  },
371
817
  }, async ({ fromEntity, relationshipType, toEntity, propertiesJson, createdBy }) => {
372
818
  const properties = propertiesJson ? safeJsonParse(propertiesJson) : undefined;
819
+ const resolvedAgent = withDefaultAgent(createdBy);
820
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
373
821
  const result = await iranti.relate(fromEntity, relationshipType, toEntity, {
374
- createdBy: withDefaultAgent(createdBy),
822
+ createdBy: resolvedAgent,
375
823
  properties: (properties ?? {}),
376
824
  });
377
825
  return textResult({ ok: true, result });
378
826
  });
379
827
  server.registerTool('iranti_related', {
380
- description: 'Read directly related entities (1 hop) for a given entity.',
828
+ description: `Read directly related entities (1 hop) for a given entity.
829
+ REQUIRED: call iranti_attend before this discovery tool so Iranti can decide
830
+ whether memory should be injected before graph traversal.`,
381
831
  inputSchema: {
382
832
  entity: z.string().min(1).describe('Entity in entityType/entityId format.'),
833
+ agent: z.string().optional().describe('Override the default agent id for protocol tracking.'),
834
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id for protocol tracking.'),
383
835
  },
384
- }, async ({ entity }) => {
385
- const result = await iranti.getRelated(entity);
386
- return textResult(result);
836
+ }, async ({ entity, agent, agentId }) => {
837
+ const resolvedAgent = resolveToolAgent(agent, agentId);
838
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
839
+ try {
840
+ const result = await iranti.getRelated(entity);
841
+ return textResult(result);
842
+ }
843
+ catch (error) {
844
+ if (error instanceof sdk_1.ProtocolViolationError) {
845
+ return protocolViolationResult(error);
846
+ }
847
+ throw error;
848
+ }
387
849
  });
388
850
  server.registerTool('iranti_related_deep', {
389
- description: 'Read related entities up to N hops deep for a given entity.',
851
+ description: `Read related entities up to N hops deep for a given entity.
852
+ REQUIRED: call iranti_attend before this discovery tool so Iranti can decide
853
+ whether memory should be injected before graph traversal.`,
390
854
  inputSchema: {
391
855
  entity: z.string().min(1).describe('Entity in entityType/entityId format.'),
392
856
  depth: z.number().int().min(1).max(5).optional().describe('Traversal depth.'),
857
+ agent: z.string().optional().describe('Override the default agent id for protocol tracking.'),
858
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id for protocol tracking.'),
393
859
  },
394
- }, async ({ entity, depth }) => {
395
- const result = await iranti.getRelatedDeep(entity, depth ?? 2);
396
- return textResult(result);
860
+ }, async ({ entity, depth, agent, agentId }) => {
861
+ const resolvedAgent = resolveToolAgent(agent, agentId);
862
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
863
+ try {
864
+ const result = await iranti.getRelatedDeep(entity, depth ?? 2);
865
+ return textResult(result);
866
+ }
867
+ catch (error) {
868
+ if (error instanceof sdk_1.ProtocolViolationError) {
869
+ return protocolViolationResult(error);
870
+ }
871
+ throw error;
872
+ }
397
873
  });
398
874
  server.registerTool('iranti_who_knows', {
399
- description: 'List which agents have written facts about an entity.',
875
+ description: `List which agents have written facts about an entity.
876
+ REQUIRED: call iranti_attend before this discovery tool so Iranti can decide
877
+ whether memory should be injected before provenance discovery.`,
400
878
  inputSchema: {
401
879
  entity: z.string().min(1).describe('Entity in entityType/entityId format.'),
880
+ agent: z.string().optional().describe('Override the default agent id for protocol tracking.'),
881
+ agentId: z.string().optional().describe('Alias for agent. Override the default agent id for protocol tracking.'),
402
882
  },
403
- }, async ({ entity }) => {
404
- const result = await iranti.whoKnows(entity);
405
- return textResult(result);
883
+ }, async ({ entity, agent, agentId }) => {
884
+ const resolvedAgent = resolveToolAgent(agent, agentId);
885
+ syncRuntimeLedgerContext(iranti, undefined, resolvedAgent);
886
+ try {
887
+ const result = await iranti.whoKnows(entity);
888
+ return textResult(result);
889
+ }
890
+ catch (error) {
891
+ if (error instanceof sdk_1.ProtocolViolationError) {
892
+ return protocolViolationResult(error);
893
+ }
894
+ throw error;
895
+ }
406
896
  });
407
897
  if (isInteractiveTerminalLaunch()) {
408
898
  console.error('[iranti-mcp] stdio server running; waiting for an MCP client. Press Ctrl+C to exit.');
409
899
  }
410
900
  const transport = new stdio_js_1.StdioServerTransport();
901
+ installShutdownHooks(transport);
411
902
  await server.connect(transport);
412
903
  }
413
904
  main().catch((error) => {
414
905
  const formatted = (0, commandErrors_1.rewriteCommandError)('iranti mcp', error);
906
+ (0, staffEventRegistry_1.getStaffEventEmitter)().emit({
907
+ staffComponent: 'Attendant',
908
+ actionType: 'host_failure',
909
+ agentId: defaultAgentId(),
910
+ source: 'mcp',
911
+ level: 'audit',
912
+ reason: formatted.message,
913
+ metadata: {
914
+ host: currentHost() || 'generic_mcp',
915
+ operation: 'mcp_startup',
916
+ },
917
+ });
415
918
  console.error('[iranti-mcp] fatal:', formatted.message);
416
- process.exit(1);
919
+ void shutdownProcess(1, 'fatal_error');
417
920
  });
418
921
  //# sourceMappingURL=iranti-mcp.js.map