@timurproko/a1 0.1.8-dev.226 → 0.1.8-dev.259

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 (94) hide show
  1. package/bin/guardian.js +4 -2
  2. package/bin/supervisor.js +1 -1
  3. package/bin/ui.js +44 -36
  4. package/bin/warmup.js +18 -9
  5. package/dist/cli/dispatch.d.ts +9 -1
  6. package/dist/cli/dispatch.js +73 -14
  7. package/dist/cli/package-messages.d.ts +26 -0
  8. package/dist/cli/package-messages.js +71 -0
  9. package/dist/cli/packages.d.ts +2 -1
  10. package/dist/cli/packages.js +12 -19
  11. package/dist/composition/owned-ui.d.ts +5 -2
  12. package/dist/composition/owned-ui.js +3 -1
  13. package/dist/contracts/agent-engine/package-ports.d.ts +6 -0
  14. package/dist/features/launch/intent.d.ts +5 -3
  15. package/dist/features/launch/intent.js +7 -2
  16. package/dist/features/owned-ui/index.d.ts +1 -0
  17. package/dist/features/owned-ui/index.js +1 -0
  18. package/dist/features/owned-ui/session-fork-prompt.d.ts +8 -0
  19. package/dist/features/owned-ui/session-fork-prompt.js +34 -0
  20. package/dist/foundation/launch-guardian/main.d.ts +2 -0
  21. package/dist/foundation/launch-guardian/main.js +8 -3
  22. package/dist/foundation/lifecycle/index.d.ts +2 -0
  23. package/dist/foundation/lifecycle/index.js +2 -0
  24. package/dist/foundation/lifecycle/paths.d.ts +2 -0
  25. package/dist/foundation/lifecycle/paths.js +9 -2
  26. package/dist/foundation/lifecycle/session-selection.d.ts +9 -0
  27. package/dist/foundation/lifecycle/session-selection.js +39 -0
  28. package/dist/foundation/lifecycle/supervisor-startup.d.ts +30 -0
  29. package/dist/foundation/lifecycle/supervisor-startup.js +106 -0
  30. package/dist/foundation/process-containment/darwin-process-inspector.d.ts +5 -0
  31. package/dist/foundation/process-containment/darwin-process-inspector.js +7 -0
  32. package/dist/foundation/process-containment/index.d.ts +1 -0
  33. package/dist/foundation/process-containment/index.js +1 -0
  34. package/dist/foundation/process-containment/windows-process-inspector.d.ts +2 -1
  35. package/dist/foundation/process-containment/windows-process-inspector.js +4 -2
  36. package/dist/foundation/release/bootstrap.d.ts +10 -3
  37. package/dist/foundation/release/bootstrap.js +58 -25
  38. package/dist/foundation/release/dependency-layer.js +11 -10
  39. package/dist/foundation/release/immutable-platform.d.ts +6 -0
  40. package/dist/foundation/release/immutable-platform.js +13 -0
  41. package/dist/foundation/release/index.d.ts +1 -0
  42. package/dist/foundation/release/index.js +1 -0
  43. package/dist/foundation/release/release-store.d.ts +1 -1
  44. package/dist/foundation/release/release-store.js +4 -2
  45. package/dist/foundation/release/restart-certification.d.ts +67 -0
  46. package/dist/foundation/release/restart-certification.js +211 -0
  47. package/dist/foundation/release/update.js +4 -4
  48. package/dist/foundation/release/warmup.js +1 -1
  49. package/dist/foundation/startup/startup-runtime.d.ts +2 -2
  50. package/dist/foundation/startup/startup-runtime.js +1 -1
  51. package/dist/foundation/supervision/main.d.ts +1 -1
  52. package/dist/foundation/supervision/main.js +60 -30
  53. package/dist/foundation/supervision/server.js +14 -1
  54. package/dist/integrations/pi/components/shell-footer-status.js +28 -12
  55. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +4 -1
  56. package/dist/integrations/pi/components/shell-selectors-dialogs.js +2 -1
  57. package/dist/integrations/pi/components/shell-shared-facade.d.ts +5 -0
  58. package/dist/integrations/pi/engine/adapter.d.ts +6 -0
  59. package/dist/integrations/pi/engine/adapter.js +354 -53
  60. package/dist/integrations/pi/engine/index.d.ts +1 -0
  61. package/dist/integrations/pi/engine/index.js +1 -0
  62. package/dist/integrations/pi/engine/package-integration.js +31 -25
  63. package/dist/integrations/pi/engine/runtime-integration.d.ts +3 -0
  64. package/dist/integrations/pi/engine/runtime-integration.js +11 -4
  65. package/dist/integrations/pi/engine/session-selection.d.ts +28 -0
  66. package/dist/integrations/pi/engine/session-selection.js +90 -0
  67. package/dist/integrations/pi/engine/workflows.d.ts +18 -1
  68. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +5 -1
  69. package/dist/integrations/pi/session-ui/session-shell-root.js +76 -24
  70. package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
  71. package/dist/integrations/pi/session-ui/session-shell.js +46 -14
  72. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +2 -1
  73. package/dist/integrations/pi/session-ui/session-viewport-controller.js +47 -18
  74. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +12 -1
  75. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +166 -26
  76. package/dist/native/darwin-arm64/manifest.json +4 -4
  77. package/dist/native/darwin-arm64/process-guardian +0 -0
  78. package/dist/native/linux-x64/manifest.json +1 -1
  79. package/dist/native/linux-x64/process-guardian +0 -0
  80. package/dist/native/win32-x64/manifest.json +2 -2
  81. package/dist/native/win32-x64/process-guardian.exe +0 -0
  82. package/dist/ui/components/index.d.ts +1 -0
  83. package/dist/ui/components/index.js +1 -0
  84. package/dist/ui/components/text-selection.js +8 -35
  85. package/dist/ui/components/transcript-viewport.d.ts +8 -2
  86. package/dist/ui/components/transcript-viewport.js +8 -6
  87. package/dist/ui/components/visible-hyperlinks.d.ts +19 -0
  88. package/dist/ui/components/visible-hyperlinks.js +64 -0
  89. package/docs/architecture/process-guardian-provenance.md +3 -1
  90. package/docs/architecture/ui-reference-provenance.md +2 -0
  91. package/docs/ci-release-runbook.md +1 -1
  92. package/docs/features/launch-profiles.md +17 -1
  93. package/docs/manual-ghost-link-baseline.md +89 -0
  94. package/package.json +1 -1
