@pellux/goodvibes-agent 1.5.5 → 1.5.6

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 (165) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +1 -1
  3. package/dist/package/main.js +94130 -79335
  4. package/docs/tools-and-commands.md +2 -1
  5. package/package.json +6 -3
  6. package/src/agent/autonomy-schedule-format.ts +1 -1
  7. package/src/agent/autonomy-schedule.ts +3 -3
  8. package/src/agent/calendar/calendar-oauth-service.ts +324 -0
  9. package/src/agent/calendar-subscription-registry.ts +497 -0
  10. package/src/agent/email/email-service.ts +57 -0
  11. package/src/agent/email/smtp-client.ts +30 -10
  12. package/src/agent/memory-prompt.ts +143 -12
  13. package/src/agent/operator-actions.ts +26 -22
  14. package/src/agent/prompt-context-receipts.ts +45 -18
  15. package/src/agent/reminder-schedule-format.ts +1 -1
  16. package/src/agent/reminder-schedule.ts +3 -3
  17. package/src/agent/routine-schedule-format.ts +2 -2
  18. package/src/agent/routine-schedule-promotion.ts +5 -5
  19. package/src/agent/routine-schedule-receipts.ts +2 -2
  20. package/src/agent/schedule-edit-format.ts +1 -1
  21. package/src/agent/schedule-edit.ts +5 -5
  22. package/src/agent/session-registration.ts +281 -0
  23. package/src/agent/vibe-file.ts +50 -0
  24. package/src/cli/local-library-command-shared.ts +146 -0
  25. package/src/cli/management.ts +2 -1
  26. package/src/cli/personas-command.ts +252 -0
  27. package/src/cli/resume-relaunch-notice.ts +151 -0
  28. package/src/cli/routines-command.ts +25 -2
  29. package/src/cli/service-posture.ts +3 -2
  30. package/src/cli/skill-bundle-command.ts +175 -0
  31. package/src/cli/skills-command.ts +309 -0
  32. package/src/cli/status.ts +43 -7
  33. package/src/cli/tui-startup.ts +24 -1
  34. package/src/config/agent-settings-policy.ts +0 -1
  35. package/src/config/secret-config.ts +4 -0
  36. package/src/core/conversation-rendering.ts +20 -11
  37. package/src/core/focus-tracker.ts +41 -0
  38. package/src/core/system-message-noise.ts +108 -0
  39. package/src/core/system-message-router.ts +54 -1
  40. package/src/core/thinking-overlay.ts +83 -0
  41. package/src/input/agent-workspace-access-command-editor-submission.ts +75 -108
  42. package/src/input/agent-workspace-access-command-editors.ts +108 -128
  43. package/src/input/agent-workspace-activation.ts +15 -0
  44. package/src/input/agent-workspace-basic-command-editor-submission.ts +292 -513
  45. package/src/input/agent-workspace-basic-command-editors.ts +433 -564
  46. package/src/input/agent-workspace-calendar-connect-editor.ts +116 -0
  47. package/src/input/agent-workspace-calendar-oauth-editor.ts +152 -0
  48. package/src/input/agent-workspace-calendar-subscribe-editor.ts +128 -0
  49. package/src/input/agent-workspace-categories.ts +7 -2
  50. package/src/input/agent-workspace-channel-command-editor-submission.ts +38 -52
  51. package/src/input/agent-workspace-channel-command-editors.ts +42 -46
  52. package/src/input/agent-workspace-command-editor-engine.ts +133 -0
  53. package/src/input/agent-workspace-command-editor.ts +4 -0
  54. package/src/input/agent-workspace-direct-editor-submission.ts +59 -0
  55. package/src/input/agent-workspace-email-connect-editor.ts +162 -0
  56. package/src/input/agent-workspace-knowledge-command-editor-submission.ts +52 -81
  57. package/src/input/agent-workspace-knowledge-command-editors.ts +71 -71
  58. package/src/input/agent-workspace-library-command-editor-submission.ts +10 -28
  59. package/src/input/agent-workspace-library-command-editors.ts +16 -2
  60. package/src/input/agent-workspace-live-counters.ts +55 -0
  61. package/src/input/agent-workspace-mcp-command-editor-submission.ts +60 -82
  62. package/src/input/agent-workspace-mcp-command-editors.ts +52 -0
  63. package/src/input/agent-workspace-media-command-editor-submission.ts +12 -39
  64. package/src/input/agent-workspace-media-command-editors.ts +10 -8
  65. package/src/input/agent-workspace-memory-command-editor-submission.ts +76 -151
  66. package/src/input/agent-workspace-memory-command-editors.ts +116 -141
  67. package/src/input/agent-workspace-operations-command-editor-submission.ts +131 -184
  68. package/src/input/agent-workspace-operations-command-editors.ts +150 -162
  69. package/src/input/agent-workspace-provider-command-editor-submission.ts +60 -106
  70. package/src/input/agent-workspace-provider-command-editors.ts +58 -68
  71. package/src/input/agent-workspace-search.ts +8 -1
  72. package/src/input/agent-workspace-session-command-editor-submission.ts +104 -132
  73. package/src/input/agent-workspace-session-command-editors.ts +160 -195
  74. package/src/input/agent-workspace-settings.ts +40 -1
  75. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +55 -71
  76. package/src/input/agent-workspace-skill-bundle-command-editors.ts +65 -69
  77. package/src/input/agent-workspace-snapshot-builders.ts +431 -0
  78. package/src/input/agent-workspace-snapshot-config.ts +43 -0
  79. package/src/input/agent-workspace-snapshot.ts +198 -432
  80. package/src/input/agent-workspace-task-command-editor-submission.ts +34 -44
  81. package/src/input/agent-workspace-task-command-editors.ts +27 -28
  82. package/src/input/agent-workspace-types.ts +35 -1
  83. package/src/input/agent-workspace.ts +5 -14
  84. package/src/input/command-registry.ts +23 -1
  85. package/src/input/commands/calendar-connect-runtime.ts +226 -0
  86. package/src/input/commands/calendar-runtime.ts +110 -7
  87. package/src/input/commands/calendar-subscription-runtime.ts +225 -0
  88. package/src/input/commands/email-runtime.ts +100 -40
  89. package/src/input/commands/knowledge.ts +1 -1
  90. package/src/input/commands/network-scan-runtime.ts +75 -0
  91. package/src/input/commands/operator-actions-runtime.ts +9 -6
  92. package/src/input/commands/personas-runtime.ts +1 -1
  93. package/src/input/commands/schedule-runtime.ts +4 -4
  94. package/src/input/commands/session-content.ts +13 -1
  95. package/src/input/commands/session-workflow.ts +15 -20
  96. package/src/input/commands/session.ts +3 -1
  97. package/src/input/commands/shell-core.ts +20 -6
  98. package/src/input/commands.ts +2 -2
  99. package/src/input/delete-key-policy.ts +46 -0
  100. package/src/input/feed-context-factory.ts +10 -0
  101. package/src/input/handler-feed.ts +87 -0
  102. package/src/input/handler-modal-routes.ts +6 -1
  103. package/src/input/handler.ts +5 -0
  104. package/src/input/panel-paste-flood-guard.ts +94 -0
  105. package/src/input/settings-modal-types.ts +5 -3
  106. package/src/input/settings-modal.ts +21 -0
  107. package/src/main.ts +36 -36
  108. package/src/permissions/approval-posture.ts +141 -0
  109. package/src/renderer/agent-workspace-context-lines.ts +10 -2
  110. package/src/renderer/compositor.ts +27 -4
  111. package/src/renderer/diff.ts +61 -18
  112. package/src/renderer/fullscreen-primitives.ts +37 -18
  113. package/src/renderer/markdown.ts +20 -10
  114. package/src/renderer/modal-factory.ts +25 -15
  115. package/src/renderer/overlay-box.ts +23 -12
  116. package/src/renderer/process-indicator.ts +8 -3
  117. package/src/renderer/prompt-content-width.ts +16 -0
  118. package/src/renderer/settings-modal-helpers.ts +2 -0
  119. package/src/renderer/settings-modal.ts +2 -0
  120. package/src/renderer/startup-theme-probe.ts +35 -0
  121. package/src/renderer/status-glyphs.ts +11 -15
  122. package/src/renderer/system-message.ts +17 -2
  123. package/src/renderer/term-caps.ts +318 -0
  124. package/src/renderer/terminal-bg-probe.ts +373 -0
  125. package/src/renderer/terminal-escapes.ts +24 -0
  126. package/src/renderer/theme-mode-config.ts +87 -0
  127. package/src/renderer/theme.ts +241 -0
  128. package/src/renderer/thinking.ts +12 -3
  129. package/src/renderer/tool-call.ts +7 -3
  130. package/src/renderer/ui-factory.ts +92 -36
  131. package/src/renderer/ui-primitives.ts +33 -93
  132. package/src/runtime/bootstrap-core.ts +15 -0
  133. package/src/runtime/bootstrap-hook-bridge.ts +6 -0
  134. package/src/runtime/bootstrap-shell.ts +2 -0
  135. package/src/runtime/bootstrap.ts +68 -5
  136. package/src/runtime/calendar-boot-refresh.ts +105 -0
  137. package/src/runtime/lan-scan-consent.ts +253 -0
  138. package/src/runtime/services.ts +127 -2
  139. package/src/runtime/session-spine-rest-transport.ts +160 -0
  140. package/src/runtime/terminal-output-guard.ts +6 -1
  141. package/src/runtime/ui-services.ts +3 -0
  142. package/src/shell/agent-workspace-fullscreen.ts +5 -0
  143. package/src/shell/terminal-focus-mode.ts +120 -0
  144. package/src/shell/ui-openers.ts +13 -4
  145. package/src/tools/agent-harness-autonomy-intake.ts +5 -5
  146. package/src/tools/agent-harness-autonomy-queue.ts +1 -1
  147. package/src/tools/agent-harness-background-processes.ts +2 -1
  148. package/src/tools/agent-harness-metadata.ts +16 -8
  149. package/src/tools/agent-harness-operator-methods.ts +44 -8
  150. package/src/tools/agent-harness-personal-ops-discovery.ts +57 -10
  151. package/src/tools/agent-harness-personal-ops-lanes.ts +17 -5
  152. package/src/tools/agent-harness-personal-ops-operations.ts +7 -7
  153. package/src/tools/agent-harness-personal-ops-types.ts +6 -0
  154. package/src/tools/agent-harness-prompt-context.ts +26 -12
  155. package/src/tools/agent-harness-workspace-actions.ts +4 -0
  156. package/src/tools/agent-local-registry-args.ts +117 -0
  157. package/src/tools/agent-local-registry-memory.ts +227 -0
  158. package/src/tools/agent-local-registry-tool.ts +19 -237
  159. package/src/tools/agent-operator-briefing-tool.ts +2 -2
  160. package/src/tools/agent-operator-method-tool.ts +13 -0
  161. package/src/tools/agent-policy-explanation.ts +39 -4
  162. package/src/tools/agent-schedule-tool.ts +5 -5
  163. package/src/utils/terminal-width.ts +98 -1
  164. package/src/version.ts +1 -1
  165. package/src/cli/local-library-command.ts +0 -825
