@pellux/goodvibes-agent 0.1.117 → 1.0.1

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 (255) hide show
  1. package/CHANGELOG.md +20 -4
  2. package/README.md +35 -14
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +176093 -171065
  5. package/docs/README.md +11 -5
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +3 -3
  8. package/docs/getting-started.md +29 -15
  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 +44 -9
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +2 -5
  16. package/src/agent/channel-delivery.ts +201 -0
  17. package/src/agent/media-generation.ts +159 -0
  18. package/src/agent/memory-prompt.ts +0 -1
  19. package/src/agent/note-registry.ts +329 -0
  20. package/src/agent/operator-actions.ts +343 -0
  21. package/src/agent/persona-registry.ts +15 -14
  22. package/src/agent/record-labels.ts +107 -0
  23. package/src/agent/reminder-schedule-format.ts +33 -24
  24. package/src/agent/reminder-schedule.ts +26 -25
  25. package/src/agent/routine-registry.ts +13 -12
  26. package/src/agent/routine-schedule-args.ts +2 -1
  27. package/src/agent/routine-schedule-format.ts +77 -53
  28. package/src/agent/routine-schedule-promotion.ts +34 -32
  29. package/src/agent/routine-schedule-receipts.ts +28 -26
  30. package/src/agent/runtime-profile-starters.ts +2 -2
  31. package/src/agent/runtime-profile.ts +18 -17
  32. package/src/agent/skill-registry.ts +25 -24
  33. package/src/cli/agent-knowledge-args.ts +5 -1
  34. package/src/cli/agent-knowledge-command.ts +39 -33
  35. package/src/cli/agent-knowledge-format.ts +80 -67
  36. package/src/cli/agent-knowledge-methods.ts +1 -1
  37. package/src/cli/agent-knowledge-runtime.ts +32 -26
  38. package/src/cli/bundle-command.ts +13 -8
  39. package/src/cli/config-overrides.ts +37 -36
  40. package/src/cli/external-runtime.ts +10 -4
  41. package/src/cli/help.ts +29 -11
  42. package/src/cli/local-library-command.ts +159 -73
  43. package/src/cli/management-commands.ts +98 -62
  44. package/src/cli/management.ts +52 -26
  45. package/src/cli/memory-command.ts +66 -32
  46. package/src/cli/parser.ts +37 -24
  47. package/src/cli/profiles-command.ts +52 -35
  48. package/src/cli/provider-auth-routes.ts +2 -1
  49. package/src/cli/routines-command.ts +59 -39
  50. package/src/cli/service-posture.ts +6 -6
  51. package/src/cli/status.ts +46 -121
  52. package/src/core/conversation-message-snapshot.ts +131 -0
  53. package/src/input/agent-workspace-activation.ts +33 -7
  54. package/src/input/agent-workspace-basic-command-editor-submission.ts +7 -3
  55. package/src/input/agent-workspace-basic-command-editors.ts +30 -10
  56. package/src/input/agent-workspace-categories.ts +276 -64
  57. package/src/input/agent-workspace-channel-command-editor-submission.ts +34 -0
  58. package/src/input/agent-workspace-channel-command-editors.ts +23 -5
  59. package/src/input/agent-workspace-channels.ts +35 -2
  60. package/src/input/agent-workspace-command-editor.ts +18 -2
  61. package/src/input/agent-workspace-config-reader.ts +16 -0
  62. package/src/input/agent-workspace-delegation-editor-submission.ts +1 -1
  63. package/src/input/agent-workspace-editors.ts +140 -2
  64. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  65. package/src/input/agent-workspace-knowledge-url-editor.ts +4 -11
  66. package/src/input/agent-workspace-learned-behavior.ts +2 -2
  67. package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
  68. package/src/input/agent-workspace-library-command-editors.ts +2 -2
  69. package/src/input/agent-workspace-local-operations.ts +218 -0
  70. package/src/input/agent-workspace-local-selection.ts +75 -0
  71. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  72. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  73. package/src/input/agent-workspace-memory-command-editors.ts +1 -1
  74. package/src/input/agent-workspace-memory-editor.ts +2 -2
  75. package/src/input/agent-workspace-navigation.ts +38 -2
  76. package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
  77. package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
  78. package/src/input/agent-workspace-operations-command-editors.ts +80 -3
  79. package/src/input/agent-workspace-panel-route.ts +43 -0
  80. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  81. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  82. package/src/input/agent-workspace-search.ts +169 -0
  83. package/src/input/agent-workspace-setup.ts +22 -11
  84. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
  85. package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
  86. package/src/input/agent-workspace-snapshot.ts +112 -13
  87. package/src/input/agent-workspace-task-command-editors.ts +4 -4
  88. package/src/input/agent-workspace-token.ts +18 -2
  89. package/src/input/agent-workspace-types.ts +92 -4
  90. package/src/input/agent-workspace-voice-media.ts +3 -6
  91. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  92. package/src/input/agent-workspace.ts +136 -208
  93. package/src/input/command-registry.ts +4 -1
  94. package/src/input/commands/agent-local-library-args.ts +52 -0
  95. package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
  96. package/src/input/commands/agent-skills-runtime.ts +87 -99
  97. package/src/input/commands/agent-workspace-runtime.ts +23 -7
  98. package/src/input/commands/brief-runtime.ts +25 -24
  99. package/src/input/commands/channels-runtime.ts +145 -31
  100. package/src/input/commands/delegation-runtime.ts +29 -23
  101. package/src/input/commands/experience-runtime.ts +6 -4
  102. package/src/input/commands/guidance-runtime.ts +4 -4
  103. package/src/input/commands/health-runtime.ts +140 -115
  104. package/src/input/commands/knowledge.ts +57 -56
  105. package/src/input/commands/local-provider-runtime.ts +36 -18
  106. package/src/input/commands/local-runtime.ts +138 -16
  107. package/src/input/commands/local-setup-review.ts +7 -7
  108. package/src/input/commands/mcp-runtime.ts +118 -50
  109. package/src/input/commands/notify-runtime.ts +38 -9
  110. package/src/input/commands/operator-actions-runtime.ts +138 -0
  111. package/src/input/commands/operator-runtime.ts +6 -17
  112. package/src/input/commands/personas-runtime.ts +49 -59
  113. package/src/input/commands/planning-runtime.ts +1 -1
  114. package/src/input/commands/platform-access-runtime.ts +29 -29
  115. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  116. package/src/input/commands/qrcode-runtime.ts +45 -6
  117. package/src/input/commands/recall-bundle.ts +11 -11
  118. package/src/input/commands/recall-capture.ts +13 -11
  119. package/src/input/commands/recall-query.ts +29 -21
  120. package/src/input/commands/recall-review.ts +2 -1
  121. package/src/input/commands/routines-runtime.ts +63 -72
  122. package/src/input/commands/schedule-runtime.ts +33 -20
  123. package/src/input/commands/security-runtime.ts +4 -4
  124. package/src/input/commands/session-content.ts +80 -78
  125. package/src/input/commands/session-workflow.ts +132 -93
  126. package/src/input/commands/session.ts +3 -174
  127. package/src/input/commands/shell-core.ts +32 -17
  128. package/src/input/commands/subscription-runtime.ts +53 -179
  129. package/src/input/commands/tasks-runtime.ts +20 -20
  130. package/src/input/commands/work-plan-runtime.ts +33 -8
  131. package/src/input/commands.ts +2 -2
  132. package/src/input/feed-context-factory.ts +2 -1
  133. package/src/input/file-picker.ts +3 -2
  134. package/src/input/handler-command-route.ts +4 -7
  135. package/src/input/handler-content-actions.ts +89 -1
  136. package/src/input/handler-feed-routes.ts +19 -12
  137. package/src/input/handler-feed.ts +6 -3
  138. package/src/input/handler-interactions.ts +7 -5
  139. package/src/input/handler-modal-stack.ts +3 -3
  140. package/src/input/handler-onboarding.ts +24 -80
  141. package/src/input/handler-shortcuts.ts +15 -4
  142. package/src/input/handler.ts +47 -17
  143. package/src/input/input-history.ts +5 -6
  144. package/src/input/keybindings.ts +22 -11
  145. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
  146. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  147. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
  148. package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
  149. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  150. package/src/input/onboarding/onboarding-wizard.ts +68 -0
  151. package/src/input/profile-picker-modal.ts +31 -12
  152. package/src/input/session-picker-modal.ts +22 -4
  153. package/src/input/settings-modal-behavior.ts +0 -3
  154. package/src/input/settings-modal-subscriptions.ts +3 -3
  155. package/src/input/settings-modal-types.ts +2 -13
  156. package/src/input/settings-modal.ts +6 -52
  157. package/src/input/submission-intent.ts +0 -1
  158. package/src/input/submission-router.ts +3 -3
  159. package/src/main.ts +18 -8
  160. package/src/panels/approval-panel.ts +8 -8
  161. package/src/panels/automation-control-panel.ts +2 -2
  162. package/src/panels/base-panel.ts +1 -1
  163. package/src/panels/builtin/agent.ts +1 -1
  164. package/src/panels/builtin/operations.ts +1 -10
  165. package/src/panels/builtin/session.ts +9 -9
  166. package/src/panels/builtin/shared.ts +2 -2
  167. package/src/panels/cost-tracker-panel.ts +1 -1
  168. package/src/panels/docs-panel.ts +5 -3
  169. package/src/panels/index.ts +0 -1
  170. package/src/panels/knowledge-panel.ts +6 -5
  171. package/src/panels/memory-panel.ts +7 -6
  172. package/src/panels/panel-list-panel.ts +36 -80
  173. package/src/panels/project-planning-panel.ts +18 -11
  174. package/src/panels/provider-account-snapshot.ts +51 -25
  175. package/src/panels/provider-accounts-panel.ts +33 -18
  176. package/src/panels/provider-health-domains.ts +45 -4
  177. package/src/panels/provider-health-panel.ts +5 -4
  178. package/src/panels/qr-panel.ts +1 -1
  179. package/src/panels/schedule-panel.ts +9 -17
  180. package/src/panels/security-panel.ts +8 -8
  181. package/src/panels/session-browser-panel.ts +10 -10
  182. package/src/panels/subscription-panel.ts +3 -3
  183. package/src/panels/system-messages-panel.ts +1 -1
  184. package/src/panels/tasks-panel.ts +20 -13
  185. package/src/panels/tool-inspector-panel.ts +19 -12
  186. package/src/panels/work-plan-panel.ts +5 -5
  187. package/src/planning/project-planning-coordinator.ts +1 -1
  188. package/src/provider-auth-route-display.ts +9 -0
  189. package/src/renderer/agent-workspace-style.ts +34 -0
  190. package/src/renderer/agent-workspace.ts +228 -52
  191. package/src/renderer/autocomplete-overlay.ts +25 -6
  192. package/src/renderer/bookmark-modal.ts +19 -4
  193. package/src/renderer/buffer.ts +4 -2
  194. package/src/renderer/context-inspector.ts +50 -13
  195. package/src/renderer/diff.ts +1 -1
  196. package/src/renderer/file-picker-overlay.ts +19 -6
  197. package/src/renderer/help-overlay.ts +106 -33
  198. package/src/renderer/history-search-overlay.ts +19 -4
  199. package/src/renderer/live-tail-modal.ts +27 -5
  200. package/src/renderer/mcp-workspace.ts +164 -50
  201. package/src/renderer/model-picker-overlay.ts +58 -2
  202. package/src/renderer/model-workspace.ts +104 -20
  203. package/src/renderer/process-modal.ts +27 -6
  204. package/src/renderer/profile-picker-modal.ts +20 -4
  205. package/src/renderer/search-overlay.ts +25 -5
  206. package/src/renderer/selection-modal-overlay.ts +46 -14
  207. package/src/renderer/session-picker-modal.ts +18 -1
  208. package/src/renderer/settings-modal-helpers.ts +2 -40
  209. package/src/renderer/settings-modal.ts +83 -50
  210. package/src/renderer/tool-call.ts +20 -11
  211. package/src/runtime/agent-runtime-events.ts +6 -6
  212. package/src/runtime/bootstrap-command-context.ts +7 -1
  213. package/src/runtime/bootstrap-command-parts.ts +9 -7
  214. package/src/runtime/bootstrap-core.ts +19 -1
  215. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  216. package/src/runtime/bootstrap-shell.ts +4 -4
  217. package/src/runtime/bootstrap.ts +31 -22
  218. package/src/runtime/connected-host-auth.ts +7 -2
  219. package/src/runtime/diagnostics/panels/index.ts +2 -2
  220. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  221. package/src/runtime/index.ts +2 -1
  222. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  223. package/src/runtime/onboarding/apply.ts +80 -79
  224. package/src/runtime/onboarding/derivation.ts +5 -8
  225. package/src/runtime/onboarding/snapshot.ts +0 -15
  226. package/src/runtime/onboarding/types.ts +8 -19
  227. package/src/runtime/onboarding/verify.ts +32 -10
  228. package/src/runtime/operator-token-cleanup.ts +1 -1
  229. package/src/runtime/services.ts +91 -22
  230. package/src/runtime/store/selectors/index.ts +3 -3
  231. package/src/runtime/store/state.ts +1 -4
  232. package/src/runtime/surface-feature-flags.ts +41 -6
  233. package/src/runtime/ui-read-models.ts +3 -4
  234. package/src/runtime/ui-services.ts +6 -6
  235. package/src/shell/blocking-input.ts +2 -1
  236. package/src/shell/ui-openers.ts +3 -13
  237. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  238. package/src/tools/agent-channel-send-tool.ts +133 -0
  239. package/src/tools/agent-context-policy.ts +1 -1
  240. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  241. package/src/tools/agent-knowledge-tool.ts +170 -0
  242. package/src/tools/agent-local-registry-tool.ts +269 -69
  243. package/src/tools/agent-media-generate-tool.ts +133 -0
  244. package/src/tools/agent-notify-tool.ts +143 -0
  245. package/src/tools/agent-operator-action-tool.ts +137 -0
  246. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  247. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  248. package/src/tools/agent-tool-policy-guard.ts +8 -8
  249. package/src/tools/agent-work-plan-tool.ts +256 -0
  250. package/src/version.ts +1 -1
  251. package/src/input/commands/policy-dispatch.ts +0 -339
  252. package/src/input/commands/policy.ts +0 -13
  253. package/src/panels/panel-picker.ts +0 -105
  254. package/src/panels/policy-panel.ts +0 -308
  255. 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
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,24 +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
- ctx.print(` Saved panel state ignored: ${summary.openPanels.slice(0, 4).join(', ')}. Use /agent for the Agent operator workspace.`);
85
- return [];
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
+ ];
86
103
  }