@@ -1,12 +1,13 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
+ import { existsSync } from "node:fs";
3
4
  import { mkdir, rm, writeFile } from "node:fs/promises";
4
5
  import { tmpdir } from "node:os";
5
6
  import { dirname, join, resolve } from "node:path";
6
7
  import { promisify } from "node:util";
7
8
  import { PRODUCT_IDENTITY } from "../../../product-identity.js";
8
9
  import { configureOwnedHttpDispatcher } from "./http-dispatcher.js";
9
- import { copyToClipboard, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
10
+ import { copyToClipboard, CredentialSynchronizationError, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
10
11
  import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiSnapshot, } from "../../../contracts/owned-ui/index.js";
11
12
  import { PINNED_PI_SETTINGS_CALLBACKS, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "./workflows.js";
12
13
  import { createPiRuntimeIntegration } from "./runtime-integration.js";
@@ -23,6 +24,50 @@ const execFileAsync = promisify(execFile);
23
24
  // input and makes an in-progress mouse selection appear frozen.
24
25
  const EVENT_DELIVERY_BATCH = 1;
25
26
  const TOOL_UPDATE_COALESCE_MS = 50;
27
+ const AUTH_REFRESH_TIMEOUT_MS = 15_000;
28
+ // Provenance: Pi 0.84.2 core/model-resolver.ts defaultModelPerProvider.
29
+ const PINNED_DEFAULT_MODEL_BY_PROVIDER = Object.freeze({
30
+ "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
31
+ "ant-ling": "Ring-2.6-1T",
32
+ anthropic: "claude-opus-4-8",
33
+ openai: "gpt-5.5",
34
+ "azure-openai-responses": "gpt-5.4",
35
+ "openai-codex": "gpt-5.5",
36
+ radius: "auto",
37
+ nvidia: "nvidia/nemotron-3-super-120b-a12b",
38
+ deepseek: "deepseek-v4-pro",
39
+ google: "gemini-3.1-pro-preview",
40
+ "google-vertex": "gemini-3.1-pro-preview",
41
+ "github-copilot": "gpt-5.4",
42
+ openrouter: "moonshotai/kimi-k2.6",
43
+ "vercel-ai-gateway": "zai/glm-5.1",
44
+ xai: "grok-4.5",
45
+ groq: "openai/gpt-oss-120b",
46
+ cerebras: "zai-glm-4.7",
47
+ zai: "glm-5.1",
48
+ "zai-coding-cn": "glm-5.1",
49
+ mistral: "devstral-medium-latest",
50
+ minimax: "MiniMax-M2.7",
51
+ "minimax-cn": "MiniMax-M2.7",
52
+ moonshotai: "kimi-k2.6",
53
+ "moonshotai-cn": "kimi-k2.6",
54
+ huggingface: "moonshotai/Kimi-K2.6",
55
+ fireworks: "accounts/fireworks/models/kimi-k2p6",
56
+ together: "moonshotai/Kimi-K2.6",
57
+ baseten: "zai-org/GLM-5.2",
58
+ opencode: "kimi-k2.6",
59
+ "opencode-go": "kimi-k2.6",
60
+ "kimi-coding": "kimi-for-coding",
61
+ "cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6",
62
+ "cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6",
63
+ "qwen-token-plan": "qwen3.7-max",
64
+ "qwen-token-plan-cn": "qwen3.7-max",
65
+ "qwen-token-plan-individual": "qwen3.8-max",
66
+ xiaomi: "mimo-v2.5-pro",
67
+ "xiaomi-token-plan-cn": "mimo-v2.5-pro",
68
+ "xiaomi-token-plan-ams": "mimo-v2.5-pro",
69
+ "xiaomi-token-plan-sgp": "mimo-v2.5-pro",
70
+ });
26
71
  const DEFAULT_SURFACE = {
27
72
  columns: 100,
28
73
  rows: 32,
@@ -37,6 +82,8 @@ export class PiEngineAdapter {
37
82
  #agentDir;
38
83
  #sessionId;
39
84
  #sessionPath;
85
+ #sessionSelection;
86
+ #sessionForkPrompt;
40
87
  #workflowHost;
41
88
  #workflowInteraction;
42
89
  #listeners = new Set();
@@ -99,6 +146,8 @@ export class PiEngineAdapter {
99
146
  this.#agentDir = options.agentDir ?? getAgentDir();
100
147
  this.#sessionId = options.sessionId ?? "owned-session-1";
101
148
  this.#sessionPath = options.sessionPath;
149
+ this.#sessionSelection = options.sessionSelection;
150
+ this.#sessionForkPrompt = options.sessionForkPrompt;
102
151
  this.#runtimeFactory = options.createRuntime ?? createDefaultPiRuntime;
103
152
  this.#checkPackageUpdates = options.checkPackageUpdates
104
153
  ?? (options.createRuntime
@@ -119,6 +168,9 @@ export class PiEngineAdapter {
119
168
  get sessionGeneration() {
120
169
  return this.#sessionGeneration;
121
170
  }
171
+ get cwd() {
172
+ return this.#runtime?.cwd ?? this.#cwd;
173
+ }
122
174
  get agentDir() {
123
175
  return this.#agentDir;
124
176
  }
@@ -137,7 +189,7 @@ export class PiEngineAdapter {
137
189
  || typeof manager.getSessionDir !== "function"
138
190
  || typeof manager.usesDefaultSessionDir !== "function"
139
191
  || manager.isPersisted() !== true
140
- || this.currentSessionFile() === null)
192
+ || !existsSync(this.currentSessionFile() ?? ""))
141
193
  return null;
142
194
  const sessionId = manager.getSessionId();
143
195
  const sessionDir = manager.getSessionDir();
@@ -156,6 +208,8 @@ export class PiEngineAdapter {
156
208
  agentDir: this.#agentDir,
157
209
  sessionId: this.#sessionId,
158
210
  ...(this.#sessionPath === undefined ? {} : { sessionPath: this.#sessionPath }),
211
+ ...(this.#sessionSelection === undefined ? {} : { sessionSelection: this.#sessionSelection }),
212
+ ...(this.#sessionForkPrompt === undefined ? {} : { sessionForkPrompt: this.#sessionForkPrompt }),
159
213
  ...(this.#projectTrustPrompt === undefined ? {} : { projectTrustPrompt: this.#projectTrustPrompt }),
160
214
  }).catch(error => {
161
215
  this.#lifecycle = "failed";
@@ -163,6 +217,7 @@ export class PiEngineAdapter {
163
217
  throw error;
164
218
  });
165
219
  this.#runtime = runtime;
220
+ this.#cwd = runtime.cwd ?? this.#cwd;
166
221
  this.#gitBranch = await readGitBranch(this.#cwd);
167
222
  this.#terminal = {
168
223
  ...this.#terminal,
@@ -458,7 +513,8 @@ export class PiEngineAdapter {
458
513
  const session = this.#requireWorkflowSession();
459
514
  const result = await requireCapability(session.cycleModel, "cycleModel").call(session, direction);
460
515
  if (!isRecord(result) || !isRecord(result.model)) {
461
- return workflowResult("model", "cancelled", "No other model is available");
516
+ const scoped = Array.isArray(session.scopedModels) && session.scopedModels.length > 0;
517
+ return workflowResult("model", "completed", scoped ? "Only one model in scope" : "Only one model available", undefined, "status");
462
518
  }
463
519
  const model = readModel(result.model);
464
520
  if (model)
@@ -466,7 +522,11 @@ export class PiEngineAdapter {
466
522
  if (result.thinkingLevel !== undefined)
467
523
  this.#thinkingLevel = readThinkingLevel(result.thinkingLevel);
468
524
  this.#emitView();
469
- return workflowResult("model", "completed", model ? `Selected model ${model.providerId}/${model.modelId}` : "Selected model");
525
+ const modelName = stringProperty(result.model, "name") ?? stringProperty(result.model, "id") ?? "model";
526
+ const reasoning = result.model.reasoning === true;
527
+ const thinking = this.#thinkingLevel;
528
+ const suffix = reasoning && thinking !== "off" ? ` (thinking: ${thinking})` : "";
529
+ return workflowResult("model", "completed", `Switched to ${modelName}${suffix}`, undefined, "status");
470
530
  }
471
531
  catch (error) {
472
532
  return workflowResult("model", "failed", error instanceof Error ? error.message : String(error));
@@ -875,7 +935,13 @@ export class PiEngineAdapter {
875
935
  const message = error instanceof Error ? error.message : String(error);
876
936
  const contextualMessage = request.command === "export"
877
937
  ? `Failed to export session: ${message}`
878
- : request.command === "reload" ? `Reload failed: ${message}` : message;
938
+ : request.command === "import"
939
+ ? `Failed to import session: ${message}`
940
+ : request.command === "new"
941
+ ? `Failed to create session: ${message}`
942
+ : request.command === "resume"
943
+ ? `Failed to resume session: ${message}`
944
+ : request.command === "reload" ? `Reload failed: ${message}` : message;
879
945
  return workflowResult(request.command, "failed", contextualMessage);
880
946
  }
881
947
  }
@@ -994,16 +1060,60 @@ export class PiEngineAdapter {
994
1060
  const reference = selection ?? argument;
995
1061
  if (!reference)
996
1062
  return workflowResult(request.command, "failed", "Model requires the owned model controller");
997
- const [providerId, modelId] = reference.split("/", 2);
998
- if (!providerId || !modelId)
999
- return workflowResult(request.command, "failed", "Model requires provider/model");
1000
- const model = runtime.services.modelRuntime.getModel(providerId, modelId);
1001
- if (!model)
1002
- return workflowResult(request.command, "failed", `Model is unavailable: ${reference}`);
1063
+ const scopedModels = Array.isArray(session.scopedModels)
1064
+ ? session.scopedModels.map(item => item.model)
1065
+ : [];
1066
+ let availableModels = scopedModels.length > 0
1067
+ ? scopedModels
1068
+ : [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
1069
+ let model = findExactWorkflowModel(reference, availableModels);
1070
+ const messages = [];
1071
+ if (model === undefined && scopedModels.length === 0) {
1072
+ messages.push({ kind: "status", message: "Refreshing model catalogs…" });
1073
+ const controller = new AbortController();
1074
+ let timedOut = false;
1075
+ const timeout = setTimeout(() => {
1076
+ timedOut = true;
1077
+ controller.abort();
1078
+ }, AUTH_REFRESH_TIMEOUT_MS);
1079
+ try {
1080
+ const refreshed = await runtime.services.modelRuntime.refresh?.({ signal: controller.signal });
1081
+ if (isRecord(refreshed) && refreshed.aborted === true && timedOut) {
1082
+ messages.push({ kind: "warning", message: "Model refresh timed out; searching cached models." });
1083
+ }
1084
+ else if (isRecord(refreshed) && refreshed.errors instanceof Map && refreshed.errors.size > 0) {
1085
+ messages.push({ kind: "warning", message: `Could not refresh ${[...refreshed.errors.keys()].join(", ")}; searching cached models.` });
1086
+ }
1087
+ }
1088
+ catch (error) {
1089
+ messages.push({
1090
+ kind: "warning",
1091
+ message: timedOut
1092
+ ? "Model refresh timed out; searching cached models."
1093
+ : `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`,
1094
+ });
1095
+ }
1096
+ finally {
1097
+ clearTimeout(timeout);
1098
+ }
1099
+ availableModels = [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
1100
+ model = findExactWorkflowModel(reference, availableModels);
1101
+ }
1102
+ if (model === undefined) {
1103
+ return {
1104
+ ...workflowResult(request.command, "requires-selection", "Select a model", reference, "silent"),
1105
+ ...(messages.length === 0 ? {} : { messages: Object.freeze(messages) }),
1106
+ };
1107
+ }
1003
1108
  await session.setModel(model);
1109
+ const providerId = stringProperty(model, "provider") ?? "unknown";
1110
+ const modelId = stringProperty(model, "id") ?? reference;
1004
1111
  this.#activeModel = { providerId, modelId, displayName: stringProperty(model, "name") ?? modelId };
1005
1112
  this.#emitView();
1006
- return workflowResult(request.command, "completed", `Model: ${modelId}`);
1113
+ const resultMessage = { kind: "status", message: `Model: ${modelId}` };
1114
+ return messages.length === 0
1115
+ ? workflowResult(request.command, "completed", resultMessage.message)
1116
+ : workflowResult(request.command, "completed", resultMessage.message, undefined, "status", [...messages, resultMessage]);
1007
1117
  }
1008
1118
  case "scoped-models": {
1009
1119
  if (!selection)
@@ -1030,27 +1140,71 @@ export class PiEngineAdapter {
1030
1140
  return workflowConfirmation(request.command, `Replace current session with ${path}?`);
1031
1141
  }
1032
1142
  if (!request.confirmed)
1033
- return workflowResult(request.command, "cancelled", "Import cancelled");
1034
- const result = await requireCapability(runtime.importFromJsonl, "importFromJsonl").call(runtime, path);
1035
- if (isRecord(result) && result.cancelled === true)
1036
- return workflowResult(request.command, "cancelled", "Import cancelled");
1037
- return workflowResult(request.command, "completed", `Session imported from: ${path}`);
1143
+ return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
1144
+ try {
1145
+ const result = await requireCapability(runtime.importFromJsonl, "importFromJsonl").call(runtime, path, request.cwdOverride);
1146
+ if (isRecord(result) && result.cancelled === true)
1147
+ return workflowResult(request.command, "cancelled", "Import cancelled", undefined, "status");
1148
+ return workflowResult(request.command, "completed", `Session imported from: ${path}`);
1149
+ }
1150
+ catch (error) {
1151
+ const issue = isRecord(error) && isRecord(error.issue) ? error.issue : undefined;
1152
+ const fallbackCwd = issue === undefined ? undefined : stringProperty(issue, "fallbackCwd");
1153
+ if (fallbackCwd !== undefined && request.cwdOverride === undefined) {
1154
+ const sessionCwd = stringProperty(issue, "sessionCwd") ?? "the session working directory";
1155
+ return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`, fallbackCwd);
1156
+ }
1157
+ throw error;
1158
+ }
1038
1159
  }
1039
1160
  case "share": {
1040
- const auth = await this.#workflowHost.runCommand("gh", ["auth", "status"]).catch(error => {
1041
- throw new Error(`GitHub CLI is unavailable or not logged in: ${error instanceof Error ? error.message : String(error)}`);
1042
- });
1043
- if (auth.stderr && !auth.stdout)
1044
- throw new Error(auth.stderr.trim());
1161
+ const signal = request.signal;
1162
+ if (signal?.aborted)
1163
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1164
+ let auth;
1165
+ try {
1166
+ auth = await this.#workflowHost.runCommand("gh", ["auth", "status"], signal === undefined ? undefined : { signal });
1167
+ }
1168
+ catch (error) {
1169
+ if (signal?.aborted || isAbortError(error))
1170
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1171
+ const message = commandMissing(error)
1172
+ ? "GitHub CLI (gh) is not installed. Install it from https://cli.github.com/"
1173
+ : "GitHub CLI is not logged in. Run 'gh auth login' first.";
1174
+ return workflowResult(request.command, "failed", message);
1175
+ }
1176
+ if (auth.stderr && !auth.stdout) {
1177
+ return workflowResult(request.command, "failed", "GitHub CLI is not logged in. Run 'gh auth login' first.");
1178
+ }
1045
1179
  const temporary = join(tmpdir(), `${PRODUCT_IDENTITY.filesystem.temporaryPrefix}pi-session-${process.pid}.html`);
1046
1180
  try {
1047
- await requireCapability(session.exportToHtml, "exportToHtml").call(session, temporary);
1048
- const gist = await this.#workflowHost.runCommand("gh", ["gist", "create", "--public=false", temporary]);
1181
+ try {
1182
+ await requireCapability(session.exportToHtml, "exportToHtml").call(session, temporary);
1183
+ }
1184
+ catch (error) {
1185
+ return workflowResult(request.command, "failed", `Failed to export session: ${errorMessage(error, "Unknown error")}`);
1186
+ }
1187
+ if (signal?.aborted)
1188
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1189
+ let gist;
1190
+ try {
1191
+ gist = await this.#workflowHost.runCommand("gh", ["gist", "create", "--public=false", temporary], signal === undefined ? undefined : { signal });
1192
+ }
1193
+ catch (error) {
1194
+ if (signal?.aborted || isAbortError(error))
1195
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1196
+ return workflowResult(request.command, "failed", `Failed to create gist: ${errorMessage(error, "Unknown error")}`);
1197
+ }
1198
+ if (signal?.aborted)
1199
+ return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1200
+ if (gist.stderr && !gist.stdout) {
1201
+ return workflowResult(request.command, "failed", `Failed to create gist: ${gist.stderr.trim() || "Unknown error"}`);
1202
+ }
1049
1203
  const gistUrl = gist.stdout.trim();
1050
1204
  const gistId = gistUrl.split("/").at(-1);
1051
1205
  if (!gistId)
1052
- throw new Error("Failed to parse gist ID from gh output");
1053
- return workflowResult(request.command, "completed", `Share URL: https://pi.gptscript.ai/gist/${gistId}`, gistUrl);
1206
+ return workflowResult(request.command, "failed", "Failed to parse gist ID from gh output");
1207
+ return workflowResult(request.command, "completed", `Share URL: ${shareViewerUrl(gistId)}`, gistUrl);
1054
1208
  }
1055
1209
  finally {
1056
1210
  await rm(temporary, { force: true });
@@ -1069,7 +1223,7 @@ export class PiEngineAdapter {
1069
1223
  const current = manager?.getSessionName();
1070
1224
  return typeof current === "string"
1071
1225
  ? workflowResult(request.command, "completed", `Session name: ${current}`)
1072
- : workflowResult(request.command, "failed", "Usage: /name <name>");
1226
+ : workflowResult(request.command, "failed", "Usage: /name <name>", undefined, "warning");
1073
1227
  }
1074
1228
  requireCapability(session.setSessionName, "setSessionName").call(session, argument);
1075
1229
  const normalized = manager?.getSessionName();
@@ -1096,17 +1250,17 @@ export class PiEngineAdapter {
1096
1250
  return workflowResult(request.command, "failed", "Fork requires the owned user-message controller");
1097
1251
  const result = await requireCapability(runtime.fork, "fork").call(runtime, selection, { position: "before" });
1098
1252
  if (isRecord(result) && result.cancelled === true)
1099
- return workflowResult(request.command, "cancelled", "Fork cancelled");
1253
+ return workflowResult(request.command, "cancelled", "Fork cancelled", undefined, "silent");
1100
1254
  return workflowResult(request.command, "completed", "Forked to new session");
1101
1255
  }
1102
1256
  case "clone": {
1103
1257
  const manager = session.sessionManager;
1104
1258
  const leaf = manager?.getLeafId?.();
1105
1259
  if (typeof leaf !== "string")
1106
- return workflowResult(request.command, "failed", "No session position is available to clone");
1260
+ return workflowResult(request.command, "completed", "Nothing to clone yet", undefined, "status");
1107
1261
  const result = await requireCapability(runtime.fork, "fork").call(runtime, leaf, { position: "at" });
1108
1262
  if (isRecord(result) && result.cancelled === true)
1109
- return workflowResult(request.command, "cancelled", "Clone cancelled");
1263
+ return workflowResult(request.command, "cancelled", "Clone cancelled", undefined, "silent");
1110
1264
  return workflowResult(request.command, "completed", "Cloned to new session");
1111
1265
  }
1112
1266
  case "tree": {
@@ -1120,9 +1274,9 @@ export class PiEngineAdapter {
1120
1274
  ...(request.treeSummary.customInstructions === undefined ? {} : { customInstructions: request.treeSummary.customInstructions }),
1121
1275
  });
1122
1276
  if (isRecord(result) && result.aborted === true)
1123
- return workflowResult(request.command, "cancelled", "Branch summarization cancelled");
1277
+ return workflowResult(request.command, "cancelled", "Branch summarization cancelled", undefined, "status");
1124
1278
  if (isRecord(result) && result.cancelled === true)
1125
- return workflowResult(request.command, "cancelled", "Navigation cancelled");
1279
+ return workflowResult(request.command, "cancelled", "Navigation cancelled", undefined, "status");
1126
1280
  return workflowResult(request.command, "completed", "Navigated to selected point");
1127
1281
  }
1128
1282
  case "trust": {
@@ -1157,6 +1311,7 @@ export class PiEngineAdapter {
1157
1311
  const authType = authTypeValue === "api_key" ? "api_key" : "oauth";
1158
1312
  const provider = modelRuntime.getProvider?.(providerId);
1159
1313
  const providerName = stringProperty(provider, "name") ?? providerId;
1314
+ const previousModel = session.model;
1160
1315
  this.#workflowInteraction.startLogin?.({ providerId, providerName, authType });
1161
1316
  try {
1162
1317
  await requireCapability(modelRuntime.login, "login").call(modelRuntime, providerId, authType, {
@@ -1194,25 +1349,40 @@ export class PiEngineAdapter {
1194
1349
  });
1195
1350
  }
1196
1351
  catch (error) {
1197
- if (error instanceof Error && error.message === "Login cancelled")
1198
- return workflowResult(request.command, "cancelled", "Login cancelled");
1199
- throw error;
1352
+ if (error instanceof Error && error.message === "Login cancelled") {
1353
+ return workflowResult(request.command, "cancelled", "Login cancelled", undefined, "silent");
1354
+ }
1355
+ const detail = error instanceof Error ? error.message : String(error);
1356
+ const actionLabel = authType === "api_key" ? `Saved API key for ${providerName}` : `Logged in to ${providerName}`;
1357
+ const message = error instanceof CredentialSynchronizationError
1358
+ ? `${actionLabel}, but local model state could not be synchronized: ${detail}`
1359
+ : authType === "api_key"
1360
+ ? `Failed to save API key for ${providerName}: ${detail}`
1361
+ : `Failed to login to ${providerName}: ${detail}`;
1362
+ return workflowResult(request.command, "failed", message);
1200
1363
  }
1201
1364
  finally {
1202
1365
  this.#workflowInteraction.finishLogin?.();
1203
1366
  }
1204
- this.#reconcileActiveModelAvailability();
1205
- this.#emitView();
1206
- return workflowResult(request.command, "completed", `Logged in to ${providerName}. Credentials saved to ${join(this.#agentDir, "auth.json")}`);
1367
+ return await this.#completeProviderAuthentication(providerId, providerName, authType, previousModel);
1207
1368
  }
1208
1369
  case "logout": {
1209
1370
  if (!selection)
1210
1371
  return workflowResult(request.command, "failed", "Logout requires the owned authentication controller");
1211
1372
  const [credentialType = "oauth", providerId = selection] = selection.includes(":") ? selection.split(":", 2) : ["oauth", selection];
1212
1373
  const modelRuntime = runtime.services.modelRuntime;
1213
- await requireCapability(modelRuntime.logout, "logout").call(modelRuntime, providerId, { signal: AbortSignal.timeout(15_000) });
1214
1374
  const provider = modelRuntime.getProvider?.(providerId);
1215
1375
  const providerName = stringProperty(provider, "name") ?? providerId;
1376
+ try {
1377
+ await requireCapability(modelRuntime.logout, "logout").call(modelRuntime, providerId, { signal: AbortSignal.timeout(15_000) });
1378
+ }
1379
+ catch (error) {
1380
+ const detail = error instanceof Error ? error.message : String(error);
1381
+ const message = error instanceof CredentialSynchronizationError
1382
+ ? `Credentials removed for ${providerName}, but local model state could not be synchronized: ${detail}`
1383
+ : `Logout failed: ${detail}`;
1384
+ return workflowResult(request.command, "failed", message);
1385
+ }
1216
1386
  this.#reconcileActiveModelAvailability();
1217
1387
  this.#emitView();
1218
1388
  return workflowResult(request.command, "completed", credentialType === "api_key"
@@ -1222,12 +1392,17 @@ export class PiEngineAdapter {
1222
1392
  case "new": {
1223
1393
  const result = await runtime.newSession();
1224
1394
  if (isRecord(result) && result.cancelled === true)
1225
- return workflowResult(request.command, "cancelled", "New session cancelled");
1226
- return workflowResult(request.command, "completed", "✓ New session started");
1395
+ return workflowResult(request.command, "cancelled", "New session cancelled", undefined, "silent");
1396
+ return workflowResult(request.command, "completed", "✓ New session started", undefined, "accent");
1227
1397
  }
1228
1398
  case "compact": {
1229
- await session.compact(argument || undefined);
1230
- return workflowResult(request.command, "completed", "Compaction requested");
1399
+ try {
1400
+ await session.compact(argument || undefined);
1401
+ return workflowResult(request.command, "completed", "Compaction requested", undefined, "silent");
1402
+ }
1403
+ catch (error) {
1404
+ return workflowResult(request.command, "failed", error instanceof Error ? error.message : String(error), undefined, "silent");
1405
+ }
1231
1406
  }
1232
1407
  case "resume": {
1233
1408
  if (!selection && !argument)
@@ -1236,7 +1411,7 @@ export class PiEngineAdapter {
1236
1411
  try {
1237
1412
  const result = await runtime.switchSession(sessionPath);
1238
1413
  if (isRecord(result) && result.cancelled === true)
1239
- return workflowResult(request.command, "cancelled", "Resume cancelled");
1414
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
1240
1415
  return workflowResult(request.command, "completed", "Resumed session");
1241
1416
  }
1242
1417
  catch (error) {
@@ -1249,18 +1424,18 @@ export class PiEngineAdapter {
1249
1424
  return workflowConfirmation(request.command, `cwd from session file does not exist\n${sessionCwd}\n\ncontinue in current cwd\n${fallbackCwd}`);
1250
1425
  }
1251
1426
  if (!request.confirmed)
1252
- return workflowResult(request.command, "cancelled", "Resume cancelled");
1427
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "status");
1253
1428
  const result = await runtime.switchSession(sessionPath, { cwdOverride: fallbackCwd });
1254
1429
  if (isRecord(result) && result.cancelled === true)
1255
- return workflowResult(request.command, "cancelled", "Resume cancelled");
1430
+ return workflowResult(request.command, "cancelled", "Resume cancelled", undefined, "silent");
1256
1431
  return workflowResult(request.command, "completed", "Resumed session in current cwd");
1257
1432
  }
1258
1433
  }
1259
1434
  case "reload": {
1260
1435
  if (session.isStreaming)
1261
- return workflowResult(request.command, "failed", "Wait for the current response to finish before reloading.");
1436
+ return workflowResult(request.command, "failed", "Wait for the current response to finish before reloading.", undefined, "warning");
1262
1437
  if (session.isCompacting)
1263
- return workflowResult(request.command, "failed", "Wait for compaction to finish before reloading.");
1438
+ return workflowResult(request.command, "failed", "Wait for compaction to finish before reloading.", undefined, "warning");
1264
1439
  await requireCapability(session.reload, "reload").call(session);
1265
1440
  await this.#bindExtensionUiToSession();
1266
1441
  return workflowResult(request.command, "completed", "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
@@ -1281,6 +1456,91 @@ export class PiEngineAdapter {
1281
1456
  return workflowResult(request.command, "completed", "Demented elves announcement");
1282
1457
  }
1283
1458
  }
1459
+ async #completeProviderAuthentication(providerId, providerName, authType, previousModel) {
1460
+ const session = this.#requireWorkflowSession();
1461
+ const modelRuntime = this.#runtime?.services.modelRuntime;
1462
+ if (!modelRuntime)
1463
+ throw new Error("engine runtime is unavailable");
1464
+ const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
1465
+ let selectedModelId;
1466
+ let selectionError;
1467
+ if (isUnknownModel(previousModel)) {
1468
+ const available = modelRuntime.getAvailableSnapshot?.() ?? [];
1469
+ const providerModels = available.filter(model => stringProperty(model, "provider") === providerId);
1470
+ const defaultModelId = PINNED_DEFAULT_MODEL_BY_PROVIDER[providerId];
1471
+ if (defaultModelId === undefined) {
1472
+ selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
1473
+ }
1474
+ else if (providerModels.length === 0) {
1475
+ selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
1476
+ }
1477
+ else {
1478
+ const selectedModel = providerModels.find(model => stringProperty(model, "id") === defaultModelId);
1479
+ if (selectedModel === undefined) {
1480
+ selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
1481
+ }
1482
+ else {
1483
+ try {
1484
+ await session.setModel(selectedModel);
1485
+ selectedModelId = stringProperty(selectedModel, "id") ?? defaultModelId;
1486
+ this.#activeModel = {
1487
+ providerId,
1488
+ modelId: selectedModelId,
1489
+ displayName: stringProperty(selectedModel, "name") ?? selectedModelId,
1490
+ };
1491
+ }
1492
+ catch (error) {
1493
+ selectionError = `${actionLabel}, but selecting its default model failed: ${error instanceof Error ? error.message : String(error)}. Use /model to select a model.`;
1494
+ }
1495
+ }
1496
+ }
1497
+ }
1498
+ this.#reconcileActiveModelAvailability();
1499
+ this.#emitView();
1500
+ const status = `${actionLabel}.${selectedModelId ? ` Selected ${selectedModelId}.` : ""} Credentials saved to ${join(this.#agentDir, "auth.json")}`;
1501
+ const messages = [
1502
+ { kind: "status", message: status },
1503
+ ...(selectionError === undefined ? [] : [{ kind: "error", message: selectionError }]),
1504
+ ];
1505
+ this.#scheduleAuthenticatedProviderRefresh(providerId, actionLabel);
1506
+ return workflowResult("login", "completed", status, undefined, "status", messages);
1507
+ }
1508
+ #scheduleAuthenticatedProviderRefresh(providerId, actionLabel) {
1509
+ const runtime = this.#runtime;
1510
+ const refresh = runtime?.services.modelRuntime.refresh;
1511
+ if (!runtime || typeof refresh !== "function")
1512
+ return;
1513
+ const generation = this.#sessionGeneration;
1514
+ const publish = this.#workflowInteraction.publish;
1515
+ const controller = new AbortController();
1516
+ const timeout = setTimeout(() => controller.abort(), AUTH_REFRESH_TIMEOUT_MS);
1517
+ // Compatibility: post-authentication refresh starts on the next turn so its notices follow the saved-credential status.
1518
+ void new Promise(resolve => setTimeout(resolve, 0))
1519
+ .then(() => refresh.call(runtime.services.modelRuntime, { providers: [providerId], signal: controller.signal }))
1520
+ .then(result => {
1521
+ if (this.#disposed || this.#sessionGeneration !== generation)
1522
+ return;
1523
+ if (isRecord(result) && result.aborted === true) {
1524
+ publish?.({ kind: "warning", message: `${actionLabel}, but its model catalog refresh timed out; using cached models.` });
1525
+ }
1526
+ else if (isRecord(result) && result.errors instanceof Map && result.errors.size > 0) {
1527
+ publish?.({ kind: "warning", message: `${actionLabel}, but its model catalog could not be refreshed; using cached models.` });
1528
+ }
1529
+ this.#reconcileActiveModelAvailability();
1530
+ this.#emitView();
1531
+ })
1532
+ .catch(error => {
1533
+ if (this.#disposed || this.#sessionGeneration !== generation)
1534
+ return;
1535
+ publish?.({
1536
+ kind: "warning",
1537
+ message: controller.signal.aborted
1538
+ ? `${actionLabel}, but its model catalog refresh timed out; using cached models.`
1539
+ : `${actionLabel}, but its model catalog could not be refreshed: ${error instanceof Error ? error.message : String(error)}`,
1540
+ });
1541
+ })
1542
+ .finally(() => clearTimeout(timeout));
1543
+ }
1284
1544
  #requireWorkflowSession() {
1285
1545
  if (this.#disposed || !this.#runtime || !this.#session)
1286
1546
  throw new Error("engine adapter is not running");
@@ -2204,6 +2464,8 @@ async function createDefaultPiRuntime(input) {
2204
2464
  cwd: input.cwd,
2205
2465
  agentDir: input.agentDir,
2206
2466
  ...(input.sessionPath === undefined ? {} : { sessionPath: input.sessionPath }),
2467
+ ...(input.sessionSelection === undefined ? {} : { sessionSelection: input.sessionSelection }),
2468
+ ...(input.sessionForkPrompt === undefined ? {} : { sessionForkPrompt: input.sessionForkPrompt }),
2207
2469
  ...(input.projectTrustPrompt === undefined ? {} : { projectTrustPrompt: input.projectTrustPrompt }),
2208
2470
  });
2209
2471
  }
@@ -2328,8 +2590,8 @@ function pinnedCacheWaste(entries, modelRuntime) {
2328
2590
  function defaultWorkflowHost() {
2329
2591
  return {
2330
2592
  copyText: copyToClipboard,
2331
- async runCommand(command, arguments_) {
2332
- const result = await execFileAsync(command, [...arguments_], { encoding: "utf8" });
2593
+ async runCommand(command, arguments_, options) {
2594
+ const result = await execFileAsync(command, [...arguments_], { encoding: "utf8", signal: options?.signal });
2333
2595
  return { stdout: result.stdout, stderr: result.stderr };
2334
2596
  },
2335
2597
  readChangelog: async () => "No changelog entries found.",
@@ -2365,14 +2627,22 @@ function workflowLoginNotification(event) {
2365
2627
  }
2366
2628
  return { type: event.type === "waiting" ? "waiting" : "progress", message };
2367
2629
  }
2368
- function workflowResult(command, outcome, message, detail) {
2369
- return { command, outcome, message, ...(detail === undefined ? {} : { detail }) };
2630
+ function workflowResult(command, outcome, message, detail, messageKind, messages) {
2631
+ return {
2632
+ command,
2633
+ outcome,
2634
+ message,
2635
+ ...(detail === undefined ? {} : { detail }),
2636
+ ...(messageKind === undefined ? {} : { messageKind }),
2637
+ ...(messages === undefined ? {} : { messages: Object.freeze([...messages]) }),
2638
+ };
2370
2639
  }
2371
- function workflowConfirmation(command, message) {
2640
+ function workflowConfirmation(command, message, detail) {
2372
2641
  return {
2373
2642
  command,
2374
2643
  outcome: "requires-confirmation",
2375
2644
  message,
2645
+ ...(detail === undefined ? {} : { detail }),
2376
2646
  selectorTitle: "Confirm",
2377
2647
  options: [
2378
2648
  { id: "yes", label: "Yes" },
@@ -2380,6 +2650,19 @@ function workflowConfirmation(command, message) {
2380
2650
  ],
2381
2651
  };
2382
2652
  }
2653
+ function errorMessage(error, fallback) {
2654
+ return error instanceof Error ? error.message : fallback;
2655
+ }
2656
+ function commandMissing(error) {
2657
+ return isRecord(error) && error.code === "ENOENT";
2658
+ }
2659
+ function isAbortError(error) {
2660
+ return isRecord(error) && (error.name === "AbortError" || error.code === "ABORT_ERR");
2661
+ }
2662
+ function shareViewerUrl(gistId) {
2663
+ const baseUrl = process.env.PI_SHARE_VIEWER_URL || "https://pi.dev/session/";
2664
+ return `${baseUrl}#${gistId}`;
2665
+ }
2383
2666
  function workflowOptions(value, idKey, labelKey) {
2384
2667
  if (!Array.isArray(value))
2385
2668
  return [];
@@ -2598,6 +2881,24 @@ function stringProperty(value, key) {
2598
2881
  const item = value[key];
2599
2882
  return typeof item === "string" && item.length > 0 ? item : undefined;
2600
2883
  }
2884
+ function isUnknownModel(value) {
2885
+ return isRecord(value)
2886
+ && value.provider === "unknown"
2887
+ && value.id === "unknown"
2888
+ && value.api === "unknown";
2889
+ }
2890
+ function findExactWorkflowModel(reference, models) {
2891
+ const normalized = reference.trim().toLowerCase();
2892
+ const canonical = models.filter(model => {
2893
+ const provider = stringProperty(model, "provider");
2894
+ const id = stringProperty(model, "id");
2895
+ return provider !== undefined && id !== undefined && `${provider}/${id}`.toLowerCase() === normalized;
2896
+ });
2897
+ if (canonical.length === 1)
2898
+ return canonical[0];
2899
+ const bare = models.filter(model => stringProperty(model, "id")?.toLowerCase() === normalized);
2900
+ return bare.length === 1 ? bare[0] : undefined;
2901
+ }
2601
2902
  /**
2602
2903
  * Some ecosystem extensions retain a `pi-<name>` slash-command alias beside
2603
2904
  * their unprefixed command. A1 presents the product-neutral command once while