@pellux/goodvibes-agent 0.1.106 → 0.1.108

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 (79) hide show
  1. package/CHANGELOG.md +41 -10
  2. package/README.md +28 -9
  3. package/docs/README.md +5 -5
  4. package/docs/{runtime-connection.md → connected-services.md} +8 -8
  5. package/docs/getting-started.md +29 -10
  6. package/docs/release-and-publishing.md +4 -4
  7. package/package.json +2 -12
  8. package/src/agent/reminder-schedule-format.ts +75 -0
  9. package/src/agent/reminder-schedule.ts +494 -0
  10. package/src/agent/routine-schedule-format.ts +7 -7
  11. package/src/agent/routine-schedule-promotion.ts +1 -1
  12. package/src/agent/routine-schedule-receipts.ts +1 -1
  13. package/src/agent/skill-discovery.ts +2 -0
  14. package/src/cli/agent-knowledge-args.ts +93 -0
  15. package/src/cli/agent-knowledge-command.ts +200 -369
  16. package/src/cli/agent-knowledge-format.ts +58 -7
  17. package/src/cli/agent-knowledge-methods.ts +84 -0
  18. package/src/cli/agent-knowledge-runtime.ts +240 -0
  19. package/src/cli/config-overrides.ts +2 -2
  20. package/src/cli/help.ts +31 -11
  21. package/src/cli/management-commands.ts +2 -2
  22. package/src/cli/management.ts +8 -8
  23. package/src/cli/package-verification.ts +7 -3
  24. package/src/cli/parser.ts +10 -2
  25. package/src/cli/service-posture.ts +6 -6
  26. package/src/cli/status.ts +32 -32
  27. package/src/cli/tui-startup.ts +3 -1
  28. package/src/input/agent-workspace-activation.ts +24 -13
  29. package/src/input/agent-workspace-basic-command-editors.ts +448 -0
  30. package/src/input/agent-workspace-categories.ts +42 -34
  31. package/src/input/agent-workspace-channels.ts +3 -3
  32. package/src/input/agent-workspace-command-editor.ts +65 -0
  33. package/src/input/agent-workspace-editors.ts +17 -2
  34. package/src/input/agent-workspace-knowledge-query-editor.ts +74 -0
  35. package/src/input/agent-workspace-knowledge-url-editor.ts +95 -0
  36. package/src/input/agent-workspace-reminder-schedule-editor.ts +125 -0
  37. package/src/input/agent-workspace-routine-schedule-editor.ts +127 -0
  38. package/src/input/agent-workspace-setup.ts +2 -2
  39. package/src/input/agent-workspace-types.ts +20 -2
  40. package/src/input/agent-workspace-voice-media.ts +5 -5
  41. package/src/input/agent-workspace.ts +39 -2
  42. package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
  43. package/src/input/commands/agent-skills-runtime.ts +94 -2
  44. package/src/input/commands/brief-runtime.ts +126 -0
  45. package/src/input/commands/channels-runtime.ts +47 -0
  46. package/src/input/commands/health-runtime.ts +10 -10
  47. package/src/input/commands/knowledge.ts +176 -1
  48. package/src/input/commands/planning-runtime.ts +1 -1
  49. package/src/input/commands/platform-access-runtime.ts +10 -10
  50. package/src/input/commands/policy-dispatch.ts +1 -1
  51. package/src/input/commands/schedule-runtime.ts +42 -5
  52. package/src/input/commands/security-runtime.ts +1 -1
  53. package/src/input/commands/session-content.ts +1 -1
  54. package/src/input/commands/session-workflow.ts +1 -1
  55. package/src/input/commands/shell-core.ts +4 -2
  56. package/src/input/commands/tasks-runtime.ts +3 -3
  57. package/src/input/commands.ts +4 -0
  58. package/src/input/handler-onboarding.ts +4 -4
  59. package/src/input/handler.ts +3 -2
  60. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  61. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +13 -13
  62. package/src/input/onboarding/onboarding-wizard-steps.ts +8 -8
  63. package/src/input/settings-modal-agent-policy.ts +1 -1
  64. package/src/input/slash-command-parser.ts +60 -0
  65. package/src/panels/builtin/agent.ts +1 -1
  66. package/src/panels/builtin/operations.ts +2 -2
  67. package/src/panels/provider-account-snapshot.ts +1 -1
  68. package/src/panels/provider-health-domains.ts +6 -6
  69. package/src/panels/subscription-panel.ts +1 -1
  70. package/src/panels/tasks-panel.ts +3 -3
  71. package/src/renderer/agent-workspace.ts +43 -30
  72. package/src/renderer/help-overlay.ts +1 -1
  73. package/src/renderer/settings-modal.ts +13 -13
  74. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  75. package/src/runtime/bootstrap.ts +8 -8
  76. package/src/runtime/index.ts +2 -2
  77. package/src/runtime/onboarding/derivation.ts +6 -6
  78. package/src/shell/service-settings-sync.ts +1 -1
  79. package/src/version.ts +1 -1
