@pasko70/pibo 1.13.0 → 1.15.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 (58) 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/{context-files-ui/assets/dist-BFRDpzcv.js → chat-ui/assets/dist-BFwSuVfD.js} +1 -1
  4. package/dist/apps/chat-ui/assets/{dist-XmG4qG-A.js → dist-BYTi90un.js} +1 -1
  5. package/dist/apps/{context-files-ui/assets/dist-B_zXPNMb.js → chat-ui/assets/dist-Bi74WdBU.js} +1 -1
  6. package/dist/apps/chat-ui/assets/{dist-UYBI2F8E.js → dist-CYMM1_bS.js} +1 -1
  7. package/dist/apps/chat-ui/assets/{dist-DVPXynJE.js → dist-CuIwfDgF.js} +1 -1
  8. package/dist/apps/{context-files-ui/assets/dist-B5mpygOZ.js → chat-ui/assets/dist-DS1ScAMf.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{dist-B_8EcoB6.js → dist-DUgrMhHX.js} +1 -1
  10. package/dist/apps/chat-ui/assets/{dist-CVLKu7NV.js → dist-Dq3i1LVZ.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-DugRsLaG.js → dist-Dy5L1xOD.js} +1 -1
  12. package/dist/apps/{context-files-ui/assets/dist-D6pYe6d_.js → chat-ui/assets/dist-Zy3KnDp7.js} +1 -1
  13. package/dist/apps/chat-ui/assets/index-BZO4zpMi.js +237 -0
  14. package/dist/apps/chat-ui/assets/index-CoCl8FG8.css +1 -0
  15. package/dist/apps/chat-ui/index.html +2 -2
  16. package/dist/apps/chat-vscode-web/assets/index-V_pKPVAm.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.15.0.vsix +0 -0
  32. package/dist/cli-session/localSessionSource.js +2 -0
  33. package/dist/core/runtime.js +27 -5
  34. package/dist/core/session-router.js +3 -0
  35. package/dist/gateway/client.js +8 -0
  36. package/dist/gateway/server.js +18 -2
  37. package/dist/gateway/web.js +1 -0
  38. package/dist/local/extension.js +2 -0
  39. package/dist/loops/plugin.js +34 -1
  40. package/dist/loops/service.js +85 -0
  41. package/dist/mcp/config-command.js +5 -3
  42. package/dist/plugins/builtin.js +7 -0
  43. package/dist/plugins/context-files-store.js +287 -101
  44. package/dist/plugins/context-files.js +102 -154
  45. package/dist/session-ui/terminalRows.js +12 -2
  46. package/dist/shared/trace-live-reducer.js +6 -1
  47. package/dist/tools/browser-use-leases.js +16 -0
  48. package/dist/tools/codex-browser-node-repl.js +224 -0
  49. package/dist/tools/codex-browser-node-worker-source.js +403 -0
  50. package/dist/tools/codex-browser.js +461 -0
  51. package/dist/web/channel.js +105 -16
  52. package/dist/web/http.js +14 -2
  53. package/package.json +13 -7
  54. package/dist/apps/chat-ui/assets/index-DhFQ1th-.js +0 -237
  55. package/dist/apps/chat-ui/assets/index-hjygk9Ol.css +0 -1
  56. package/dist/apps/chat-vscode-web/assets/index-1wWonouX.js +0 -41
  57. package/dist/apps/context-files-ui/assets/index-C7X2UvWe.js +0 -202
  58. 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) {
@@ -25,6 +25,7 @@ import { registerOpenAiGpt56Models } from "../providers/openai-gpt56.js";
25
25
  import { PIBO_APP_CONTEXT } from "../app-context.js";
26
26
  import { createRuntimeToolDefinition } from "../tools/runtime/tool.js";
27
27
  import { RuntimeSessionRegistry } from "../tools/runtime/registry.js";
28
+ import { CODEX_BROWSER_TOOL_NAMES, CodexBrowserSessionController, createCodexBrowserToolDefinitions, } from "../tools/codex-browser.js";
28
29
  import { compactValidationToolResultForContext } from "./test-output-compaction.js";
29
30
  import { installPiboTranscriptIntegrity } from "./transcript-integrity.js";
30
31
  function hasOwnRetrySetting(settings, key) {
@@ -102,12 +103,18 @@ function getEnabledSkillPaths(cwd, profile) {
102
103
  .filter((skill) => skill.enabled !== false)
103
104
  .map((skill) => resolveProfilePath(cwd, skill.path));
104
105
  }
105
- function getEnabledToolDefinitions(profile, options, subagentRunner, runToolController, runtimeToolController) {
106
+ function getEnabledToolDefinitions(profile, options, subagentRunner, runToolController, runtimeToolController, codexBrowserController) {
106
107
  const runtimeProfileTool = profile.tools.find(isEnabledRuntimeTool);
107
108
  const runtimeTool = runtimeProfileTool && runtimeToolController
108
109
  ? createRuntimeToolDefinition(runtimeToolController)
109
110
  : undefined;
110
- const profileTools = profile.tools.filter((tool) => !isRuntimeTool(tool)).filter(hasEnabledToolDefinition);
111
+ const selectedCodexBrowserToolNames = profile.tools
112
+ .filter(isEnabledCodexBrowserTool)
113
+ .map((tool) => tool.name);
114
+ const codexBrowserTools = codexBrowserController
115
+ ? createCodexBrowserToolDefinitions(codexBrowserController, selectedCodexBrowserToolNames)
116
+ : [];
117
+ const profileTools = profile.tools.filter((tool) => !isRuntimeTool(tool) && !isCodexBrowserTool(tool)).filter(hasEnabledToolDefinition);
111
118
  const profileToolDefinitions = profileTools.map((tool) => getToolDefinition(tool, options.toolContext));
112
119
  const codexCompatEnabled = profile.toolPackages.codexCompat === true;
113
120
  const runControlEnabled = profile.toolPackages.runControl === true;
@@ -129,6 +136,7 @@ function getEnabledToolDefinitions(profile, options, subagentRunner, runToolCont
129
136
  ...(runControlBashTool ? [runControlBashTool] : []),
130
137
  ...profileTools.filter((tool) => tool.yieldable !== false).map((tool) => getToolDefinition(tool, options.toolContext)),
131
138
  ...(runtimeTool && runtimeProfileTool?.yieldable !== false ? [runtimeTool] : []),
139
+ ...codexBrowserTools.filter((definition) => profile.tools.find((tool) => tool.name === definition.name)?.yieldable !== false),
132
140
  ...subagentTools,
133
141
  ...codexCompatTools,
134
142
  ];
@@ -139,6 +147,7 @@ function getEnabledToolDefinitions(profile, options, subagentRunner, runToolCont
139
147
  ...(runControlBashTool ? [runControlBashTool] : []),
140
148
  ...profileToolDefinitions,
141
149
  ...(runtimeTool ? [runtimeTool] : []),
150
+ ...codexBrowserTools,
142
151
  ...subagentTools,
143
152
  ...codexCompatTools,
144
153
  ...goalTools,
@@ -159,6 +168,12 @@ function isRuntimeTool(tool) {
159
168
  function isEnabledRuntimeTool(tool) {
160
169
  return tool.enabled !== false && isRuntimeTool(tool);
161
170
  }
171
+ function isCodexBrowserTool(tool) {
172
+ return tool.builtInPiboTool === "codex_browser" || CODEX_BROWSER_TOOL_NAMES.includes(tool.name);
173
+ }
174
+ function isEnabledCodexBrowserTool(tool) {
175
+ return tool.enabled !== false && isCodexBrowserTool(tool);
176
+ }
162
177
  function isGeneratedPiboTool(name) {
163
178
  return name === "runtime" || name.startsWith("pibo_subagent_") || name.startsWith("pibo_run_") || PIBO_GOAL_TOOL_NAMES.includes(name);
164
179
  }
@@ -280,6 +295,12 @@ export async function createPiboRuntime(options = {}) {
280
295
  const localRuntimeRegistry = ownsLocalRuntimeRegistry ? new RuntimeSessionRegistry({ cwd: runtimeCwd }) : undefined;
281
296
  const runtimeToolController = options.runtimeToolController
282
297
  ?? localRuntimeRegistry?.createController(profile.sessionId ?? "local");
298
+ const codexBrowserController = profile.tools.some(isEnabledCodexBrowserTool)
299
+ ? new CodexBrowserSessionController({
300
+ cwd: runtimeCwd,
301
+ piboSessionId: options.sessionContext?.piboSessionId ?? profile.sessionId ?? runtimeSessionManager.getSessionId(),
302
+ })
303
+ : undefined;
283
304
  const customTools = getEnabledToolDefinitions(profile, {
284
305
  runtimeCwd,
285
306
  shellCommandPrefix: services.settingsManager.getShellCommandPrefix(),
@@ -290,7 +311,7 @@ export async function createPiboRuntime(options = {}) {
290
311
  profileName: profile.profileName,
291
312
  getActiveMessage: options.sessionContext?.getActiveMessage,
292
313
  },
293
- }, options.subagentRunner, options.runToolController, runtimeToolController);
314
+ }, options.subagentRunner, options.runToolController, runtimeToolController, codexBrowserController);
294
315
  const modelDefaults = options.modelDefaults ?? loadPiboModelDefaults(runtimeCwd);
295
316
  const created = await createAgentSessionFromServices({
296
317
  services,
@@ -324,6 +345,7 @@ export async function createPiboRuntime(options = {}) {
324
345
  if (localRuntimeRegistry) {
325
346
  void localRuntimeRegistry.closeControllerSessions(profile.sessionId ?? "local", { force: true });
326
347
  }
348
+ void codexBrowserController?.dispose();
327
349
  originalDispose();
328
350
  };
329
351
  return {
@@ -444,8 +466,8 @@ export async function inspectPiboProfile(options = {}) {
444
466
  })),
445
467
  tools: profile.tools.map((tool) => ({
446
468
  name: tool.name,
447
- hasDefinition: Boolean(tool.definition) || Boolean(tool.createDefinition) || isRuntimeTool(tool),
448
- registered: registeredToolNames.has(tool.name) || tool.providerTool !== undefined || isRuntimeTool(tool),
469
+ hasDefinition: Boolean(tool.definition) || Boolean(tool.createDefinition) || isRuntimeTool(tool) || isCodexBrowserTool(tool),
470
+ registered: registeredToolNames.has(tool.name) || tool.providerTool !== undefined || isRuntimeTool(tool) || isCodexBrowserTool(tool),
449
471
  active: activeToolNames.has(tool.name) || tool.providerTool !== undefined,
450
472
  })).concat(generatedTools),
451
473
  subagents: profile.subagents.map((subagent) => {
@@ -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') {
@@ -4,6 +4,7 @@ import { createPiboGatewayToolProfiles } from "../gateway/tool.js";
4
4
  import { InitialSessionContextBuilder } from "../core/profiles.js";
5
5
  import { parsePiboThinkingLevel } from "../core/thinking.js";
6
6
  import { createWebSearchToolProfile } from "../tools/web-search.js";
7
+ import { CODEX_BROWSER_TOOL_NAMES, createCodexBrowserToolProfiles } from "../tools/codex-browser.js";
7
8
  import { createRuntimeToolProfile } from "../tools/runtime/tool.js";
8
9
  import { completeLogin, getLoginStatus, removeLogin, setApiKey, startLogin } from "../auth/login-actions.js";
9
10
  import { loadModelCatalog } from "../apps/chat/model-catalog.js";
@@ -166,6 +167,12 @@ export const piboCorePlugin = definePiboPlugin({
166
167
  });
167
168
  api.registerTool(createWebSearchToolProfile());
168
169
  api.registerTool(createRuntimeToolProfile());
170
+ api.registerTools(createCodexBrowserToolProfiles());
171
+ api.registerCapabilityPackage({
172
+ name: "codex-browser-interface",
173
+ description: "Expose Browser Use and its persistent browser-bound Node REPL through a Codex-familiar structured tool surface.",
174
+ toolNames: [...CODEX_BROWSER_TOOL_NAMES],
175
+ });
169
176
  registerPiboNativeTooling(api);
170
177
  api.registerProfile({
171
178
  name: DEFAULT_PIBO_PROFILE_NAME,