@@ -36,5 +36,10 @@ export function createAgentWorkspaceFullscreenComposite(
36
36
  width: number,
37
37
  height: number,
38
38
  ): CompositeRequest {
39
+ // W4-A6: mirror disk state on every repaint of the workspace panel so the
40
+ // memory count and routine start counts never show a stale point-in-time
41
+ // snapshot from the last workspace action (e.g. an external delete, or a
42
+ // routine started from another shell/CLI invocation).
43
+ workspace.syncLiveCountersForRender();
39
44
  return createFullscreenCompositeFromLines(renderAgentWorkspace(workspace, width, height), width, height);
40
45
  }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * terminal-focus-mode.ts — W4-R3: DECSET ?1004h OS window-focus reporting +
3
+ * the approval-alert wiring that consumes it.
4
+ *
5
+ * Extracted from main.ts (architecture line-count gate: MAX_SOURCE_LINES=800
6
+ * in scripts/check-architecture.ts) — mirrors goodvibes-tui's own precedent of
7
+ * extracting terminal-mode literals out of its entry file (see that repo's
8
+ * src/renderer/terminal-escapes.ts docstring). Behavior is unchanged from an
9
+ * inline version; this is purely a line-count extraction plus the one new
10
+ * behavior (the approval alert) this work order adds.
11
+ *
12
+ * FOCUS_ENABLE/FOCUS_DISABLE: sourced from W4-R2's terminal-escapes.ts (the
13
+ * shared home for the DECSET ?1004 literals — its own docstring names R3 as
14
+ * the importer) and re-exported here so the focus-mode callers (main.ts and
15
+ * the tests) keep one import site for the whole focus-mode surface. This is
16
+ * the R2/R3 convergence the W4-R1 parity matrix pre-ruled: a relocation of
17
+ * the literals, no behavior change.
18
+ *
19
+ * installFocusModeExitGuard(): a last-resort safety net. Node/Bun fire 'exit'
20
+ * listeners synchronously for every process-termination path this app can
21
+ * take — explicit process.exit() calls (normal /exit, double-Ctrl+C via
22
+ * exitApp()) AND the default uncaughtException termination — even though
23
+ * main.ts registers no explicit uncaughtException/SIGTERM handler of its own.
24
+ * Named top-5 risk (W4-R1 matrix): ?1004h MUST be disabled on every exit path
25
+ * or the user's shell inherits focus-reporting escape garbage. The write is
26
+ * harmless even if exitApp() already disabled it moments earlier (?1004l is
27
+ * idempotent). It is GATED on focus mode having actually been enabled
28
+ * (markFocusModeEnabled, called at the TUI's ?1004h-enable site): only the
29
+ * interactive TUI launch turns focus reporting on, so a blocked/scriptable
30
+ * command (serve/status/--help/…) that exits before that must leave stdout
31
+ * pristine — a stray ?1004l on such a command's stdout is escape garbage that
32
+ * would, e.g., corrupt `status --json` for a machine consumer.
33
+ *
34
+ * wrapRequestPermissionWithApprovalAlert(): fires an unfocused-user alert the
35
+ * moment a tool call becomes a real, user-blocking permission prompt. Ported
36
+ * concept from goodvibes-tui's core/approval-alert.ts, scoped down: that
37
+ * module's config-gated alert-class system (alert-gating.ts's
38
+ * behavior.notifyOnApprovalPending / notifyOnlyWhenUnfocused settings, shared
39
+ * with budget-breach/long-task/chain-failure notifiers) is TUI-specific
40
+ * infrastructure the W4-R1 parity matrix does not list as a PORT item — only
41
+ * core/focus-tracker.ts is. This is the minimal, self-contained wiring the
42
+ * matrix's own text calls for ("route focus in/out events ... to drive
43
+ * awaiting-approval alerts"). PRIVACY: message content is tool name + category
44
+ * only — no args, no file contents, no command strings.
45
+ */
46
+ import { logger, notifyCompletion, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
47
+ import type { PermissionRequestHandler, PermissionPromptRequest } from '@pellux/goodvibes-sdk/platform/permissions';
48
+ import type { FocusTracker } from '../core/focus-tracker.ts';
49
+
50
+ import { FOCUS_DISABLE } from '../renderer/terminal-escapes.ts';
51
+ export { FOCUS_ENABLE, FOCUS_DISABLE } from '../renderer/terminal-escapes.ts';
52
+
53
+ /**
54
+ * notifyCompletion (SDK platform/utils) only rings the bell above 5s of
55
+ * "duration" and only pops a desktop notification above 30s — a heuristic
56
+ * tuned for long-running-turn notifications (ported alongside
57
+ * goodvibes-tui's alert-gating.ts FORCE_NOTIFY_DURATION_MS). The
58
+ * approval-alert wire is a point-in-time event with no natural duration, so
59
+ * it passes this constant as durationMs to force both the bell and the
60
+ * desktop popup unconditionally.
61
+ */
62
+ export const FORCE_APPROVAL_NOTIFY_DURATION_MS = 30_001;
63
+
64
+ /**
65
+ * Whether the TUI actually enabled OS focus reporting (DECSET ?1004h) this run.
66
+ * Set once, at the TUI launch site that writes FOCUS_ENABLE; the process exits
67
+ * without ever clearing it, so the teardown guard fires on every abnormal-exit
68
+ * path once focus mode is on. Left false by every blocked/scriptable command.
69
+ */
70
+ let focusModeEnabled = false;
71
+
72
+ /** Record that OS focus reporting was enabled; call at the same site that writes FOCUS_ENABLE. */
73
+ export function markFocusModeEnabled(): void {
74
+ focusModeEnabled = true;
75
+ }
76
+
77
+ /**
78
+ * Registers the process-wide ?1004h teardown safety net. Call once at startup.
79
+ * The write only fires when focus mode was actually enabled (isFocusModeEnabled),
80
+ * so non-TUI commands never leak ?1004l onto stdout. The predicate is injectable
81
+ * so tests can drive both branches deterministically without module-global state.
82
+ */
83
+ export function installFocusModeExitGuard(
84
+ stdout: Pick<NodeJS.WriteStream, 'write'> = process.stdout,
85
+ isFocusModeEnabled: () => boolean = () => focusModeEnabled,
86
+ ): void {
87
+ process.on('exit', () => {
88
+ if (!isFocusModeEnabled()) return; // never enabled (blocked/scriptable command) — keep stdout clean
89
+ try { stdout.write(FOCUS_DISABLE); } catch { /* stdout may already be torn down */ }
90
+ });
91
+ }
92
+
93
+ export interface ApprovalAlertDeps {
94
+ readonly focusTracker: Pick<FocusTracker, 'shouldAlertWhenUnfocused'>;
95
+ readonly notify?: typeof notifyCompletion;
96
+ }
97
+
98
+ /**
99
+ * Wrap a PermissionRequestHandler so every call also fires an unfocused-user
100
+ * alert. The wrapped handler's behavior (what it resolves to) is completely
101
+ * unchanged — this only adds a side effect at call time, fired synchronously
102
+ * before the wrapped promise settles (reflects "a prompt just appeared", not
103
+ * "a prompt was just resolved").
104
+ */
105
+ export function wrapRequestPermissionWithApprovalAlert(
106
+ original: PermissionRequestHandler,
107
+ deps: ApprovalAlertDeps,
108
+ ): PermissionRequestHandler {
109
+ const notify = deps.notify ?? notifyCompletion;
110
+ return (request: PermissionPromptRequest) => {
111
+ if (deps.focusTracker.shouldAlertWhenUnfocused()) {
112
+ try {
113
+ notify('GoodVibes — approval needed', `${request.tool} (${request.category}) is waiting for approval`, FORCE_APPROVAL_NOTIFY_DURATION_MS);
114
+ } catch (err) {
115
+ logger.debug('approval-alert: desktop notify error', { error: summarizeError(err) });
116
+ }
117
+ }
118
+ return original(request);
119
+ };
120
+ }
@@ -14,6 +14,7 @@ import type { ServiceInspectionQuery } from '../runtime/ui-service-queries.ts';
14
14
  import type { ModelPickerTargetInfo } from '../input/model-picker.ts';
15
15
  import { buildLocalFitRecommendations, buildSignInRow, LOCAL_REC_PROVIDER } from '../input/model-picker-local-fit.ts';
16
16
  import { syncServiceSettingToPlatform } from './service-settings-sync.ts';
17
+ import { applyThemeModeSettingChange, THEME_MODE_CONFIG_KEY } from '../renderer/theme-mode-config.ts';
17
18
 
18
19
  type WireShellUiOpenersOptions = {
19
20
  commandContext: CommandContext;
@@ -422,10 +423,18 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
422
423
  commandContext.openSettingsModal = (target?: string) => {
423
424
  input.modalOpened('settings');
424
425
  input.settingsModal.open(configManager, featureFlags, subscriptionManager, serviceRegistry, mcpRegistry, secretsManager, {
425
- onSettingApplied: (change) => syncServiceSettingToPlatform(
426
- { configManager, workingDirectory, homeDirectory },
427
- change,
428
- ),
426
+ onSettingApplied: (change) => {
427
+ // W4-R4: forced dark/light applies immediately (mode flip + full
428
+ // repaint via clearScreen's resetDiff); auto only re-probes at startup,
429
+ // so it takes effect next launch (stated honestly).
430
+ if (String(change.key) === THEME_MODE_CONFIG_KEY) {
431
+ return applyThemeModeSettingChange(change.value, () => commandContext.clearScreen?.());
432
+ }
433
+ return syncServiceSettingToPlatform(
434
+ { configManager, workingDirectory, homeDirectory },
435
+ change,
436
+ );
437
+ },
429
438
  });
430
439
  input.settingsModal.selectTarget(target);
431
440
  render();
@@ -417,8 +417,8 @@ function asksForEventTrigger(lower: string): boolean {
417
417
 
418
418
  function buildTriggerWorkflows(request: string, schedule: ScheduleDetection): readonly TriggerWorkflow[] {
419
419
  const methodIds = operatorMethodIds();
420
- const schedulesCreatePublished = methodIds.has('schedules.create');
421
- const schedulesListPublished = methodIds.has('schedules.list');
420
+ const schedulesCreatePublished = methodIds.has('automation.schedules.create');
421
+ const schedulesListPublished = methodIds.has('automation.schedules.list');
422
422
  const watcherCreatePublished = methodIds.has('watchers.create');
423
423
  const watcherListPublished = methodIds.has('watchers.list');
424
424
  const watcherRunPublished = methodIds.has('watchers.run');
@@ -437,10 +437,10 @@ function buildTriggerWorkflows(request: string, schedule: ScheduleDetection): re
437
437
  status: scheduleReady ? 'ready' : schedulesCreatePublished ? 'attention' : 'setup-needed',
438
438
  userOutcome: 'Run autonomous work on an exact at/every/cron cadence without making the user understand scheduler internals.',
439
439
  summary: scheduleReady
440
- ? 'The request includes an exact cadence and the connected host publishes schedules.create.'
440
+ ? 'The request includes an exact cadence and the connected host publishes automation.schedules.create.'
441
441
  : schedulesCreatePublished
442
442
  ? 'The schedule route is published, but this request still needs an exact cadence before creation.'
443
- : 'The connected host does not publish schedules.create in the current SDK contract.',
443
+ : 'The connected host does not publish automation.schedules.create in the current SDK contract.',
444
444
  nextStep: scheduleReady
445
445
  ? 'Create through schedule action:"create" with confirm:true and explicit success criteria.'
446
446
  : 'Ask for the exact ISO time, every interval, or cron expression before offering schedule creation.',
@@ -449,7 +449,7 @@ function buildTriggerWorkflows(request: string, schedule: ScheduleDetection): re
449
449
  modelRoute: `schedule action:"create" task:"${shortRequest}" successCriteria:"..." scheduleKind:"${schedule.kind ?? 'at|every|cron'}" scheduleValue:"${schedule.value ?? '...'}" confirm:true explicitUserRequest:"..."`,
450
450
  inspectRoute: 'autonomy action:"item" queueItemId:"connected-schedules"',
451
451
  setupRoutes: [
452
- 'host action:"method" methodId:"schedules.create"',
452
+ 'host action:"method" methodId:"automation.schedules.create"',
453
453
  'autonomy action:"item" queueItemId:"connected-schedules"',
454
454
  ],
455
455
  evidence: {
@@ -381,7 +381,7 @@ function buildQueueItems(context: CommandContext): readonly AutonomyQueueItem[]
381
381
  current: `${scheduleMethods.length} schedule/reminder daemon method(s); autonomous schedules require explicit task, cadence, success criteria, and user request provenance.`,
382
382
  next: scheduleMethods.length > 0
383
383
  ? 'Create one visible autonomous schedule only after the user gives exact timing and success criteria.'
384
- : 'Update the connected GoodVibes host until schedules.create is available.',
384
+ : 'Update the connected GoodVibes host until automation.schedules.create is available.',
385
385
  inspectRoute: 'autonomy action:"intake" query:"..."',
386
386
  modelRoute: 'schedule action:"create"',
387
387
  createRoute: 'schedule action:"create" task:"..." successCriteria:"..." scheduleKind:"..." scheduleValue:"..." confirm:true explicitUserRequest:"..."',
@@ -104,7 +104,8 @@ function matchingOperatorMethodRoutes(tokens: readonly string[]): readonly Recor
104
104
  return operatorContractMethods()
105
105
  .filter((method) => {
106
106
  const text = operatorMethodSearchText(method);
107
- return tokens.some((token) => text.includes(token));
107
+ // \b-anchored: a raw text.includes(token) false-matches short tokens like 'pty' inside an unrelated word ('empty').
108
+ return tokens.some((token) => new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(text));
108
109
  })
109
110
  .map((method) => ({
110
111
  methodId: method.id,
@@ -86,6 +86,14 @@ export function describeCommandPolicy(commandName: string): CommandExecutionPoli
86
86
  boundary: 'Provider selection and custom provider files belong to Agent provider configuration. Adding, removing, or switching providers requires explicit user intent.',
87
87
  };
88
88
  }
89
+ if (root === 'network-scan' || root === 'discover-lan') {
90
+ return {
91
+ effect: 'local-state',
92
+ confirmation,
93
+ preferredModelTool: settingsActions('get', 'set'),
94
+ boundary: 'Turns the local-network model-server scan on or off and persists that decision to a local consent file; it never probes the network itself. Enabling it requires the user\'s explicit request — do not turn it on unprompted.',
95
+ };
96
+ }
89
97
  if (root === 'refresh-models') {
90
98
  return {
91
99
  effect: 'external-network',
@@ -387,14 +395,14 @@ export function connectedHostCapabilityMap(toolRegistry: ToolRegistry): readonly
387
395
  'approvals.deny',
388
396
  'approvals.cancel',
389
397
  'automation.jobs.run',
390
- 'automation.jobs.pause',
391
- 'automation.jobs.resume',
398
+ 'automation.jobs.disable',
399
+ 'automation.jobs.enable',
392
400
  'automation.runs.cancel',
393
401
  'automation.runs.retry',
394
- 'schedules.delete',
395
- 'schedules.disable',
396
- 'schedules.enable',
397
- 'schedules.run',
402
+ 'automation.schedules.delete',
403
+ 'automation.schedules.disable',
404
+ 'automation.schedules.enable',
405
+ 'automation.schedules.run',
398
406
  ],
399
407
  purpose: 'Perform one explicit allowlisted approval, automation, run, or schedule action.',
400
408
  }),
@@ -404,7 +412,7 @@ export function connectedHostCapabilityMap(toolRegistry: ToolRegistry): readonly
404
412
  modelTools: ['schedule', 'agent_schedule_edit'],
405
413
  workspaceCategories: ['automation', 'personal-ops'],
406
414
  slashCommandFamilies: ['schedule'],
407
- methodIds: ['automation.jobs.patch'],
415
+ methodIds: ['automation.jobs.update'],
408
416
  purpose: 'Edit one explicit connected schedule name, cadence, or prompt.',
409
417
  }),
410
418
  withAvailability({
@@ -737,6 +745,6 @@ export function settingsPolicySummary(): Record<string, unknown> {
737
745
  mutation: 'Use settings action:"set" or action:"reset" with key, target, or query plus confirm:true and explicitUserRequest; ambiguous setting lookups are refused.',
738
746
  secretHandling: 'Raw secret values are persisted through the secret manager; config receives only a secret reference and tool output is redacted.',
739
747
  writablePolicy: 'Each setting descriptor includes writable, visibleInWorkspace, and lockReason when applicable.',
740
- protectedRawDangerKeys: ['danger.daemon', 'danger.httpListener'],
748
+ protectedRawDangerKeys: ['danger.httpListener'],
741
749
  };
742
750
  }
@@ -43,9 +43,35 @@ interface OperatorMethodDescriptor {
43
43
  readonly category: string;
44
44
  readonly access: string;
45
45
  readonly scopes: readonly string[];
46
+ readonly available: boolean;
46
47
  readonly parameters?: readonly Record<string, unknown>[];
47
48
  }
48
49
 
50
+ /**
51
+ * W4-A3 (capability-advertisement honesty): the SDK operator contract marks
52
+ * a method `invokable: false` when it is cataloged but not backed by a real
53
+ * daemon route (see @pellux/goodvibes-sdk's method-catalog-route-reconcile —
54
+ * email.inbox.list/read, email.draft.create, email.send are the dogfood
55
+ * case: they advertise /api/email/* paths no router dispatch chain serves).
56
+ * Before this, `invokable` was declared on the local contract-method shape
57
+ * but never read — an unavailable method looked identical to a live one in
58
+ * this catalog, so the model (or a human skimming host action:"methods")
59
+ * had no way to tell the ad from the reality short of trying the call and
60
+ * getting a 404. Degrading the listing here means the ad itself — not just
61
+ * the eventual answer — tells the truth.
62
+ */
63
+ function methodIsAvailable(method: OperatorContractMethod): boolean {
64
+ return method.invokable !== false;
65
+ }
66
+
67
+ const UNAVAILABLE_MODEL_TOOL = 'none — unavailable; do not call this method';
68
+ const UNAVAILABLE_CONFIRMATION = 'Unavailable: cataloged by the daemon but not backed by a served route. Do not call it; it will not succeed.';
69
+ const UNAVAILABLE_BOUNDARY = 'This method is advertised in the operator contract but the daemon does not currently serve its route (invokable:false). Treat it as absent — do not attempt to call it, and tell the user the capability is not wired up rather than guessing at a workaround.';
70
+
71
+ function unavailableLabel(method: OperatorContractMethod): string {
72
+ return `${method.id} — unavailable (route not served by this daemon)`;
73
+ }
74
+
49
75
  type OperatorMethodResolution =
50
76
  | { readonly status: 'found'; readonly method: Record<string, unknown> }
51
77
  | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
@@ -121,22 +147,26 @@ function toDescriptor(method: OperatorContractMethod): OperatorMethodDescriptor
121
147
  const httpMethod = method.http?.method?.toUpperCase() ?? 'GET';
122
148
  const path = method.http?.path ?? '/';
123
149
  const effect = methodEffect(method);
124
- const label = method.title ?? method.description ?? method.id;
150
+ const available = methodIsAvailable(method);
151
+ const label = available ? (method.title ?? method.description ?? method.id) : unavailableLabel(method);
125
152
  return {
126
153
  id: method.id,
127
154
  label,
128
155
  route: `${httpMethod} ${path}`,
129
156
  effect,
130
157
  owner: 'goodvibes-daemon',
131
- preferredModelTool: preferredToolFor(effect),
132
- confirmation: confirmationFor(effect),
133
- boundary: effect === 'read-only-network'
134
- ? 'Reads the connected GoodVibes daemon operator route and returns a redacted response.'
135
- : 'Runs a confirmed connected GoodVibes daemon operator route. The model must keep the action visible, reversible when possible, and tied to the user request.',
158
+ preferredModelTool: available ? preferredToolFor(effect) : UNAVAILABLE_MODEL_TOOL,
159
+ confirmation: available ? confirmationFor(effect) : UNAVAILABLE_CONFIRMATION,
160
+ boundary: available
161
+ ? (effect === 'read-only-network'
162
+ ? 'Reads the connected GoodVibes daemon operator route and returns a redacted response.'
163
+ : 'Runs a confirmed connected GoodVibes daemon operator route. The model must keep the action visible, reversible when possible, and tied to the user request.')
164
+ : UNAVAILABLE_BOUNDARY,
136
165
  category: method.category ?? 'uncategorized',
137
166
  access: method.access ?? 'authenticated',
138
167
  scopes: method.scopes ?? [],
139
- parameters: parametersFromInputSchema(method),
168
+ available,
169
+ parameters: available ? parametersFromInputSchema(method) : [],
140
170
  };
141
171
  }
142
172
 
@@ -170,6 +200,7 @@ function describeMethod(
170
200
  effect: method.effect,
171
201
  owner: method.owner,
172
202
  route: method.route,
203
+ available: method.available,
173
204
  modelRoute: previewHarnessText(method.preferredModelTool),
174
205
  scopes: method.scopes,
175
206
  confirmation: method.confirmation,
@@ -190,6 +221,7 @@ function describeCandidate(method: OperatorMethodDescriptor): Record<string, unk
190
221
  category: method.category,
191
222
  effect: method.effect,
192
223
  route: method.route,
224
+ available: method.available,
193
225
  modelRoute: previewHarnessText(method.preferredModelTool),
194
226
  };
195
227
  }
@@ -209,14 +241,18 @@ export function operatorMethodCatalogStatus(): Record<string, unknown> {
209
241
  acc[method.category] = (acc[method.category] ?? 0) + 1;
210
242
  return acc;
211
243
  }, {});
244
+ const unavailableMethods = methods.filter((method) => !method.available).length;
212
245
  return {
213
246
  modes: ['operator_methods', 'operator_method'],
214
247
  methods: methods.length,
215
248
  readOnlyMethods: methods.filter((method) => method.effect === 'read-only-network').length,
216
249
  confirmedMethods: methods.filter((method) => method.effect !== 'read-only-network').length,
217
250
  adminMethods: methods.filter((method) => method.effect === 'confirmed-admin-connected-host-state').length,
251
+ unavailableMethods,
218
252
  categories,
219
- policy: 'Full GoodVibes SDK operator contract. Read-only routes can run through agent_operator_method; write/admin routes require confirm:true and explicitUserRequest.',
253
+ policy: unavailableMethods > 0
254
+ ? `Full GoodVibes SDK operator contract. Read-only routes can run through agent_operator_method; write/admin routes require confirm:true and explicitUserRequest. ${unavailableMethods} cataloged method(s) are currently unavailable (no served route) — check each method's "available" field before treating it as callable.`
255
+ : 'Full GoodVibes SDK operator contract. Read-only routes can run through agent_operator_method; write/admin routes require confirm:true and explicitUserRequest.',
220
256
  };
221
257
  }
222
258
 
@@ -35,17 +35,56 @@ export function methodSearchText(method: OperatorContractMethod): string {
35
35
  ].filter(Boolean).join('\n').toLowerCase();
36
36
  }
