@pasko70/pibo 1.13.0 → 1.14.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.
Files changed (51) hide show
  1. package/dist/apps/chat/static-assets.js +3 -1
  2. package/dist/apps/chat/web-app.js +10 -0
  3. package/dist/apps/chat-ui/assets/{dist-B_8EcoB6.js → dist-1eQpSfYj.js} +1 -1
  4. package/dist/apps/chat-ui/assets/{dist-DVPXynJE.js → dist-BG1J2hGI.js} +1 -1
  5. package/dist/apps/chat-ui/assets/{dist-UYBI2F8E.js → dist-BIwXuAMQ.js} +1 -1
  6. package/dist/apps/{context-files-ui/assets/dist-D6pYe6d_.js → chat-ui/assets/dist-BeCHnL7P.js} +1 -1
  7. package/dist/apps/chat-ui/assets/{dist-CVLKu7NV.js → dist-CtsdsIpj.js} +1 -1
  8. package/dist/apps/chat-ui/assets/{dist-DugRsLaG.js → dist-DfdQQObC.js} +1 -1
  9. package/dist/apps/{context-files-ui/assets/dist-BFRDpzcv.js → chat-ui/assets/dist-DmqwP_tM.js} +1 -1
  10. package/dist/apps/{context-files-ui/assets/dist-B5mpygOZ.js → chat-ui/assets/dist-LWXXqiGT.js} +1 -1
  11. package/dist/apps/{context-files-ui/assets/dist-B_zXPNMb.js → chat-ui/assets/dist-hoDORUlL.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-XmG4qG-A.js → dist-ur4yXrlj.js} +1 -1
  13. package/dist/apps/chat-ui/assets/index-CoCl8FG8.css +1 -0
  14. package/dist/apps/chat-ui/assets/index-DLNTzhdh.js +237 -0
  15. package/dist/apps/chat-ui/index.html +2 -2
  16. package/dist/apps/chat-vscode-web/assets/index-R_7kgvvD.js +41 -0
  17. package/dist/apps/chat-vscode-web/index.html +1 -1
  18. package/dist/apps/{chat-ui/assets/dist-CnX234jg.js → context-files-ui/assets/dist-B7gXtTZC.js} +1 -1
  19. package/dist/apps/{chat-ui/assets/dist-CELRUR8b.js → context-files-ui/assets/dist-BD262Ouw.js} +1 -1
  20. package/dist/apps/context-files-ui/assets/{dist-Em3LWmNi.js → dist-BKuGdcnX.js} +1 -1
  21. package/dist/apps/{chat-ui/assets/dist-CVmpu9FK.js → context-files-ui/assets/dist-C-Jm45P4.js} +1 -1
  22. package/dist/apps/context-files-ui/assets/{dist-DM6yvMZv.js → dist-CHdx1Jri.js} +1 -1
  23. package/dist/apps/context-files-ui/assets/{dist-BLI6sQZG.js → dist-CULMH-fB.js} +1 -1
  24. package/dist/apps/context-files-ui/assets/{dist-CLrlJjPP.js → dist-Cjq6RWEt.js} +1 -1
  25. package/dist/apps/context-files-ui/assets/{dist-D8ppb7gk.js → dist-CntqFySz.js} +1 -1
  26. package/dist/apps/context-files-ui/assets/{dist-SEP88Pg7.js → dist-DxEGebCR.js} +1 -1
  27. package/dist/apps/{chat-ui/assets/dist-KOXty1JB.js → context-files-ui/assets/dist-kPuQud7t.js} +1 -1
  28. package/dist/apps/context-files-ui/assets/index-DZWsbuvg.js +202 -0
  29. package/dist/apps/context-files-ui/index.html +1 -1
  30. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  31. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.14.0.vsix +0 -0
  32. package/dist/cli-session/localSessionSource.js +2 -0
  33. package/dist/core/session-router.js +3 -0
  34. package/dist/gateway/client.js +8 -0
  35. package/dist/gateway/server.js +18 -2
  36. package/dist/gateway/web.js +1 -0
  37. package/dist/local/extension.js +2 -0
  38. package/dist/loops/plugin.js +34 -1
  39. package/dist/loops/service.js +85 -0
  40. package/dist/mcp/config-command.js +5 -3
  41. package/dist/plugins/context-files-store.js +287 -101
  42. package/dist/plugins/context-files.js +102 -154
  43. package/dist/shared/trace-live-reducer.js +6 -1
  44. package/dist/web/channel.js +105 -16
  45. package/dist/web/http.js +14 -2
  46. package/package.json +11 -7
  47. package/dist/apps/chat-ui/assets/index-DhFQ1th-.js +0 -237
  48. package/dist/apps/chat-ui/assets/index-hjygk9Ol.css +0 -1
  49. package/dist/apps/chat-vscode-web/assets/index-1wWonouX.js +0 -41
  50. package/dist/apps/context-files-ui/assets/index-C7X2UvWe.js +0 -202
  51. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.13.0.vsix +0 -0
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Pibo Context Files</title>
7
- <script type="module" crossorigin src="/apps/context-files/assets/index-C7X2UvWe.js"></script>
7
+ <script type="module" crossorigin src="/apps/context-files/assets/index-DZWsbuvg.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/apps/context-files/assets/dist-D1KyNDg5.js">
9
9
  <link rel="stylesheet" crossorigin href="/apps/context-files/assets/index-Cs4XQITe.css">
