@pellux/goodvibes-agent 0.1.107 → 0.1.109

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 (83) hide show
  1. package/CHANGELOG.md +42 -13
  2. package/README.md +27 -11
  3. package/docs/README.md +5 -5
  4. package/docs/{runtime-connection.md → connected-services.md} +8 -8
  5. package/docs/getting-started.md +28 -12
  6. package/docs/release-and-publishing.md +4 -4
  7. package/package.json +2 -12
  8. package/src/agent/memory-prompt.ts +35 -0
  9. package/src/agent/reminder-schedule-format.ts +75 -0
  10. package/src/agent/reminder-schedule.ts +494 -0
  11. package/src/agent/routine-schedule-format.ts +7 -7
  12. package/src/agent/routine-schedule-promotion.ts +1 -1
  13. package/src/agent/routine-schedule-receipts.ts +1 -1
  14. package/src/agent/skill-discovery.ts +2 -0
  15. package/src/cli/agent-knowledge-args.ts +93 -0
  16. package/src/cli/agent-knowledge-command.ts +200 -369
  17. package/src/cli/agent-knowledge-format.ts +58 -7
  18. package/src/cli/agent-knowledge-methods.ts +84 -0
  19. package/src/cli/agent-knowledge-runtime.ts +240 -0
  20. package/src/cli/completion.ts +0 -2
  21. package/src/cli/config-overrides.ts +2 -2
  22. package/src/cli/help.ts +34 -15
  23. package/src/cli/management-commands.ts +2 -2
  24. package/src/cli/management.ts +8 -8
  25. package/src/cli/package-verification.ts +7 -3
  26. package/src/cli/parser.ts +10 -4
  27. package/src/cli/service-posture.ts +6 -6
  28. package/src/cli/status.ts +32 -32
  29. package/src/input/agent-workspace-activation.ts +24 -13
  30. package/src/input/agent-workspace-basic-command-editors.ts +448 -0
  31. package/src/input/agent-workspace-categories.ts +42 -34
  32. package/src/input/agent-workspace-channels.ts +3 -3
  33. package/src/input/agent-workspace-command-editor.ts +65 -0
  34. package/src/input/agent-workspace-editors.ts +17 -2
  35. package/src/input/agent-workspace-knowledge-query-editor.ts +74 -0
  36. package/src/input/agent-workspace-knowledge-url-editor.ts +95 -0
  37. package/src/input/agent-workspace-reminder-schedule-editor.ts +125 -0
  38. package/src/input/agent-workspace-routine-schedule-editor.ts +127 -0
  39. package/src/input/agent-workspace-setup.ts +2 -2
  40. package/src/input/agent-workspace-snapshot.ts +5 -2
  41. package/src/input/agent-workspace-types.ts +21 -2
  42. package/src/input/agent-workspace-voice-media.ts +5 -5
  43. package/src/input/agent-workspace.ts +39 -2
  44. package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
  45. package/src/input/commands/agent-skills-runtime.ts +94 -2
  46. package/src/input/commands/brief-runtime.ts +126 -0
  47. package/src/input/commands/channels-runtime.ts +47 -0
  48. package/src/input/commands/health-runtime.ts +10 -10
  49. package/src/input/commands/knowledge.ts +176 -1
  50. package/src/input/commands/planning-runtime.ts +1 -1
  51. package/src/input/commands/platform-access-runtime.ts +10 -10
  52. package/src/input/commands/policy-dispatch.ts +1 -1
  53. package/src/input/commands/schedule-runtime.ts +42 -5
  54. package/src/input/commands/security-runtime.ts +1 -1
  55. package/src/input/commands/session-content.ts +1 -1
  56. package/src/input/commands/session-workflow.ts +1 -1
  57. package/src/input/commands/shell-core.ts +4 -2
  58. package/src/input/commands/tasks-runtime.ts +3 -3
  59. package/src/input/commands.ts +4 -0
  60. package/src/input/handler-onboarding.ts +4 -4
  61. package/src/input/handler.ts +3 -2
  62. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  63. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +13 -13
  64. package/src/input/onboarding/onboarding-wizard-steps.ts +8 -8
  65. package/src/input/settings-modal-agent-policy.ts +1 -1
  66. package/src/input/slash-command-parser.ts +60 -0
  67. package/src/panels/builtin/agent.ts +1 -1
  68. package/src/panels/builtin/operations.ts +2 -2
  69. package/src/panels/provider-account-snapshot.ts +1 -1
  70. package/src/panels/provider-health-domains.ts +6 -6
  71. package/src/panels/subscription-panel.ts +1 -1
  72. package/src/panels/tasks-panel.ts +3 -3
  73. package/src/renderer/agent-workspace.ts +44 -31
  74. package/src/renderer/help-overlay.ts +1 -1
  75. package/src/renderer/settings-modal.ts +13 -13
  76. package/src/runtime/bootstrap-core.ts +1 -1
  77. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  78. package/src/runtime/bootstrap.ts +11 -9
  79. package/src/runtime/index.ts +2 -2
  80. package/src/runtime/onboarding/derivation.ts +6 -6
  81. package/src/shell/service-settings-sync.ts +1 -1
  82. package/src/tools/agent-local-registry-tool.ts +208 -9
  83. package/src/version.ts +1 -1
