@pellux/goodvibes-agent 0.1.116 → 1.0.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 (285) hide show
  1. package/CHANGELOG.md +48 -661
  2. package/README.md +64 -57
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +199180 -192185
  5. package/docs/README.md +15 -8
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +59 -0
  8. package/docs/getting-started.md +61 -50
  9. package/docs/knowledge-artifacts-and-multimodal.md +91 -0
  10. package/docs/project-planning.md +79 -0
  11. package/docs/providers-and-routing.md +46 -0
  12. package/docs/release-and-publishing.md +48 -11
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +3 -6
  16. package/src/agent/behavior-discovery-summary.ts +1 -3
  17. package/src/agent/channel-delivery.ts +201 -0
  18. package/src/agent/media-generation.ts +159 -0
  19. package/src/agent/memory-prompt.ts +0 -1
  20. package/src/agent/note-registry.ts +329 -0
  21. package/src/agent/operator-actions.ts +343 -0
  22. package/src/agent/persona-discovery.ts +1 -3
  23. package/src/agent/persona-registry.ts +19 -16
  24. package/src/agent/record-labels.ts +107 -0
  25. package/src/agent/reminder-schedule-format.ts +33 -24
  26. package/src/agent/reminder-schedule.ts +27 -26
  27. package/src/agent/routine-registry.ts +54 -14
  28. package/src/agent/routine-schedule-args.ts +2 -1
  29. package/src/agent/routine-schedule-format.ts +77 -53
  30. package/src/agent/routine-schedule-promotion.ts +35 -33
  31. package/src/agent/routine-schedule-receipts.ts +29 -27
  32. package/src/agent/runtime-profile-starters.ts +5 -5
  33. package/src/agent/runtime-profile.ts +91 -12
  34. package/src/agent/skill-registry.ts +210 -32
  35. package/src/cli/agent-knowledge-args.ts +5 -1
  36. package/src/cli/agent-knowledge-command.ts +40 -34
  37. package/src/cli/agent-knowledge-format.ts +80 -67
  38. package/src/cli/agent-knowledge-methods.ts +1 -1
  39. package/src/cli/agent-knowledge-runtime.ts +32 -26
  40. package/src/cli/bundle-command.ts +14 -9
  41. package/src/cli/config-overrides.ts +37 -36
  42. package/src/cli/entrypoint.ts +16 -7
  43. package/src/cli/external-runtime.ts +10 -4
  44. package/src/cli/help.ts +54 -29
  45. package/src/cli/local-library-command.ts +203 -83
  46. package/src/cli/management-commands.ts +105 -68
  47. package/src/cli/management.ts +55 -74
  48. package/src/cli/memory-command.ts +66 -32
  49. package/src/cli/parser.ts +37 -3
  50. package/src/cli/profiles-command.ts +166 -31
  51. package/src/cli/provider-auth-routes.ts +2 -1
  52. package/src/cli/routines-command.ts +176 -40
  53. package/src/cli/service-posture.ts +16 -16
  54. package/src/cli/status.ts +56 -135
  55. package/src/core/conversation-message-snapshot.ts +131 -0
  56. package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
  57. package/src/input/agent-workspace-access-command-editors.ts +170 -0
  58. package/src/input/agent-workspace-activation.ts +35 -8
  59. package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
  60. package/src/input/agent-workspace-basic-command-editors.ts +502 -9
  61. package/src/input/agent-workspace-categories.ts +423 -51
  62. package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
  63. package/src/input/agent-workspace-channel-command-editors.ts +65 -0
  64. package/src/input/agent-workspace-channels.ts +35 -2
  65. package/src/input/agent-workspace-command-editor.ts +41 -3
  66. package/src/input/agent-workspace-config-reader.ts +16 -0
  67. package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
  68. package/src/input/agent-workspace-editors.ts +166 -1
  69. package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
  70. package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
  71. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  72. package/src/input/agent-workspace-learned-behavior.ts +67 -0
  73. package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
  74. package/src/input/agent-workspace-library-command-editors.ts +35 -0
  75. package/src/input/agent-workspace-local-operations.ts +218 -0
  76. package/src/input/agent-workspace-local-selection.ts +75 -0
  77. package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
  78. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  79. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  80. package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
  81. package/src/input/agent-workspace-memory-command-editors.ts +180 -0
  82. package/src/input/agent-workspace-memory-editor.ts +2 -2
  83. package/src/input/agent-workspace-navigation.ts +81 -0
  84. package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
  85. package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
  86. package/src/input/agent-workspace-operations-command-editors.ts +196 -0
  87. package/src/input/agent-workspace-panel-route.ts +43 -0
  88. package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
  89. package/src/input/agent-workspace-provider-command-editors.ts +93 -0
  90. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  91. package/src/input/agent-workspace-requirements.ts +11 -0
  92. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  93. package/src/input/agent-workspace-search.ts +169 -0
  94. package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
  95. package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
  96. package/src/input/agent-workspace-session-command-editors.ts +249 -0
  97. package/src/input/agent-workspace-setup.ts +37 -16
  98. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
  99. package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
  100. package/src/input/agent-workspace-snapshot.ts +162 -21
  101. package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
  102. package/src/input/agent-workspace-task-command-editors.ts +47 -0
  103. package/src/input/agent-workspace-token.ts +18 -2
  104. package/src/input/agent-workspace-types.ts +212 -4
  105. package/src/input/agent-workspace-voice-media.ts +4 -7
  106. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  107. package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
  108. package/src/input/agent-workspace.ts +178 -240
  109. package/src/input/command-registry.ts +5 -2
  110. package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
  111. package/src/input/commands/agent-skills-runtime.ts +149 -72
  112. package/src/input/commands/agent-workspace-runtime.ts +23 -6
  113. package/src/input/commands/brief-runtime.ts +55 -25
  114. package/src/input/commands/channels-runtime.ts +147 -33
  115. package/src/input/commands/compat-runtime.ts +32 -0
  116. package/src/input/commands/delegation-runtime.ts +29 -23
  117. package/src/input/commands/experience-runtime.ts +6 -4
  118. package/src/input/commands/guidance-runtime.ts +4 -4
  119. package/src/input/commands/health-runtime.ts +149 -126
  120. package/src/input/commands/knowledge.ts +82 -60
  121. package/src/input/commands/local-provider-runtime.ts +43 -21
  122. package/src/input/commands/local-runtime.ts +138 -16
  123. package/src/input/commands/local-setup-review.ts +10 -20
  124. package/src/input/commands/mcp-runtime.ts +63 -46
  125. package/src/input/commands/notify-runtime.ts +38 -9
  126. package/src/input/commands/operator-actions-runtime.ts +138 -0
  127. package/src/input/commands/operator-runtime.ts +6 -76
  128. package/src/input/commands/personas-runtime.ts +46 -31
  129. package/src/input/commands/planning-runtime.ts +1 -1
  130. package/src/input/commands/platform-access-runtime.ts +35 -98
  131. package/src/input/commands/product-runtime.ts +2 -2
  132. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  133. package/src/input/commands/qrcode-runtime.ts +50 -9
  134. package/src/input/commands/recall-bundle.ts +11 -11
  135. package/src/input/commands/recall-capture.ts +13 -11
  136. package/src/input/commands/recall-query.ts +29 -21
  137. package/src/input/commands/recall-review.ts +2 -1
  138. package/src/input/commands/routines-runtime.ts +99 -45
  139. package/src/input/commands/runtime-services.ts +1 -14
  140. package/src/input/commands/schedule-runtime.ts +33 -20
  141. package/src/input/commands/security-runtime.ts +6 -11
  142. package/src/input/commands/session-content.ts +80 -78
  143. package/src/input/commands/session-workflow.ts +134 -104
  144. package/src/input/commands/session.ts +3 -174
  145. package/src/input/commands/shell-core.ts +38 -17
  146. package/src/input/commands/subscription-runtime.ts +54 -185
  147. package/src/input/commands/support-bundle-runtime.ts +53 -0
  148. package/src/input/commands/tasks-runtime.ts +21 -21
  149. package/src/input/commands/work-plan-runtime.ts +33 -8
  150. package/src/input/commands.ts +6 -2
  151. package/src/input/feed-context-factory.ts +2 -2
  152. package/src/input/file-picker.ts +3 -2
  153. package/src/input/handler-command-route.ts +4 -7
  154. package/src/input/handler-content-actions.ts +89 -26
  155. package/src/input/handler-feed-routes.ts +19 -12
  156. package/src/input/handler-feed.ts +6 -5
  157. package/src/input/handler-interactions.ts +9 -36
  158. package/src/input/handler-modal-stack.ts +3 -3
  159. package/src/input/handler-onboarding.ts +52 -91
  160. package/src/input/handler-shortcuts.ts +16 -7
  161. package/src/input/handler.ts +50 -23
  162. package/src/input/input-history.ts +5 -6
  163. package/src/input/keybindings.ts +27 -16
  164. package/src/input/mcp-workspace.ts +90 -26
  165. package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
  166. package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
  167. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  168. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  169. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
  170. package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
  171. package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
  172. package/src/input/onboarding/onboarding-wizard.ts +87 -2
  173. package/src/input/profile-picker-modal.ts +31 -12
  174. package/src/input/session-picker-modal.ts +21 -4
  175. package/src/input/settings-modal-agent-policy.ts +6 -6
  176. package/src/input/settings-modal-behavior.ts +0 -3
  177. package/src/input/settings-modal-subscriptions.ts +3 -3
  178. package/src/input/settings-modal-types.ts +2 -13
  179. package/src/input/settings-modal.ts +20 -66
  180. package/src/input/submission-intent.ts +0 -1
  181. package/src/input/submission-router.ts +3 -3
  182. package/src/main.ts +18 -10
  183. package/src/panels/approval-panel.ts +8 -8
  184. package/src/panels/automation-control-panel.ts +4 -4
  185. package/src/panels/base-panel.ts +1 -1
  186. package/src/panels/builtin/agent.ts +1 -1
  187. package/src/panels/builtin/operations.ts +3 -12
  188. package/src/panels/builtin/session.ts +32 -24
  189. package/src/panels/builtin/shared.ts +7 -7
  190. package/src/panels/cost-tracker-panel.ts +1 -1
  191. package/src/panels/docs-panel.ts +5 -3
  192. package/src/panels/index.ts +0 -1
  193. package/src/panels/knowledge-panel.ts +6 -5
  194. package/src/panels/memory-panel.ts +7 -6
  195. package/src/panels/panel-list-panel.ts +36 -80
  196. package/src/panels/project-planning-panel.ts +19 -12
  197. package/src/panels/provider-account-snapshot.ts +51 -25
  198. package/src/panels/provider-accounts-panel.ts +33 -18
  199. package/src/panels/provider-health-domains.ts +48 -7
  200. package/src/panels/provider-health-panel.ts +5 -4
  201. package/src/panels/qr-panel.ts +44 -20
  202. package/src/panels/schedule-panel.ts +9 -17
  203. package/src/panels/security-panel.ts +8 -8
  204. package/src/panels/session-browser-panel.ts +10 -10
  205. package/src/panels/subscription-panel.ts +6 -6
  206. package/src/panels/system-messages-panel.ts +3 -3
  207. package/src/panels/tasks-panel.ts +21 -14
  208. package/src/panels/tool-inspector-panel.ts +19 -12
  209. package/src/panels/work-plan-panel.ts +5 -5
  210. package/src/planning/project-planning-coordinator.ts +3 -3
  211. package/src/provider-auth-route-display.ts +9 -0
  212. package/src/renderer/agent-workspace-style.ts +34 -0
  213. package/src/renderer/agent-workspace.ts +254 -57
  214. package/src/renderer/autocomplete-overlay.ts +25 -6
  215. package/src/renderer/block-actions.ts +1 -3
  216. package/src/renderer/bookmark-modal.ts +19 -4
  217. package/src/renderer/buffer.ts +4 -2
  218. package/src/renderer/context-inspector.ts +50 -13
  219. package/src/renderer/diff.ts +1 -1
  220. package/src/renderer/file-picker-overlay.ts +19 -6
  221. package/src/renderer/help-overlay.ts +112 -34
  222. package/src/renderer/history-search-overlay.ts +19 -4
  223. package/src/renderer/live-tail-modal.ts +27 -5
  224. package/src/renderer/mcp-workspace.ts +176 -43
  225. package/src/renderer/model-picker-overlay.ts +58 -3
  226. package/src/renderer/model-workspace.ts +104 -22
  227. package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
  228. package/src/renderer/process-modal.ts +27 -6
  229. package/src/renderer/profile-picker-modal.ts +21 -5
  230. package/src/renderer/search-overlay.ts +25 -5
  231. package/src/renderer/selection-modal-overlay.ts +46 -14
  232. package/src/renderer/session-picker-modal.ts +18 -1
  233. package/src/renderer/settings-modal-helpers.ts +2 -40
  234. package/src/renderer/settings-modal.ts +88 -55
  235. package/src/renderer/system-message.ts +1 -1
  236. package/src/renderer/tool-call.ts +20 -11
  237. package/src/runtime/agent-runtime-events.ts +129 -0
  238. package/src/runtime/bootstrap-command-context.ts +7 -1
  239. package/src/runtime/bootstrap-command-parts.ts +11 -8
  240. package/src/runtime/bootstrap-core.ts +23 -65
  241. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  242. package/src/runtime/bootstrap-shell.ts +46 -8
  243. package/src/runtime/bootstrap.ts +36 -46
  244. package/src/runtime/connected-host-auth.ts +47 -0
  245. package/src/runtime/diagnostics/panels/index.ts +2 -2
  246. package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
  247. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  248. package/src/runtime/index.ts +2 -5
  249. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  250. package/src/runtime/onboarding/apply.ts +288 -74
  251. package/src/runtime/onboarding/derivation.ts +10 -13
  252. package/src/runtime/onboarding/snapshot.ts +2 -17
  253. package/src/runtime/onboarding/types.ts +34 -20
  254. package/src/runtime/onboarding/verify.ts +105 -4
  255. package/src/runtime/operator-token-cleanup.ts +3 -3
  256. package/src/runtime/services.ts +95 -11
  257. package/src/runtime/store/selectors/index.ts +3 -3
  258. package/src/runtime/store/state.ts +1 -4
  259. package/src/runtime/surface-feature-flags.ts +41 -6
  260. package/src/runtime/ui-read-models.ts +3 -4
  261. package/src/runtime/ui-services.ts +6 -6
  262. package/src/shell/blocking-input.ts +2 -1
  263. package/src/shell/service-settings-sync.ts +7 -7
  264. package/src/shell/ui-openers.ts +17 -38
  265. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  266. package/src/tools/agent-channel-send-tool.ts +133 -0
  267. package/src/tools/agent-context-policy.ts +1 -1
  268. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  269. package/src/tools/agent-knowledge-tool.ts +170 -0
  270. package/src/tools/agent-local-registry-tool.ts +279 -73
  271. package/src/tools/agent-media-generate-tool.ts +133 -0
  272. package/src/tools/agent-notify-tool.ts +143 -0
  273. package/src/tools/agent-operator-action-tool.ts +137 -0
  274. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  275. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  276. package/src/tools/agent-tool-policy-guard.ts +13 -15
  277. package/src/tools/agent-work-plan-tool.ts +256 -0
  278. package/src/version.ts +1 -1
  279. package/docs/connected-services.md +0 -51
  280. package/src/cli/package-verification.ts +0 -274
  281. package/src/input/commands/policy-dispatch.ts +0 -339
  282. package/src/input/commands/policy.ts +0 -17
  283. package/src/panels/panel-picker.ts +0 -105
  284. package/src/panels/policy-panel.ts +0 -308
  285. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -3,12 +3,13 @@ import { randomBytes } from 'node:crypto';
