@timurproko/a1 0.2.7-dev.1255 → 0.2.7-dev.1264

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 (78) hide show
  1. package/dist/app/session-shell/prompt-submission.d.ts +1 -1
  2. package/dist/app/session-shell/prompt-submission.js +9 -9
  3. package/dist/app/session-shell/resource-labels.d.ts +2 -0
  4. package/dist/app/session-shell/resource-labels.js +158 -0
  5. package/dist/app/session-shell/session-prompt-history.d.ts +29 -0
  6. package/dist/app/session-shell/session-prompt-history.js +80 -0
  7. package/dist/app/session-shell/session-selectors.d.ts +46 -0
  8. package/dist/app/session-shell/session-selectors.js +269 -0
  9. package/dist/app/session-shell/session-shell-root.d.ts +6 -28
  10. package/dist/app/session-shell/session-shell-root.js +26 -220
  11. package/dist/app/session-shell/session-shell.d.ts +3 -23
  12. package/dist/app/session-shell/session-shell.js +54 -365
  13. package/dist/app/session-shell/session-viewport-controller.d.ts +2 -10
  14. package/dist/app/session-shell/session-viewport-controller.js +40 -318
  15. package/dist/app/session-shell/settings-bindings.d.ts +1 -1
  16. package/dist/app/session-shell/settings-bindings.js +2 -2
  17. package/dist/app/session-shell/viewport-pointer.d.ts +83 -0
  18. package/dist/app/session-shell/viewport-pointer.js +361 -0
  19. package/dist/composition/owned-ui.js +63 -59
  20. package/dist/contracts/owned-ui/index.d.ts +1 -1
  21. package/dist/contracts/owned-ui/index.js +1 -1
  22. package/dist/contracts/owned-ui/validation.d.ts +1 -11
  23. package/dist/contracts/owned-ui/validation.js +8 -104
  24. package/dist/contracts/presentation/index.d.ts +1 -1
  25. package/dist/contracts/presentation/terminal.d.ts +0 -6
  26. package/dist/features/owned-ui/settings-app.js +13 -208
  27. package/dist/features/owned-ui/settings-model-dialog.d.ts +41 -0
  28. package/dist/features/owned-ui/settings-model-dialog.js +194 -0
  29. package/dist/foundation/release/automatic-update.js +1 -8
  30. package/dist/foundation/release/dependency-layer.js +3 -3
  31. package/dist/foundation/release/latest-release.js +1 -1
  32. package/dist/foundation/release/managed-path.d.ts +10 -0
  33. package/dist/foundation/release/managed-path.js +26 -0
  34. package/dist/foundation/release/release-gc.js +6 -8
  35. package/dist/foundation/release/restart-certification.js +3 -4
  36. package/dist/foundation/release/update-activation.js +1 -1
  37. package/dist/foundation/release/update-execution-lease.js +1 -7
  38. package/dist/foundation/release/update-installer.d.ts +45 -0
  39. package/dist/foundation/release/update-installer.js +57 -0
  40. package/dist/foundation/release/update-recovery.js +2 -9
  41. package/dist/foundation/release/update.d.ts +4 -23
  42. package/dist/foundation/release/update.js +160 -179
  43. package/dist/integrations/pi/components/bracketed-paste.d.ts +33 -0
  44. package/dist/integrations/pi/components/bracketed-paste.js +123 -0
  45. package/dist/integrations/pi/components/owned-editor-ux.js +46 -140
  46. package/dist/integrations/pi/engine/adapter.d.ts +1 -8
  47. package/dist/integrations/pi/engine/adapter.js +6 -49
  48. package/dist/integrations/pi/engine/host.js +1 -1
  49. package/dist/integrations/pi/engine/repository-metadata.d.ts +49 -0
  50. package/dist/integrations/pi/engine/repository-metadata.js +189 -0
  51. package/dist/integrations/pi/engine/session-runtime.d.ts +6 -12
  52. package/dist/integrations/pi/engine/session-runtime.js +21 -178
  53. package/dist/integrations/pi/engine/view-state.d.ts +21 -2
  54. package/dist/integrations/pi/engine/view-state.js +129 -1
  55. package/dist/integrations/pi/engine/workflow-runner.d.ts +0 -1
  56. package/dist/integrations/pi/engine/workflow-runner.js +254 -365
  57. package/dist/integrations/pi/engine/workflow-share.d.ts +9 -0
  58. package/dist/integrations/pi/engine/workflow-share.js +168 -0
  59. package/dist/integrations/pi/engine/workflow-support.d.ts +0 -14
  60. package/dist/integrations/pi/engine/workflow-support.js +0 -47
  61. package/dist/integrations/pi/tui-runtime/adapter.d.ts +9 -19
  62. package/dist/integrations/pi/tui-runtime/adapter.js +35 -144
  63. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +4 -4
  64. package/dist/integrations/pi/tui-runtime/index.d.ts +0 -1
  65. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +2 -2
  66. package/dist/integrations/pi/tui-runtime/presentation-adapter.d.ts +2 -2
  67. package/dist/ui/components/transcript-viewport-frame.d.ts +120 -0
  68. package/dist/ui/components/transcript-viewport-frame.js +314 -0
  69. package/dist/ui/components/transcript-viewport-types.d.ts +147 -0
  70. package/dist/ui/components/transcript-viewport.d.ts +2 -147
  71. package/dist/ui/components/transcript-viewport.js +87 -315
  72. package/package.json +3 -3
  73. package/dist/integrations/pi/engine/diagnostics-state.d.ts +0 -9
  74. package/dist/integrations/pi/engine/diagnostics-state.js +0 -34
  75. package/dist/integrations/pi/engine/usage-view.d.ts +0 -4
  76. package/dist/integrations/pi/engine/usage-view.js +0 -56
  77. package/dist/integrations/pi/tui-runtime/contracts.d.ts +0 -22
  78. /package/dist/{integrations/pi/tui-runtime/contracts.js → ui/components/transcript-viewport-types.js} +0 -0