87
104
 
88
105
  function printSessionExport(
89
106
  ctx: { print: (text: string) => void },
90
107
  sessionId: string,
91
108
  title: string,
92
- messages: Array<Record<string, unknown>>,
109
+ messages: readonly ConversationMessageSnapshot[],
93
110
  format: string,
94
111
  ): void {
95
112
  const lines: string[] = [];
@@ -97,21 +114,20 @@ function printSessionExport(
97
114
  lines.push(`# Session: ${title || sessionId}`);
98
115
  lines.push('');
99
116
  for (const msg of messages) {
100
- const role = String(msg.role ?? 'unknown');
101
- const content = String(msg.content ?? '');
117
+ const content = conversationMessageContentText(msg);
102
118
  if (!content.trim()) continue;
103
- if (role === 'user') {
119
+ if (msg.role === 'user') {
104
120
  lines.push('## User');
105
121
  lines.push('');
106
122
  lines.push(content);
107
123
  lines.push('');
108
- } else if (role === 'assistant') {
124
+ } else if (msg.role === 'assistant') {
109
125
  lines.push('## Assistant');
110
126
  lines.push('');
111
127
  lines.push(content);
112
128
  lines.push('');
113
- } else if (role === 'tool') {
114
- const toolName = String(msg.toolName ?? 'tool');
129
+ } else if (msg.role === 'tool') {
130
+ const toolName = msg.toolName ?? 'tool';
115
131
  lines.push(`## Tool Result: ${toolName}`);
116
132
  lines.push('');
117
133
  lines.push('```');
@@ -122,8 +138,8 @@ function printSessionExport(
122
138
  }
123
139
  } else {
124
140
  for (const msg of messages) {
125
- const role = String(msg.role ?? 'unknown').toUpperCase();
126
- const content = String(msg.content ?? '');
141
+ const role = msg.role.toUpperCase();
142
+ const content = conversationMessageContentText(msg);
127
143
  if (!content.trim()) continue;
128
144
  lines.push(`[${role}]`);
129
145
  lines.push(content);
@@ -133,6 +149,14 @@ function printSessionExport(
133
149
  ctx.print(lines.join('\n'));
134
150
  }
135
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
+
136
160
  export async function handleSessionWorkflowCommand(args: string[], ctx: CommandContext): Promise<boolean> {
137
161
  const sm = requireSessionManager(ctx);
138
162
  const sub = args[0];
@@ -145,11 +169,11 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
145
169
  const started = meta ? new Date(meta.timestamp).toLocaleString() : 'this session';
146
170
  ctx.print([
147
171
  'Current session',
148
- ` ID: ${id}`,
149
- ` Name: ${title}`,
150
- ` Started: ${started}`,
151
- ` Messages: ${msgCount}`,
152
- ` 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})`,
153
177
  ].join('\n'));
154
178
  return true;
155
179
  }
@@ -160,7 +184,7 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
160
184
  ctx.print('No saved sessions. Use /session save [name] to save the current session.');
161
185
  return true;
162
186
  }
163
- const lines = ['Sessions (most recent first):', ''];
187
+ const lines = ['Sessions (most recent first)', ''];
164
188
  for (const session of sessions) {
165
189
  const date = new Date(session.timestamp).toLocaleString();
166
190
  const name = session.title || session.name;
@@ -169,12 +193,12 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
169
193
  lines.push(`${active} ${session.name.padEnd(28)} ${name.slice(0, 22).padEnd(22)} ${date} ${session.messageCount} msgs${model}`);
170
194
  if (session.returnContext?.activeTasks || session.returnContext?.blockedTasks || session.returnContext?.pendingApprovals || session.returnContext?.openPanels?.length) {
171
195
  const posture = [
172
- session.returnContext.activeTasks ? `active=${session.returnContext.activeTasks}` : null,
173
- session.returnContext.blockedTasks ? `blocked=${session.returnContext.blockedTasks}` : null,
174
- session.returnContext.pendingApprovals ? `approvals=${session.returnContext.pendingApprovals}` : null,
175
- session.returnContext.openPanels?.length ? `saved-panel-state-ignored=${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,
176
200
  ].filter(Boolean).join(' ');
177
- if (posture) lines.push(` posture: ${posture}`);
201
+ if (posture) lines.push(` posture ${posture}`);
178
202
  }
179
203
  }
180
204
  ctx.print(lines.join('\n'));
@@ -190,20 +214,23 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
190
214
  try {
191
215
  const existingMeta = sm.getMeta(ctx.session.runtime.sessionId);
192
216
  if (!existingMeta) {
193
- const exportData = ctx.session.conversationManager.toJSON() as { messages: object[]; timestamp?: number };
194
- sm.save(ctx.session.runtime.sessionId, exportData.messages ?? [], {
217
+ sm.save(ctx.session.runtime.sessionId, ctx.session.conversationManager.getMessageSnapshot(), {
195
218
  title: ctx.session.conversationManager.title || '',
196
219
  model: ctx.session.runtime.model,
197
220
  provider: ctx.session.runtime.provider,
198
221
  timestamp: Date.now(),
222
+ titleSource: ctx.session.conversationManager.getTitleSource(),
199
223
  });
200
224
  }
201
225
  sm.rename(ctx.session.runtime.sessionId, newName);
202
226
  ctx.session.conversationManager.title = newName;
203
- ctx.print(`Session renamed to: ${newName}`);
227
+ ctx.print([
228
+ 'Session renamed',
229
+ ` name ${newName}`,
230
+ ].join('\n'));
204
231
  ctx.renderRequest();
205
232
  } catch (e) {
206
- ctx.print(`Failed to rename: ${summarizeError(e)}`);
233
+ ctx.print(formatSessionFailure('rename session', e));
207
234
  }
208
235
  return true;
209
236
  }
@@ -221,14 +248,14 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
221
248
  session.title.toLowerCase() === target.toLowerCase(),
222
249
  );
223
250
  if (!found) {
224
- 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.`);
225
252
  return true;
226
253
  }
227
254
  try {
228
255
  const { meta, messages } = sm.load(found.name);
229
256
  const providerApi = requireProviderApi(ctx);
230
257
  ctx.session.conversationManager.resetAll();
231
- 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 });
232
259
  ctx.session.conversationManager.rebuildHistory();
233
260
  ctx.session.runtime.sessionId = found.name;
234
261
  if (meta.model) {
@@ -243,83 +270,93 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
243
270
  }
244
271
  if (meta.provider) ctx.session.runtime.provider = meta.provider;
245
272
  ctx.renderRequest();
246
- ctx.print(`Resumed session: ${found.name}\n Name: ${meta.title || '(untitled)'}\n Messages: ${messages.length}\n Model: ${meta.model || ctx.session.runtime.model}`);
247
- 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);
248
281
  const returnContextMode = getReturnContextMode(ctx.platform.configManager);
249
282
  if (returnContextMode !== 'off' && meta.returnContext) {
250
283
  for (const line of formatReturnContextForDisplay(meta.returnContext)) {
251
284
  if (line.startsWith('Open panels:')) continue;
252
285
  ctx.print(` ${line}`);
253
286
  }
254
- if (reopenedPanels.length > 0) {
255
- ctx.print(` Reopened panels: ${reopenedPanels.join(', ')}`);
256
- }
257
287
  if ((meta.returnContext.remoteRunners?.length ?? 0) > 0) {
258
- 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.');
259
289
  }
260
290
  if ((meta.returnContext.worktreePaths?.length ?? 0) > 0) {
261
- 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.');
262
292
  }
263
293
  if (returnContextMode === 'assisted') {
264
294
  const helperModel = providerApi.createHelperModel(ctx.platform.configManager);
265
295
  void maybeAssistReturnContextSummary(ctx.platform.configManager, helperModel, meta.returnContext).then((assisted) => {
266
296
  if (!assisted.assistedNarrative) return;
267
- ctx.print(` Assist: ${assisted.assistedNarrative}`);
297
+ ctx.print(` Assist ${assisted.assistedNarrative}`);
268
298
  ctx.renderRequest();
269
299
  });
270
300
  }
271
301
  }
272
302
  } catch (e) {
273
- ctx.print(`Failed to resume session: ${summarizeError(e)}`);
303
+ ctx.print(formatSessionFailure('resume session', e));
274
304
  }
275
305
  return true;
276
306
  }
277
307
 
278
308
  if (sub === 'fork') {
309
+ const sourceSessionId = ctx.session.runtime.sessionId;
279
310
  const newId = `user-${randomBytes(4).toString('hex')}`;
280
- const exportData = ctx.session.conversationManager.toJSON() as SessionExportData;
281
- const messages = exportData.messages ?? [];
282
- const currentTitle = ctx.session.conversationManager.title;
283
- const forkName = args[1] ? args.slice(1).join(' ').trim() : `fork-of-${ctx.session.runtime.sessionId.slice(0, 8)}`;
284
- const meta: SessionMeta = {
285
- title: forkName,
286
- model: ctx.session.runtime.model,
287
- 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,
288
318
  timestamp: Date.now(),
289
- titleSource: exportData.titleSource,
290
- returnContext: exportData.returnContext,
319
+ titleSource: ctx.session.conversationManager.getTitleSource(),
291
320
  };
292
321
  try {
293
322
  sm.save(newId, messages, meta);
294
323
  ctx.session.runtime.sessionId = newId;
295
324
  ctx.session.conversationManager.title = forkName;
296
325
  ctx.renderRequest();
297
- 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'));
298
333
  } catch (e) {
299
- ctx.print(`Failed to fork session: ${summarizeError(e)}`);
334
+ ctx.print(formatSessionFailure('fork session', e));
300
335
  }
301
336
  return true;
302
337
  }
303
338
 
304
339
  if (sub === 'save') {
305
- const exportData = ctx.session.conversationManager.toJSON() as SessionExportData;
306
- const messages = exportData.messages ?? [];
307
- const rawName = args[1] ? args.slice(1).join(' ').trim() : (ctx.session.conversationManager.title || ctx.session.runtime.sessionId);
308
- const meta: SessionMeta = {
309
- title: ctx.session.conversationManager.title,
310
- model: ctx.session.runtime.model,
311
- 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,
312
346
  timestamp: Date.now(),
313
- titleSource: exportData.titleSource,
314
- returnContext: exportData.returnContext,
347
+ titleSource: ctx.session.conversationManager.getTitleSource(),
315
348
  };
316
349
  try {
317
350
  const { filePath, sanitizedName } = sm.save(rawName, messages, meta);
318
351
  ctx.session.runtime.sessionId = sanitizedName;
319
- const nameNote = sanitizedName !== rawName ? ` (saved as "${sanitizedName}")` : '';
320
- 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'));
321
358
  } catch (e) {
322
- ctx.print(`Failed to save session: ${summarizeError(e)}`);
359
+ ctx.print(formatSessionFailure('save session', e));
323
360
  }
324
361
  return true;
325
362
  }
@@ -329,19 +366,19 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
329
366
  const sessions = sm.list();
330
367
  const found = sessions.find((session) => session.name === target || session.name.startsWith(target));
331
368
  if (!found) {
332
- ctx.print(`Session not found: ${target}`);
369
+ ctx.print(`Session not found ${target}`);
333
370
  return true;
334
371
  }
335
372
  const date = new Date(found.timestamp).toLocaleString();
336
373
  ctx.print([
337
- `Session: ${found.name}`,
338
- ` Title: ${found.title || '(untitled)'}`,
339
- ` Model: ${found.model || '(unknown)'}`,
340
- ` Provider: ${found.provider || '(unknown)'}`,
341
- ` Date: ${date}`,
342
- ` Messages: ${found.messageCount}`,
343
- ` File: ${found.filePath}`,
344
- ...(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}`) : []),
345
382
  ].join('\n'));
346
383
  return true;
347
384
  }
@@ -359,18 +396,18 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
359
396
  if (!found && target !== '.') {
360
397
  try {
361
398
  const { meta, messages } = sm.load(sessionId);
362
- printSessionExport(ctx, sessionId, meta.title, messages as Array<Record<string, unknown>>, format);
399
+ printSessionExport(ctx, sessionId, meta.title, readConversationMessageSnapshots(messages), format);
363
400
  } catch {
364
- ctx.print(`Session not found: ${sessionId}`);
401
+ ctx.print(`Session not found ${sessionId}`);
365
402
  }
366
403
  return true;
367
404
  }
368
405
  const loadName = found ? found.name : sessionId;
369
406
  try {
370
407
  const { meta, messages } = sm.load(loadName);
371
- printSessionExport(ctx, loadName, meta.title, messages as Array<Record<string, unknown>>, format);
408
+ printSessionExport(ctx, loadName, meta.title, readConversationMessageSnapshots(messages), format);
372
409
  } catch (e) {
373
- ctx.print(`Failed to export session: ${summarizeError(e)}`);
410
+ ctx.print(formatSessionFailure('export session', e));
374
411
  }
375
412
  return true;
376
413
  }
@@ -383,10 +420,10 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
383
420
  }
384
421
  const results = sm.search(query);
385
422
  if (results.length === 0) {
386
- ctx.print(`No sessions found matching: "${query}"`);
423
+ ctx.print(`No sessions found matching "${query}"`);
387
424
  return true;
388
425
  }
389
- 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`];
390
427
  for (const result of results) {
391
428
  const date = new Date(result.session.timestamp).toLocaleString();
392
429
  lines.push(` ${result.session.name} ${result.session.title || '(untitled)'} ${date} (${result.matchCount} match${result.matchCount !== 1 ? 'es' : ''})`);
@@ -419,7 +456,7 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
419
456
  const sessions = sm.list();
420
457
  const found = sessions.find((session) => session.name === target || session.name.startsWith(target));
421
458
  if (!found) {
422
- ctx.print(`Session not found: ${target}`);
459
+ ctx.print(`Session not found ${target}`);
423
460
  return true;
424
461
  }
425
462
  if (found.name === ctx.session.runtime.sessionId) {
@@ -428,9 +465,13 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
428
465
  }
429
466
  try {
430
467
  sm.delete(found.name);
431
- 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'));
432
473
  } catch (e) {
433
- ctx.print(`Failed to delete session: ${summarizeError(e)}`);
474
+ ctx.print(formatSessionFailure('delete session', e));
434
475
  }
435
476
  return true;
436
477
  }
@@ -448,14 +489,12 @@ export function registerSessionWorkflowCommands(registry: CommandRegistry): void
448
489
  async handler(args, ctx) {
449
490
  const handled = await handleSessionWorkflowCommand(args, ctx);
450
491
  if (!handled) {
451
- 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'));
452
497
  }
453
498
  },
454
499
  });
455
500
  }
