@north-light/crouter 0.3.170 → 0.3.171

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 (121) hide show
  1. package/dist/build-root.d.ts +1 -16
  2. package/dist/build-root.js +1 -25
  3. package/dist/builtin-memory/insights/capture.md +66 -0
  4. package/dist/builtin-memory/insights/init.md +45 -0
  5. package/dist/builtin-memory/internal/plugins.md +12 -11
  6. package/dist/cli.js +2 -6
  7. package/dist/clients/attach/__tests__/attach-keybindings.test.js +7 -1
  8. package/dist/clients/attach/__tests__/context-message.test.js +50 -3
  9. package/dist/clients/attach/__tests__/diagram.test.js +1 -1
  10. package/dist/clients/attach/chrome/header.d.ts +10 -0
  11. package/dist/clients/attach/chrome/header.js +35 -0
  12. package/dist/clients/attach/chrome/status-line.d.ts +3 -0
  13. package/dist/clients/attach/chrome/status-line.js +5 -0
  14. package/dist/clients/attach/chrome/widgets.js +7 -18
  15. package/dist/clients/attach/input/controller.d.ts +10 -0
  16. package/dist/clients/attach/input/controller.js +3 -0
  17. package/dist/clients/attach/overlays/help.d.ts +33 -0
  18. package/dist/clients/attach/overlays/help.js +204 -0
  19. package/dist/clients/attach/render/chat-view.d.ts +81 -9
  20. package/dist/clients/attach/render/chat-view.js +434 -90
  21. package/dist/clients/attach/render/context-message.js +3 -3
  22. package/dist/clients/attach/render/diagram.js +2 -2
  23. package/dist/clients/attach/render/measured-container.d.ts +94 -0
  24. package/dist/clients/attach/render/measured-container.js +307 -0
  25. package/dist/clients/attach/render/transcript-copy.d.ts +9 -0
  26. package/dist/clients/attach/render/transcript-copy.js +81 -0
  27. package/dist/clients/attach/render/viewport.d.ts +29 -0
  28. package/dist/clients/attach/render/viewport.js +160 -0
  29. package/dist/clients/attach/session/bindings.d.ts +5 -1
  30. package/dist/clients/attach/session/bindings.js +16 -0
  31. package/dist/clients/attach/session/context.d.ts +5 -3
  32. package/dist/clients/attach/session/frame.d.ts +34 -0
  33. package/dist/clients/attach/session/frame.js +173 -0
  34. package/dist/clients/attach/session/frames.d.ts +2 -0
  35. package/dist/clients/attach/session/frames.js +3 -0
  36. package/dist/clients/attach/session/input-wiring.d.ts +10 -4
  37. package/dist/clients/attach/session/input-wiring.js +13 -13
  38. package/dist/clients/attach/session/keys.d.ts +4 -1
  39. package/dist/clients/attach/session/keys.js +9 -4
  40. package/dist/clients/attach/session/layout.d.ts +10 -8
  41. package/dist/clients/attach/session/layout.js +32 -31
  42. package/dist/clients/attach/session/mouse.d.ts +15 -0
  43. package/dist/clients/attach/session/mouse.js +61 -0
  44. package/dist/clients/attach/slash/dispatch.d.ts +10 -0
  45. package/dist/clients/attach/slash/dispatch.js +35 -2
  46. package/dist/clients/attach/viewer.js +605 -581
  47. package/dist/clients/web/dev-server.d.ts +4 -1
  48. package/dist/clients/web/dev-server.js +8 -19
  49. package/dist/clients/web/web-cmd.js +1 -1
  50. package/dist/commands/human/prompts.js +2 -1
  51. package/dist/commands/pkg/plugin-manage.js +143 -68
  52. package/dist/commands/sys/__tests__/setup-core.test.js +4 -2
  53. package/dist/commands/sys/setup-core.js +21 -16
  54. package/dist/core/clipboard-text.d.ts +8 -0
  55. package/dist/core/clipboard-text.js +18 -0
  56. package/dist/core/command-manifests/registry.d.ts +1 -3
  57. package/dist/core/command-plugins/bundle.d.ts +29 -0
  58. package/dist/core/command-plugins/bundle.js +198 -0
  59. package/dist/core/command-plugins/discovery.d.ts +0 -8
  60. package/dist/core/command-plugins/discovery.js +2 -39
  61. package/dist/core/command-plugins/transport/http-fetch.d.ts +4 -30
  62. package/dist/core/command-plugins/transport/http-fetch.js +17 -95
  63. package/dist/core/command.d.ts +1 -13
  64. package/dist/core/command.js +1 -16
  65. package/dist/core/config.js +2 -1
  66. package/dist/core/keybindings/__tests__/resolve.test.js +4 -0
  67. package/dist/core/keybindings/attach-control.d.ts +3 -0
  68. package/dist/core/keybindings/attach-control.js +1 -0
  69. package/dist/core/keybindings/catalog.d.ts +2 -2
  70. package/dist/core/keybindings/catalog.js +8 -0
  71. package/dist/core/runtime/broker-protocol.d.ts +13 -1
  72. package/dist/core/runtime/broker.js +213 -5
  73. package/dist/core/runtime/canvas-extensions.d.ts +3 -0
  74. package/dist/core/runtime/canvas-extensions.js +3 -0
  75. package/dist/core/runtime/front-door.js +4 -4
  76. package/dist/core/runtime/kickoff.d.ts +1 -5
  77. package/dist/core/runtime/kickoff.js +1 -5
  78. package/dist/core/runtime/naming.d.ts +7 -0
  79. package/dist/core/runtime/naming.js +5 -2
  80. package/dist/core/runtime/node-read.js +15 -0
  81. package/dist/core/runtime/package-health.d.ts +6 -21
  82. package/dist/core/runtime/package-health.js +12 -123
  83. package/dist/core/runtime/stop-guard.d.ts +1 -1
  84. package/dist/core/runtime/stop-guard.js +3 -3
  85. package/dist/core/runtime/tool-group-summary.d.ts +19 -0
  86. package/dist/core/runtime/tool-group-summary.js +117 -0
  87. package/dist/core/termrender/code-doc.d.ts +12 -0
  88. package/dist/core/termrender/code-doc.js +91 -0
  89. package/dist/core/termrender/display.d.ts +12 -0
  90. package/dist/core/termrender/display.js +19 -0
  91. package/dist/core/termrender/termrender.d.ts +73 -0
  92. package/dist/core/termrender/termrender.js +795 -0
  93. package/dist/core/termrender/version.d.ts +1 -0
  94. package/dist/core/termrender/version.js +1 -0
  95. package/dist/core/user-settings.d.ts +14 -0
  96. package/dist/core/user-settings.js +20 -0
  97. package/dist/daemon/crtrd.js +5 -6
  98. package/dist/index.d.ts +2 -0
  99. package/dist/index.js +1 -0
  100. package/dist/pi-extensions/__tests__/canvas-structured-output.test.d.ts +1 -0
  101. package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +63 -0
  102. package/dist/pi-extensions/canvas-stophook.js +2 -2
  103. package/dist/pi-extensions/canvas-structured-output.js +52 -8
  104. package/dist/pi-extensions/summary-tool.d.ts +33 -0
  105. package/dist/pi-extensions/summary-tool.js +62 -0
  106. package/dist/shared/generated-context.d.ts +19 -4
  107. package/dist/shared/generated-context.js +98 -7
  108. package/dist/shared/tool-groups.d.ts +23 -0
  109. package/dist/shared/tool-groups.js +60 -0
  110. package/dist/types.d.ts +11 -0
  111. package/dist/types.js +1 -0
  112. package/dist/web-client/assets/{index-U_NZ66VE.js → index-D7I3gHCL.js} +26 -19
  113. package/dist/web-client/index.html +1 -1
  114. package/dist/web-client/sw.js +1 -1
  115. package/package.json +3 -1
  116. package/runtime.lock.json +62 -3
  117. package/scripts/postinstall.mjs +14 -1
  118. package/dist/core/command-plugins/store.d.ts +0 -16
  119. package/dist/core/command-plugins/store.js +0 -64
  120. package/dist/core/runtime/fault-recovery-nudge.d.ts +0 -3
  121. package/dist/core/runtime/fault-recovery-nudge.js +0 -3
