@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
@@ -0,0 +1,108 @@
1
+ /**
2
+ * System-message noise policy — decides whether an operational status message
3
+ * should reach the Recent feed / transcript unchanged, be dropped, or be folded.
4
+ * Every noisy source these rules catch funnels through SystemMessageRouter, so
5
+ * this one classifier is where first-run plumbing is kept out of the user's
6
+ * stream while the information stays reachable elsewhere (activity log, /health,
7
+ * /model, the fleet/agents surface).
8
+ *
9
+ * Ported from the TUI (src/core/system-message-noise.ts). The TUI regexes are
10
+ * kept VERBATIM (provider-replay fold / agents-snapshot drop / stale-replay
11
+ * drop); the only agent-specific addition is the `[Terminal] Captured …` rule
12
+ * (W4-R2, absorbing A8 papercut #3 — the stdout-capture leak). Dropped noise is
13
+ * drop-from-the-feed, NOT delete: the captured-write detail stays reachable via
14
+ * the activity log (the terminal-output guard logs each intercept + the count).
15
+ */
16
+
17
+ export interface NoiseGateDeps {
18
+ /**
19
+ * True when a WRFC chain is terminal (passed/failed) or gone (killed/removed).
20
+ * Used to suppress stale "[Replay] … transitioned … waiting for action"
21
+ * re-notifications for chains that can no longer act. Absent → never suppress.
22
+ */
23
+ readonly isChainTerminal?: (chainId: string) => boolean;
24
+ }
25
+
26
+ export type NoiseVerdict =
27
+ /** Pass through to the feed/transcript unchanged. */
28
+ | { readonly action: 'emit' }
29
+ /** Suppress entirely — reachable via another live surface. */
30
+ | { readonly action: 'drop' }
31
+ /** Provider "from last session" replay line — buffer and fold to one line. */
32
+ | { readonly action: 'foldProviderReplay' };
33
+
34
+ const EMIT: NoiseVerdict = { action: 'emit' };
35
+ const DROP: NoiseVerdict = { action: 'drop' };
36
+ const FOLD: NoiseVerdict = { action: 'foldProviderReplay' };
37
+
38
+ /** Matches the SDK's per-provider persisted-replay line (1b). */
39
+ const PROVIDER_REPLAY_RE = / — from last session$/;
40
+
41
+ /** Matches the SDK's 30s periodic running-agents snapshot (1d). */
42
+ const AGENTS_RUNNING_SNAPSHOT_RE = /^\[Agents\] \d+ running:/;
43
+
44
+ /** Extracts the chain id from a "[Replay]… WRFC chain <id> transitioned …" line (1c). */
45
+ const REPLAY_CHAIN_RE = /WRFC chain (\S+) transitioned .* waiting for action/;
46
+
47
+ /**
48
+ * Matches the terminal-output guard's aggregate captured-write notice
49
+ * (W4-R2, agent-specific). First-run plumbing writes a burst of direct stdout
50
+ * that the guard intercepts and would otherwise summarize into the Recent feed;
51
+ * the count stays reachable in the activity log, so the feed copy is dropped.
52
+ */
53
+ const TERMINAL_CAPTURED_RE = /^\[Terminal\] Captured \d+ direct /;
54
+
55
+ /**
56
+ * Classify one system message. Pure aside from the injected `isChainTerminal`
57
+ * lookup, so it is trivially testable.
58
+ */
59
+ export function classifyNoise(message: string, deps: NoiseGateDeps): NoiseVerdict {
60
+ // agent (W4-R2) — the terminal-output guard's aggregate captured-write notice.
61
+ // Kept out of the Recent feed; the detail stays in the activity log.
62
+ if (TERMINAL_CAPTURED_RE.test(message)) {
63
+ return DROP;
64
+ }
65
+
66
+ // 1b — provider-discovery replay burst ("[Local] … — from last session").
67
+ if (message.startsWith('[Local]') && PROVIDER_REPLAY_RE.test(message)) {
68
+ return FOLD;
69
+ }
70
+
71
+ // 1d — periodic "[Agents] N running:" status snapshot. The same live detail
72
+ // is shown in the fleet/agents surface and the footer count, so the periodic
73
+ // transcript churn is dropped. Meaningful lifecycle lines ("[Agents] ✓ …",
74
+ // "[Agents] Cohort …", "[Agents] ✗ …") do not match.
75
+ if (AGENTS_RUNNING_SNAPSHOT_RE.test(message)) {
76
+ return DROP;
77
+ }
78
+
79
+ // 1c — stale "[Replay] … waiting for action" for a terminal/killed chain.
80
+ if (message.startsWith('[Replay]') && deps.isChainTerminal) {
81
+ const match = REPLAY_CHAIN_RE.exec(message);
82
+ if (match && deps.isChainTerminal(match[1])) return DROP;
83
+ }
84
+
85
+ return EMIT;
86
+ }
87
+
88
+ /** Provider name from a "[Local] <name> at host:port (…) — from last session" line. */
89
+ export function providerNameFromReplay(message: string): string {
90
+ const match = /^\[Local\]\s+(.+?)\s+at\s/.exec(message);
91
+ if (match) return match[1];
92
+ // Fallback: first token after the tag.
93
+ const bare = /^\[Local\]\s+(\S+)/.exec(message);
94
+ return bare ? bare[1] : 'provider';
95
+ }
96
+
97
+ /**
98
+ * Build the single folded summary line for a burst of provider-replay lines.
99
+ * Names a few providers; the full list stays reachable via /health and /model.
100
+ */
101
+ export function foldProviderReplayLines(messages: readonly string[]): string {
102
+ const names = messages.map(providerNameFromReplay);
103
+ const count = names.length;
104
+ const noun = count === 1 ? 'provider' : 'providers';
105
+ const shown = names.slice(0, 3).join(', ');
106
+ const extra = count > 3 ? `, +${count - 3} more` : '';
107
+ return `[Local] Restored ${count} ${noun} from last session (${shown}${extra})`;
108
+ }
@@ -20,6 +20,7 @@
20
20
 