456
- interface SessionExportData {
457
- readonly messages: object[];
458
- readonly timestamp?: number;
459
- readonly titleSource?: ConversationTitleSource;
460
- readonly returnContext?: SessionReturnContextSummary;
461
- }
@@ -2,13 +2,12 @@
2
2
  * /session command handler — Multi-session Orchestration.
3
3
  *
4
4
  * Implements read-only session graph inspection plus session continuity commands.
5
- * Copied local task graph mutation commands are blocked in Agent; explicit
6
- * build/fix/review handoff must use `/delegate` so GoodVibes TUI owns execution.
5
+ * Local task graph mutation commands are blocked in Agent; explicit build/fix/review
6
+ * handoff must use `/delegate` so GoodVibes TUI owns execution.
7
7
  */
8
8
 
9
9
  import type { SlashCommand, CommandContext } from '../command-registry.ts';
10
- import type { CancellationScope, CrossSessionTaskRef } from '@pellux/goodvibes-sdk/platform/sessions';
11
- import { VALID_SCOPES } from '@pellux/goodvibes-sdk/platform/sessions';
10
+ import type { CrossSessionTaskRef } from '@pellux/goodvibes-sdk/platform/sessions';
12
11
  import { handleSessionWorkflowCommand } from './session-workflow.ts';