@@ -10,8 +10,8 @@ type ProfileMenuAction = 'up' | 'down' | 'select' | 'accept-default' | 'abort' |
10
10
  type SetupAction = 'cancel' | 'tab-next' | 'tab-previous' | 'open' | 'list-down' | 'list-up' | 'toggle' | 'details' | 'advance' | 'cell-previous' | 'cell-next';
11
11
  type SetupKeybindingAction = 'edit' | 'commit-edit' | 'cancel-edit' | 'unbind' | 'reset-row' | 'reset-all' | 'save' | 'discard' | 'page-up' | 'page-down' | 'filter' | 'filter-delete' | 'filter-clear';
12
12
  type SetupModelsAction = 'open' | 'commit' | 'cancel' | 'scope-next';
13
- export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | AttachMenuBindingId | `crtr.tmux.menu-global.${TmuxMenuEntryAction}` | `crtr.tmux.node.${'next' | 'previous'}` | `crtr.tmux.view.${'next' | 'previous'}` | 'crtr.tmux.inbox.toggle' | AttachBindingId | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.view.host.${'force-quit' | 'editor.backspace'}` | `crtr.view.canvas.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.chat.${'submit' | 'contextual-cancel' | 'reconnect' | 'quit'}` | `crtr.view.git-pr.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.inbox.${InboxAction}` | `crtr.view.linkedin.${LinkedinAction}` | `crtr.view.prompt-review.${'down' | 'up' | 'compare-previous' | 'compare-next' | 'toggle-mode' | 'expand' | 'export' | 'refresh' | 'quit'}` | `crtr.view.workspace-sidebar.${'down' | 'up' | 'open' | 'refresh' | 'quit'}` | `crtr.view.inspector.${'down' | 'up' | 'section-next' | 'section-prev' | 'page-down' | 'page-up' | 'toggle-detail' | 'cancel-item' | 'open-log' | 'confirm' | 'dismiss' | 'retarget' | 'refresh' | 'quit'}` | `crtr.view-picker.${'down' | 'up' | 'run' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}` | `crtr.setup.models.${SetupModelsAction}`;
14
- export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.attach">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.tmux.view.next"> | BindingDefinition<"crtr.tmux.view.previous"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.view.host.force-quit"> | BindingDefinition<"crtr.view.host.editor.backspace"> | BindingDefinition<"crtr.view.chat.submit"> | BindingDefinition<"crtr.view.chat.contextual-cancel"> | BindingDefinition<"crtr.view.chat.reconnect"> | BindingDefinition<"crtr.view.chat.quit"> | BindingDefinition<"crtr.view.prompt-review.down"> | BindingDefinition<"crtr.view.prompt-review.up"> | BindingDefinition<"crtr.view.prompt-review.compare-previous"> | BindingDefinition<"crtr.view.prompt-review.compare-next"> | BindingDefinition<"crtr.view.prompt-review.toggle-mode"> | BindingDefinition<"crtr.view.prompt-review.expand"> | BindingDefinition<"crtr.view.prompt-review.export"> | BindingDefinition<"crtr.view.prompt-review.refresh"> | BindingDefinition<"crtr.view.prompt-review.quit"> | BindingDefinition<"crtr.view.workspace-sidebar.down"> | BindingDefinition<"crtr.view.workspace-sidebar.up"> | BindingDefinition<"crtr.view.workspace-sidebar.open"> | BindingDefinition<"crtr.view.workspace-sidebar.refresh"> | BindingDefinition<"crtr.view.workspace-sidebar.quit"> | BindingDefinition<"crtr.view.inspector.down"> | BindingDefinition<"crtr.view.inspector.up"> | BindingDefinition<"crtr.view.inspector.section-next"> | BindingDefinition<"crtr.view.inspector.section-prev"> | BindingDefinition<"crtr.view.inspector.page-down"> | BindingDefinition<"crtr.view.inspector.page-up"> | BindingDefinition<"crtr.view.inspector.toggle-detail"> | BindingDefinition<"crtr.view.inspector.cancel-item"> | BindingDefinition<"crtr.view.inspector.open-log"> | BindingDefinition<"crtr.view.inspector.confirm"> | BindingDefinition<"crtr.view.inspector.dismiss"> | BindingDefinition<"crtr.view.inspector.retarget"> | BindingDefinition<"crtr.view.inspector.refresh"> | BindingDefinition<"crtr.view.inspector.quit"> | BindingDefinition<"crtr.view-picker.down"> | BindingDefinition<"crtr.view-picker.up"> | BindingDefinition<"crtr.view-picker.run"> | BindingDefinition<"crtr.view-picker.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.9" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
13
+ export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | AttachMenuBindingId | `crtr.tmux.menu-global.${TmuxMenuEntryAction}` | `crtr.tmux.node.${'next' | 'previous'}` | `crtr.tmux.view.${'next' | 'previous'}` | 'crtr.tmux.inbox.toggle' | `crtr.attach.scroll.${'up' | 'down' | 'page-up' | 'page-down' | 'live'}` | 'crtr.attach.mouse.toggle' | 'crtr.attach.copy.message' | 'crtr.attach.help.toggle' | AttachBindingId | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.view.host.${'force-quit' | 'editor.backspace'}` | `crtr.view.canvas.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.chat.${'submit' | 'contextual-cancel' | 'reconnect' | 'quit'}` | `crtr.view.git-pr.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.inbox.${InboxAction}` | `crtr.view.linkedin.${LinkedinAction}` | `crtr.view.prompt-review.${'down' | 'up' | 'compare-previous' | 'compare-next' | 'toggle-mode' | 'expand' | 'export' | 'refresh' | 'quit'}` | `crtr.view.workspace-sidebar.${'down' | 'up' | 'open' | 'refresh' | 'quit'}` | `crtr.view.inspector.${'down' | 'up' | 'section-next' | 'section-prev' | 'page-down' | 'page-up' | 'toggle-detail' | 'cancel-item' | 'open-log' | 'confirm' | 'dismiss' | 'retarget' | 'refresh' | 'quit'}` | `crtr.view-picker.${'down' | 'up' | 'run' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}` | `crtr.setup.models.${SetupModelsAction}`;
14
+ export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.attach">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.tmux.view.next"> | BindingDefinition<"crtr.tmux.view.previous"> | BindingDefinition<"crtr.attach.scroll.up"> | BindingDefinition<"crtr.attach.scroll.down"> | BindingDefinition<"crtr.attach.scroll.page-up"> | BindingDefinition<"crtr.attach.scroll.page-down"> | BindingDefinition<"crtr.attach.scroll.live"> | BindingDefinition<"crtr.attach.mouse.toggle"> | BindingDefinition<"crtr.attach.copy.message"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.view.host.force-quit"> | BindingDefinition<"crtr.view.host.editor.backspace"> | BindingDefinition<"crtr.view.chat.submit"> | BindingDefinition<"crtr.view.chat.contextual-cancel"> | BindingDefinition<"crtr.view.chat.reconnect"> | BindingDefinition<"crtr.view.chat.quit"> | BindingDefinition<"crtr.view.prompt-review.down"> | BindingDefinition<"crtr.view.prompt-review.up"> | BindingDefinition<"crtr.view.prompt-review.compare-previous"> | BindingDefinition<"crtr.view.prompt-review.compare-next"> | BindingDefinition<"crtr.view.prompt-review.toggle-mode"> | BindingDefinition<"crtr.view.prompt-review.expand"> | BindingDefinition<"crtr.view.prompt-review.export"> | BindingDefinition<"crtr.view.prompt-review.refresh"> | BindingDefinition<"crtr.view.prompt-review.quit"> | BindingDefinition<"crtr.view.workspace-sidebar.down"> | BindingDefinition<"crtr.view.workspace-sidebar.up"> | BindingDefinition<"crtr.view.workspace-sidebar.open"> | BindingDefinition<"crtr.view.workspace-sidebar.refresh"> | BindingDefinition<"crtr.view.workspace-sidebar.quit"> | BindingDefinition<"crtr.view.inspector.down"> | BindingDefinition<"crtr.view.inspector.up"> | BindingDefinition<"crtr.view.inspector.section-next"> | BindingDefinition<"crtr.view.inspector.section-prev"> | BindingDefinition<"crtr.view.inspector.page-down"> | BindingDefinition<"crtr.view.inspector.page-up"> | BindingDefinition<"crtr.view.inspector.toggle-detail"> | BindingDefinition<"crtr.view.inspector.cancel-item"> | BindingDefinition<"crtr.view.inspector.open-log"> | BindingDefinition<"crtr.view.inspector.confirm"> | BindingDefinition<"crtr.view.inspector.dismiss"> | BindingDefinition<"crtr.view.inspector.retarget"> | BindingDefinition<"crtr.view.inspector.refresh"> | BindingDefinition<"crtr.view.inspector.quit"> | BindingDefinition<"crtr.view-picker.down"> | BindingDefinition<"crtr.view-picker.up"> | BindingDefinition<"crtr.view-picker.run"> | BindingDefinition<"crtr.view-picker.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.9" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
15
15
  export declare const BINDING_IDS: ReadonlySet<string>;