@@ -0,0 +1,126 @@
1
+ import type { AutomationJob } from '@pellux/goodvibes-sdk/platform/automation';
2
+ import type { CommandContext, CommandRegistry } from '../command-registry.ts';
3
+ import { buildAgentWorkspaceRuntimeSnapshot } from '../agent-workspace-snapshot.ts';
4
+ import { WORK_PLAN_STATUSES, type WorkPlanItemStatus } from '../../work-plans/work-plan-store.ts';
5
+
6
+ type StatusCounts = Record<WorkPlanItemStatus, number>;
7
+
8
+ function countReady<T extends { readonly status: string }>(items: readonly T[], status: string): number {
9
+ return items.filter((item) => item.status === status).length;
10
+ }
11
+
12
+ function workPlanCounts(ctx: CommandContext): { readonly total: number; readonly counts: StatusCounts } {
13
+ const counts = Object.fromEntries(WORK_PLAN_STATUSES.map((status) => [status, 0])) as StatusCounts;
14
+ try {
15
+ const items = ctx.workspace?.workPlanStore?.listItems?.() ?? [];
16
+ for (const item of items) {
17
+ counts[item.status] += 1;
18
+ }
19
+ return { total: items.length, counts };
20
+ } catch {
21
+ return { total: 0, counts };
22
+ }
23
+ }
24
+
25
+ function automationJobs(ctx: CommandContext): readonly AutomationJob[] {
26
+ try {
27
+ return ctx.ops?.automationManager?.listJobs?.() ?? [];
28
+ } catch {
29
+ return [];
30
+ }
31
+ }
32
+
33
+ function plural(count: number, singular: string, pluralWord = `${singular}s`): string {
34
+ return `${count} ${count === 1 ? singular : pluralWord}`;
35
+ }
36
+
37
+ function formatWorkPlanLine(total: number, counts: StatusCounts): string {
38
+ if (total === 0) return ' work plan: empty';
39
+ const active = counts.pending + counts.in_progress + counts.blocked;
40
+ return [
41
+ ` work plan: ${plural(total, 'item')}`,
42
+ `active ${active}`,
43
+ `pending ${counts.pending}`,
44
+ `in progress ${counts.in_progress}`,
45
+ `blocked ${counts.blocked}`,
46
+ `done ${counts.done}`,
47
+ ].join('; ');
48
+ }
49
+
50
+ function formatWarnings(warnings: readonly string[]): readonly string[] {
51
+ if (warnings.length === 0) return [' warnings: none'];
52
+ return [' warnings:', ...warnings.slice(0, 4).map((warning) => ` - ${warning}`)];
53
+ }
54
+
55
+ export function formatAgentOperatorBriefing(ctx: CommandContext): string {
56
+ const snapshot = buildAgentWorkspaceRuntimeSnapshot(ctx);
57
+ const workPlan = workPlanCounts(ctx);
58
+ const jobs = automationJobs(ctx);
59
+ const enabledJobs = jobs.filter((job) => job.enabled).length;
60
+ const setupReady = countReady(snapshot.setupChecklist, 'ready');
61
+ const setupRecommended = countReady(snapshot.setupChecklist, 'recommended');
62
+ const setupBlocked = countReady(snapshot.setupChecklist, 'blocked');
63
+ const readyChannels = snapshot.channels.filter((channel) => channel.ready).length;
64
+ const enabledChannels = snapshot.channels.filter((channel) => channel.enabled).length;
65
+
66
+ const nextActions = [
67
+ snapshot.provider === 'unknown' || snapshot.model === 'unknown'
68
+ ? 'Choose the assistant model with /model.'
69
+ : '',
70
+ snapshot.localMemoryCount === 0
71
+ ? 'Store durable non-secret facts with /memory add or the Agent workspace memory form.'
72
+ : snapshot.localMemoryReviewQueueCount > 0
73
+ ? `Review ${plural(snapshot.localMemoryReviewQueueCount, 'memory record')} with /memory queue.`
74
+ : '',
75
+ snapshot.localSkillCount === 0 && snapshot.localSkillBundleCount === 0
76
+ ? 'Create reusable procedures with /agent-skills create or import reviewed skill files.'
77
+ : snapshot.activeSkillCount === 0
78
+ ? 'Enable reviewed skills or bundles with /agent-skills enabled and /agent-skills bundle enabled.'
79
+ : '',
80
+ snapshot.localRoutineCount === 0
81
+ ? 'Create repeatable workflows with /routines create; promote schedules only with explicit confirmation.'
82
+ : snapshot.enabledRoutineCount === 0
83
+ ? 'Enable reviewed routines with /routines enable.'
84
+ : '',
85
+ 'Use /knowledge status, /knowledge search, and explicit ingest forms for Agent Knowledge only.',
86
+ 'Use /delegate only for explicit build, fix, implementation, or review handoff to GoodVibes TUI.',
87
+ ].filter((line): line is string => line.length > 0);
88
+
89
+ return [
90
+ 'Agent Briefing',
91
+ ` chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`,
92
+ ` session: ${snapshot.sessionId}`,
93
+ ` profile: ${snapshot.activeRuntimeProfile}`,
94
+ ` policy: ${snapshot.executionPolicy}; WRFC ${snapshot.wrfcPolicy}`,
95
+ ` knowledge: ${snapshot.knowledgeRoute} (${snapshot.knowledgeIsolation}; no fallback)`,
96
+ '',
97
+ 'Readiness',
98
+ ` setup: ${setupReady}/${snapshot.setupChecklist.length} ready; ${setupRecommended} recommended; ${setupBlocked} blocked`,
99
+ ` local memory: ${plural(snapshot.localMemoryCount, 'record')}; review queue ${snapshot.localMemoryReviewQueueCount}`,
100
+ ` personas: ${plural(snapshot.localPersonaCount, 'persona')}; active ${snapshot.activePersonaName}`,
101
+ ` skills: ${snapshot.enabledSkillCount}/${snapshot.localSkillCount} enabled; bundles ${snapshot.enabledSkillBundleCount}/${snapshot.localSkillBundleCount}; active ${snapshot.activeSkillCount}`,
102
+ ` routines: ${snapshot.enabledRoutineCount}/${snapshot.localRoutineCount} enabled`,
103
+ ` channels: ${readyChannels}/${snapshot.channels.length} ready; ${enabledChannels} enabled`,
104
+ ` voice/media: ${snapshot.voiceProviderCount} voice, ${snapshot.mediaProviderCount} media; browser surface ${snapshot.browserSurfaceEnabled ? 'enabled' : 'disabled'}`,
105
+ formatWorkPlanLine(workPlan.total, workPlan.counts),
106
+ ` schedules: ${enabledJobs}/${jobs.length} visible jobs enabled`,
107
+ '',
108
+ 'Next Actions',
109
+ ...nextActions.map((line) => ` - ${line}`),
110
+ '',
111
+ ...formatWarnings(snapshot.warnings),
112
+ ].join('\n');
113
+ }
114
+
115
+ export function registerBriefRuntimeCommands(registry: CommandRegistry): void {
116
+ registry.register({
117
+ name: 'brief',
118
+ aliases: ['briefing'],
119
+ description: 'Show a concise Agent operator briefing and next actions',
120
+ usage: '',
121
+ argsHint: '',
122
+ handler(_args, ctx) {
123
+ ctx.print(formatAgentOperatorBriefing(ctx));
124
+ },
125
+ });
126
+ }
@@ -0,0 +1,47 @@
1
+ import type { CommandRegistry } from '../command-registry.ts';
2
+ import { buildAgentWorkspaceChannels } from '../agent-workspace-channels.ts';
3
+
4
+ export function registerChannelsRuntimeCommands(registry: CommandRegistry): void {
5
+ registry.register({
6
+ name: 'channels',
7
+ aliases: ['channel'],
8
+ description: 'Inspect Agent channel readiness without sending messages',
9
+ usage: '[list|readiness]',
10
+ argsHint: 'list|readiness',
11
+ handler(_args, ctx) {
12
+ const channels = buildAgentWorkspaceChannels(ctx);
13
+ const ready = channels.filter((channel) => channel.ready);
14
+ const enabled = channels.filter((channel) => channel.enabled);
15
+ const needsTarget = channels.filter((channel) => channel.setupState === 'needs-target');
16
+ const needsConfig = channels.filter((channel) => channel.setupState === 'needs-config');
17
+ const lines: string[] = [
18
+ 'Channel Readiness',
19
+ ` ready: ${ready.length}/${channels.length}`,
20
+ ` enabled: ${enabled.length}/${channels.length}`,
21
+ ` needs target: ${needsTarget.length}`,
22
+ ` needs config: ${needsConfig.length}`,
23
+ ' policy: read-only inspection; sends require explicit user action and Agent policy',
24
+ '',
25
+ ...channels.map((channel) => {
26
+ const missing = channel.missingRequiredKeys.length > 0
27
+ ? ` missing=${channel.missingRequiredKeys.join('|')}`
28
+ : '';
29
+ const target = channel.configuredDefaultTargetKeys.length > 0
30
+ ? ` target=${channel.configuredDefaultTargetKeys.join('|')}`
31
+ : channel.defaultTargetKeys.length > 0
32
+ ? ` target=missing(${channel.defaultTargetKeys.join('|')})`
33
+ : ' target=not-required';
34
+ return [
35
+ ` ${channel.label}: ${channel.setupState}`,
36
+ `ready=${channel.ready ? 'yes' : 'no'}`,
37
+ `delivery=${channel.delivery}`,
38
+ `risk=${channel.risk}`,
39
+ target,
40
+ missing,
41
+ ].join(' ');
42
+ }),
43
+ ];
44
+ ctx.print(lines.join('\n'));
45
+ },
46
+ });
47
+ }
@@ -77,13 +77,13 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
77
77
  if (sub === 'auth') {
78
78
  const auth = readModels.localAuth.getSnapshot();
79
79
  ctx.print([
80
- 'Health Review: Runtime Auth',
81
- ' owner: external GoodVibes runtime host',
80
+ 'Health Review: Connected-Service Auth',
81
+ ' owner: connected GoodVibes services',
82
82
  ` compatibility users visible: ${auth.userCount}`,
83
83
  ` compatibility sessions visible: ${auth.sessionCount}`,
84
84
  ` bootstrap file signal: ${auth.bootstrapCredentialPresent ? 'present' : 'cleared'}`,
85
- ' Agent action: review provider/subscription auth only; do not mutate runtime auth users or bootstrap credentials.',
86
- ...(auth.bootstrapCredentialPresent ? [' issue: bootstrap cleanup belongs to the runtime-owning TUI or host tooling'] : []),
85
+ ' Agent action: review provider/subscription auth only; do not mutate connected-service auth users or bootstrap credentials.',
86
+ ...(auth.bootstrapCredentialPresent ? [' issue: bootstrap cleanup belongs outside Agent'] : []),
87
87
  ].join('\n'));
88
88
  return;
89
89
  }