13
12
  import { requireSessionOrchestration } from './runtime-services.ts';
14
13
 
@@ -24,21 +23,6 @@ function flagValue(args: string[], flag: string): string | undefined {
24
23
  return args[idx + 1];
25
24
  }
26
25
 
27
- /**
28
- * Parse a cross-session task ref from a string of the form
29
- * `<sessionId>:<taskId>` or just `<taskId>` (uses currentSessionId as owner).
30
- */
31
- function parseRef(
32
- raw: string,
33
- currentSessionId: string,
34
- ): { sessionId: string; taskId: string } {
35
- const parts = raw.split(':', 2);
36
- if (parts.length === 2) {
37
- return { sessionId: parts[0]!, taskId: parts[1]! };
38
- }
39
- return { sessionId: currentSessionId, taskId: raw };
40
- }
41
-
42
26
  // ── Formatting helpers ────────────────────────────────────────────────────────
43
27
 
44
28
  /** Status badge for display. */
@@ -69,101 +53,6 @@ function printSessionGraphMutationBlocked(context: CommandContext): void {
69
53
  ].join('\n'));
70
54
  }
71
55
 
72
- // ── /session link-task ────────────────────────────────────────────────────────
73
-
74
- function handleLinkTask(args: string[], context: CommandContext): void {
75
- const taskId = args[0];
76
- if (!taskId) {
77
- context.print(
78
- '[session] Usage: /session link-task <taskId> [--session <sessionId>] ' +
79
- '[--depends-on <sessionId:taskId>] [--label <label>]',
80
- );
81
- return;
82
- }
83
-
84
- // Defense-in-depth: parser splits on whitespace but guard against future changes
85
- if (!taskId.trim()) {
86
- context.print('Error: taskId cannot be empty or whitespace.');
87
- return;
88
- }
89
-
90
- if (taskId.includes(':')) {
91
- context.print('Error: taskId cannot contain ":" — it conflicts with the composite key format.');
92
- return;
93
- }
94
-
95
- const sessionId = flagValue(args, '--session') ?? context.session.runtime.sessionId;
96
- const dependsOnRaw = flagValue(args, '--depends-on');
97
- const label = flagValue(args, '--label');
98
-
99
- const orchestration = requireSessionOrchestration(context);
100
-
101
- const ref: CrossSessionTaskRef = {
102
- sessionId,
103
- taskId,
104
- title: label ?? taskId,
105
- status: 'queued',
106
- createdAt: Date.now(),
107
- updatedAt: Date.now(),
108
- label,
109
- };
110
-
111
- const dependsOn = dependsOnRaw ? parseRef(dependsOnRaw, sessionId) : undefined;
112
-
113
- const result = orchestration.linkTask(ref, dependsOn);
114
-
115
- if (!result.ok) {
116
- context.print(`[session] link-task failed: ${result.error}`);
117
- return;
118
- }
119
-
120
- context.print(
121
- `[session] Task linked: ${sessionId.slice(0, 8)}...:${taskId}` +
122
- (label ? ` [${label}]` : '') +
123
- (dependsOn ? ` → depends on ${dependsOnRaw}` : ''),
124
- );
125
- }
126
-
127
- // ── /session handoff ──────────────────────────────────────────────────────────
128
-
129
- function handleHandoff(args: string[], context: CommandContext): void {
130
- const taskId = args[0];
131
- const toSessionId = flagValue(args, '--to');
132
- const fromSessionId = flagValue(args, '--session') ?? context.session.runtime.sessionId;
133
- const reason = flagValue(args, '--reason');
134
-
135
- if (!taskId || !toSessionId) {
136
- context.print(
137
- '[session] Usage: /session handoff <taskId> --to <sessionId> ' +
138
- '[--session <fromSessionId>] [--reason <reason>]',
139
- );
140
- return;
141
- }
142
-
143
- const orchestration = requireSessionOrchestration(context);
144
-
145
- const result = orchestration.initiateHandoff(
146
- { sessionId: fromSessionId, taskId },
147
- fromSessionId,
148
- toSessionId,
149
- reason,
150
- );
151
-
152
- if (!result.ok) {
153
- context.print(`[session] handoff failed: ${result.error}`);
154
- return;
155
- }
156
-
157
- context.print(
158
- `[session] Handoff initiated: ${taskId} (${fromSessionId.slice(0, 8)}...) → (${toSessionId.slice(0, 8)}...)` +
159
- (reason ? ` reason: ${reason}` : '') +
160
- `\n[session] handoffId: ${result.handoffId}`,
161
- );
162
- context.print(
163
- '[session] The task is now blocked pending acknowledgement from the destination session.',
164
- );
165
- }
166
-
167
56
  // ── /session graph ────────────────────────────────────────────────────────────