16
16
  export declare const SAFETY_REQUIREMENTS: readonly SafetyRequirement[];
17
17
  export {};
@@ -74,11 +74,19 @@ export const BINDING_CATALOG = Object.freeze([
74
74
  ...attachEntry('crtr.tmux.menu.attach.clear-or-detach', 'crtr.attach.clear-or-detach', 'Clear or detach', 'Clear input or detach when input is empty.', 'c', ['ctrl+c']),
75
75
  ...attachEntry('crtr.tmux.menu.attach.mode-cycle', 'crtr.mode.cycle', 'Cycle mode', 'Cycle the active agent mode.', 'p', ['alt+shift+p']),
76
76
  ...attachEntry('crtr.tmux.menu.attach.graph.toggle', 'crtr.attach.graph.toggle', 'Toggle graph', 'Open or close the graph overlay.', 'g', ['alt+g']),
77
+ ...attachEntry('crtr.tmux.menu.attach.help.toggle', 'crtr.attach.help.toggle', 'Help', 'Open the attach help overlay.', 'k', ['alt+shift+k']),
77
78
  ...attachEntry('crtr.tmux.menu.attach.model-ladder.next', 'crtr.attach.model-ladder.next', 'Next model ladder', 'Move to the next model ladder rung.', ']', ['alt+m']),
78
79
  ...attachEntry('crtr.tmux.menu.attach.model-ladder.previous', 'crtr.attach.model-ladder.previous', 'Previous model ladder', 'Move to the previous model ladder rung.', '[', ['alt+shift+m']),
79
80
  ...attachEntry('crtr.tmux.menu.attach.command.inspect', 'crtr.attach.command.inspect', 'Inspect loaded command', 'Open the loaded slash command’s expanded prompt.', 'h', ['alt+shift+h']),
80
81
  ...attachEntry('crtr.tmux.menu.attach.file-review', 'crtr.attach.file-review', 'Review a file', 'Open a line-anchored review of a file named in the transcript.', 'r', ['alt+shift+r']),
81
82
  ...attachEntry('crtr.tmux.menu.attach.profile-files', 'crtr.attach.profile-files', 'Search profile files', 'Search files across the node’s profile projects, cwd, and ancestor crouter memory stores.', 'f', []),
83
+ b('crtr.attach.scroll.up', 'Attach', 'Scroll up', 'Scroll the transcript one line up.', ['shift+up'], 'terminal', ATTACH_CONTEXTS),
84
+ b('crtr.attach.scroll.down', 'Attach', 'Scroll down', 'Scroll the transcript one line down.', ['shift+down'], 'terminal', ATTACH_CONTEXTS),
85
+ b('crtr.attach.scroll.page-up', 'Attach', 'Scroll page up', 'Scroll the transcript one page up.', ['pageup'], 'terminal', ATTACH_CONTEXTS),
86
+ b('crtr.attach.scroll.page-down', 'Attach', 'Scroll page down', 'Scroll the transcript one page down.', ['pagedown'], 'terminal', ATTACH_CONTEXTS),
87
+ b('crtr.attach.scroll.live', 'Attach', 'Return to live', 'Return the transcript to the live bottom.', ['shift+end'], 'terminal', ATTACH_CONTEXTS),
88
+ b('crtr.attach.mouse.toggle', 'Attach', 'Toggle mouse wheel scrolling', 'Toggle mouse wheel scrolling on or off.', ['alt+shift+o'], 'terminal', ATTACH_CONTEXTS),
89
+ b('crtr.attach.copy.message', 'Attach', 'Copy visible message', 'Copy the newest message visible in the transcript viewport.', ['alt+shift+y'], 'terminal', ATTACH_CONTEXTS),
82
90
  b('crtr.graph.down', 'GRAPH', 'Move down', 'Move to the next graph row.', ['j'], 'terminal', ['graph']),
83
91
  b('crtr.graph.up', 'GRAPH', 'Move up', 'Move to the previous graph row.', ['k'], 'terminal', ['graph']),
84
92
  b('crtr.graph.first', 'GRAPH', 'First row', 'Move to the first graph row.', ['g'], 'terminal', ['graph']),
@@ -1,6 +1,7 @@
1
1
  import type { AgentSession, AgentSessionEvent, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcSessionState, SessionEntry, SessionInfo, SettingsConfig, SessionStats } from '@earendil-works/pi-coding-agent';
2
2
  import type { Api, ImageContent, Model } from '@earendil-works/pi-ai';
3
3
  import type { BrokerErrorFrame } from '../../api/dto/broker.js';
4
+ import type { ToolGroupSummary } from '../../shared/tool-groups.js';
4
5
  /** pi's own RPC types, re-exported unchanged. `broker-protocol` stays the one
5
6
  * import site for the wire union; the shapes themselves are pi's, versioned by
6
7
  * pi, so a pi change is a compile error here instead of silent drift. */
@@ -45,6 +46,10 @@ export interface BrokerSnapshot {
45
46
  }>;
46
47
  title: string | undefined;
47
48
  };
49
+ /** Broker-owned summaries keyed by the first tool-call id in each group.
50
+ * Structured objects with bullets, nodesSpawned, and filesEdited.
51
+ * Optional so snapshots from an older live runtime remain readable. */
52
+ toolGroupSummaries?: Record<string, ToolGroupSummary>;
48
53
  }