3
3
  import type { CommandContext, CommandRegistry } from '../command-registry.ts';
4
4
  import { type SessionMeta } from '@pellux/goodvibes-sdk/platform/sessions';
5
5
  import type { TranscriptEventKind } from '@pellux/goodvibes-sdk/platform/core';
6
- import type { ConversationTitleSource } from '../../core/conversation';
7
6
  import type { SessionReturnContextSummary } from '@/runtime/index.ts';
7
+ import type { ConversationMessageSnapshot } from '../../core/conversation.ts';
8
8
  import { formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@/runtime/index.ts';
9
- import { requirePanelManager, requireProviderApi, requireSessionManager } from './runtime-services.ts';
9
+ import { requireProviderApi, requireSessionManager } from './runtime-services.ts';
10
10
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
11
11
  import { requireYesFlag, stripYesFlag } from './confirmation.ts';
12
+ import { readConversationMessageSnapshots } from '../../core/conversation-message-snapshot.ts';
12
13
 
13
14
  function parseTranscriptKind(raw: string | undefined): TranscriptEventKind | 'all' {
14
15
  const normalized = (raw ?? 'all').toLowerCase().replace(/-/g, '_');
@@ -32,6 +33,13 @@ function parseTranscriptKind(raw: string | undefined): TranscriptEventKind | 'al
32
33
  return allowed.has(normalized as TranscriptEventKind | 'all') ? (normalized as TranscriptEventKind | 'all') : 'all';
33
34
  }
34
35
 
36
+ function formatSessionFailure(action: string, error: unknown): string {
37
+ return [
38
+ `Failed to ${action}`,
39
+ ` error ${summarizeError(error)}`,
40
+ ].join('\n');
41
+ }
42
+
35
43
  function buildTranscriptReviewLines(
36
44
  ctx: CommandContext,
37
45
  kind: TranscriptEventKind | 'all',
@@ -43,8 +51,8 @@ function buildTranscriptReviewLines(
43
51
 
44
52
  if (mode === 'groups') {
45
53
  return [
46
- `Transcript Groups${kind === 'all' ? '' : `: ${kind}`}`,
47
- ` groups: ${groups.length}`,
54
+ `Transcript Groups${kind === 'all' ? '' : ` ${kind}`}`,
55
+ ` groups ${groups.length}`,
48
56
  ...groups.slice(0, 12).map((group) => (
49
57
  ` ${group.kind.padEnd(20)} ${String(group.events.length).padStart(2)} event(s) msgs=${group.messageIndexes[0]}-${group.messageIndexes[group.messageIndexes.length - 1]} ${group.title}`
50
58
  )),
@@ -72,34 +80,33 @@ function buildTranscriptReviewLines(
72
80
  }
73
81
 
74
82
  return [
75
- `Transcript Events${kind === 'all' ? '' : `: ${kind}`}`,
76
- ` events: ${events.length}`,
83
+ `Transcript Events${kind === 'all' ? '' : ` ${kind}`}`,
84
+ ` events ${events.length}`,
77
85
  ...events.slice(0, 16).map((event) => ` #${String(event.messageIndex).padStart(3)} ${event.kind.padEnd(20)} ${event.title} — ${event.detail}`),
78
86
  ...(events.length > 16 ? [` … ${events.length - 16} more event(s)`] : []),
79
87
  ];
80
88
  }
81
89
 
82
- function reopenPanelsFromReturnContext(ctx: CommandContext, summary: SessionReturnContextSummary | undefined): string[] {
83
- if (!summary?.openPanels || summary.openPanels.length === 0) return [];
84
- const panelManager = requirePanelManager(ctx);
85
- const reopened: string[] = [];
86
- for (const panelId of summary.openPanels.slice(0, 4)) {
87
- try {
88
- panelManager.open(panelId);
89
- reopened.push(panelId);
90
- } catch {
91
- // Ignore unknown or currently unavailable panel ids during resume.
92
- }
93
- }
94
- if (reopened.length > 0) panelManager.show();
95
- return reopened;
90
+ function printIgnoredPanelsFromReturnContext(ctx: CommandContext, summary: SessionReturnContextSummary | undefined): void {
91
+ if (!summary?.openPanels || summary.openPanels.length === 0) return;
92
+ ctx.print(` Saved panel state ignored: ${summary.openPanels.slice(0, 4).join(', ')}. Open the Agent workspace for current operator controls.`);
93
+ }
94
+
95
+ function formatAgentReturnContextForDisplay(summary: SessionReturnContextSummary): string[] {
96
+ const ignoredPanels = summary.openPanels?.slice(0, 4) ?? [];
97
+ return [
98
+ ...formatReturnContextForDisplay(summary).filter((line) => !line.startsWith('Open panels:')),
99
+ ...(ignoredPanels.length > 0
100
+ ? [`Saved panel state ignored: ${ignoredPanels.join(', ')}. Open the Agent workspace for current operator controls.`]
101
+ : []),
102
+ ];
96
103
  }
97
104
 
98
105
  function printSessionExport(
99
106
  ctx: { print: (text: string) => void },
100
107
  sessionId: string,
101
108
  title: string,
102
- messages: Array<Record<string, unknown>>,
109
+ messages: readonly ConversationMessageSnapshot[],
103
110
  format: string,
104
111
  ): void {
105
112
  const lines: string[] = [];
@@ -107,21 +114,20 @@ function printSessionExport(
107
114
  lines.push(`# Session: ${title || sessionId}`);
108
115
  lines.push('');
109
116
  for (const msg of messages) {
110
- const role = String(msg.role ?? 'unknown');
111
- const content = String(msg.content ?? '');
117
+ const content = conversationMessageContentText(msg);
112
118
  if (!content.trim()) continue;
113
- if (role === 'user') {
119
+ if (msg.role === 'user') {
114
120
  lines.push('## User');
115
121
  lines.push('');
116
122
  lines.push(content);
117
123
  lines.push('');
118
- } else if (role === 'assistant') {
124
+ } else if (msg.role === 'assistant') {
119
125
  lines.push('## Assistant');
120
126
  lines.push('');
121
127
  lines.push(content);
122
128
  lines.push('');
123
- } else if (role === 'tool') {
124
- const toolName = String(msg.toolName ?? 'tool');
129
+ } else if (msg.role === 'tool') {
130
+ const toolName = msg.toolName ?? 'tool';
125
131
  lines.push(`## Tool Result: ${toolName}`);
126
132
  lines.push('');
127
133
  lines.push('```');
@@ -132,8 +138,8 @@ function printSessionExport(
132
138
  }
133
139
  } else {
134
140
  for (const msg of messages) {
135
- const role = String(msg.role ?? 'unknown').toUpperCase();
136
- const content = String(msg.content ?? '');
141
+ const role = msg.role.toUpperCase();
142
+ const content = conversationMessageContentText(msg);
137
143
  if (!content.trim()) continue;
138
144
  lines.push(`[${role}]`);
139
145
  lines.push(content);
@@ -143,6 +149,14 @@ function printSessionExport(
143
149
  ctx.print(lines.join('\n'));
144
150
  }
145
151
 
152
+ function conversationMessageContentText(message: ConversationMessageSnapshot): string {
153
+ if (typeof message.content === 'string') return message.content;
154
+ return message.content.map((part) => {
155
+ if (part.type === 'text') return part.text;
156
+ return `[image: ${part.mediaType}]`;
157
+ }).join('\n');
158
+ }
159
+
146
160
  export async function handleSessionWorkflowCommand(args: string[], ctx: CommandContext): Promise<boolean> {
147
161
  const sm = requireSessionManager(ctx);
148
162
  const sub = args[0];
@@ -155,11 +169,11 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
155
169
  const started = meta ? new Date(meta.timestamp).toLocaleString() : 'this session';
156
170
  ctx.print([
157
171
  'Current session',
158
- ` ID: ${id}`,
159
- ` Name: ${title}`,
160
- ` Started: ${started}`,
161
- ` Messages: ${msgCount}`,
162
- ` Model: ${ctx.session.runtime.model} (${ctx.session.runtime.provider})`,
172
+ ` id ${id}`,
173
+ ` name ${title}`,
174
+ ` started ${started}`,
175
+ ` messages ${msgCount}`,
176
+ ` model ${ctx.session.runtime.model} (${ctx.session.runtime.provider})`,
163
177
  ].join('\n'));
164
178
  return true;
165
179
  }
@@ -170,7 +184,7 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
170
184
  ctx.print('No saved sessions. Use /session save [name] to save the current session.');
171
185
  return true;
172
186
  }
173
- const lines = ['Sessions (most recent first):', ''];
187
+ const lines = ['Sessions (most recent first)', ''];
174
188
  for (const session of sessions) {
175
189
  const date = new Date(session.timestamp).toLocaleString();
176
190
  const name = session.title || session.name;
@@ -179,12 +193,12 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
179
193
  lines.push(`${active} ${session.name.padEnd(28)} ${name.slice(0, 22).padEnd(22)} ${date} ${session.messageCount} msgs${model}`);
180
194
  if (session.returnContext?.activeTasks || session.returnContext?.blockedTasks || session.returnContext?.pendingApprovals || session.returnContext?.openPanels?.length) {
181
195
  const posture = [
182
- session.returnContext.activeTasks ? `active=${session.returnContext.activeTasks}` : null,
183
- session.returnContext.blockedTasks ? `blocked=${session.returnContext.blockedTasks}` : null,
184
- session.returnContext.pendingApprovals ? `approvals=${session.returnContext.pendingApprovals}` : null,
185
- session.returnContext.openPanels?.length ? `panels=${session.returnContext.openPanels.slice(0, 3).join(',')}` : null,
196
+ session.returnContext.activeTasks ? `active ${session.returnContext.activeTasks}` : null,
197
+ session.returnContext.blockedTasks ? `blocked ${session.returnContext.blockedTasks}` : null,
198
+ session.returnContext.pendingApprovals ? `approvals ${session.returnContext.pendingApprovals}` : null,
199
+ session.returnContext.openPanels?.length ? `saved panels ignored ${session.returnContext.openPanels.slice(0, 3).join(',')}` : null,
186
200
  ].filter(Boolean).join(' ');
187
- if (posture) lines.push(` posture: ${posture}`);
201
+ if (posture) lines.push(` posture ${posture}`);
188
202
  }
189
203
  }
190
204
  ctx.print(lines.join('\n'));
@@ -200,20 +214,23 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
200
214
  try {
201
215
  const existingMeta = sm.getMeta(ctx.session.runtime.sessionId);
202
216
  if (!existingMeta) {
203
- const exportData = ctx.session.conversationManager.toJSON() as { messages: object[]; timestamp?: number };
204
- sm.save(ctx.session.runtime.sessionId, exportData.messages ?? [], {
217
+ sm.save(ctx.session.runtime.sessionId, ctx.session.conversationManager.getMessageSnapshot(), {
205
218
  title: ctx.session.conversationManager.title || '',
206
219
  model: ctx.session.runtime.model,
207
220
  provider: ctx.session.runtime.provider,
208
221
  timestamp: Date.now(),
222
+ titleSource: ctx.session.conversationManager.getTitleSource(),
209
223
  });
210
224
  }
211
225
  sm.rename(ctx.session.runtime.sessionId, newName);
212
226
  ctx.session.conversationManager.title = newName;
213
- ctx.print(`Session renamed to: ${newName}`);
227
+ ctx.print([
228
+ 'Session renamed',
229
+ ` name ${newName}`,
230
+ ].join('\n'));
214
231
  ctx.renderRequest();
215
232
  } catch (e) {
216
- ctx.print(`Failed to rename: ${summarizeError(e)}`);
233
+ ctx.print(formatSessionFailure('rename session', e));
217
234
  }
218
235
  return true;
219
236
  }
@@ -231,14 +248,14 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
231
248
  session.title.toLowerCase() === target.toLowerCase(),
232
249
  );
233
250
  if (!found) {
234
- ctx.print(`Session not found: ${target}\nUse /session list to see available sessions.`);
251
+ ctx.print(`Session not found ${target}\nUse /session list to see available sessions.`);
235
252
  return true;
236
253
  }
237
254
  try {
238
255
  const { meta, messages } = sm.load(found.name);
239
256
  const providerApi = requireProviderApi(ctx);
240
257
  ctx.session.conversationManager.resetAll();
241
- ctx.session.conversationManager.fromJSON({ messages: messages as never[], title: meta.title, titleSource: meta.titleSource });
258
+ ctx.session.conversationManager.fromJSON({ messages: readConversationMessageSnapshots(messages), title: meta.title, titleSource: meta.titleSource });
242
259
  ctx.session.conversationManager.rebuildHistory();
243
260
  ctx.session.runtime.sessionId = found.name;
244
261
  if (meta.model) {
@@ -253,82 +270,93 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
253
270
  }
254
271
  if (meta.provider) ctx.session.runtime.provider = meta.provider;
255
272
  ctx.renderRequest();
256
- ctx.print(`Resumed session: ${found.name}\n Name: ${meta.title || '(untitled)'}\n Messages: ${messages.length}\n Model: ${meta.model || ctx.session.runtime.model}`);
257
- const reopenedPanels = reopenPanelsFromReturnContext(ctx, meta.returnContext);
273
+ ctx.print([
274
+ 'Resumed session',
275
+ ` id ${found.name}`,
276
+ ` name ${meta.title || '(untitled)'}`,
277
+ ` messages ${messages.length}`,
278
+ ` model ${meta.model || ctx.session.runtime.model}`,
279
+ ].join('\n'));
280
+ printIgnoredPanelsFromReturnContext(ctx, meta.returnContext);
258
281
  const returnContextMode = getReturnContextMode(ctx.platform.configManager);
259
282
  if (returnContextMode !== 'off' && meta.returnContext) {
260
283
  for (const line of formatReturnContextForDisplay(meta.returnContext)) {
284
+ if (line.startsWith('Open panels:')) continue;
261
285
  ctx.print(` ${line}`);
262
286
  }
263
- if (reopenedPanels.length > 0) {
264
- ctx.print(` Reopened panels: ${reopenedPanels.join(', ')}`);
265
- }
266
287
  if ((meta.returnContext.remoteRunners?.length ?? 0) > 0) {
267
- ctx.print(' Remote re-entry: handle remote runner recovery outside Agent; delegate explicit build/fix/review recovery from Agent.');
288
+ ctx.print(' Remote re-entry. Handle remote build-host recovery outside Agent; delegate explicit build/fix/review recovery from Agent.');
268
289
  }
269
290
  if ((meta.returnContext.worktreePaths?.length ?? 0) > 0) {
270
- ctx.print(' Worktree re-entry: open GoodVibes TUI in the target workspace; delegate explicit build/fix/review recovery from Agent.');
291
+ ctx.print(' Worktree re-entry. Open GoodVibes TUI in the target workspace; delegate explicit build/fix/review recovery from Agent.');
271
292
  }
272
293
  if (returnContextMode === 'assisted') {
273
294
  const helperModel = providerApi.createHelperModel(ctx.platform.configManager);
274
295
  void maybeAssistReturnContextSummary(ctx.platform.configManager, helperModel, meta.returnContext).then((assisted) => {
275
296
  if (!assisted.assistedNarrative) return;
276
- ctx.print(` Assist: ${assisted.assistedNarrative}`);
297
+ ctx.print(` Assist ${assisted.assistedNarrative}`);
277
298
  ctx.renderRequest();
278
299
  });
279
300
  }
280
301
  }
281
302
  } catch (e) {
282
- ctx.print(`Failed to resume session: ${summarizeError(e)}`);
303
+ ctx.print(formatSessionFailure('resume session', e));
283
304
  }
284
305
  return true;
285
306
  }
286
307
 
287
308
  if (sub === 'fork') {
309
+ const sourceSessionId = ctx.session.runtime.sessionId;
288
310
  const newId = `user-${randomBytes(4).toString('hex')}`;
289
- const exportData = ctx.session.conversationManager.toJSON() as SessionExportData;
290
- const messages = exportData.messages ?? [];
291
- const currentTitle = ctx.session.conversationManager.title;
292
- const forkName = args[1] ? args.slice(1).join(' ').trim() : `fork-of-${ctx.session.runtime.sessionId.slice(0, 8)}`;
293
- const meta: SessionMeta = {
294
- title: forkName,
295
- model: ctx.session.runtime.model,
296
- provider: ctx.session.runtime.provider,
311
+ const messages = ctx.session.conversationManager.getMessageSnapshot();
312
+ const currentTitle = ctx.session.conversationManager.title;
313
+ const forkName = args[1] ? args.slice(1).join(' ').trim() : `fork-of-${sourceSessionId.slice(0, 8)}`;
314
+ const meta: SessionMeta = {
315
+ title: forkName,
316
+ model: ctx.session.runtime.model,
317
+ provider: ctx.session.runtime.provider,
297
318
  timestamp: Date.now(),
298
- titleSource: exportData.titleSource,
299
- returnContext: exportData.returnContext,
319
+ titleSource: ctx.session.conversationManager.getTitleSource(),
300
320
  };
301
321
  try {
302
322
  sm.save(newId, messages, meta);
303
323
  ctx.session.runtime.sessionId = newId;
304
324
  ctx.session.conversationManager.title = forkName;
305
325
  ctx.renderRequest();
306
- ctx.print(`Session forked:\n New ID: ${newId}\n Name: ${forkName}\n From: ${currentTitle || ctx.session.runtime.sessionId}\n Messages: ${messages.length}`);
326
+ ctx.print([
327
+ 'Session forked',
328
+ ` id ${newId}`,
329
+ ` name ${forkName}`,
330
+ ` from ${currentTitle || sourceSessionId}`,
331
+ ` messages ${messages.length}`,
332
+ ].join('\n'));
307
333
  } catch (e) {
308
- ctx.print(`Failed to fork session: ${summarizeError(e)}`);
334
+ ctx.print(formatSessionFailure('fork session', e));
309
335
  }
310
336
  return true;
311
337
  }
312
338
 
313
339
  if (sub === 'save') {
314
- const exportData = ctx.session.conversationManager.toJSON() as SessionExportData;
315
- const messages = exportData.messages ?? [];
316
- const rawName = args[1] ? args.slice(1).join(' ').trim() : (ctx.session.conversationManager.title || ctx.session.runtime.sessionId);
317
- const meta: SessionMeta = {
318
- title: ctx.session.conversationManager.title,
319
- model: ctx.session.runtime.model,
320
- provider: ctx.session.runtime.provider,
340
+ const messages = ctx.session.conversationManager.getMessageSnapshot();
341
+ const rawName = args[1] ? args.slice(1).join(' ').trim() : (ctx.session.conversationManager.title || ctx.session.runtime.sessionId);
342
+ const meta: SessionMeta = {
343
+ title: ctx.session.conversationManager.title,
344
+ model: ctx.session.runtime.model,
345
+ provider: ctx.session.runtime.provider,
321
346
  timestamp: Date.now(),
322
- titleSource: exportData.titleSource,
323
- returnContext: exportData.returnContext,
347
+ titleSource: ctx.session.conversationManager.getTitleSource(),
324
348
  };
325
349
  try {
326
350
  const { filePath, sanitizedName } = sm.save(rawName, messages, meta);
327
351
  ctx.session.runtime.sessionId = sanitizedName;
328
- const nameNote = sanitizedName !== rawName ? ` (saved as "${sanitizedName}")` : '';
329
- ctx.print(`Session saved: ${rawName}${nameNote}\n → ${filePath}`);
352
+ ctx.print([
353
+ 'Session saved',
354
+ ` name ${rawName}`,
355
+ ...(sanitizedName !== rawName ? [` saved as ${sanitizedName}`] : []),
356
+ ` path ${filePath}`,
357
+ ].join('\n'));
330
358
  } catch (e) {
331
- ctx.print(`Failed to save session: ${summarizeError(e)}`);
359
+ ctx.print(formatSessionFailure('save session', e));
332
360
  }
333
361
  return true;
334
362
  }
@@ -338,19 +366,19 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
338
366
  const sessions = sm.list();
339
367
  const found = sessions.find((session) => session.name === target || session.name.startsWith(target));
340
368
  if (!found) {
341
- ctx.print(`Session not found: ${target}`);
369
+ ctx.print(`Session not found ${target}`);
342
370
  return true;
343
371
  }
344
372
  const date = new Date(found.timestamp).toLocaleString();
345
373
  ctx.print([
346
- `Session: ${found.name}`,
347
- ` Title: ${found.title || '(untitled)'}`,
348
- ` Model: ${found.model || '(unknown)'}`,
349
- ` Provider: ${found.provider || '(unknown)'}`,
350
- ` Date: ${date}`,
351
- ` Messages: ${found.messageCount}`,
352
- ` File: ${found.filePath}`,
353
- ...(found.returnContext ? formatReturnContextForDisplay(found.returnContext).map((line) => ` ${line}`) : []),
374
+ `Session ${found.name}`,
375
+ ` title ${found.title || '(untitled)'}`,
376
+ ` model ${found.model || '(unknown)'}`,
377
+ ` provider ${found.provider || '(unknown)'}`,
378
+ ` date ${date}`,
379
+ ` messages ${found.messageCount}`,
380
+ ` file ${found.filePath}`,
381
+ ...(found.returnContext ? formatAgentReturnContextForDisplay(found.returnContext).map((line) => ` ${line}`) : []),
354
382
  ].join('\n'));
355
383
  return true;
356
384
  }
@@ -368,18 +396,18 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
368
396
  if (!found && target !== '.') {
369
397
  try {
370
398
  const { meta, messages } = sm.load(sessionId);
371
- printSessionExport(ctx, sessionId, meta.title, messages as Array<Record<string, unknown>>, format);
399
+ printSessionExport(ctx, sessionId, meta.title, readConversationMessageSnapshots(messages), format);
372
400
  } catch {
373
- ctx.print(`Session not found: ${sessionId}`);
401
+ ctx.print(`Session not found ${sessionId}`);
374
402
  }
375
403
  return true;
376
404
  }
377
405
  const loadName = found ? found.name : sessionId;
378
406
  try {
379
407
  const { meta, messages } = sm.load(loadName);
380
- printSessionExport(ctx, loadName, meta.title, messages as Array<Record<string, unknown>>, format);
408
+ printSessionExport(ctx, loadName, meta.title, readConversationMessageSnapshots(messages), format);
381
409
  } catch (e) {
382
- ctx.print(`Failed to export session: ${summarizeError(e)}`);
410
+ ctx.print(formatSessionFailure('export session', e));
383
411
  }
384
412
  return true;
385
413
  }
@@ -392,10 +420,10 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
392
420
  }
393
421
  const results = sm.search(query);
394
422
  if (results.length === 0) {
395
- ctx.print(`No sessions found matching: "${query}"`);
423
+ ctx.print(`No sessions found matching "${query}"`);
396
424
  return true;
397
425
  }
398
- const lines = [`Search results for "${query}" (${results.length} session${results.length !== 1 ? 's' : ''}):\n`];
426
+ const lines = [`Search results for "${query}" (${results.length} session${results.length !== 1 ? 's' : ''})\n`];
399
427
  for (const result of results) {
400
428
  const date = new Date(result.session.timestamp).toLocaleString();
401
429
  lines.push(` ${result.session.name} ${result.session.title || '(untitled)'} ${date} (${result.matchCount} match${result.matchCount !== 1 ? 'es' : ''})`);
@@ -428,7 +456,7 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
428
456
  const sessions = sm.list();
429
457
  const found = sessions.find((session) => session.name === target || session.name.startsWith(target));
430
458
  if (!found) {
431
- ctx.print(`Session not found: ${target}`);
459
+ ctx.print(`Session not found ${target}`);
432
460
  return true;
433
461
  }
434
462
  if (found.name === ctx.session.runtime.sessionId) {
@@ -437,9 +465,13 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
437
465
  }
438
466
  try {
439
467
  sm.delete(found.name);
440
- ctx.print(`Session deleted: ${found.name}${found.title ? ` (${found.title})` : ''}`);
468
+ ctx.print([
469
+ 'Session deleted',
470
+ ` id ${found.name}`,
471
+ ...(found.title ? [` title ${found.title}`] : []),
472
+ ].join('\n'));
441
473
  } catch (e) {
442
- ctx.print(`Failed to delete session: ${summarizeError(e)}`);
474
+ ctx.print(formatSessionFailure('delete session', e));
443
475
  }
444
476
  return true;
445
477
  }
@@ -457,14 +489,12 @@ export function registerSessionWorkflowCommands(registry: CommandRegistry): void
457
489
  async handler(args, ctx) {
458
490
  const handled = await handleSessionWorkflowCommand(args, ctx);
459
491
  if (!handled) {
460
- ctx.print('Unknown subcommand: ' + (args[0] ?? '') + '\nUsage: /session [list | rename <name> | resume <id> | fork [name] | save [name] | info [id] | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id> --yes]');
492
+ ctx.print([
493
+ 'Unknown session subcommand',
494
+ ` subcommand ${args[0] ?? ''}`,
495
+ 'Usage: /session [list | rename <name> | resume <id> | fork [name] | save [name] | info [id] | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id> --yes]',
496
+ ].join('\n'));
461
497
  }
462
498
  },
463
499
  });
464
500
  }
465
- interface SessionExportData {
466
- readonly messages: object[];
467
- readonly timestamp?: number;
468
- readonly titleSource?: ConversationTitleSource;
469
- readonly returnContext?: SessionReturnContextSummary;
470
- }