@pasko70/pibo 3.6.2 → 3.6.4
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/dist/agent-runtime/auth-contract.js +215 -0
- package/dist/agent-runtime/registry.js +1 -214
- package/dist/agent-runtime/routed-session.js +7 -0
- package/dist/agent-runtimes/codex-native/turn.js +2 -2
- package/dist/agent-runtimes/pi/auth.js +1 -0
- package/dist/agent-runtimes/pi/model-catalog.js +2 -0
- package/dist/agent-runtimes/pi/runtime.js +2 -0
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/web-app.js +1 -0
- package/dist/apps/chat-ui/assets/{dist-fEoIZhiB.js → dist-BHiMQOG6.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CkvR8VSK.js → dist-BfNGI8zD.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bw_Uk-TC.js → dist-Cf5vD7GX.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CRCfZH6c.js → dist-Ds-8WrKd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Di_Ioy0T.js → dist-iE0b01WP.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-BDe_bSi9.js → index-BWhdVZpM.js} +86 -86
- package/dist/apps/chat-ui/assets/index-VZBlQTo5.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/{index-l2TxPbpk.js → index-CtaeZkpG.js} +4 -4
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/core/session-router.js +112 -3
- package/dist/data/chat-read-projections.js +46 -33
- package/dist/mcp/config.js +13 -48
- package/dist/providers/meta-muse.js +51 -0
- package/dist/runs/resource-isolation.js +3 -125
- package/dist/runs/windows-process-tree.js +132 -0
- package/dist/session-ui/terminalRows.js +29 -3
- package/dist/shared/trace-engine.js +2 -1
- package/dist/shared/trace-event-projection.js +80 -0
- package/dist/shared/trace-patch-nodes.js +14 -0
- package/dist/subagents/context.js +9 -3
- package/dist/subagents/observations.js +1 -1
- package/dist/subagents/tool.js +14 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/packages/workflows/dist/runtime/adapter-node.d.ts.map +1 -1
- package/packages/workflows/dist/runtime/adapter-node.js +1 -13
- package/packages/workflows/dist/runtime/adapter-node.js.map +1 -1
- package/packages/workflows/dist/runtime/dispatch-failures.d.ts +1 -0
- package/packages/workflows/dist/runtime/dispatch-failures.d.ts.map +1 -1
- package/packages/workflows/dist/runtime/dispatch-failures.js +12 -0
- package/packages/workflows/dist/runtime/dispatch-failures.js.map +1 -1
- package/packages/workflows/dist/runtime/edge-transfer.d.ts.map +1 -1
- package/packages/workflows/dist/runtime/edge-transfer.js +1 -12
- package/packages/workflows/dist/runtime/edge-transfer.js.map +1 -1
- package/dist/apps/chat-ui/assets/index-zmsI0NvB.css +0 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-CtaeZkpG.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-b18ZkEo0.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -7,6 +7,7 @@ import { InitialSessionContext, } from "./profiles.js";
|
|
|
7
7
|
import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault, selectDefaultPiboProfileName } from "../plugins/builtin.js";
|
|
8
8
|
import { RUN_REMINDER_MAX_DURATION_MS, RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-session.js";
|
|
9
9
|
import { runtimeSessionErrorDetails } from "./session-errors.js";
|
|
10
|
+
import { PiboSteeringUnavailableError, } from "./events.js";
|
|
10
11
|
import { OutputRenderSequencer, outputRenderHighWaterStore } from "./output-render-sequence.js";
|
|
11
12
|
import { normalizePiboAgentSessionName, } from "../subagents/tool.js";
|
|
12
13
|
import { piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationSourceFromEvent, piboAgentObservationText, } from "../subagents/observations.js";
|
|
@@ -1100,8 +1101,98 @@ export class PiboSessionRouter {
|
|
|
1100
1101
|
subscribeSignalStatuses(listener) {
|
|
1101
1102
|
return this.signalRegistry.subscribeAll(listener);
|
|
1102
1103
|
}
|
|
1104
|
+
async emitSteeringMessageAndWaitForReply(event, timeoutMs, signal) {
|
|
1105
|
+
return await new Promise((resolve, reject) => {
|
|
1106
|
+
let settled = false;
|
|
1107
|
+
let steeringAccepted = false;
|
|
1108
|
+
let targetEventId = this.sessions.get(event.piboSessionId)?.getStatus().activeEventId;
|
|
1109
|
+
let dispatchPromise;
|
|
1110
|
+
let timeout;
|
|
1111
|
+
const assistantMessages = new Map();
|
|
1112
|
+
const finishedEventIds = new Set();
|
|
1113
|
+
const sessionErrors = new Map();
|
|
1114
|
+
const claimSettlement = () => {
|
|
1115
|
+
if (settled)
|
|
1116
|
+
return false;
|
|
1117
|
+
settled = true;
|
|
1118
|
+
if (timeout)
|
|
1119
|
+
clearTimeout(timeout);
|
|
1120
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1121
|
+
unsubscribe();
|
|
1122
|
+
return true;
|
|
1123
|
+
};
|
|
1124
|
+
const finish = (result) => {
|
|
1125
|
+
if (!claimSettlement())
|
|
1126
|
+
return;
|
|
1127
|
+
if (result instanceof Error)
|
|
1128
|
+
reject(result);
|
|
1129
|
+
else
|
|
1130
|
+
resolve(result);
|
|
1131
|
+
};
|
|
1132
|
+
const finishTargetIfReady = () => {
|
|
1133
|
+
if (!steeringAccepted || !targetEventId)
|
|
1134
|
+
return;
|
|
1135
|
+
const error = sessionErrors.get(targetEventId);
|
|
1136
|
+
if (error) {
|
|
1137
|
+
finish(error);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
if (!finishedEventIds.has(targetEventId))
|
|
1141
|
+
return;
|
|
1142
|
+
finish(assistantMessages.get(targetEventId)
|
|
1143
|
+
?? new Error(`Pibo session "${event.piboSessionId}" finished without an assistant reply`));
|
|
1144
|
+
};
|
|
1145
|
+
const rejectAfterSteeringCancellation = (error) => {
|
|
1146
|
+
if (!claimSettlement())
|
|
1147
|
+
return;
|
|
1148
|
+
void Promise.resolve(dispatchPromise).then(() => reject(error), (dispatchError) => reject(dispatchError instanceof Error ? dispatchError : new Error(String(dispatchError))));
|
|
1149
|
+
};
|
|
1150
|
+
const onAbort = () => {
|
|
1151
|
+
rejectAfterSteeringCancellation(subagentAbortError());
|
|
1152
|
+
};
|
|
1153
|
+
const unsubscribe = this.subscribe((output) => {
|
|
1154
|
+
if (output.piboSessionId !== event.piboSessionId || !("eventId" in output) || !output.eventId)
|
|
1155
|
+
return;
|
|
1156
|
+
if (output.type === "assistant_message") {
|
|
1157
|
+
assistantMessages.set(output.eventId, output);
|
|
1158
|
+
}
|
|
1159
|
+
else if (output.type === "message_finished") {
|
|
1160
|
+
finishedEventIds.add(output.eventId);
|
|
1161
|
+
}
|
|
1162
|
+
else if (output.type === "session_error") {
|
|
1163
|
+
sessionErrors.set(output.eventId, new Error(output.error));
|
|
1164
|
+
}
|
|
1165
|
+
finishTargetIfReady();
|
|
1166
|
+
});
|
|
1167
|
+
if (signal?.aborted) {
|
|
1168
|
+
finish(subagentAbortError());
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1172
|
+
if (timeoutMs !== undefined) {
|
|
1173
|
+
timeout = setTimeout(() => {
|
|
1174
|
+
rejectAfterSteeringCancellation(new PiboRunExecutionTimeoutError(`Timed out waiting for assistant reply from Pibo session "${event.piboSessionId}"`, "lifetime"));
|
|
1175
|
+
}, timeoutMs);
|
|
1176
|
+
}
|
|
1177
|
+
dispatchPromise = this.emit(event);
|
|
1178
|
+
dispatchPromise.then((output) => {
|
|
1179
|
+
if (output.type !== "message_steered" || !output.activeEventId) {
|
|
1180
|
+
finish(new Error(`Pibo session "${event.piboSessionId}" did not identify the active turn for steering`));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
targetEventId = output.activeEventId;
|
|
1184
|
+
steeringAccepted = true;
|
|
1185
|
+
finishTargetIfReady();
|
|
1186
|
+
}).catch((error) => {
|
|
1187
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
1188
|
+
});
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1103
1191
|
async emitMessageAndWaitForReply(event, timeoutMs, signal) {
|
|
1104
1192
|
const eventWithId = { ...event, id: event.id ?? randomUUID() };
|
|
1193
|
+
if (eventWithId.delivery === "steer") {
|
|
1194
|
+
return await this.emitSteeringMessageAndWaitForReply(eventWithId, timeoutMs, signal);
|
|
1195
|
+
}
|
|
1105
1196
|
return await new Promise((resolve, reject) => {
|
|
1106
1197
|
let settled = false;
|
|
1107
1198
|
let dispatchPromise;
|
|
@@ -2121,7 +2212,7 @@ export class PiboSessionRouter {
|
|
|
2121
2212
|
}
|
|
2122
2213
|
createAgentsController(parentPiboSessionId) {
|
|
2123
2214
|
return {
|
|
2124
|
-
sendMessage: async ({ subagent, sessionName, message, threadKey, toolCallId, requestId, parentProvenance, signal }) => {
|
|
2215
|
+
sendMessage: async ({ subagent, sessionName, message, threadKey, queue, toolCallId, requestId, parentProvenance, signal }) => {
|
|
2125
2216
|
if (signal?.aborted)
|
|
2126
2217
|
throw subagentAbortError();
|
|
2127
2218
|
if (typeof requestId !== "string" || !requestId.trim())
|
|
@@ -2140,10 +2231,14 @@ export class PiboSessionRouter {
|
|
|
2140
2231
|
: parentProvenance?.kind === "subagent-request"
|
|
2141
2232
|
? parentProvenance.loopRunId
|
|
2142
2233
|
: undefined;
|
|
2143
|
-
|
|
2234
|
+
let delivery = queue === true || !this.sessions.get(child.id)?.canSteerMessage()
|
|
2235
|
+
? "queue"
|
|
2236
|
+
: "steer";
|
|
2237
|
+
let event = {
|
|
2144
2238
|
type: "message",
|
|
2145
2239
|
piboSessionId: child.id,
|
|
2146
2240
|
text: message,
|
|
2241
|
+
delivery,
|
|
2147
2242
|
source: "actor",
|
|
2148
2243
|
id: randomUUID(),
|
|
2149
2244
|
provenance: {
|
|
@@ -2181,7 +2276,19 @@ export class PiboSessionRouter {
|
|
|
2181
2276
|
});
|
|
2182
2277
|
let settlement = { status: "fulfilled" };
|
|
2183
2278
|
try {
|
|
2184
|
-
|
|
2279
|
+
let reply;
|
|
2280
|
+
try {
|
|
2281
|
+
reply = await this.emitMessageAndWaitForReply(event, undefined, requestSignal);
|
|
2282
|
+
}
|
|
2283
|
+
catch (error) {
|
|
2284
|
+
if (delivery !== "steer" || !(error instanceof PiboSteeringUnavailableError) || requestSignal.aborted)
|
|
2285
|
+
throw error;
|
|
2286
|
+
this.subagentRequestIdsByEvent.delete(subagentRequestEventKey(child.id, event.id));
|
|
2287
|
+
delivery = "queue";
|
|
2288
|
+
event = { ...event, id: randomUUID(), delivery };
|
|
2289
|
+
this.subagentRequestIdsByEvent.set(subagentRequestEventKey(child.id, event.id), requestId);
|
|
2290
|
+
reply = await this.emitMessageAndWaitForReply(event, undefined, requestSignal);
|
|
2291
|
+
}
|
|
2185
2292
|
return {
|
|
2186
2293
|
requestId,
|
|
2187
2294
|
agentId: child.id,
|
|
@@ -2189,6 +2296,8 @@ export class PiboSessionRouter {
|
|
|
2189
2296
|
profile: child.profile,
|
|
2190
2297
|
threadKey: resolvedThreadKey,
|
|
2191
2298
|
eventId: event.id,
|
|
2299
|
+
delivery,
|
|
2300
|
+
...(delivery === "steer" && reply.eventId ? { activeEventId: reply.eventId } : {}),
|
|
2192
2301
|
finalMessage: reply.text,
|
|
2193
2302
|
reply,
|
|
2194
2303
|
};
|
|
@@ -85,10 +85,18 @@ export class ChatReadProjectionStore {
|
|
|
85
85
|
this.db = db;
|
|
86
86
|
}
|
|
87
87
|
status() {
|
|
88
|
-
const
|
|
89
|
-
return {
|
|
88
|
+
const row = this.db.prepare("SELECT cursor,target,event_cursor,event_target,paused FROM chat_read_backfill WHERE id=1").get();
|
|
89
|
+
return {
|
|
90
|
+
...row,
|
|
91
|
+
paused: Boolean(row.paused),
|
|
92
|
+
historyComplete: row.cursor >= row.target,
|
|
93
|
+
unreadComplete: row.event_cursor >= row.event_target,
|
|
94
|
+
complete: row.cursor >= row.target && row.event_cursor >= row.event_target,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
setPaused(paused) {
|
|
98
|
+
this.db.prepare("UPDATE chat_read_backfill SET paused=? WHERE id=1").run(paused ? 1 : 0);
|
|
90
99
|
}
|
|
91
|
-
setPaused(paused) { this.db.prepare("UPDATE chat_read_backfill SET paused=? WHERE id=1").run(paused ? 1 : 0); }
|
|
92
100
|
step(limit = 128, maxMs = 4) {
|
|
93
101
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 512)
|
|
94
102
|
throw Error("Backfill row budget must be 1..512");
|
|
@@ -102,36 +110,9 @@ export class ChatReadProjectionStore {
|
|
|
102
110
|
this.db.exec("COMMIT");
|
|
103
111
|
return { ...current, processed: 0 };
|
|
104
112
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
let processed = 0;
|
|
109
|
-
const started = performance.now();
|
|
110
|
-
for (const row of rows) {
|
|
111
|
-
if (row.session_id)
|
|
112
|
-
insert.run(row.stream_id, row.session_id);
|
|
113
|
-
processed++;
|
|
114
|
-
if (performance.now() - started >= maxMs)
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
const cursor = processed === rows.length && rows.length < limit ? current.event_target : rows[processed - 1].stream_id;
|
|
118
|
-
this.db.prepare("UPDATE chat_read_backfill SET event_cursor=? WHERE id=1").run(cursor);
|
|
119
|
-
this.db.exec("COMMIT");
|
|
120
|
-
return { ...this.status(), processed };
|
|
121
|
-
}
|
|
122
|
-
const rows = this.db.prepare("SELECT rowid AS cursor,id FROM chat_messages WHERE rowid>? AND rowid<=? ORDER BY rowid LIMIT ?").all(current.cursor, current.target, limit);
|
|
123
|
-
const insert = this.db.prepare(`INSERT OR IGNORE INTO chat_history_index(message_id,session_id,event_sequence,source_stream_id,role,created_at)
|
|
124
|
-
SELECT m.id,m.session_id,COALESCE(e.session_sequence,m.sequence),m.source_stream_id,m.role,m.created_at FROM chat_messages m LEFT JOIN event_log e ON e.stream_id=m.source_stream_id WHERE m.id=?`);
|
|
125
|
-
let processed = 0;
|
|
126
|
-
const started = performance.now();
|
|
127
|
-
for (const row of rows) {
|
|
128
|
-
insert.run(row.id);
|
|
129
|
-
processed++;
|
|
130
|
-
if (performance.now() - started >= maxMs)
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
const cursor = processed === rows.length && rows.length < limit ? current.target : rows[processed - 1].cursor;
|
|
134
|
-
this.db.prepare("UPDATE chat_read_backfill SET cursor=? WHERE id=1").run(cursor);
|
|
113
|
+
const processed = current.historyComplete
|
|
114
|
+
? this.backfillUnread(current.event_cursor, current.event_target, limit, maxMs)
|
|
115
|
+
: this.backfillHistory(current.cursor, current.target, limit, maxMs);
|
|
135
116
|
this.db.exec("COMMIT");
|
|
136
117
|
return { ...this.status(), processed };
|
|
137
118
|
}
|
|
@@ -140,6 +121,38 @@ export class ChatReadProjectionStore {
|
|
|
140
121
|
throw error;
|
|
141
122
|
}
|
|
142
123
|
}
|
|
124
|
+
backfillUnread(cursor, target, limit, maxMs) {
|
|
125
|
+
const rows = this.db.prepare("SELECT stream_id,session_id FROM event_log INDEXED BY idx_event_log_unread_stream WHERE stream_id>? AND stream_id<=? AND type='message_finished' ORDER BY stream_id LIMIT ?").all(cursor, target, limit);
|
|
126
|
+
const insert = this.db.prepare("INSERT OR IGNORE INTO chat_unread_index VALUES(?,?)");
|
|
127
|
+
let processed = 0;
|
|
128
|
+
const started = performance.now();
|
|
129
|
+
for (const row of rows) {
|
|
130
|
+
if (row.session_id)
|
|
131
|
+
insert.run(row.stream_id, row.session_id);
|
|
132
|
+
processed++;
|
|
133
|
+
if (performance.now() - started >= maxMs)
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
const nextCursor = processed === rows.length && rows.length < limit ? target : rows[processed - 1].stream_id;
|
|
137
|
+
this.db.prepare("UPDATE chat_read_backfill SET event_cursor=? WHERE id=1").run(nextCursor);
|
|
138
|
+
return processed;
|
|
139
|
+
}
|
|
140
|
+
backfillHistory(cursor, target, limit, maxMs) {
|
|
141
|
+
const rows = this.db.prepare("SELECT rowid AS cursor,id FROM chat_messages WHERE rowid>? AND rowid<=? ORDER BY rowid LIMIT ?").all(cursor, target, limit);
|
|
142
|
+
const insert = this.db.prepare(`INSERT OR IGNORE INTO chat_history_index(message_id,session_id,event_sequence,source_stream_id,role,created_at)
|
|
143
|
+
SELECT m.id,m.session_id,COALESCE(e.session_sequence,m.sequence),m.source_stream_id,m.role,m.created_at FROM chat_messages m LEFT JOIN event_log e ON e.stream_id=m.source_stream_id WHERE m.id=?`);
|
|
144
|
+
let processed = 0;
|
|
145
|
+
const started = performance.now();
|
|
146
|
+
for (const row of rows) {
|
|
147
|
+
insert.run(row.id);
|
|
148
|
+
processed++;
|
|
149
|
+
if (performance.now() - started >= maxMs)
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
const nextCursor = processed === rows.length && rows.length < limit ? target : rows[processed - 1].cursor;
|
|
153
|
+
this.db.prepare("UPDATE chat_read_backfill SET cursor=? WHERE id=1").run(nextCursor);
|
|
154
|
+
return processed;
|
|
155
|
+
}
|
|
143
156
|
}
|
|
144
157
|
export const CHAT_NAVIGATION_REVISION_SCHEMA = `
|
|
145
158
|
CREATE INDEX IF NOT EXISTS idx_sessions_navigation_cursor ON sessions(room_id,id) WHERE deleted_at IS NULL;
|
package/dist/mcp/config.js
CHANGED
|
@@ -113,61 +113,40 @@ export function debug(message) {
|
|
|
113
113
|
console.error(`[pibo mcp] ${message}`);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
function readIntegerEnv(name, fallback, minimum = 1) {
|
|
117
|
+
const value = process.env[name];
|
|
118
|
+
if (!value)
|
|
119
|
+
return fallback;
|
|
120
|
+
const parsed = Number.parseInt(value, 10);
|
|
121
|
+
return !Number.isNaN(parsed) && parsed >= minimum ? parsed : fallback;
|
|
122
|
+
}
|
|
116
123
|
/**
|
|
117
124
|
* Get configured timeout in milliseconds
|
|
118
125
|
* @env MCP_TIMEOUT - timeout in seconds (default: 1800 = 30 minutes)
|
|
119
126
|
*/
|
|
120
127
|
export function getTimeoutMs() {
|
|
121
|
-
|
|
122
|
-
if (envTimeout) {
|
|
123
|
-
const seconds = Number.parseInt(envTimeout, 10);
|
|
124
|
-
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
125
|
-
return seconds * 1000;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return DEFAULT_TIMEOUT_MS;
|
|
128
|
+
return readIntegerEnv('MCP_TIMEOUT', DEFAULT_TIMEOUT_SECONDS) * 1000;
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* Get concurrency limit for parallel server connections
|
|
132
132
|
* @env MCP_CONCURRENCY - max parallel connections (default: 5)
|
|
133
133
|
*/
|
|
134
134
|
export function getConcurrencyLimit() {
|
|
135
|
-
|
|
136
|
-
if (envConcurrency) {
|
|
137
|
-
const limit = Number.parseInt(envConcurrency, 10);
|
|
138
|
-
if (!Number.isNaN(limit) && limit > 0) {
|
|
139
|
-
return limit;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return DEFAULT_CONCURRENCY;
|
|
135
|
+
return readIntegerEnv('MCP_CONCURRENCY', DEFAULT_CONCURRENCY);
|
|
143
136
|
}
|
|
144
137
|
/**
|
|
145
138
|
* Get max retry attempts for transient failures
|
|
146
139
|
* @env MCP_MAX_RETRIES - max retry attempts (default: 3, use 0 to disable retries)
|
|
147
140
|
*/
|
|
148
141
|
export function getMaxRetries() {
|
|
149
|
-
|
|
150
|
-
if (envRetries) {
|
|
151
|
-
const retries = Number.parseInt(envRetries, 10);
|
|
152
|
-
if (!Number.isNaN(retries) && retries >= 0) {
|
|
153
|
-
return retries;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return DEFAULT_MAX_RETRIES;
|
|
142
|
+
return readIntegerEnv('MCP_MAX_RETRIES', DEFAULT_MAX_RETRIES, 0);
|
|
157
143
|
}
|
|
158
144
|
/**
|
|
159
145
|
* Get base delay for retry backoff in milliseconds
|
|
160
146
|
* @env MCP_RETRY_DELAY - base delay in milliseconds (default: 1000)
|
|
161
147
|
*/
|
|
162
148
|
export function getRetryDelayMs() {
|
|
163
|
-
|
|
164
|
-
if (envDelay) {
|
|
165
|
-
const delay = Number.parseInt(envDelay, 10);
|
|
166
|
-
if (!Number.isNaN(delay) && delay > 0) {
|
|
167
|
-
return delay;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return DEFAULT_RETRY_DELAY_MS;
|
|
149
|
+
return readIntegerEnv('MCP_RETRY_DELAY', DEFAULT_RETRY_DELAY_MS);
|
|
171
150
|
}
|
|
172
151
|
// ============================================================================
|
|
173
152
|
// Daemon Configuration
|
|
@@ -184,28 +163,14 @@ export function isDaemonEnabled() {
|
|
|
184
163
|
* @env MCP_DAEMON_TIMEOUT - timeout in seconds (default: 60)
|
|
185
164
|
*/
|
|
186
165
|
export function getDaemonTimeoutMs() {
|
|
187
|
-
|
|
188
|
-
if (envTimeout) {
|
|
189
|
-
const seconds = Number.parseInt(envTimeout, 10);
|
|
190
|
-
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
191
|
-
return seconds * 1000;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return DEFAULT_DAEMON_TIMEOUT_SECONDS * 1000;
|
|
166
|
+
return readIntegerEnv('MCP_DAEMON_TIMEOUT', DEFAULT_DAEMON_TIMEOUT_SECONDS) * 1000;
|
|
195
167
|
}
|
|
196
168
|
/**
|
|
197
169
|
* Get daemon request timeout in milliseconds
|
|
198
170
|
* @env MCP_DAEMON_REQUEST_TIMEOUT - timeout in seconds (default: 60)
|
|
199
171
|
*/
|
|
200
172
|
export function getDaemonRequestTimeoutMs() {
|
|
201
|
-
|
|
202
|
-
if (envTimeout) {
|
|
203
|
-
const seconds = Number.parseInt(envTimeout, 10);
|
|
204
|
-
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
205
|
-
return seconds * 1000;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return DEFAULT_DAEMON_REQUEST_TIMEOUT_SECONDS * 1000;
|
|
173
|
+
return readIntegerEnv('MCP_DAEMON_REQUEST_TIMEOUT', DEFAULT_DAEMON_REQUEST_TIMEOUT_SECONDS) * 1000;
|
|
209
174
|
}
|
|
210
175
|
function getDaemonUserKey(platform) {
|
|
211
176
|
const uid = process.getuid?.();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { registerOpenAiCompatProvider, } from "./openai-compat.js";
|
|
2
|
+
export const META_MUSE_PROVIDER_ID = "meta-muse";
|
|
3
|
+
export const META_MUSE_PROVIDER_NAME = "Meta Muse";
|
|
4
|
+
export const META_MUSE_DEFAULT_BASE_URL = "https://api.meta.ai/v1";
|
|
5
|
+
export const META_MUSE_API_KEY_ENV = "META_API_KEY";
|
|
6
|
+
const COMPAT = {
|
|
7
|
+
supportsStore: false,
|
|
8
|
+
maxTokensField: "max_tokens",
|
|
9
|
+
};
|
|
10
|
+
const REASONING_LEVELS = {
|
|
11
|
+
off: null,
|
|
12
|
+
minimal: "minimal",
|
|
13
|
+
low: "low",
|
|
14
|
+
medium: "medium",
|
|
15
|
+
high: "high",
|
|
16
|
+
xhigh: "xhigh",
|
|
17
|
+
max: "max",
|
|
18
|
+
};
|
|
19
|
+
export const META_MUSE_MODELS = [
|
|
20
|
+
{
|
|
21
|
+
id: "muse-spark-1.3",
|
|
22
|
+
name: "Muse Spark 1.3",
|
|
23
|
+
reasoning: true,
|
|
24
|
+
thinkingLevelMap: REASONING_LEVELS,
|
|
25
|
+
compat: COMPAT,
|
|
26
|
+
contextWindow: 1_000_000,
|
|
27
|
+
maxTokens: 131_072,
|
|
28
|
+
cost: { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
|
|
29
|
+
input: ["text", "image"],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "muse-spark-1.3-contributor",
|
|
33
|
+
name: "Muse Spark 1.3 Contributor",
|
|
34
|
+
reasoning: true,
|
|
35
|
+
thinkingLevelMap: { ...REASONING_LEVELS, max: null },
|
|
36
|
+
compat: COMPAT,
|
|
37
|
+
contextWindow: 1_000_000,
|
|
38
|
+
maxTokens: 131_072,
|
|
39
|
+
cost: { input: 0.10, output: 0.20, cacheRead: 0.002, cacheWrite: 0 },
|
|
40
|
+
input: ["text", "image"],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
export function registerMetaMuseProvider(modelRegistry) {
|
|
44
|
+
return registerOpenAiCompatProvider(modelRegistry, {
|
|
45
|
+
id: META_MUSE_PROVIDER_ID,
|
|
46
|
+
name: META_MUSE_PROVIDER_NAME,
|
|
47
|
+
baseUrl: process.env.PIBO_META_MUSE_BASE_URL || META_MUSE_DEFAULT_BASE_URL,
|
|
48
|
+
apiKeyEnv: META_MUSE_API_KEY_ENV,
|
|
49
|
+
models: META_MUSE_MODELS,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -4,6 +4,8 @@ import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
+
import { removeWindowsProcessIdentity, terminateWindowsProcessTree, windowsProcessTreeCommand } from "./windows-process-tree.js";
|
|
8
|
+
export { windowsProcessTreeCommand } from "./windows-process-tree.js";
|
|
7
9
|
const execFileAsync = promisify(execFile);
|
|
8
10
|
export class PiboRunResourceLimitError extends Error {
|
|
9
11
|
resources;
|
|
@@ -112,7 +114,7 @@ export function prepareYieldedRunExecution(toolName, params, options = {}) {
|
|
|
112
114
|
return await operation();
|
|
113
115
|
}
|
|
114
116
|
finally {
|
|
115
|
-
|
|
117
|
+
removeWindowsProcessIdentity(windowsPidPath);
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
if (!shouldUseSystemd || !unitName)
|
|
@@ -145,130 +147,6 @@ export function prepareYieldedRunExecution(toolName, params, options = {}) {
|
|
|
145
147
|
},
|
|
146
148
|
};
|
|
147
149
|
}
|
|
148
|
-
export function windowsProcessTreeCommand(command, pidPath) {
|
|
149
|
-
const portablePidPath = pidPath.replaceAll("\\", "/");
|
|
150
|
-
return [
|
|
151
|
-
"__pibo_win_pid=\"$(ps -W | awk -v p=$$ 'NR > 1 && $1 == p { print $4; exit }')\"",
|
|
152
|
-
"if [ -z \"$__pibo_win_pid\" ]; then printf '%s\\n' 'Pibo could not identify the Windows Bash process.' >&2; exit 125; fi",
|
|
153
|
-
`printf '%s %s' "$$" "$__pibo_win_pid" > ${bashSingleQuote(portablePidPath)}`,
|
|
154
|
-
"unset __pibo_win_pid",
|
|
155
|
-
command,
|
|
156
|
-
].join("\n");
|
|
157
|
-
}
|
|
158
|
-
async function terminateWindowsProcessTree(pidPath) {
|
|
159
|
-
const identity = await waitForWindowsProcessIdentity(pidPath);
|
|
160
|
-
if (!identity)
|
|
161
|
-
return;
|
|
162
|
-
try {
|
|
163
|
-
const rows = await listWindowsProcessTree(identity.msysPid);
|
|
164
|
-
for (const row of rows) {
|
|
165
|
-
try {
|
|
166
|
-
process.kill(row.windowsPid, "SIGKILL");
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
// The process may have exited between the snapshot and termination.
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
for (let attempt = 0; attempt < 100 && processIsAlive(identity.windowsPid); attempt += 1) {
|
|
173
|
-
await new Promise((resolveWait) => setTimeout(resolveWait, 25));
|
|
174
|
-
}
|
|
175
|
-
if (processIsAlive(identity.windowsPid)) {
|
|
176
|
-
throw new Error(`Windows yielded-run process tree rooted at PID ${identity.windowsPid} is still active after termination.`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
finally {
|
|
180
|
-
unlinkOptional(pidPath);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
async function waitForWindowsProcessIdentity(pidPath) {
|
|
184
|
-
for (let attempt = 0; attempt < 80; attempt += 1) {
|
|
185
|
-
try {
|
|
186
|
-
const match = readFileSync(pidPath, "utf8").trim().match(/^([1-9]\d*)\s+([1-9]\d*)$/);
|
|
187
|
-
if (match)
|
|
188
|
-
return { msysPid: Number(match[1]), windowsPid: Number(match[2]) };
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// The Bash wrapper may still be starting.
|
|
192
|
-
}
|
|
193
|
-
await new Promise((resolveWait) => setTimeout(resolveWait, 25));
|
|
194
|
-
}
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
197
|
-
async function listWindowsProcessTree(rootMsysPid) {
|
|
198
|
-
const { stdout } = await execFileAsync(windowsBashExecutable(), ["-lc", "ps -W"], {
|
|
199
|
-
encoding: "utf8",
|
|
200
|
-
timeout: 5_000,
|
|
201
|
-
windowsHide: true,
|
|
202
|
-
});
|
|
203
|
-
const rows = stdout.split(/\r?\n/).flatMap((line) => {
|
|
204
|
-
const fields = line.trim().split(/\s+/);
|
|
205
|
-
if (fields.length < 4 || !fields.slice(0, 4).every((field) => /^\d+$/.test(field)))
|
|
206
|
-
return [];
|
|
207
|
-
return [{
|
|
208
|
-
msysPid: Number(fields[0]),
|
|
209
|
-
parentMsysPid: Number(fields[1]),
|
|
210
|
-
processGroupId: Number(fields[2]),
|
|
211
|
-
windowsPid: Number(fields[3]),
|
|
212
|
-
}];
|
|
213
|
-
});
|
|
214
|
-
const selected = new Set([rootMsysPid]);
|
|
215
|
-
for (let changed = true; changed;) {
|
|
216
|
-
changed = false;
|
|
217
|
-
for (const row of rows) {
|
|
218
|
-
if (selected.has(row.msysPid))
|
|
219
|
-
continue;
|
|
220
|
-
if (row.processGroupId === rootMsysPid || selected.has(row.parentMsysPid)) {
|
|
221
|
-
selected.add(row.msysPid);
|
|
222
|
-
changed = true;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return rows
|
|
227
|
-
.filter((row) => selected.has(row.msysPid) && row.windowsPid > 0)
|
|
228
|
-
.sort((left, right) => processTreeDepth(right, rows) - processTreeDepth(left, rows));
|
|
229
|
-
}
|
|
230
|
-
function processTreeDepth(row, rows) {
|
|
231
|
-
const byPid = new Map(rows.map((candidate) => [candidate.msysPid, candidate]));
|
|
232
|
-
let depth = 0;
|
|
233
|
-
let current = row;
|
|
234
|
-
const seen = new Set();
|
|
235
|
-
while (!seen.has(current.msysPid)) {
|
|
236
|
-
seen.add(current.msysPid);
|
|
237
|
-
const parent = byPid.get(current.parentMsysPid);
|
|
238
|
-
if (!parent)
|
|
239
|
-
break;
|
|
240
|
-
depth += 1;
|
|
241
|
-
current = parent;
|
|
242
|
-
}
|
|
243
|
-
return depth;
|
|
244
|
-
}
|
|
245
|
-
function windowsBashExecutable() {
|
|
246
|
-
const candidates = [
|
|
247
|
-
process.env.ProgramFiles ? join(process.env.ProgramFiles, "Git", "bin", "bash.exe") : undefined,
|
|
248
|
-
process.env["ProgramFiles(x86)"] ? join(process.env["ProgramFiles(x86)"], "Git", "bin", "bash.exe") : undefined,
|
|
249
|
-
].filter((candidate) => Boolean(candidate));
|
|
250
|
-
return candidates.find((candidate) => existsSync(candidate)) ?? "bash.exe";
|
|
251
|
-
}
|
|
252
|
-
function processIsAlive(pid) {
|
|
253
|
-
try {
|
|
254
|
-
process.kill(pid, 0);
|
|
255
|
-
return true;
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function unlinkOptional(path) {
|
|
262
|
-
try {
|
|
263
|
-
unlinkSync(path);
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
// Best-effort transient process metadata cleanup.
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
function bashSingleQuote(value) {
|
|
270
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
271
|
-
}
|
|
272
150
|
export function systemdRunCommand(command, unitName, policy, metricsPath = `/tmp/${unitName}.metrics`) {
|
|
273
151
|
const captureScript = [
|
|
274
152
|
'/bin/bash -c "$1"',
|