@timurproko/a1 0.1.8-dev.299 → 0.1.8-dev.322

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 (73) hide show
  1. package/README.md +16 -4
  2. package/bin/guardian.js +4 -3
  3. package/bin/ui.js +4 -2
  4. package/bin/update-recovery.js +3 -1
  5. package/dist/composition/owned-ui.js +2 -1
  6. package/dist/features/launch/profile-paths.d.ts +2 -0
  7. package/dist/features/launch/profile-paths.js +11 -0
  8. package/dist/foundation/launch-context/index.d.ts +29 -0
  9. package/dist/foundation/launch-context/index.js +83 -0
  10. package/dist/foundation/launch-guardian/main.js +4 -3
  11. package/dist/foundation/release/bootstrap.d.ts +1 -1
  12. package/dist/foundation/release/bootstrap.js +18 -12
  13. package/dist/foundation/release/cohort-state.d.ts +1 -0
  14. package/dist/foundation/release/cohort-state.js +4 -0
  15. package/dist/foundation/release/release-gc.js +2 -2
  16. package/dist/foundation/release/release-store.js +10 -3
  17. package/dist/foundation/release/release.d.ts +3 -1
  18. package/dist/foundation/release/release.js +7 -3
  19. package/dist/foundation/release/restart-certification.d.ts +1 -0
  20. package/dist/foundation/release/restart-certification.js +5 -0
  21. package/dist/foundation/release/update-recovery.d.ts +2 -0
  22. package/dist/foundation/release/update-recovery.js +4 -0
  23. package/dist/foundation/release/update.js +5 -0
  24. package/dist/foundation/release/warmup.d.ts +1 -1
  25. package/dist/foundation/release/warmup.js +5 -4
  26. package/dist/foundation/startup/startup-runtime.js +13 -11
  27. package/dist/foundation/supervision/main.js +8 -7
  28. package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -0
  29. package/dist/integrations/pi/components/owned-editor-ux.js +54 -3
  30. package/dist/integrations/pi/components/shell-editor-autocomplete.js +3 -0
  31. package/dist/integrations/pi/components/shell-presenters-info.d.ts +9 -2
  32. package/dist/integrations/pi/components/shell-presenters-info.js +35 -4
  33. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +3 -2
  34. package/dist/integrations/pi/components/shell-selectors-dialogs.js +8 -5
  35. package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
  36. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +12 -0
  37. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +25 -2
  38. package/dist/integrations/pi/engine/adapter.d.ts +13 -1
  39. package/dist/integrations/pi/engine/adapter.js +86 -25
  40. package/dist/integrations/pi/engine/changelog.d.ts +2 -0
  41. package/dist/integrations/pi/engine/changelog.js +63 -0
  42. package/dist/integrations/pi/engine/resources/changelog.json +12 -0
  43. package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
  44. package/dist/integrations/pi/engine/workflows.d.ts +1 -1
  45. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +3 -1
  46. package/dist/integrations/pi/session-ui/prompt-chips.js +37 -13
  47. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +25 -0
  48. package/dist/integrations/pi/session-ui/session-shell-root.js +65 -74
  49. package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
  50. package/dist/integrations/pi/session-ui/session-shell.js +58 -7
  51. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  52. package/dist/integrations/pi/session-ui/session-viewport-controller.js +21 -15
  53. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +7 -0
  54. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +7 -6
  55. package/dist/native/darwin-arm64/manifest.json +1 -1
  56. package/dist/native/linux-x64/manifest.json +1 -1
  57. package/dist/native/win32-x64/manifest.json +2 -2
  58. package/dist/native/win32-x64/process-guardian.exe +0 -0
  59. package/dist/product-identity.d.ts +1 -1
  60. package/dist/product-identity.js +2 -35
  61. package/dist/product-identity.json +0 -22
  62. package/dist/ui/components/transcript-viewport.d.ts +8 -0
  63. package/dist/ui/components/transcript-viewport.js +23 -2
  64. package/dist/ui/components/visible-hyperlinks.d.ts +7 -0
  65. package/dist/ui/components/visible-hyperlinks.js +1 -0
  66. package/dist/ui/settings/declarations.js +1 -0
  67. package/dist/ui/settings/sections.js +11 -1
  68. package/docs/architecture/boundaries.md +2 -1
  69. package/docs/architecture/internal-naming.md +89 -0
  70. package/docs/architecture/resource-and-data-policy.md +9 -4
  71. package/docs/architecture/toolchain.md +15 -1
  72. package/docs/features/prompt-history.md +39 -11
  73. package/package.json +8 -3