@@ -1,10 +1,7 @@
1
- import { randomUUID } from "node:crypto";
2
- import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
1
+ import { mkdir, writeFile } from "node:fs/promises";
4
2
  import { dirname, join } from "node:path";
5
- import { PRODUCT_IDENTITY } from "../../../product-identity.js";
6
3
  import { isRecord, readModel, readThinkingLevel, requireCapability, stringProperty } from "./message-values.js";
7
- import { AUTH_REFRESH_TIMEOUT_MS, commandMissing, errorMessage, findExactWorkflowModel, isAbortError, pathArgument, pinnedHotkeySummary, pinnedSessionInfoPresentation, RADIUS_AUTH_MIN_VALIDITY_MS, RADIUS_SHARE_GATEWAY, radiusAuthCredential, shareCommandFailureDetail, shareViewerUrl, withRadiusShareTrailingEntry, workflowConfirmation, workflowResult, } from "./workflow-support.js";
4
+ import { AUTH_REFRESH_TIMEOUT_MS, errorMessage, findExactWorkflowModel, pathArgument, pinnedHotkeySummary, pinnedSessionInfoPresentation, workflowConfirmation, workflowResult, } from "./workflow-support.js";
8
5
  import { PiProviderAuthentication } from "./provider-authentication.js";
9
6
  /** How many workflows and engine commands may be admitted together before new work is rejected. */
10
7
  const MAX_PENDING_WORK = 32;
@@ -91,61 +88,6 @@ export class PiWorkflowRunner {
91
88
  await this.#host.copyText(text);
92
89
  return true;
93
90
  }