@@ -132,7 +132,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
132
132
  ` degraded: ${snapshot.degradedConnections}`,
133
133
  ...(issues.length > 0 ? issues.map((issue) => ` issue: ${issue}`) : [' no active remote recovery issues detected']),
134
134
  ' next: /delegate <build/fix/review task> for explicit TUI build work',
135
- ' next: use the external runtime host for remote worker repair',
135
+ ' next: repair remote worker state outside Agent',
136
136
  ].join('\n'));
137
137
  return;
138
138
  }
@@ -217,7 +217,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
217
217
  lines.push(' domain: settings');
218
218
  lines.push(...(
219
219
  settings.conflicts.length > 0
220
- ? [' /settings', ' /config <key>', ' runtime-owned managed setting repair stays external']
220
+ ? [' /settings', ' /config <key>', ' service-owned managed setting repair stays external']
221
221
  : [' no active settings repair actions suggested']
222
222
  ));
223
223
  lines.push(' verify: /health settings');
@@ -226,7 +226,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
226
226
  lines.push(' /auth review');
227
227
  lines.push(' /provider');
228
228
  lines.push(' /subscription providers');
229
- lines.push(' runtime auth users/bootstrap cleanup: use the runtime-owning GoodVibes TUI or host tooling');
229
+ lines.push(' connected-service auth users/bootstrap cleanup: manage outside Agent');
230
230
  lines.push(' verify: /health auth');
231
231
  } else if (domain === 'accounts') {
232
232
  lines.push(' domain: accounts');
@@ -238,12 +238,12 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
238
238
  } else if (domain === 'services') {
239
239
  lines.push(' domain: services');
240
240
  lines.push(' /health services');
241
- lines.push(' runtime service repair belongs to the external GoodVibes runtime host');
241
+ lines.push(' runtime service repair belongs outside Agent');
242
242
  lines.push(' verify: /health services');
243
243
  } else if (domain === 'remote') {
244
244
  lines.push(' domain: remote');
245
245
  lines.push(' /delegate <build/fix/review task> for explicit TUI build work');
246
- lines.push(' remote runner setup and recovery belong to the external runtime host');
246
+ lines.push(' remote runner setup and recovery belong outside Agent');
247
247
  lines.push(' verify: /health remote');
248
248
  } else if (domain === 'mcp') {
249
249
  lines.push(' domain: mcp');
@@ -311,7 +311,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
311
311
  ` account issues: ${accountSnapshot.issueCount}`,
312
312
  ` settings conflicts: ${settingsSnapshot.conflicts.length}`,
313
313
  ` managed locks: ${settingsSnapshot.managedLockCount}`,
314
- ` runtime auth owner: external`,
314
+ ` connected-service auth owner: outside Agent`,
315
315
  ` remote workers: ${snapshot.remoteRunnerCount}`,
316
316
  ...formatSessionMaintenanceLines(maintenance, 'guided').map((line) => ` ${line}`),
317
317
  ...(snapshot.issues.length > 0 ? ['', ...snapshot.issues.map((issue) => ` [${issue.severity.toUpperCase()}] ${issue.area}: ${issue.message}`)] : []),
@@ -1,8 +1,11 @@
1
1
  import type { KnowledgeService } from '@pellux/goodvibes-sdk/platform/knowledge';
2
+ import type { BrowserKnowledgeKind, BrowserKnowledgeSourceKind } from '@pellux/goodvibes-sdk/platform/knowledge';
2
3
  import type { CommandContext, SlashCommand } from '../command-registry.ts';
3
4
  import { requireYesFlag, stripYesFlag } from './confirmation.ts';
4
5
 
5
6
  const KNOWLEDGE_REVIEW_ACTIONS = ['accept', 'reject', 'resolve', 'reopen', 'edit', 'forget'] as const;
7
+ const BROWSER_KINDS = ['chrome', 'chromium', 'brave', 'edge', 'vivaldi', 'arc', 'opera', 'firefox', 'zen', 'librewolf', 'waterfox', 'floorp', 'safari', 'orion', 'epiphany'] as const satisfies readonly BrowserKnowledgeKind[];
8
+ const BROWSER_SOURCE_KINDS = ['history', 'bookmark'] as const satisfies readonly BrowserKnowledgeSourceKind[];
6
9
 
7
10
  type KnowledgeReviewAction = typeof KNOWLEDGE_REVIEW_ACTIONS[number];
8
11
  type KnowledgeAskInput = Parameters<KnowledgeService['ask']>[0];
@@ -63,6 +66,45 @@ function readPositiveIntFlag(args: string[], name: string, fallback: number): nu
63
66
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
64
67
  }
65
68
 
69
+ function readFirstStringListFlag(args: string[], names: readonly string[]): string[] {
70
+ for (const name of names) {
71
+ const values = readStringListFlag(args, name);
72
+ if (values.length > 0) return values;
73
+ }
74
+ return [];
75
+ }
76
+
77
+ function toBrowserKinds(values: readonly string[]): readonly BrowserKnowledgeKind[] {
78
+ return values.filter((value): value is BrowserKnowledgeKind => BROWSER_KINDS.includes(value as BrowserKnowledgeKind));
79
+ }
80
+
81
+ function toBrowserSourceKinds(values: readonly string[]): readonly BrowserKnowledgeSourceKind[] {
82
+ return values.filter((value): value is BrowserKnowledgeSourceKind => BROWSER_SOURCE_KINDS.includes(value as BrowserKnowledgeSourceKind));
83
+ }
84
+
85
+ function readSinceMsFlag(args: string[]): number | undefined {
86
+ const raw = readFlag(args, '--since-days');
87
+ if (!raw) return undefined;
88
+ const parsed = Number.parseInt(raw, 10);
89
+ if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
90
+ return Date.now() - parsed * 24 * 60 * 60 * 1000;
91
+ }
92
+
93
+ function parseConnectorInputFlag(args: string[]): unknown {
94
+ const value = readFlag(args, '--input');
95
+ if (!value) return undefined;
96
+ const trimmed = value.trim();
97
+ if (!trimmed) return undefined;
98
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
99
+ try {
100
+ return JSON.parse(trimmed) as unknown;
101
+ } catch {
102
+ return trimmed;
103
+ }
104
+ }
105
+ return trimmed;
106
+ }
107
+
66
108
  function readJsonObjectFlag(args: string[], name: string): Record<string, unknown> | null | undefined {
67
109
  const value = readFlag(args, name);
68
110
  if (!value) return undefined;
@@ -159,7 +201,7 @@ export const knowledgeCommand: SlashCommand = {
159
201
  aliases: ['know', 'kb'],
160
202
  description: 'Agent Knowledge/Wiki: isolated Agent-owned sources, graph, review queue, and compact prompt packets.',
161
203
  usage: '<subcommand> [args]',
162
- argsHint: 'status|ask|ingest-url --yes|import-bookmarks --yes|list|search|get|queue|review-issue --yes',
204
+ argsHint: 'status|ask|ingest-url --yes|ingest-file --yes|import-bookmarks --yes|list|search|get|queue|review-issue --yes',
163
205
  handler: async (args: string[], context: CommandContext): Promise<void> => {
164
206
  const knowledge = requireAgentKnowledgeApi(context);
165
207
  if (!knowledge) {
@@ -243,6 +285,31 @@ export const knowledgeCommand: SlashCommand = {
243
285
  break;
244
286
  }
245
287
 
288
+ case 'ingest-file':
289
+ case 'ingest-artifact': {
290
+ const [path] = positionalArgs(rest, ['--title', '--tags', '--folder', '--connector']);
291
+ if (!path) {
292
+ context.print('[knowledge] Usage: /knowledge ingest-file <path> [--title <title>] [--tags <a,b>] [--folder <path>] --yes');
293
+ return;
294
+ }
295
+ if (!confirmation.yes) {
296
+ requireYesFlag(context, `ingest file into Agent Knowledge ${path}`, '/knowledge ingest-file <path> [--title <title>] [--tags <a,b>] [--folder <path>] --yes');
297
+ return;
298
+ }
299
+ const result = await knowledge.ingest.artifact({
300
+ path,
301
+ title: readFlag(rest, '--title'),
302
+ tags: readStringListFlag(rest, '--tags'),
303
+ folderPath: readFlag(rest, '--folder'),
304
+ sessionId: context.session.runtime.sessionId,
305
+ connectorId: readFlag(rest, '--connector') ?? 'goodvibes-agent-file',
306
+ });
307
+ context.print(`[knowledge] Ingested ${result.source.id} ${result.source.canonicalUri ?? result.source.sourceUri ?? path}`);
308
+ if (result.source.summary) context.print(` ${result.source.summary}`);
309
+ if (result.artifactId) context.print(` artifact: ${result.artifactId}`);
310
+ break;
311
+ }
312
+
246
313
  case 'import-bookmarks': {
247
314
  const [path] = positionalArgs(rest);
248
315
  if (!path) {
@@ -279,6 +346,61 @@ export const knowledgeCommand: SlashCommand = {
279
346
  break;
280
347
  }
281
348
 
349
+ case 'import-browser-history':
350
+ case 'sync-browser-history': {
351
+ if (!confirmation.yes) {
352
+ requireYesFlag(context, 'import browser history into Agent Knowledge', '/knowledge import-browser-history [--browsers chrome,firefox] [--sources history,bookmark] [--limit <n>] [--since-days <n>] --yes');
353
+ return;
354
+ }
355
+ const result = await knowledge.ingest.browserHistory({
356
+ browsers: toBrowserKinds(readFirstStringListFlag(rest, ['--browsers', '--browser'])),
357
+ sourceKinds: toBrowserSourceKinds(readFirstStringListFlag(rest, ['--sources', '--source-kinds', '--source-kind'])),
358
+ homeOverride: readFlag(rest, '--home'),
359
+ limit: readPositiveIntFlag(rest, '--limit', 250),
360
+ sinceMs: readSinceMsFlag(rest),
361
+ connectorId: 'goodvibes-agent-browser-history',
362
+ sessionId: context.session.runtime.sessionId,
363
+ });
364
+ context.print(`[knowledge] Imported browser knowledge: ${result.imported} ok, ${result.failed} failed`);
365
+ if (result.profiles.length > 0) context.print(` profiles: ${result.profiles.length}`);
366
+ if (result.errors.length > 0) {
367
+ for (const error of result.errors.slice(0, 10)) context.print(` error: ${error}`);
368
+ }
369
+ break;
370
+ }
371
+
372
+ case 'ingest-connector': {
373
+ const [connectorId] = positionalArgs(rest, ['--input', '--path', '--content']);
374
+ if (!connectorId) {
375
+ context.print('[knowledge] Usage: /knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes');
376
+ return;
377
+ }
378
+ const input = parseConnectorInputFlag(rest);
379
+ const path = readFlag(rest, '--path');
380
+ const content = readFlag(rest, '--content');
381
+ if (input === undefined && !path && !content) {
382
+ context.print('[knowledge] Usage: /knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes');
383
+ return;
384
+ }
385
+ if (!confirmation.yes) {
386
+ requireYesFlag(context, `ingest connector input into Agent Knowledge for ${connectorId}`, '/knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes');
387
+ return;
388
+ }
389
+ const result = await knowledge.ingest.connectorInput({
390
+ connectorId,
391
+ input,
392
+ path,
393
+ content,
394
+ sessionId: context.session.runtime.sessionId,
395
+ allowPrivateHosts: rest.includes('--allow-private-hosts'),
396
+ });
397
+ context.print(`[knowledge] Imported connector input: ${result.imported} ok, ${result.failed} failed`);
398
+ if (result.errors.length > 0) {
399
+ for (const error of result.errors.slice(0, 10)) context.print(` error: ${error}`);
400
+ }
401
+ break;
402
+ }
403
+
282
404
  case 'list': {
283
405
  const limit = Math.max(1, Number.parseInt(readFlag(rest, '--limit') ?? '10', 10) || 10);
284
406
  const kind = (readFlag(rest, '--kind') ?? 'sources').toLowerCase();
@@ -464,6 +586,55 @@ export const knowledgeCommand: SlashCommand = {
464
586
  break;
465
587
  }
466
588
 
589
+ case 'connectors': {
590
+ const [first, second] = positionalArgs(rest);
591
+ if (first === 'doctor') {
592
+ if (!second) {
593
+ context.print('[knowledge] Usage: /knowledge connectors doctor <connectorId>');
594
+ return;
595
+ }
596
+ const report = await knowledge.connectors.doctor(second);
597
+ if (!report) {
598
+ context.print(`[knowledge] Connector doctor report unavailable: ${second}`);
599
+ return;
600
+ }
601
+ context.print([
602
+ `[knowledge] Connector doctor ${report.connectorId}`,
603
+ ` ready: ${report.ready ? 'yes' : 'no'}`,
604
+ ` summary: ${report.summary}`,
605
+ ...(report.checks.length > 0 ? [' checks:', ...report.checks.slice(0, 10).map((check) => ` - ${check.id} [${check.status}] ${check.label} - ${check.detail}`)] : []),
606
+ ...(report.hints.length > 0 ? [' hints:', ...report.hints.slice(0, 8).map((hint) => ` - ${hint}`)] : []),
607
+ ].join('\n'));
608
+ return;
609
+ }
610
+ if (first) {
611
+ const connector = knowledge.connectors.get(first);
612
+ if (!connector) {
613
+ context.print(`[knowledge] Unknown connector: ${first}`);
614
+ return;
615
+ }
616
+ context.print([
617
+ `[knowledge] Connector ${connector.id}`,
618
+ ` name: ${connector.displayName ?? connector.id}`,
619
+ ` sourceType: ${connector.sourceType}`,
620
+ ` description: ${connector.description}`,
621
+ ` capabilities: ${connector.capabilities?.join(', ') || 'none'}`,
622
+ ].join('\n'));
623
+ return;
624
+ }
625
+ const connectors = knowledge.connectors.list();
626
+ if (connectors.length === 0) {
627
+ context.print('[knowledge] No connectors.');
628
+ return;
629
+ }
630
+ context.print(`[knowledge] Connectors (${connectors.length}):`);
631
+ for (const connector of connectors) {
632
+ context.print(` ${connector.id} [${connector.sourceType}] ${connector.displayName ?? connector.id}`);
633
+ context.print(` ${connector.description}`);
634
+ }
635
+ break;
636
+ }
637
+
467
638
  case 'reports': {
468
639
  const [limitArg] = positionalArgs(rest);
469
640
  const limit = Math.max(1, Number.parseInt(limitArg ?? '10', 10) || 10);
@@ -564,14 +735,18 @@ export const knowledgeCommand: SlashCommand = {
564
735
  ' status',
565
736
  ' ask <query> [--limit <n>] [--mode <concise|standard|detailed>]',
566
737
  ' ingest-url <url> [--title <title>] [--tags <a,b>] [--folder <path>] --yes',
738
+ ' ingest-file <path> [--title <title>] [--tags <a,b>] [--folder <path>] --yes',
739
+ ' ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes',
567
740
  ' import-bookmarks <path> --yes',
568
741
  ' import-urls <path> --yes',
742
+ ' import-browser-history [--browsers chrome,firefox] [--sources history,bookmark] [--limit <n>] [--since-days <n>] --yes',
569
743
  ' list [--kind <sources|nodes|issues>] [--limit <n>]',
570
744
  ' search <query> [--limit <n>]',
571
745
  ' get <id>',
572
746
  ' queue [limit]',
573
747
  ' review-issue <issueId> <accept|reject|resolve|reopen|edit|forget> [--reviewer <name>] [--value <json-object>] --yes',
574
748
  ' candidates [limit]',
749
+ ' connectors [connectorId|doctor <connectorId>]',
575
750
  ' reports [limit]',
576
751
  ' schedules',
577
752
  ' lint',
@@ -62,7 +62,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
62
62
  const parsed = stripYesFlag(args);
63
63
  const subcommand = parsed.rest[0]?.toLowerCase() ?? '';
64
64
  if ((subcommand === 'override' || subcommand === 'clear') && !parsed.yes) {
65
- requireYesFlag(ctx, `${subcommand} planner runtime state`, `/plan ${subcommand}${subcommand === 'override' ? ' <strategy>' : ''} --yes`);
65
+ requireYesFlag(ctx, `${subcommand} planner state`, `/plan ${subcommand}${subcommand === 'override' ? ' <strategy>' : ''} --yes`);
66
66
  return;
67
67
  }
68
68
  const result = ctx.ops.planRuntime
@@ -55,9 +55,9 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
55
55
  }
56
56
  if (target === 'service' || target === 'runtime' || target === 'listener' || target === 'daemon') {
57
57
  ctx.print([
58
- 'Runtime service login is external to GoodVibes Agent.',
59
- 'Agent does not create, exchange, store, rotate, revoke, or clear runtime/listener service sessions.',
60
- 'Use the runtime-owning GoodVibes TUI or host tooling for runtime auth administration.',
58
+ 'Connected-service login is outside GoodVibes Agent.',
59
+ 'Agent does not create, exchange, store, rotate, revoke, or clear connected-service sessions.',
60
+ 'Use the owning GoodVibes host for connected-service auth administration.',
61
61
  'Agent login supports provider subscriptions only: /login provider <name> start|finish <code> --yes.',
62
62
  ].join('\n'));
63
63
  return;
@@ -100,18 +100,18 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
100
100
  const sub = commandArgs[0] ?? 'review';
101
101
  if (sub === 'local') {
102
102
  ctx.print([
103
- 'Local runtime auth management is external to GoodVibes Agent.',
104
- 'Agent connects to an already-running GoodVibes runtime and does not create, delete, rotate, revoke, or clear runtime auth users, sessions, or bootstrap credentials.',
105
- 'Use the runtime-owning GoodVibes TUI or host tooling for runtime auth administration.',
103
+ 'Connected-service auth management is outside GoodVibes Agent.',
104
+ 'Agent connects to GoodVibes services owned outside this package and does not create, delete, rotate, revoke, or clear connected-service auth users, sessions, or bootstrap credentials.',
105
+ 'Use the owning GoodVibes host for connected-service auth administration.',
106
106
  'Agent auth commands available here: /auth review, /auth show <provider>, /auth repair <provider>, /auth bundle export <path> --yes, /auth bundle inspect <path>.',
107
107
  ].join('\n'));
108
108
  return;
109
109
  }
110
110
  if (sub === 'login') {
111
111
  ctx.print([
112
- 'Runtime service login is external to GoodVibes Agent.',
113
- 'Agent does not create, exchange, store, rotate, revoke, or clear runtime/listener service sessions.',
114
- 'Use the runtime-owning GoodVibes TUI or host tooling for runtime auth administration.',
112
+ 'Connected-service login is outside GoodVibes Agent.',
113
+ 'Agent does not create, exchange, store, rotate, revoke, or clear connected-service sessions.',
114
+ 'Use the owning GoodVibes host for connected-service auth administration.',
115
115
  ].join('\n'));
116
116
  return;
117
117
  }
@@ -129,7 +129,7 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
129
129
  const builtinProviders = listBuiltinSubscriptionProviders().map((entry) => entry.provider);
130
130
  ctx.print([
131
131
  'Auth Review',
132
- ' runtime auth: managed outside goodvibes-agent',
132
+ ' connected-service auth: managed outside goodvibes-agent',
133
133
  ` stored secrets: ${snapshot.secretKeyCount}`,
134
134
  ` built-in providers: ${builtinProviders.length}${builtinProviders.length > 0 ? ` (${builtinProviders.join(', ')})` : ''}`,
135
135
  ` active subscriptions: ${snapshot.activeSubscriptions}${snapshot.activeSubscriptions > 0 ? ` (${snapshot.providers.filter((provider) => provider.activeSubscription).map((provider) => provider.provider).join(', ')})` : ''}`,
@@ -15,7 +15,7 @@ import { requireShellPaths } from './runtime-services.ts';
15
15
  function getPolicyState(ctx?: CommandContext): PolicyRuntimeState {
16
16
  const policyRuntimeState = ctx?.extensions.policyRuntimeState;
17
17
  if (!policyRuntimeState) {
18
- throw new Error('Policy runtime state is not available in this runtime.');
18
+ throw new Error('Policy state is not available in this Agent session.');
19
19
  }
20
20
  return policyRuntimeState;
21
21
  }
@@ -4,6 +4,17 @@ import {
4
4
  } from '@pellux/goodvibes-sdk/platform/automation';
5
5
  import type { AutomationJob } from '@pellux/goodvibes-sdk/platform/automation';
6
6
  import type { AutomationScheduleDefinition } from '@pellux/goodvibes-sdk/platform/automation';
7
+ import {
8
+ buildReminderSchedulePreview,
9
+ createReminderSchedule,
10
+ parseReminderScheduleArgs,
11
+ resolveReminderDaemonConnection,
12
+ } from '../../agent/reminder-schedule.ts';
13
+ import {
14
+ formatReminderScheduleFailure,
15
+ formatReminderSchedulePreview,
16
+ formatReminderScheduleSuccess,
17
+ } from '../../agent/reminder-schedule-format.ts';
7
18
  import { AgentRoutineRegistry } from '../../agent/routine-registry.ts';
8
19
  import {
9
20
  buildRoutineSchedulePreview,
@@ -56,7 +67,7 @@ function printReadOnlyScheduleBoundary(print: (text: string) => void, requestedA
56
67
  ` requested: ${requestedAction}`,
57
68
  ' policy: no local Agent automation jobs, scheduled spawns, or immediate automation runs',
58
69
  ' use: /schedule list',
59
- ' schedule route: use /schedule promote-routine <routine> --cron <expr> --yes to create an external schedule explicitly',
70
+ ' schedule route: use /schedule promote-routine <routine> --cron <expr> --yes to create a connected schedule explicitly',
60
71
  ].join('\n'));
61
72
  }
62
73
 
@@ -86,16 +97,41 @@ async function promoteRoutineSchedule(args: readonly string[], ctx: CommandConte
86
97
  ctx.print(result.ok ? `${formatRoutineScheduleSuccess(result)}\n receipt: ${receipt.id}` : `${formatRoutineScheduleFailure(result)}\n receipt: ${receipt.id}`);
87
98
  }
88
99
 
100
+ async function createReminder(args: readonly string[], ctx: CommandContext): Promise<void> {
101
+ const parsed = parseReminderScheduleArgs(args);
102
+ if (parsed.errors.length > 0) {
103
+ ctx.print([
104
+ 'Usage: /schedule remind (--cron <expr>|--every <interval>|--at <iso-time>) (--message <text>|<text...>) [--timezone <tz>] [--name <schedule-name>] [--provider <id>] [--model <model>] [--delivery-channel <channel[:route[:label]]>|--delivery-route <route[:label]>|--delivery-webhook <url>|--delivery-link <url>] [--disabled] --yes',
105
+ ...parsed.errors.map((error) => ` ${error}`),
106
+ ].join('\n'));
107
+ return;
108
+ }
109
+ const preview = buildReminderSchedulePreview(parsed);
110
+ if (!parsed.yes) {
111
+ ctx.print(formatReminderSchedulePreview(preview));
112
+ return;
113
+ }
114
+ const shellPaths = requireShellPaths(ctx);
115
+ const connection = resolveReminderDaemonConnection(ctx.platform.configManager, shellPaths.homeDirectory);
116
+ const result = await createReminderSchedule(connection, preview);
117
+ ctx.print(result.ok ? formatReminderScheduleSuccess(result) : formatReminderScheduleFailure(result));
118
+ }
119
+
89
120
  export function registerScheduleRuntimeCommands(registry: CommandRegistry): void {
90
121
  registry.register({
91
122
  name: 'schedule',
92
123
  aliases: ['sched'],
93
- description: 'Inspect schedules and explicitly promote local Agent routines to external schedules',
94
- usage: 'list | receipts | reconcile | receipt <id> | promote-routine <routine-id> --cron <expr> [--delivery-channel slack] --yes',
95
- argsHint: 'list | receipts | reconcile | receipt <id> | promote-routine <routine-id> --cron <expr> [--delivery-channel slack] --yes',
124
+ description: 'Inspect schedules, create confirmed reminders, and explicitly promote local Agent routines to connected schedules',
125
+ usage: 'list | remind --at <iso> --message <text> --yes | receipts | reconcile | receipt <id> | promote-routine <routine-id> --cron <expr> [--delivery-channel slack] --yes',
126
+ argsHint: 'list | remind --at <iso> --message <text> --yes | receipts | reconcile | receipt <id> | promote-routine <routine-id> --cron <expr> [--delivery-channel slack] --yes',
96
127
  async handler(args, ctx) {
97
128
  const sub = args[0];
98
129
 
130
+ if (sub === 'remind' || sub === 'reminder') {
131
+ await createReminder(args.slice(1), ctx);
132
+ return;
133
+ }
134
+
99
135
  if (sub === 'promote-routine' || sub === 'promote' || sub === 'create-routine-schedule') {
100
136
  await promoteRoutineSchedule(args.slice(1), ctx);
101
137
  return;
@@ -136,7 +172,7 @@ export function registerScheduleRuntimeCommands(registry: CommandRegistry): void
136
172
  if (jobs.length === 0) {
137
173
  ctx.print(
138
174
  'No automation jobs.\n'
139
- + 'Local add/run/enable/disable/remove are blocked. Use /schedule promote-routine <routine> --cron <expr> --yes for an explicit external schedule.'
175
+ + 'Local add/run/enable/disable/remove are blocked. Use /schedule remind --at <time> --message <text> --yes for reminders or /schedule promote-routine <routine> --cron <expr> --yes for explicit connected schedules.'
140
176
  );
141
177
  return;
142
178
  }
@@ -164,6 +200,7 @@ export function registerScheduleRuntimeCommands(registry: CommandRegistry): void
164
200
  + ' /schedule receipts\n'
165
201
  + ' /schedule reconcile\n'
166
202
  + ' /schedule receipt <receipt-id>\n'
203
+ + ' /schedule remind (--cron <expr>|--every <interval>|--at <iso-time>) (--message <text>|<text...>) [--delivery-channel <channel>|--delivery-route <route>|--delivery-webhook <url>] --yes\n'
167
204
  + ' /schedule promote-routine <routine-id> (--cron <expr>|--every <interval>|--at <iso-time>) [--delivery-channel <channel>|--delivery-route <route>|--delivery-webhook <url>] --yes\n'
168
205
  + ' Local schedule mutations and runs remain blocked.'
169
206
  );
@@ -24,7 +24,7 @@ export function registerSecurityRuntimeCommands(registry: CommandRegistry): void
24
24
  const securitySnapshot = requireReadModels(ctx).security.getSnapshot();
25
25
  const policySnapshot = ctx.extensions.policyRuntimeState?.getSnapshot();
26
26
  if (!policySnapshot) {
27
- ctx.print('Policy runtime state is not available in this runtime.');
27
+ ctx.print('Policy state is not available in this Agent session.');
28
28
  return;
29
29
  }
30
30
  const attackPaths = buildMcpAttackPathReview({