@pellux/goodvibes-agent 1.4.1 → 1.4.3

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.
@@ -7,51 +7,27 @@ export const AGENT_WORKSPACE_HOST_CATEGORY: AgentWorkspaceCategory = {
7
7
  summary: 'Connected-host health, tasks, sessions, channels, and automation.',
8
8
  detail: 'Use this workspace to inspect the GoodVibes host surfaces that Agent can see: system health, remote routes, host tasks, sessions, channels, schedules, knowledge, media, MCP, provider auth, support bundles, and telemetry/config posture.',
9
9
  actions: [
10
- { id: 'host-overview', label: 'Host overview', detail: 'Review connected-host, provider, settings, continuity, and Agent health without starting or mutating host work.', command: '/health review', kind: 'command', safety: 'read-only' },
11
- { id: 'host-services', label: 'Host services', detail: 'Inspect connected-host service readiness, credentials, and integration issues without changing them.', command: '/health host', kind: 'command', safety: 'read-only' },
12
- { id: 'host-setup', label: 'Setup review', detail: 'Inspect setup issues that affect connected-host and Agent runtime readiness.', command: '/health setup', kind: 'command', safety: 'read-only' },
13
- { id: 'host-remote', label: 'Remote routes', detail: 'Inspect remote build-host route health and recovery signals without starting build work.', command: '/health remote', kind: 'command', safety: 'read-only' },
14
- { id: 'host-maintenance', label: 'Session maintenance', detail: 'Review continuity, compaction, and current-session maintenance posture.', command: '/health maintenance', kind: 'command', safety: 'read-only' },
15
- { id: 'host-continuity', label: 'Continuity', detail: 'Inspect last-session pointer, recovery-file posture, and return-context state.', command: '/health continuity', kind: 'command', safety: 'read-only' },
16
- { id: 'host-tasks', label: 'Host tasks', detail: 'Inspect connected-host task state without creating, retrying, or mutating tasks.', command: '/tasks list', kind: 'command', safety: 'read-only' },
17
10
  { id: 'host-task-filter', label: 'Filter host tasks', detail: 'Open a status/type form for read-only connected-host task filtering.', editorKind: 'task-list-filter', kind: 'editor', safety: 'read-only' },
18
11
  { id: 'host-task-show', label: 'Inspect host task', detail: 'Open a task-id form for read-only connected-host task metadata.', editorKind: 'task-show', kind: 'editor', safety: 'read-only' },
19
12
  { id: 'host-task-output', label: 'Show task output', detail: 'Open a task-id form for read-only connected-host task output.', editorKind: 'task-output', kind: 'editor', safety: 'read-only' },
20
- { id: 'host-sessions', label: 'Host session routes', detail: 'Browse saved Agent sessions and return-context posture exposed through the runtime session surface.', command: '/session list', kind: 'command', safety: 'read-only' },
21
13
  { id: 'host-session-graph', label: 'Session graph', detail: 'Open a read-only form for cross-session graph inspection; graph mutation remains blocked in Agent.', editorKind: 'session-graph', kind: 'editor', safety: 'read-only' },
22
- { id: 'host-channels-status', label: 'Channel status', detail: 'Inspect connected channel runtime status from the host without mutating delivery state.', command: '/channels status', kind: 'command', safety: 'read-only' },
23
- { id: 'host-channel-accounts', label: 'Channel accounts', detail: 'Inspect connected channel accounts without printing secret values or sending messages.', command: '/channels accounts', kind: 'command', safety: 'read-only' },
24
- { id: 'host-channel-policies', label: 'Channel policies', detail: 'Inspect channel delivery policy posture without changing routing.', command: '/channels policies', kind: 'command', safety: 'read-only' },
25
14
  { id: 'host-channels', label: 'Open Channels', detail: 'Jump to the channel setup and delivery workspace.', targetCategoryId: 'channels', kind: 'workspace', safety: 'safe' },
26
15
  { id: 'host-automation', label: 'Open Automation', detail: 'Jump to connected schedules, reminders, receipts, and explicit run controls.', targetCategoryId: 'automation', kind: 'workspace', safety: 'safe' },
27
- { id: 'host-schedules', label: 'Schedule status', detail: 'Inspect schedules and run history without running or mutating them.', command: '/schedule list', kind: 'command', safety: 'read-only' },
28
- { id: 'host-schedule-reconcile', label: 'Reconcile schedules', detail: 'Compare local promotion receipts with live connected schedules.', command: '/schedule reconcile', kind: 'command', safety: 'read-only' },
29
- { id: 'host-knowledge', label: 'Knowledge route', detail: 'Inspect isolated Agent Knowledge route readiness and source counts.', command: '/knowledge status', kind: 'command', safety: 'read-only' },
30
16
  { id: 'host-knowledge-open', label: 'Open Knowledge', detail: 'Jump to Agent Knowledge source, graph, review, and ingest controls.', targetCategoryId: 'knowledge', kind: 'workspace', safety: 'safe' },
31
- { id: 'host-media', label: 'Media providers', detail: 'Inspect configured media providers before generating media with confirmation.', command: '/media providers', kind: 'command', safety: 'read-only' },
32
17
  { id: 'host-media-open', label: 'Open Voice and Media', detail: 'Jump to voice, TTS, image input, media provider, and browser-tool setup actions.', targetCategoryId: 'voice-media', kind: 'workspace', safety: 'safe' },
33
- { id: 'host-mcp-health', label: 'MCP health', detail: 'Inspect MCP lifecycle issues without changing trust posture or approving tool-definition review.', command: '/health mcp', kind: 'command', safety: 'read-only' },
34
- { id: 'host-mcp-review', label: 'MCP review', detail: 'Inspect MCP server connection, trust, role, and review posture.', command: '/mcp review', kind: 'command', safety: 'read-only' },
35
18
  { id: 'host-tools-open', label: 'Open Tools and MCP', detail: 'Jump to MCP server setup, tool inventory, secrets, and security review.', targetCategoryId: 'tools', kind: 'workspace', safety: 'safe' },
36
- { id: 'host-provider-accounts', label: 'Provider accounts', detail: 'Review provider account routes, subscription windows, and billing-path safety.', command: '/accounts review', kind: 'command', safety: 'read-only' },
37
19
  { id: 'host-provider-detail', label: 'Provider detail', detail: 'Open a provider-id form for account and provider configuration review.', editorKind: 'provider-inspect', kind: 'editor', safety: 'read-only' },
38
20
  { id: 'host-provider-routes', label: 'Provider routes', detail: 'Open a provider-id form for account, subscription, and route inspection.', editorKind: 'provider-routes', kind: 'editor', safety: 'read-only' },
39
21
  { id: 'host-provider-repair', label: 'Provider repair guidance', detail: 'Open a provider-id form for read-only provider repair guidance.', editorKind: 'provider-account-repair', kind: 'editor', safety: 'read-only' },
40
- { id: 'host-auth-owner', label: 'Connected-host auth owner', detail: 'Show connected-host auth ownership, local companion-token posture, and the confirmed Agent setup route that can create or repair its own operator token without printing it.', command: '/auth local', kind: 'command', safety: 'read-only' },
41
- { id: 'host-auth-review', label: 'Provider auth review', detail: 'Review provider auth posture without printing token values.', command: '/auth review', kind: 'command', safety: 'read-only' },
42
22
  { id: 'host-auth-detail', label: 'Provider auth detail', detail: 'Open a provider-id form for read-only provider auth inspection.', editorKind: 'auth-show', kind: 'editor', safety: 'read-only' },
43
23
  { id: 'host-auth-repair', label: 'Provider auth repair', detail: 'Open a provider-id form for provider auth repair guidance.', editorKind: 'auth-repair', kind: 'editor', safety: 'read-only' },
44
24
  { id: 'host-auth-bundle-export', label: 'Export auth bundle', detail: 'Open a confirmed form that exports a redacted provider auth review bundle.', editorKind: 'auth-bundle-export', kind: 'editor', safety: 'safe' },
45
25
  { id: 'host-auth-bundle-inspect', label: 'Inspect auth bundle', detail: 'Open a form that inspects a provider auth review bundle before sharing or import.', editorKind: 'auth-bundle-inspect', kind: 'editor', safety: 'read-only' },
46
26
  { id: 'host-subscription-bundle-export', label: 'Export subscription bundle', detail: 'Open a confirmed form that exports redacted provider subscription state for review.', editorKind: 'subscription-bundle-export', kind: 'editor', safety: 'safe' },
47
27
  { id: 'host-subscription-bundle-inspect', label: 'Inspect subscription bundle', detail: 'Open a form that inspects provider subscription state before sharing.', editorKind: 'subscription-bundle-inspect', kind: 'editor', safety: 'read-only' },
48
- { id: 'host-security', label: 'Security review', detail: 'Inspect token posture, MCP attack paths, policy lint, plugin risk, and incident pressure.', command: '/security review', kind: 'command', safety: 'read-only' },
49
- { id: 'host-trust', label: 'Trust review', detail: 'Review permission, secret, plugin, and MCP trust posture without exporting bundles or changing trust.', command: '/trust review', kind: 'command', safety: 'read-only' },
50
28
  { id: 'host-support-bundle-export', label: 'Export support bundle', detail: 'Export a redacted Agent support bundle from the Host page.', editorKind: 'support-bundle-export', kind: 'editor', safety: 'safe' },
51
29
  { id: 'host-support-bundle-inspect', label: 'Inspect support bundle', detail: 'Inspect a support bundle before import or sharing.', editorKind: 'support-bundle-inspect', kind: 'editor', safety: 'read-only' },
52
30
  { id: 'host-support-bundle-import', label: 'Import support bundle', detail: 'Import reviewed, non-redacted config values from a support bundle.', editorKind: 'support-bundle-import', kind: 'editor', safety: 'safe' },
53
- { id: 'host-telemetry', label: 'Telemetry settings', detail: 'Open telemetry payload policy settings for connected-host and Agent observability review.', command: '/config telemetry', kind: 'command', safety: 'safe' },
54
- { id: 'host-config', label: 'Control-plane settings', detail: 'Open configuration controls for service, channels, tools, automation, provider, and storage posture.', command: '/config', kind: 'command', safety: 'safe' },
55
31
  { id: 'host-safety', label: 'Host mutation policy', detail: 'Agent may inspect connected-host surfaces and run explicit confirmed actions, but does not silently create tasks, send channel messages, mutate auth, or start automation.', kind: 'guidance', safety: 'blocked' },
56
32
  ],
57
33
  };
@@ -6,7 +6,6 @@ import { knowledgeCommand } from './commands/knowledge.ts';
6
6
  import { registerShellCoreCommands } from './commands/shell-core.ts';
7
7
  import { registerConfigCommand } from './commands/config.ts';
8
8
  import { registerSessionWorkflowCommands } from './commands/session-workflow.ts';
9
- import { registerPlanningRuntimeCommands } from './commands/planning-runtime.ts';
10
9
  import { registerScheduleRuntimeCommands } from './commands/schedule-runtime.ts';
11
10
  import { registerOperatorRuntimeCommands } from './commands/operator-runtime.ts';
12
11
  import { registerNotifyRuntimeCommands } from './commands/notify-runtime.ts';
@@ -27,7 +26,6 @@ import { registerConversationRuntimeCommands } from './commands/conversation-run
27
26
  import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
28
27
  import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
29
28
  import { registerTtsRuntimeCommands } from './commands/tts-runtime.ts';
30
- import { registerWorkPlanRuntimeCommands } from './commands/work-plan-runtime.ts';
31
29
  import { registerAgentWorkspaceRuntimeCommands } from './commands/agent-workspace-runtime.ts';
32
30
  import { registerAgentRuntimeProfileRuntimeCommands } from './commands/agent-runtime-profile-runtime.ts';
33
31
  import { registerDelegationRuntimeCommands } from './commands/delegation-runtime.ts';
@@ -90,10 +88,8 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
90
88
  registerQrcodeRuntimeCommands(registry);
91
89
  registerOnboardingRuntimeCommands(registry);
92
90
  registerTtsRuntimeCommands(registry);
93
- registerWorkPlanRuntimeCommands(registry);
94
91
  registerLocalRuntimeCommands(registry);
95
92
  registerSessionWorkflowCommands(registry);
96
- registerPlanningRuntimeCommands(registry);
97
93
  registerScheduleRuntimeCommands(registry);
98
94
  registerSessionContentCommands(registry);
99
95
  registerAgentMemoryCommand(registry);
@@ -397,7 +397,10 @@ export class InputHandler {
397
397
  }
398
398
 
399
399
  public dispatchAgentWorkspaceCommand(command: string, context: CommandContext, behavior?: 'inline' | 'compose' | 'exit'): void {
400
- const resolved = behavior ?? 'compose';
400
+ // Default to 'inline' so editor submissions stay inside the workspace modal.
401
+ // Closing the modal mid-flow (the old 'compose' default) traps users in the
402
+ // wrong context and was the primary symptom of the broken onboarding flow.
403
+ const resolved = behavior ?? 'inline';
401
404
  const { name, args } = parseSlashCommand(command);
402
405
  if (!name) return;
403
406
  if (resolved === 'exit') {
package/src/main.ts CHANGED
@@ -54,7 +54,6 @@ import { applyInitialTuiCliState, formatFatalStartupErrorForLog, formatFatalStar
54
54
  import { wireSpokenTurnRuntime } from './audio/spoken-turn-wiring.ts';
55
55
  import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './audio/spoken-turn-model-routing.ts';
56
56
  import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/terminal-output-guard.ts';
57
- import { ProjectPlanningCoordinator } from './planning/project-planning-coordinator.ts';
58
57
  import { buildCommandArgsHint } from './input/command-args-hint.ts';
59
58
  import { GOODVIBES_AGENT_PAIRING_SURFACE } from './config/surface.ts';
60
59
 
@@ -262,18 +261,6 @@ async function main() {
262
261
  configManager,
263
262
  notify: (message) => { systemMessageRouter.high(message); render(); },
264
263
  }));
265
- const projectPlanningCoordinator = new ProjectPlanningCoordinator({
266
- service: ctx.services.projectPlanningService,
267
- projectId: ctx.services.projectPlanningProjectId,
268
- workingDirectory: workingDir,
269
- notify: (message) => { systemMessageRouter.high(message); render(); },
270
- openPanel: () => {
271
- panelManager.open('project-planning');
272
- panelManager.show();
273
- render();
274
- },
275
- });
276
-
277
264
  const submitInput = (text: string, content?: ContentPart[], options: { readonly spokenOutput?: boolean } = {}) => {
278
265
  input.clearModalStack();
279
266
  scrollLocked = true; // Re-lock on user input
@@ -308,33 +295,7 @@ async function main() {
308
295
  }
309
296
  if (processedText || content) {
310
297
  void (async () => {
311
- let inputOptions = options.spokenOutput ? createSpokenTurnInputOptions() : undefined;
312
- if (!options.spokenOutput && processedText) {
313
- try {
314
- const planning = await projectPlanningCoordinator.prepareTurn(processedText);
315
- if (planning) {
316
- if (planning.handledLocally) {
317
- systemMessageRouter.high(planning.statusMessage);
318
- render();
319
- return;
320
- }
321
- conversation.addSystemMessage(planning.systemMessage);
322
- inputOptions = {
323
- origin: {
324
- source: 'project-planning',
325
- surface: GOODVIBES_AGENT_PAIRING_SURFACE,
326
- metadata: {
327
- projectId: ctx.services.projectPlanningProjectId,
328
- knowledgeSpaceId: planning.state.knowledgeSpaceId,
329
- readiness: planning.evaluation.readiness,
330
- },
331
- },
332
- };
333
- }
334
- } catch (err) {
335
- systemMessageRouter.high(`[Planning] ${summarizeError(err)}`);
336
- }
337
- }
298
+ const inputOptions = options.spokenOutput ? createSpokenTurnInputOptions() : undefined;
338
299
  if (options.spokenOutput && processedText) {
339
300
  spokenTurns.submitNextTurn(processedText);
340
301
  }
@@ -3,8 +3,6 @@ import { ContextVisualizerPanel } from '../context-visualizer-panel.ts';
3
3
  import { ThinkingPanel } from '../thinking-panel.ts';
4
4
  import { ToolInspectorPanel } from '../tool-inspector-panel.ts';
5
5
  import { SchedulePanel } from '../schedule-panel.ts';
6
- import { ProjectPlanningPanel } from '../project-planning-panel.ts';
7
- import { WorkPlanPanel } from '../work-plan-panel.ts';
8
6
  import type { ResolvedBuiltinPanelDeps } from './shared.ts';
9
7
  import { requireAutomationManager, requireUiServices } from './shared.ts';
10
8
 
@@ -49,32 +47,6 @@ export function registerAgentPanels(manager: PanelManager, deps: ResolvedBuiltin
49
47
  ),
50
48
  });
51
49
 
52
- manager.registerType({
53
- id: 'work-plan',
54
- name: 'Work Plan',
55
- icon: 'L',
56
- category: 'agent',
57
- description: 'Persistent workspace checklist for multi-step work and cross-session task tracking',
58
- preload: true,
59
- factory: () => new WorkPlanPanel(deps.workPlanStore),
60
- });
61
-
62
- manager.registerType({
63
- id: 'project-planning',
64
- name: 'Planning',
65
- icon: 'P',
66
- category: 'agent',
67
- description: 'Passive project planning artifacts: readiness, questions, decisions, language, task graph, and agent handoff metadata',
68
- preload: true,
69
- factory: () => new ProjectPlanningPanel({
70
- service: deps.projectPlanningService,
71
- projectId: deps.projectPlanningProjectId,
72
- requestRender: deps.requestRender,
73
- submitAnswer: deps.submitPlanningAnswer,
74
- dismissPlanning: deps.dismissPlanning,
75
- }),
76
- });
77
-
78
50
  manager.registerType({
79
51
  id: 'schedule',
80
52
  name: 'Schedule',
@@ -392,35 +392,36 @@ function onboardingActionColumns(workspace: AgentWorkspace, action: AgentWorkspa
392
392
  currentValue: '(unknown)',
393
393
  };
394
394
  }
395
+ // Non-setting rows keep the 3-column layout but use placeholders for Default and Current,
396
+ // since those concepts only make sense for actual settings. The action label carries
397
+ // the meaning of the row.
395
398
  return {
396
- setting: onboardingActionLabel(workspace, action),
397
- defaultValue: '',
398
- currentValue: '',
399
+ setting: action.label,
400
+ defaultValue: '',
401
+ currentValue: '',
399
402
  };
400
403
  }
401
404
 
402
405
  function actionChange(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction): string {
403
- return isOnboardingCategory(category) ? onboardingActionColumns(workspace, action).setting : actionCommand(action);
406
+ // Always show the action label via onboardingActionColumns for consistency across categories.
407
+ return onboardingActionColumns(workspace, action).setting;
404
408
  }
405
409
 
406
410
  function actionMetaLine(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction): ContextLine {
407
- const onboarding = isOnboardingCategory(category);
408
- if (onboarding) {
409
- return {
410
- text: `About: ${compactText(action.detail, 100)}`,
411
- fg: action.safety === 'blocked' ? PALETTE.warn : PALETTE.muted,
412
- };
413
- }
411
+ // Same About-style detail line on every category.
414
412
  return {
415
- text: `Does: ${actionChange(workspace, category, action)}`,
416
- fg: action.safety === 'blocked' ? PALETTE.warn : action.kind === 'command' ? PALETTE.info : PALETTE.muted,
413
+ text: `About: ${compactText(action.detail, 100)}`,
414
+ fg: action.safety === 'blocked' ? PALETTE.warn : PALETTE.muted,
417
415
  };
418
416
  }
419
417
 
420
418
  function shouldRenderOnboardingSettingsTable(actions: readonly AgentWorkspaceAction[]): boolean {
421
- // Exclude the synthetic finish footer row it is not a setting and must not block table mode.
419
+ // Always use the Setting/Default/Current 3-column layout on ONBOARDING pages so the
420
+ // user gets a consistent visual structure across every category, even ones that mix
421
+ // settings with editors, guidance, or pickers. Non-setting rows fill Default/Current
422
+ // with placeholders via onboardingActionColumns().
422
423
  const nonFinish = actions.filter((a) => a.kind !== 'onboarding-complete');
423
- return nonFinish.length > 0 && nonFinish.every((action) => action.kind === 'setting');
424
+ return nonFinish.length > 0;
424
425
  }
425
426
 
426
427
  function editorContextLines(editor: AgentWorkspaceLocalEditor, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
@@ -569,8 +570,9 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
569
570
  if (workspace.localEditor) return buildEditorRows(workspace.localEditor, width, height);
570
571
  const category = workspace.selectedActionCategory;
571
572
  const onboarding = isOnboardingCategory(category);
572
- const settingTable = onboarding
573
- && !workspace.actionSearchActive
573
+ // Apply the consistent Setting/Default/Current 3-column layout to EVERY category, not
574
+ // just the ONBOARDING group. Different layouts across pages were confusing.
575
+ const settingTable = !workspace.actionSearchActive
574
576
  && shouldRenderOnboardingSettingsTable(workspace.actions);
575
577
  const rows: WorkspaceRow[] = [];
576
578
  const valueWidth = settingTable
@@ -22,7 +22,6 @@ const FEATURED_HELP_COMMANDS: Array<[name: string, argHint: string, desc: string
22
22
  ['personas', '', 'Manage serial Agent operating personas'],
23
23
  ['skills', '', 'Manage Agent-local skills and bundles'],
24
24
  ['routines', '', 'Manage reusable main-conversation routines'],
25
- ['workplan', '', 'Inspect shared work-plan state'],
26
25
  ['approval', '', 'Review and explicitly act on approvals'],
27
26
  ['automation', '', 'Run confirmed connected-host automation actions'],
28
27
  ['schedule', 'remind', 'Create confirmed reminders or inspect schedules'],
@@ -202,7 +201,6 @@ export function renderHelpOverlay(
202
201
  'personas',
203
202
  'skills',
204
203
  'routines',
205
- 'workplan',
206
204
  'approval',
207
205
  'schedule',
208
206
  'delegate',
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '1.4.1';
9
+ let _version = '1.4.3';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
12
12
  readonly version?: unknown;
@@ -1,81 +0,0 @@
1
- # Project Planning
2
-
3
- GoodVibes Agent owns the active planning loop for operator work. Planning is part of the Agent main conversation, not a hidden execution flow and not a default knowledge query.
4
-
5
- ## Boundary
6
-
7
- The Agent owns:
8
-
9
- - natural-language planning intent detection in the main terminal conversation;
10
- - one-question-at-a-time clarification;
11
- - the Agent-owned planning loop;
12
- - transcript-first planning summaries and the Agent operator workspace planning state;
13
- - explicit execution approval;
14
- - delegation metadata when work needs GoodVibes TUI.
15
-
16
- The connected-host runtime owns durable storage and route contracts:
17
-
18
- - planning namespaces such as `project:<projectId>`;
19
- - readiness evaluation and next-question hints;
20
- - decision records;
21
- - task, dependency, verification, and assignment metadata;
22
- - read-only public operator methods.
23
-
24
- Other surfaces can store or inspect planning artifacts, but conversation control stays in Agent. Agent planning state is not default knowledge, another product segment, or arbitrary knowledge data.
25
-
26
- ## Agent Behavior
27
-
28
- The Agent derives a stable `projectId` from the workspace path and passes it to the connected-host planning service. Planning artifacts are stored under the matching planning namespace so unrelated workspaces do not share state.
29
-
30
- Normal conversation can start planning when the user asks for an execution strategy, dependency graph, verification gates, or delegation handoff. The Agent then:
31
-
32
- - prints a concise planning summary in the main transcript;
33
- - persists the current planning state through public connected-host runtime routes;
34
- - records active open questions and user answers;
35
- - calls readiness evaluation for gaps and the suggested next question;
36
- - asks one focused question instead of executing prematurely.
37
-
38
- The planning loop can be paused with natural language such as "stop planning" or "pause planning".
39
-
40
- ## Planning View
41
-
42
- The planning view is transcript-first. It shows:
43
-
44
- - workspace project id and planning namespace;
45
- - readiness and approval state;
46
- - goal, scope, known context, and current next question;
47
- - blocking or advisory readiness gaps;
48
- - task graph and verification gates;
49
- - delegation candidates;
50
- - durable decisions;
51
- - project language and ambiguity resolutions.
52
-
53
- Keyboard behavior stays in the main Agent TUI: predictable prompt editing, scroll, submit, dismiss, and return-to-chat behavior.
54
-
55
- ## `/plan`
56
-
57
- `/plan` remains a command workspace for inspection and seeding:
58
-
59
- - `/plan` prints current planning readiness and the next planning question;
60
- - `/plan panel` is guidance-only in Agent and points back to `/plan status` or `/plan list`;
61
- - `/plan approve` records explicit execution approval;
62
- - `/plan <goal>` seeds Agent workspace planning state;
63
- - `/plan list` and `/plan show <id>` inspect older execution-plan records.
64
-
65
- Use natural language such as "pause planning" or "stop planning" when planning should pause and normal chat should continue.
66
-
67
- ## Work Plan
68
-
69
- The work-plan tracker is for concrete, durable task state after work becomes actionable. It is separate from the planning interview loop.
70
-
71
- Use `/workplan` when the work already has tasks and needs status tracking. Use `/delegate` when explicit build/fix/review work should go to GoodVibes TUI; include the original ask, delegation reason, success evidence, workspace/worktree hint, priority, and explicit review intent.
72
-
73
- The model can keep the same visible work plan current with `agent_work_plan`. Approved items can be dispatched through confirmed `agent_work_plan action:"dispatch_agents"` into visible `agent` spawn or batch-spawn calls, with linked-agent receipts written back to the plan and post-dispatch next routes for orchestration, work-plan detail, agent inspect/wait/message/cancel, and closeout. Those linked receipts and matching remote-runner artifacts appear in `agent_harness mode:"agent_orchestration"` closeout cards with exact work-plan and artifact review routes. It can also inspect planning/work-plan visibility through `agent_harness` workspace and panel metadata, and inspect explicit build-delegation posture with `delegation action:"status|routes|route"`; destructive work-plan changes, agent dispatch, and delegated submissions still require explicit user request and confirmation.
74
-
75
- ## Agent Knowledge Boundary
76
-
77
- Planning may link to Agent Knowledge evidence, but it must not query or ingest through default knowledge. Source-backed facts for Agent belong under:
78
-
79
- ```text
80
- /api/goodvibes-agent/knowledge/*
81
- ```
@@ -1,215 +0,0 @@
1
- import type {
2
- ProjectPlanningEvaluation,
3
- ProjectPlanningQuestion,
4
- ProjectPlanningService,
5
- ProjectPlanningState,
6
- } from '@pellux/goodvibes-sdk/platform/knowledge';
7
- import type { CommandRegistry } from '../command-registry.ts';
8
- import { requirePlanManager, requireSessionLineageTracker } from './runtime-services.ts';
9
- import { requireYesFlag, stripYesFlag } from './confirmation.ts';
10
-
11
- function recordNextQuestion(
12
- state: Partial<ProjectPlanningState>,
13
- question: ProjectPlanningQuestion | undefined,
14
- ): Partial<ProjectPlanningState> {
15
- if (!question) return state;
16
- const answered = new Set((state.answeredQuestions ?? []).map((entry) => entry.id));
17
- if (answered.has(question.id)) return state;
18
- const openQuestions = [...(state.openQuestions ?? [])];
19
- const existingIndex = openQuestions.findIndex((entry) => entry.id === question.id);
20
- const normalized = { ...question, status: question.status ?? 'open' } satisfies ProjectPlanningQuestion;
21
- if (existingIndex >= 0) openQuestions[existingIndex] = normalized;
22
- else openQuestions.unshift(normalized);
23
- return { ...state, openQuestions };
24
- }
25
-
26
- async function persistEvaluatedNextQuestion(
27
- service: ProjectPlanningService,
28
- projectId: string,
29
- state: ProjectPlanningState,
30
- evaluation: ProjectPlanningEvaluation,
31
- ): Promise<{ state: ProjectPlanningState; evaluation: ProjectPlanningEvaluation }> {
32
- if (!evaluation.nextQuestion) return { state, evaluation };
33
- if (state.openQuestions.some((question) => question.id === evaluation.nextQuestion?.id)) {
34
- return { state, evaluation };
35
- }
36
- const withQuestion = recordNextQuestion(evaluation.state ?? state, evaluation.nextQuestion);
37
- const saved = await service.upsertState({ projectId, state: withQuestion });
38
- const nextState = saved.state ?? state;
39
- const nextEvaluation = await service.evaluate({ projectId, state: nextState });
40
- return { state: nextState, evaluation: nextEvaluation };
41
- }
42
-
43
- function formatNextQuestion(question: ProjectPlanningQuestion | undefined): string {
44
- if (!question) return 'No next question recorded.';
45
- const lines = [`Next question: ${question.prompt}`];
46
- if (question.recommendedAnswer) lines.push(`Recommended answer: ${question.recommendedAnswer}`);
47
- lines.push('Answer in the main prompt or review planning state with /plan status.');
48
- return lines.join('\n');
49
- }
50
-
51
- export function registerPlanningRuntimeCommands(registry: CommandRegistry): void {
52
- registry.register({
53
- name: 'plan',
54
- description: 'Inspect or seed Agent workspace planning state',
55
- usage: '[approve --yes | list | show <id> | mode | explain | override <strategy> --yes | status | clear --yes | <planning goal>]',
56
- argsHint: '[approve|status|list|<goal>]',
57
- async handler(args, ctx) {
58
- const planManager = requirePlanManager(ctx);
59
- const sessionLineageTracker = requireSessionLineageTracker(ctx);
60
- const plannerSubs = ['mode', 'explain', 'override', 'status', 'clear'];
61
- if (args.length > 0 && plannerSubs.includes(args[0].toLowerCase())) {
62
- const parsed = stripYesFlag(args);
63
- const subcommand = parsed.rest[0]?.toLowerCase() ?? '';
64
- if ((subcommand === 'override' || subcommand === 'clear') && !parsed.yes) {
65
- requireYesFlag(ctx, `${subcommand} planner state`, `/plan ${subcommand}${subcommand === 'override' ? ' <strategy>' : ''} --yes`);
66
- return;
67
- }
68
- const result = ctx.ops.planRuntime
69
- ? ctx.ops.planRuntime(parsed.rest[0] ?? args[0], parsed.rest.slice(1))
70
- : { ok: false, output: 'Plan runtime route is not available in this runtime.' };
71
- ctx.print(result.output);
72
- return;
73
- }
74
-
75
- const projectPlanningService = ctx.workspace.projectPlanningService;
76
- const projectId = ctx.workspace.projectPlanningProjectId;
77
-
78
- if (args.length === 0) {
79
- if (projectPlanningService && projectId) {
80
- const [status, stateResult] = await Promise.all([
81
- projectPlanningService.status({ projectId }),
82
- projectPlanningService.getState({ projectId }),
83
- ]);
84
- const initialEvaluation = await projectPlanningService.evaluate({
85
- projectId,
86
- ...(stateResult.state ? { state: stateResult.state } : {}),
87
- });
88
- const { evaluation } = stateResult.state
89
- ? await persistEvaluatedNextQuestion(projectPlanningService, projectId, stateResult.state, initialEvaluation)
90
- : { evaluation: initialEvaluation };
91
- const planningNamespace = String(
92
- status[['knowledge', 'SpaceId'].join('') as keyof typeof status] ?? `project:${status.projectId}`,
93
- );
94
- ctx.print(
95
- `Project planning: ${evaluation.readiness}\n` +
96
- `Project: ${status.projectId}\n` +
97
- `Planning namespace: ${planningNamespace}\n` +
98
- `Artifacts: ${status.counts.states} state, ${status.counts.decisions} decisions, ${status.counts.languageArtifacts} language\n` +
99
- formatNextQuestion(evaluation.nextQuestion),
100
- );
101
- return;
102
- }
103
- const active = planManager.getActive(ctx.session.runtime.sessionId);
104
- if (!active) {
105
- ctx.print('No active execution plan.');
106
- return;
107
- }
108
- const summary = planManager.getSummary(active);
109
- ctx.print(`Active plan: "${active.title}" [${active.status.toUpperCase()}]\n${summary}`);
110
- return;
111
- }
112
-
113
- if (args[0] === 'panel') {
114
- ctx.print('Use /plan status or /plan list for compact command output. Open Agent Workspace -> Work -> Planning status or Saved plans for the workspace view.');
115
- return;
116
- }
117
-
118
- if (args[0] === 'approve') {
119
- const parsed = stripYesFlag(args);
120
- if (!parsed.yes) {
121
- requireYesFlag(ctx, 'approve project planning state for execution', '/plan approve --yes');
122
- return;
123
- }
124
- if (!projectPlanningService || !projectId) {
125
- ctx.print('Project planning service is not available in this runtime.');
126
- return;
127
- }
128
- const current = await projectPlanningService.getState({ projectId });
129
- if (!current.state) {
130
- ctx.print('No project planning state exists to approve.');
131
- return;
132
- }
133
- const result = await projectPlanningService.upsertState({
134
- projectId,
135
- state: {
136
- ...current.state,
137
- executionApproved: true,
138
- metadata: {
139
- ...(current.state.metadata ?? {}),
140
- approvedFrom: 'plan-command',
141
- approvedAt: Date.now(),
142
- },
143
- },
144
- });
145
- const evaluation = await projectPlanningService.evaluate({ projectId });
146
- ctx.print(`Project planning approved. Readiness: ${evaluation.readiness}. State: ${result.state?.id ?? 'current'}.`);
147
- return;
148
- }
149
-
150
- if (args[0] === 'list') {
151
- const plans = planManager.list();
152
- if (plans.length === 0) {
153
- ctx.print('No plans found.');
154
- return;
155
- }
156
- ctx.print(`Plans (${plans.length}):\n${plans.map((plan) => {
157
- const marker = plan.status === 'active' ? '▶' : ' ';
158
- return ` ${marker} ${plan.id.slice(0, 8)} [${plan.status.padEnd(8)}] ${plan.title}`;
159
- }).join('\n')}`);
160
- return;
161
- }
162
-
163
- if (args[0] === 'show') {
164
- const id = args[1];
165
- if (!id) {
166
- ctx.print('Usage: /plan show <plan-id>');
167
- return;
168
- }
169
- const plans = planManager.list();
170
- const plan = plans.find((entry) => entry.id === id || entry.id.startsWith(id));
171
- if (!plan) {
172
- ctx.print(`Plan not found: ${id}`);
173
- return;
174
- }
175
- ctx.print(planManager.toMarkdown(plan));
176
- return;
177
- }
178
-
179
- const taskDescription = args.join(' ');
180
- if (!projectPlanningService || !projectId) {
181
- ctx.print('Project planning service is not available in this runtime.');
182
- return;
183
- }
184
- const result = await projectPlanningService.upsertState({
185
- projectId,
186
- state: {
187
- goal: taskDescription,
188
- knownContext: [
189
- `Workspace planning was seeded from the Agent /plan command.`,
190
- ],
191
- metadata: {
192
- active: true,
193
- owner: 'agent',
194
- source: 'plan-command',
195
- lastPromptAt: Date.now(),
196
- },
197
- },
198
- });
199
- const initialEvaluation = await projectPlanningService.evaluate({
200
- projectId,
201
- ...(result.state ? { state: result.state } : {}),
202
- });
203
- const { state, evaluation } = result.state
204
- ? await persistEvaluatedNextQuestion(projectPlanningService, projectId, result.state, initialEvaluation)
205
- : { state: result.state, evaluation: initialEvaluation };
206
- sessionLineageTracker.setOriginalTask(taskDescription.slice(0, 200));
207
-
208
- ctx.print(
209
- `Project planning seeded: "${state?.goal ?? taskDescription}"\n` +
210
- `Readiness: ${evaluation.readiness}\n` +
211
- formatNextQuestion(evaluation.nextQuestion),
212
- );
213
- },
214
- });
215
- }