@minasoft/mina-ai-router 0.1.5 → 0.2.1

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 (31) hide show
  1. package/README.md +69 -16
  2. package/dist/apps/cli/src/index.js +1251 -46
  3. package/dist/apps/http-server/src/index.js +559 -43
  4. package/dist/apps/http-server/src/public/assets/index-Bl059Jd0.js +9 -0
  5. package/dist/apps/http-server/src/public/assets/index-CaPxN_Ez.css +1 -0
  6. package/dist/apps/http-server/src/public/index.html +2 -2
  7. package/dist/apps/mcp-server/src/index.js +55 -9
  8. package/dist/packages/core/src/capability-profile.js +145 -0
  9. package/dist/packages/core/src/index.js +4 -0
  10. package/dist/packages/core/src/mcp-preflight.js +80 -0
  11. package/dist/packages/core/src/registry.js +128 -3
  12. package/dist/packages/core/src/request-store.js +158 -0
  13. package/dist/packages/core/src/response-parser.js +76 -8
  14. package/dist/packages/core/src/router.js +408 -13
  15. package/dist/packages/core/src/runtime-paths.js +16 -0
  16. package/dist/packages/core/src/version.js +57 -0
  17. package/dist/packages/mcp/src/provider.js +57 -6
  18. package/dist/packages/transports/src/headless/headless-transport.js +13 -8
  19. package/dist/packages/transports/src/tmux/tmux-client.js +334 -0
  20. package/dist/packages/transports/src/tmux/tmux-transport.js +10 -0
  21. package/docs/DEVELOPER-START-GUIDE.md +9 -1
  22. package/docs/GETTING-STARTED.md +10 -5
  23. package/docs/HTTP-UI-MCP.md +39 -13
  24. package/docs/MCP-CLIENT-SETUP.md +56 -3
  25. package/docs/SKILL-INSTALL-GUIDE.md +21 -3
  26. package/docs/TROUBLESHOOTING.md +51 -2
  27. package/docs/USER-START-GUIDE.md +157 -26
  28. package/docs/assets/mina-ai-router-overview.svg +109 -0
  29. package/package.json +8 -2
  30. package/dist/apps/http-server/src/public/assets/index-Be0tne90.js +0 -9
  31. package/dist/apps/http-server/src/public/assets/index-CEhd8YGG.css +0 -1
@@ -1,9 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AgentRouter = void 0;
3
+ exports.AgentRouter = exports.AgentNotRouteReadyError = void 0;
4
4
  const ids_1 = require("./ids");
5
5
  const prompt_envelope_1 = require("./prompt-envelope");
6
6
  const response_parser_1 = require("./response-parser");