10
10
  </head>
@@ -1050,6 +1050,8 @@ function executionParamsForSlash(command, args) {
1050
1050
  const trimmed = args?.trim();
1051
1051
  if (command === "compact" && trimmed)
1052
1052
  return { customInstructions: trimmed };
1053
+ if (command === "goal")
1054
+ return { command: trimmed ?? "" };
1053
1055
  if (command === "thinking" && trimmed)
1054
1056
  return { level: trimmed };
1055
1057
  if (command === "model" && trimmed) {
@@ -210,6 +210,9 @@ export class PiboSessionRouter {
210
210
  recoveredRuns: this.runRegistry.listRecoveredRuns(),
211
211
  }) ?? []
212
212
  : [];
213
+ if (recoveredRuntimeState.length > 0 && options.runtimeInstanceId) {
214
+ console.error(`[pibo] authoritative runtime ${options.runtimeInstanceId} recovered ${recoveredRuntimeState.length} interrupted turn(s)`);
215
+ }
213
216
  for (const recovery of recoveredRuntimeState) {
214
217
  const session = this.sessionStore.get(recovery.event.piboSessionId);
215
218
  if (session)
@@ -186,6 +186,14 @@ export async function runGatewayClient(options = {}) {
186
186
  });
187
187
  continue;
188
188
  }
189
+ if (text === "/goal" || text.startsWith("/goal ")) {
190
+ writeFrame(socket, {
191
+ type: "req",
192
+ id: randomUUID(),
193
+ event: { type: "execution", piboSessionId, action: "goal", params: { command: text.slice("/goal".length).trim() } },
194
+ });
195
+ continue;
196
+ }
189
197
  writeFrame(socket, {
190
198
  type: "req",
191
199
  id: randomUUID(),
@@ -1,9 +1,11 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { createServer } from "node:net";
2
3
  import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault } from "../plugins/builtin.js";
3
4
  import { PiboSessionRouter } from "../core/session-router.js";
4
5
  import { createLoopMessagePreflight } from "../loops/store.js";
5
6
  import { loadPiboModelDefaults, selectRequestedModelProfile } from "../core/model-defaults.js";
6
7
  import { ResourceReaperService } from "../resources/reaper.js";
8
+ import { InMemoryPiboSessionStore } from "../sessions/store.js";
7
9
  import { DEFAULT_GATEWAY_HOST, DEFAULT_GATEWAY_PORT, encodeFrame, errorResponse, isGatewayRequestFrame, isGatewaySubscribeFrame, } from "./protocol.js";
8
10
  import { releaseFallbackGatewayPid, releaseGatewayPid, writeFallbackGatewayPid, writeGatewayPid } from "./pidfile.js";
9
11
  const DEFAULT_MAX_BACKPRESSURE_FRAMES = 1_000;
@@ -118,12 +120,15 @@ async function createGatewaySessionStore(options) {
118
120
  const { SqlitePiboSessionStore } = await import("../sessions/sqlite-store.js");
119
121
  return new SqlitePiboSessionStore(options.sessionDbPath);
120
122
  }
123
+ if (options.persistSession === false)
124
+ return new InMemoryPiboSessionStore();
121
125
  const { createDefaultPiboDataSessionStore } = await import("../sessions/pibo-data-store.js");
122
126
  return createDefaultPiboDataSessionStore();
123
127
  }