49
54
  /** Opens the connection and FIXES this client's role for its lifetime. A repeated
50
55
  * `hello` on an already-helloed socket never changes the established role. */
@@ -563,10 +568,17 @@ export interface ExtensionUIDismissFrame {
563
568
  type: 'extension_ui_dismiss';
564
569
  id: string;
565
570
  }
571
+ /** A completed broker-side tool-group summary. The viewer owns presentation;
572
+ * this frame carries the durable group key and structured summary object. */
573
+ export interface ToolGroupSummaryFrame {
574
+ type: 'tool_group_summary';
575
+ key: string;
576
+ summary: ToolGroupSummary;
577
+ }
566
578
  /** Everything the broker can send. Live `AgentSessionEvent`s are relayed
567
579
  * verbatim (the broker adds nothing); the broker's own control frames carry
568
580
  * non-colliding `type` discriminants. */
569
- export type BrokerToClient = WelcomeFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | AgentSessionEvent;
581
+ export type BrokerToClient = WelcomeFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | ToolGroupSummaryFrame | AgentSessionEvent;
570
582
  /** Encode one frame as a single newline-terminated JSON line. */
571
583
  export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
572
584
  /** Byte bounds for a {@link FrameDecoder} (C5). */
@@ -32,6 +32,8 @@ import { operationIdContext } from '../events/operation-id.js';
32
32
  import { findMissingPackagePaths } from './package-health.js';
33
33
  import { SessionListCache } from './session-list-cache.js';
34
34
  import { getNode, updateNode } from '../canvas/index.js';
35
+ import { readConfig } from '../config.js';
36
+ import { atomicWriteJson, readJsonIfExists } from '../fs-utils.js';
35
37
  import { buildRefInventory } from '../memory/inline-ref-inventory.js';
36
38
  import { buildGuidance } from '../memory/inline-ref-guidance.js';
37
39
  import { clearSessionCache } from '../substrate/session-cache.js';
@@ -44,7 +46,9 @@ import { PoolCredentialStore } from '../pool-credential-store.js';
44
46
  import { clearFault, isModelNotFoundError, isProviderUnconfiguredError, readFault, recordFault, recordFaultForCurrentNode } from './fault.js';
45
47
  import { clearCleanAbort, markBusy, markCleanAbort } from './busy.js';
46
48
  import { extractCoolingDeadline } from './managed-provider-cooling.js';
47
- import { AUTH_FAULT_RECOVERY_BODY, CONNECTION_FAULT_RECOVERY_BODY, PROVIDER_FAULT_RECOVERY_BODY } from './fault-recovery-nudge.js';
49
+ import { AUTH_FAULT_RECOVERY_BODY, CONNECTION_FAULT_RECOVERY_BODY, PROVIDER_FAULT_RECOVERY_BODY, formatModelFallbackRecovery, generatedContextText, } from '../../shared/generated-context.js';
50
+ import { assistantVisibleText, isTrueUserMessage, normalizeToolGroupSummaryRecord, } from '../../shared/tool-groups.js';
51
+ import { headlessToolGroupSummary, serializeToolGroup } from './tool-group-summary.js';
48
52
  import { markFatalOnExhaust, nextFaultRetry, policyFor } from './fault-recovery.js';
49
53
  import { probeOnline as probeOnlineReal } from './connectivity.js';
