@getpaseo/server 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/scripts/supervisor.js +51 -21
  2. package/dist/server/server/agent/agent-manager.js +22 -2
  3. package/dist/server/server/agent/agent-prompt.js +12 -0
  4. package/dist/server/server/agent/agent-sdk-types.d.ts +3 -0
  5. package/dist/server/server/agent/mcp-shared.d.ts +5 -1
  6. package/dist/server/server/agent/mcp-shared.js +2 -1
  7. package/dist/server/server/agent/provider-launch-config.d.ts +2 -2
  8. package/dist/server/server/agent/provider-launch-config.js +1 -1
  9. package/dist/server/server/agent/provider-registry.js +2 -2
  10. package/dist/server/server/agent/providers/acp-agent.js +20 -11
  11. package/dist/server/server/agent/providers/claude/agent.d.ts +1 -2
  12. package/dist/server/server/agent/providers/claude/agent.js +10 -9
  13. package/dist/server/server/agent/providers/claude/models.d.ts +1 -1
  14. package/dist/server/server/agent/providers/claude/models.js +1 -5
  15. package/dist/server/server/agent/providers/claude/project-dir.d.ts +1 -0
  16. package/dist/server/server/agent/providers/claude/project-dir.js +5 -1
  17. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +6 -1
  18. package/dist/server/server/agent/providers/codex-app-server-agent.js +10 -1
  19. package/dist/server/server/agent/providers/generic-acp-agent.js +3 -1
  20. package/dist/server/server/agent/providers/omp/agent.js +23 -6
  21. package/dist/server/server/agent/providers/omp/rpc-types.d.ts +1 -0
  22. package/dist/server/server/agent/providers/omp/rpc-types.js +1 -0
  23. package/dist/server/server/agent/providers/opencode/bridge.d.ts +5 -1
  24. package/dist/server/server/agent/providers/opencode/bridge.js +56 -5
  25. package/dist/server/server/agent/providers/opencode/http-error.d.ts +8 -0
  26. package/dist/server/server/agent/providers/opencode/http-error.js +13 -0
  27. package/dist/server/server/agent/providers/opencode/runtime-client.d.ts +38 -0
  28. package/dist/server/server/agent/providers/opencode/runtime-client.js +131 -0
  29. package/dist/server/server/agent/providers/opencode/runtime-notice.d.ts +3 -0
  30. package/dist/server/server/agent/providers/opencode/runtime-notice.js +47 -0
  31. package/dist/server/server/agent/providers/opencode/server-manager.js +19 -8
  32. package/dist/server/server/agent/providers/opencode/v2/agent.d.ts +36 -0
  33. package/dist/server/server/agent/providers/opencode/v2/agent.js +211 -0
  34. package/dist/server/server/agent/providers/opencode/v2/api.d.ts +18 -0
  35. package/dist/server/server/agent/providers/opencode/v2/api.js +2 -0
  36. package/dist/server/server/agent/providers/opencode/v2/bridge-plugin.bundle.mjs +6693 -0
  37. package/dist/server/server/agent/providers/opencode/v2/capabilities.d.ts +3 -0
  38. package/dist/server/server/agent/providers/opencode/v2/capabilities.js +13 -0
  39. package/dist/server/server/agent/providers/opencode/v2/children.d.ts +20 -0
  40. package/dist/server/server/agent/providers/opencode/v2/children.js +75 -0
  41. package/dist/server/server/agent/providers/opencode/v2/commands.d.ts +4 -0
  42. package/dist/server/server/agent/providers/opencode/v2/commands.js +36 -0
  43. package/dist/server/server/agent/providers/opencode/v2/configuration.d.ts +8 -0
  44. package/dist/server/server/agent/providers/opencode/v2/configuration.js +64 -0
  45. package/dist/server/server/agent/providers/opencode/v2/history.d.ts +4 -0
  46. package/dist/server/server/agent/providers/opencode/v2/history.js +15 -0
  47. package/dist/server/server/agent/providers/opencode/v2/mapping.d.ts +7 -0
  48. package/dist/server/server/agent/providers/opencode/v2/mapping.js +42 -0
  49. package/dist/server/server/agent/providers/opencode/v2/permissions.d.ts +18 -0
  50. package/dist/server/server/agent/providers/opencode/v2/permissions.js +157 -0
  51. package/dist/server/server/agent/providers/opencode/v2/readiness.d.ts +18 -0
  52. package/dist/server/server/agent/providers/opencode/v2/readiness.js +51 -0
  53. package/dist/server/server/agent/providers/opencode/v2/runtime.d.ts +36 -0
  54. package/dist/server/server/agent/providers/opencode/v2/runtime.js +230 -0
  55. package/dist/server/server/agent/providers/opencode/v2/session.d.ts +84 -0
  56. package/dist/server/server/agent/providers/opencode/v2/session.js +468 -0
  57. package/dist/server/server/agent/providers/opencode/v2/structured-output.d.ts +8 -0
  58. package/dist/server/server/agent/providers/opencode/v2/structured-output.js +32 -0
  59. package/dist/server/server/agent/providers/opencode/v2/timeline.d.ts +28 -0
  60. package/dist/server/server/agent/providers/opencode/v2/timeline.js +154 -0
  61. package/dist/server/server/agent/providers/opencode/v2/turns.d.ts +40 -0
  62. package/dist/server/server/agent/providers/opencode/v2/turns.js +226 -0
  63. package/dist/server/server/agent/providers/opencode-agent.d.ts +26 -7
  64. package/dist/server/server/agent/providers/opencode-agent.js +100 -40
  65. package/dist/server/server/agent/providers/pi/agent.d.ts +6 -6
  66. package/dist/server/server/agent/providers/pi/agent.js +114 -165
  67. package/dist/server/server/agent/providers/pi/extensions/child-session.d.ts +4 -0
  68. package/dist/server/server/agent/providers/pi/extensions/child-session.js +59 -0
  69. package/dist/server/server/agent/providers/pi/extensions/contract.d.ts +68 -0
  70. package/dist/server/server/agent/providers/pi/extensions/contract.js +2 -0
  71. package/dist/server/server/agent/providers/pi/extensions/gotgenes-pi-subagents/index.d.ts +3 -0
  72. package/dist/server/server/agent/providers/pi/extensions/gotgenes-pi-subagents/index.js +159 -0
  73. package/dist/server/server/agent/providers/pi/extensions/host.d.ts +28 -0
  74. package/dist/server/server/agent/providers/pi/extensions/host.js +94 -0
  75. package/dist/server/server/agent/providers/pi/extensions/index.d.ts +7 -0
  76. package/dist/server/server/agent/providers/pi/extensions/index.js +6 -0
  77. package/dist/server/server/agent/providers/pi/extensions/pi-ask-user/index.d.ts +3 -0
  78. package/dist/server/server/agent/providers/pi/extensions/pi-ask-user/index.js +138 -0
  79. package/dist/server/server/agent/providers/pi/extensions/pi-example-todo/index.d.ts +3 -0
  80. package/dist/server/server/agent/providers/pi/extensions/pi-example-todo/index.js +38 -0
  81. package/dist/server/server/agent/providers/pi/extensions/pi-goal-x/index.d.ts +3 -0
  82. package/dist/server/server/agent/providers/pi/extensions/pi-goal-x/index.js +63 -0
  83. package/dist/server/server/agent/providers/pi/extensions/pi-mcp-adapter/index.d.ts +3 -0
  84. package/dist/server/server/agent/providers/pi/extensions/pi-mcp-adapter/index.js +48 -0
  85. package/dist/server/server/agent/providers/pi/extensions/pi-subagents/index.d.ts +3 -0
  86. package/dist/server/server/agent/providers/pi/extensions/pi-subagents/index.js +131 -0
  87. package/dist/server/server/agent/providers/pi/extensions/registry.d.ts +2 -0
  88. package/dist/server/server/agent/providers/pi/extensions/registry.js +21 -0
  89. package/dist/server/server/agent/providers/pi/extensions/rpiv-ask-user-question/index.d.ts +3 -0
  90. package/dist/server/server/agent/providers/pi/extensions/rpiv-ask-user-question/index.js +180 -0
  91. package/dist/server/server/agent/providers/pi/extensions/rpiv-todo/index.d.ts +3 -0
  92. package/dist/server/server/agent/providers/pi/extensions/rpiv-todo/index.js +51 -0
  93. package/dist/server/server/agent/providers/pi/extensions/subagent-fixture-test.d.ts +22 -0
  94. package/dist/server/server/agent/providers/pi/extensions/subagent-fixture-test.js +66 -0
  95. package/dist/server/server/agent/providers/pi/extensions/task-fixture-test.d.ts +4 -0
  96. package/dist/server/server/agent/providers/pi/extensions/task-fixture-test.js +79 -0
  97. package/dist/server/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.d.ts +3 -0
  98. package/dist/server/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.js +149 -0
  99. package/dist/server/server/agent/providers/pi/history-mapper.d.ts +7 -2
  100. package/dist/server/server/agent/providers/pi/history-mapper.js +78 -35
  101. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +3 -0
  102. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +170 -0
  103. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +355 -0
  104. package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +1 -6
  105. package/dist/server/server/agent/providers/pi/tool-call-mapper.js +1 -97
  106. package/dist/server/server/agent/tools/paseo-tools.js +22 -15
  107. package/dist/server/server/auth.d.ts +1 -2
  108. package/dist/server/server/auth.js +8 -14
  109. package/dist/server/server/bootstrap.js +10 -2
  110. package/dist/server/server/daemon-instance.d.ts +1 -0
  111. package/dist/server/server/daemon-instance.js +1 -0
  112. package/dist/server/server/exports.d.ts +1 -1
  113. package/dist/server/server/exports.js +1 -1
  114. package/dist/server/server/local-credential.d.ts +6 -0
  115. package/dist/server/server/local-credential.js +82 -0
  116. package/dist/server/server/session-admission-auth.d.ts +16 -0
  117. package/dist/server/server/session-admission-auth.js +26 -0
  118. package/dist/server/server/websocket-server.d.ts +6 -1
  119. package/dist/server/server/websocket-server.js +132 -55
  120. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-f41bce08c3c4ac2de0652d20234d19e9.js → desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js} +1 -1
  121. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-f41bce08c3c4ac2de0652d20234d19e9.js.br → desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js.br} +0 -0
  122. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js.gz +0 -0
  123. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-9e271e161480b0d1dd35e58616a37341.js → desktop-attachment-store-d5d3402a752965d3037191484127123b.js} +1 -1
  124. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-9e271e161480b0d1dd35e58616a37341.js.br → desktop-attachment-store-d5d3402a752965d3037191484127123b.js.br} +0 -0
  125. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d5d3402a752965d3037191484127123b.js.gz +0 -0
  126. package/dist/server/web-ui/_expo/static/js/web/{index-8868523fc6d311c5da524c067d1cd426.js → index-136fa41d4adb7ec5c6392839cc354136.js} +1580 -1576
  127. package/dist/server/web-ui/_expo/static/js/web/index-136fa41d4adb7ec5c6392839cc354136.js.br +0 -0
  128. package/dist/server/web-ui/_expo/static/js/web/index-136fa41d4adb7ec5c6392839cc354136.js.gz +0 -0
  129. package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-369cdb67f5856fd2c72d640df0b7b79e.js → indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js} +1 -1
  130. package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-369cdb67f5856fd2c72d640df0b7b79e.js.br → indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js.br} +0 -0
  131. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js.gz +0 -0
  132. package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-bcd89f6ce714279a8f4fa0e024f3a610.js → native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js} +3 -3
  133. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js.br +0 -0
  134. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js.gz +0 -0
  135. package/dist/server/web-ui/index.html +1 -1
  136. package/dist/server/web-ui/index.html.br +0 -0
  137. package/dist/server/web-ui/index.html.gz +0 -0
  138. package/dist/src/server/agent/provider-launch-config.js +1 -1
  139. package/dist/src/server/daemon-instance.js +1 -0
  140. package/dist/src/server/local-credential.js +82 -0
  141. package/package.json +8 -7
  142. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-f41bce08c3c4ac2de0652d20234d19e9.js.gz +0 -0
  143. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-9e271e161480b0d1dd35e58616a37341.js.gz +0 -0
  144. package/dist/server/web-ui/_expo/static/js/web/index-8868523fc6d311c5da524c067d1cd426.js.br +0 -0
  145. package/dist/server/web-ui/_expo/static/js/web/index-8868523fc6d311c5da524c067d1cd426.js.gz +0 -0
  146. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-369cdb67f5856fd2c72d640df0b7b79e.js.gz +0 -0
  147. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-bcd89f6ce714279a8f4fa0e024f3a610.js.br +0 -0
  148. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-bcd89f6ce714279a8f4fa0e024f3a610.js.gz +0 -0