124
128
  export class PiboGatewayServer {
125
129
  options;
126
130
  pluginRegistry;
131
+ runtimeInstanceId;
127
132
  sessionStore;
128
133
  ownsSessionStore = false;
129
134
  router;
@@ -137,6 +142,7 @@ export class PiboGatewayServer {
137
142
  constructor(options = {}) {
138
143
  this.options = options;
139
144
  this.pluginRegistry = options.pluginRegistry ?? createDefaultPiboPluginRegistry();
145
+ this.runtimeInstanceId = options.runtimeInstanceId ?? `gateway:${process.pid}:${randomUUID()}`;
140
146
  }
141
147
  async start() {
142
148
  if (this.server)
@@ -144,12 +150,16 @@ export class PiboGatewayServer {
144
150
  this.validateChannels();
145
151
  this.sessionStore = this.options.sessionStore ?? (await createGatewaySessionStore(this.options));
146
152
  this.ownsSessionStore = !this.options.sessionStore;
153
+ const hasExplicitPersistentStore = this.options.sessionStore !== undefined || this.options.sessionDbPath !== undefined;
154
+ const recoverInterruptedRuntimeState = this.options.authoritativeRuntime === true
155
+ && (this.options.persistSession !== false || hasExplicitPersistentStore);
147
156
  this.router = new PiboSessionRouter({
148
157
  persistSession: this.options.persistSession,
149
158
  pluginRegistry: this.pluginRegistry,
150
159
  sessionStore: this.sessionStore,
151
160
  messagePreflight: createLoopMessagePreflight({ path: this.options.loopStorePath }),
152
- recoverInterruptedRuntimeState: true,
161
+ recoverInterruptedRuntimeState,
162
+ runtimeInstanceId: recoverInterruptedRuntimeState ? this.runtimeInstanceId : undefined,
153
163
  });
154
164
  this.unsubscribe = this.router.subscribe((event) => this.broadcastRouterEvent(event));
155
165
  this.server = createServer((socket) => this.handleSocket(socket));
@@ -270,6 +280,8 @@ export class PiboGatewayServer {
270
280
  getDiagnostics() {
271
281
  const connectionDetails = [...this.connections].map((connection) => ({ ...connection.diagnostics }));
272
282
  return {
283
+ runtimeInstanceId: this.runtimeInstanceId,
284
+ authoritativeRuntime: this.options.authoritativeRuntime === true,
273
285
  connections: connectionDetails.length,
274
286
  slowConnections: connectionDetails.filter((connection) => connection.slow).length,
275
287
  droppedEvents: this.droppedRouterEvents,
@@ -377,7 +389,11 @@ export async function runGatewayServer(options = {}) {
377
389
  const releasePid = fallbackMode ? releaseFallbackGatewayPid : releaseGatewayPid;
378
390
  let server;
379
391
  try {
380
- server = new PiboGatewayServer({ ...options, resourceReaper: resolveGatewayResourceReaperOptions(options) });
392
+ server = new PiboGatewayServer({
393
+ ...options,
394
+ authoritativeRuntime: options.authoritativeRuntime ?? true,
395
+ resourceReaper: resolveGatewayResourceReaperOptions(options),
396
+ });
381
397
  await server.start();
382
398
  }
383
399
  catch (error) {
@@ -195,6 +195,7 @@ export async function runWebGatewayServer(options = {}) {
195
195
  const pluginRegistry = resolvedOptions.pluginRegistry ?? createWebPiboPluginRegistry(resolvedOptions);
196
196
  server = new PiboGatewayServer({
197
197
  ...resolvedOptions,
198
+ authoritativeRuntime: resolvedOptions.authoritativeRuntime ?? true,
198
199
  pluginRegistry,
199
200
  resourceReaper: resolveGatewayResourceReaperOptions(resolvedOptions),
200
201
  loopStorePath: resolvedOptions.chat?.ralphStorePath,
@@ -202,6 +202,8 @@ function createExecutionParams(slashCommand, args = "") {
202
202
  }
203
203
  if (slashCommand === "/compact")
204
204
  return trimmedArgs ? { customInstructions: trimmedArgs } : undefined;
205
+ if (slashCommand === "/goal")
206
+ return { command: trimmedArgs };
205
207
  return undefined;
206
208
  }
207
209
  function formatExecutionResult(event) {
@@ -1,7 +1,24 @@
1
1
  import { definePiboPlugin } from '../plugins/registry.js';
2
- import { createPiboLoopChannel } from './channel.js';
2
+ import { createPiboLoopChannel, getPiboLoopService } from './channel.js';
3
3
  import { createBuiltInLoopStopConditions } from './stopping.js';
4
4
  import { configurePiboGoalToolStorePath } from './tools.js';
5
+ function objectParams(event) {
6
+ const params = 'params' in event ? event.params : undefined;
7
+ return params && typeof params === 'object' && !Array.isArray(params) ? params : undefined;
8
+ }
9
+ export function parsePiboSessionGoalCommand(event) {
10
+ const command = objectParams(event)?.command;
11
+ if (typeof command !== 'string' || !command.trim())
12
+ throw new Error('Usage: /goal <objective> | /goal pause | /goal resume');
13
+ const normalized = command.trim();
14
+ if (normalized.toLowerCase() === 'pause')
15
+ return { operation: 'pause' };
16
+ if (normalized.toLowerCase() === 'resume')
17
+ return { operation: 'resume' };
18
+ if (normalized.length > 20_000)
19
+ throw new Error('Goal objective is too long');
20
+ return { operation: 'set', objective: normalized };
21
+ }
5
22
  export function createPiboLoopPlugin(options = {}) {
6
23
  configurePiboGoalToolStorePath(options.loopStorePath);
7
24
  return definePiboPlugin({
@@ -10,6 +27,22 @@ export function createPiboLoopPlugin(options = {}) {
10
27
  register(api) {
11
28
  for (const condition of createBuiltInLoopStopConditions())
12
29
  api.registerLoopStopCondition(condition);
30
+ api.registerGatewayAction({
31
+ name: 'goal',
32
+ description: 'Create or update the session Goal Loop. Use /goal pause or /goal resume to control it.',
33
+ slashCommands: ['goal'],
34
+ execute(context, event) {
35
+ const service = getPiboLoopService();
36
+ if (!service)
37
+ throw new Error('Loop service is not running');
38
+ const command = parsePiboSessionGoalCommand(event);
39
+ if (command.operation === 'pause')
40
+ return service.pauseSessionGoal(context.piboSessionId);
41
+ if (command.operation === 'resume')
42
+ return service.resumeSessionGoal(context.piboSessionId);
43
+ return service.setSessionGoal(context.piboSessionId, command.objective);
44
+ },
45
+ });
13
46
  api.registerChannel(createPiboLoopChannel(options));
14
47
  },
15
48
  });
@@ -44,6 +44,8 @@ function failureRecovery(details) {
44
44
  return details?.retryable === false ? 'Resolve the reported non-retryable failure, then explicitly restart the Goal.' : 'Pibo will retry automatically after the recorded backoff.';
45
45
  }
46
46
  function isTerminalGoalStatus(status) { return status === 'complete' || status === 'blocked' || status === 'budget_limited'; }
47
+ function currentGoalStatus(job) { return job.state.goalStatus ?? (job.enabled ? 'active' : 'paused'); }
48
+ function goalName(objective) { return objective.replace(/\s+/g, ' ').trim().slice(0, 80); }
47
49
  function retryBackoffMs(consecutiveErrors, baseMs, maxMs, jitterRatio, random) {
48
50
  const exponent = Math.max(0, Math.min(30, consecutiveErrors - 1));
49
51
  const bounded = Math.min(maxMs, baseMs * (2 ** exponent));
@@ -145,6 +147,89 @@ export class PiboLoopService {
145
147
  this.armSoon();
146
148
  return this.store.getJob(id);
147
149
  }
150
+ setSessionGoal(piboSessionId, objective) {
151
+ const normalizedObjective = objective.trim();
152
+ if (!normalizedObjective)
153
+ throw new Error('Usage: /goal <objective> | /goal pause | /goal resume');
154
+ if (normalizedObjective.length > 20_000)
155
+ throw new Error('Goal objective is too long');
156
+ const session = this.options.context.getSession(piboSessionId);
157
+ if (!session)
158
+ throw new Error('Pibo Session not found');
159
+ const existing = this.store.getSessionGoalOwner(piboSessionId);
160
+ if (!existing) {
161
+ const goal = this.store.createSessionGoal({
162
+ name: goalName(normalizedObjective),
163
+ target: this.sessionGoalTarget(session.metadata?.chatRoomId),
164
+ profile: session.profile,
165
+ prompt: normalizedObjective,
166
+ initialPiboSessionId: piboSessionId,
167
+ }, this.now());
168
+ this.armSoon();
169
+ return { operation: 'created', goal };
170
+ }
171
+ const status = currentGoalStatus(existing);
172
+ if (status === 'complete')
173
+ throw new Error('The current Goal is completing; wait for its active run to finish before creating another Goal');
174
+ if (status === 'blocked')
175
+ throw new Error('The current Goal is blocked; explicitly reopen it before updating or resuming it');
176
+ if (status === 'budget_limited')
177
+ throw new Error('The current Goal is budget limited; increase or clear its token budget before updating and resuming it');
178
+ const goal = this.store.updateJob(existing.id, {
179
+ name: goalName(normalizedObjective),
180
+ prompt: normalizedObjective,
181
+ enabled: true,
182
+ }, this.now());
183
+ if (!goal)
184
+ throw new Error('Goal Loop not found');
185
+ this.armSoon();
186
+ return { operation: 'updated', goal };
187
+ }
188
+ pauseSessionGoal(piboSessionId) {
189
+ const existing = this.requireSessionGoal(piboSessionId);
190
+ const status = currentGoalStatus(existing);
191
+ if (status === 'paused')
192
+ return { operation: 'already-paused', goal: existing };
193
+ if (status !== 'active')
194
+ throw new Error(`Goal Loop cannot be paused while its status is ${status}`);
195
+ const goal = this.store.requestStop(existing.id, this.now());
196
+ if (!goal)
197
+ throw new Error('Goal Loop not found');
198
+ this.armSoon();
199
+ return { operation: 'paused', goal };
200
+ }
201
+ resumeSessionGoal(piboSessionId) {
202
+ const existing = this.requireSessionGoal(piboSessionId);
203
+ const status = currentGoalStatus(existing);
204
+ if (status === 'active' && existing.enabled)
205
+ return { operation: 'already-active', goal: existing };
206
+ if (status !== 'paused')
207
+ throw new Error(`Goal Loop cannot be resumed while its status is ${status}`);
208
+ const goal = this.store.updateJob(existing.id, { enabled: true }, this.now());
209
+ if (!goal)
210
+ throw new Error('Goal Loop not found');
211
+ this.armSoon();
212
+ return { operation: 'resumed', goal };
213
+ }
214
+ requireSessionGoal(piboSessionId) {
215
+ if (!this.options.context.getSession(piboSessionId))
216
+ throw new Error('Pibo Session not found');
217
+ const goal = this.store.getSessionGoalOwner(piboSessionId);
218
+ if (!goal)
219
+ throw new Error('This Pibo Session has no Goal Loop');
220
+ return goal;
221
+ }
222
+ sessionGoalTarget(value) {
223
+ if (typeof value !== 'string' || !value.trim())
224
+ return { kind: 'default-chat' };
225
+ const roomId = value.trim();
226
+ const room = this.roomService.getRoom(roomId);
227
+ if (!room)
228
+ throw new Error('The Pibo Session room no longer exists');
229
+ if (isPiboRoomArchived(room))
230
+ throw new Error('Archived rooms are read-only');
231
+ return { kind: 'room', roomId };
232
+ }
148
233
  repairGoalSessionMetadata() {
149
234
  for (const session of this.options.context.listSessions?.() ?? []) {
150
235
  if (session.metadata?.loopMode === 'goal' && (session.metadata.loopJobId !== undefined || session.metadata.loopRunId !== undefined))
@@ -1,3 +1,4 @@
1
+ import { existsSync } from 'node:fs';
1
2
  import { readFile, writeFile } from 'node:fs/promises';
2
3
  import { ensureConfigExists, findConfigPath, getPreferredConfigPath, } from './config.js';
3
4
  import { ErrorCode, formatCliError } from './errors.js';
@@ -155,9 +156,10 @@ export async function configCommand(options) {
155
156
  return;
156
157
  }
157
158
  if (options.action === 'init') {
158
- const existingPath = findConfigPath(options.configPath);
159
- const path = await ensureConfigExists(options.configPath);
160
- console.log(existingPath ? `MCP config ready: ${path}` : `Created MCP config: ${path}`);
159
+ const preferredPath = getPreferredConfigPath(options.configPath);
160
+ const alreadyExists = existsSync(preferredPath);
161
+ const path = await ensureConfigExists(preferredPath);
162
+ console.log(alreadyExists ? `MCP config ready: ${path}` : `Created MCP config: ${path}`);
161
163
  return;
162
164
  }
163
165
  if (options.action === 'describe') {