@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.
- package/CHANGELOG.md +20 -4
- package/README.md +35 -14
- package/bin/goodvibes-agent.ts +16 -2
- package/dist/package/main.js +176093 -171065
- package/docs/README.md +11 -5
- package/docs/channels-remote-and-api.md +50 -0
- package/docs/connected-host.md +3 -3
- package/docs/getting-started.md +29 -15
- package/docs/knowledge-artifacts-and-multimodal.md +91 -0
- package/docs/project-planning.md +79 -0
- package/docs/providers-and-routing.md +46 -0
- package/docs/release-and-publishing.md +44 -9
- package/docs/tools-and-commands.md +123 -0
- package/docs/voice-and-live-tts.md +51 -0
- package/package.json +2 -5
- package/src/agent/channel-delivery.ts +201 -0
- package/src/agent/media-generation.ts +159 -0
- package/src/agent/memory-prompt.ts +0 -1
- package/src/agent/note-registry.ts +329 -0
- package/src/agent/operator-actions.ts +343 -0
- package/src/agent/persona-registry.ts +15 -14
- package/src/agent/record-labels.ts +107 -0
- package/src/agent/reminder-schedule-format.ts +33 -24
- package/src/agent/reminder-schedule.ts +26 -25
- package/src/agent/routine-registry.ts +13 -12
- package/src/agent/routine-schedule-args.ts +2 -1
- package/src/agent/routine-schedule-format.ts +77 -53
- package/src/agent/routine-schedule-promotion.ts +34 -32
- package/src/agent/routine-schedule-receipts.ts +28 -26
- package/src/agent/runtime-profile-starters.ts +2 -2
- package/src/agent/runtime-profile.ts +18 -17
- package/src/agent/skill-registry.ts +25 -24
- package/src/cli/agent-knowledge-args.ts +5 -1
- package/src/cli/agent-knowledge-command.ts +39 -33
- package/src/cli/agent-knowledge-format.ts +80 -67
- package/src/cli/agent-knowledge-methods.ts +1 -1
- package/src/cli/agent-knowledge-runtime.ts +32 -26
- package/src/cli/bundle-command.ts +13 -8
- package/src/cli/config-overrides.ts +37 -36
- package/src/cli/external-runtime.ts +10 -4
- package/src/cli/help.ts +29 -11
- package/src/cli/local-library-command.ts +159 -73
- package/src/cli/management-commands.ts +98 -62
- package/src/cli/management.ts +52 -26
- package/src/cli/memory-command.ts +66 -32
- package/src/cli/parser.ts +37 -24
- package/src/cli/profiles-command.ts +52 -35
- package/src/cli/provider-auth-routes.ts +2 -1
- package/src/cli/routines-command.ts +59 -39
- package/src/cli/service-posture.ts +6 -6
- package/src/cli/status.ts +46 -121
- package/src/core/conversation-message-snapshot.ts +131 -0
- package/src/input/agent-workspace-activation.ts +33 -7
- package/src/input/agent-workspace-basic-command-editor-submission.ts +7 -3
- package/src/input/agent-workspace-basic-command-editors.ts +30 -10
- package/src/input/agent-workspace-categories.ts +276 -64
- package/src/input/agent-workspace-channel-command-editor-submission.ts +34 -0
- package/src/input/agent-workspace-channel-command-editors.ts +23 -5
- package/src/input/agent-workspace-channels.ts +35 -2
- package/src/input/agent-workspace-command-editor.ts +18 -2
- package/src/input/agent-workspace-config-reader.ts +16 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +1 -1
- package/src/input/agent-workspace-editors.ts +140 -2
- package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
- package/src/input/agent-workspace-knowledge-url-editor.ts +4 -11
- package/src/input/agent-workspace-learned-behavior.ts +2 -2
- package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
- package/src/input/agent-workspace-library-command-editors.ts +2 -2
- package/src/input/agent-workspace-local-operations.ts +218 -0
- package/src/input/agent-workspace-local-selection.ts +75 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
- package/src/input/agent-workspace-media-command-editors.ts +27 -0
- package/src/input/agent-workspace-memory-command-editors.ts +1 -1
- package/src/input/agent-workspace-memory-editor.ts +2 -2
- package/src/input/agent-workspace-navigation.ts +38 -2
- package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
- package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
- package/src/input/agent-workspace-operations-command-editors.ts +80 -3
- package/src/input/agent-workspace-panel-route.ts +43 -0
- package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
- package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
- package/src/input/agent-workspace-search.ts +169 -0
- package/src/input/agent-workspace-setup.ts +22 -11
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
- package/src/input/agent-workspace-snapshot.ts +112 -13
- package/src/input/agent-workspace-task-command-editors.ts +4 -4
- package/src/input/agent-workspace-token.ts +18 -2
- package/src/input/agent-workspace-types.ts +92 -4
- package/src/input/agent-workspace-voice-media.ts +3 -6
- package/src/input/agent-workspace-web-research-editor.ts +104 -0
- package/src/input/agent-workspace.ts +136 -208
- package/src/input/command-registry.ts +4 -1
- package/src/input/commands/agent-local-library-args.ts +52 -0
- package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
- package/src/input/commands/agent-skills-runtime.ts +87 -99
- package/src/input/commands/agent-workspace-runtime.ts +23 -7
- package/src/input/commands/brief-runtime.ts +25 -24
- package/src/input/commands/channels-runtime.ts +145 -31
- package/src/input/commands/delegation-runtime.ts +29 -23
- package/src/input/commands/experience-runtime.ts +6 -4
- package/src/input/commands/guidance-runtime.ts +4 -4
- package/src/input/commands/health-runtime.ts +140 -115
- package/src/input/commands/knowledge.ts +57 -56
- package/src/input/commands/local-provider-runtime.ts +36 -18
- package/src/input/commands/local-runtime.ts +138 -16
- package/src/input/commands/local-setup-review.ts +7 -7
- package/src/input/commands/mcp-runtime.ts +118 -50
- package/src/input/commands/notify-runtime.ts +38 -9
- package/src/input/commands/operator-actions-runtime.ts +138 -0
- package/src/input/commands/operator-runtime.ts +6 -17
- package/src/input/commands/personas-runtime.ts +49 -59
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +29 -29
- package/src/input/commands/provider-accounts-runtime.ts +60 -39
- package/src/input/commands/qrcode-runtime.ts +45 -6
- package/src/input/commands/recall-bundle.ts +11 -11
- package/src/input/commands/recall-capture.ts +13 -11
- package/src/input/commands/recall-query.ts +29 -21
- package/src/input/commands/recall-review.ts +2 -1
- package/src/input/commands/routines-runtime.ts +63 -72
- package/src/input/commands/schedule-runtime.ts +33 -20
- package/src/input/commands/security-runtime.ts +4 -4
- package/src/input/commands/session-content.ts +80 -78
- package/src/input/commands/session-workflow.ts +132 -93
- package/src/input/commands/session.ts +3 -174
- package/src/input/commands/shell-core.ts +32 -17
- package/src/input/commands/subscription-runtime.ts +53 -179
- package/src/input/commands/tasks-runtime.ts +20 -20
- package/src/input/commands/work-plan-runtime.ts +33 -8
- package/src/input/commands.ts +2 -2
- package/src/input/feed-context-factory.ts +2 -1
- package/src/input/file-picker.ts +3 -2
- package/src/input/handler-command-route.ts +4 -7
- package/src/input/handler-content-actions.ts +89 -1
- package/src/input/handler-feed-routes.ts +19 -12
- package/src/input/handler-feed.ts +6 -3
- package/src/input/handler-interactions.ts +7 -5
- package/src/input/handler-modal-stack.ts +3 -3
- package/src/input/handler-onboarding.ts +24 -80
- package/src/input/handler-shortcuts.ts +15 -4
- package/src/input/handler.ts +47 -17
- package/src/input/input-history.ts +5 -6
- package/src/input/keybindings.ts +22 -11
- package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
- package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/onboarding/onboarding-wizard.ts +68 -0
- package/src/input/profile-picker-modal.ts +31 -12
- package/src/input/session-picker-modal.ts +22 -4
- package/src/input/settings-modal-behavior.ts +0 -3
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +2 -13
- package/src/input/settings-modal.ts +6 -52
- package/src/input/submission-intent.ts +0 -1
- package/src/input/submission-router.ts +3 -3
- package/src/main.ts +18 -8
- package/src/panels/approval-panel.ts +8 -8
- package/src/panels/automation-control-panel.ts +2 -2
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +1 -10
- package/src/panels/builtin/session.ts +9 -9
- package/src/panels/builtin/shared.ts +2 -2
- package/src/panels/cost-tracker-panel.ts +1 -1
- package/src/panels/docs-panel.ts +5 -3
- package/src/panels/index.ts +0 -1
- package/src/panels/knowledge-panel.ts +6 -5
- package/src/panels/memory-panel.ts +7 -6
- package/src/panels/panel-list-panel.ts +36 -80
- package/src/panels/project-planning-panel.ts +18 -11
- package/src/panels/provider-account-snapshot.ts +51 -25
- package/src/panels/provider-accounts-panel.ts +33 -18
- package/src/panels/provider-health-domains.ts +45 -4
- package/src/panels/provider-health-panel.ts +5 -4
- package/src/panels/qr-panel.ts +1 -1
- package/src/panels/schedule-panel.ts +9 -17
- package/src/panels/security-panel.ts +8 -8
- package/src/panels/session-browser-panel.ts +10 -10
- package/src/panels/subscription-panel.ts +3 -3
- package/src/panels/system-messages-panel.ts +1 -1
- package/src/panels/tasks-panel.ts +20 -13
- package/src/panels/tool-inspector-panel.ts +19 -12
- package/src/panels/work-plan-panel.ts +5 -5
- package/src/planning/project-planning-coordinator.ts +1 -1
- package/src/provider-auth-route-display.ts +9 -0
- package/src/renderer/agent-workspace-style.ts +34 -0
- package/src/renderer/agent-workspace.ts +228 -52
- package/src/renderer/autocomplete-overlay.ts +25 -6
- package/src/renderer/bookmark-modal.ts +19 -4
- package/src/renderer/buffer.ts +4 -2
- package/src/renderer/context-inspector.ts +50 -13
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +19 -6
- package/src/renderer/help-overlay.ts +106 -33
- package/src/renderer/history-search-overlay.ts +19 -4
- package/src/renderer/live-tail-modal.ts +27 -5
- package/src/renderer/mcp-workspace.ts +164 -50
- package/src/renderer/model-picker-overlay.ts +58 -2
- package/src/renderer/model-workspace.ts +104 -20
- package/src/renderer/process-modal.ts +27 -6
- package/src/renderer/profile-picker-modal.ts +20 -4
- package/src/renderer/search-overlay.ts +25 -5
- package/src/renderer/selection-modal-overlay.ts +46 -14
- package/src/renderer/session-picker-modal.ts +18 -1
- package/src/renderer/settings-modal-helpers.ts +2 -40
- package/src/renderer/settings-modal.ts +83 -50
- package/src/renderer/tool-call.ts +20 -11
- package/src/runtime/agent-runtime-events.ts +6 -6
- package/src/runtime/bootstrap-command-context.ts +7 -1
- package/src/runtime/bootstrap-command-parts.ts +9 -7
- package/src/runtime/bootstrap-core.ts +19 -1
- package/src/runtime/bootstrap-hook-bridge.ts +7 -18
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +31 -22
- package/src/runtime/connected-host-auth.ts +7 -2
- package/src/runtime/diagnostics/panels/index.ts +2 -2
- package/src/runtime/diagnostics/panels/policy.ts +7 -7
- package/src/runtime/index.ts +2 -1
- package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
- package/src/runtime/onboarding/apply.ts +80 -79
- package/src/runtime/onboarding/derivation.ts +5 -8
- package/src/runtime/onboarding/snapshot.ts +0 -15
- package/src/runtime/onboarding/types.ts +8 -19
- package/src/runtime/onboarding/verify.ts +32 -10
- package/src/runtime/operator-token-cleanup.ts +1 -1
- package/src/runtime/services.ts +91 -22
- package/src/runtime/store/selectors/index.ts +3 -3
- package/src/runtime/store/state.ts +1 -4
- package/src/runtime/surface-feature-flags.ts +41 -6
- package/src/runtime/ui-read-models.ts +3 -4
- package/src/runtime/ui-services.ts +6 -6
- package/src/shell/blocking-input.ts +2 -1
- package/src/shell/ui-openers.ts +3 -13
- package/src/tools/agent-analysis-registry-policy.ts +0 -1
- package/src/tools/agent-channel-send-tool.ts +133 -0
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
- package/src/tools/agent-knowledge-tool.ts +170 -0
- package/src/tools/agent-local-registry-tool.ts +269 -69
- package/src/tools/agent-media-generate-tool.ts +133 -0
- package/src/tools/agent-notify-tool.ts +143 -0
- package/src/tools/agent-operator-action-tool.ts +137 -0
- package/src/tools/agent-operator-briefing-tool.ts +217 -0
- package/src/tools/agent-reminder-schedule-tool.ts +237 -0
- package/src/tools/agent-tool-policy-guard.ts +8 -8
- package/src/tools/agent-work-plan-tool.ts +256 -0
- package/src/version.ts +1 -1
- package/src/input/commands/policy-dispatch.ts +0 -339
- package/src/input/commands/policy.ts +0 -13
- package/src/panels/panel-picker.ts +0 -105
- package/src/panels/policy-panel.ts +0 -308
- 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' ? '' :
|
|
47
|
-
` groups
|
|
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' ? '' :
|
|
76
|
-
` events
|
|
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
|
|
83
|
-
if (!summary?.openPanels || summary.openPanels.length === 0) return
|
|
84
|
-
ctx.print(` Saved panel state ignored: ${summary.openPanels.slice(0, 4).join(', ')}.
|
|
85
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
126
|
-
const 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
|
-
`
|
|
149
|
-
`
|
|
150
|
-
`
|
|
151
|
-
`
|
|
152
|
-
`
|
|
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
|
|
173
|
-
session.returnContext.blockedTasks ? `blocked
|
|
174
|
-
session.returnContext.pendingApprovals ? `approvals
|
|
175
|
-
session.returnContext.openPanels?.length ? `saved
|
|
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
|
|
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
|
-
|
|
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(
|
|
227
|
+
ctx.print([
|
|
228
|
+
'Session renamed',
|
|
229
|
+
` name ${newName}`,
|
|
230
|
+
].join('\n'));
|
|
204
231
|
ctx.renderRequest();
|
|
205
232
|
} catch (e) {
|
|
206
|
-
ctx.print(
|
|
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
|
|
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
|
|
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(
|
|
247
|
-
|
|
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
|
|
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
|
|
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
|
|
297
|
+
ctx.print(` Assist ${assisted.assistedNarrative}`);
|
|
268
298
|
ctx.renderRequest();
|
|
269
299
|
});
|
|
270
300
|
}
|
|
271
301
|
}
|
|
272
302
|
} catch (e) {
|
|
273
|
-
ctx.print(
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
334
|
+
ctx.print(formatSessionFailure('fork session', e));
|
|
300
335
|
}
|
|
301
336
|
return true;
|
|
302
337
|
}
|
|
303
338
|
|
|
304
339
|
if (sub === 'save') {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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:
|
|
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
|
-
|
|
320
|
-
|
|
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(
|
|
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
|
|
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
|
|
338
|
-
`
|
|
339
|
-
`
|
|
340
|
-
`
|
|
341
|
-
`
|
|
342
|
-
`
|
|
343
|
-
`
|
|
344
|
-
...(found.returnContext ?
|
|
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
|
|
399
|
+
printSessionExport(ctx, sessionId, meta.title, readConversationMessageSnapshots(messages), format);
|
|
363
400
|
} catch {
|
|
364
|
-
ctx.print(`Session not found
|
|
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
|
|
408
|
+
printSessionExport(ctx, loadName, meta.title, readConversationMessageSnapshots(messages), format);
|
|
372
409
|
} catch (e) {
|
|
373
|
-
ctx.print(
|
|
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
|
|
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' : ''})
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
*
|
|
6
|
-
*
|
|
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 {
|
|
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
|
/**
|