@@ -1,10 +1,28 @@
1
1
  // Mechanically adapted from Pi commit 914cf14
2
2
  // packages/coding-agent/src/modes/interactive/components/earendil-announcement.ts (MIT).
3
- // Local modifications: remap private imports to public package-root/owned theme boundaries and omit optional package-private imagery.
4
- import { Container, Spacer, Text } from "#pi-tui";
3
+ // Local modifications: remap private imports to public package-root/owned theme boundaries; load the verified owned copy of the image lazily.
4
+ import { readFileSync } from "node:fs";
5
+ import { Container, Image, Spacer, Text } from "#pi-tui";
5
6
  import { DynamicBorder } from "@earendil-works/pi-coding-agent";
6
7
  import { piTheme } from "../theme/theme.js";
7
8
  const BLOG_URL = "https://mariozechner.at/posts/2026-04-08-ive-sold-out/";
9
+ const IMAGE_FILENAME = "clankolas.png";
10
+ let cachedImageBase64;
11
+ let attemptedImageLoad = false;
12
+ function loadImageBase64() {
13
+ if (attemptedImageLoad)
14
+ return cachedImageBase64;
15
+ attemptedImageLoad = true;
16
+ try {
17
+ const resource = JSON.parse(readFileSync(new URL("../assets/earendil-image.json", import.meta.url), "utf8"));
18
+ if (resource && typeof resource === "object" && "data" in resource && typeof resource.data === "string")
19
+ cachedImageBase64 = resource.data;
20
+ }
21
+ catch {
22
+ cachedImageBase64 = undefined;
23
+ }
24
+ return cachedImageBase64;
25
+ }
8
26
  export class EarendilAnnouncementComponent extends Container {
9
27
  constructor() {
10
28
  super();
@@ -14,6 +32,11 @@ export class EarendilAnnouncementComponent extends Container {
14
32
  this.addChild(new Text(piTheme().fg("muted", "Read the blog post:"), 1, 0));
15
33
  this.addChild(new Text(piTheme().fg("mdLink", BLOG_URL), 1, 0));
16
34
  this.addChild(new Spacer(1));
35
+ const imageBase64 = loadImageBase64();
36
+ if (imageBase64) {
37
+ this.addChild(new Image(imageBase64, "image/png", { fallbackColor: text => piTheme().fg("muted", text) }, { maxWidthCells: 56, filename: IMAGE_FILENAME }));
38
+ this.addChild(new Spacer(1));
39
+ }
17
40
  this.addChild(new DynamicBorder((text) => piTheme().fg("accent", text)));
18
41
  }
19
42
  }
@@ -1,5 +1,5 @@
1
1
  import type { PiSessionForkPrompt, PiSessionSelection } from "./session-selection.js";
2
- import { type AgentSessionRuntime, type AgentSessionServices, type SessionInfo } from "@earendil-works/pi-coding-agent";
2
+ import { type AgentSession, type AgentSessionRuntime, type AgentSessionServices, type SessionInfo } from "@earendil-works/pi-coding-agent";
3
3
  import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, type OwnedUiCommand, type OwnedUiCommandOutcome, type OwnedUiImageAttachment, type OwnedUiPromptSuggestionGeneratorPort, type OwnedUiPromptSuggestionRequest, type OwnedUiPromptSuggestionResult, type OwnedUiEvent, type OwnedUiSessionViewModel, type OwnedUiSnapshot } from "../../../contracts/owned-ui/index.js";