94
- // Protocol: the pinned Radius `/share` route. Undefined means Radius has no provider or credential,
95
- // so the caller may fall back to a gist; any other outcome, including an upload failure, is final.
96
- async #shareViaRadius(session, runtime, request) {
97
- const signal = request.signal;
98
- const models = runtime.services.modelRuntime;
99
- if (models.getProvider?.("radius") === undefined)
100
- return undefined;
101
- const token = radiusAuthCredential(await models.getAuth("radius", { minOAuthValidityMs: RADIUS_AUTH_MIN_VALIDITY_MS }));
102
- if (!token)
103
- return undefined;
104
- if (signal?.aborted)
105
- return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
106
- request.onProgress?.("Uploading to Radius…");
107
- const temporary = join(tmpdir(), `${PRODUCT_IDENTITY.filesystem.temporaryPrefix}pi-session-${process.pid}.jsonl`);
108
- let body;
109
- try {
110
- requireCapability(session.exportToJsonl, "exportToJsonl").call(session, temporary);
111
- body = withRadiusShareTrailingEntry(await readFile(temporary, "utf8"), session.state ?? {}, randomUUID().slice(0, 8));
112
- }
113
- catch (error) {
114
- return workflowResult(request.command, "failed", `Failed to export session: ${errorMessage(error, "Unknown error")}`);
115
- }
116
- finally {
117
- await rm(temporary, { force: true });
118
- }
119
- const url = new URL("/v1/artifacts", RADIUS_SHARE_GATEWAY);
120
- url.searchParams.set("visibility", "organization");
121
- url.searchParams.set("title", "Pi session");
122
- try {
123
- const response = await (this.#host.fetch ?? fetch)(url, {
124
- method: "POST",
125
- headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
126
- body,
127
- ...(signal === undefined ? {} : { signal }),
128
- });
129
- const json = await response.json().catch(() => null);
130
- if (signal?.aborted)
131
- return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
132
- const artifact = isRecord(json) && isRecord(json.artifact) ? json.artifact : undefined;
133
- const shareUrl = artifact === undefined ? undefined : stringProperty(artifact, "canonical_url");
134
- if (!response.ok || shareUrl === undefined) {
135
- const detail = (isRecord(json) ? stringProperty(json, "error") : undefined) || response.statusText || String(response.status);
136
- return workflowResult(request.command, "failed", `Failed to upload Radius artifact: ${detail}`);
137
- }
138
- return workflowResult(request.command, "completed", `Share URL: ${shareUrl}`);
139
- }
140
- catch (error) {
141
- if (signal?.aborted || isAbortError(error))
142
- return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
143
- return workflowResult(request.command, "failed", `Failed to upload Radius artifact: ${errorMessage(error, "Unknown error")}`);
144
- }
145
- }
146
- abortBashWorkflow() {
147
- this.#requireSession().abortBash?.();
148
- }
149
91
  async executeBashWorkflow(command, excludeFromContext) {
150
92
  const session = this.#requireSession();
151
93
  const result = await requireCapability(session.executeBash, "executeBash").call(session, command, undefined, { excludeFromContext });
@@ -288,313 +230,260 @@ export class PiWorkflowRunner {
288
230
  const runtime = this.#ports.runtime();
289
231
  if (!runtime)
290
232
  throw new Error("engine runtime is unavailable");
291
- const argument = request.argument.trim();
292
233
  const selected = request.selection?.trim();
293
- const selection = selected && selected.length > 0 ? selected : undefined;
294
- switch (request.command) {
295
- case "settings": {
296
- if (!selection)
297
- return workflowResult(request.command, "failed", "Settings requires the owned settings controller");
298
- return await this.#ports.applyPinnedSetting(selection);
299
- }
300
- case "thinking": {
301
- const requested = (selection ?? argument).trim();
302
- if (!requested)
303
- return workflowResult(request.command, "requires-selection", "Select a thinking level", undefined, "silent");
304
- const available = (session.getAvailableThinkingLevels?.() ?? []).map(String);
305
- // Compatibility: pinned Pi matches the argument case-insensitively and words the failure with the available levels.
306
- const level = available.find(candidate => candidate.toLowerCase() === requested.toLowerCase());
307
- if (level === undefined) {
308
- return workflowResult(request.command, "failed", `Unknown thinking level "${requested}". Available levels: ${available.join(", ")}.`);
309
- }
310
- try {
311
- session.setThinkingLevel(readThinkingLevel(level), { persist: request.persist === true });
312
- }
313
- catch (error) {
314
- return workflowResult(request.command, "failed", error instanceof Error ? error.message : String(error));
315
- }
316
- this.#ports.setThinkingLevel(readThinkingLevel(session.thinkingLevel));
317
- this.#ports.emitView();
318
- return workflowResult(request.command, "completed", request.persist === true ? `Default thinking level: ${level}` : `Thinking level: ${level}`);
319
- }
320
- case "model": {
321
- const reference = selection ?? argument;
322
- if (!reference)
323
- return workflowResult(request.command, "failed", "Model requires the owned model controller");
324
- const scopedModels = Array.isArray(session.scopedModels)
325
- ? session.scopedModels.map(item => item.model)
326
- : [];
327
- // Compatibility: pinned Pi searches the session scope when one exists and refreshes catalogs only for an unscoped miss.
328
- return this.#selectModel(request, reference, {
329
- pool: scopedModels.length > 0 ? scopedModels : [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])],
330
- refreshOnMiss: scopedModels.length === 0,
331
- persist: request.persist === true,
332
- onMiss: "requires-selection",
333
- });
334
- }
335
- case "models": {
336
- // Invariant: the bare dialog seeds its query from the argument; only an explicit selection switches the current session.
337
- if (!selection)
338
- return workflowResult(request.command, "requires-selection", "Select a model", argument || undefined, "silent");
339
- return this.#selectModel(request, selection, {
340
- pool: [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])],
341
- refreshOnMiss: true,
342
- persist: false,
343
- onMiss: "failed",
344
- });
345
- }
346
- case "scoped-models": {
347
- if (!selection)
348
- return workflowResult(request.command, "failed", "Scoped models requires the owned scoped-model controller");
349
- const [providerId, modelId] = selection.split("/", 2);
350
- const model = providerId && modelId ? runtime.services.modelRuntime.getModel(providerId, modelId) : undefined;
351
- if (!model)
352
- return workflowResult(request.command, "failed", `Model is unavailable: ${selection}`);
353
- requireCapability(session.setScopedModels, "setScopedModels").call(session, [{ model }]);
354
- return workflowResult(request.command, "completed", `Enabled scoped model ${selection}`);
355
- }
356
- case "export": {
357
- const path = pathArgument(argument);
358
- const file = path?.toLowerCase().endsWith(".jsonl")
359
- ? requireCapability(session.exportToJsonl, "exportToJsonl").call(session, path)
360
- : await requireCapability(session.exportToHtml, "exportToHtml").call(session, path);
361
- return workflowResult(request.command, "completed", `Session exported to: ${String(file)}`);
362
- }
363
- case "import": {
364
- const path = pathArgument(argument);
365
- if (!path)
366
- return workflowResult(request.command, "failed", "Usage: /import <path.jsonl>");
367
- if (request.confirmed === undefined) {
368
- return workflowConfirmation(request.command, `Replace current session with ${path}?`);
369
- }
370
- if (!request.confirmed)
371
- return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
372
- try {
373
- const result = await requireCapability(runtime.importFromJsonl, "importFromJsonl").call(runtime, path, request.cwdOverride);
374
- if (isRecord(result) && result.cancelled === true)
375
- return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
376
- return workflowResult(request.command, "completed", `Session imported from: ${path}`);
377
- }
378
- catch (error) {
379
- const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
380
- const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
381
- if (fallbackCwd !== undefined && request.cwdOverride === undefined) {
382
- const sessionCwd = stringProperty(issue, "sessionCwd") ?? "the session working directory";
383
- return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`, fallbackCwd);
384
- }
385
- throw error;
386
- }
387
- }
388
- case "share": {
389
- const signal = request.signal;
390
- if (signal?.aborted)
391
- return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
392
- const destination = runtime.services.settingsManager?.getSessionShareDestination?.() ?? "auto";
393
- if (destination !== "gist") {
394
- const radius = await this.#shareViaRadius(session, runtime, request);
395
- if (radius !== undefined)
396
- return radius;
397
- if (destination === "radius")
398
- return workflowResult(request.command, "failed", "Radius authentication is not configured. Run '/login radius' first.");
399
- }
400
- request.onProgress?.("Creating gist…");
401
- try {
402
- await this.#host.runCommand("gh", ["auth", "status"], signal === undefined ? undefined : { signal });
403
- }
404
- catch (error) {
405
- if (signal?.aborted || isAbortError(error))
406
- return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
407
- const message = commandMissing(error)
408
- ? "GitHub CLI (gh) is not installed. Install it from https://cli.github.com/"
409
- : "GitHub CLI is not logged in. Run 'gh auth login' first.";
410
- return workflowResult(request.command, "failed", message);
411
- }
412
- const temporary = join(tmpdir(), `${PRODUCT_IDENTITY.filesystem.temporaryPrefix}pi-session-${process.pid}.html`);
413
- try {
414
- try {
415
- await requireCapability(session.exportToHtml, "exportToHtml").call(session, temporary);
416
- }
417
- catch (error) {
418
- return workflowResult(request.command, "failed", `Failed to export session: ${errorMessage(error, "Unknown error")}`);
419
- }
420
- if (signal?.aborted)
421
- return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
422
- let gist;
423
- try {
424
- gist = await this.#host.runCommand("gh", ["gist", "create", "--public=false", temporary], signal === undefined ? undefined : { signal });
425
- }
426
- catch (error) {
427
- if (signal?.aborted || isAbortError(error))
428
- return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
429
- return workflowResult(request.command, "failed", `Failed to create gist: ${shareCommandFailureDetail(error)}`);
430
- }
431
- if (signal?.aborted)
432
- return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
433
- const gistUrl = gist.stdout.trim();
434
- const gistId = gistUrl.split("/").at(-1);
435
- if (!gistId)
436
- return workflowResult(request.command, "failed", "Failed to parse gist ID from gh output");
437
- return workflowResult(request.command, "completed", `Share URL: ${shareViewerUrl(gistId)}`, gistUrl);
438
- }
439
- finally {
440
- await rm(temporary, { force: true });
441
- }
442
- }
443
- case "copy": {
444
- const text = requireCapability(session.getLastAssistantText, "getLastAssistantText").call(session);
445
- if (typeof text !== "string" || text.length === 0)
446
- return workflowResult(request.command, "completed", "", undefined, "silent");
447
- const acknowledged = await this.copyWorkflowText(text);
448
- return workflowResult(request.command, "completed", acknowledged ? "Copied last agent message to clipboard" : "Submitted last agent message to clipboard");
449
- }
450
- case "name": {
451
- const manager = session.sessionManager;
452
- if (!argument) {
453
- const current = manager?.getSessionName();
454
- return typeof current === "string"
455
- ? workflowResult(request.command, "completed", `Session name: ${current}`)
456
- : workflowResult(request.command, "failed", "Usage: /name <name>", undefined, "warning");
457
- }
458
- requireCapability(session.setSessionName, "setSessionName").call(session, argument);
459
- this.#ports.emitView();
460
- const actual = sessionNameOr(manager?.getSessionName(), argument);
461
- return workflowResult(request.command, "completed", `Session name set: ${actual}`, actual === argument ? undefined : `Session name was normalized from ${JSON.stringify(argument)} to ${JSON.stringify(actual)}`);
462
- }
463
- case "session": {
464
- const manager = session.sessionManager;
465
- const stats = requireCapability(session.getSessionStats, "getSessionStats").call(session);
466
- const entries = manager?.getEntries();
467
- return {
468
- ...workflowResult(request.command, "completed", "Session Info"),
469
- presentation: pinnedSessionInfoPresentation(stats, manager?.getSessionName(), Array.isArray(entries) ? entries : [], runtime.services.modelRuntime, {
470
- mode: runtime.services.settingsManager?.getCacheWarmingMode?.(),
471
- status: session.cacheWarmingStatus,
472
- }),
473
- };
474
- }
475
- case "changelog": {
476
- const changelog = await this.#host.readChangelog();
477
- return workflowResult(request.command, "completed", "What's New", changelog);
478
- }
479
- case "hotkeys":
480
- return workflowResult(request.command, "completed", "Keyboard Shortcuts", pinnedHotkeySummary());
481
- case "fork": {
482
- if (!selection)
483
- return workflowResult(request.command, "failed", "Fork requires the owned user-message controller");
484
- const result = await requireCapability(runtime.fork, "fork").call(runtime, selection, { position: "before" });
485
- if (isRecord(result) && result.cancelled === true)
486
- return workflowResult(request.command, "cancelled", "Fork cancelled", undefined, "silent");
487
- return workflowResult(request.command, "completed", "Forked to new session");
488
- }
489
- case "clone": {
490
- const manager = session.sessionManager;
491
- const leaf = manager?.getLeafId?.();
492
- if (typeof leaf !== "string")
493
- return workflowResult(request.command, "completed", "Nothing to clone yet", undefined, "status");
494
- const result = await requireCapability(runtime.fork, "fork").call(runtime, leaf, { position: "at" });
495
- if (isRecord(result) && result.cancelled === true)
496
- return workflowResult(request.command, "cancelled", "Clone cancelled", undefined, "silent");
497
- return workflowResult(request.command, "completed", "Cloned to new session");
498
- }
499
- case "tree": {
500
- if (!selection)
501
- return workflowResult(request.command, "failed", "Tree navigation requires the owned tree controller");
502
- const navigateTree = requireCapability(session.navigateTree, "navigateTree");
503
- const result = request.treeSummary === undefined
504
- ? await navigateTree.call(session, selection)
505
- : await navigateTree.call(session, selection, {
506
- summarize: request.treeSummary.summarize,
507
- ...(request.treeSummary.customInstructions === undefined ? {} : { customInstructions: request.treeSummary.customInstructions }),
508
- });
509
- if (isRecord(result) && result.aborted === true)
510
- return workflowResult(request.command, "cancelled", "Branch summarization cancelled", undefined, "status");
511
- if (isRecord(result) && result.cancelled === true)
512
- return workflowResult(request.command, "cancelled", "Navigation cancelled", undefined, "status");
513
- const editorText = isRecord(result) && typeof result.editorText === "string" ? result.editorText : undefined;
514
- return workflowResult(request.command, "completed", "Navigated to selected point", editorText);
515
- }
516
- case "trust": {
517
- if (!selection)
518
- return workflowResult(request.command, "failed", "Trust requires the owned trust controller");
519
- requireCapability(runtime.services.settingsManager?.setProjectTrusted, "setProjectTrusted").call(runtime.services.settingsManager, selection === "trust");
520
- return workflowResult(request.command, "completed", selection === "trust" ? "Project trusted" : "Project trust removed");
521
- }
522
- case "login":
523
- return await this.#authentication.login(request, session, runtime, argument, selection);
524
- case "logout":
525
- if (!selection)
526
- return workflowResult(request.command, "failed", "Logout requires the owned authentication controller");
527
- return await this.#authentication.logout(request, runtime, selection);
528
- case "new": {
529
- const result = await runtime.newSession();
530
- if (isRecord(result) && result.cancelled === true)
531
- return workflowResult(request.command, "cancelled", "New session cancelled", undefined, "silent");
532
- return workflowResult(request.command, "completed", "✓ New session started", undefined, "accent");
533
- }
534
- case "compact": {
535
- try {
536
- await session.compact(argument || undefined);
537
- return workflowResult(request.command, "completed", "Compaction requested", undefined, "silent");
538
- }
539
- catch (error) {
540
- return workflowResult(request.command, "failed", error instanceof Error ? error.message : String(error));
541
- }
542
- }
543
- case "resume": {
544
- if (!selection && !argument)
545
- return workflowResult(request.command, "failed", "Resume requires the owned session controller");
546
- const sessionPath = selection ?? argument;
547
- try {
548
- const result = await runtime.switchSession(sessionPath);
549
- if (isRecord(result) && result.cancelled === true)
550
- return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
551
- return workflowResult(request.command, "completed", "Resumed session");
552
- }
553
- catch (error) {
554
- const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
555
- const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
556
- if (fallbackCwd === undefined)
557
- throw error;
558
- if (request.confirmed === undefined) {
559
- const sessionCwd = stringProperty(issue, "sessionCwd") ?? "the session working directory";
560
- return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`);
561
- }
562
- if (!request.confirmed)
563
- return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "status");
564
- const result = await runtime.switchSession(sessionPath, { cwdOverride: fallbackCwd });
565
- if (isRecord(result) && result.cancelled === true)
566
- return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
567
- return workflowResult(request.command, "completed", "Resumed session in current cwd");
568
- }
569
- }
570
- case "reload": {
571
- const blocked = this.reloadBlockedResult();
572
- if (blocked)
573
- return blocked;
574
- await requireCapability(session.reload, "reload").call(session);
575
- await this.#ports.bindExtensionUiToSession();
576
- const message = "Reloaded keybindings, extensions, skills, prompts, themes, and context files";
577
- const modelError = runtime.services.modelRuntime.getError?.();
578
- return workflowResult(request.command, "completed", message, undefined, "status", modelError ? [
579
- { kind: "error", message: `models.json error: ${modelError}` },
580
- { kind: "status", message },
581
- ] : undefined);
582
- }
583
- case "quit": {
584
- await this.#ports.dispose();
585
- return workflowResult(request.command, "completed", "Shutdown complete");
586
- }
587
- case "debug": {
588
- const debugPath = join(this.#agentDir, "pi-debug.log");
589
- await mkdir(dirname(debugPath), { recursive: true });
590
- await writeFile(debugPath, `${JSON.stringify(this.#ports.snapshot(), null, 2)}\n`, "utf8");
591
- return workflowResult(request.command, "completed", "✓ Debug log written", debugPath);
592
- }
593
- case "arminsayshi":
594
- return workflowResult(request.command, "completed", "Armin says hi");
595
- case "dementedelves":
596
- return workflowResult(request.command, "completed", "Demented elves announcement");
234
+ const call = { request, session, runtime, argument: request.argument.trim(), selection: selected && selected.length > 0 ? selected : undefined };
235
+ const { command } = request;
236
+ const { argument, selection } = call;
237
+ switch (command) {
238
+ case "settings": return selection ? await this.#ports.applyPinnedSetting(selection) : workflowResult(command, "failed", "Settings requires the owned settings controller");
239
+ case "thinking": return thinkingWorkflow(call, this.#ports);
240
+ case "model": return this.#modelWorkflow(call);
241
+ case "models": return this.#modelsWorkflow(call);
242
+ case "scoped-models": return scopedModelsWorkflow(call);
243
+ case "export": return exportWorkflow(call);
244
+ case "import": return importWorkflow(call);
245
+ case "share": return (await import("./workflow-share.js")).runShareWorkflow({ request, session, runtime, host: this.#host });
246
+ case "copy": return copyWorkflow(call, text => this.copyWorkflowText(text));
247
+ case "name": return nameWorkflow(call, this.#ports);
248
+ case "session": return sessionInfoWorkflow(call);
249
+ case "changelog": return workflowResult(command, "completed", "What's New", await this.#host.readChangelog());
250
+ case "hotkeys": return workflowResult(command, "completed", "Keyboard Shortcuts", pinnedHotkeySummary());
251
+ case "fork": return forkWorkflow(call);
252
+ case "clone": return cloneWorkflow(call);
253
+ case "tree": return treeWorkflow(call);
254
+ case "trust": return trustWorkflow(call);
255
+ case "login": return await this.#authentication.login(request, session, runtime, argument, selection);
256
+ case "logout": return selection ? await this.#authentication.logout(request, runtime, selection) : workflowResult(command, "failed", "Logout requires the owned authentication controller");
257
+ case "new": return newSessionWorkflow(call);
258
+ case "compact": return compactWorkflow(call);
259
+ case "resume": return resumeWorkflow(call);
260
+ case "reload": return this.#reloadWorkflow(call);
261
+ case "quit": return this.#ports.dispose().then(() => workflowResult(command, "completed", "Shutdown complete"));
262
+ case "debug": return this.#debugWorkflow(request);
263
+ case "arminsayshi": return workflowResult(command, "completed", "Armin says hi");
264
+ case "dementedelves": return workflowResult(command, "completed", "Demented elves announcement");
265
+ }
266
+ }
267
+ #modelWorkflow({ request, session, runtime, argument, selection }) {
268
+ const reference = selection ?? argument;
269
+ if (!reference)
270
+ return Promise.resolve(workflowResult(request.command, "failed", "Model requires the owned model controller"));
271
+ const scopedModels = Array.isArray(session.scopedModels)
272
+ ? session.scopedModels.map(item => item.model)
273
+ : [];
274
+ // Compatibility: pinned Pi searches the session scope when one exists and refreshes catalogs only for an unscoped miss.
275
+ return this.#selectModel(request, reference, {
276
+ pool: scopedModels.length > 0 ? scopedModels : [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])],
277
+ refreshOnMiss: scopedModels.length === 0,
278
+ persist: request.persist === true,
279
+ onMiss: "requires-selection",
280
+ });
281
+ }
282
+ #modelsWorkflow({ request, runtime, argument, selection }) {
283
+ // Invariant: the bare dialog seeds its query from the argument; only an explicit selection switches the current session.
284
+ if (!selection)
285
+ return Promise.resolve(workflowResult(request.command, "requires-selection", "Select a model", argument || undefined, "silent"));
286
+ return this.#selectModel(request, selection, {
287
+ pool: [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])],
288
+ refreshOnMiss: true,
289
+ persist: false,
290
+ onMiss: "failed",
291
+ });
292
+ }
293
+ async #reloadWorkflow({ request, session, runtime }) {
294
+ const blocked = this.reloadBlockedResult();
295
+ if (blocked)
296
+ return blocked;
297
+ await requireCapability(session.reload, "reload").call(session);
298
+ await this.#ports.bindExtensionUiToSession();
299
+ const message = "Reloaded keybindings, extensions, skills, prompts, themes, and context files";
300
+ const modelError = runtime.services.modelRuntime.getError?.();
301
+ return workflowResult(request.command, "completed", message, undefined, "status", modelError ? [
302
+ { kind: "error", message: `models.json error: ${modelError}` },
303
+ { kind: "status", message },
304
+ ] : undefined);
305
+ }
306
+ async #debugWorkflow(request) {
307
+ const debugPath = join(this.#agentDir, "pi-debug.log");
308
+ await mkdir(dirname(debugPath), { recursive: true });
309
+ await writeFile(debugPath, `${JSON.stringify(this.#ports.snapshot(), null, 2)}\n`, "utf8");
310
+ return workflowResult(request.command, "completed", "✓ Debug log written", debugPath);
311
+ }
312
+ }
313
+ function thinkingWorkflow({ request, session, argument, selection }, ports) {
314
+ const requested = (selection ?? argument).trim();
315
+ if (!requested)
316
+ return workflowResult(request.command, "requires-selection", "Select a thinking level", undefined, "silent");
317
+ const available = (session.getAvailableThinkingLevels?.() ?? []).map(String);
318
+ // Compatibility: pinned Pi matches the argument case-insensitively and words the failure with the available levels.
319
+ const level = available.find(candidate => candidate.toLowerCase() === requested.toLowerCase());
320
+ if (level === undefined) {
321
+ return workflowResult(request.command, "failed", `Unknown thinking level "${requested}". Available levels: ${available.join(", ")}.`);
322
+ }
323
+ try {
324
+ session.setThinkingLevel(readThinkingLevel(level), { persist: request.persist === true });
325
+ }
326
+ catch (error) {
327
+ return workflowResult(request.command, "failed", error instanceof Error ? error.message : String(error));
328
+ }
329
+ ports.setThinkingLevel(readThinkingLevel(session.thinkingLevel));
330
+ ports.emitView();
331
+ return workflowResult(request.command, "completed", request.persist === true ? `Default thinking level: ${level}` : `Thinking level: ${level}`);
332
+ }
333
+ function scopedModelsWorkflow({ request, session, runtime, selection }) {
334
+ if (!selection)
335
+ return workflowResult(request.command, "failed", "Scoped models requires the owned scoped-model controller");
336
+ const [providerId, modelId] = selection.split("/", 2);
337
+ const model = providerId && modelId ? runtime.services.modelRuntime.getModel(providerId, modelId) : undefined;
338
+ if (!model)
339
+ return workflowResult(request.command, "failed", `Model is unavailable: ${selection}`);
340
+ requireCapability(session.setScopedModels, "setScopedModels").call(session, [{ model }]);
341
+ return workflowResult(request.command, "completed", `Enabled scoped model ${selection}`);
342
+ }
343
+ async function exportWorkflow({ request, session, argument }) {
344
+ const path = pathArgument(argument);
345
+ const file = path?.toLowerCase().endsWith(".jsonl")
346
+ ? requireCapability(session.exportToJsonl, "exportToJsonl").call(session, path)
347
+ : await requireCapability(session.exportToHtml, "exportToHtml").call(session, path);
348
+ return workflowResult(request.command, "completed", `Session exported to: ${String(file)}`);
349
+ }
350
+ async function importWorkflow({ request, runtime, argument }) {
351
+ const path = pathArgument(argument);
352
+ if (!path)
353
+ return workflowResult(request.command, "failed", "Usage: /import <path.jsonl>");
354
+ if (request.confirmed === undefined) {
355
+ return workflowConfirmation(request.command, `Replace current session with ${path}?`);
356
+ }
357
+ if (!request.confirmed)
358
+ return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
359
+ try {
360
+ const result = await requireCapability(runtime.importFromJsonl, "importFromJsonl").call(runtime, path, request.cwdOverride);
361
+ if (isRecord(result) && result.cancelled === true)
362
+ return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
363
+ return workflowResult(request.command, "completed", `Session imported from: ${path}`);
364
+ }
365
+ catch (error) {
366
+ const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
367
+ const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
368
+ if (fallbackCwd !== undefined && request.cwdOverride === undefined) {
369
+ const sessionCwd = stringProperty(issue, "sessionCwd") ?? "the session working directory";
370
+ return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`, fallbackCwd);
371
+ }
372
+ throw error;
373
+ }
374
+ }
375
+ async function copyWorkflow({ request, session }, copy) {
376
+ const text = requireCapability(session.getLastAssistantText, "getLastAssistantText").call(session);
377
+ if (typeof text !== "string" || text.length === 0)
378
+ return workflowResult(request.command, "completed", "", undefined, "silent");
379
+ const acknowledged = await copy(text);
380
+ return workflowResult(request.command, "completed", acknowledged ? "Copied last agent message to clipboard" : "Submitted last agent message to clipboard");
381
+ }
382
+ function nameWorkflow({ request, session, argument }, ports) {
383
+ const manager = session.sessionManager;
384
+ if (!argument) {
385
+ const current = manager?.getSessionName();
386
+ return typeof current === "string"
387
+ ? workflowResult(request.command, "completed", `Session name: ${current}`)
388
+ : workflowResult(request.command, "failed", "Usage: /name <name>", undefined, "warning");
389
+ }
390
+ requireCapability(session.setSessionName, "setSessionName").call(session, argument);
391
+ ports.emitView();
392
+ const actual = sessionNameOr(manager?.getSessionName(), argument);
393
+ return workflowResult(request.command, "completed", `Session name set: ${actual}`, actual === argument ? undefined : `Session name was normalized from ${JSON.stringify(argument)} to ${JSON.stringify(actual)}`);
394
+ }
395
+ function sessionInfoWorkflow({ request, session, runtime }) {
396
+ const manager = session.sessionManager;
397
+ const stats = requireCapability(session.getSessionStats, "getSessionStats").call(session);
398
+ const entries = manager?.getEntries();
399
+ return {
400
+ ...workflowResult(request.command, "completed", "Session Info"),
401
+ presentation: pinnedSessionInfoPresentation(stats, manager?.getSessionName(), Array.isArray(entries) ? entries : [], runtime.services.modelRuntime, {
402
+ mode: runtime.services.settingsManager?.getCacheWarmingMode?.(),
403
+ status: session.cacheWarmingStatus,
404
+ }),
405
+ };
406
+ }
407
+ async function forkWorkflow({ request, runtime, selection }) {
408
+ if (!selection)
409
+ return workflowResult(request.command, "failed", "Fork requires the owned user-message controller");
410
+ const result = await requireCapability(runtime.fork, "fork").call(runtime, selection, { position: "before" });
411
+ if (isRecord(result) && result.cancelled === true)
412
+ return workflowResult(request.command, "cancelled", "Fork cancelled", undefined, "silent");
413
+ return workflowResult(request.command, "completed", "Forked to new session");
414
+ }
415
+ async function cloneWorkflow({ request, session, runtime }) {
416
+ const leaf = session.sessionManager?.getLeafId?.();
417
+ if (typeof leaf !== "string")
418
+ return workflowResult(request.command, "completed", "Nothing to clone yet", undefined, "status");
419
+ const result = await requireCapability(runtime.fork, "fork").call(runtime, leaf, { position: "at" });
420
+ if (isRecord(result) && result.cancelled === true)
421
+ return workflowResult(request.command, "cancelled", "Clone cancelled", undefined, "silent");
422
+ return workflowResult(request.command, "completed", "Cloned to new session");
423
+ }
424
+ async function treeWorkflow({ request, session, selection }) {
425
+ if (!selection)
426
+ return workflowResult(request.command, "failed", "Tree navigation requires the owned tree controller");
427
+ const navigateTree = requireCapability(session.navigateTree, "navigateTree");
428
+ const result = request.treeSummary === undefined
429
+ ? await navigateTree.call(session, selection)
430
+ : await navigateTree.call(session, selection, {
431
+ summarize: request.treeSummary.summarize,
432
+ ...(request.treeSummary.customInstructions === undefined ? {} : { customInstructions: request.treeSummary.customInstructions }),
433
+ });
434
+ if (isRecord(result) && result.aborted === true)
435
+ return workflowResult(request.command, "cancelled", "Branch summarization cancelled", undefined, "status");
436
+ if (isRecord(result) && result.cancelled === true)
437
+ return workflowResult(request.command, "cancelled", "Navigation cancelled", undefined, "status");
438
+ const editorText = isRecord(result) && typeof result.editorText === "string" ? result.editorText : undefined;
439
+ return workflowResult(request.command, "completed", "Navigated to selected point", editorText);
440
+ }
441
+ function trustWorkflow({ request, runtime, selection }) {
442
+ if (!selection)
443
+ return workflowResult(request.command, "failed", "Trust requires the owned trust controller");
444
+ requireCapability(runtime.services.settingsManager?.setProjectTrusted, "setProjectTrusted").call(runtime.services.settingsManager, selection === "trust");
445
+ return workflowResult(request.command, "completed", selection === "trust" ? "Project trusted" : "Project trust removed");
446
+ }
447
+ async function newSessionWorkflow({ request, runtime }) {
448
+ const result = await runtime.newSession();
449
+ if (isRecord(result) && result.cancelled === true)
450
+ return workflowResult(request.command, "cancelled", "New session cancelled", undefined, "silent");
451
+ return workflowResult(request.command, "completed", "✓ New session started", undefined, "accent");
452
+ }
453
+ async function compactWorkflow({ request, session, argument }) {
454
+ try {
455
+ await session.compact(argument || undefined);
456
+ return workflowResult(request.command, "completed", "Compaction requested", undefined, "silent");
457
+ }
458
+ catch (error) {
459
+ return workflowResult(request.command, "failed", error instanceof Error ? error.message : String(error));
460
+ }
461
+ }
462
+ async function resumeWorkflow({ request, runtime, argument, selection }) {
463
+ if (!selection && !argument)
464
+ return workflowResult(request.command, "failed", "Resume requires the owned session controller");
465
+ const sessionPath = selection ?? argument;
466
+ try {
467
+ const result = await runtime.switchSession(sessionPath);
468
+ if (isRecord(result) && result.cancelled === true)
469
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
470
+ return workflowResult(request.command, "completed", "Resumed session");
471
+ }
472
+ catch (error) {
473
+ const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
474
+ const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
475
+ if (fallbackCwd === undefined)
476
+ throw error;
477
+ if (request.confirmed === undefined) {
478
+ const sessionCwd = stringProperty(issue, "sessionCwd") ?? "the session working directory";
479
+ return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`);
597
480
  }
481
+ if (!request.confirmed)
482
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "status");
483
+ const result = await runtime.switchSession(sessionPath, { cwdOverride: fallbackCwd });
484
+ if (isRecord(result) && result.cancelled === true)
485
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
486
+ return workflowResult(request.command, "completed", "Resumed session in current cwd");
598
487
  }
599
488
  }
600
489
  /**