@@ -14,24 +14,23 @@ import { buildBinaryDiagnosticRows, buildCommandResolutionDiagnosticRows, format
14
14
  import { getUserMessageText, streamPiHistory, } from "./history-mapper.js";
15
15
  import { materializeProviderImage } from "../provider-image-output.js";
16
16
  import { PiCliRuntime } from "./cli-runtime.js";
17
+ import { createPiExtensionHost } from "./extensions/index.js";
17
18
  import { revertPiConversation } from "./rewind.js";
18
19
  import { listPiImportableSessions, readPiImportSessionConfig } from "./session-descriptor.js";
19
20
  import { PiUsagePoller } from "./usage-poller.js";
20
- import { mapToolDetail, parseToolArgs, parseToolResult, resolveToolCallName, } from "./tool-call-mapper.js";
21
+ import { mapToolDetail, parseToolArgs, parseToolResult, } from "./tool-call-mapper.js";
21
22
  const PI_PROVIDER = "pi";
22
23
  const DEFAULT_PI_THINKING_LEVEL = "medium";
23
24
  const PI_BINARY_COMMAND = process.env.PI_COMMAND ?? process.env.PI_ACP_PI_COMMAND ?? "pi";
24
25
  const PASEO_PI_TREE_EXTENSION_COMMAND = "paseo_tree";
25
26
  const PASEO_PI_CAPTURE_EXTENSION_COMMAND = "paseo_capture_entries";
27
+ const PASEO_PI_REWIND_ENTRY_TYPE = "paseo_rewind";
26
28
  const PASEO_PI_ENTRY_CAPTURE_MARKER = "PASEO_ENTRY_CAPTURE";
27
29
  const PASEO_PI_SUBMITTED_USER_ENTRY_MARKER = "PASEO_SUBMITTED_USER_ENTRY";
28
30
  const PASEO_PI_COMMAND_RESULT_MARKER = "PASEO_COMMAND_RESULT";
29
31
  const DEFAULT_PI_EXTENSION_RESULT_TIMEOUT_MS = 30000;
30
32
  const DEFAULT_PI_RPC_TIMEOUT_MS = 60000;
31
33
  const QUESTION_RESPONSE_HEADER = "Response";
32
- const QUESTION_COMMENT_HEADER = "Comment";
33
- const PI_ASK_USER_FREEFORM_SENTINEL = "✏️ Type custom response...";
34
- const COMBINED_ASK_USER_METADATA = "ask_user_select_optional_comment";
35
34
  export const PiProviderParamsSchema = z
36
35
  .object({
37
36
  sessionDir: z.string().min(1).optional(),
@@ -374,9 +373,8 @@ function createPiPaseoExtensionFile(systemPrompt) {
374
373
  .join("\\n\\n");
375
374
  }
376
375
 
377
- function getCapturedUserEntries(ctx) {
378
- return ctx.sessionManager
379
- .getEntries()
376
+ function toCapturedUserEntries(entries) {
377
+ return entries
380
378
  .filter((entry) => entry.type === "message" && entry.message?.role === "user")
381
379
  .map(toCapturedUserEntry);
382
380
  }
@@ -392,7 +390,14 @@ function createPiPaseoExtensionFile(systemPrompt) {
392
390
  function emitEntryCapture(ctx, reason, requestId) {
393
391
  ctx.ui.notify(
394
392
  "${PASEO_PI_ENTRY_CAPTURE_MARKER} " +
395
- JSON.stringify({ reason, requestId, entries: getCapturedUserEntries(ctx) }),
393
+ JSON.stringify({
394
+ reason,
395
+ requestId,
396
+ // Rewind targets: rows can still show entries that a rewind or compaction left off the branch.
397
+ treeEntries: toCapturedUserEntries(ctx.sessionManager.getEntries()),
398
+ // The entries getMessages() replays, so the nth one is the nth replayed user message.
399
+ contextEntries: toCapturedUserEntries(ctx.sessionManager.buildContextEntries()),
400
+ }),
396
401
  "info",
397
402
  );
398
403
  }
@@ -470,6 +475,8 @@ function createPiPaseoExtensionFile(systemPrompt) {
470
475
  const payload = decodePayload(args.trim());
471
476
  try {
472
477
  const result = await ctx.navigateTree(payload.targetId, { summarize: false });
478
+ // Pi reopens a session at its last entry, so record the rewind on the new branch to keep it.
479
+ pi.appendEntry("${PASEO_PI_REWIND_ENTRY_TYPE}", { targetId: payload.targetId });
473
480
  emitEntryCapture(ctx, "tree_navigation");
474
481
  emitCommandResult(ctx, payload.requestId, { ok: true, result });
475
482
  } catch (error) {
@@ -609,19 +616,6 @@ function parseCapturedEntries(value) {
609
616
  ];
610
617
  });
611
618
  }
612
- function optionalBoolean(value) {
613
- return typeof value === "boolean" ? value : undefined;
614
- }
615
- function readActiveAskUserDialog(toolName, args) {
616
- if (toolName !== "ask_user" || !isRecord(args)) {
617
- return null;
618
- }
619
- return {
620
- allowComment: optionalBoolean(args.allowComment) ?? false,
621
- allowFreeform: optionalBoolean(args.allowFreeform) ?? true,
622
- allowMultiple: optionalBoolean(args.allowMultiple) ?? false,
623
- };
624
- }
625
619
  function isOptionalInputPlaceholder(placeholder) {
626
620
  return /\boptional\b|\bskip\b/i.test(placeholder ?? "");
627
621
  }
@@ -639,24 +633,12 @@ function readStringArray(value) {
639
633
  ? value.filter((item) => typeof item === "string")
640
634
  : [];
641
635
  }
642
- function isPiAskUserFreeformOption(option) {
643
- return option === PI_ASK_USER_FREEFORM_SENTINEL;
644
- }
645
636
  function mapExtensionUiRequestToPermission(event, options = {}) {
646
637
  const provider = options.provider ?? PI_PROVIDER;
647
638
  const label = options.label ?? "Pi";
648
639
  switch (event.method) {
649
640
  case "select": {
650
641
  const selectOptions = readStringArray(event.options);
651
- if (options.combineOptionalComment) {
652
- return buildCombinedAskUserQuestionPermission(event, {
653
- provider,
654
- label,
655
- question: optionalString(event.title) ?? "Select an option",
656
- options: selectOptions,
657
- allowFreeform: options.allowFreeform === true,
658
- });
659
- }
660
642
  return buildExtensionUiQuestionPermission(event, {
661
643
  provider,
662
644
  label,
@@ -753,52 +735,6 @@ function buildExtensionUiQuestionPermission(event, input) {
753
735
  },
754
736
  };
755
737
  }
756
- function buildCombinedAskUserQuestionPermission(event, input) {
757
- const visibleOptions = input.options.filter((option) => !isPiAskUserFreeformOption(option));
758
- const allowOther = input.allowFreeform || visibleOptions.length !== input.options.length;
759
- return {
760
- id: event.id,
761
- provider: input.provider,
762
- name: `${input.label} ask_user`,
763
- kind: "question",
764
- title: input.question,
765
- input: {
766
- questions: [
767
- {
768
- question: input.question,
769
- header: QUESTION_RESPONSE_HEADER,
770
- options: visibleOptions.map((label) => ({ label })),
771
- multiSelect: false,
772
- ...(allowOther ? { allowOther: true } : {}),
773
- },
774
- {
775
- question: "Optional comment",
776
- header: QUESTION_COMMENT_HEADER,
777
- options: [],
778
- multiSelect: false,
779
- placeholder: "Optional comment (press Enter to skip)...",
780
- allowEmpty: true,
781
- },
782
- ],
783
- },
784
- metadata: {
785
- extensionUiMethod: event.method,
786
- answerHeader: QUESTION_RESPONSE_HEADER,
787
- commentHeader: QUESTION_COMMENT_HEADER,
788
- combinedAskUser: COMBINED_ASK_USER_METADATA,
789
- selectOptions: visibleOptions,
790
- ...(allowOther ? { freeformSentinel: PI_ASK_USER_FREEFORM_SENTINEL } : {}),
791
- },
792
- };
793
- }
794
- function permissionAnswer(input, header) {
795
- const answers = isRecord(input?.answers) ? input.answers : null;
796
- if (!answers) {
797
- return null;
798
- }
799
- const answer = answers[header];
800
- return typeof answer === "string" ? answer : null;
801
- }
802
738
  function firstPermissionAnswer(input) {
803
739
  const answers = isRecord(input?.answers) ? input.answers : null;
804
740
  if (!answers) {
@@ -807,29 +743,6 @@ function firstPermissionAnswer(input) {
807
743
  const first = Object.values(answers).find((value) => typeof value === "string");
808
744
  return typeof first === "string" ? first : null;
809
745
  }
810
- function isCombinedAskUserPermission(request) {
811
- return request.metadata?.combinedAskUser === COMBINED_ASK_USER_METADATA;
812
- }
813
- function buildCombinedAskUserSelectionResponse(request, response) {
814
- if (response.behavior === "deny") {
815
- return { uiResponse: { cancelled: true }, pendingResponse: null };
816
- }
817
- const answer = permissionAnswer(response.updatedInput, QUESTION_RESPONSE_HEADER);
818
- if (answer === null) {
819
- return { uiResponse: { cancelled: true }, pendingResponse: null };
820
- }
821
- const selectOptions = readStringArray(request.metadata?.selectOptions);
822
- const freeformSentinel = optionalString(request.metadata?.freeformSentinel);
823
- const isFreeform = Boolean(freeformSentinel) && !selectOptions.includes(answer);
824
- const comment = permissionAnswer(response.updatedInput, QUESTION_COMMENT_HEADER) ?? "";
825
- return {
826
- uiResponse: { value: isFreeform ? freeformSentinel : answer },
827
- pendingResponse: {
828
- comment,
829
- freeform: isFreeform ? answer : null,
830
- },
831
- };
832
- }
833
746
  function buildExtensionUiResponse(request, response) {
834
747
  if (response.behavior === "deny") {
835
748
  return { cancelled: true };
@@ -907,8 +820,6 @@ export class PiRpcAgentSession {
907
820
  this.subscribers = new Set();
908
821
  this.activeToolCalls = new Map();
909
822
  this.pendingExtensionUiRequests = new Map();
910
- this.activeAskUserDialog = null;
911
- this.pendingCombinedAskUserResponse = null;
912
823
  this.activeTurnId = null;
913
824
  this.activeClientMessageId = null;
914
825
  this.activeAssistantMessageId = null;
@@ -920,14 +831,16 @@ export class PiRpcAgentSession {
920
831
  this.activePromptRequestId = null;
921
832
  this.pendingPromptResults = new Map();
922
833
  this.pendingSteerSubmissions = [];
923
- this.capturedUserEntries = [];
924
- this.capturedUserEntriesById = new Map();
834
+ this.contextUserEntries = [];
835
+ this.treeUserEntriesById = new Map();
925
836
  this.pendingExtensionResults = new Map();
926
837
  this.outOfBandCompactionEmit = null;
927
838
  this.outOfBandCompactionStarted = false;
928
839
  this.outOfBandCompactionCompleted = false;
929
840
  this.commandCache = null;
930
841
  this.closed = false;
842
+ this.closeController = new AbortController();
843
+ this.pendingExtensionHydrations = new Set();
931
844
  // Pi publishes the terminal before acknowledging abort. Autonomous runs have no
932
845
  // turn ID; retain their errors too until the cancellation request settles.
933
846
  this.interruptingTurn = null;
@@ -941,6 +854,7 @@ export class PiRpcAgentSession {
941
854
  this.cleanup = options.cleanup;
942
855
  this.extensionTimeoutMs = options.extensionTimeoutMs ?? DEFAULT_PI_EXTENSION_RESULT_TIMEOUT_MS;
943
856
  this.logger = options.logger;
857
+ this.extensionHost = createPiExtensionHost(this.logger);
944
858
  this.usagePoller = new PiUsagePoller({
945
859
  scheduler: options.usagePollScheduler,
946
860
  readStats: () => this.runtimeSession.getSessionStats(),
@@ -1102,7 +1016,9 @@ export class PiRpcAgentSession {
1102
1016
  }
1103
1017
  async *streamHistory() {
1104
1018
  await this.requestEntryCapture("history");
1105
- yield* streamPiHistory(this.provider, await this.runtimeSession.getMessages(), this.capturedUserEntries);
1019
+ yield* streamPiHistory(this.provider, await this.runtimeSession.getMessages(), this.contextUserEntries, {},
1020
+ // At most eight 2 MiB child files per replay; later cards retain their summaries.
1021
+ createPiExtensionHost(this.logger, undefined, 16 * 1024 * 1024), this.closeController.signal);
1106
1022
  }
1107
1023
  async getRuntimeInfo() {
1108
1024
  await this.refreshState();
@@ -1132,13 +1048,11 @@ export class PiRpcAgentSession {
1132
1048
  throw new Error(`No pending permission request with id '${requestId}'`);
1133
1049
  }
1134
1050
  this.pendingExtensionUiRequests.delete(requestId);
1135
- if (isCombinedAskUserPermission(request)) {
1136
- const combined = buildCombinedAskUserSelectionResponse(request, response);
1137
- this.pendingCombinedAskUserResponse = combined.pendingResponse;
1138
- this.runtimeSession.respondToExtensionUiRequest(requestId, combined.uiResponse);
1139
- }
1140
- else {
1141
- this.runtimeSession.respondToExtensionUiRequest(requestId, buildExtensionUiResponse(request, response));
1051
+ const mapped = this.extensionHost.respondToPermission(request, response);
1052
+ for (const reply of mapped?.responses ?? [
1053
+ { id: requestId, response: buildExtensionUiResponse(request, response) },
1054
+ ]) {
1055
+ this.runtimeSession.respondToExtensionUiRequest(reply.id, reply.response);
1142
1056
  }
1143
1057
  this.emit({
1144
1058
  type: "permission_resolved",
@@ -1232,7 +1146,7 @@ export class PiRpcAgentSession {
1232
1146
  }
1233
1147
  await this.refreshState().catch(() => undefined);
1234
1148
  await this.requestEntryCapture("rewind");
1235
- const targetEntry = this.capturedUserEntriesById.get(input.messageId);
1149
+ const targetEntry = this.treeUserEntriesById.get(input.messageId);
1236
1150
  if (!targetEntry) {
1237
1151
  throw new Error(`Pi rewind target ${input.messageId} was not found in captured tree entries`);
1238
1152
  }
@@ -1257,11 +1171,13 @@ export class PiRpcAgentSession {
1257
1171
  return;
1258
1172
  }
1259
1173
  this.closed = true;
1174
+ this.closeController.abort();
1260
1175
  this.usagePoller.close();
1261
1176
  try {
1262
1177
  await this.runtimeSession.close();
1263
1178
  }
1264
1179
  finally {
1180
+ await Promise.all(this.pendingExtensionHydrations);
1265
1181
  this.rejectAllExtensionResults(new Error("Pi session closed"));
1266
1182
  this.cleanup?.();
1267
1183
  }
@@ -1321,6 +1237,26 @@ export class PiRpcAgentSession {
1321
1237
  subscriber(event);
1322
1238
  }
1323
1239
  }
1240
+ emitExtensionOutput(output, turnId) {
1241
+ if (!output || this.closed)
1242
+ return;
1243
+ for (const event of output.events) {
1244
+ this.emit(event.type === "timeline" ? { ...event, turnId } : event);
1245
+ }
1246
+ const pending = output.hydration
1247
+ .then((events) => {
1248
+ if (this.closeController.signal.aborted)
1249
+ return;
1250
+ for (const event of events)
1251
+ this.emit(event);
1252
+ return undefined;
1253
+ })
1254
+ .catch((error) => {
1255
+ this.logger.warn({ err: error }, "Pi extension hydration failed");
1256
+ });
1257
+ this.pendingExtensionHydrations.add(pending);
1258
+ void pending.then(() => this.pendingExtensionHydrations.delete(pending));
1259
+ }
1324
1260
  currentTurnIdForEvent() {
1325
1261
  return this.activeTurnId ?? undefined;
1326
1262
  }
@@ -1544,11 +1480,11 @@ export class PiRpcAgentSession {
1544
1480
  this.rejectExtensionResult(requestId, error);
1545
1481
  }
1546
1482
  }
1547
- recordCapturedUserEntries(entries) {
1548
- this.capturedUserEntries.splice(0, this.capturedUserEntries.length, ...entries);
1549
- this.capturedUserEntriesById.clear();
1550
- for (const entry of entries) {
1551
- this.capturedUserEntriesById.set(entry.id, entry);
1483
+ recordCapturedUserEntries(input) {
1484
+ this.contextUserEntries.splice(0, this.contextUserEntries.length, ...input.contextEntries);
1485
+ this.treeUserEntriesById.clear();
1486
+ for (const entry of input.treeEntries) {
1487
+ this.treeUserEntriesById.set(entry.id, entry);
1552
1488
  }
1553
1489
  }
1554
1490
  handleSubmittedUserEntryMarker(message) {
@@ -1582,10 +1518,12 @@ export class PiRpcAgentSession {
1582
1518
  if (!payload) {
1583
1519
  return false;
1584
1520
  }
1585
- const entries = parseCapturedEntries(payload.entries);
1586
- this.recordCapturedUserEntries(entries);
1521
+ this.recordCapturedUserEntries({
1522
+ treeEntries: parseCapturedEntries(payload.treeEntries),
1523
+ contextEntries: parseCapturedEntries(payload.contextEntries),
1524
+ });
1587
1525
  if (typeof payload.requestId === "string") {
1588
- this.resolveExtensionResult(payload.requestId, entries);
1526
+ this.resolveExtensionResult(payload.requestId, undefined);
1589
1527
  }
1590
1528
  return true;
1591
1529
  }
@@ -1625,18 +1563,16 @@ export class PiRpcAgentSession {
1625
1563
  });
1626
1564
  return;
1627
1565
  }
1628
- if (this.respondToCombinedAskUserFollowUp(event)) {
1566
+ const mapped = this.extensionHost.mapDialog(event, this.provider);
1567
+ if (mapped?.type === "response") {
1568
+ this.runtimeSession.respondToExtensionUiRequest(event.id, mapped.response);
1629
1569
  return;
1630
1570
  }
1631
- const shouldCombineOptionalComment = event.method === "select" &&
1632
- this.activeAskUserDialog?.allowComment === true &&
1633
- this.activeAskUserDialog.allowMultiple === false;
1634
- const request = mapExtensionUiRequestToPermission(event, {
1635
- provider: this.provider,
1636
- label: "Pi",
1637
- combineOptionalComment: shouldCombineOptionalComment,
1638
- allowFreeform: this.activeAskUserDialog?.allowFreeform,
1639
- });
1571
+ if (mapped?.type === "deferred")
1572
+ return;
1573
+ const request = mapped?.type === "permission"
1574
+ ? mapped.request
1575
+ : mapExtensionUiRequestToPermission(event, { provider: this.provider, label: "Pi" });
1640
1576
  if (!request) {
1641
1577
  return;
1642
1578
  }
@@ -1648,27 +1584,6 @@ export class PiRpcAgentSession {
1648
1584
  turnId: this.currentTurnIdForEvent(),
1649
1585
  });
1650
1586
  }
1651
- respondToCombinedAskUserFollowUp(event) {
1652
- const pending = this.pendingCombinedAskUserResponse;
1653
- if (!pending || event.method !== "input") {
1654
- return false;
1655
- }
1656
- const placeholder = optionalString(event.placeholder);
1657
- if (pending.freeform !== null && !isOptionalInputPlaceholder(placeholder)) {
1658
- this.pendingCombinedAskUserResponse = {
1659
- ...pending,
1660
- freeform: null,
1661
- };
1662
- this.runtimeSession.respondToExtensionUiRequest(event.id, { value: pending.freeform });
1663
- return true;
1664
- }
1665
- if (isOptionalInputPlaceholder(placeholder)) {
1666
- this.pendingCombinedAskUserResponse = null;
1667
- this.runtimeSession.respondToExtensionUiRequest(event.id, { value: pending.comment });
1668
- return true;
1669
- }
1670
- return false;
1671
- }
1672
1587
  handleCommandOutput(textValue) {
1673
1588
  if (!this.activeTurnId) {
1674
1589
  return;
@@ -1786,7 +1701,22 @@ export class PiRpcAgentSession {
1786
1701
  case "tool_execution_start": {
1787
1702
  const toolCall = parseToolArgs(event.toolName, event.args);
1788
1703
  this.activeToolCalls.set(event.toolCallId, toolCall);
1789
- this.activeAskUserDialog = readActiveAskUserDialog(event.toolName, event.args);
1704
+ const request = this.extensionHost.onToolStart({
1705
+ callId: event.toolCallId,
1706
+ toolName: event.toolName,
1707
+ args: toolCall.args,
1708
+ status: "running",
1709
+ result: null,
1710
+ }, this.provider);
1711
+ if (request) {
1712
+ this.pendingExtensionUiRequests.set(request.id, request);
1713
+ this.emit({
1714
+ type: "permission_requested",
1715
+ provider: this.provider,
1716
+ request,
1717
+ turnId: this.currentTurnIdForEvent(),
1718
+ });
1719
+ }
1790
1720
  this.emitToolCallEvent(event.toolCallId, toolCall, "running", null, null);
1791
1721
  return;
1792
1722
  }
@@ -1857,13 +1787,16 @@ export class PiRpcAgentSession {
1857
1787
  handleToolExecutionEnd(event) {
1858
1788
  const toolCall = this.activeToolCalls.get(event.toolCallId) ?? parseToolArgs(event.toolName, null);
1859
1789
  this.activeToolCalls.delete(event.toolCallId);
1860
- if (event.toolName === "ask_user") {
1861
- this.activeAskUserDialog = null;
1862
- this.pendingCombinedAskUserResponse = null;
1863
- }
1864
1790
  const result = parseToolResult(event.result);
1865
1791
  const error = event.isError ? event.result : null;
1866
1792
  const status = event.isError ? "failed" : "completed";
1793
+ this.extensionHost.onToolEnd({
1794
+ callId: event.toolCallId,
1795
+ toolName: toolCall.toolName,
1796
+ args: toolCall.args,
1797
+ status,
1798
+ result,
1799
+ });
1867
1800
  this.emitToolCallEvent(event.toolCallId, toolCall, status, result, error);
1868
1801
  }
1869
1802
  emitCompactionTimeline(input) {
@@ -1935,6 +1868,8 @@ export class PiRpcAgentSession {
1935
1868
  return;
1936
1869
  }
1937
1870
  if (event.message.role === "custom") {
1871
+ const customMapping = this.extensionHost.mapCustomMessage(event.message);
1872
+ this.emitExtensionOutput(customMapping, turnId);
1938
1873
  const text = getUserMessageText(event.message.content);
1939
1874
  if (text) {
1940
1875
  this.emit({
@@ -1952,14 +1887,21 @@ export class PiRpcAgentSession {
1952
1887
  }
1953
1888
  emitToolCallEvent(toolCallId, toolCall, status, result, error) {
1954
1889
  const turnId = this.currentTurnIdForEvent();
1955
- const detail = this.mapToolDetail(toolCallId, toolCall, result);
1890
+ const mapping = this.extensionHost.mapToolCall({
1891
+ callId: toolCallId,
1892
+ toolName: toolCall.toolName,
1893
+ args: toolCall.args,
1894
+ status,
1895
+ result,
1896
+ });
1897
+ const detail = mapping?.detail ?? mapToolDetail(toolCall, result);
1956
1898
  if (!detail) {
1957
1899
  return false;
1958
1900
  }
1959
1901
  const baseItem = {
1960
1902
  type: "tool_call",
1961
1903
  callId: toolCallId,
1962
- name: resolveToolCallName(toolCall, result),
1904
+ name: mapping?.name ?? toolCall.toolName,
1963
1905
  detail,
1964
1906
  };
1965
1907
  const item = status === "failed" ? { ...baseItem, status, error } : { ...baseItem, status, error: null };
@@ -1969,11 +1911,9 @@ export class PiRpcAgentSession {
1969
1911
  turnId,
1970
1912
  item,
1971
1913
  });
1914
+ this.emitExtensionOutput(mapping, turnId);
1972
1915
  return true;
1973
1916
  }
1974
- mapToolDetail(_toolCallId, toolCall, result) {
1975
- return mapToolDetail(toolCall, result);
1976
- }
1977
1917
  completeTurn(turnId, messages) {
1978
1918
  const errorMessage = latestPiErrorMessage(messages);
1979
1919
  if (this.interruptingTurn &&
@@ -2142,7 +2082,16 @@ export class PiRpcAgentClient {
2142
2082
  if (!runtimeSession)
2143
2083
  throw new Error("Pi catalog runtime did not start");
2144
2084
  const catalogSession = runtimeSession;
2145
- const models = transformPiModels((await runProviderRefreshActivity(context, "get_available_models", () => catalogSession.getAvailableModels(null))).map((model) => mapPiModel(model, PI_PROVIDER)));
2085
+ const piModels = await runProviderRefreshActivity(context, "get_available_models", () => catalogSession.getAvailableModels(null));
2086
+ // A fresh Pi session starts on the model Pi resolves from its own settings.
2087
+ const { model: configuredModel } = await runProviderRefreshActivity(context, "get_state", () => catalogSession.getState());
2088
+ const models = transformPiModels(piModels.map((model) => {
2089
+ const mapped = mapPiModel(model, PI_PROVIDER);
2090
+ const isConfigured = model.provider === configuredModel?.provider && model.id === configuredModel?.id;
2091
+ if (isConfigured)
2092
+ mapped.isDefault = true;
2093
+ return mapped;
2094
+ }));
2146
2095
  return { models, modes: [] };
2147
2096
  }
2148
2097
  finally {
@@ -0,0 +1,4 @@
1
+ import type { ProviderSubagentInputEvent } from "../../../provider-subagents/store.js";
2
+ /** Child session files are immutable once the completed result exposes their path. */
3
+ export declare function mapPiChildSession(id: string, file: string, maxBytes?: number): Promise<ProviderSubagentInputEvent[]>;
4
+ //# sourceMappingURL=child-session.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { open } from "node:fs/promises";
2
+ import { limitAgentTimelineItemContent } from "../../../agent-timeline-content.js";
3
+ import { PiHistoryMapper } from "../history-mapper.js";
4
+ const MAX_BYTES = 2 * 1024 * 1024;
5
+ const MAX_ITEMS = 200;
6
+ /** Child session files are immutable once the completed result exposes their path. */
7
+ export async function mapPiChildSession(id, file, maxBytes = MAX_BYTES) {
8
+ try {
9
+ const handle = await open(file, "r");
10
+ try {
11
+ const fileSize = (await handle.stat()).size;
12
+ const size = Math.min(fileSize, MAX_BYTES, maxBytes);
13
+ if (!size)
14
+ return [];
15
+ const buffer = Buffer.alloc(size);
16
+ const { bytesRead } = await handle.read(buffer, 0, size, 0);
17
+ const text = buffer.toString("utf8", 0, bytesRead);
18
+ const completeText = bytesRead < fileSize ? text.slice(0, text.lastIndexOf("\n")) : text;
19
+ return parseChildTimeline(id, completeText);
20
+ }
21
+ finally {
22
+ await handle.close();
23
+ }
24
+ }
25
+ catch {
26
+ return [];
27
+ }
28
+ }
29
+ function parseChildTimeline(id, text) {
30
+ const mapper = new PiHistoryMapper("pi");
31
+ const events = [];
32
+ for (const line of text.split("\n")) {
33
+ if (!line || events.length >= MAX_ITEMS)
34
+ break;
35
+ let entry;
36
+ try {
37
+ entry = JSON.parse(line);
38
+ }
39
+ catch {
40
+ continue;
41
+ }
42
+ if (!entry.message || typeof entry.message !== "object" || !("role" in entry.message))
43
+ continue;
44
+ for (const mapped of mapper.mapMessages([entry.message])) {
45
+ if (mapped.type !== "timeline")
46
+ continue;
47
+ events.push({
48
+ type: "timeline",
49
+ id,
50
+ item: limitAgentTimelineItemContent(mapped.item),
51
+ ...(entry.timestamp ? { timestamp: entry.timestamp } : {}),
52
+ });
53
+ if (events.length >= MAX_ITEMS)
54
+ break;
55
+ }
56
+ }
57
+ return events;
58
+ }
59
+ //# sourceMappingURL=child-session.js.map
@@ -0,0 +1,68 @@
1
+ import type { AgentPermissionRequest, AgentPermissionResponse, ToolCallDetail, AgentTimelineItem } from "../../../agent-sdk-types.js";
2
+ import type { PiRuntimeEvent } from "../rpc-types.js";
3
+ import type { PiToolResult } from "../tool-call-mapper.js";
4
+ import type { ProviderSubagentInputEvent } from "../../../provider-subagents/store.js";
5
+ import type { PiAgentMessage } from "../rpc-types.js";
6
+ export interface PiExtensionToolCall {
7
+ callId: string;
8
+ toolName: string;
9
+ args: unknown;
10
+ status: "running" | "completed" | "failed";
11
+ result: PiToolResult;
12
+ }
13
+ export interface PiExtensionToolMapping {
14
+ name?: string;
15
+ detail?: ToolCallDetail;
16
+ timeline?: AgentTimelineItem[];
17
+ subagents?: ProviderSubagentInputEvent[];
18
+ /** Completed Pi child sessions to hydrate through the normal Pi history mapper. */
19
+ childSessions?: Array<{
20
+ id: string;
21
+ file: string;
22
+ }>;
23
+ }
24
+ export interface PiExtensionCustomMapping {
25
+ subagents: ProviderSubagentInputEvent[];
26
+ childSessions?: Array<{
27
+ id: string;
28
+ file: string;
29
+ }>;
30
+ }
31
+ export type PiExtensionDialog = Extract<PiRuntimeEvent, {
32
+ type: "extension_ui_request";
33
+ }>;
34
+ export interface PiExtensionUiResponse {
35
+ value?: string;
36
+ cancelled?: boolean;
37
+ confirmed?: boolean;
38
+ }
39
+ export type PiExtensionDialogMapping = {
40
+ type: "permission";
41
+ request: AgentPermissionRequest;
42
+ } | {
43
+ type: "response";
44
+ response: PiExtensionUiResponse;
45
+ } | {
46
+ type: "deferred";
47
+ };
48
+ export interface PiExtensionUiReply {
49
+ responses: Array<{
50
+ id: string;
51
+ response: PiExtensionUiResponse;
52
+ }>;
53
+ }
54
+ export interface PiExtensionSession {
55
+ mapToolCall?(call: PiExtensionToolCall): PiExtensionToolMapping | undefined;
56
+ mapCustomMessage?(message: Extract<PiAgentMessage, {
57
+ role: "custom";
58
+ }>): PiExtensionCustomMapping | undefined;
59
+ onToolStart?(call: PiExtensionToolCall, provider: string): AgentPermissionRequest | undefined;
60
+ onToolEnd?(call: PiExtensionToolCall): void;
61
+ mapDialog?(dialog: PiExtensionDialog, provider: string): PiExtensionDialogMapping | undefined;
62
+ respondToPermission?(request: AgentPermissionRequest, response: AgentPermissionResponse): PiExtensionUiReply | undefined;
63
+ }
64
+ export interface PiExtension {
65
+ id: string;
66
+ createSession(): PiExtensionSession;
67
+ }
68
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1,3 @@
1
+ import type { PiExtension } from "../contract.js";
2
+ export declare const gotgenesPiSubagents: PiExtension;
3
+ //# sourceMappingURL=index.d.ts.map