4
4
  import { type PiAuthenticationProviderOption, type PiBashWorkflowResult, type PiPinnedSettingsCallback, type PiPinnedSettingsSnapshot, type PiWorkflowAutocompleteCommand, type PiWorkflowHost, type PiWorkflowInteractionHost, type PiWorkflowOption, type PiWorkflowRequest, type PiWorkflowResult } from "./workflows.js";
5
5
  import { PiSettingsIntegration } from "./settings-integration.js";
@@ -176,16 +176,28 @@ export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGenerator
176
176
  readonly title: string;
177
177
  readonly options: readonly PiWorkflowOption[];
178
178
  };
179
+ pinnedAmbientAuthentication(selection: string): {
180
+ readonly providerId: string;
181
+ readonly providerName: string;
182
+ readonly title: string;
183
+ readonly message: string;
184
+ } | null;
179
185
  pinnedLogoutOptions(): Promise<readonly PiAuthenticationProviderOption[]>;
180
186
  pinnedForkOptions(): readonly PiWorkflowOption[];
181
187
  pinnedTreeSelectorContext(): PiTreeSelectorContext;
182
188
  pinnedSettingsSnapshot(): PiPinnedSettingsSnapshot;
183
189
  pinnedMessageRenderer(customType: string): unknown;
190
+ pinnedShortcutDescriptions(bindings: Parameters<AgentSession["extensionRunner"]["getShortcuts"]>[0]): readonly {
191
+ readonly key: string;
192
+ readonly description: string;
193
+ }[];
184
194
  pinnedToolDefinition(toolName: string): unknown;
195
+ copyWorkflowText(text: string): Promise<void>;
185
196
  clearQueuedWorkflows(): readonly string[];
186
197
  abortBashWorkflow(): void;
187
198
  executeBashWorkflow(command: string, excludeFromContext: boolean): Promise<PiBashWorkflowResult>;
188
199
  applyPinnedSettingValue(callback: PiPinnedSettingsCallback, value: unknown): Promise<PiWorkflowResult>;
200
+ reloadBlockedResult(): PiWorkflowResult | null;
189
201
  executeWorkflow(request: PiWorkflowRequest): Promise<PiWorkflowResult>;
190
202
  view(): OwnedUiSessionViewModel;
191
203
  snapshot(): OwnedUiSnapshot;
@@ -7,6 +7,7 @@ import { dirname, join, resolve } from "node:path";
7
7
  import { promisify } from "node:util";
8
8
  import { PRODUCT_IDENTITY } from "../../../product-identity.js";
9
9
  import { configureOwnedHttpDispatcher } from "./http-dispatcher.js";
10
+ import { readPinnedCommandChangelog } from "./changelog.js";
10
11
  import { copyToClipboard, CredentialSynchronizationError, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
11
12
  import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiSnapshot, normalizePromptSuggestionCandidate, } from "../../../contracts/owned-ui/index.js";
12
13
  import { PINNED_PI_SETTINGS_CALLBACKS, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "./workflows.js";
@@ -308,14 +309,22 @@ export class PiEngineAdapter {
308
309
  if (!settingsManager || typeof settingsManager.getLastChangelogVersion !== "function"
309
310
  || typeof settingsManager.setLastChangelogVersion !== "function")
310
311
  return;
311
- if (settingsManager.getLastChangelogVersion() === VERSION)
312
+ if ((this.#session?.messages.length ?? 0) > 0)
312
313
  return;
313
- const markdown = await this.#workflowHost.readChangelog().catch(() => "");
314
+ const lastVersion = settingsManager.getLastChangelogVersion();
315
+ if (lastVersion === VERSION)
316
+ return;
317
+ if (!lastVersion) {
318
+ settingsManager.setLastChangelogVersion(VERSION);
319
+ await settingsManager.flush();
320
+ return;
321
+ }
322
+ const markdown = await this.#workflowHost.readChangelog(lastVersion).catch(() => "");
314
323
  if (markdown.trim().length > 0) {
315
324
  this.#addDiagnostic("info", settingsManager.getCollapseChangelog() ? "changelog-collapsed" : "changelog-expanded", markdown, true);
325
+ settingsManager.setLastChangelogVersion(VERSION);
326
+ await settingsManager.flush();
316
327
  }
317
- settingsManager.setLastChangelogVersion(VERSION);
318
- await settingsManager.flush();
319
328
  }
320
329
  async #announcePackageUpdates(settingsManager) {
321
330
  if (process.env.PI_OFFLINE)
@@ -877,6 +886,17 @@ export class PiEngineAdapter {
877
886
  }));
878
887
  return { title: `Select authentication method for ${providerName}:`, options };
879
888
  }