168
57
 
169
58
  function handleGraph(args: string[], context: CommandContext): void {
@@ -251,66 +140,6 @@ function handleGraph(args: string[], context: CommandContext): void {
251
140
  context.print(lines.join('\n'));
252
141
  }
253
142
 
254
- // ── /session cancel ───────────────────────────────────────────────────────────
255
-
256
- function handleCancel(args: string[], context: CommandContext): void {
257
- const scopeRaw = flagValue(args, '--scope');
258
- if (scopeRaw && !VALID_SCOPES.includes(scopeRaw as CancellationScope)) {
259
- context.print(`Invalid --scope: "${scopeRaw}". Valid: ${VALID_SCOPES.join(', ')}`);
260
- return;
261
- }
262
- const scope: CancellationScope = (scopeRaw as CancellationScope) ?? 'task';
263
- const sessionId = flagValue(args, '--session') ?? context.session.runtime.sessionId;
264
- const reason = flagValue(args, '--reason');
265
-
266
- // For session scope, taskId is not required
267
- const taskId = scope === 'session' ? undefined : args[0];
268
-
269
- if (scope !== 'session' && !taskId) {
270
- context.print(
271
- '[session] Usage: /session cancel <taskId> [--scope task|subtree|session] ' +
272
- '[--session <sessionId>] [--reason <reason>]\n' +
273
- ' --scope task Cancel only this task (default)\n' +
274
- ' --scope subtree Cancel this task and all tasks that transitively depend on it\n' +
275
- ' --scope session Cancel all tasks in the session',
276
- );
277
- return;
278
- }
279
-
280
- const orchestration = requireSessionOrchestration(context);
281
-
282
- const result = orchestration.cancel({
283
- sessionId,
284
- taskId,
285
- scope,
286
- reason,
287
- requestedAt: Date.now(),
288
- });
289
-
290
- if (!result.ok) {
291
- context.print(`[session] cancel failed: ${result.error}`);
292
- return;
293
- }
294
-
295
- const lines: string[] = [
296
- `[session] Cancelled ${result.cancelled.length} task${result.cancelled.length !== 1 ? 's' : ''} ` +
297
- `(scope=${scope}):`,
298
- ];
299
-
300
- for (const t of result.cancelled) {
301
- lines.push(` [x] ${t.sessionId.slice(0, 8)}...:${t.taskId.slice(0, 8)}... "${t.title}"`);
302
- }
303
-
304
- if (result.skipped.length > 0) {
305
- lines.push(` Skipped ${result.skipped.length} (already terminal):`);
306
- for (const s of result.skipped) {
307
- lines.push(` [-] ${s.sessionId.slice(0, 8)}...:${s.taskId.slice(0, 8)}... "${s.title}" (${s.reason})`);
308
- }
309
- }
310
-
311
- context.print(lines.join('\n'));
312
- }
313
-
314
143
  // ── Top-level command definition ───────────────────────────────────────────────
315
144
 
316
145
  /**