50
54
  import { CRTR_CYCLE_CUSTOM_TYPE, cycleAwareMessages } from './session-cycles.js';
@@ -588,7 +592,26 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
588
592
  const displayStatuses = new Map();
589
593
  const displayWidgets = new Map();
590
594
  let displayTitle;
595
+ const toolSummariesPath = join(jobDir(nodeId), 'tool-summaries.json');
596
+ const toolGroupSummaries = new Map();
597
+ try {
598
+ const persisted = readJsonIfExists(toolSummariesPath);
599
+ for (const [key, summary] of Object.entries(normalizeToolGroupSummaryRecord(persisted))) {
600
+ toolGroupSummaries.set(key, summary);
601
+ }
602
+ }
603
+ catch {
604
+ // A malformed sidecar merely withholds summaries; transcript tools remain visible.
605
+ }
606
+ const summaryAttempted = new Set(toolGroupSummaries.keys());
607
+ const generatingToolGroups = new Set();
608
+ const toolGroupsByCallId = new Map();
609
+ const summaryTasks = new Set();
610
+ let openToolGroup;
611
+ let lastToolGroupBoundaryProse = '';
612
+ let sawAssistantProse = false;
591
613
  let disposed = false;
614
+ let disposePending = false;
592
615
  let agentRunActive = false;
593
616
  let shutdownRequested = false;
594
617
  let server;
@@ -729,6 +752,168 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
729
752
  }
730
753
  broadcast(frame);
731
754
  };
755
+ /** Record and asynchronously summarize the maximal non-boundary region between
756
+ * visible prose messages. A boundary can land while a tool is still running,
757
+ * so closed groups retain their call ids until every result has joined the
758
+ * same serialized segment; only then may they become collapsible. */
759
+ const ensureOpenToolGroup = () => {
760
+ if (openToolGroup === undefined) {
761
+ openToolGroup = {
762
+ toolCount: 0,
763
+ pieces: [],
764
+ boundaryProse: lastToolGroupBoundaryProse,
765
+ pendingToolIds: new Set(),
766
+ closed: false,
767
+ summarizeWhenSettled: false,
768
+ };
769
+ }
770
+ return openToolGroup;
771
+ };
772
+ const generateSettledToolGroup = (group) => {
773
+ if (!group.closed || group.pendingToolIds.size > 0 || group.key === undefined || !group.summarizeWhenSettled)
774
+ return;
775
+ const key = group.key;
776
+ if (summaryAttempted.has(key) || generatingToolGroups.has(key))
777
+ return;
778
+ summaryAttempted.add(key);
779
+ generatingToolGroups.add(key);
780
+ let task;
781
+ task = headlessToolGroupSummary(serializeToolGroup(group.boundaryProse, group.pieces))
782
+ .then((summary) => {
783
+ if (summary === null)
784
+ return;
785
+ toolGroupSummaries.set(key, summary);
786
+ try {
787
+ atomicWriteJson(toolSummariesPath, Object.fromEntries(toolGroupSummaries));
788
+ }
789
+ catch {
790
+ // A delivery without durable state would disappear on reattach, so do
791
+ // not advertise it. The calls remain folded normally instead.
792
+ toolGroupSummaries.delete(key);
793
+ return;
794
+ }
795
+ broadcast({ type: 'tool_group_summary', key, summary });
796
+ })
797
+ .catch(() => undefined)
798
+ .finally(() => {
799
+ generatingToolGroups.delete(key);
800
+ summaryTasks.delete(task);
801
+ });
802
+ summaryTasks.add(task);
803
+ };
804
+ const finishOpenToolGroup = () => {
805
+ const group = openToolGroup;
806
+ openToolGroup = undefined;
807
+ if (group === undefined)
808
+ return;
809
+ group.closed = true;
810
+ // Configuration is read at the boundary, not when a slow tool later settles.
811
+ group.summarizeWhenSettled = readConfig('user').summarize_tool_calls;
812
+ generateSettledToolGroup(group);
813
+ };
814
+ const visibleBoundary = (prose) => {
815
+ finishOpenToolGroup();
816
+ lastToolGroupBoundaryProse = prose;
817
+ };
818
+ const observeToolGroupEvent = (event) => {
819
+ // The viewer turns every compaction_end into a summary, cancellation, or
820
+ // error line, each of which is a hard transcript seam.
821
+ if (event.type === 'compaction_end') {
822
+ finishOpenToolGroup();
823
+ return;
824
+ }
825
+ // A failed automatic retry is rendered as ChatView's visible error line.
826
+ // Successful retries only clear activity and remain within the same group.
827
+ if (event.type === 'auto_retry_end' && event.success !== true) {
828
+ finishOpenToolGroup();
829
+ return;
830
+ }
831
+ // The attach viewer renders aborted and errored assistant endings as visible
832
+ // transcript seams. Close their completed tool run here too; any tool that
833
+ // never emitted its execution_end keeps the group unsettled and visible.
834
+ if (event.type === 'message_end') {
835
+ const message = event.message;
836
+ if (message !== null && typeof message === 'object') {
837
+ const item = message;
838
+ if (item.role === 'assistant' && (item.stopReason === 'aborted' || item.stopReason === 'error')) {
839
+ finishOpenToolGroup();
840
+ }
841
+ }
842
+ return;
843
+ }
844
+ if (event.type === 'tool_execution_start') {
845
+ const group = ensureOpenToolGroup();
846
+ const id = typeof event.toolCallId === 'string' ? event.toolCallId : '';
847
+ if (group.key === undefined && id !== '')
848
+ group.key = id;
849
+ if (id !== '') {
850
+ group.pendingToolIds.add(id);
851
+ toolGroupsByCallId.set(id, group);
852
+ }
853
+ group.toolCount += 1;
854
+ group.pieces.push({ type: 'tool', name: typeof event.toolName === 'string' ? event.toolName : 'tool', args: event.args });
855
+ return;
856
+ }
857
+ if (event.type === 'tool_execution_end') {
858
+ const id = typeof event.toolCallId === 'string' ? event.toolCallId : '';
859
+ const group = id === '' ? openToolGroup : toolGroupsByCallId.get(id);
860
+ if (group !== undefined) {
861
+ group.pieces.push({
862
+ type: 'result',
863
+ name: typeof event.toolName === 'string' ? event.toolName : 'tool',
864
+ result: event.result,
865
+ isError: event.isError === true,
866
+ });
867
+ if (id !== '') {
868
+ group.pendingToolIds.delete(id);
869
+ toolGroupsByCallId.delete(id);
870
+ }
871
+ generateSettledToolGroup(group);
872
+ }
873
+ return;
874
+ }
875
+ if (event.type !== 'message_start' && event.type !== 'message_update')
876
+ return;
877
+ const message = event.message;
878
+ if (message === null || typeof message !== 'object')
879
+ return;
880
+ const item = message;
881
+ const role = item.role;
882
+ if (role === 'assistant') {
883
+ if (event.type === 'message_start')
884
+ sawAssistantProse = false;
885
+ const prose = assistantVisibleText(item);
886
+ if (!sawAssistantProse && prose.trim() !== '') {
887
+ sawAssistantProse = true;
888
+ visibleBoundary(prose);
889
+ }
890
+ return;
891
+ }
892
+ if (event.type !== 'message_start')
893
+ return;
894
+ if (role === 'compactionSummary' || role === 'branchSummary' || role === 'bashExecution' || role === 'status' || role === 'error' || item.customType === 'crtr-cycle-divider') {
895
+ finishOpenToolGroup();
896
+ return;
897
+ }
898
+ if (isTrueUserMessage(item)) {
899
+ visibleBoundary(generatedContextText(item));
900
+ return;
901
+ }
902
+ if (item.display === false)
903
+ return;
904
+ const text = generatedContextText(item);
905
+ const isBearingsOrRevive = item.customType === 'crtr-context' || (role === 'user' && text.startsWith('You have been revived fresh after a context refresh'));
906
+ if (isBearingsOrRevive) {
907
+ if (openToolGroup !== undefined && text !== '')
908
+ openToolGroup.pieces.push({ type: 'text', text });
909
+ return;
910
+ }
911
+ // Generated inbox/situational/nudge and visible custom output are part of
912
+ // the between-message activity and may begin a group before its first tool.
913
+ const group = ensureOpenToolGroup();
914
+ if (text !== '')
915
+ group.pieces.push({ type: 'text', text });
916
+ };
732
917
  // ---------------------------------------------------------------------------
