@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
@@ -1,54 +1,35 @@
1
1
  import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
2
2
  import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
3
3
  import type { ShellPathService } from '@/runtime/index.ts';
4
- import { MemoryRegistry, type MemoryClass, type MemoryRecord, type MemoryScope } from '@pellux/goodvibes-sdk/platform/state';
4
+ import { MemoryRegistry } from '@pellux/goodvibes-sdk/platform/state';
5
5
  import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
6
6
  import { AgentNoteRegistry, type AgentNoteRecord } from '../agent/note-registry.ts';
7
7
  import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
8
8
  import { AgentSkillRegistry, type AgentSkillBundleRecord, type AgentSkillRecord } from '../agent/skill-registry.ts';
9
- import { assertNoSecretLikeMemoryText } from '../agent/memory-safety.ts';
10
- import { formatAgentRecordOrigin, formatAgentRecordReference, formatAgentRecordReviewState } from '../agent/record-labels.ts';
9
+ import { formatAgentRecordOrigin, formatAgentRecordReviewState } from '../agent/record-labels.ts';
11
10
  import { buildAgentLocalRequirements } from './agent-local-registry-requirements.ts';
11
+ import {
12
+ AGENT_TOOL_PROVENANCE,
13
+ type AgentLocalRegistryToolArgs,
14
+ readAffirmative,
15
+ readString,
16
+ readStringList,
17
+ registryError,
18
+ registryOutput,
19
+ requireConfirmedDelete,
20
+ requireDescription,
21
+ requireId,
22
+ requireName,
23
+ requireTextField,
24
+ } from './agent-local-registry-args.ts';
25
+ import { handleMemory, MEMORY_CLASSES, MEMORY_SCOPES } from './agent-local-registry-memory.ts';
12
26
 
13
27
  export type AgentLocalRegistryDomain = 'memory' | 'note' | 'persona' | 'skill' | 'skill_bundle' | 'routine';
14
28
  export type AgentLocalRegistryAction = 'list' | 'search' | 'get' | 'create' | 'update' | 'enable' | 'disable' | 'review' | 'stale' | 'use' | 'clear_active' | 'start' | 'delete';
15
-
16
- export interface AgentLocalRegistryToolArgs {
17
- readonly domain?: unknown;
18
- readonly action?: unknown;
19
- readonly id?: unknown;
20
- readonly query?: unknown;
21
- readonly cls?: unknown;
22
- readonly scope?: unknown;
23
- readonly summary?: unknown;
24
- readonly detail?: unknown;
25
- readonly confidence?: unknown;
26
- readonly title?: unknown;
27
- readonly name?: unknown;
28
- readonly description?: unknown;
29
- readonly body?: unknown;
30
- readonly sourceUrl?: unknown;
31
- readonly procedure?: unknown;
32
- readonly steps?: unknown;
33
- readonly skills?: unknown;
34
- readonly skillIds?: unknown;
35
- readonly requiresEnv?: unknown;
36
- readonly requiresCommands?: unknown;
37
- readonly triggers?: unknown;
38
- readonly tags?: unknown;
39
- readonly reason?: unknown;
40
- readonly enabled?: unknown;
41
- readonly activate?: unknown;
42
- readonly provenance?: unknown;
43
- readonly confirm?: unknown;
44
- readonly explicitUserRequest?: unknown;
45
- }
29
+ export type { AgentLocalRegistryToolArgs } from './agent-local-registry-args.ts';
46
30
 
47
31
  const DOMAINS: readonly AgentLocalRegistryDomain[] = ['memory', 'note', 'persona', 'skill', 'skill_bundle', 'routine'];
48
32
  const ACTIONS: readonly AgentLocalRegistryAction[] = ['list', 'search', 'get', 'create', 'update', 'enable', 'disable', 'review', 'stale', 'use', 'clear_active', 'start', 'delete'];
49
- const MEMORY_CLASSES: readonly MemoryClass[] = ['decision', 'constraint', 'incident', 'pattern', 'fact', 'risk', 'runbook', 'architecture', 'ownership'];
50
- const MEMORY_SCOPES: readonly MemoryScope[] = ['session', 'project', 'team'];
51
- const AGENT_TOOL_PROVENANCE = 'agent-local-registry-tool';
52
33
 
53
34
  function isDomain(value: unknown): value is AgentLocalRegistryDomain {
54
35
  return typeof value === 'string' && DOMAINS.includes(value as AgentLocalRegistryDomain);
@@ -58,206 +39,6 @@ function isAction(value: unknown): value is AgentLocalRegistryAction {
58
39
  return typeof value === 'string' && ACTIONS.includes(value as AgentLocalRegistryAction);
59
40
  }
60
41
 
61
- function readString(value: unknown): string {
62
- return typeof value === 'string' ? value.trim() : '';
63
- }
64
-
65
- function readAffirmative(value: unknown): boolean {
66
- const normalized = readString(value).toLowerCase(); return value === true || (typeof value === 'string' && (normalized === '' || normalized === 'yes' || normalized === 'y' || normalized === 'true' || normalized === 'on'));
67
- }
68
-
69
- function readStringList(value: unknown): readonly string[] {
70
- if (typeof value === 'string') {
71
- return value.split(',').map((entry) => entry.trim()).filter(Boolean);
72
- }
73
- if (!Array.isArray(value)) return [];
74
- return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
75
- }
76
-
77
- function readOptionalNumber(value: unknown): number | undefined {
78
- if (typeof value === 'number' && Number.isFinite(value)) return value;
79
- if (typeof value !== 'string' || !value.trim()) return undefined;
80
- const parsed = Number(value);
81
- return Number.isFinite(parsed) ? parsed : undefined;
82
- }
83
-
84
- function readOptionalConfidence(value: unknown): number | undefined {
85
- const confidence = readOptionalNumber(value);
86
- if (confidence === undefined) return undefined;
87
- if (confidence < 0 || confidence > 100) throw new Error('confidence must be between 0 and 100.');
88
- return confidence;
89
- }
90
-
91
- function isMemoryClass(value: unknown): value is MemoryClass {
92
- return typeof value === 'string' && MEMORY_CLASSES.includes(value as MemoryClass);
93
- }
94
-
95
- function isMemoryScope(value: unknown): value is MemoryScope {
96
- return typeof value === 'string' && MEMORY_SCOPES.includes(value as MemoryScope);
97
- }
98
-
99
- function registryError(message: string): { readonly success: false; readonly error: string } {
100
- return { success: false, error: message };
101
- }
102
-
103
- function registryOutput(output: string): { readonly success: true; readonly output: string } {
104
- return { success: true, output };
105
- }
106
-
107
- function requireId(args: AgentLocalRegistryToolArgs): string {
108
- const id = readString(args.id);
109
- if (!id) throw new Error('id is required.');
110
- return id;
111
- }
112
-
113
- function requireConfirmedDelete(args: AgentLocalRegistryToolArgs, label: string): void {
114
- const explicitUserRequest = readString(args.explicitUserRequest);
115
- if (!explicitUserRequest) throw new Error(`${label} deletion requires explicitUserRequest with the user's exact request or a short faithful summary.`);
116
- if (args.confirm !== true) throw new Error(`${label} deletion requires confirm:true after an explicit user request.`);
117
- }
118
-
119
- function requireName(args: AgentLocalRegistryToolArgs): string {
120
- const name = readString(args.name);
121
- if (!name) throw new Error('name is required.');
122
- return name;
123
- }
124
-
125
- function requireDescription(args: AgentLocalRegistryToolArgs): string {
126
- const description = readString(args.description);
127
- if (!description) throw new Error('description is required.');
128
- return description;
129
- }
130
-
131
- function requireTextField(value: unknown, fieldName: string): string {
132
- const text = readString(value);
133
- if (!text) throw new Error(`${fieldName} is required.`);
134
- return text;
135
- }
136
-
137
- function requireSummary(args: AgentLocalRegistryToolArgs): string {
138
- const summary = readString(args.summary || args.description);
139
- if (!summary) throw new Error('summary is required.');
140
- return summary;
141
- }
142
-
143
- function requireMemoryClass(args: AgentLocalRegistryToolArgs): MemoryClass {
144
- const cls = args.cls || 'fact';
145
- if (!isMemoryClass(cls)) throw new Error(`Invalid memory class. Valid values ${MEMORY_CLASSES.join(', ')}.`);
146
- return cls;
147
- }
148
-
149
- function readMemoryScope(args: AgentLocalRegistryToolArgs): MemoryScope {
150
- const scope = args.scope || 'project';
151
- if (!isMemoryScope(scope)) throw new Error(`Invalid memory scope. Valid values ${MEMORY_SCOPES.join(', ')}.`);
152
- return scope;
153
- }
154
-
155
- function formatMemory(record: MemoryRecord): string {
156
- const tags = record.tags.length > 0 ? ` tags ${record.tags.join(', ')}` : '';
157
- return `${record.id} ${record.scope}/${record.cls} ${formatAgentRecordReviewState(record.reviewState)} ${record.confidence}%${tags} ${record.summary}`;
158
- }
159
-
160
- async function handleMemory(registry: MemoryRegistry, action: AgentLocalRegistryAction, args: AgentLocalRegistryToolArgs): Promise<string> {
161
- if (action === 'list') {
162
- const records = registry.getAll();
163
- return records.length === 0
164
- ? 'Agent-local memory\nNo Agent-local memory records.'
165
- : ['Agent-local memory', ...records.map(formatMemory)].join('\n');
166
- }
167
- if (action === 'search') {
168
- const query = readString(args.query);
169
- const records = registry.search({ query, limit: 10 });
170
- return records.length === 0
171
- ? `Agent-local memory search\nNo Agent-local memory records matched "${query}".`
172
- : ['Agent-local memory search', `query ${query || '(all)'}`, ...records.map(formatMemory)].join('\n');
173
- }
174
- if (action === 'get') {
175
- const record = registry.get(requireId(args));
176
- if (!record) return `Unknown Agent-local memory ${readString(args.id)}`;
177
- return [
178
- formatMemory(record),
179
- `created ${new Date(record.createdAt).toISOString()}`,
180
- `updated ${new Date(record.updatedAt).toISOString()}`,
181
- `origin ${record.provenance.map((entry) => formatAgentRecordReference({ kind: String(entry.kind), ref: String(entry.ref) })).join(', ') || '(none)'}`,
182
- `provenance: ${record.provenance.map((entry) => `${String(entry.kind)}:${String(entry.ref)}`).join(',') || '(none)'}`,
183
- '',
184
- record.detail || '(no detail)',
185
- ].join('\n');
186
- }
187
- if (action === 'create') {
188
- const summary = requireSummary(args);
189
- const detail = readString(args.detail || args.body);
190
- const tags = readStringList(args.tags);
191
- const confidence = readOptionalConfidence(args.confidence);
192
- assertNoSecretLikeMemoryText([summary, detail, ...tags]);
193
- const record = await registry.add({
194
- scope: readMemoryScope(args),
195
- cls: requireMemoryClass(args),
196
- summary,
197
- detail,
198
- tags: [...tags],
199
- ...(confidence === undefined ? {} : { review: { state: 'fresh' as const, confidence } }),
200
- provenance: [{ kind: 'event', ref: readString(args.provenance) || AGENT_TOOL_PROVENANCE }],
201
- });
202
- return [
203
- 'Created Agent-local memory',
204
- ` id ${record.id}`,
205
- ` summary ${record.summary}`,
206
- ].join('\n');
207
- }
208
- if (action === 'update') {
209
- const summary = readString(args.summary || args.description);
210
- const detail = readString(args.detail || args.body);
211
- const tags = args.tags === undefined ? undefined : [...readStringList(args.tags)];
212
- assertNoSecretLikeMemoryText([summary, detail, ...(tags ?? [])]);
213
- const record = registry.update(requireId(args), {
214
- summary: summary || undefined,
215
- detail: detail || undefined,
216
- tags,
217
- scope: args.scope === undefined ? undefined : readMemoryScope(args),
218
- });
219
- if (!record) return `Unknown Agent-local memory ${readString(args.id)}`;
220
- return [
221
- 'Updated Agent-local memory',
222
- ` id ${record.id}`,
223
- ` summary ${record.summary}`,
224
- ].join('\n');
225
- }
226
- if (action === 'review') {
227
- const record = registry.review(requireId(args), {
228
- state: 'reviewed',
229
- confidence: readOptionalConfidence(args.confidence),
230
- reviewedBy: 'agent',
231
- });
232
- if (!record) return `Unknown Agent-local memory ${readString(args.id)}`;
233
- return [
234
- 'Reviewed Agent-local memory',
235
- ` id ${record.id}`,
236
- ].join('\n');
237
- }
238
- if (action === 'stale') {
239
- const record = registry.review(requireId(args), {
240
- state: 'stale',
241
- staleReason: readString(args.reason) || 'Marked stale by Agent.',
242
- });
243
- if (!record) return `Unknown Agent-local memory ${readString(args.id)}`;
244
- return [
245
- 'Marked Agent-local memory stale',
246
- ` id ${record.id}`,
247
- ].join('\n');
248
- }
249
- if (action === 'delete') {
250
- const id = requireId(args);
251
- requireConfirmedDelete(args, 'Agent-local memory');
252
- if (!registry.delete(id)) return `Unknown Agent-local memory ${id}`;
253
- return [
254
- 'Deleted Agent-local memory',
255
- ` id ${id}`,
256
- ].join('\n');
257
- }
258
- throw new Error(`Action ${action} is not valid for memory.`);
259
- }
260
-
261
42
  function formatNote(note: AgentNoteRecord): string {
262
43
  const tags = note.tags.length > 0 ? ` tags ${note.tags.join(', ')}` : '';
263
44
  const sourceUrl = note.sourceUrl ? ` source ${note.sourceUrl}` : '';
@@ -743,6 +524,7 @@ export function createAgentLocalRegistryTool(shellPaths: ShellPathService, memor
743
524
  action: { type: 'string', enum: [...ACTIONS] },
744
525
  id: { type: 'string' },
745
526
  query: { type: 'string' },
527
+ semantic: { type: 'boolean', description: 'Memory search: semantic by default; false forces literal matching.' },
746
528
  cls: { type: 'string', enum: [...MEMORY_CLASSES], description: 'Memory class when domain is memory.' },
747
529
  scope: { type: 'string', enum: [...MEMORY_SCOPES], description: 'Memory scope when domain is memory.' },
748
530
  summary: { type: 'string', description: 'Memory summary when domain is memory.' },
@@ -30,7 +30,7 @@ const OPERATOR_BRIEFING_ROUTES: readonly OperatorRouteDescriptor[] = [
30
30
  { id: 'projectPlanning.workPlan.snapshot', path: '/api/projects/planning/work-plan' },
31
31
  { id: 'approvals.list', path: '/api/approvals' },
32
32
  { id: 'automation.integration.snapshot', path: '/api/automation' },
33
- { id: 'schedules.list', path: '/api/automation/schedules' },
33
+ { id: 'automation.schedules.list', path: '/api/automation/schedules' },
34
34
  { id: 'scheduler.capacity', path: '/api/runtime/scheduler' },
35
35
  ] as const;
36
36
 
@@ -149,7 +149,7 @@ function formatRoute(result: OperatorRouteResult): string {
149
149
  if (result.route.id === 'projectPlanning.workPlan.snapshot') return formatWorkPlan(result.body);
150
150
  if (result.route.id === 'approvals.list') return formatApprovals(result.body);
151
151
  if (result.route.id === 'automation.integration.snapshot') return formatAutomation(result.body);
152
- if (result.route.id === 'schedules.list') return formatSchedules(result.body);
152
+ if (result.route.id === 'automation.schedules.list') return formatSchedules(result.body);
153
153
  return formatCapacity(result.body);
154
154
  }
155
155
 
@@ -17,6 +17,7 @@ interface OperatorContractMethod {
17
17
  readonly method?: string;
18
18
  readonly path?: string;
19
19
  };
20
+ readonly invokable?: boolean;
20
21
  }
21
22
 
22
23
  interface OperatorMethodToolArgs {
@@ -562,6 +563,18 @@ export function createAgentOperatorMethodTool(
562
563
  error: `Unknown GoodVibes operator method '${methodId}'. Inspect host action:"methods" first.`,
563
564
  };
564
565
  }
566
+ // W4-A3 (capability-advertisement honesty): the operator contract marks a
567
+ // cataloged-but-not-route-backed method invokable:false (email.inbox.list,
568
+ // email.send, etc. — see @pellux/goodvibes-sdk's method-catalog-route-reconcile).
569
+ // Refuse here, before prepareOperatorRoute/fetch, rather than letting the
570
+ // model discover a bare 404 from a route that was never wired up. The ad
571
+ // must degrade at the source, not rely on the model coping with a 404.
572
+ if (method.invokable === false) {
573
+ return {
574
+ success: false,
575
+ error: `operator method '${method.id}' is unavailable (route not served by this daemon). Do not retry it; the daemon has not wired this capability up.`,
576
+ };
577
+ }
565
578
 
566
579
  let route: PreparedOperatorRoute;
567
580
  try {
@@ -6,6 +6,7 @@ import type { CommandContext } from '../input/command-registry.ts';
6
6
  import { fallbackPermissionCategoryForArgs } from '../runtime/tool-permission-safety.ts';
7
7
  import { HARNESS_MODE_DESCRIPTORS } from './agent-harness-mode-catalog.ts';
8
8
  import { explainAgentToolPolicyInvocation } from './agent-tool-policy-guard.ts';
9
+ import { computeApprovalPosture, type ApprovalPosture } from '../permissions/approval-posture.ts';
9
10
 
10
11
  type PolicyExplanationStatus = 'allowed' | 'confirmation_required' | 'denied';
11
12
  type PermissionPredictionOutcome = 'allowed' | 'prompt' | 'denied';
@@ -186,18 +187,42 @@ function readAutoApprove(context: CommandContext): boolean {
186
187
  return context.platform.config?.behavior?.autoApprove === true;
187
188
  }
188
189
 
190
+ /**
191
+ * Reads the SAME effective approval posture that cli/status.ts, the doctor
192
+ * surface, and the footer read (via computeApprovalPosture) — the shared
193
+ * single source of truth, so this tool's "explain" output never disagrees
194
+ * with what those other surfaces say about auto-approve / permission mode.
195
+ */
196
+ function readEffectivePosture(context: CommandContext): ApprovalPosture {
197
+ const mode = readPermissionMode(context);
198
+ const customTools: Record<string, unknown> = {};
199
+ if (mode === 'custom') {
200
+ for (const key of Object.values(TOOL_CONFIG_KEYS)) {
201
+ if (key in customTools) continue;
202
+ customTools[key] = readPermissionToolAction(context, key) ?? undefined;
203
+ }
204
+ }
205
+ return computeApprovalPosture({
206
+ autoApprove: readAutoApprove(context),
207
+ mode,
208
+ customTools,
209
+ });
210
+ }
211
+
189
212
  function predictPermission(context: CommandContext, toolName: string, category: PermissionCategory): PermissionPrediction {
190
- if (readAutoApprove(context)) {
213
+ const posture = readEffectivePosture(context);
214
+
215
+ if (posture.autoApprove) {
191
216
  return {
192
217
  outcome: 'allowed',
193
218
  sourceLayer: 'config_policy',
194
219
  reasonCode: 'config_allow',
195
- mode: readPermissionMode(context),
196
- reason: 'Automatic tool approval is enabled for this session.',
220
+ mode: posture.mode,
221
+ reason: posture.detail,
197
222
  };
198
223
  }
199
224
 
200
- const mode = readPermissionMode(context);
225
+ const mode = posture.mode;
201
226
  if (mode === 'allow-all') {
202
227
  return {
203
228
  outcome: 'allowed',
@@ -350,6 +375,10 @@ export function explainAgentPolicyDecision(
350
375
  ...(permission.outcome === 'prompt' ? [`Answer the ${category} permission prompt for ${toolName}.`] : []),
351
376
  ...(confirmation.required && !confirmation.confirmed ? ['Call the route with confirm:true and explicitUserRequest.'] : []),
352
377
  ];
378
+ // The overall approval posture — computed by the SAME shared helper that
379
+ // cli/status.ts, the doctor surface, and the footer use, so this tool's
380
+ // own displayed posture text can never disagree with theirs.
381
+ const posture = readEffectivePosture(context);
353
382
 
354
383
  return {
355
384
  status: 'found',
@@ -359,6 +388,12 @@ export function explainAgentPolicyDecision(
359
388
  registered: definition !== undefined,
360
389
  category,
361
390
  userExplanation: userExplanation(status, category),
391
+ posture: {
392
+ label: posture.label,
393
+ autoApprove: posture.autoApprove,
394
+ mode: posture.mode,
395
+ bypassesPrompts: posture.bypassesPrompts,
396
+ },
362
397
  policyLayers: [
363
398
  {
364
399
  layer: 'Agent route guard',
@@ -52,10 +52,10 @@ interface AgentScheduleToolArgs {
52
52
  }
53
53
 
54
54
  const LIFECYCLE_ACTIONS: Readonly<Record<'run' | 'pause' | 'resume' | 'delete', string>> = {
55
- run: 'schedules.run',
56
- pause: 'schedules.disable',
57
- resume: 'schedules.enable',
58
- delete: 'schedules.delete',
55
+ run: 'automation.schedules.run',
56
+ pause: 'automation.schedules.disable',
57
+ resume: 'automation.schedules.enable',
58
+ delete: 'automation.schedules.delete',
59
59
  };
60
60
 
61
61
  function readString(value: unknown): string {
@@ -248,7 +248,7 @@ export function createAgentScheduleTool(
248
248
  const action = readAction(args);
249
249
  if (action === 'list') {
250
250
  return operatorMethodTool.execute({
251
- methodId: 'schedules.list',
251
+ methodId: 'automation.schedules.list',
252
252
  input: listInput(args),
253
253
  });
254
254
  }
@@ -1,9 +1,54 @@
1
+ /**
2
+ * Strip ANSI SGR/CSI escape sequences and OSC-8 hyperlink sequences
3
+ * from a string so that only visible characters remain for width measurement.
4
+ *
5
+ * Covers:
6
+ * - CSI sequences: ESC [ ... <final byte 0x40-0x7E> (includes SGR \x1b[...m)
7
+ * - OSC sequences: ESC ] ... ST where ST is ESC\\ or BEL (0x07)
8
+ * - Simple ESC followed by a single non-bracket/non-] character (e.g. ESC c)
9
+ */
10
+ function stripAnsi(text: string): string {
11
+ let result = '';
12
+ let i = 0;
13
+ while (i < text.length) {
14
+ if (text[i] === '\x1b') {
15
+ const next = text[i + 1];
16
+ if (next === '[') {
17
+ // CSI: skip until final byte (0x40-0x7E)
18
+ i += 2;
19
+ while (i < text.length) {
20
+ const c = text.charCodeAt(i);
21
+ i++;
22
+ if (c >= 0x40 && c <= 0x7e) break;
23
+ }
24
+ } else if (next === ']') {
25
+ // OSC: skip until ST (ESC\\ or BEL)
26
+ i += 2;
27
+ while (i < text.length) {
28
+ if (text[i] === '\x07') { i++; break; }
29
+ if (text[i] === '\x1b' && text[i + 1] === '\\') { i += 2; break; }
30
+ i++;
31
+ }
32
+ } else {
33
+ // Simple two-byte escape (ESC + one char)
34
+ i += next !== undefined ? 2 : 1;
35
+ }
36
+ } else {
37
+ result += text[i];
38
+ i++;
39
+ }
40
+ }
41
+ return result;
42
+ }
43
+
1
44
  /**
2
45
  * Calculates the visual width of a string in the terminal.
3
46
  * Handles CJK characters, emoji (including ZWJ sequences), and
4
- * variation selectors correctly as double-width.
47
+ * variation selectors correctly as double-width. ANSI escape sequences
48
+ * (SGR/CSI/OSC-8) are stripped before measurement.
5
49
  */
6
50
  export function getDisplayWidth(text: string): number {
51
+ text = stripAnsi(text);
7
52
  let width = 0;
8
53
  let i = 0;
9
54
  while (i < text.length) {
@@ -34,6 +79,13 @@ export function getDisplayWidth(text: string): number {
34
79
  code === 0x2717 ||
35
80
  code === 0x2714 ||
36
81
  code === 0x2718 ||
82
+ // ✕ (0x2715) and ✖ (0x2716) — the multiplication-X cross family used for
83
+ // the error-line prefix. Terminals draw them one cell wide, but they sit
84
+ // inside the 0x2600–0x27bf emoji block below and would otherwise be counted
85
+ // as width 2, desyncing the styled cell grid from the physical glyph and
86
+ // corrupting the following text (the "✕t" glitch on the steer error line).
87
+ code === 0x2715 ||
88
+ code === 0x2716 ||
37
89
  code === 0x2022 ||
38
90
  code === 0x258d ||
39
91
  (code >= 0x2500 && code <= 0x257f)
@@ -107,6 +159,51 @@ export function truncateDisplay(text: string, width: number, ellipsis = '…'):
107
159
  return result + ellipsis;
108
160
  }
109
161
 
162
+ /** A single footer/status-line segment plus its survival priority. */
163
+ export interface PrioritizedSegment {
164
+ readonly text: string;
165
+ /** Lower number = higher priority = dropped LAST under width pressure. */
166
+ readonly priority: number;
167
+ }
168
+
169
+ /**
170
+ * Join segments left-to-right with `separator`, but when the joined line
171
+ * would exceed `width`, drop whole low-priority segments (highest `priority`
172
+ * number first) one at a time until it fits — rather than character-truncating
173
+ * the joined string, which can mangle a high-value segment mid-word (e.g. a
174
+ * `spine:online` daemon-liveness marker clipped to `spi…`).
175
+ *
176
+ * Only falls back to character truncation (via truncateDisplay) if the
177
+ * remaining highest-priority segments still don't fit at width — a rare,
178
+ * very-narrow-terminal case.
179
+ */
180
+ export function joinPrioritizedSegments(
181
+ segments: readonly PrioritizedSegment[],
182
+ separator: string,
183
+ width: number,
184
+ ): string {
185
+ const join = (list: readonly PrioritizedSegment[]) => list.map(s => s.text).join(separator);
186
+ let kept = segments;
187
+ // Keep dropping whole segments while more than one remains; once a single
188
+ // segment is left, stop — an empty result would be a worse outcome than
189
+ // falling through to character truncation on that last segment below.
190
+ while (kept.length > 1 && getDisplayWidth(join(kept)) > width) {
191
+ // Drop the single lowest-priority (highest `priority` number) segment;
192
+ // ties broken toward the leftmost (earlier-declared) segment surviving —
193
+ // `>=` (not `>`) so that on equal priority the LATER index keeps winning
194
+ // as the drop candidate, leaving the earliest-declared segment of that
195
+ // priority tier intact (e.g. cwd survives over model when both are
196
+ // priority 0, since cwd is declared first).
197
+ let dropIdx = 0;
198
+ for (let i = 1; i < kept.length; i++) {
199
+ if (kept[i].priority >= kept[dropIdx].priority) dropIdx = i;
200
+ }
201
+ kept = kept.slice(0, dropIdx).concat(kept.slice(dropIdx + 1));
202
+ }
203
+ const joined = join(kept);
204
+ return getDisplayWidth(joined) > width ? truncateDisplay(joined, width) : joined;
205
+ }
206
+
110
207
  export function padDisplayEnd(text: string, width: number): string {
111
208
  const currentWidth = getDisplayWidth(text);
112
209
  if (currentWidth >= width) return text;
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '1.5.5';
9
+ let _version = '1.5.6';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
12
12
  readonly version?: unknown;