@@ -31,26 +31,26 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
31
31
  storage: 'Local storage posture, including secret storage policy and maximum artifact size for Agent Knowledge, artifacts, and document ingestion.',
32
32
  permissions: 'Permission mode and tool-class policy. These settings decide whether the shell prompts before read/write/exec/network/agent actions.',
33
33
  orchestration: 'Agent orchestration limits and recursion controls.',
34
- wrfc: 'WRFC is external to normal Agent operation. Review these runtime compatibility values only for explicit GoodVibes TUI build delegation.',
34
+ wrfc: 'WRFC is external to normal Agent operation. Review these compatibility values only for explicit GoodVibes TUI build delegation.',
35
35
  helper: 'Helper model defaults used by helper subsystems when they do not use the main chat route.',
36
36
  tts: 'Text-to-speech provider, voice, and optional spoken-turn LLM overrides.',
37
- service: 'External runtime installation posture. Agent shows these compatibility keys for inspection only and does not install, start, stop, restart, or autostart anything.',
38
- controlPlane: 'External runtime API connection settings. Agent connects to that runtime and does not mutate its bind posture.',
39
- httpListener: 'Inbound event endpoint settings owned by the external runtime. Agent inspects readiness and does not expose endpoints.',
37
+ service: 'Connected-service installation posture. Agent shows these compatibility keys for inspection only and does not install, start, stop, restart, or autostart anything.',
38
+ controlPlane: 'Connected-service API settings. Agent uses these settings for access and does not mutate bind posture.',
39
+ httpListener: 'Inbound event endpoint settings owned by connected GoodVibes services. Agent inspects readiness and does not expose endpoints.',
40
40
  web: 'External browser companion settings. Agent does not own browser hosting or network bind lifecycle.',
41
- batch: 'Batch execution settings reported from the external GoodVibes runtime. Agent does not own remote queue provisioning.',
41
+ batch: 'Batch execution settings reported from connected GoodVibes services. Agent does not own remote queue provisioning.',
42
42
  automation: 'Scheduled and automated run settings, concurrency, timeout, catch-up, cooldown, and retention behavior.',
43
43
  watchers: 'File/process watcher heartbeat, polling, and recovery-window behavior.',
44
- runtime: 'Runtime guardrails such as companion chat limiter and event bus listener caps.',
44
+ runtime: 'Connected-service guardrails such as companion chat limiter and event bus listener caps.',
45
45
  telemetry: 'Telemetry payload policy.',
46
46
  cache: 'Provider and model cache behavior, TTL, and hit-rate monitoring.',
47
47
  mcp: 'MCP server trust and scope review. Trust changes can expose local files, tools, databases, browsers, or remote automation depending on the server.',
48
48
  surfaces: 'Messaging and notification channel accounts such as Slack, Discord, ntfy, Telegram, chat bridges, and delivery providers.',
49
49
  release: 'Release-channel preference.',
50
- danger: 'High-impact runtime switches. Agent renders host-owned switches read-only; use the owning GoodVibes runtime host to change them.',
50
+ danger: 'High-impact service switches. Agent renders service-owned switches read-only; change them outside Agent.',
51
51
  tools: 'Tool LLM and helper model routing. Empty provider/model values inherit the active chat route unless a specific helper/tool route is set.',
52
- flags: 'Feature flags are SDK runtime gates. They are separate from normal config keys because they enable or disable staged runtime behavior.',
53
- network: 'Read-only view of external runtime API, inbound-event, and browser companion bind posture plus editable Agent network settings.',
52
+ flags: 'Feature flags are SDK gates. They are separate from normal config keys because they enable or disable staged behavior.',
53
+ network: 'Read-only view of connected GoodVibes API, inbound-event, and browser companion bind posture plus editable Agent network settings.',
54
54
  };
55
55
 