733
918
  // F2: message_update coalescing. `relayEvent` is the single entry point for
734
919
  // engine events headed to viewers: a message_update is held (latest wins) and
@@ -951,9 +1136,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
951
1136
  // node from reviving straight back into fault-recovery chrome.
952
1137
  if (unconfigured)
953
1138
  clearFault(nodeId, { link: 'pi→provider' });
954
- await operationIdContext.fresh(() => session.prompt(unconfigured
955
- ? `The previous model (${currentSpec}) had no usable provider credential, so you were automatically switched to ${resolved}. Continue the task from where the failed turn left off.`
956
- : `The previous model (${currentSpec}) was unavailable (provider 404 not_found), so you were automatically switched to ${resolved}. Continue the task from where the failed turn left off.`));
1139
+ await operationIdContext.fresh(() => session.prompt(formatModelFallbackRecovery(currentSpec, resolved, unconfigured ? 'credential' : 'not-found')));
957
1140
  })()
958
1141
  .catch((err) => {
959
1142
  emitEvent({
@@ -1295,6 +1478,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1295
1478
  widgets: Object.fromEntries([...displayWidgets].map(([k, w]) => [k, { lines: [...w.lines], placement: w.placement }])),
1296
1479
  title: displayTitle,
1297
1480
  },
1481
+ toolGroupSummaries: Object.fromEntries(toolGroupSummaries),
1298
1482
  });
1299
1483
  // Send a client its catch-up snapshot. welcome.pending_dialog carries a single
1300
1484
  // still-in-flight dialog to a WRITABLE client attaching mid-dialog; an observer
@@ -1337,7 +1521,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1337
1521
  // The single exit helper (plan §0): dispose the engine, close + unlink the
1338
1522
  // socket, exit 0. Idempotent — every trigger converges here.
1339
1523
  // -------------------------------------------------------------------------
1340
- const disposeAndExit = (_reason, status = 0) => {
1524
+ const completeExit = (status) => {
1341
1525
  if (disposed)
1342
1526
  return;
1343
1527
  disposed = true;
@@ -1381,6 +1565,23 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1381
1565
  }
1382
1566
  process.exit(status);
1383
1567
  };
1568
+ const disposeAndExit = (reason, status = 0) => {
1569
+ if (disposed)
1570
+ return;
1571
+ // A normal terminal shutdown follows a visible boundary that may have just
1572
+ // launched a capped (45s) headless summary. Keep the broker and its child
1573
+ // alive until those already-started tasks persist/broadcast, rather than
1574
+ // process.exit() orphaning pi and losing the sidecar. Abrupt failures and
1575
+ // signals retain their existing immediate teardown behavior.
1576
+ if ((reason === 'shutdown-hook' || reason === 'shutdown-after-settle') && summaryTasks.size > 0) {
1577
+ if (disposePending)
1578
+ return;
1579
+ disposePending = true;
1580
+ void Promise.allSettled([...summaryTasks]).finally(() => completeExit(status));
1581
+ return;
1582
+ }
1583
+ completeExit(status);
1584
+ };
1384
1585
  // A stophook can request shutdown while pi is still unwinding a completed
1385
1586
  // turn. Let that turn reach agent_settled first: inbox delivery commits its
1386
1587
  // durable cursor there, and only there. Explicit operator shutdown and OS
@@ -1491,6 +1692,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1491
1692
  return;
1492
1693
  }
1493
1694
  }
1695
+ observeToolGroupEvent(event);
1494
1696
  relayEvent(event, generation);
1495
1697
  if (event.type === 'agent_settled')
1496
1698
  scheduleFaultRetry();
@@ -1526,6 +1728,9 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1526
1728
  providerFallbackInFlight = null;
1527
1729
  notFoundFallbackInFlight = null;
1528
1730
  reloadAuthInFlight = null;
1731
+ openToolGroup = undefined;
1732
+ toolGroupsByCallId.clear();
1733
+ sawAssistantProse = false;
1529
1734
  await candidateSession.bindExtensions({
1530
1735
  uiContext,
1531
1736
  // 'rpc' — NOT 'print'. Per pi docs/rpc.md:1078 this is exactly our shape:
@@ -1810,6 +2015,9 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
1810
2015
  // Order before any held assistant message_update, so a `!` run mid-stream
1811
2016
  // never renders ahead of the update it follows.
1812
2017
  flushPendingUpdate();
2018
+ // The live bash component is a visible transcript boundary, even though
2019
+ // its persisted bashExecution message arrives later via the session.
2020
+ finishOpenToolGroup();
1813
2021
  broadcast({ type: 'bash_start', command, excludeFromContext: exclude });
1814
2022
  void session
1815
2023
  .executeBash(command, (chunk) => broadcast({ type: 'bash_output', chunk }), {
@@ -12,6 +12,9 @@ export declare const CANVAS_BASH_VALVE_PATH: string;
12
12
  * (`runtime/naming.ts`) to give it a constrained-schema `name_session` tool.
13
13
  * Deliberately absent from CANVAS_EXTENSIONS: a node must never carry it. */
14
14
  export declare const NAMING_TOOL_PATH: string;
15
+ /** NOT a canvas extension — loaded only by the broker's headless tool-group
16
+ * summarizer, which is the sole owner of summary generation and persistence. */
17
+ export declare const SUMMARY_TOOL_PATH: string;
15
18
  /** The canvas extensions every node loads, in order. The inbox watcher commits
16
19
  * its cursor at agent_settled before the stophook checks it for idle release. */
17
20
  export declare const CANVAS_EXTENSIONS: string[];
@@ -24,6 +24,9 @@ export const CANVAS_BASH_VALVE_PATH = resolveExtension('canvas-bash-valve');
24
24
  * (`runtime/naming.ts`) to give it a constrained-schema `name_session` tool.
25
25
  * Deliberately absent from CANVAS_EXTENSIONS: a node must never carry it. */
26
26
  export const NAMING_TOOL_PATH = resolveExtension('naming-tool');
27
+ /** NOT a canvas extension — loaded only by the broker's headless tool-group
28
+ * summarizer, which is the sole owner of summary generation and persistence. */
29
+ export const SUMMARY_TOOL_PATH = resolveExtension('summary-tool');
27
30
  /** The canvas extensions every node loads, in order. The inbox watcher commits
28
31
  * its cursor at agent_settled before the stophook checks it for idle release. */
29
32
  export const CANVAS_EXTENSIONS = [
@@ -151,7 +151,7 @@ export async function maybeBootRoot(argv) {
151
151
  // Unambiguous front-door launch → boot a resident root inline (exec pi in
152
152
  // this terminal). Does not return. Load the heavy boot-path deps lazily now
153
153
  // that we've committed to booting (see the import note at the top of file).
154
- const [{ bootRoot }, { resumeRoot }, { globalHumanloopWarning, missingPackageWarning, piAgentDir }] = await Promise.all([
154
+ const [{ bootRoot }, { resumeRoot }, { rendererWarning, missingPackageWarning, piAgentDir }] = await Promise.all([
155
155
  import('./boot-root.js'),
156
156
  import('./resume-root.js'),
157
157
  import('./package-health.js'),
@@ -164,9 +164,9 @@ export async function maybeBootRoot(argv) {
164
164
  const warn = missingPackageWarning(piAgentDir());
165
165
  if (warn !== null)
166
166
  process.stderr.write(`\n${warn}\n\n`);
167
- const humanloopWarn = globalHumanloopWarning();
168
- if (humanloopWarn !== null)
169
- process.stderr.write(`\n${humanloopWarn}\n\n`);
167
+ const rendererWarn = rendererWarning();
168
+ if (rendererWarn !== null)
169
+ process.stderr.write(`\n${rendererWarn}\n\n`);
170
170
  }
171
171
  catch {
172
172
  /* best-effort diagnostic — never block a boot */
@@ -1,6 +1,6 @@
1
1
  import { type NodeMeta } from '../canvas/index.js';
2
2
  import { type WakeOrigin } from './bearings.js';
3
- export { REVIVE_KICKOFF_SENTINEL } from '../../shared/generated-context.js';
3
+ export { REVIVE_KICKOFF_SENTINEL, RUNTIME_RESTART_CONTINUATION, } from '../../shared/generated-context.js';
4
4
  /** The goal file — the prompt/task a node was spawned with, persisted at birth
5
5
  * so a fresh revive can re-read its mandate. */
6
6
  export declare function goalPath(nodeId: string): string;
@@ -16,10 +16,6 @@ export declare function captureGoalIfAbsent(nodeId: string, text: string): boole
16
16
  /** Sentinel opening the fresh-revive kickoff message (see buildReviveKickoff).
17
17
  * The goal-capture extension and attached renderers share its pure definition,
18
18
  * so the same exact contract controls both prompt exclusion and display. */
19
- /** Injected only when a broker cleanly persisted an aborted in-flight turn
20
- * before a daemon restart. The transcript already carries the full picture, so
21
- * this is a bare nudge — a longer explanation only destabilizes the resume. */
22
- export declare const RUNTIME_RESTART_CONTINUATION = "continue";
23
19
  /** The yield-message file — a short note `crtr node yield` records for the next
24
20
  * fresh/cycling revive ("on wake, do X"). It remains durable through failed
25
21
  * launch attempts and is cleared only when session_start confirms a boot. */
@@ -25,7 +25,7 @@ import { buildWakeBearings } from './bearings.js';
25
25
  import { personaDrift, commitPersonaAck } from './persona.js';
26
26
  import { readInboxSince, readCursor, writeCursor, coalesce, } from '../feed/inbox.js';
27
27
  import { REVIVE_KICKOFF_SENTINEL } from '../../shared/generated-context.js';
28
- export { REVIVE_KICKOFF_SENTINEL } from '../../shared/generated-context.js';
28
+ export { REVIVE_KICKOFF_SENTINEL, RUNTIME_RESTART_CONTINUATION, } from '../../shared/generated-context.js';
29
29
  // ---------------------------------------------------------------------------
30
30
  // Companion context files: the goal (the spawning mandate) and the one-shot
31
31
  // yield message (a note from the prior self to the revived self).
@@ -65,10 +65,6 @@ export function captureGoalIfAbsent(nodeId, text) {
65
65
  /** Sentinel opening the fresh-revive kickoff message (see buildReviveKickoff).
66
66
  * The goal-capture extension and attached renderers share its pure definition,
67
67
  * so the same exact contract controls both prompt exclusion and display. */
68
- /** Injected only when a broker cleanly persisted an aborted in-flight turn
69
- * before a daemon restart. The transcript already carries the full picture, so
70
- * this is a bare nudge — a longer explanation only destabilizes the resume. */
71
- export const RUNTIME_RESTART_CONTINUATION = 'continue';
72
68
  /** The yield-message file — a short note `crtr node yield` records for the next
73
69
  * fresh/cycling revive ("on wake, do X"). It remains durable through failed
74
70
  * launch attempts and is cleared only when session_start confirms a boot. */
@@ -5,6 +5,13 @@ export declare function sanitizeSessionName(raw: string): string;
5
5
  /** Local fallback: derive a name straight from the prompt (no pi call). Drops
6
6
  * stop-words, takes the first few content words. */
7
7
  export declare function slugFromPrompt(prompt: string): string;
8
+ /** The namer's model: the `light` rung of the DEFAULT provider's ladder, with
9
+ * any thinking suffix stripped (we pass `--thinking off` explicitly — naming is
10
+ * a trivial classification that never needs a reasoning budget). Reading the
11
+ * ladder rather than pinning a concrete model id keeps naming working for a
12
+ * user who never configured Anthropic: an OpenAI default provider resolves to
13
+ * its own light rung. `CRTR_NAME_MODEL` still overrides. */
14
+ export declare function lightModel(overrideEnv?: string): string;
8
15
  /** A generated session name: the kebab-case handle plus the Nerd Font glyph the
9
16
  * namer chose for it. `icon` is '' when the model returned nothing usable —
10
17
  * every surface renders the name alone in that case. */
@@ -119,13 +119,16 @@ export function slugFromPrompt(prompt) {
119
119
  * ladder rather than pinning a concrete model id keeps naming working for a
120
120
  * user who never configured Anthropic: an OpenAI default provider resolves to
121
121
  * its own light rung. `CRTR_NAME_MODEL` still overrides. */
122
- function nameModel() {
123
- const override = process.env['CRTR_NAME_MODEL'];
122
+ export function lightModel(overrideEnv) {
123
+ const override = overrideEnv === undefined ? undefined : process.env[overrideEnv];
124
124
  if (override !== undefined && override.trim() !== '')
125
125
  return override.trim();
126
126
  const ladders = modelLadders();
127
127
  return stripThinkingSuffix(ladders[defaultProvider(ladders)].light);
128
128
  }
129
+ function nameModel() {
130
+ return lightModel('CRTR_NAME_MODEL');
131
+ }
129
132
  /** The pi argv for a headless TEXT request (issue titles). Stripped down (no
130
133
  * tools, session, context files, extensions, skills, templates, themes) so
131
134
  * it's fast and side-effect free. */
@@ -8,6 +8,7 @@
8
8
  // and were imported into the handlers from there; Stage B-3 relocated them here
9
9
  // so the CLI leaves' import graph reaches no canvas state store (spec §1/§10).
10
10
  import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
11
+ import { readJsonIfExists } from '../fs-utils.js';
11
12
  import { tmpdir } from 'node:os';
12
13
  import { basename, join } from 'node:path';
13
14
  import { InputError } from '../io.js';
@@ -17,6 +18,7 @@ import { SessionManager } from './broker-sdk.js';
17
18
  import { piAgentDir } from './package-health.js';
18
19
  import { BUILTIN_SLASH_COMMANDS } from './pi-vendored.js';
19
20
  import { cycleAwareMessages } from './session-cycles.js';
21
+ import { normalizeToolGroupSummaryRecord } from '../../shared/tool-groups.js';
20
22
  import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
21
23
  import { PoolCredentialStore } from '../pool-credential-store.js';
22
24
  /** A session `.jsonl` records only `{ provider, modelId }` — pi never persists a
@@ -131,6 +133,15 @@ function readIfPresent(path) {
131
133
  return null;
132
134
  }
133
135
  }
136
+ function readToolGroupSummaries(nodeId) {
137
+ try {
138
+ const raw = readJsonIfExists(join(jobDir(nodeId), 'tool-summaries.json'));
139
+ return normalizeToolGroupSummaryRecord(raw);
140
+ }
141
+ catch {
142
+ return {};
143
+ }
144
+ }
134
145
  /** Reconstruct a persisted session without launching its broker. Backs the
135
146
  * machine-readable snapshot endpoint and the human transcript render. */
136
147
  export async function readNodeSnapshot(nodeId) {
@@ -175,6 +186,10 @@ export async function readNodeSnapshot(nodeId) {
175
186
  // process state (last setStatus/setWidget/setTitle) and is not persisted,
176
187
  // so an offline read has none.
177
188
  display: { statuses: {}, widgets: {}, title: undefined },
189
+ // Sidecar summaries survive broker exit so a dormant transcript receives
190
+ // the same completed-group data as a live welcome snapshot. Malformed or
191
+ // absent sidecars are deliberately ignored: tool calls remain visible.
192
+ toolGroupSummaries: readToolGroupSummaries(nodeId),
178
193
  };
179
194
  return {
180
195
  nodeId,
@@ -4,27 +4,12 @@ export interface MissingPackage {
4
4
  /** Absolute path it resolved to (which does not exist). */
5
5
  resolved: string;
6
6
  }
7
- export interface GlobalHumanloopHealth {
8
- bundledVersion: string | null;
9
- globalVersion: string | null;
10
- current: boolean;
11
- }
12
- /** Read the installed library's version rather than coupling this check to the
13
- * dependency range in crouter's package.json. The bundled copy is the floor
14
- * required by crouter's generated `hl inbox toggle` binding. */
15
- export declare function bundledHumanloopVersion(): string | null;
16
- /** Read the PATH-visible `hl` package directly. Spawning `npm ls -g` here
17
- * makes every front-door boot pay for npm's global dependency-tree scan; a
18
- * linked development install can make that scan take many seconds. The real
19
- * executable points into the package for both ordinary and npm-linked installs,
20
- * so walking upward to its package.json is authoritative for the CLI crtr will
21
- * actually invoke. */
22
- export declare function globalHumanloopVersion(): string | null;
23
- export declare function globalHumanloopHealth(): GlobalHumanloopHealth;
24
- /** A non-fatal action-oriented diagnostic for a missing, unreadable, or stale
25
- * global `hl` executable. */
26
- export declare function globalHumanloopWarning(): string | null;
27
- export declare function globalHumanloopInstallCommand(): string;
7
+ /** A non-fatal, action-oriented diagnostic for an unprovisioned managed
8
+ * renderer — the one remaining out-of-tree dependency. Deliberately calls
9
+ * `isRendererReady()`, NOT `ensureRenderer()`: this runs on the front-door boot
10
+ * path and at crtrd startup, where a `uv pip install` (up to 180s) must never
11
+ * happen. Readiness is a handful of fs stats, no subprocess. */
12
+ export declare function rendererWarning(): string | null;
28
13
  /**
29
14
  * The pi agent config dir (`~/.pi/agent`, or `$PI_CODING_AGENT_DIR`) — a local
30
15
  * reimplementation of pi's `getAgentDir()`. Reimplemented (not imported) ON