7
+ const rawEvidenceExcerptLimit = 4000;
8
+ const defaultAgentStaleAfterMs = 15 * 60 * 1000;
9
+ class RequestNoLongerOpenError extends Error {
10
+ constructor(request) {
11
+ super(`Request "${request.id}" is ${request.status} and can no longer be updated by the active router call.`);
12
+ this.request = request;
13
+ }
14
+ }
15
+ class AgentNotRouteReadyError extends Error {
16
+ constructor(agentId, reason) {
17
+ super(`Agent "${agentId}" is not ready to receive routed work: ${reason}`);
18
+ this.agentId = agentId;
19
+ this.reason = reason;
20
+ }
21
+ }
22
+ exports.AgentNotRouteReadyError = AgentNotRouteReadyError;
7
23
  class AgentRouter {
8
24
  constructor(options) {
9
25
  this.busyAgents = new Set();
@@ -12,6 +28,7 @@ class AgentRouter {
12
28
  this.transports = options.transports;
13
29
  this.defaultSourceAgent = options.defaultSourceAgent ?? "main";
14
30
  this.defaultTimeoutMs = options.defaultTimeoutMs ?? 300000;
31
+ this.agentStaleAfterMs = options.agentStaleAfterMs ?? defaultAgentStaleAfterMs;
15
32
  this.onStateChanged = options.onStateChanged;
16
33
  }
17
34
  listAgents() {
@@ -20,8 +37,10 @@ class AgentRouter {
20
37
  listRequests() {
21
38
  return this.requestStore.list();
22
39
  }
23
- async listAgentStatuses() {
40
+ async listAgentStatuses(options = {}) {
24
41
  const requests = this.requestStore.list();
42
+ const checkedAt = new Date().toISOString();
43
+ let healthChanged = false;
25
44
  return Promise.all(this.registry.list().map(async (agent) => {
26
45
  const agentRequests = requests.filter((request) => request.targetAgent === agent.id);
27
46
  const lastRequest = agentRequests[agentRequests.length - 1];
@@ -29,6 +48,39 @@ class AgentRouter {
29
48
  const status = transport.status
30
49
  ? await transport.status(agent)
31
50
  : { status: "unknown" };
51
+ const bootstrapStatus = status.bootstrapStatus ?? agent.bootstrapStatus;
52
+ if (status.bootstrapStatus && status.bootstrapStatus !== agent.bootstrapStatus) {
53
+ this.registry.register({
54
+ ...agent,
55
+ bootstrapStatus: status.bootstrapStatus,
56
+ });
57
+ healthChanged = true;
58
+ }
59
+ const lastActivityAt = lastRequest?.updatedAt ?? agent.lastActivityAt;
60
+ const lastSeenAt = status.status === "available" ? checkedAt : agent.lastSeenAt;
61
+ const healthStatus = classifyAgentHealth({
62
+ transportStatus: status.status,
63
+ bootstrapStatus,
64
+ registrationStatus: agent.registrationStatus,
65
+ mcpPreflightStatus: agent.mcpPreflightStatus,
66
+ busy: this.busyAgents.has(agent.id),
67
+ lastSeenAt,
68
+ lastActivityAt,
69
+ lastRequest,
70
+ checkedAt,
71
+ staleAfterMs: this.agentStaleAfterMs,
72
+ });
73
+ const routeReadiness = classifyRouteReadiness({
74
+ healthStatus: healthStatus.status,
75
+ healthDetail: healthStatus.detail ?? status.detail,
76
+ bootstrapStatus,
77
+ registrationStatus: agent.registrationStatus,
78
+ mcpPreflightStatus: agent.mcpPreflightStatus,
79
+ });
80
+ if (lastSeenAt !== agent.lastSeenAt || lastActivityAt !== agent.lastActivityAt) {
81
+ this.registry.updateHealth(agent.id, { lastSeenAt, lastActivityAt });
82
+ healthChanged = true;
83
+ }
32
84
  return {
33
85
  id: agent.id,
34
86
  name: agent.name,
@@ -38,22 +90,127 @@ class AgentRouter {
38
90
  projectRoot: agent.projectRoot,
39
91
  capabilitySummary: agent.capabilitySummary,
40
92
  capabilitySources: agent.capabilitySources,
41
- status: this.busyAgents.has(agent.id) ? "busy" : status.status,
42
- detail: status.detail,
93
+ capabilitySource: agent.capabilitySource,
94
+ capabilityUpdatedAt: agent.capabilityUpdatedAt,
95
+ lastCapabilityRefreshAt: agent.lastCapabilityRefreshAt,
96
+ capabilityProfile: agent.capabilityProfile,
97
+ bootstrapStatus,
98
+ registrationSource: agent.registrationSource,
99
+ registrationStatus: agent.registrationStatus,
100
+ lastRegistrationAttemptAt: agent.lastRegistrationAttemptAt,
101
+ confirmedByAgentAt: agent.confirmedByAgentAt,
102
+ sessionFingerprint: agent.sessionFingerprint,
103
+ registrationHistory: agent.registrationHistory,
104
+ registrationWarnings: agent.registrationWarnings,
105
+ permissionProfile: agent.permissionProfile,
106
+ permissionProfileStatus: agent.permissionProfileStatus,
107
+ permissionProfileDetail: agent.permissionProfileDetail,
108
+ permissionPrompt: status.permissionPrompt,
109
+ mcpPreflightStatus: agent.mcpPreflightStatus,
110
+ mcpPreflightDetail: agent.mcpPreflightDetail,
111
+ mcpSetupCommand: agent.mcpSetupCommand,
112
+ mcpVerifyCommand: agent.mcpVerifyCommand,
113
+ mcpRemoveCommand: agent.mcpRemoveCommand,
114
+ mcpUrl: agent.mcpUrl,
115
+ lastSeenAt,
116
+ lastActivityAt,
117
+ activeRequestId: agent.activeRequestId,
118
+ leaseStatus: agent.leaseStatus,
119
+ leaseStartedAt: agent.leaseStartedAt,
120
+ leaseExpiresAt: agent.leaseExpiresAt,
121
+ leaseReleasedAt: agent.leaseReleasedAt,
122
+ healthCheckedAt: checkedAt,
123
+ staleAfterMs: this.agentStaleAfterMs,
124
+ status: healthStatus.status,
125
+ detail: healthStatus.detail ?? status.detail,
126
+ routeReady: routeReadiness.routeReady,
127
+ routeBlockedReason: routeReadiness.routeBlockedReason,
43
128
  lastRequestStatus: lastRequest?.status,
129
+ isSelf: options.callerAgentId === agent.id || undefined,
44
130
  };
45
- }));
131
+ })).finally(() => {
132
+ if (healthChanged) {
133
+ this.persistState();
134
+ }
135
+ });
46
136
  }
47
137
  getRequest(requestId) {
48
138
  return this.requestStore.require(requestId);
49
139
  }
140
+ recoverRequestLease(requestId, source, message = "Marked recovered by operator.") {
141
+ const updated = this.requestStore.markRecovered(requestId, source, message);
142
+ const agentId = updated.leaseOwnerAgentId ?? updated.targetAgent;
143
+ this.releaseAgentLease(agentId, updated.id);
144
+ this.persistState();
145
+ return this.requestStore.require(updated.id);
146
+ }
147
+ archiveRequest(requestId, source, reason = "Archived by operator.") {
148
+ const updated = this.requestStore.archive(requestId, reason, source);
149
+ if (updated.leaseStatus === "released") {
150
+ const agentId = updated.leaseOwnerAgentId ?? updated.targetAgent;
151
+ this.releaseAgentLease(agentId, updated.id);
152
+ }
153
+ this.persistState();
154
+ return this.requestStore.require(updated.id);
155
+ }
50
156
  async callAgent(input) {
51
157
  const target = this.registry.require(input.target);
158
+ if (input.sourceAgent && input.sourceAgent === target.id && !input.allowSelfCall) {
159
+ throw new Error(`Refusing self-call from agent "${target.id}" to itself. Choose another target from list_agents or pass allowSelfCall: true for diagnostics.`);
160
+ }
52
161
  if (this.busyAgents.has(target.id)) {
53
162
  throw new Error(`Agent "${target.id}" is busy with another request.`);
54
163
  }
164
+ const transport = this.transports.get(target.transport);
165
+ const checkedAt = new Date().toISOString();
166
+ const transportStatus = transport.status
167
+ ? await transport.status(target)
168
+ : { status: "unknown" };
169
+ const bootstrapStatus = transportStatus.bootstrapStatus ?? target.bootstrapStatus;
170
+ if (transportStatus.bootstrapStatus && transportStatus.bootstrapStatus !== target.bootstrapStatus) {
171
+ this.registry.register({
172
+ ...target,
173
+ bootstrapStatus: transportStatus.bootstrapStatus,
174
+ });
175
+ this.persistState();
176
+ }
177
+ const agentRequests = this.requestStore.list().filter((request) => request.targetAgent === target.id);
178
+ const lastRequest = agentRequests[agentRequests.length - 1];
179
+ const healthStatus = classifyAgentHealth({
180
+ transportStatus: transportStatus.status,
181
+ bootstrapStatus,
182
+ registrationStatus: target.registrationStatus,
183
+ mcpPreflightStatus: target.mcpPreflightStatus,
184
+ busy: false,
185
+ lastSeenAt: transportStatus.status === "available" ? checkedAt : target.lastSeenAt,
186
+ lastActivityAt: lastRequest?.updatedAt ?? target.lastActivityAt,
187
+ lastRequest,
188
+ checkedAt,
189
+ staleAfterMs: this.agentStaleAfterMs,
190
+ });
191
+ const routeReadiness = classifyRouteReadiness({
192
+ healthStatus: healthStatus.status,
193
+ healthDetail: healthStatus.detail ?? transportStatus.detail,
194
+ bootstrapStatus,
195
+ registrationStatus: target.registrationStatus,
196
+ mcpPreflightStatus: target.mcpPreflightStatus,
197
+ });
198
+ if (!routeReadiness.routeReady) {
199
+ throw new AgentNotRouteReadyError(target.id, routeReadiness.routeBlockedReason ?? "Agent is not route-ready.");
200
+ }
55
201
  this.busyAgents.add(target.id);
56
202
  const now = new Date().toISOString();
203
+ const timeoutMs = input.timeoutMs ?? this.defaultTimeoutMs;
204
+ const leaseExpiresAt = new Date(Date.now() + timeoutMs).toISOString();
205
+ this.registry.register({
206
+ ...target,
207
+ activeRequestId: undefined,
208
+ leaseStatus: undefined,
209
+ leaseStartedAt: undefined,
210
+ leaseExpiresAt: undefined,
211
+ leaseReleasedAt: undefined,
212
+ lastActivityAt: now,
213
+ });
57
214
  const request = this.requestStore.create({
58
215
  id: (0, ids_1.createRequestId)(),
59
216
  sourceAgent: input.sourceAgent ?? this.defaultSourceAgent,
@@ -62,19 +219,49 @@ class AgentRouter {
62
219
  status: "created",
63
220
  createdAt: now,
64
221
  updatedAt: now,
222
+ retryOfRequestId: input.retryOfRequestId,
223
+ leaseStatus: "active",
224
+ leaseStartedAt: now,
225
+ leaseExpiresAt,
226
+ leaseOwnerAgentId: target.id,
227
+ leaseTargetSessionId: target.sessionId,
228
+ leaseTargetSessionFingerprint: target.sessionFingerprint,
229
+ });
230
+ this.registry.register({
231
+ ...target,
232
+ activeRequestId: request.id,
233
+ leaseStatus: "active",
234
+ leaseStartedAt: now,
235
+ leaseExpiresAt,
236
+ leaseReleasedAt: undefined,
237
+ lastActivityAt: now,
65
238
  });
66
239
  this.persistState();
67
- const transport = this.transports.get(target.transport);
68
240
  const prompt = (0, prompt_envelope_1.buildPromptEnvelope)(request, target);
241
+ this.requestStore.patch(request.id, {
242
+ promptEvidence: rawEvidenceFromOutput(prompt, "prompt_envelope"),
243
+ });
244
+ let output = "";
69
245
  try {
70
246
  await transport.send(target, prompt, request.id);
71
- this.requestStore.updateStatus(request.id, "sent");
247
+ this.updateOpenRequestStatus(request.id, "sent");
72
248
  this.persistState();
73
- this.requestStore.updateStatus(request.id, "waiting");
249
+ this.updateOpenRequestStatus(request.id, "waiting");
74
250
  this.persistState();
75
- const output = await transport.waitForResponse(target, request.id, input.timeoutMs ?? this.defaultTimeoutMs);
76
- const answer = (0, response_parser_1.parseMarkedResponse)(output, request.id);
77
- this.requestStore.updateStatus(request.id, "answered", { answer });
251
+ output = await transport.waitForResponse(target, request.id, input.timeoutMs ?? this.defaultTimeoutMs);
252
+ const parsed = (0, response_parser_1.inspectMarkedResponse)(output, request.id);
253
+ if (!parsed.ok) {
254
+ throw new response_parser_1.ResponseParseError(parsed.diagnostics);
255
+ }
256
+ const answer = parsed.answer;
257
+ this.updateOpenRequestStatus(request.id, "answered", {
258
+ answer,
259
+ diagnosticStatus: "answered",
260
+ parserDiagnostics: parsed.diagnostics,
261
+ rawEvidence: rawEvidenceFromOutput(output),
262
+ ...releasedLeasePatch(),
263
+ });
264
+ this.releaseAgentLease(target.id, request.id);
78
265
  this.persistState();
79
266
  return {
80
267
  requestId: request.id,
@@ -83,18 +270,226 @@ class AgentRouter {
83
270
  };
84
271
  }
85
272
  catch (error) {
273
+ if (error instanceof RequestNoLongerOpenError) {
274
+ this.releaseAgentLease(target.id, request.id);
275
+ this.persistState();
276
+ throw error;
277
+ }
86
278
  const message = error instanceof Error ? error.message : String(error);
87
279
  const status = /time(?:d)?\s*out|timeout/i.test(message) ? "timeout" : "failed";
88
- this.requestStore.updateStatus(request.id, status, { error: message });
280
+ const capturedOutput = output || extractLastCapture(message);
281
+ const updated = this.requestStore.updateOpenStatus(request.id, status, {
282
+ error: message,
283
+ diagnosticStatus: diagnosticStatusForError(error, status),
284
+ parserDiagnostics: error instanceof response_parser_1.ResponseParseError ? error.diagnostics : undefined,
285
+ rawEvidence: capturedOutput ? rawEvidenceFromOutput(capturedOutput) : undefined,
286
+ ...(status === "timeout" ? orphanedLeasePatch() : releasedLeasePatch()),
287
+ });
288
+ if (!updated) {
289
+ this.releaseAgentLease(target.id, request.id);
290
+ throw new RequestNoLongerOpenError(this.requestStore.require(request.id));
291
+ }
292
+ if (status === "timeout") {
293
+ this.orphanAgentLease(target.id, request.id);
294
+ }
295
+ else {
296
+ this.releaseAgentLease(target.id, request.id);
297
+ }
89
298
  this.persistState();
90
299
  throw error;
91
300
  }
92
301
  finally {
93
- this.busyAgents.delete(target.id);
302
+ const current = this.registry.get(target.id);
303
+ if (current?.activeRequestId !== request.id) {
304
+ this.busyAgents.delete(target.id);
305
+ }
306
+ }
307
+ }
308
+ updateOpenRequestStatus(requestId, status, patch = {}) {
309
+ const updated = this.requestStore.updateOpenStatus(requestId, status, patch);
310
+ if (!updated) {
311
+ throw new RequestNoLongerOpenError(this.requestStore.require(requestId));
94
312
  }
313
+ return updated;
95
314
  }
96
315
  persistState() {
97
316
  this.onStateChanged?.();
98
317
  }
318
+ releaseAgentLease(agentId, requestId) {
319
+ const current = this.registry.get(agentId);
320
+ if (!current || current.activeRequestId !== requestId) {
321
+ this.busyAgents.delete(agentId);
322
+ return;
323
+ }
324
+ this.registry.register({
325
+ ...current,
326
+ activeRequestId: undefined,
327
+ leaseStatus: "released",
328
+ leaseReleasedAt: new Date().toISOString(),
329
+ });
330
+ this.busyAgents.delete(agentId);
331
+ }
332
+ orphanAgentLease(agentId, requestId) {
333
+ const current = this.registry.get(agentId);
334
+ if (!current || current.activeRequestId !== requestId) {
335
+ return;
336
+ }
337
+ this.registry.register({
338
+ ...current,
339
+ activeRequestId: requestId,
340
+ leaseStatus: "orphaned",
341
+ });
342
+ }
99
343
  }
100
344
  exports.AgentRouter = AgentRouter;
345
+ function releasedLeasePatch() {
346
+ return {
347
+ leaseStatus: "released",
348
+ leaseReleasedAt: new Date().toISOString(),
349
+ };
350
+ }
351
+ function orphanedLeasePatch() {
352
+ return {
353
+ leaseStatus: "orphaned",
354
+ };
355
+ }
356
+ function diagnosticStatusForError(error, status) {
357
+ if (status === "timeout") {
358
+ return "timeout";
359
+ }
360
+ if (error instanceof response_parser_1.ResponseParseError) {
361
+ return "parse_failure";
362
+ }
363
+ return "transport_failure";
364
+ }
365
+ function classifyAgentHealth(input) {
366
+ if (input.lastRequest?.leaseStatus === "orphaned") {
367
+ return { status: "busy", detail: `Request "${input.lastRequest.id}" timed out in the router, but the target session lease is still attached for recovery.` };
368
+ }
369
+ if (input.lastRequest?.leaseStatus === "active" && ["created", "sent", "waiting"].includes(input.lastRequest.status)) {
370
+ return { status: "busy", detail: `Request "${input.lastRequest.id}" still has an active lease.` };
371
+ }
372
+ if (input.busy) {
373
+ return { status: "busy", detail: "Agent is currently handling a routed request." };
374
+ }
375
+ if (input.transportStatus === "missing") {
376
+ return { status: "missing" };
377
+ }
378
+ if (input.transportStatus === "needs-attention") {
379
+ return { status: "needs-attention" };
380
+ }
381
+ if (input.bootstrapStatus === "permission-required") {
382
+ return {
383
+ status: "needs-attention",
384
+ detail: "Agent is waiting for operator permission before it can receive routed work.",
385
+ };
386
+ }
387
+ if (input.bootstrapStatus === "client-update-required") {
388
+ return {
389
+ status: "needs-attention",
390
+ detail: "Agent CLI is waiting at an update prompt before Mina can continue registration.",
391
+ };
392
+ }
393
+ if (input.bootstrapStatus === "mcp-configuring") {
394
+ return {
395
+ status: "needs-attention",
396
+ detail: "Agent is waiting for Mina MCP setup before it can self-register or receive routed work.",
397
+ };
398
+ }
399
+ if (input.bootstrapStatus === "registration-pending" || input.registrationStatus === "pending") {
400
+ return {
401
+ status: "needs-attention",
402
+ detail: "Agent self-registration has not been confirmed yet.",
403
+ };
404
+ }
405
+ if (input.mcpPreflightStatus === "missing" || input.mcpPreflightStatus === "stale") {
406
+ return {
407
+ status: "needs-attention",
408
+ detail: "Agent MCP preflight is not ready; fix MCP setup before routing work.",
409
+ };
410
+ }
411
+ const attentionStatuses = new Set(["failed", "timeout"]);
412
+ if (input.lastRequest && attentionStatuses.has(input.lastRequest.status) && input.lastRequest.recoveryStatus !== "recovered") {
413
+ return {
414
+ status: "needs-attention",
415
+ detail: input.lastRequest.error
416
+ ? `Last request ${input.lastRequest.status}: ${input.lastRequest.error}`
417
+ : `Last request is ${input.lastRequest.status}.`,
418
+ };
419
+ }
420
+ const staleReference = input.lastSeenAt ?? input.lastActivityAt;
421
+ if (staleReference && isOlderThan(staleReference, input.checkedAt, input.staleAfterMs)) {
422
+ return {
423
+ status: "stale",
424
+ detail: `No confirmed agent reachability within ${Math.round(input.staleAfterMs / 1000)} seconds.`,
425
+ };
426
+ }
427
+ if (input.transportStatus === "available") {
428
+ return { status: "available" };
429
+ }
430
+ if (input.transportStatus === "stale") {
431
+ return { status: input.transportStatus };
432
+ }
433
+ return { status: "unknown" };
434
+ }
435
+ function classifyRouteReadiness(input) {
436
+ const blocker = routeBlocker(input);
437
+ return blocker
438
+ ? { routeReady: false, routeBlockedReason: blocker }
439
+ : { routeReady: true };
440
+ }
441
+ function routeBlocker(input) {
442
+ if (input.bootstrapStatus === "permission-required") {
443
+ return "Agent is waiting for operator permission before it can receive routed work.";
444
+ }
445
+ if (input.bootstrapStatus === "client-update-required") {
446
+ return "Agent CLI is waiting at an update prompt before Mina can continue registration.";
447
+ }
448
+ if (input.bootstrapStatus === "mcp-configuring") {
449
+ return "Agent is waiting for Mina MCP setup before it can self-register or receive routed work.";
450
+ }
451
+ if (input.bootstrapStatus === "registration-pending" || input.registrationStatus === "pending") {
452
+ return "Agent self-registration has not been confirmed yet.";
453
+ }
454
+ if (input.mcpPreflightStatus === "missing" || input.mcpPreflightStatus === "stale") {
455
+ return "Agent MCP preflight is not ready; fix MCP setup before routing work.";
456
+ }
457
+ if (input.healthStatus === "busy") {
458
+ return input.healthDetail ?? "Agent is currently handling a routed request.";
459
+ }
460
+ if (input.healthStatus === "missing") {
461
+ return input.healthDetail ?? "Agent transport is missing.";
462
+ }
463
+ if (input.healthStatus === "stale") {
464
+ return input.healthDetail ?? "Agent health is stale.";
465
+ }
466
+ if (input.healthStatus === "needs-attention") {
467
+ return input.healthDetail ?? "Agent needs operator attention before it can receive routed work.";
468
+ }
469
+ return undefined;
470
+ }
471
+ function isOlderThan(timestamp, now, staleAfterMs) {
472
+ const timestampMs = Date.parse(timestamp);
473
+ const nowMs = Date.parse(now);
474
+ return Number.isFinite(timestampMs)
475
+ && Number.isFinite(nowMs)
476
+ && nowMs - timestampMs > staleAfterMs;
477
+ }
478
+ function rawEvidenceFromOutput(output, kind = "transport_capture") {
479
+ const excerpt = output.slice(-rawEvidenceExcerptLimit);
480
+ return {
481
+ kind,
482
+ capturedAt: new Date().toISOString(),
483
+ characterCount: output.length,
484
+ excerpt,
485
+ truncated: output.length > excerpt.length,
486
+ };
487
+ }
488
+ function extractLastCapture(message) {
489
+ const marker = "Last capture:\n";
490
+ const index = message.indexOf(marker);
491
+ if (index === -1) {
492
+ return undefined;
493
+ }
494
+ return message.slice(index + marker.length);
495
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultRuntimeDir = defaultRuntimeDir;
4
+ exports.defaultRouterStatePath = defaultRouterStatePath;
5
+ exports.defaultServerPidPath = defaultServerPidPath;
6
+ const node_os_1 = require("node:os");
7
+ const node_path_1 = require("node:path");
8
+ function defaultRuntimeDir() {
9
+ return process.env.MINA_RUNTIME_DIR ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".mair");
10
+ }
11
+ function defaultRouterStatePath() {
12
+ return (0, node_path_1.join)(defaultRuntimeDir(), "router-state.json");
13
+ }
14
+ function defaultServerPidPath() {
15
+ return (0, node_path_1.join)(defaultRuntimeDir(), "mair-server.json");
16
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.packageVersion = packageVersion;
4
+ exports.packageRoot = packageRoot;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const fallbackVersion = "0.0.0";
8
+ const packageName = "@minasoft/mina-ai-router";
9
+ function packageVersion() {
10
+ const packagePath = findPackageJson();
11
+ if (!packagePath) {
12
+ return fallbackVersion;
13
+ }
14
+ try {
15
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(packagePath, "utf8"));
16
+ return typeof parsed.version === "string" && parsed.version.trim()
17
+ ? parsed.version
18
+ : fallbackVersion;
19
+ }
20
+ catch {
21
+ return fallbackVersion;
22
+ }
23
+ }
24
+ function packageRoot() {
25
+ const packagePath = findPackageJson();
26
+ return packagePath ? (0, node_path_1.dirname)(packagePath) : undefined;
27
+ }
28
+ function findPackageJson() {
29
+ return findUpPackageJson(__dirname);
30
+ }
31
+ function findUpPackageJson(start) {
32
+ let current = (0, node_path_1.resolve)(start);
33
+ for (let depth = 0; depth < 10; depth += 1) {
34
+ const candidate = (0, node_path_1.join)(current, "package.json");
35
+ if (isMinaPackageJson(candidate)) {
36
+ return candidate;
37
+ }
38
+ const parent = (0, node_path_1.dirname)(current);
39
+ if (parent === current) {
40
+ return undefined;
41
+ }
42
+ current = parent;
43
+ }
44
+ return undefined;
45
+ }
46
+ function isMinaPackageJson(candidate) {
47
+ if (!(0, node_fs_1.existsSync)(candidate)) {
48
+ return false;
49
+ }
50
+ try {
51
+ const parsed = JSON.parse((0, node_fs_1.readFileSync)(candidate, "utf8"));
52
+ return parsed.name === packageName;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }