@pellux/goodvibes-agent 1.0.35 → 1.0.36

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 (88) hide show
  1. package/CHANGELOG.md +15 -9
  2. package/README.md +8 -7
  3. package/dist/package/{ast-grep-napi.linux-x64-gnu-swtppvy9.node → ast-grep-napi.linux-x64-gnu-mkk8xwww.node} +0 -0
  4. package/dist/package/{ast-grep-napi.linux-x64-musl-ttfcdtap.node → ast-grep-napi.linux-x64-musl-ryqtgdv6.node} +0 -0
  5. package/dist/package/main.js +51504 -49337
  6. package/docs/README.md +6 -5
  7. package/docs/channels-remote-and-api.md +3 -3
  8. package/docs/connected-host.md +3 -3
  9. package/docs/getting-started.md +12 -7
  10. package/docs/knowledge-artifacts-and-multimodal.md +9 -4
  11. package/docs/project-planning.md +3 -3
  12. package/docs/providers-and-routing.md +2 -2
  13. package/docs/release-and-publishing.md +8 -8
  14. package/docs/tools-and-commands.md +31 -18
  15. package/docs/voice-and-live-tts.md +1 -1
  16. package/package.json +1 -1
  17. package/release/live-verification/live-verification.json +3 -3
  18. package/release/live-verification/live-verification.md +2 -4
  19. package/release/release-notes.md +4 -4
  20. package/release/release-readiness.json +46 -46
  21. package/src/agent/harness-control.ts +35 -9
  22. package/src/agent/reminder-schedule-format.ts +2 -5
  23. package/src/agent/reminder-schedule.ts +4 -11
  24. package/src/agent/routine-schedule-format.ts +3 -6
  25. package/src/agent/routine-schedule-promotion.ts +5 -14
  26. package/src/agent/routine-schedule-receipts.ts +4 -9
  27. package/src/cli/agent-knowledge-command.ts +6 -11
  28. package/src/cli/agent-knowledge-format.ts +4 -9
  29. package/src/cli/agent-knowledge-runtime.ts +7 -14
  30. package/src/cli/external-runtime.ts +3 -46
  31. package/src/cli/help.ts +2 -2
  32. package/src/cli/status.ts +6 -7
  33. package/src/input/agent-workspace-categories.ts +23 -23
  34. package/src/input/agent-workspace-channels.ts +3 -3
  35. package/src/input/agent-workspace-operations-command-editors.ts +1 -1
  36. package/src/input/command-registry.ts +1 -1
  37. package/src/input/commands/compat-runtime.ts +1 -1
  38. package/src/input/commands/planning-runtime.ts +1 -1
  39. package/src/input/commands/runtime-services.ts +1 -1
  40. package/src/input/mcp-workspace.ts +1 -1
  41. package/src/input/submission-router.ts +1 -1
  42. package/src/panels/builtin/shared.ts +1 -1
  43. package/src/panels/project-planning-panel.ts +4 -4
  44. package/src/panels/qr-panel.ts +2 -2
  45. package/src/renderer/conversation-overlays.ts +1 -0
  46. package/src/runtime/bootstrap-command-parts.ts +1 -1
  47. package/src/runtime/bootstrap-core.ts +1 -1
  48. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  49. package/src/runtime/bootstrap-shell.ts +1 -1
  50. package/src/runtime/bootstrap.ts +5 -4
  51. package/src/runtime/context.ts +2 -2
  52. package/src/runtime/index.ts +88 -5
  53. package/src/runtime/store/state.ts +1 -1
  54. package/src/tools/agent-channel-send-tool.ts +1 -1
  55. package/src/tools/agent-harness-channel-metadata.ts +36 -25
  56. package/src/tools/agent-harness-cli-metadata.ts +19 -1
  57. package/src/tools/agent-harness-command-catalog.ts +15 -4
  58. package/src/tools/agent-harness-connected-host-status.ts +7 -6
  59. package/src/tools/agent-harness-delegation-posture.ts +15 -4
  60. package/src/tools/agent-harness-keybinding-metadata.ts +67 -16
  61. package/src/tools/agent-harness-mcp-metadata.ts +11 -3
  62. package/src/tools/agent-harness-media-posture.ts +25 -14
  63. package/src/tools/agent-harness-metadata.ts +97 -6
  64. package/src/tools/agent-harness-mode-catalog.ts +81 -28
  65. package/src/tools/agent-harness-model-routing.ts +18 -3
  66. package/src/tools/agent-harness-model-tool-catalog.ts +85 -7
  67. package/src/tools/agent-harness-notification-metadata.ts +30 -19
  68. package/src/tools/agent-harness-operator-methods.ts +13 -8
  69. package/src/tools/agent-harness-pairing-posture.ts +17 -5
  70. package/src/tools/agent-harness-panel-metadata.ts +10 -4
  71. package/src/tools/agent-harness-provider-account-metadata.ts +46 -35
  72. package/src/tools/agent-harness-release-evidence.ts +31 -3
  73. package/src/tools/agent-harness-release-readiness.ts +30 -0
  74. package/src/tools/agent-harness-security-posture.ts +18 -4
  75. package/src/tools/agent-harness-service-posture.ts +8 -1
  76. package/src/tools/agent-harness-session-metadata.ts +24 -8
  77. package/src/tools/agent-harness-setup-posture.ts +32 -21
  78. package/src/tools/agent-harness-text.ts +6 -0
  79. package/src/tools/agent-harness-tool.ts +26 -34
  80. package/src/tools/agent-harness-ui-surface-metadata.ts +84 -84
  81. package/src/tools/agent-harness-workspace-actions.ts +64 -3
  82. package/src/tools/agent-knowledge-ingest-tool.ts +1 -1
  83. package/src/tools/agent-knowledge-tool.ts +1 -1
  84. package/src/tools/agent-media-generate-tool.ts +2 -2
  85. package/src/tools/agent-notify-tool.ts +1 -1
  86. package/src/tools/agent-reminder-schedule-tool.ts +1 -1
  87. package/src/tools/tool-definition-compaction.ts +35 -8
  88. package/src/version.ts +2 -8
@@ -95,7 +95,7 @@ export interface BuiltinPanelDeps {
95
95
  planManager?: ExecutionPlanManager;
96
96
  /** Adaptive planner for ops strategy panels. */
97
97
  adaptivePlanner?: AdaptivePlanner;
98
- /** Passive SDK-backed project planning artifact service. */
98
+ /** Passive project planning artifact service. */
99
99
  projectPlanningService?: ProjectPlanningService;
100
100
  /** Stable workspace project id for project:<projectId> planning namespaces. */
101
101
  projectPlanningProjectId?: string;
@@ -192,7 +192,7 @@ export class ProjectPlanningPanel extends BasePanel {
192
192
  lines: buildEmptyState(
193
193
  width,
194
194
  'No project planning state has been saved for this workspace.',
195
- 'Describe the intended operator outcome in normal chat to let the Agent start the planning interview. The SDK stores and evaluates artifacts; it does not start execution.',
195
+ 'Describe the intended operator outcome in normal chat to let the Agent start the planning interview. The planning artifact service stores and evaluates artifacts; it does not start execution.',
196
196
  [],
197
197
  C,
198
198
  ),
@@ -234,7 +234,7 @@ export class ProjectPlanningPanel extends BasePanel {
234
234
 
235
235
  return buildPanelWorkspace(width, height, {
236
236
  title: this.loading ? 'Project Planning - loading' : 'Project Planning',
237
- intro: 'SDK-backed planning artifacts for the current Agent workspace. Conversation control stays in the main Agent turn.',
237
+ intro: 'Planning artifacts for the current Agent workspace. Conversation control stays in the main Agent turn.',
238
238
  sections: [{ ...scroll.section, lines: sectionLines }],
239
239
  footerLines: this.footerLines(width, footerSummary),
240
240
  palette: C,
@@ -557,8 +557,8 @@ export class ProjectPlanningPanel extends BasePanel {
557
557
  actions.push({
558
558
  id: 'scope-agent-first',
559
559
  label: 'Agent-first scope',
560
- detail: 'Fix Agent behavior here; report SDK blockers instead of patching around SDK-owned bugs.',
561
- answer: 'Scope is Agent-owned behavior first. If a blocker is SDK-owned, report the exact SDK contract/runtime issue instead of patching around it locally. Include runtime contracts only where Agent consumes public runtime routes.',
560
+ detail: 'Fix Agent behavior here; report public-runtime blockers instead of patching around external runtime bugs.',
561
+ answer: 'Scope is Agent-owned behavior first. If a blocker belongs to the public runtime, report the exact contract/runtime issue instead of patching around it locally. Include runtime contracts only where Agent consumes public runtime routes.',
562
562
  });
563
563
  }
564
564
  actions.push({
@@ -31,7 +31,7 @@ export interface QrPanelConnectionInfo {
31
31
  readonly username: string;
32
32
  /** Bootstrap password for companion authentication */
33
33
  readonly password?: string;
34
- /** SDK/surface version (defaults to '0.0.0' if omitted) */
34
+ /** Pairing payload version (defaults to '0.0.0' if omitted) */
35
35
  readonly version?: string;
36
36
  /** Surface identifier (defaults to GoodVibes Agent if omitted) */
37
37
  readonly surface?: string;
@@ -54,7 +54,7 @@ export type CopyToClipboardFn = (text: string) => void;
54
54
  * Shows connection URL, truncated token, and username above the QR code.
55
55
  * Supports `r` to regenerate the token and `c` to copy the token.
56
56
  *
57
- * QR matrix generation uses the SDK's `generateQrMatrix` via `encodeConnectionPayload`.
57
+ * QR matrix generation uses `generateQrMatrix` via `encodeConnectionPayload`.
58
58
  */
59
59
  export class QrPanel extends BasePanel {
60
60
  private connectionInfo: QrPanelConnectionInfo;
@@ -43,6 +43,7 @@ export function applyConversationOverlays(
43
43
  if (input.onboardingWizard.active) {
44
44
  const lines = renderOnboardingWizard(input.onboardingWizard, conversationWidth, viewportHeight);
45
45
  next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
46
+ if (!input.modelPicker.active) return next;
46
47
  }
47
48
 
48
49
  if (input.filePicker.active) {
@@ -133,7 +133,7 @@ export interface BootstrapCommandSectionOptions {
133
133
  }
134
134
 
135
135
  function unwiredShellAction(name: string): never {
136
- throw new Error(`commandContext.${name} was called before the shell bridge was attached in main.ts`);
136
+ throw new Error(`Agent runtime action "${name}" was called before the operator route was attached.`);
137
137
  }
138
138
 
139
139
  export function createBootstrapCommandActions(
@@ -149,7 +149,7 @@ export async function initializeBootstrapCore(
149
149
  metadata: {
150
150
  product: 'goodvibes-agent',
151
151
  surfaceRoot: GOODVIBES_AGENT_SURFACE_ROOT,
152
- clientKindNote: 'SDK compatibility client kind; GoodVibes Agent remains an interactive operator TUI.',
152
+ clientKindNote: 'Connected-host client kind; GoodVibes Agent remains an interactive operator TUI.',
153
153
  },
154
154
  }, 'bootstrap.control-plane');
155
155
 
@@ -85,7 +85,7 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
85
85
  sessionId: options.runtime.sessionId,
86
86
  timestamp: Date.now(),
87
87
  payload: { sessionId },
88
- }).catch((err: unknown) => logger.debug('Hook bridge fire error', {
88
+ }).catch((err: unknown) => logger.debug('Hook route fire error', {
89
89
  path: 'Lifecycle:session:load',
90
90
  error: summarizeError(err),
91
91
  }));
@@ -92,7 +92,7 @@ export function submitPlanningAnswerWithShellFallback(
92
92
  }
93
93
 
94
94
  options.addSystemMessage([
95
- '[Planning] Could not submit the selected answer because the prompt bridge is not ready.',
95
+ '[Planning] Could not submit the selected answer because the prompt route is not ready.',
96
96
  `Paste this answer into the prompt to continue planning: ${answer}`,
97
97
  ].join('\n'));
98
98
  options.requestRender();
@@ -50,6 +50,7 @@ const GOODVIBES_AGENT_OPERATOR_POLICY = [
50
50
  '- Work serially in the main conversation by default: answer, inspect, summarize, remember useful non-secret facts, configure Agent-local state, and use safe read-only connected-host/operator routes.',
51
51
  '- Connected-host lifecycle is external. Do not start, stop, restart, install, expose, or mutate host listeners/network posture from Agent.',
52
52
  '- Read tools: `agent_operator_briefing` for connected work/approvals/automation/schedules, `agent_knowledge` for isolated Agent Knowledge, `agent_harness` for harness catalogs/settings/status.',
53
+ '- Harness access: use `agent_harness` modes `commands`/`run_command`, `workspace_actions`/`run_workspace_action`, and `settings`/`set_setting`/`reset_setting` to use the same surfaces the user can use.',
53
54
  '- State tools: `agent_work_plan` for visible local work items; `agent_local_registry` for Agent-local notes, memory, personas, skills, bundles, and routines. Keep records non-secret, sourced, and reviewable.',
54
55
  '- Confirmed tools: use `agent_operator_action`, `agent_knowledge_ingest`, `agent_media_generate`, `agent_notify`, `agent_channel_send`, and `agent_reminder_schedule` only for explicit user requests with confirm:true and explicitUserRequest.',
55
56
  '- Agent Knowledge must use only `/api/goodvibes-agent/knowledge/*` and fail closed. Do not use default knowledge or non-Agent knowledge spaces.',
@@ -89,9 +90,9 @@ export type BootstrapContext = RuntimeContext & {
89
90
  agentStatusIntervalRef: { value: ReturnType<typeof setInterval> | null };
90
91
  /** Mutable refs for viewport/scroll/render functions; main.ts patches these after constructing UI state. */
91
92
  orchestratorRefs: { getViewportHeight: () => number; scrollToEnd: (vHeight: number) => void; requestRender: () => void };
92
- /** Patch the bootstrap-owned render bridge after main.ts constructs the real render loop. */
93
+ /** Patch the bootstrap-owned render route after main.ts constructs the real render loop. */
93
94
  setRenderRequest: (fn: () => void) => void;
94
- /** Shell-owned permission prompt bridge that main.ts patches after UI setup. */
95
+ /** Shell-owned permission prompt route that main.ts patches after UI setup. */
95
96
  permissionPromptRef: { requestPermission: PermissionRequestHandler };
96
97
  /** Load the most recently saved conversation from disk. */
97
98
  loadLastConversation: () => { messages: Array<Record<string, unknown>> } | null;
@@ -130,7 +131,7 @@ export type BootstrapContext = RuntimeContext & {
130
131
  * 1. Config, caches, keybindings
131
132
  * 2. Runtime event bus, conversation, compositor, selection
132
133
  * 3. Tool registry + agent wiring
133
- * 4. Runtime bus subscriptions (delegation, subagent, hook bridge)
134
+ * 4. Runtime bus subscriptions (delegation, subagent, hook route)
134
135
  * 5. Providers, webhooks, PermissionManager, HookDispatcher
135
136
  * 6. Orchestrator and Agent-local task read models
136
137
  * 7. MCP auto-connect + workspace/panel manager
@@ -547,7 +548,7 @@ export async function bootstrapRuntime(
547
548
  },
548
549
  };
549
550
 
550
- // Wire exit from options if provided; otherwise main.ts binds the shell bridge.
551
+ // Wire exit from options if provided; otherwise main.ts binds the operator route.
551
552
  if (options?.exit) {
552
553
  ctx.commandContext.exit = options.exit;
553
554
  }
@@ -35,8 +35,8 @@ export interface BootstrapOptions {
35
35
  configManager: import('@pellux/goodvibes-sdk/platform/config').ConfigManager;
36
36
  /**
37
37
  * Callback invoked when the app should exit.
38
- * If provided, commandContext.exit is wired during bootstrap.
39
- * Otherwise main.ts binds the shell-owned exit bridge immediately after bootstrap returns.
38
+ * If provided, the exit route is wired during bootstrap.
39
+ * Otherwise main.ts binds the app-owned exit route immediately after bootstrap returns.
40
40
  */
41
41
  exit?: () => void;
42
42
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Runtime module barrel for GoodVibes Agent.
3
3
  *
4
- * SDK 0.33 intentionally removed private deep imports and the runtime root
5
- * god-barrel. This file keeps the Agent app on public SDK seams
4
+ * The shared runtime removed private deep imports and the runtime root
5
+ * god-barrel. This file keeps the Agent app on public runtime exports
6
6
  * while preserving the local import surface used by the shell.
7
7
  */
8
8
 
@@ -50,7 +50,7 @@ export function createFeatureFlagManager(): FeatureFlagManager {
50
50
  return manager;
51
51
  }
52
52
 
53
- // Public SDK runtime seams.
53
+ // Public runtime exports.
54
54
  export * from '@pellux/goodvibes-sdk/platform/runtime/state';
55
55
  export * from '@pellux/goodvibes-sdk/platform/runtime/store';
56
56
  export * from '@pellux/goodvibes-sdk/platform/runtime/ui';
@@ -491,8 +491,6 @@ export const canonicalize = security.canonicalize;
491
491
  export const classifyCommand = security.classifyCommand;
492
492
  export const classifySegment = security.classifySegment;
493
493
  export const collectCommandNodes = security.collectCommandNodes;
494
- export const evaluateCommandAST = security.evaluateCommandAST;
495
- export const evaluateSegmentNode = security.evaluateSegmentNode;
496
494
  export const higherPriority = security.higherPriority;
497
495
  export const parseAST = security.parseAST;
498
496
  export const parseCommandAST = security.parseCommandAST;
@@ -505,6 +503,91 @@ export const verifyBundle = security.verifyBundle;
505
503
  export const MAX_INPUT_LENGTH = security.MAX_INPUT_LENGTH;
506
504
  export const MAX_TOKEN_COUNT = security.MAX_TOKEN_COUNT;
507
505
 
506
+ type RuntimeSegmentVerdict = ReturnType<typeof security.evaluateSegmentNode>;
507
+ type RuntimeCompoundVerdict = ReturnType<typeof security.evaluateCommandAST>;
508
+ type RuntimeShellNode = Parameters<typeof security.evaluateCommandAST>[1];
509
+
510
+ const AGENT_OBFUSCATION_CHECKS: Array<{ description: string; test: (raw: string) => boolean }> = [
511
+ {
512
+ description: 'base64-encoded argument (possible command injection)',
513
+ test: (raw) =>
514
+ extractInspectableShellWords(raw).some((word) =>
515
+ /^[A-Za-z0-9+/]+={0,2}$/.test(word) && word.length >= 12 && word.length % 4 === 0,
516
+ ),
517
+ },
518
+ {
519
+ description: 'URL-encoded content in argument',
520
+ test: (raw) => /%[0-9a-fA-F]{2}/.test(raw),
521
+ },
522
+ ];
523
+
524
+ function extractInspectableShellWords(raw: string): string[] {
525
+ return (raw.match(/"[^"]*"|'[^']*'|`[^`]*`|\S+/g) ?? []).map((word) =>
526
+ word.replace(/^["'`]+|["'`;|&]+$/g, ''),
527
+ );
528
+ }
529
+
530
+ function agentObfuscationPatterns(raw: string, existing: readonly string[]): string[] {
531
+ const patterns = new Set(existing);
532
+ for (const check of AGENT_OBFUSCATION_CHECKS) {
533
+ if (check.test(raw)) {
534
+ patterns.add(check.description);
535
+ }
536
+ }
537
+ return [...patterns];
538
+ }
539
+
540
+ function enforceAgentObfuscationVerdict(verdict: RuntimeSegmentVerdict): RuntimeSegmentVerdict {
541
+ const obfuscationPatterns = agentObfuscationPatterns(verdict.raw, verdict.obfuscationPatterns);
542
+ if (obfuscationPatterns.length === verdict.obfuscationPatterns.length) {
543
+ return verdict;
544
+ }
545
+
546
+ return {
547
+ ...verdict,
548
+ allowed: false,
549
+ reason: `obfuscation detected: ${obfuscationPatterns.join('; ')}`,
550
+ hasObfuscation: true,
551
+ obfuscationPatterns,
552
+ };
553
+ }
554
+
555
+ export function evaluateSegmentNode(
556
+ node: Security.CommandNode,
557
+ allowedClasses?: ReadonlySet<Security.CommandClassification>,
558
+ ): RuntimeSegmentVerdict {
559
+ const verdict = allowedClasses === undefined
560
+ ? security.evaluateSegmentNode(node)
561
+ : security.evaluateSegmentNode(node, allowedClasses);
562
+ return enforceAgentObfuscationVerdict(verdict);
563
+ }
564
+
565
+ export function evaluateCommandAST(
566
+ original: string,
567
+ ast: RuntimeShellNode,
568
+ allowedClasses?: ReadonlySet<Security.CommandClassification>,
569
+ ): RuntimeCompoundVerdict {
570
+ const compound = allowedClasses === undefined
571
+ ? security.evaluateCommandAST(original, ast)
572
+ : security.evaluateCommandAST(original, ast, allowedClasses);
573
+ const segments = compound.segments.map(enforceAgentObfuscationVerdict);
574
+ const allowed = segments.every((segment) => segment.allowed);
575
+ const hasObfuscation = segments.some((segment) => segment.hasObfuscation);
576
+
577
+ const next: RuntimeCompoundVerdict = {
578
+ ...compound,
579
+ allowed,
580
+ segments,
581
+ hasObfuscation,
582
+ };
583
+ if (allowed) {
584
+ delete next.denialExplanation;
585
+ } else {
586
+ next.denialExplanation = security.buildDenialExplanation(original, segments);
587
+ }
588
+ return next;
589
+ }
590
+
508
591
  export type AuthInspectionSnapshot = Security.AuthInspectionSnapshot;
509
592
  export type ProviderAuthInspection = Security.ProviderAuthInspection;
510
593
  export type DivergenceDashboardSnapshot = Security.DivergenceDashboardSnapshot;
@@ -34,7 +34,7 @@ import type { DiscoveryDomainState } from '@/runtime/index.ts';
34
34
  import type { IntelligenceDomainState } from '@/runtime/index.ts';
35
35
  import type { UiPerfDomainState } from './domains/ui-perf.ts';
36
36
  // UiPerfDomainState is structurally identical to SDK's SurfacePerfDomainState.
37
- // Export as SurfacePerfDomainState alias for SDK compatibility.
37
+ // Export as SurfacePerfDomainState alias for shared runtime callers.
38
38
  export type { UiPerfDomainState };
39
39
 
40
40
  import { createInitialSessionState } from '@/runtime/index.ts';
@@ -75,7 +75,7 @@ export function createAgentChannelSendTool(
75
75
  },
76
76
  explicitUserRequest: {
77
77
  type: 'string',
78
- description: 'Short quote or summary of the user request that authorized this delivery.',
78
+ description: 'User request authorizing this delivery.',
79
79
  },
80
80
  },
81
81
  required: ['message', 'confirm', 'explicitUserRequest'],
@@ -1,6 +1,7 @@
1
1
  import type { CommandContext } from '../input/command-registry.ts';
2
2
  import type { AgentWorkspaceChannelStatus } from '../input/agent-workspace-channels.ts';
3
3
  import { buildAgentWorkspaceChannels } from '../input/agent-workspace-channels.ts';
4
+ import { previewHarnessText } from './agent-harness-text.ts';
4
5
 
5
6
  export interface AgentHarnessChannelArgs {
6
7
  readonly channelId?: unknown;
@@ -59,9 +60,14 @@ function describeChannelCandidate(channel: AgentWorkspaceChannelStatus): Record<
59
60
  setupState: channel.setupState,
60
61
  ready: channel.ready,
61
62
  delivery: channel.delivery,
63
+ modelRoute: channelModelRoute(),
62
64
  };
63
65
  }
64
66
 
67
+ function channelModelRoute(): string {
68
+ return 'agent_channel_send or agent_harness mode:"channel"';
69
+ }
70
+
65
71
  function describeChannel(
66
72
  channel: AgentWorkspaceChannelStatus,
67
73
  options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {},
@@ -82,32 +88,37 @@ function describeChannel(
82
88
  missingConfigKeys: channel.missingRequiredKeys,
83
89
  defaultTargetKeys: channel.defaultTargetKeys,
84
90
  configuredDefaultTargetKeys: channel.configuredDefaultTargetKeys,
91
+ modelRoute: channelModelRoute(),
85
92
  ...(options.lookup ? { lookup: options.lookup } : {}),
86
- ...(options.includeParameters ? {
87
- policy: {
88
- effect: 'read-only',
89
- values: 'Config key names and target key names are shown; secret values and stored target values are never returned.',
90
- delivery: 'Use agent_channel_send only for one explicit, confirmed delivery target requested by the user.',
91
- setup: 'Use connected-host setup/account/policy routes only as read-only diagnostics unless another confirmed first-class tool owns the mutation.',
92
- },
93
- modelAccess: {
94
- sendTool: 'agent_channel_send',
95
- notificationTool: 'agent_notify',
96
- reminderTool: 'agent_reminder_schedule',
97
- slashCommandDetail: `/channels show ${channel.id}`,
98
- readOnlyConnectedRoutes: [
99
- '/channels accounts',
100
- '/channels policies',
101
- '/channels status',
102
- `/channels doctor ${channel.id}`,
103
- `/channels setup ${channel.id}`,
104
- ],
105
- settingsFilter: `agent_harness mode:"settings" prefix:"surfaces.${channel.id}" includeHidden:true`,
106
- connectedHostBoundary: 'agent_harness mode:"connected_host_capability" query:"delivery"',
107
- deliveryTargetShape: 'surface[:route[:label]]',
108
- exampleTarget: `${channel.id}:route:Label`,
109
- },
110
- } : {}),
93
+ ...(options.includeParameters
94
+ ? {
95
+ policy: {
96
+ effect: 'read-only',
97
+ values: 'Config key names and target key names are shown; secret values and stored target values are never returned.',
98
+ delivery: 'Use agent_channel_send only for one explicit, confirmed delivery target requested by the user.',
99
+ setup: 'Use connected-host setup/account/policy routes only as read-only diagnostics unless another confirmed first-class tool owns the mutation.',
100
+ },
101
+ modelAccess: {
102
+ sendTool: 'agent_channel_send',
103
+ notificationTool: 'agent_notify',
104
+ reminderTool: 'agent_reminder_schedule',
105
+ slashCommandDetail: `/channels show ${channel.id}`,
106
+ readOnlyConnectedRoutes: [
107
+ '/channels accounts',
108
+ '/channels policies',
109
+ '/channels status',
110
+ `/channels doctor ${channel.id}`,
111
+ `/channels setup ${channel.id}`,
112
+ ],
113
+ settingsFilter: `agent_harness mode:"settings" prefix:"surfaces.${channel.id}" includeHidden:true`,
114
+ connectedHostBoundary: 'agent_harness mode:"connected_host_capability" query:"delivery"',
115
+ deliveryTargetShape: 'surface[:route[:label]]',
116
+ exampleTarget: `${channel.id}:route:Label`,
117
+ },
118
+ }
119
+ : {
120
+ summary: previewHarnessText(channel.nextStep || `${channel.label} channel ${channel.setupState}`),
121
+ }),
111
122
  };
112
123
  }
113
124
 
@@ -34,6 +34,11 @@ function readLimit(value: unknown, fallback: number): number {
34
34
  return Math.max(1, Math.min(500, Math.trunc(parsed)));
35
35
  }
36
36
 
37
+ function previewText(value: string, maxLength = 56): string {
38
+ const normalized = value.replace(/\s+/g, ' ').trim();
39
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
40
+ }
41
+
37
42
  function redactCliLookupInput(input: string): string {
38
43
  return input
39
44
  .replace(/(--config=)([^=\s]+)=\S+/g, '$1$2=<redacted>')
@@ -58,6 +63,8 @@ function fallbackCliSummary(command: GoodVibesCliCommand): string {
58
63
  function describeCliCommand(command: GoodVibesCliCommand, lookup?: CliCommandLookup): Record<string, unknown> {
59
64
  const help = describeGoodVibesCommandHelp(command);
60
65
  const tokens = cliCommandTokens(command);
66
+ const policy = describeCliCommandPolicy(command);
67
+ const modelRoute = previewText(policy.preferredModelTool ?? 'agent_harness mode:"cli_command"');
61
68
  return {
62
69
  name: command,
63
70
  tokens,
@@ -69,7 +76,13 @@ function describeCliCommand(command: GoodVibesCliCommand, lookup?: CliCommandLoo
69
76
  aliases: help?.aliases ?? tokens.filter((token) => token !== command),
70
77
  subcommands: help?.subcommands ?? [],
71
78
  examples: help?.examples ?? [],
72
- policy: describeCliCommandPolicy(command),
79
+ modelRoute,
80
+ modelAccess: {
81
+ inspect: `agent_harness mode:"cli_command" cliCommand:"${command}"`,
82
+ executeEquivalent: policy.preferredModelTool ?? 'Use the current conversation or an explicit user-requested shell command.',
83
+ preferred: modelRoute,
84
+ },
85
+ policy,
73
86
  };
74
87
  }
75
88
 
@@ -88,11 +101,16 @@ function cliCommandMatches(command: Record<string, unknown>, query: string): boo
88
101
  }
89
102
 
90
103
  function cliCommandCandidate(command: Record<string, unknown>): Record<string, unknown> {
104
+ const policy = typeof command.policy === 'object' && command.policy !== null
105
+ ? command.policy as { readonly effect?: unknown; readonly preferredModelTool?: unknown }
106
+ : {};
91
107
  return {
92
108
  name: command.name,
93
109
  tokens: command.tokens,
94
110
  invocation: command.invocation,
95
111
  summary: command.summary,
112
+ effect: typeof policy.effect === 'string' ? policy.effect : 'unknown',
113
+ ...(typeof policy.preferredModelTool === 'string' ? { modelRoute: previewText(policy.preferredModelTool) } : {}),
96
114
  };
97
115
  }
98
116
 
@@ -39,9 +39,9 @@ function readStringArray(value: unknown): readonly string[] {
39
39
  return value.map((entry) => typeof entry === 'string' ? entry : String(entry));
40
40
  }
41
41
 
42
- function previewText(value: string, maxLength = 120): string {
42
+ function previewText(value: string, maxLength = 56): string {
43
43
  const normalized = value.replace(/\s+/g, ' ').trim();
44
- return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1).trimEnd()}...`;
44
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
45
45
  }
46
46
 
47
47
  function commandMatches(command: SlashCommand, query: string): boolean {
@@ -57,6 +57,8 @@ function commandMatches(command: SlashCommand, query: string): boolean {
57
57
  }
58
58
 
59
59
  function describeCommand(command: SlashCommand, lookup?: CommandDetailLookup): Record<string, unknown> {
60
+ const policy = describeCommandPolicy(command.name);
61
+ const modelRoute = previewText(policy.preferredModelTool ?? 'agent_harness mode:"command", mode:"run_command"');
60
62
  return {
61
63
  name: command.name,
62
64
  slash: `/${command.name}`,
@@ -64,6 +66,12 @@ function describeCommand(command: SlashCommand, lookup?: CommandDetailLookup): R
64
66
  description: command.description,
65
67
  usage: command.usage ?? '',
66
68
  argsHint: command.argsHint ?? command.usage ?? '',
69
+ modelRoute,
70
+ modelAccess: {
71
+ inspect: `agent_harness mode:"command" commandName:"${command.name}"`,
72
+ run: `agent_harness mode:"run_command" commandName:"${command.name}" confirm:true explicitUserRequest:"..."`,
73
+ preferred: modelRoute,
74
+ },
67
75
  ...(lookup ? {
68
76
  lookup: {
69
77
  source: lookup.source,
@@ -73,16 +81,19 @@ function describeCommand(command: SlashCommand, lookup?: CommandDetailLookup): R
73
81
  resolvedBy: lookup.resolvedBy,
74
82
  },
75
83
  } : {}),
76
- policy: describeCommandPolicy(command.name),
84
+ policy,
77
85
  };
78
86
  }
79
87
 
80
88
  function describeCommandCandidate(command: SlashCommand): Record<string, unknown> {
89
+ const policy = describeCommandPolicy(command.name);
81
90
  return {
82
91
  name: command.name,
83
92
  slash: `/${command.name}`,
84
93
  aliases: command.aliases ?? [],
85
94
  summary: previewText(command.description),
95
+ effect: policy.effect,
96
+ modelRoute: previewText(policy.preferredModelTool ?? 'agent_harness mode:"command", mode:"run_command"'),
86
97
  ...(command.argsHint ? { argsHint: command.argsHint } : {}),
87
98
  };
88
99
  }
@@ -170,7 +181,7 @@ export function resolveHarnessCommandDetail(commandRegistry: CommandRegistry, ar
170
181
 
171
182
  export function listHarnessCommands(commandRegistry: CommandRegistry, args: AgentHarnessCommandCatalogArgs): readonly Record<string, unknown>[] {
172
183
  const query = readString(args.query);
173
- const limit = readLimit(args.limit, 200);
184
+ const limit = readLimit(args.limit, 500);
174
185
  const includeParameters = args.includeParameters === true;
175
186
  return commandRegistry.list()
176
187
  .filter((command) => commandMatches(command, query))
@@ -39,11 +39,11 @@ function connectedHostFindings(
39
39
  });
40
40
  } else if (!runtime.compatible) {
41
41
  findings.push({
42
- id: 'connected-host-version-mismatch',
42
+ id: 'connected-host-incompatible',
43
43
  severity: 'warning',
44
- summary: 'Connected host SDK version does not match Agent.',
45
- cause: `Connected host reports SDK ${runtime.version}; Agent expects ${runtime.expectedVersion}.`,
46
- action: 'Update the owning GoodVibes host so its /status route reports the Agent SDK pin.',
44
+ summary: 'Connected host compatibility does not satisfy Agent readiness.',
45
+ cause: 'Connected host is reachable, but at least one public Agent route is unavailable or incompatible.',
46
+ action: 'Update the owning GoodVibes host so its public Agent routes are compatible.',
47
47
  });
48
48
  }
49
49
 
@@ -88,6 +88,7 @@ export async function connectedHostStatusSummary(
88
88
  return {
89
89
  ownership: 'external-connected-host',
90
90
  readOnly: true,
91
+ modelRoute: 'agent_harness mode:"connected_host_status" or mode:"service_posture"',
91
92
  timeoutMs: CONNECTED_HOST_STATUS_TIMEOUT_MS,
92
93
  lifecycle: 'GoodVibes Agent can inspect connected-host readiness and use public operator routes, but does not start, stop, restart, install, expose, or mutate the host listener.',
93
94
  paths: {
@@ -122,8 +123,6 @@ export async function connectedHostStatusSummary(
122
123
  route: '/status',
123
124
  reachable: runtime.reachable,
124
125
  statusCode: runtime.statusCode,
125
- sdkVersion: runtime.version,
126
- expectedSdkVersion: runtime.expectedVersion,
127
126
  compatible: runtime.compatible,
128
127
  },
129
128
  {
@@ -142,6 +141,8 @@ export async function connectedHostStatusSummary(
142
141
  },
143
142
  ...(options.includeParameters ? { modelAccess: {
144
143
  diagnostics: 'Use mode:"connected_host_status" for live read-only host readiness, mode:"service_posture" for endpoint posture, mode:"service_endpoint" for one endpoint, and mode:"connected_host" for capability and boundary inventory.',
144
+ daemonAliases: 'mode:"daemon_status" is an alias for mode:"connected_host_status"; mode:"daemon" is an alias for mode:"connected_host".',
145
+ lifecycleBlocked: 'Start, stop, restart, install, expose, and listener mutation are not exposed through Agent.',
145
146
  cliMirrors: ['goodvibes-agent status --json', 'goodvibes-agent doctor', 'goodvibes-agent compat'],
146
147
  tuiMirrors: ['Agent Workspace -> Home -> Host compatibility', 'Agent Workspace -> Home -> Doctor diagnostics', 'Agent Workspace -> Home -> Review health'],
147
148
  },
@@ -1,4 +1,5 @@
1
1
  import type { CommandContext } from '../input/command-registry.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
2
3
 
3
4
  export interface AgentHarnessDelegationArgs {
4
5
  readonly delegationRouteId?: unknown;
@@ -113,9 +114,16 @@ function describeCandidate(route: DelegationRoute): Record<string, unknown> {
113
114
  label: route.label,
114
115
  effect: route.effect,
115
116
  confirmationRequired: route.confirmationRequired === true,
117
+ modelRoute: delegationRouteModelRoute(route),
116
118
  };
117
119
  }
118
120
 
121
+ function delegationRouteModelRoute(route: DelegationRoute): string {
122
+ if (route.workspaceActionId) return 'agent_harness mode:"run_workspace_action"';
123
+ if (route.command || route.commandTemplate) return 'agent_harness mode:"run_command"';
124
+ return route.effect === 'main-conversation' ? 'main conversation' : 'agent_harness mode:"delegation_route"';
125
+ }
126
+
119
127
  function describeRoute(
120
128
  context: CommandContext,
121
129
  route: DelegationRoute,
@@ -124,17 +132,20 @@ function describeRoute(
124
132
  return {
125
133
  delegationRouteId: route.id,
126
134
  label: route.label,
127
- detail: route.detail,
135
+ ...(options.includeParameters ? { detail: route.detail } : { summary: previewHarnessText(route.detail) }),
128
136
  effect: route.effect,
129
- ...(route.command ? { command: route.command } : {}),
130
- ...(route.commandTemplate ? { commandTemplate: route.commandTemplate } : {}),
131
- ...(route.workspaceActionId ? { workspaceActionId: route.workspaceActionId } : {}),
132
137
  confirmationRequired: route.confirmationRequired === true,
133
138
  reviewPolicy: route.reviewPolicy ?? 'not-applicable',
139
+ modelRoute: delegationRouteModelRoute(route),
134
140
  runtime: {
135
141
  sessionId: context.session.runtime.sessionId,
136
142
  operatorClientAttached: Boolean(context.clients?.operator),
137
143
  },
144
+ ...(options.includeParameters ? {
145
+ ...(route.command ? { command: route.command } : {}),
146
+ ...(route.commandTemplate ? { commandTemplate: route.commandTemplate } : {}),
147
+ ...(route.workspaceActionId ? { workspaceActionId: route.workspaceActionId } : {}),
148
+ } : {}),
138
149
  ...(options.lookup ? { lookup: options.lookup } : {}),
139
150
  ...(options.includeParameters ? {
140
151
  policy: {