21
21
  import type { ConversationManager } from './conversation';
22
22
  import type { ActivityFeed } from './activity-feed.ts';
23
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
23
24
  import {
24
25
  classifySystemMessageKind,
25
26
  classifySystemMessagePriority,
@@ -28,6 +29,12 @@ import {
28
29
  type SystemMessageKind,
29
30
  type SystemMessageTarget,
30
31
  } from '@/runtime/index.ts';
32
+ import {
33
+ classifyNoise,
34
+ foldProviderReplayLines,
35
+ providerNameFromReplay,
36
+ type NoiseGateDeps,
37
+ } from './system-message-noise.ts';
31
38
 
32
39
  export type {
33
40
  SystemMessageKind,
@@ -41,10 +48,16 @@ export type SystemMessagePriority = 'high' | 'low';
41
48
  * based on priority level and per-kind targets.
42
49
  */
43
50
  export class SystemMessageRouter {
51
+ /** Buffered provider "from last session" replay lines, folded on a microtask. */
52
+ private providerReplayBuffer: string[] = [];
53
+ private providerReplayScheduled = false;
54
+
44
55
  constructor(
45
56
  private readonly conversation: ConversationManager,
46
57
  private feed: ActivityFeed | null,
47
58
  private readonly getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget = defaultSystemMessageTarget,
59
+ /** Noise-gate dependencies (WRFC terminal-chain lookup). See system-message-noise.ts. */
60
+ private readonly noiseDeps: NoiseGateDeps = {},
48
61
  ) {}
49
62
 
50
63
  // ── Public API ────────────────────────────────────────────────────────────────
@@ -54,6 +67,16 @@ export class SystemMessageRouter {
54
67
  priority: SystemMessagePriority,
55
68
  kind: SystemMessageKind,
56
69
  ): void {
70
+ // Noise gate — keep first-run plumbing out of the Recent feed / transcript
71
+ // while the information stays reachable via other live surfaces (activity
72
+ // log, /health, /model). Dropped noise is drop-from-the-feed, not delete.
73
+ const verdict = classifyNoise(message, this.noiseDeps);
74
+ if (verdict.action === 'drop') return;
75
+ if (verdict.action === 'foldProviderReplay') {
76
+ this.bufferProviderReplay(message);
77
+ return;
78
+ }
79
+
57
80
  const target = this.getTargetForKind(kind);
58
81
  const delivery = resolveSystemMessageDelivery(target, this.feed !== null);
59
82
  if (delivery.toPanel) {
@@ -64,6 +87,35 @@ export class SystemMessageRouter {
64
87
  }
65
88
  }
66
89
 
90
+ /**
91
+ * Buffer a provider "from last session" replay line and schedule a microtask
92
+ * flush. The SDK emits the whole persisted-provider burst synchronously, so a
93
+ * single microtask captures the full burst and folds it to one quiet line.
94
+ */
95
+ private bufferProviderReplay(message: string): void {
96
+ this.providerReplayBuffer.push(message);
97
+ if (this.providerReplayScheduled) return;
98
+ this.providerReplayScheduled = true;
99
+ queueMicrotask(() => this.flushProviderReplay());
100
+ }
101
+
102
+ /**
103
+ * Record the folded provider-replay summary to the activity log and reset the
104
+ * buffer. The boot-only "— from last session" burst stays out of the Recent
105
+ * feed; the persisted-provider set it summarizes is reachable on demand via
106
+ * /health and /model, and the fold line is logged for diagnosis. Only the
107
+ * boot burst folds — mid-session provider-discovery lines never match
108
+ * PROVIDER_REPLAY_RE, so they still reach the feed as live product signal.
109
+ */
110
+ flushProviderReplay(): void {
111
+ this.providerReplayScheduled = false;
112
+ if (this.providerReplayBuffer.length === 0) return;
113
+ const summary = foldProviderReplayLines(this.providerReplayBuffer);
114
+ const providerNames = this.providerReplayBuffer.map(providerNameFromReplay);
115
+ this.providerReplayBuffer = [];
116
+ logger.info(summary, { count: providerNames.length, providers: providerNames });
117
+ }
118
+
67
119
  routeSystemMessage(message: string, priority: SystemMessagePriority): void {
68
120
  this.routeTypedSystemMessage(message, priority, classifySystemMessageKind(message));
69
121
  }
@@ -117,6 +169,7 @@ export function createSystemMessageRouter(
117
169
  conversation: ConversationManager,
118
170
  feed: ActivityFeed | null = null,
119
171
  getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget = defaultSystemMessageTarget,
172
+ noiseDeps: NoiseGateDeps = {},
120
173
  ): SystemMessageRouter {
121
- return new SystemMessageRouter(conversation, feed, getTargetForKind);
174
+ return new SystemMessageRouter(conversation, feed, getTargetForKind, noiseDeps);
122
175
  }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * thinking-overlay.ts (W4-R4) — the thinking-indicator overlay + its honest
3
+ * stall clock, extracted from main.ts's render loop.
4
+ *
5
+ * The SDK orchestrator surfaces no lastDeltaAtMs / reconnect signal directly, so
6
+ * ThinkingStallClock derives a per-turn last-delta clock from streaming
7
+ * output-token advances — a real, honest proxy that degrades gracefully with
8
+ * zero new SDK events. buildThinkingOverlay turns that into the honest waiting
9
+ * state (via UIFactory.createThinkingFragment, which consumes the SDK
10
+ * presentation contract's waitingPhrase).
11
+ */
12
+
13
+ import { UIFactory, type ThinkingStallInfo } from '../renderer/ui-factory.ts';
14
+ import type { Line } from '../types/grid.ts';
15
+ import type { Orchestrator } from './orchestrator.ts';
16
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
17
+
18
+ /**
19
+ * Per-turn last-delta clock. tick() seeds at turn start, then pushes the clock
20
+ * forward whenever the streaming output-token count advances; reset() clears it
21
+ * so the next turn re-seeds. Suppresses stall detection while a tool is active
22
+ * (the model isn't producing tokens then — a "Stalled" label would be a false
23
+ * positive).
24
+ */
25
+ export class ThinkingStallClock {
26
+ private startedAt: number | null = null;
27
+ private lastDeltaAt = 0;
28
+ private lastOutputTokens = 0;
29
+
30
+ tick(streamingOutputTokens: number, toolActive: boolean, nowMs: number): ThinkingStallInfo | undefined {
31
+ if (this.startedAt === null) {
32
+ this.startedAt = nowMs;
33
+ this.lastDeltaAt = nowMs;
34
+ this.lastOutputTokens = streamingOutputTokens;
35
+ } else if (streamingOutputTokens > this.lastOutputTokens) {
36
+ this.lastDeltaAt = nowMs;
37
+ this.lastOutputTokens = streamingOutputTokens;
38
+ }
39
+ return UIFactory.computeRenderStallInfo({ toolActive, lastDeltaAtMs: this.lastDeltaAt, nowMs });
40
+ }
41
+
42
+ reset(): void {
43
+ this.startedAt = null;
44
+ }
45
+ }
46
+
47
+ export interface ThinkingOverlayDeps {
48
+ readonly orchestrator: Pick<Orchestrator,
49
+ 'isThinking' | 'getSpinner' | 'thinkingFrame' | 'streamingInputTokens' | 'streamingOutputTokens'>;
50
+ readonly configManager: Pick<ConfigManager, 'get'>;
51
+ /** The raw snapshot tool preview (a truthy value means a tool is executing). */
52
+ readonly streamToolPreview: string | undefined;
53
+ readonly streamTokenSpeed: number;
54
+ readonly approvalPending: boolean;
55
+ readonly width: number;
56
+ readonly clock: ThinkingStallClock;
57
+ }
58
+
59
+ /**
60
+ * Build the thinking-indicator overlay lines. Returns [] when not thinking (and
61
+ * resets the stall clock so the next turn re-seeds). The stall/approval signals
62
+ * drive the honest waiting state inside createThinkingFragment.
63
+ */
64
+ export function buildThinkingOverlay(deps: ThinkingOverlayDeps): Line[] {
65
+ if (!deps.orchestrator.isThinking) {
66
+ deps.clock.reset();
67
+ return [];
68
+ }
69
+ const showSpeed = deps.configManager.get('display.showTokenSpeed') as boolean;
70
+ const showPreview = deps.configManager.get('display.showToolPreview') as boolean;
71
+ const stallInfo = deps.clock.tick(deps.orchestrator.streamingOutputTokens, !!deps.streamToolPreview, Date.now());
72
+ return UIFactory.createThinkingFragment(
73
+ deps.width,
74
+ deps.orchestrator.getSpinner(),
75
+ deps.orchestrator.thinkingFrame,
76
+ showSpeed ? deps.streamTokenSpeed : undefined,
77
+ showPreview ? deps.streamToolPreview : undefined,
78
+ deps.orchestrator.streamingInputTokens > 0 ? deps.orchestrator.streamingInputTokens : undefined,
79
+ deps.orchestrator.streamingOutputTokens > 0 ? deps.orchestrator.streamingOutputTokens : undefined,
80
+ stallInfo,
81
+ deps.approvalPending,
82
+ );
83
+ }
@@ -1,149 +1,116 @@
1
- import type { AgentWorkspaceActionResult, AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
1
+ import type { AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
2
2
  import type { AgentWorkspaceAccessCommandEditorKind } from './agent-workspace-access-command-editors.ts';
3
3
  import { isAgentWorkspaceAccessCommandEditorKind } from './agent-workspace-access-command-editors.ts';
4
4
  import { quoteSlashCommandArg } from './slash-command-parser.ts';
5
+ import type { AgentWorkspaceCommandEditorSubmission, AgentWorkspaceCommandSubmissionHandler, AgentWorkspaceFieldReader } from './agent-workspace-command-editor-engine.ts';
6
+ import { buildCommandEditorSubmissionFromTable, dispatchCommandEditorSubmission, editorMessageSubmission, isAffirmative } from './agent-workspace-command-editor-engine.ts';
5
7
 
6
- type AgentWorkspaceFieldReader = (fieldId: string) => string;
7
-
8
- export type AgentWorkspaceAccessCommandEditorSubmission =
9
- | {
10
- readonly kind: 'editor';
11
- readonly editor: AgentWorkspaceLocalEditor;
12
- readonly status: string;
13
- readonly actionResult?: AgentWorkspaceActionResult;
14
- }
15
- | {
16
- readonly kind: 'dispatch';
17
- readonly command: string;
18
- readonly status: string;
19
- readonly actionResult: AgentWorkspaceActionResult;
20
- };
8
+ export type AgentWorkspaceAccessCommandEditorSubmission = AgentWorkspaceCommandEditorSubmission;
21
9
 
22
10
  export function isAgentWorkspaceAccessCommandSubmissionKind(kind: string): kind is AgentWorkspaceAccessCommandEditorKind {
23
11
  return isAgentWorkspaceAccessCommandEditorKind(kind as AgentWorkspaceAccessCommandEditorKind);
24
12
  }
25
13
 
26
- function isAffirmative(value: string): boolean {
27
- return /^(y|yes|true)$/i.test(value.trim());
28
- }
29
-
30
- function unconfirmed(editor: AgentWorkspaceLocalEditor, message: string): AgentWorkspaceAccessCommandEditorSubmission {
31
- return {
32
- kind: 'editor',
33
- editor: { ...editor, message },
34
- status: message,
35
- };
14
+ function unconfirmed(editor: AgentWorkspaceLocalEditor, message: string): AgentWorkspaceCommandEditorSubmission {
15
+ return editorMessageSubmission(editor, message);
36
16
  }
37
17
 
38
- function dispatch(command: string, title: string, detail: string, safety: AgentWorkspaceActionResult['safety']): AgentWorkspaceAccessCommandEditorSubmission {
39
- return {
40
- kind: 'dispatch',
41
- command,
42
- status: `${title}.`,
43
- actionResult: {
44
- kind: 'dispatched',
45
- title,
46
- detail,
47
- command,
48
- safety,
49
- },
50
- };
51
- }
52
-
53
- export function buildAgentWorkspaceAccessCommandEditorSubmission(
54
- editor: AgentWorkspaceLocalEditor,
55
- readField: AgentWorkspaceFieldReader,
56
- ): AgentWorkspaceAccessCommandEditorSubmission {
57
- if (editor.kind === 'auth-show') {
58
- return dispatch(
59
- `/auth show ${quoteSlashCommandArg(readField('provider'))}`,
60
- 'Opening provider auth inspection',
61
- 'The workspace handed a read-only provider auth inspection command to the shell-owned command router.',
62
- 'read-only',
63
- );
64
- }
65
- if (editor.kind === 'auth-repair') {
66
- return dispatch(
67
- `/auth repair ${quoteSlashCommandArg(readField('provider'))}`,
68
- 'Opening provider auth repair review',
69
- 'The workspace handed a read-only provider auth repair review command to the shell-owned command router.',
70
- 'read-only',
71
- );
72
- }
73
- if (editor.kind === 'auth-bundle-export') {
18
+ const ACCESS_COMMAND_SUBMISSION_HANDLERS: Readonly<Record<AgentWorkspaceAccessCommandEditorKind, AgentWorkspaceCommandSubmissionHandler>> = {
19
+ 'auth-show': (_editor, readField) => dispatchCommandEditorSubmission(
20
+ `/auth show ${quoteSlashCommandArg(readField('provider'))}`,
21
+ 'Opening provider auth inspection',
22
+ 'The workspace handed a read-only provider auth inspection command to the shell-owned command router.',
23
+ 'read-only',
24
+ ),
25
+ 'auth-repair': (_editor, readField) => dispatchCommandEditorSubmission(
26
+ `/auth repair ${quoteSlashCommandArg(readField('provider'))}`,
27
+ 'Opening provider auth repair review',
28
+ 'The workspace handed a read-only provider auth repair review command to the shell-owned command router.',
29
+ 'read-only',
30
+ ),
31
+ 'auth-bundle-export': (editor, readField) => {
74
32
  if (!isAffirmative(readField('confirm'))) return unconfirmed(editor, 'Auth review bundle export not confirmed. Type yes, then press Enter.');
75
- return dispatch(
33
+ return dispatchCommandEditorSubmission(
76
34
  `/auth bundle export ${quoteSlashCommandArg(readField('path'))} --yes`,
77
35
  'Opening auth review bundle export',
78
36
  'The workspace handed a confirmed auth review bundle export command to the shell-owned command router.',
79
37
  'safe',
80
38
  );
81
- }
82
- if (editor.kind === 'auth-bundle-inspect') {
83
- return dispatch(
84
- `/auth bundle inspect ${quoteSlashCommandArg(readField('path'))}`,
85
- 'Opening auth review bundle inspection',
86
- 'The workspace handed a read-only auth review bundle inspect command to the shell-owned command router.',
87
- 'read-only',
88
- );
89
- }
90
- if (editor.kind === 'trust-bundle-export') {
39
+ },
40
+ 'auth-bundle-inspect': (_editor, readField) => dispatchCommandEditorSubmission(
41
+ `/auth bundle inspect ${quoteSlashCommandArg(readField('path'))}`,
42
+ 'Opening auth review bundle inspection',
43
+ 'The workspace handed a read-only auth review bundle inspect command to the shell-owned command router.',
44
+ 'read-only',
45
+ ),
46
+ 'trust-bundle-export': (editor, readField) => {
91
47
  if (!isAffirmative(readField('confirm'))) return unconfirmed(editor, 'Trust bundle export not confirmed. Type yes, then press Enter.');
92
- return dispatch(
48
+ return dispatchCommandEditorSubmission(
93
49
  `/trust bundle export ${quoteSlashCommandArg(readField('path'))} --yes`,
94
50
  'Opening trust bundle export',
95
51
  'The workspace handed a confirmed trust bundle export command to the shell-owned command router.',
96
52
  'safe',
97
53
  );
98
- }
99
- if (editor.kind === 'trust-bundle-inspect') {
100
- return dispatch(
101
- `/trust bundle inspect ${quoteSlashCommandArg(readField('path'))}`,
102
- 'Opening trust bundle inspection',
103
- 'The workspace handed a read-only trust bundle inspect command to the shell-owned command router.',
104
- 'read-only',
105
- );
106
- }
107
- if (editor.kind === 'subscription-bundle-export') {
54
+ },
55
+ 'trust-bundle-inspect': (_editor, readField) => dispatchCommandEditorSubmission(
56
+ `/trust bundle inspect ${quoteSlashCommandArg(readField('path'))}`,
57
+ 'Opening trust bundle inspection',
58
+ 'The workspace handed a read-only trust bundle inspect command to the shell-owned command router.',
59
+ 'read-only',
60
+ ),
61
+ 'subscription-bundle-export': (editor, readField) => {
108
62
  if (!isAffirmative(readField('confirm'))) return unconfirmed(editor, 'Subscription bundle export not confirmed. Type yes, then press Enter.');
109
- return dispatch(
63
+ return dispatchCommandEditorSubmission(
110
64
  `/subscription bundle export ${quoteSlashCommandArg(readField('path'))} --yes`,
111
65
  'Opening subscription bundle export',
112
66
  'The workspace handed a confirmed subscription bundle export command to the shell-owned command router.',
113
67
  'safe',
114
68
  );
115
- }
116
- if (editor.kind === 'subscription-bundle-inspect') {
117
- return dispatch(
118
- `/subscription bundle inspect ${quoteSlashCommandArg(readField('path'))}`,
119
- 'Opening subscription bundle inspection',
120
- 'The workspace handed a read-only subscription bundle inspect command to the shell-owned command router.',
121
- 'read-only',
122
- );
123
- }
124
- if (editor.kind === 'voice-enable' || editor.kind === 'voice-disable') {
125
- if (!isAffirmative(readField('confirm'))) return unconfirmed(editor, `Voice interaction ${editor.kind === 'voice-enable' ? 'enable' : 'disable'} not confirmed. Type yes, then press Enter.`);
126
- const mode = editor.kind === 'voice-enable' ? 'enable' : 'disable';
127
- return dispatch(
128
- `/voice ${mode} --yes`,
129
- `Opening voice interaction ${mode}`,
130
- `The workspace handed a confirmed voice ${mode} command to the shell-owned command router.`,
131
- 'safe',
132
- );
133
- }
134
- if (editor.kind === 'voice-bundle-export') {
69
+ },
70
+ 'subscription-bundle-inspect': (_editor, readField) => dispatchCommandEditorSubmission(
71
+ `/subscription bundle inspect ${quoteSlashCommandArg(readField('path'))}`,
72
+ 'Opening subscription bundle inspection',
73
+ 'The workspace handed a read-only subscription bundle inspect command to the shell-owned command router.',
74
+ 'read-only',
75
+ ),
76
+ 'voice-enable': (editor, readField) => voiceEnableDisable(editor, readField),
77
+ 'voice-disable': (editor, readField) => voiceEnableDisable(editor, readField),
78
+ 'voice-bundle-export': (editor, readField) => {
135
79
  if (!isAffirmative(readField('confirm'))) return unconfirmed(editor, 'Voice bundle export not confirmed. Type yes, then press Enter.');
136
- return dispatch(
80
+ return dispatchCommandEditorSubmission(
137
81
  `/voice bundle export ${quoteSlashCommandArg(readField('path'))} --yes`,
138
82
  'Opening voice bundle export',
139
83
  'The workspace handed a confirmed voice bundle export command to the shell-owned command router.',
140
84
  'safe',
141
85
  );
142
- }
143
- return dispatch(
86
+ },
87
+ 'voice-bundle-inspect': (_editor, readField) => dispatchCommandEditorSubmission(
144
88
  `/voice bundle inspect ${quoteSlashCommandArg(readField('path'))}`,
145
89
  'Opening voice bundle inspection',
146
90
  'The workspace handed a read-only voice bundle inspect command to the shell-owned command router.',
147
91
  'read-only',
92
+ ),
93
+ };
94
+
95
+ function voiceEnableDisable(editor: AgentWorkspaceLocalEditor, readField: AgentWorkspaceFieldReader): AgentWorkspaceCommandEditorSubmission {
96
+ if (!isAffirmative(readField('confirm'))) return unconfirmed(editor, `Voice interaction ${editor.kind === 'voice-enable' ? 'enable' : 'disable'} not confirmed. Type yes, then press Enter.`);
97
+ const mode = editor.kind === 'voice-enable' ? 'enable' : 'disable';
98
+ return dispatchCommandEditorSubmission(
99
+ `/voice ${mode} --yes`,
100
+ `Opening voice interaction ${mode}`,
101
+ `The workspace handed a confirmed voice ${mode} command to the shell-owned command router.`,
102
+ 'safe',
103
+ );
104
+ }
105
+
106
+ export function buildAgentWorkspaceAccessCommandEditorSubmission(
107
+ editor: AgentWorkspaceLocalEditor,
108
+ readField: AgentWorkspaceFieldReader,
109
+ ): AgentWorkspaceCommandEditorSubmission {
110
+ return buildCommandEditorSubmissionFromTable(
111
+ editor.kind as AgentWorkspaceAccessCommandEditorKind,
112
+ editor,
113
+ readField,
114
+ ACCESS_COMMAND_SUBMISSION_HANDLERS,
148
115
  );
149
116
  }