37
37
 
38
- export function methodIdsMatching(tokens: readonly string[]): readonly string[] {
38
+ /**
39
+ * W4-A3/W4-A5 capability honesty: a method whose contract entry carries
40
+ * invokable:false is cataloged but NOT dispatchable — no daemon route or
41
+ * handler serves it. Counting it toward "workflow ready" would repeat the
42
+ * dogfood finding (a lane card claiming readiness backed only by methods a
43
+ * caller cannot actually invoke). Absent flag = invokable (older contracts).
44
+ */
45
+ export function isInvokableContractMethod(method: OperatorContractMethod): boolean {
46
+ return method.invokable !== false;
47
+ }
48
+
49
+ function matchingContractMethods(tokens: readonly string[]): readonly OperatorContractMethod[] {
39
50
  if (tokens.length === 0) return [];
40
- return operatorContractMethods()
41
- .filter((method) => {
42
- const text = methodSearchText(method);
43
- return tokens.some((token) => text.includes(token));
44
- })
51
+ return operatorContractMethods().filter((method) => {
52
+ const text = methodSearchText(method);
53
+ return tokens.some((token) => text.includes(token));
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Method ids matching the tokens that are actually DISPATCHABLE. Readiness
59
+ * (workflowStatus, lane status, capability counts) must be computed from
60
+ * this list only — advertised-but-unavailable methods are returned
61
+ * separately by unavailableMethodIdsMatching for honest degraded wording.
62
+ */
63
+ export function methodIdsMatching(tokens: readonly string[]): readonly string[] {
64
+ return matchingContractMethods(tokens)
65
+ .filter(isInvokableContractMethod)
45
66
  .map((method) => method.id)
46
67
  .sort((left, right) => left.localeCompare(right));
47
68
  }
48
69
 
70
+ /** Method ids matching the tokens that are cataloged but NOT dispatchable (invokable:false). */
71
+ export function unavailableMethodIdsMatching(tokens: readonly string[]): readonly string[] {
72
+ return matchingContractMethods(tokens)
73
+ .filter((method) => !isInvokableContractMethod(method))
74
+ .map((method) => method.id)
75
+ .sort((left, right) => left.localeCompare(right));
76
+ }
77
+
78
+ /**
79
+ * One honest line naming advertised-but-undispatchable methods, matching the
80
+ * W4-A3 degraded-ad pattern ("unavailable (route not served by this daemon)").
81
+ * Returns null when nothing is degraded.
82
+ */
83
+ export function unavailableMethodsNote(unavailableMethodIds: readonly string[]): string | null {
84
+ if (unavailableMethodIds.length === 0) return null;
85
+ return `${unavailableMethodIds.length} advertised method(s) are unavailable (route not served by this daemon) and cannot be dispatched: ${unavailableMethodIds.join(', ')}.`;
86
+ }
87
+
49
88
  export function mcpServerRecords(context: CommandContext): readonly {
50
89
  readonly name: string;
51
90
  readonly connected: boolean;
@@ -337,14 +376,18 @@ export function workflowInspectRoutes(methodIds: readonly string[], connectors:
337
376
  return routes.length > 0 ? routes : [`personal_ops action:"lane" laneId:"${fallbackQuery}"`];
338
377
  }
339
378
 
340
- export function inboxWorkflows(methodIds: readonly string[], connectors: readonly PersonalOpsConnectorSignal[]): readonly PersonalOpsWorkflow[] {
379
+ export function inboxWorkflows(methodIds: readonly string[], connectors: readonly PersonalOpsConnectorSignal[], unavailableMethodIds: readonly string[] = []): readonly PersonalOpsWorkflow[] {
341
380
  const status = workflowStatus(methodIds, connectors);
342
381
  const inspectRoutes = workflowInspectRoutes(methodIds, connectors, 'inbox');
343
382
  const modelRoute = workflowModelRoute('email', connectors);
344
383
  const readToolCount = connectorToolCount(connectors, 'read-only');
345
384
  const writeToolCount = connectorToolCount(connectors, 'confirmed-effect');
385
+ const degradedAdNote = unavailableMethodsNote(unavailableMethodIds);
346
386
  const setupPrerequisite = status === 'needs-setup'
347
- ? ['Install or configure an email-capable daemon method, MCP server, or plugin first.']
387
+ ? [
388
+ 'Install or configure an email-capable daemon method, MCP server, or plugin first.',
389
+ ...(degradedAdNote ? [degradedAdNote] : []),
390
+ ]
348
391
  : status === 'attention'
349
392
  ? ['Review connector trust/schema freshness before reading inbox data.']
350
393
  : [
@@ -389,14 +432,18 @@ export function inboxWorkflows(methodIds: readonly string[], connectors: readonl
389
432
  ];
390
433
  }
391
434
 
392
- export function calendarWorkflows(methodIds: readonly string[], connectors: readonly PersonalOpsConnectorSignal[]): readonly PersonalOpsWorkflow[] {
435
+ export function calendarWorkflows(methodIds: readonly string[], connectors: readonly PersonalOpsConnectorSignal[], unavailableMethodIds: readonly string[] = []): readonly PersonalOpsWorkflow[] {
393
436
  const status = workflowStatus(methodIds, connectors);
394
437
  const inspectRoutes = workflowInspectRoutes(methodIds, connectors, 'calendar');
395
438
  const modelRoute = workflowModelRoute('calendar', connectors);
396
439
  const readToolCount = connectorToolCount(connectors, 'read-only');
397
440
  const writeToolCount = connectorToolCount(connectors, 'confirmed-effect');
441
+ const degradedAdNote = unavailableMethodsNote(unavailableMethodIds);
398
442
  const setupPrerequisite = status === 'needs-setup'
399
- ? ['Install or configure a calendar-capable daemon method, CalDAV MCP server, or plugin first.']
443
+ ? [
444
+ 'Install or configure a calendar-capable daemon method, CalDAV MCP server, or plugin first.',
445
+ ...(degradedAdNote ? [degradedAdNote] : []),
446
+ ]
400
447
  : status === 'attention'
401
448
  ? ['Review connector trust/schema freshness before reading calendar data.']
402
449
  : [
@@ -1,6 +1,6 @@
1
1
  import type { CommandContext } from '../input/command-registry.ts';
2
2
  import { buildAgentWorkspaceRuntimeSnapshot } from '../input/agent-workspace-snapshot.ts';
3
- import { calendarWorkflows, connectorSignalsMatching, inboxWorkflows, methodIdsMatching, reminderWorkflows, taskWorkflows } from './agent-harness-personal-ops-discovery.ts';
3
+ import { calendarWorkflows, connectorSignalsMatching, inboxWorkflows, methodIdsMatching, reminderWorkflows, taskWorkflows, unavailableMethodIdsMatching } from './agent-harness-personal-ops-discovery.ts';
4
4
  import { providerBackedQueueRecords } from './agent-harness-personal-ops-provider-records.ts';
5
5
  import { providerBackedReminderRecords, providerBackedTaskRecords } from './agent-harness-personal-ops-provider-task-records.ts';
6
6
  import { channelRecords, connectorRecords, localRecord, refreshableSavedRecordCount, reminderOperationRecords, routineReceiptRecord, savedProviderEffectReceiptRecords, savedReviewArtifactRecords, savedReviewQueueRecords, taskOperationRecords } from './agent-harness-personal-ops-records.ts';
@@ -15,8 +15,14 @@ export function buildLanes(
15
15
  } = {},
16
16
  ): readonly PersonalOpsLane[] {
17
17
  const snapshot = buildAgentWorkspaceRuntimeSnapshot(context);
18
+ // Readiness counts only DISPATCHABLE methods (W4-A3/W4-A5 honesty): a
19
+ // method the contract marks invokable:false has no serving route, so it
20
+ // must not make a lane read "ready"/"partial". The unavailable lists feed
21
+ // honest degraded wording instead.
18
22
  const emailMethods = methodIdsMatching(['email', 'mail', 'imap', 'smtp']);
23
+ const unavailableEmailMethods = unavailableMethodIdsMatching(['email', 'mail', 'imap', 'smtp']);
19
24
  const calendarMethods = methodIdsMatching(['calendar', 'caldav', 'agenda']);
25
+ const unavailableCalendarMethods = unavailableMethodIdsMatching(['calendar', 'caldav', 'agenda']);
20
26
  const toolsByName = options.toolsByServer ?? new Map<string, readonly McpToolRecord[]>();
21
27
  const schemasByQualifiedName = options.schemasByQualifiedName ?? new Map<string, McpToolSchema>();
22
28
  const emailConnectors = connectorSignalsMatching(context, ['email', 'mail', 'imap', 'smtp', 'gmail'], { lane: 'inbox', toolsByServer: toolsByName, schemasByQualifiedName });
@@ -66,6 +72,8 @@ export function buildLanes(
66
72
  ? 'Saved inbox review artifacts and thread queue items are available for recap, local draft review, or promotion; no fresh email connector is currently ready.'
67
73
  : inboxEffectReceiptRecords.length > 0
68
74
  ? 'Saved inbox provider-effect receipts are available for audit and follow-up; no fresh email connector is currently ready.'
75
+ : unavailableEmailMethods.length > 0
76
+ ? `The operator contract advertises ${unavailableEmailMethods.length} email method(s), but they are unavailable (route not served by this daemon) and cannot be dispatched.`
69
77
  : 'No email/IMAP/SMTP methods are present in the current GoodVibes SDK operator contract.',
70
78
  next: inboxProviderRecords.length > 0
71
79
  ? 'Inspect one fresh thread record, summarize or draft locally, and use only the published confirmed follow-up routes for replies, sends, labels, or archive.'
@@ -82,7 +90,8 @@ export function buildLanes(
82
90
  modelRoute: emailConnectors.length > 0 ? 'agent_harness mode:"mcp_servers" query:"email"' : 'host action:"methods" query:"email"',
83
91
  signals: [
84
92
  `${inboxProviderRecords.length} fresh provider-backed inbox thread record(s)`,
85
- `${emailMethods.length} email-like daemon method(s)`,
93
+ `${emailMethods.length} dispatchable email-like daemon method(s)`,
94
+ ...(unavailableEmailMethods.length > 0 ? [`${unavailableEmailMethods.length} advertised-but-unavailable email method(s) (route not served by this daemon)`] : []),
86
95
  `${emailConnectors.length} email-like MCP connector(s)`,
87
96
  `${inboxArtifactRecords.length} saved inbox review artifact(s)`,
88
97
  `${inboxReviewQueueRecords.length} saved inbox thread queue item(s)`,
@@ -92,7 +101,7 @@ export function buildLanes(
92
101
  ],
93
102
  methodIds: emailMethods,
94
103
  connectorSignals: emailConnectors,
95
- workflows: [...inboxWorkflows(emailMethods, emailConnectors), styleReplyAdditions.workflow],
104
+ workflows: [...inboxWorkflows(emailMethods, emailConnectors, unavailableEmailMethods), styleReplyAdditions.workflow],
96
105
  liveRecords: [
97
106
  ...inboxProviderRecords,
98
107
  ...inboxReviewQueueRecords,
@@ -119,6 +128,8 @@ export function buildLanes(
119
128
  ? 'Saved calendar review artifacts and event queue items are available for agenda recap, reminder creation, or follow-up planning; no fresh calendar connector is currently ready.'
120
129
  : calendarEffectReceiptRecords.length > 0
121
130
  ? 'Saved calendar provider-effect receipts are available for audit and follow-up; no fresh calendar connector is currently ready.'
131
+ : unavailableCalendarMethods.length > 0
132
+ ? `The operator contract advertises ${unavailableCalendarMethods.length} calendar method(s), but they are unavailable (route not served by this daemon) and cannot be dispatched. The local Agent calendar (/calendar) remains available.`
122
133
  : 'No calendar/CalDAV/agenda methods are present in the current GoodVibes SDK operator contract.',
123
134
  next: calendarProviderRecords.length > 0
124
135
  ? 'Inspect one fresh event record, brief the agenda or conflicts locally, and use only the published confirmed follow-up routes for edits, RSVP, reschedule, or delete.'
@@ -135,7 +146,8 @@ export function buildLanes(
135
146
  modelRoute: calendarConnectors.length > 0 ? 'agent_harness mode:"mcp_servers" query:"calendar"' : 'host action:"methods" query:"calendar"',
136
147
  signals: [
137
148
  `${calendarProviderRecords.length} fresh provider-backed calendar event record(s)`,
138
- `${calendarMethods.length} calendar-like daemon method(s)`,
149
+ `${calendarMethods.length} dispatchable calendar-like daemon method(s)`,
150
+ ...(unavailableCalendarMethods.length > 0 ? [`${unavailableCalendarMethods.length} advertised-but-unavailable calendar method(s) (route not served by this daemon)`] : []),
139
151
  `${calendarConnectors.length} calendar-like MCP connector(s)`,
140
152
  `${calendarArtifactRecords.length} saved calendar review artifact(s)`,
141
153
  `${calendarReviewQueueRecords.length} saved calendar event queue item(s)`,
@@ -145,7 +157,7 @@ export function buildLanes(
145
157
  ],
146
158
  methodIds: calendarMethods,
147
159
  connectorSignals: calendarConnectors,
148
- workflows: calendarWorkflows(calendarMethods, calendarConnectors),
160
+ workflows: calendarWorkflows(calendarMethods, calendarConnectors, unavailableCalendarMethods),
149
161
  liveRecords: [
150
162
  ...calendarProviderRecords,
151
163
  ...calendarReviewQueueRecords,
@@ -107,7 +107,7 @@ export function reminderOperationRecords(methodIds: readonly string[], deliveryC
107
107
  {
108
108
  id: 'reminder-create',
109
109
  label: 'Create confirmed reminder',
110
- status: hasMethod(methodIds, 'schedules.create') ? deliveryConfigured ? 'ready' : 'attention' : 'needs-setup',
110
+ status: hasMethod(methodIds, 'automation.schedules.create') ? deliveryConfigured ? 'ready' : 'attention' : 'needs-setup',
111
111
  summary: deliveryConfigured
112
112
  ? 'Create one connected reminder schedule with real timing and a visible delivery path.'
113
113
  : 'Create one reminder only after confirming timing and delivery scope; no configured delivery target was detected.',
@@ -123,7 +123,7 @@ export function reminderOperationRecords(methodIds: readonly string[], deliveryC
123
123
  {
124
124
  id: 'autonomous-schedule-create',
125
125
  label: 'Create autonomous schedule',
126
- status: hasMethod(methodIds, 'schedules.create') ? 'ready' : 'needs-setup',
126
+ status: hasMethod(methodIds, 'automation.schedules.create') ? 'ready' : 'needs-setup',
127
127
  summary: 'Create one visible autonomous schedule only when task, cadence, success criteria, and user request provenance are explicit.',
128
128
  userRoute: 'Agent Workspace -> Automation -> Create schedule',
129
129
  modelRoute: 'schedule action:"create" task:"..." successCriteria:"..." scheduleKind:"..." scheduleValue:"..." confirm:true explicitUserRequest:"..."',
@@ -134,7 +134,7 @@ export function reminderOperationRecords(methodIds: readonly string[], deliveryC
134
134
  confirmationRequired: true,
135
135
  },
136
136
  ];
137
- if (hasMethod(methodIds, 'schedules.list')) {
137
+ if (hasMethod(methodIds, 'automation.schedules.list')) {
138
138
  records.push({
139
139
  id: 'schedule-list',
140
140
  label: 'List connected schedules',
@@ -161,7 +161,7 @@ export function reminderOperationRecords(methodIds: readonly string[], deliveryC
161
161
  confirmationRequired: true,
162
162
  });
163
163
  }
164
- if (hasMethod(methodIds, 'schedules.run')) {
164
+ if (hasMethod(methodIds, 'automation.schedules.run')) {
165
165
  records.push({
166
166
  id: 'schedule-run-now',
167
167
  label: 'Run schedule now',
@@ -176,7 +176,7 @@ export function reminderOperationRecords(methodIds: readonly string[], deliveryC
176
176
  confirmationRequired: true,
177
177
  });
178
178
  }
179
- if (hasMethod(methodIds, 'schedules.disable')) {
179
+ if (hasMethod(methodIds, 'automation.schedules.disable')) {
180
180
  records.push({
181
181
  id: 'schedule-pause',
182
182
  label: 'Pause connected schedule',
@@ -191,7 +191,7 @@ export function reminderOperationRecords(methodIds: readonly string[], deliveryC
191
191
  confirmationRequired: true,
192
192
  });
193
193
  }
194
- if (hasMethod(methodIds, 'schedules.enable')) {
194
+ if (hasMethod(methodIds, 'automation.schedules.enable')) {
195
195
  records.push({
196
196
  id: 'schedule-resume',
197
197
  label: 'Resume connected schedule',
@@ -206,7 +206,7 @@ export function reminderOperationRecords(methodIds: readonly string[], deliveryC
206
206
  confirmationRequired: true,
207
207
  });
208
208
  }
209
- if (hasMethod(methodIds, 'schedules.delete')) {
209
+ if (hasMethod(methodIds, 'automation.schedules.delete')) {
210
210
  records.push({
211
211
  id: 'schedule-delete',
212
212
  label: 'Delete connected schedule',
@@ -28,6 +28,12 @@ export interface OperatorContractMethod {
28
28
  readonly title?: string;
29
29
  readonly description?: string;
30
30
  readonly category?: string;
31
+ /**
32
+ * W4-A3 capability-advertisement honesty: false means the method is
33
+ * cataloged but NOT dispatchable (no route/handler serves it). Absent
34
+ * means invokable (older contracts predate the flag).
35
+ */
36
+ readonly invokable?: boolean;
31
37
  readonly http?: {
32
38
  readonly method?: string;
33
39
  readonly path?: string;