889
+ pinnedAmbientAuthentication(selection) {
890
+ if (!selection.startsWith("api_key:"))
891
+ return null;
892
+ const providerId = selection.slice("api_key:".length);
893
+ const provider = this.#runtime?.services.modelRuntime.getProvider?.(providerId);
894
+ const method = dynamicObject(dynamicObject(provider, "auth"), "apiKey");
895
+ if (!method || typeof method.login === "function")
896
+ return null;
897
+ const providerName = stringProperty(provider, "name") ?? providerId;
898
+ return { providerId, providerName, title: `${providerName} setup`, message: `${stringProperty(method, "name") ?? "Authentication"} is configured outside pi.` };
899
+ }
880
900
  pinnedLogoutOptions() {
881
901
  return this.#logoutOptions();
882
902
  }
@@ -957,9 +977,16 @@ export class PiEngineAdapter {
957
977
  pinnedMessageRenderer(customType) {
958
978
  return this.#requireWorkflowSession().extensionRunner?.getMessageRenderer?.(customType);
959
979
  }
980
+ pinnedShortcutDescriptions(bindings) {
981
+ const shortcuts = this.#requireWorkflowSession().extensionRunner?.getShortcuts?.(bindings);
982
+ return shortcuts === undefined ? [] : [...shortcuts].map(([key, shortcut]) => ({ key, description: shortcut.description ?? shortcut.extensionPath }));
983
+ }
960
984
  pinnedToolDefinition(toolName) {
961
985
  return this.#requireWorkflowSession().extensionRunner?.getToolDefinition?.(toolName);
962
986
  }
987
+ async copyWorkflowText(text) {
988
+ await this.#workflowHost.copyText(text);
989
+ }
963
990
  clearQueuedWorkflows() {
964
991
  const session = this.#requireWorkflowSession();
965
992
  const result = session.clearQueue?.();
@@ -992,6 +1019,14 @@ export class PiEngineAdapter {
992
1019
  return workflowResult("settings", "failed", error instanceof Error ? error.message : String(error));
993
1020
  }
994
1021
  }
1022
+ reloadBlockedResult() {
1023
+ const session = this.#requireWorkflowSession();
1024
+ if (session.isStreaming)
1025
+ return workflowResult("reload", "failed", "Wait for the current response to finish before reloading.", undefined, "warning");
1026
+ if (session.isCompacting)
1027
+ return workflowResult("reload", "failed", "Wait for compaction to finish before reloading.", undefined, "warning");
1028
+ return null;
1029
+ }
995
1030
  async executeWorkflow(request) {
996
1031
  try {
997
1032
  return await this.#performWorkflow(request);
@@ -999,7 +1034,7 @@ export class PiEngineAdapter {
999
1034
  catch (error) {
1000
1035
  const message = error instanceof Error ? error.message : String(error);
1001
1036
  const contextualMessage = request.command === "export"
1002
- ? `Failed to export session: ${message}`
1037
+ ? `Failed to export session: ${errorMessage(error, "Unknown error")}`
1003
1038
  : request.command === "import"
1004
1039
  ? `Failed to import session: ${message}`
1005
1040
  : request.command === "new"
@@ -1133,8 +1168,18 @@ export class PiEngineAdapter {
1133
1168
  : [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
1134
1169
  let model = findExactWorkflowModel(reference, availableModels);
1135
1170
  const messages = [];
1171
+ const generation = this.#sessionGeneration;
1172
+ const current = () => !this.#disposed && generation === this.#sessionGeneration;
1173
+ const publish = (message) => {
1174
+ if (!current())
1175
+ return;
1176
+ if (this.#workflowInteraction.publish)
1177
+ this.#workflowInteraction.publish(message);
1178
+ else
1179
+ messages.push(message);
1180
+ };
1136
1181
  if (model === undefined && scopedModels.length === 0) {
1137
- messages.push({ kind: "status", message: "Refreshing model catalogs…" });
1182
+ publish({ kind: "status", message: "Refreshing model catalogs…" });
1138
1183
  const controller = new AbortController();
1139
1184
  let timedOut = false;
1140
1185
  const timeout = setTimeout(() => {
@@ -1144,14 +1189,14 @@ export class PiEngineAdapter {
1144
1189
  try {
1145
1190
  const refreshed = await runtime.services.modelRuntime.refresh?.({ signal: controller.signal });
1146
1191
  if (isRecord(refreshed) && refreshed.aborted === true && timedOut) {
1147
- messages.push({ kind: "warning", message: "Model refresh timed out; searching cached models." });
1192
+ publish({ kind: "warning", message: "Model refresh timed out; searching cached models." });
1148
1193
  }
1149
1194
  else if (isRecord(refreshed) && refreshed.errors instanceof Map && refreshed.errors.size > 0) {
1150
- messages.push({ kind: "warning", message: `Could not refresh ${[...refreshed.errors.keys()].join(", ")}; searching cached models.` });
1195
+ publish({ kind: "warning", message: `Could not refresh ${[...refreshed.errors.keys()].join(", ")}; searching cached models.` });
1151
1196
  }
1152
1197
  }
1153
1198
  catch (error) {
1154
- messages.push({
1199
+ publish({
1155
1200
  kind: "warning",
1156
1201
  message: timedOut
1157
1202
  ? "Model refresh timed out; searching cached models."
@@ -1164,13 +1209,25 @@ export class PiEngineAdapter {
1164
1209
  availableModels = [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
1165
1210
  model = findExactWorkflowModel(reference, availableModels);
1166
1211
  }
1212
+ if (!current())
1213
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1167
1214
  if (model === undefined) {
1168
1215
  return {
1169
1216
  ...workflowResult(request.command, "requires-selection", "Select a model", reference, "silent"),
1170
1217
  ...(messages.length === 0 ? {} : { messages: Object.freeze(messages) }),
1171
1218
  };
1172
1219
  }
1173
- await session.setModel(model);
1220
+ try {
1221
+ await session.setModel(model);
1222
+ }
1223
+ catch (error) {
1224
+ if (!current())
1225
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1226
+ const failure = { kind: "error", message: error instanceof Error ? error.message : String(error) };
1227
+ return workflowResult(request.command, "failed", failure.message, undefined, "error", messages.length === 0 ? undefined : [...messages, failure]);
1228
+ }
1229
+ if (!current())
1230
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1174
1231
  const providerId = stringProperty(model, "provider") ?? "unknown";
1175
1232
  const modelId = stringProperty(model, "id") ?? reference;
1176
1233
  this.#activeModel = { providerId, modelId, displayName: stringProperty(model, "name") ?? modelId };
@@ -1226,9 +1283,8 @@ export class PiEngineAdapter {
1226
1283
  const signal = request.signal;
1227
1284
  if (signal?.aborted)
1228
1285
  return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1229
- let auth;
1230
1286
  try {
1231
- auth = await this.#workflowHost.runCommand("gh", ["auth", "status"], signal === undefined ? undefined : { signal });
1287
+ await this.#workflowHost.runCommand("gh", ["auth", "status"], signal === undefined ? undefined : { signal });
1232
1288
  }
1233
1289
  catch (error) {
1234
1290
  if (signal?.aborted || isAbortError(error))
@@ -1238,9 +1294,6 @@ export class PiEngineAdapter {
1238
1294
  : "GitHub CLI is not logged in. Run 'gh auth login' first.";
1239
1295
  return workflowResult(request.command, "failed", message);
1240
1296
  }
1241
- if (auth.stderr && !auth.stdout) {
1242
- return workflowResult(request.command, "failed", "GitHub CLI is not logged in. Run 'gh auth login' first.");
1243
- }
1244
1297
  const temporary = join(tmpdir(), `${PRODUCT_IDENTITY.filesystem.temporaryPrefix}pi-session-${process.pid}.html`);
1245
1298
  try {
1246
1299
  try {
@@ -1258,13 +1311,10 @@ export class PiEngineAdapter {
1258
1311
  catch (error) {
1259
1312
  if (signal?.aborted || isAbortError(error))
1260
1313
  return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1261
- return workflowResult(request.command, "failed", `Failed to create gist: ${errorMessage(error, "Unknown error")}`);
1314
+ return workflowResult(request.command, "failed", `Failed to create gist: ${shareCommandFailureDetail(error)}`);
1262
1315
  }
1263
1316
  if (signal?.aborted)
1264
1317
  return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1265
- if (gist.stderr && !gist.stdout) {
1266
- return workflowResult(request.command, "failed", `Failed to create gist: ${gist.stderr.trim() || "Unknown error"}`);
1267
- }
1268
1318
  const gistUrl = gist.stdout.trim();
1269
1319
  const gistId = gistUrl.split("/").at(-1);
1270
1320
  if (!gistId)
@@ -1497,13 +1547,17 @@ export class PiEngineAdapter {
1497
1547
  }
1498
1548
  }
1499
1549
  case "reload": {
1500
- if (session.isStreaming)
1501
- return workflowResult(request.command, "failed", "Wait for the current response to finish before reloading.", undefined, "warning");
1502
- if (session.isCompacting)
1503
- return workflowResult(request.command, "failed", "Wait for compaction to finish before reloading.", undefined, "warning");
1550
+ const blocked = this.reloadBlockedResult();
1551
+ if (blocked)
1552
+ return blocked;
1504
1553
  await requireCapability(session.reload, "reload").call(session);
1505
1554
  await this.#bindExtensionUiToSession();
1506
- return workflowResult(request.command, "completed", "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
1555
+ const message = "Reloaded keybindings, extensions, skills, prompts, themes, and context files";
1556
+ const modelError = runtime.services.modelRuntime.getError?.();
1557
+ return workflowResult(request.command, "completed", message, undefined, "status", modelError ? [
1558
+ { kind: "error", message: `models.json error: ${modelError}` },
1559
+ { kind: "status", message },
1560
+ ] : undefined);
1507
1561
  }
1508
1562
  case "quit": {
1509
1563
  await this.dispose();
@@ -2698,7 +2752,7 @@ function defaultWorkflowHost() {
2698
2752
  const result = await execFileAsync(command, [...arguments_], { encoding: "utf8", signal: options?.signal });
2699
2753
  return { stdout: result.stdout, stderr: result.stderr };
2700
2754
  },
2701
- readChangelog: async () => "No changelog entries found.",
2755
+ readChangelog: readPinnedCommandChangelog,
2702
2756
  };
2703
2757
  }
2704
2758
  function workflowLoginNotification(event) {
@@ -2757,6 +2811,13 @@ function workflowConfirmation(command, message, detail) {
2757
2811
  function errorMessage(error, fallback) {
2758
2812
  return error instanceof Error ? error.message : fallback;
2759
2813
  }
2814
+ function shareCommandFailureDetail(error) {
2815
+ // Compatibility: Pi reports a failed child's stderr, not execFile's command wrapper.
2816
+ if (isRecord(error) && (typeof error.code === "number" || typeof error.signal === "string") && typeof error.stderr === "string") {
2817
+ return error.stderr.trim() || "Unknown error";
2818
+ }
2819
+ return errorMessage(error, "Unknown error");
2820
+ }
2760
2821
  function commandMissing(error) {
2761
2822
  return isRecord(error) && error.code === "ENOENT";
2762
2823
  }
@@ -0,0 +1,2 @@
1
+ export declare function readPinnedCommandChangelog(sinceVersion?: string): Promise<string>;
2
+ export declare function formatPinnedCommandChangelog(content: string, sinceVersion?: string): string;
@@ -0,0 +1,63 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { posix } from "node:path";
4
+ const REPOSITORY = "https://github.com/earendil-works/pi";
5
+ export async function readPinnedCommandChangelog(sinceVersion) {
6
+ const path = new URL("./resources/changelog.json", import.meta.url);
7
+ if (!existsSync(path))
8
+ return sinceVersion === undefined ? "No changelog entries found." : "";
9
+ try {
10
+ const resource = JSON.parse(await readFile(path, "utf8"));
11
+ if (!resource || typeof resource !== "object" || !("data" in resource) || typeof resource.data !== "string")
12
+ throw new TypeError("Invalid owned changelog resource");
13
+ const markdown = resource.data;
14
+ return formatPinnedCommandChangelog(markdown, sinceVersion);
15
+ }
16
+ catch (error) {
17
+ // Compatibility: Pi's data-file parser reports a warning and supplies an empty changelog.
18
+ console.error(`Warning: Could not parse changelog: ${error}`);
19
+ return sinceVersion === undefined ? "No changelog entries found." : "";
20
+ }
21
+ }
22
+ export function formatPinnedCommandChangelog(content, sinceVersion) {
23
+ // Provenance: behavior follows Pi 0.84.2 utils/changelog.ts at 914cf1472e715297caa30db4b9535d534a9eb718.
24
+ // Runtime reads only the owned data resource; maintenance tooling verifies its pinned provenance.
25
+ let releases = content.split(/(?=^## )/m).flatMap((section) => {
26
+ const version = /^##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/.exec(section);
27
+ return version ? [{ version: version.slice(1).map(Number), markdown: section.trim() }] : [];
28
+ });
29
+ if (sinceVersion !== undefined) {
30
+ const previous = sinceVersion.split(".").map(value => Number(value) || 0);
31
+ releases = releases.filter(release => {
32
+ for (let index = 0; index < 3; index++) {
33
+ const difference = release.version[index] - (previous[index] ?? 0);
34
+ if (difference !== 0)
35
+ return difference > 0;
36
+ }
37
+ return false;
38
+ });
39
+ }
40
+ else
41
+ releases.reverse();
42
+ if (releases.length === 0)
43
+ return sinceVersion === undefined ? "No changelog entries found." : "";
44
+ return releases.map(release => release.markdown.replace(/(!?\[[^\]\n]+\]\()([^\s)]+)((?:\s+[^)]*)?\))/g, (_match, prefix, target, suffix) => `${prefix}${releaseLink(target, `v${release.version.join(".")}`)}${suffix}`)).join("\n\n");
45
+ }
46
+ function releaseLink(target, tag) {
47
+ const canonical = target
48
+ .replace(/^https:\/\/github\.com\/(?:badlogic|earendil-works)\/pi-mono(?=\/|$)/, REPOSITORY)
49
+ .replace(/^(https:\/\/github\.com\/earendil-works\/pi\/(?:blob|tree)\/)(?:main|master)\//, `$1${tag}/`);
50
+ if (/^(?:#|\/\/|[a-z][a-z0-9+.-]*:)/i.test(canonical))
51
+ return canonical;
52
+ const boundary = canonical.search(/[?#]/);
53
+ const path = boundary === -1 ? canonical : canonical.slice(0, boundary);
54
+ const suffix = boundary === -1 ? "" : canonical.slice(boundary);
55
+ if (!path)
56
+ return canonical;
57
+ const normalized = path.replaceAll("\\", "/");
58
+ const repositoryPath = posix.normalize(normalized.startsWith("/") ? normalized.replace(/^\/+/, "") : posix.join("packages/coding-agent", normalized));
59
+ if (repositoryPath === "." || repositoryPath === ".." || repositoryPath.startsWith("../"))
60
+ return canonical;
61
+ const route = path.endsWith("/") || !posix.basename(repositoryPath).includes(".") ? "tree" : "blob";
62
+ return `${REPOSITORY}/${route}/${tag}/${encodeURI(repositoryPath)}${suffix}`;
63
+ }