@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.
Files changed (45) hide show
  1. package/dist/agent-runtime/auth-contract.js +215 -0
  2. package/dist/agent-runtime/registry.js +1 -214
  3. package/dist/agent-runtime/routed-session.js +7 -0
  4. package/dist/agent-runtimes/codex-native/turn.js +2 -2
  5. package/dist/agent-runtimes/pi/auth.js +1 -0
  6. package/dist/agent-runtimes/pi/model-catalog.js +2 -0
  7. package/dist/agent-runtimes/pi/runtime.js +2 -0
  8. package/dist/apps/chat/trace-v2.js +1 -0
  9. package/dist/apps/chat/web-app.js +1 -0
  10. package/dist/apps/chat-ui/assets/{dist-fEoIZhiB.js → dist-BHiMQOG6.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-CkvR8VSK.js → dist-BfNGI8zD.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-Bw_Uk-TC.js → dist-Cf5vD7GX.js} +1 -1
  13. package/dist/apps/chat-ui/assets/{dist-CRCfZH6c.js → dist-Ds-8WrKd.js} +1 -1
  14. package/dist/apps/chat-ui/assets/{dist-Di_Ioy0T.js → dist-iE0b01WP.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{index-BDe_bSi9.js → index-BWhdVZpM.js} +86 -86
  16. package/dist/apps/chat-ui/assets/index-VZBlQTo5.css +1 -0
  17. package/dist/apps/chat-ui/index.html +2 -2
  18. package/dist/apps/chat-vscode-web/assets/{index-l2TxPbpk.js → index-CtaeZkpG.js} +4 -4
  19. package/dist/apps/chat-vscode-web/index.html +1 -1
  20. package/dist/core/session-router.js +112 -3
  21. package/dist/data/chat-read-projections.js +46 -33
  22. package/dist/mcp/config.js +13 -48
  23. package/dist/providers/meta-muse.js +51 -0
  24. package/dist/runs/resource-isolation.js +3 -125
  25. package/dist/runs/windows-process-tree.js +132 -0
  26. package/dist/session-ui/terminalRows.js +29 -3
  27. package/dist/shared/trace-engine.js +2 -1
  28. package/dist/shared/trace-event-projection.js +80 -0
  29. package/dist/shared/trace-patch-nodes.js +14 -0
  30. package/dist/subagents/context.js +9 -3
  31. package/dist/subagents/observations.js +1 -1
  32. package/dist/subagents/tool.js +14 -6
  33. package/npm-shrinkwrap.json +2 -2
  34. package/package.json +1 -1
  35. package/packages/workflows/dist/runtime/adapter-node.d.ts.map +1 -1
  36. package/packages/workflows/dist/runtime/adapter-node.js +1 -13
  37. package/packages/workflows/dist/runtime/adapter-node.js.map +1 -1
  38. package/packages/workflows/dist/runtime/dispatch-failures.d.ts +1 -0
  39. package/packages/workflows/dist/runtime/dispatch-failures.d.ts.map +1 -1
  40. package/packages/workflows/dist/runtime/dispatch-failures.js +12 -0
  41. package/packages/workflows/dist/runtime/dispatch-failures.js.map +1 -1
  42. package/packages/workflows/dist/runtime/edge-transfer.d.ts.map +1 -1
  43. package/packages/workflows/dist/runtime/edge-transfer.js +1 -12
  44. package/packages/workflows/dist/runtime/edge-transfer.js.map +1 -1
  45. 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-l2TxPbpk.js"></script>
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
- const event = {
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
- const reply = await this.emitMessageAndWaitForReply(event, undefined, requestSignal);
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 r = this.db.prepare("SELECT cursor,target,event_cursor,event_target,paused FROM chat_read_backfill WHERE id=1").get();
89
- return { ...r, paused: Boolean(r.paused), historyComplete: r.cursor >= r.target, unreadComplete: r.event_cursor >= r.event_target, complete: r.cursor >= r.target && r.event_cursor >= r.event_target };
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
- if (current.historyComplete) {
106
- 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(current.event_cursor, current.event_target, limit);
107
- const insert = this.db.prepare("INSERT OR IGNORE INTO chat_unread_index VALUES(?,?)");
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;
@@ -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
- const envTimeout = process.env.MCP_TIMEOUT;
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
- const envConcurrency = process.env.MCP_CONCURRENCY;
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
- const envRetries = process.env.MCP_MAX_RETRIES;
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
- const envDelay = process.env.MCP_RETRY_DELAY;
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
- const envTimeout = process.env.MCP_DAEMON_TIMEOUT;
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
- const envTimeout = process.env.MCP_DAEMON_REQUEST_TIMEOUT;
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
- unlinkOptional(windowsPidPath);
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"',