56
56
  const ENUM_VALUE_DESCRIPTIONS: Record<string, Record<string, string>> = {
@@ -148,7 +148,7 @@ function buildSettingContext(modal: SettingsModal, entry: SettingEntry): string[
148
148
  ];
149
149
 
150
150
  if (entry.locked) lines.push(`Locked: ${entry.lockReason ?? 'This setting is locked by a higher-priority layer.'}`);
151
- if (entry.conflict) lines.push(`Conflict: inspect with /settings and resolve runtime-owned sync state in the external host.`);
151
+ if (entry.conflict) lines.push(`Conflict: inspect with /settings and resolve service-owned sync state in the owning host.`);
152
152
 
153
153
  lines.push('', entry.setting.description);
154
154
 
@@ -202,7 +202,7 @@ function buildFlagContext(entry: FlagEntry | null): string[] {
202
202
  `State: ${entry.state}`,
203
203
  `Default: ${entry.flag.defaultState}`,
204
204
  `Tier: ${entry.flag.tier}`,
205
- `Runtime toggleable: ${entry.flag.runtimeToggleable ? 'yes' : 'no'}`,
205
+ `Live toggleable: ${entry.flag.runtimeToggleable ? 'yes' : 'no'}`,
206
206
  '',
207
207
  entry.flag.description,
208
208
  ...(entry.state === 'killed' && entry.flag.killReason ? ['', `Kill reason: ${entry.flag.killReason}`] : []),
@@ -479,7 +479,7 @@ function footerText(modal: SettingsModal): string {
479
479
  if (modal.currentCategory === 'flags') return 'Focus feature flags · Up/Down flag · Left categories · Tab pane · Enter/Space toggle · Esc close';
480
480
  const selected = modal.getSelected();
481
481
  if (selected && isExternalDaemonOwnedSettingKey(selected.setting.key)) {
482
- return 'Read-only external runtime setting · Change from GoodVibes TUI or the owning host · Esc close';
482
+ return 'Read-only connected-service setting · Change from GoodVibes TUI or the owning host · Esc close';
483
483
  }
484
484
  return 'Focus settings · Up/Down setting · Left categories · Tab pane · Enter/Space edit/toggle · R reset · Esc close';
485
485
  }
@@ -490,7 +490,7 @@ export function renderSettingsModal(
490
490
  viewportHeight = 24,
491
491
  ): Line[] {
492
492
  const notices = [
493
- ...(modal.lastSaveTriggeredRestart ? [`External runtime owner must restart ${modal.lastSaveTriggeredRestart}`] : []),
493
+ ...(modal.lastSaveTriggeredRestart ? [`Owning host must restart ${modal.lastSaveTriggeredRestart}`] : []),
494
494
  ...(modal.lastSettingEffectMessage ? [modal.lastSettingEffectMessage] : []),
495
495
  ];
496
496
  const metrics = getFullscreenWorkspaceMetrics({ width, height: viewportHeight });
@@ -228,7 +228,7 @@ export async function initializeBootstrapCore(
228
228
  overflowHandler: services.overflowHandler,
229
229
  changeTracker: services.sessionChangeTracker,
230
230
  });
231
- registerAgentLocalRegistryTool(toolRegistry, services.shellPaths);
231
+ registerAgentLocalRegistryTool(toolRegistry, services.shellPaths, services.memoryRegistry);
232
232
  installAgentToolPolicyGuard(toolRegistry, {
233
233
  getLastUserMessage: () => conversation.getLastUserMessage(),
234
234
  });
@@ -74,7 +74,7 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
74
74
  options.conversation.log(`Resume: Reopened panels: ${reopenedPanels.join(', ')}`, { fg: '244' });
75
75
  }
76
76
  if ((meta.returnContext.remoteRunners?.length ?? 0) > 0) {
77
- options.conversation.log('Resume: Remote re-entry belongs to the external runtime host; delegate explicit build/fix/review recovery from Agent.', { fg: '244' });
77
+ options.conversation.log('Resume: Remote runner recovery belongs outside Agent; delegate explicit build/fix/review recovery from Agent.', { fg: '244' });
78
78
  }
79
79
  if (returnContextMode === 'assisted') {
80
80
  const helperModel = new HelperModel({
@@ -43,12 +43,13 @@ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
43
43
  import { buildActivePersonaPrompt } from '../agent/persona-registry.ts';
44
44
  import { buildEnabledSkillsPrompt } from '../agent/skill-registry.ts';
45
45
  import { buildEnabledRoutinesPrompt } from '../agent/routine-registry.ts';
46
+ import { buildReviewedMemoryPrompt } from '../agent/memory-prompt.ts';
46
47
 
47
48
  const GOODVIBES_AGENT_OPERATOR_POLICY = [
48
49
  '## GoodVibes Agent Operator Policy',
49
- '- Default to serial, proactive assistant work in the main conversation. Answer, inspect, summarize, remember useful non-secret facts, configure local Agent state, use read-only runtime/operator routes, and take safe non-destructive actions without spawning local agents or WRFC.',
50
- '- GoodVibes Agent connects to an externally managed GoodVibes runtime host. Do not start, stop, restart, install, expose, or mutate runtime-host network/listener posture from Agent runtime.',
51
- '- Use the `agent_local_registry` tool when a reusable persona, skill, or routine would improve future work. Keep those records local, non-secret, source/provenance tagged, and reviewable. Starting a routine means applying its steps in this same serial conversation, not creating a background job.',
50
+ '- Default to serial, proactive assistant work in the main conversation. Answer, inspect, summarize, remember useful non-secret facts, configure local Agent state, use read-only connected-service/operator routes, and take safe non-destructive actions without spawning local agents or WRFC.',
51
+ '- GoodVibes Agent connects to GoodVibes services owned outside this package. Do not start, stop, restart, install, expose, or mutate connected-service network/listener posture from Agent.',
52
+ '- Use the `agent_local_registry` tool when a durable memory, reusable persona, skill, skill bundle, or routine would improve future work. Keep those records local, non-secret, source/provenance tagged, and reviewable. Review memory with a confidence score when it should shape future turns. Starting a routine means applying its steps in this same serial conversation, not creating a background job.',
52
53
  '- WRFC is never the default Agent reasoning path. Do not create local WRFC chains for planning, research, operations, knowledge, memory, configuration, approvals, automation observability, or ordinary assistant work.',
53
54
  '- GoodVibes Agent is not the coding TUI. Do not use the `agent` tool to spawn local Engineer, Reviewer, Tester, Verifier, or batch-spawn roots from Agent.',
54
55
  '- When the user explicitly asks to build, implement, fix, patch, or review code, preserve the full original user ask and delegate one build request to GoodVibes TUI through the public shared-session/build-delegation contract. Include clear executionIntent and request WRFC only for explicit build/fix/review work or when the user explicitly asks for WRFC/agent review.',
@@ -206,6 +207,7 @@ export async function bootstrapRuntime(
206
207
  return joinPromptParts(
207
208
  runtime.systemPrompt,
208
209
  GOODVIBES_AGENT_OPERATOR_POLICY,
210
+ buildReviewedMemoryPrompt(services.memoryRegistry),
209
211
  buildEnabledRoutinesPrompt(services.shellPaths),
210
212
  buildEnabledSkillsPrompt(services.shellPaths),
211
213
  buildActivePersonaPrompt(services.shellPaths),
@@ -332,8 +334,8 @@ export async function bootstrapRuntime(
332
334
  port,
333
335
  baseUrl: formatHostServiceBaseUrl(host, port),
334
336
  reason: service === 'daemon'
335
- ? 'GoodVibes Agent connects to an externally managed GoodVibes runtime host and does not start or restart it.'
336
- : 'GoodVibes Agent does not own external listener lifecycle.',
337
+ ? 'GoodVibes Agent connects to GoodVibes services owned outside this product and does not start or restart them.'
338
+ : 'GoodVibes Agent does not own listener lifecycle.',
337
339
  };
338
340
  };
339
341
 
@@ -373,7 +375,7 @@ export async function bootstrapRuntime(
373
375
  daemonStatus: createExternalAgentServiceStatus('daemon'),
374
376
  httpListenerStatus: createExternalAgentServiceStatus('httpListener'),
375
377
  };
376
- systemMessageRouter.high('[Startup] GoodVibes Agent does not start or restart runtime-host services. Start the GoodVibes runtime from its owner, then refresh status.');
378
+ systemMessageRouter.high('[Startup] GoodVibes Agent does not start or restart connected GoodVibes services. Start them from GoodVibes TUI or the owning host, then refresh status.');
377
379
  requestRender();
378
380
  return inspectExternalServices();
379
381
  },
@@ -450,8 +452,8 @@ export async function bootstrapRuntime(
450
452
  });
451
453
  bootstrapUnsubs.push(() => mcpAutoReload.stop());
452
454
  if (configManager.get('automation.enabled')) {
453
- logger.warn('Local automation startup is disabled in GoodVibes Agent; use external runtime observability instead.');
454
- systemMessageRouter.low('[Startup] Local automation runners are disabled in GoodVibes Agent; use read-only automation observability or explicit external-runtime actions.');
455
+ logger.warn('Local automation startup is disabled in GoodVibes Agent; use connected-service observability instead.');
456
+ systemMessageRouter.low('[Startup] Local automation runners are disabled in GoodVibes Agent; use read-only automation observability or explicit connected-service actions.');
455
457
  }
456
458
 
457
459
  // ── Phase 12: Session:start lifecycle hook ─────────────────────────────
@@ -553,7 +555,7 @@ export async function bootstrapRuntime(
553
555
  };
554
556
 
555
557
  // ── Phase 12b: Operator intervention wiring (feature-gated) ──────────────
556
- // Keep runtime-owned control-plane state internal. GoodVibes Agent does not
558
+ // Keep service-owned control-plane state internal. GoodVibes Agent does not
557
559
  // expose the local ops-control panel; operator control is surfaced
558
560
  // through Agent-owned status, approvals, automation, and delegation flows.
559
561
  ctx.commandContext.ops.acpManager = acpManager;
@@ -113,8 +113,8 @@ function agentExternalHostStatus(
113
113
  port,
114
114
  baseUrl: `http://${host}:${port}`,
115
115
  reason: service === 'daemon'
116
- ? 'GoodVibes Agent connects to an externally managed GoodVibes runtime host and does not start or restart it.'
117
- : 'GoodVibes Agent does not own external listener lifecycle.',
116
+ ? 'GoodVibes Agent connects to GoodVibes services owned outside this product and does not start or restart them.'
117
+ : 'GoodVibes Agent does not own listener lifecycle.',
118
118
  };
119
119
  }
120
120
 
@@ -264,10 +264,10 @@ function hasAutomationReviewSignals(snapshot: OnboardingSnapshotState): boolean
264
264
 
265
265
  function describeOperatorTerminal(snapshot: OnboardingSnapshotState): string {
266
266
  if (!hasAnyServerEnabled(snapshot)) {
267
- return 'Use GoodVibes Agent as the terminal operator while connecting to the existing GoodVibes runtime. Agent setup does not create new entrypoints.';
267
+ return 'Use GoodVibes Agent as the terminal operator while connecting to existing GoodVibes services. Agent setup does not create new entrypoints.';
268
268
  }
269
269
 
270
- return 'Use GoodVibes Agent as the terminal operator; runtime host settings are shown only so the connection is understandable.';
270
+ return 'Use GoodVibes Agent as the terminal operator; connection settings are shown only so setup is understandable.';
271
271
  }
272
272
 
273
273
  function describeProviderAccess(snapshot: OnboardingSnapshotState): string {
@@ -500,23 +500,23 @@ export function deriveReopenEditAcknowledgementState(
500
500
  snapshot,
501
501
  'auth',
502
502
  'bootstrap-credential',
503
- 'An external runtime bootstrap credential signal is still visible to Agent.',
503
+ 'A connected-service bootstrap credential signal is still visible to Agent.',
504
504
  )
505
505
  : authSessionCount > 0
506
506
  ? buildRequiredAcknowledgement(
507
507
  snapshot,
508
508
  'auth',
509
509
  'active-sessions',
510
- `${authSessionCount} external runtime auth session signal(s) are currently visible.`,
510
+ `${authSessionCount} connected-service auth session signal(s) are currently visible.`,
511
511
  )
512
512
  : authUserCount > 0
513
513
  ? buildRequiredAcknowledgement(
514
514
  snapshot,
515
515
  'auth',
516
516
  'auth-state',
517
- `${authUserCount} external runtime auth user signal(s) are already visible.`,
517
+ `${authUserCount} connected-service auth user signal(s) are already visible.`,
518
518
  )
519
- : buildNotNeededAcknowledgement(snapshot, 'auth', 'No external runtime auth signal needs confirmation.');
519
+ : buildNotNeededAcknowledgement(snapshot, 'auth', 'No connected-service auth signal needs confirmation.');
520
520
 
521
521
  return {
522
522
  providers,
@@ -1,7 +1,7 @@
1
1
  import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import type { CliServiceRuntime } from '../cli/service-posture.ts';
3
3
 
4
- export const AGENT_EXTERNAL_DAEMON_SERVICE_MESSAGE = 'GoodVibes Agent connects to an external daemon and does not install, start, stop, restart, or uninstall daemon services. Manage daemon lifecycle from GoodVibes TUI or your daemon host tooling.';
4
+ export const AGENT_EXTERNAL_DAEMON_SERVICE_MESSAGE = 'GoodVibes Agent uses connected GoodVibes services and does not install, start, stop, restart, or uninstall them. Manage service lifecycle outside Agent.';
5
5
 
6
6
  export interface ServiceSettingsSyncChange {
7
7
  readonly key: ConfigKey;
@@ -1,11 +1,18 @@
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 {
5
+ MemoryRegistry,
6
+ type MemoryClass,
7
+ type MemoryRecord,
8
+ type MemoryScope,
9
+ } from '@pellux/goodvibes-sdk/platform/state';
4
10
  import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
5
11
  import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
6
- import { AgentSkillRegistry, type AgentSkillRecord } from '../agent/skill-registry.ts';
12
+ import { AgentSkillRegistry, type AgentSkillBundleRecord, type AgentSkillRecord } from '../agent/skill-registry.ts';
13
+ import { assertNoSecretLikeMemoryText } from '../agent/memory-safety.ts';
7
14
 
8
- export type AgentLocalRegistryDomain = 'persona' | 'skill' | 'routine';
15
+ export type AgentLocalRegistryDomain = 'memory' | 'persona' | 'skill' | 'skill_bundle' | 'routine';
9
16
  export type AgentLocalRegistryAction =
10
17
  | 'list'
11
18
  | 'search'
@@ -25,11 +32,18 @@ export interface AgentLocalRegistryToolArgs {
25
32
  readonly action?: unknown;
26
33
  readonly id?: unknown;
27
34
  readonly query?: unknown;
35
+ readonly cls?: unknown;
36
+ readonly scope?: unknown;
37
+ readonly summary?: unknown;
38
+ readonly detail?: unknown;
39
+ readonly confidence?: unknown;
28
40
  readonly name?: unknown;
29
41
  readonly description?: unknown;
30
42
  readonly body?: unknown;
31
43
  readonly procedure?: unknown;
32
44
  readonly steps?: unknown;
45
+ readonly skills?: unknown;
46
+ readonly skillIds?: unknown;
33
47
  readonly triggers?: unknown;
34
48
  readonly tags?: unknown;
35
49
  readonly reason?: unknown;
@@ -37,7 +51,7 @@ export interface AgentLocalRegistryToolArgs {
37
51
  readonly provenance?: unknown;
38
52
  }
39
53
 
40
- const DOMAINS: readonly AgentLocalRegistryDomain[] = ['persona', 'skill', 'routine'];
54
+ const DOMAINS: readonly AgentLocalRegistryDomain[] = ['memory', 'persona', 'skill', 'skill_bundle', 'routine'];
41
55
  const ACTIONS: readonly AgentLocalRegistryAction[] = [
42
56
  'list',
43
57
  'search',
@@ -52,6 +66,8 @@ const ACTIONS: readonly AgentLocalRegistryAction[] = [
52
66
  'clear_active',
53
67
  'start',
54
68
  ];
69
+ const MEMORY_CLASSES: readonly MemoryClass[] = ['decision', 'constraint', 'incident', 'pattern', 'fact', 'risk', 'runbook', 'architecture', 'ownership'];
70
+ const MEMORY_SCOPES: readonly MemoryScope[] = ['session', 'project', 'team'];
55
71
 
56
72
  function isDomain(value: unknown): value is AgentLocalRegistryDomain {
57
73
  return typeof value === 'string' && DOMAINS.includes(value as AgentLocalRegistryDomain);
@@ -73,6 +89,28 @@ function readStringList(value: unknown): readonly string[] {
73
89
  return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
74
90
  }
75
91
 
92
+ function readOptionalNumber(value: unknown): number | undefined {
93
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
94
+ if (typeof value !== 'string' || !value.trim()) return undefined;
95
+ const parsed = Number(value);
96
+ return Number.isFinite(parsed) ? parsed : undefined;
97
+ }
98
+
99
+ function readOptionalConfidence(value: unknown): number | undefined {
100
+ const confidence = readOptionalNumber(value);
101
+ if (confidence === undefined) return undefined;
102
+ if (confidence < 0 || confidence > 100) throw new Error('confidence must be between 0 and 100.');
103
+ return confidence;
104
+ }
105
+
106
+ function isMemoryClass(value: unknown): value is MemoryClass {
107
+ return typeof value === 'string' && MEMORY_CLASSES.includes(value as MemoryClass);
108
+ }
109
+
110
+ function isMemoryScope(value: unknown): value is MemoryScope {
111
+ return typeof value === 'string' && MEMORY_SCOPES.includes(value as MemoryScope);
112
+ }
113
+
76
114
  function registryError(message: string): { readonly success: false; readonly error: string } {
77
115
  return { success: false, error: message };
78
116
  }
@@ -99,6 +137,104 @@ function requireDescription(args: AgentLocalRegistryToolArgs): string {
99
137
  return description;
100
138
  }
101
139
 
140
+ function requireSummary(args: AgentLocalRegistryToolArgs): string {
141
+ const summary = readString(args.summary || args.description);
142
+ if (!summary) throw new Error('summary is required.');
143
+ return summary;
144
+ }
145
+
146
+ function requireMemoryClass(args: AgentLocalRegistryToolArgs): MemoryClass {
147
+ const cls = args.cls || 'fact';
148
+ if (!isMemoryClass(cls)) throw new Error(`Invalid memory class. Valid: ${MEMORY_CLASSES.join(', ')}.`);
149
+ return cls;
150
+ }
151
+
152
+ function readMemoryScope(args: AgentLocalRegistryToolArgs): MemoryScope {
153
+ const scope = args.scope || 'project';
154
+ if (!isMemoryScope(scope)) throw new Error(`Invalid memory scope. Valid: ${MEMORY_SCOPES.join(', ')}.`);
155
+ return scope;
156
+ }
157
+
158
+ function formatMemory(record: MemoryRecord): string {
159
+ const tags = record.tags.length > 0 ? ` tags=${record.tags.join(',')}` : '';
160
+ return `${record.id} ${record.scope}/${record.cls} ${record.reviewState} ${record.confidence}%${tags} ${record.summary}`;
161
+ }
162
+
163
+ async function handleMemory(registry: MemoryRegistry, action: AgentLocalRegistryAction, args: AgentLocalRegistryToolArgs): Promise<string> {
164
+ if (action === 'list') {
165
+ const records = registry.getAll();
166
+ return records.length === 0
167
+ ? 'Agent-local memory\nNo Agent-local memory records.'
168
+ : ['Agent-local memory', ...records.map(formatMemory)].join('\n');
169
+ }
170
+ if (action === 'search') {
171
+ const query = readString(args.query);
172
+ const records = registry.search({ query, limit: 10 });
173
+ return records.length === 0
174
+ ? `Agent-local memory search\nNo Agent-local memory records matched "${query}".`
175
+ : [`Agent-local memory search: ${query || '(all)'}`, ...records.map(formatMemory)].join('\n');
176
+ }
177
+ if (action === 'get') {
178
+ const record = registry.get(requireId(args));
179
+ if (!record) return `Unknown Agent-local memory: ${readString(args.id)}`;
180
+ return [
181
+ formatMemory(record),
182
+ `created: ${new Date(record.createdAt).toISOString()}`,
183
+ `updated: ${new Date(record.updatedAt).toISOString()}`,
184
+ `provenance: ${record.provenance.map((entry) => `${entry.kind}:${entry.ref}`).join(', ') || '(none)'}`,
185
+ '',
186
+ record.detail || '(no detail)',
187
+ ].join('\n');
188
+ }
189
+ if (action === 'create') {
190
+ const summary = requireSummary(args);
191
+ const detail = readString(args.detail || args.body);
192
+ const tags = readStringList(args.tags);
193
+ assertNoSecretLikeMemoryText([summary, detail, ...tags]);
194
+ const record = await registry.add({
195
+ scope: readMemoryScope(args),
196
+ cls: requireMemoryClass(args),
197
+ summary,
198
+ detail,
199
+ tags: [...tags],
200
+ provenance: [{ kind: 'event', ref: readString(args.provenance) || 'agent-local-registry-tool' }],
201
+ });
202
+ return `Created Agent-local memory ${record.id}: ${record.summary}`;
203
+ }
204
+ if (action === 'update') {
205
+ const summary = readString(args.summary || args.description);
206
+ const detail = readString(args.detail || args.body);
207
+ const tags = args.tags === undefined ? undefined : [...readStringList(args.tags)];
208
+ assertNoSecretLikeMemoryText([summary, detail, ...(tags ?? [])]);
209
+ const record = registry.update(requireId(args), {
210
+ summary: summary || undefined,
211
+ detail: detail || undefined,
212
+ tags,
213
+ scope: args.scope === undefined ? undefined : readMemoryScope(args),
214
+ });
215
+ if (!record) return `Unknown Agent-local memory: ${readString(args.id)}`;
216
+ return `Updated Agent-local memory ${record.id}: ${record.summary}`;
217
+ }
218
+ if (action === 'review') {
219
+ const record = registry.review(requireId(args), {
220
+ state: 'reviewed',
221
+ confidence: readOptionalConfidence(args.confidence),
222
+ reviewedBy: 'agent',
223
+ });
224
+ if (!record) return `Unknown Agent-local memory: ${readString(args.id)}`;
225
+ return `Reviewed Agent-local memory ${record.id}.`;
226
+ }
227
+ if (action === 'stale') {
228
+ const record = registry.review(requireId(args), {
229
+ state: 'stale',
230
+ staleReason: readString(args.reason) || 'Marked stale by Agent.',
231
+ });
232
+ if (!record) return `Unknown Agent-local memory: ${readString(args.id)}`;
233
+ return `Marked Agent-local memory ${record.id} stale.`;
234
+ }
235
+ throw new Error(`Action ${action} is not valid for memory.`);
236
+ }
237
+
102
238
  function formatPersona(persona: AgentPersonaRecord, activeId: string | null): string {
103
239
  const active = persona.id === activeId ? 'active' : 'inactive';
104
240
  return `${persona.id} ${active} ${persona.reviewState} ${persona.name} - ${persona.description}`;
@@ -109,6 +245,11 @@ function formatSkill(skill: AgentSkillRecord): string {
109
245
  return `${skill.id} ${enabled} ${skill.reviewState} ${skill.name} - ${skill.description}`;
110
246
  }
111
247
 
248
+ function formatSkillBundle(bundle: AgentSkillBundleRecord): string {
249
+ const enabled = bundle.enabled ? 'enabled' : 'disabled';
250
+ return `${bundle.id} ${enabled} ${bundle.reviewState} ${bundle.name} - ${bundle.description} skills=${bundle.skillIds.join(',')}`;
251
+ }
252
+
112
253
  function formatRoutine(routine: AgentRoutineRecord): string {
113
254
  const enabled = routine.enabled ? 'enabled' : 'disabled';
114
255
  return `${routine.id} ${enabled} ${routine.reviewState} starts=${routine.startCount} ${routine.name} - ${routine.description}`;
@@ -127,6 +268,12 @@ function listSkills(records: readonly AgentSkillRecord[], title: string): string
127
268
  : [title, ...records.map(formatSkill)].join('\n');
128
269
  }
129
270
 
271
+ function listSkillBundles(records: readonly AgentSkillBundleRecord[], title: string): string {
272
+ return records.length === 0
273
+ ? `${title}\nNo Agent-local skill bundles.`
274
+ : [title, ...records.map(formatSkillBundle)].join('\n');
275
+ }
276
+
130
277
  function listRoutines(records: readonly AgentRoutineRecord[], title: string): string {
131
278
  return records.length === 0
132
279
  ? `${title}\nNo Agent-local routines.`
@@ -232,6 +379,49 @@ function handleSkill(shellPaths: ShellPathService, action: AgentLocalRegistryAct
232
379
  throw new Error(`Action ${action} is not valid for skills.`);
233
380
  }
234
381
 
382
+ function handleSkillBundle(shellPaths: ShellPathService, action: AgentLocalRegistryAction, args: AgentLocalRegistryToolArgs): string {
383
+ const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
384
+ if (action === 'list') return listSkillBundles(registry.listBundles(), 'Agent-local skill bundles');
385
+ if (action === 'search') return listSkillBundles(registry.searchBundles(readString(args.query)), 'Agent-local skill bundles search');
386
+ if (action === 'get') {
387
+ const bundle = registry.getBundle(requireId(args));
388
+ if (!bundle) return `Unknown Agent-local skill bundle: ${readString(args.id)}`;
389
+ return [
390
+ formatSkillBundle(bundle),
391
+ `skills: ${bundle.skillIds.join(', ')}`,
392
+ '',
393
+ bundle.description,
394
+ ].join('\n');
395
+ }
396
+ if (action === 'create') {
397
+ const bundle = registry.createBundle({
398
+ name: requireName(args),
399
+ description: requireDescription(args),
400
+ skillIds: readStringList(args.skillIds ?? args.skills),
401
+ enabled: args.enabled === true,
402
+ source: 'agent',
403
+ provenance: readString(args.provenance) || 'agent-local-registry-tool',
404
+ });
405
+ return `Created Agent-local skill bundle ${bundle.id}: ${bundle.name}`;
406
+ }
407
+ if (action === 'update') {
408
+ const bundle = registry.updateBundle(requireId(args), {
409
+ name: readString(args.name) || undefined,
410
+ description: readString(args.description) || undefined,
411
+ skillIds: args.skillIds === undefined && args.skills === undefined ? undefined : readStringList(args.skillIds ?? args.skills),
412
+ provenance: readString(args.provenance) || 'agent-local-registry-tool',
413
+ });
414
+ return `Updated Agent-local skill bundle ${bundle.id}: ${bundle.name}`;
415
+ }
416
+ if (action === 'enable' || action === 'disable') {
417
+ const bundle = registry.setBundleEnabled(requireId(args), action === 'enable');
418
+ return `${action === 'enable' ? 'Enabled' : 'Disabled'} Agent-local skill bundle ${bundle.id}: ${bundle.name}`;
419
+ }
420
+ if (action === 'review') return `Reviewed Agent-local skill bundle ${registry.markBundleReviewed(requireId(args)).id}.`;
421
+ if (action === 'stale') return `Marked Agent-local skill bundle ${registry.markBundleStale(requireId(args), readString(args.reason)).id} stale.`;
422
+ throw new Error(`Action ${action} is not valid for skill bundles.`);
423
+ }
424
+
235
425
  function handleRoutine(shellPaths: ShellPathService, action: AgentLocalRegistryAction, args: AgentLocalRegistryToolArgs): string {
236
426
  const registry = AgentRoutineRegistry.fromShellPaths(shellPaths);
237
427
  if (action === 'list') return listRoutines(registry.list(), 'Agent-local routines');
@@ -289,14 +479,14 @@ function handleRoutine(shellPaths: ShellPathService, action: AgentLocalRegistryA
289
479
  throw new Error(`Action ${action} is not valid for routines.`);
290
480
  }
291
481
 
292
- export function createAgentLocalRegistryTool(shellPaths: ShellPathService): Tool {
482
+ export function createAgentLocalRegistryTool(shellPaths: ShellPathService, memoryRegistry: MemoryRegistry): Tool {
293
483
  return {
294
484
  definition: {
295
485
  name: 'agent_local_registry',
296
486
  description: [
297
- 'Inspect and maintain GoodVibes Agent-local personas, skills, and routines from the main conversation.',
298
- 'Use this for safe self-improvement: create or refine reusable behavior, enable skills/routines, choose personas, review/stale records, and start routines in the same serial conversation.',
299
- 'This tool cannot delete records, create schedules, mutate the daemon, send messages, run background jobs, or delegate build work.',
487
+ 'Inspect and maintain GoodVibes Agent-local memory, personas, skills, and routines from the main conversation.',
488
+ 'Use this for safe self-improvement: remember durable non-secret facts, create or refine reusable behavior, bundle related skills, enable skills/routines, choose personas, review/stale records, and start routines in the same serial conversation.',
489
+ 'This tool cannot delete records, create schedules, mutate connected services, send messages, run background jobs, or delegate build work.',
300
490
  ].join(' '),
301
491
  parameters: {
302
492
  type: 'object',
@@ -305,11 +495,18 @@ export function createAgentLocalRegistryTool(shellPaths: ShellPathService): Tool
305
495
  action: { type: 'string', enum: [...ACTIONS] },
306
496
  id: { type: 'string' },
307
497
  query: { type: 'string' },
498
+ cls: { type: 'string', enum: [...MEMORY_CLASSES], description: 'Memory class when domain is memory.' },
499
+ scope: { type: 'string', enum: [...MEMORY_SCOPES], description: 'Memory scope when domain is memory.' },
500
+ summary: { type: 'string', description: 'Memory summary when domain is memory.' },
501
+ detail: { type: 'string', description: 'Memory detail when domain is memory.' },
502
+ confidence: { type: 'number', description: 'Memory review confidence from 0 to 100.' },
308
503
  name: { type: 'string' },
309
504
  description: { type: 'string' },
310
505
  body: { type: 'string', description: 'Persona body/instructions.' },
311
506
  procedure: { type: 'string', description: 'Skill procedure.' },
312
507
  steps: { type: 'string', description: 'Routine steps.' },
508
+ skills: { type: 'array', items: { type: 'string' }, description: 'Skill ids for skill_bundle create/update.' },
509
+ skillIds: { type: 'array', items: { type: 'string' }, description: 'Skill ids for skill_bundle create/update.' },
313
510
  triggers: { type: 'array', items: { type: 'string' } },
314
511
  tags: { type: 'array', items: { type: 'string' } },
315
512
  reason: { type: 'string' },
@@ -326,8 +523,10 @@ export function createAgentLocalRegistryTool(shellPaths: ShellPathService): Tool
326
523
  if (!isDomain(args.domain)) return registryError(`Unknown domain. Valid: ${DOMAINS.join(', ')}.`);
327
524
  if (!isAction(args.action)) return registryError(`Unknown action. Valid: ${ACTIONS.join(', ')}.`);
328
525
  try {
526
+ if (args.domain === 'memory') return registryOutput(await handleMemory(memoryRegistry, args.action, args));
329
527
  if (args.domain === 'persona') return registryOutput(handlePersona(shellPaths, args.action, args));
330
528
  if (args.domain === 'skill') return registryOutput(handleSkill(shellPaths, args.action, args));
529
+ if (args.domain === 'skill_bundle') return registryOutput(handleSkillBundle(shellPaths, args.action, args));
331
530
  return registryOutput(handleRoutine(shellPaths, args.action, args));
332
531
  } catch (error) {
333
532
  return registryError(error instanceof Error ? error.message : String(error));
@@ -336,6 +535,6 @@ export function createAgentLocalRegistryTool(shellPaths: ShellPathService): Tool
336
535
  };
337
536
  }
338
537
 
339
- export function registerAgentLocalRegistryTool(registry: ToolRegistry, shellPaths: ShellPathService): void {
340
- registry.register(createAgentLocalRegistryTool(shellPaths));
538
+ export function registerAgentLocalRegistryTool(registry: ToolRegistry, shellPaths: ShellPathService, memoryRegistry: MemoryRegistry): void {
539
+ registry.register(createAgentLocalRegistryTool(shellPaths, memoryRegistry));
341
540
  }
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 = '0.1.107';
9
+ let _version = '0.1.109';
10
10
  let _sdkVersion = '0.33.35';
11
11
  try {
12
12
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {