@minasoft/mina-ai-router 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -16
- package/dist/apps/cli/src/index.js +1247 -43
- package/dist/apps/http-server/src/index.js +598 -46
- package/dist/apps/http-server/src/public/assets/index-Bl059Jd0.js +9 -0
- package/dist/apps/http-server/src/public/assets/index-CaPxN_Ez.css +1 -0
- package/dist/apps/http-server/src/public/index.html +16 -0
- package/dist/apps/mcp-server/src/index.js +54 -7
- package/dist/packages/core/src/capability-profile.js +145 -0
- package/dist/packages/core/src/index.js +3 -0
- package/dist/packages/core/src/mcp-preflight.js +80 -0
- package/dist/packages/core/src/registry.js +128 -3
- package/dist/packages/core/src/request-store.js +158 -0
- package/dist/packages/core/src/response-parser.js +76 -8
- package/dist/packages/core/src/router.js +408 -13
- package/dist/packages/core/src/version.js +57 -0
- package/dist/packages/mcp/src/provider.js +57 -6
- package/dist/packages/transports/src/headless/headless-transport.js +13 -8
- package/dist/packages/transports/src/tmux/tmux-client.js +334 -0
- package/dist/packages/transports/src/tmux/tmux-transport.js +10 -0
- package/docs/DEVELOPER-START-GUIDE.md +9 -1
- package/docs/GETTING-STARTED.md +10 -5
- package/docs/HTTP-UI-MCP.md +39 -13
- package/docs/MCP-CLIENT-SETUP.md +56 -3
- package/docs/SKILL-INSTALL-GUIDE.md +21 -3
- package/docs/TROUBLESHOOTING.md +47 -0
- package/docs/USER-START-GUIDE.md +155 -26
- package/docs/assets/mina-ai-router-overview.svg +109 -0
- package/package.json +19 -5
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RequestStore = void 0;
|
|
4
|
+
const openStatuses = new Set(["created", "sent", "waiting"]);
|
|
5
|
+
const archiveOnlyErrorPrefixes = [
|
|
6
|
+
"Archived by operator",
|
|
7
|
+
"Archived orphaned request",
|
|
8
|
+
];
|
|
4
9
|
class RequestStore {
|
|
5
10
|
constructor(requests = []) {
|
|
6
11
|
this.requests = new Map();
|
|
@@ -31,11 +36,141 @@ class RequestStore {
|
|
|
31
36
|
...current,
|
|
32
37
|
...patch,
|
|
33
38
|
status,
|
|
39
|
+
diagnosticStatus: patch.diagnosticStatus ?? diagnosticStatusFor(status),
|
|
34
40
|
updatedAt: new Date().toISOString(),
|
|
35
41
|
};
|
|
36
42
|
this.requests.set(id, updated);
|
|
37
43
|
return updated;
|
|
38
44
|
}
|
|
45
|
+
updateOpenStatus(id, status, patch = {}) {
|
|
46
|
+
const current = this.require(id);
|
|
47
|
+
if (!openStatuses.has(current.status)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return this.updateStatus(id, status, patch);
|
|
51
|
+
}
|
|
52
|
+
cancel(id, reason = "Cancelled by operator.", source = "system") {
|
|
53
|
+
const current = this.require(id);
|
|
54
|
+
this.assertActionAllowed(current, "cancel");
|
|
55
|
+
const now = new Date().toISOString();
|
|
56
|
+
return this.updateStatus(id, "cancelled", {
|
|
57
|
+
error: reason,
|
|
58
|
+
leaseStatus: "released",
|
|
59
|
+
leaseReleasedAt: now,
|
|
60
|
+
recoveryEvents: appendRecoveryEvent(current, {
|
|
61
|
+
at: now,
|
|
62
|
+
action: "cancel",
|
|
63
|
+
source,
|
|
64
|
+
message: reason,
|
|
65
|
+
previousLeaseStatus: current.leaseStatus,
|
|
66
|
+
activeRequestId: current.id,
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
archive(id, reason, source = "system") {
|
|
71
|
+
const current = this.require(id);
|
|
72
|
+
this.assertActionAllowed(current, "archive");
|
|
73
|
+
const now = new Date().toISOString();
|
|
74
|
+
const releasesLease = current.leaseStatus === "active" || current.leaseStatus === "orphaned";
|
|
75
|
+
const archiveReason = current.error ?? reason;
|
|
76
|
+
return this.updateStatus(id, "archived", {
|
|
77
|
+
archivedAt: now,
|
|
78
|
+
archivedFromStatus: current.status,
|
|
79
|
+
error: archiveReason,
|
|
80
|
+
leaseStatus: releasesLease ? "released" : current.leaseStatus,
|
|
81
|
+
leaseReleasedAt: releasesLease ? now : current.leaseReleasedAt,
|
|
82
|
+
recoveryStatus: current.leaseStatus === "orphaned" ? "recovered" : current.recoveryStatus,
|
|
83
|
+
recoveredAt: current.leaseStatus === "orphaned" ? now : current.recoveredAt,
|
|
84
|
+
recoveryEvents: current.leaseStatus === "orphaned"
|
|
85
|
+
? appendRecoveryEvent(current, {
|
|
86
|
+
at: now,
|
|
87
|
+
action: "archive",
|
|
88
|
+
source,
|
|
89
|
+
message: reason ?? "Archived orphaned request and released lease.",
|
|
90
|
+
previousLeaseStatus: current.leaseStatus,
|
|
91
|
+
activeRequestId: current.id,
|
|
92
|
+
})
|
|
93
|
+
: current.recoveryEvents,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
unarchive(id) {
|
|
97
|
+
const current = this.require(id);
|
|
98
|
+
this.assertActionAllowed(current, "unarchive");
|
|
99
|
+
const restoredStatus = current.archivedFromStatus ?? "answered";
|
|
100
|
+
const shouldClearArchiveError = current.error
|
|
101
|
+
&& restoredStatus !== "failed"
|
|
102
|
+
&& restoredStatus !== "timeout"
|
|
103
|
+
&& archiveOnlyErrorPrefixes.some((prefix) => current.error?.startsWith(prefix));
|
|
104
|
+
return this.updateStatus(id, restoredStatus, {
|
|
105
|
+
archivedAt: undefined,
|
|
106
|
+
archivedFromStatus: undefined,
|
|
107
|
+
error: shouldClearArchiveError ? undefined : current.error,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
recordRetry(originalRequestId, retryRequestId) {
|
|
111
|
+
const original = this.require(originalRequestId);
|
|
112
|
+
return this.patch(original.id, {
|
|
113
|
+
retriedByRequestId: retryRequestId,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
recordInterrupt(id, options) {
|
|
117
|
+
const current = this.require(id);
|
|
118
|
+
this.assertActionAllowed(current, "interrupt");
|
|
119
|
+
const now = new Date().toISOString();
|
|
120
|
+
return this.patch(current.id, {
|
|
121
|
+
recoveryStatus: "interrupted",
|
|
122
|
+
interruptedAt: now,
|
|
123
|
+
recoveryEvents: appendRecoveryEvent(current, {
|
|
124
|
+
at: now,
|
|
125
|
+
action: "interrupt",
|
|
126
|
+
source: options.source,
|
|
127
|
+
message: options.message ?? "Terminal interrupt sent by operator.",
|
|
128
|
+
previousLeaseStatus: current.leaseStatus,
|
|
129
|
+
activeRequestId: current.id,
|
|
130
|
+
terminalTarget: options.terminalTarget,
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
markRecovered(id, source, message = "Marked recovered by operator.") {
|
|
135
|
+
const current = this.require(id);
|
|
136
|
+
this.assertActionAllowed(current, "recover");
|
|
137
|
+
const now = new Date().toISOString();
|
|
138
|
+
return this.patch(current.id, {
|
|
139
|
+
leaseStatus: "released",
|
|
140
|
+
leaseReleasedAt: now,
|
|
141
|
+
recoveryStatus: "recovered",
|
|
142
|
+
recoveredAt: now,
|
|
143
|
+
recoveryEvents: appendRecoveryEvent(current, {
|
|
144
|
+
at: now,
|
|
145
|
+
action: "recover",
|
|
146
|
+
source,
|
|
147
|
+
message,
|
|
148
|
+
previousLeaseStatus: current.leaseStatus,
|
|
149
|
+
activeRequestId: current.id,
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
assertActionAllowed(request, action) {
|
|
154
|
+
const validActions = this.validActions(request);
|
|
155
|
+
if (!validActions.includes(action)) {
|
|
156
|
+
throw new Error(`Cannot ${action} request "${request.id}" while it is ${request.status}. Valid actions: ${validActions.join(", ") || "none"}.`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
validActions(request) {
|
|
160
|
+
if (request.status === "archived") {
|
|
161
|
+
return ["retry", "unarchive"];
|
|
162
|
+
}
|
|
163
|
+
if (request.leaseStatus === "orphaned") {
|
|
164
|
+
const recoveryActions = request.recoveryStatus === "interrupted"
|
|
165
|
+
? ["recover"]
|
|
166
|
+
: ["interrupt", "recover"];
|
|
167
|
+
return [...recoveryActions, "retry", "archive"];
|
|
168
|
+
}
|
|
169
|
+
if (openStatuses.has(request.status)) {
|
|
170
|
+
return ["cancel"];
|
|
171
|
+
}
|
|
172
|
+
return ["retry", "archive"];
|
|
173
|
+
}
|
|
39
174
|
patch(id, patch) {
|
|
40
175
|
const current = this.require(id);
|
|
41
176
|
const updated = {
|
|
@@ -48,3 +183,26 @@ class RequestStore {
|
|
|
48
183
|
}
|
|
49
184
|
}
|
|
50
185
|
exports.RequestStore = RequestStore;
|
|
186
|
+
function appendRecoveryEvent(request, event) {
|
|
187
|
+
return [...(request.recoveryEvents ?? []), event];
|
|
188
|
+
}
|
|
189
|
+
function diagnosticStatusFor(status) {
|
|
190
|
+
switch (status) {
|
|
191
|
+
case "created":
|
|
192
|
+
case "sent":
|
|
193
|
+
case "waiting":
|
|
194
|
+
return "pending";
|
|
195
|
+
case "answered":
|
|
196
|
+
return "answered";
|
|
197
|
+
case "timeout":
|
|
198
|
+
return "timeout";
|
|
199
|
+
case "cancelled":
|
|
200
|
+
return "cancelled";
|
|
201
|
+
case "archived":
|
|
202
|
+
return "archived";
|
|
203
|
+
case "failed":
|
|
204
|
+
return "unknown_failure";
|
|
205
|
+
default:
|
|
206
|
+
return "unknown_failure";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -1,33 +1,101 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ResponseMarkerNotFoundError = void 0;
|
|
3
|
+
exports.ResponseMarkerNotFoundError = exports.ResponseParseError = void 0;
|
|
4
|
+
exports.inspectMarkedResponse = inspectMarkedResponse;
|
|
4
5
|
exports.parseMarkedResponse = parseMarkedResponse;
|
|
5
|
-
class
|
|
6
|
-
constructor(
|
|
7
|
-
super(
|
|
6
|
+
class ResponseParseError extends Error {
|
|
7
|
+
constructor(diagnostics) {
|
|
8
|
+
super(diagnostics.message);
|
|
9
|
+
this.diagnostics = diagnostics;
|
|
10
|
+
this.name = "ResponseParseError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.ResponseParseError = ResponseParseError;
|
|
14
|
+
class ResponseMarkerNotFoundError extends ResponseParseError {
|
|
15
|
+
constructor(diagnostics) {
|
|
16
|
+
super(diagnostics);
|
|
8
17
|
this.name = "ResponseMarkerNotFoundError";
|
|
9
18
|
}
|
|
10
19
|
}
|
|
11
20
|
exports.ResponseMarkerNotFoundError = ResponseMarkerNotFoundError;
|
|
12
|
-
function
|
|
21
|
+
function inspectMarkedResponse(output, requestId) {
|
|
13
22
|
const startMarker = `<<<MINA_AGENT_RESPONSE_START ${requestId}>>>`;
|
|
14
23
|
const endMarker = `<<<MINA_AGENT_RESPONSE_END ${requestId}>>>`;
|
|
24
|
+
const startMarkerFound = output.includes(startMarker);
|
|
25
|
+
const endMarkerFound = output.includes(endMarker);
|
|
26
|
+
let candidateCount = 0;
|
|
27
|
+
let placeholderCount = 0;
|
|
15
28
|
let end = output.lastIndexOf(endMarker);
|
|
16
29
|
while (end !== -1) {
|
|
17
30
|
const start = output.lastIndexOf(startMarker, end);
|
|
18
31
|
if (start === -1) {
|
|
19
|
-
|
|
32
|
+
return failedDiagnostics({
|
|
33
|
+
kind: "missing_start_marker",
|
|
34
|
+
requestId,
|
|
35
|
+
message: `Response end marker was found without a matching start marker for request ${requestId}.`,
|
|
36
|
+
startMarkerFound,
|
|
37
|
+
endMarkerFound,
|
|
38
|
+
candidateCount,
|
|
39
|
+
placeholderCount,
|
|
40
|
+
});
|
|
20
41
|
}
|
|
21
42
|
const contentStart = start + startMarker.length;
|
|
22
43
|
const answer = output.slice(contentStart, end).trim();
|
|
44
|
+
candidateCount += 1;
|
|
23
45
|
if (!isPlaceholderAnswer(answer)) {
|
|
24
|
-
return
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
answer,
|
|
49
|
+
diagnostics: {
|
|
50
|
+
kind: "parsed",
|
|
51
|
+
requestId,
|
|
52
|
+
message: `Parsed marker-wrapped response for request ${requestId}.`,
|
|
53
|
+
startMarkerFound,
|
|
54
|
+
endMarkerFound,
|
|
55
|
+
candidateCount,
|
|
56
|
+
placeholderCount,
|
|
57
|
+
answerLength: answer.length,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
25
60
|
}
|
|
61
|
+
placeholderCount += 1;
|
|
26
62
|
end = output.lastIndexOf(endMarker, start - 1);
|
|
27
63
|
}
|
|
28
|
-
|
|
64
|
+
if (placeholderCount > 0) {
|
|
65
|
+
return failedDiagnostics({
|
|
66
|
+
kind: "placeholder_only",
|
|
67
|
+
requestId,
|
|
68
|
+
message: `Response markers only contained placeholder content for request ${requestId}.`,
|
|
69
|
+
startMarkerFound,
|
|
70
|
+
endMarkerFound,
|
|
71
|
+
candidateCount,
|
|
72
|
+
placeholderCount,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return failedDiagnostics({
|
|
76
|
+
kind: "missing_markers",
|
|
77
|
+
requestId,
|
|
78
|
+
message: `Response markers were not found for request ${requestId}.`,
|
|
79
|
+
startMarkerFound,
|
|
80
|
+
endMarkerFound,
|
|
81
|
+
candidateCount,
|
|
82
|
+
placeholderCount,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function parseMarkedResponse(output, requestId) {
|
|
86
|
+
const result = inspectMarkedResponse(output, requestId);
|
|
87
|
+
if (result.ok) {
|
|
88
|
+
return result.answer;
|
|
89
|
+
}
|
|
90
|
+
throw new ResponseMarkerNotFoundError(result.diagnostics);
|
|
29
91
|
}
|
|
30
92
|
function isPlaceholderAnswer(answer) {
|
|
31
93
|
const normalized = answer.replace(/[|\s]/g, "");
|
|
32
94
|
return normalized === "..." || normalized === "[youranswer]";
|
|
33
95
|
}
|
|
96
|
+
function failedDiagnostics(diagnostics) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
diagnostics,
|
|
100
|
+
};
|
|
101
|
+
}
|