@pellux/goodvibes-agent 0.1.111 → 0.1.112
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.
- package/CHANGELOG.md +16 -0
- package/README.md +3 -3
- package/dist/package/main.js +10027 -10034
- package/docs/getting-started.md +3 -3
- package/package.json +1 -1
- package/src/cli/help.ts +0 -1
- package/src/cli/parser.ts +0 -8
- package/src/cli/types.ts +0 -1
- package/src/input/agent-workspace-categories.ts +5 -7
- package/src/input/agent-workspace-snapshot.ts +2 -10
- package/src/input/agent-workspace-types.ts +2 -3
- package/src/input/commands/brief-runtime.ts +1 -1
- package/src/input/commands/channels-runtime.ts +397 -35
- package/src/input/commands/experience-runtime.ts +4 -9
- package/src/input/commands/health-runtime.ts +26 -6
- package/src/input/commands/mcp-runtime.ts +51 -26
- package/src/input/commands/memory.ts +10 -10
- package/src/input/commands/planning-runtime.ts +4 -9
- package/src/input/commands/provider-accounts-runtime.ts +2 -3
- package/src/input/commands/qrcode-runtime.ts +48 -9
- package/src/input/commands/recall-bundle.ts +16 -16
- package/src/input/commands/recall-capture.ts +18 -18
- package/src/input/commands/recall-query.ts +22 -22
- package/src/input/commands/recall-review.ts +12 -12
- package/src/input/commands/tasks-runtime.ts +9 -15
- package/src/input/commands/work-plan-runtime.ts +5 -19
- package/src/input/commands.ts +2 -7
- package/src/renderer/agent-workspace.ts +1 -2
- package/src/runtime/onboarding/derivation.ts +6 -67
- package/src/version.ts +1 -1
|
@@ -7,7 +7,6 @@ import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
|
|
|
7
7
|
import { getSettingsControlPlaneSnapshot } from '@/runtime/index.ts';
|
|
8
8
|
import { checkRecoveryFile, readLastSessionPointer } from '@/runtime/index.ts';
|
|
9
9
|
import {
|
|
10
|
-
openCommandPanel,
|
|
11
10
|
requireOperatorClient,
|
|
12
11
|
requireProviderApi,
|
|
13
12
|
requireReadModels,
|
|
@@ -22,16 +21,37 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
22
21
|
name: 'health',
|
|
23
22
|
aliases: ['doctor'],
|
|
24
23
|
description: 'Health workspace for startup posture, service readiness, provider health, and Agent continuity',
|
|
25
|
-
usage: '[
|
|
24
|
+
usage: '[review|setup|services|provider|accounts|auth|settings|remote|mcp|continuity|maintenance|repair [domain]]',
|
|
26
25
|
async handler(args, ctx) {
|
|
27
26
|
const sub = (args[0] ?? 'review').toLowerCase();
|
|
28
|
-
const readModels = requireReadModels(ctx);
|
|
29
27
|
|
|
30
|
-
if (sub === 'open' || sub === 'panel'
|
|
31
|
-
|
|
28
|
+
if (sub === 'open' || sub === 'panel') {
|
|
29
|
+
ctx.print('Health panels are not part of the Agent workspace. Use /health review.');
|
|
32
30
|
return;
|
|
33
31
|
}
|
|
34
32
|
|
|
33
|
+
if (sub === 'provider') {
|
|
34
|
+
const providerApi = requireProviderApi(ctx);
|
|
35
|
+
const currentModel = await providerApi.getCurrentModel().catch(() => null);
|
|
36
|
+
const accounts = await requireOperatorClient(ctx).providers.accountSnapshot();
|
|
37
|
+
ctx.print([
|
|
38
|
+
'Health Review: Provider',
|
|
39
|
+
` selected model: ${currentModel?.registryKey ?? 'unknown'}`,
|
|
40
|
+
` providers: ${providerApi.listProviderIds().length}`,
|
|
41
|
+
` configured accounts: ${accounts.configuredCount}`,
|
|
42
|
+
` account issues: ${accounts.issueCount}`,
|
|
43
|
+
...(accounts.issueCount > 0
|
|
44
|
+
? accounts.providers.flatMap((provider) => provider.issues.map((issue) => ` ${provider.providerId}: ${issue}`))
|
|
45
|
+
: [' no provider account issues detected']),
|
|
46
|
+
' next: /model',
|
|
47
|
+
' next: /provider',
|
|
48
|
+
' next: /accounts review',
|
|
49
|
+
].join('\n'));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const readModels = requireReadModels(ctx);
|
|
54
|
+
|
|
35
55
|
if (sub === 'services') {
|
|
36
56
|
const registry = requireServiceRegistry(ctx);
|
|
37
57
|
const all = registry.getAll();
|
|
@@ -318,7 +338,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
318
338
|
...(snapshot.serviceIssues.length > 0 ? ['', ...snapshot.serviceIssues.map((issue) => ` service: ${issue}`)] : []),
|
|
319
339
|
'',
|
|
320
340
|
'Next steps:',
|
|
321
|
-
' /health
|
|
341
|
+
' /health review',
|
|
322
342
|
' /health services',
|
|
323
343
|
' /health accounts',
|
|
324
344
|
' /health auth',
|
|
@@ -137,8 +137,8 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
137
137
|
name: 'mcp',
|
|
138
138
|
aliases: [],
|
|
139
139
|
description: 'Manage MCP servers and their tools',
|
|
140
|
-
usage: '[
|
|
141
|
-
argsHint: '[review|tools|config|add --yes|remove --yes]',
|
|
140
|
+
usage: '[servers|review|tools [<server>]|config|add|remove|reload|auth-review|repair [server]]',
|
|
141
|
+
argsHint: '[servers|review|tools|config|add --yes|remove --yes]',
|
|
142
142
|
async handler(args, ctx) {
|
|
143
143
|
const mcpApi = requireMcpApi(ctx);
|
|
144
144
|
const listServerSecurity = () => mcpApi.listServerSecurity();
|
|
@@ -163,6 +163,25 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
163
163
|
].join('\n'));
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
|
+
if (subcommand === 'servers') {
|
|
167
|
+
const servers = listServerSecurity();
|
|
168
|
+
if (servers.length === 0) {
|
|
169
|
+
ctx.print(
|
|
170
|
+
'No MCP servers configured.\n'
|
|
171
|
+
+ 'Add servers to one of these locations (scanned in order):\n'
|
|
172
|
+
+ ' ~/.config/mcp/mcp.json (global XDG)\n'
|
|
173
|
+
+ ' ~/.mcp/mcp.json (global dotdir)\n'
|
|
174
|
+
+ ' ~/.config/claude/claude_desktop_config.json (Claude Desktop)\n'
|
|
175
|
+
+ ' .mcp/mcp.json (project-local)\n'
|
|
176
|
+
+ ' .goodvibes/mcp.json (goodvibes project)\n'
|
|
177
|
+
+ '\nAdd one from inside Agent with explicit confirmation:\n'
|
|
178
|
+
+ ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --yes'
|
|
179
|
+
);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
ctx.print(formatMcpServerList(servers));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
166
185
|
if (subcommand === 'tools') {
|
|
167
186
|
const filterServer = commandArgs[1];
|
|
168
187
|
ctx.print('Fetching MCP tool list...');
|
|
@@ -434,30 +453,36 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
434
453
|
return;
|
|
435
454
|
}
|
|
436
455
|
|
|
437
|
-
|
|
438
|
-
const disconnected = servers.filter(s => !s.connected);
|
|
439
|
-
const lines: string[] = [`MCP Servers (${connected.length}/${servers.length} connected):`];
|
|
440
|
-
for (const s of servers) {
|
|
441
|
-
const pathScope = s.allowedPaths.length > 0 ? ` paths=${s.allowedPaths.length}` : '';
|
|
442
|
-
const hostScope = s.allowedHosts.length > 0 ? ` hosts=${s.allowedHosts.length}` : '';
|
|
443
|
-
const freshness = ` freshness=${s.schemaFreshness}`;
|
|
444
|
-
const quarantine = s.schemaFreshness === 'quarantined' ? ` quarantine=${s.quarantineReason ?? 'unknown'}` : '';
|
|
445
|
-
lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${s.trustMode} role=${s.role}${freshness}${quarantine}${pathScope}${hostScope}`);
|
|
446
|
-
}
|
|
447
|
-
if (connected.length > 0) {
|
|
448
|
-
lines.push('');
|
|
449
|
-
lines.push('Run "/mcp tools" to list all tools, or "/mcp tools <server>" for a specific server.');
|
|
450
|
-
lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global] --yes" to add/update.');
|
|
451
|
-
lines.push('Run "/mcp reload --yes" after editing MCP config outside Agent.');
|
|
452
|
-
lines.push('Run "/mcp trust <server> <mode> --yes" to change trust mode, or "/mcp role <server> <role> --yes" to change its coherence role.');
|
|
453
|
-
lines.push('Run "/mcp quarantine <server> [detail] --yes" to block a server, or "/mcp quarantine <server> approve [operatorId] --yes" to approve a temporary override.');
|
|
454
|
-
lines.push('Use /settings → MCP to explicitly enable allow-all for a server.');
|
|
455
|
-
}
|
|
456
|
-
if (disconnected.length > 0) {
|
|
457
|
-
lines.push('');
|
|
458
|
-
lines.push(`${disconnected.length} server(s) failed to connect. Check server command and args in your config.`);
|
|
459
|
-
}
|
|
460
|
-
ctx.print(lines.join('\n'));
|
|
456
|
+
ctx.print(formatMcpServerList(servers));
|
|
461
457
|
},
|
|
462
458
|
});
|
|
463
459
|
}
|
|
460
|
+
|
|
461
|
+
type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
|
|
462
|
+
|
|
463
|
+
function formatMcpServerList(servers: readonly McpSecurityServer[]): string {
|
|
464
|
+
const connected = servers.filter(s => s.connected);
|
|
465
|
+
const disconnected = servers.filter(s => !s.connected);
|
|
466
|
+
const lines: string[] = [`MCP Servers (${connected.length}/${servers.length} connected):`];
|
|
467
|
+
for (const s of servers) {
|
|
468
|
+
const pathScope = s.allowedPaths.length > 0 ? ` paths=${s.allowedPaths.length}` : '';
|
|
469
|
+
const hostScope = s.allowedHosts.length > 0 ? ` hosts=${s.allowedHosts.length}` : '';
|
|
470
|
+
const freshness = ` freshness=${s.schemaFreshness}`;
|
|
471
|
+
const quarantine = s.schemaFreshness === 'quarantined' ? ` quarantine=${s.quarantineReason ?? 'unknown'}` : '';
|
|
472
|
+
lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${s.trustMode} role=${s.role}${freshness}${quarantine}${pathScope}${hostScope}`);
|
|
473
|
+
}
|
|
474
|
+
if (connected.length > 0) {
|
|
475
|
+
lines.push('');
|
|
476
|
+
lines.push('Run "/mcp tools" to list all tools, or "/mcp tools <server>" for a specific server.');
|
|
477
|
+
lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global] --yes" to add/update.');
|
|
478
|
+
lines.push('Run "/mcp reload --yes" after editing MCP config outside Agent.');
|
|
479
|
+
lines.push('Run "/mcp trust <server> <mode> --yes" to change trust mode, or "/mcp role <server> <role> --yes" to change its coherence role.');
|
|
480
|
+
lines.push('Run "/mcp quarantine <server> [detail] --yes" to block a server, or "/mcp quarantine <server> approve [operatorId] --yes" to approve a temporary override.');
|
|
481
|
+
lines.push('Use /settings -> MCP to explicitly enable allow-all for a server.');
|
|
482
|
+
}
|
|
483
|
+
if (disconnected.length > 0) {
|
|
484
|
+
lines.push('');
|
|
485
|
+
lines.push(`${disconnected.length} server(s) failed to connect. Check server command and args in your config.`);
|
|
486
|
+
}
|
|
487
|
+
return lines.join('\n');
|
|
488
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* /
|
|
2
|
+
* /memory command handler.
|
|
3
3
|
*
|
|
4
4
|
* Implements the Project Memory Substrate commands:
|
|
5
5
|
*
|
|
6
|
-
* /
|
|
7
|
-
* /
|
|
8
|
-
* /
|
|
9
|
-
* /
|
|
10
|
-
* /
|
|
11
|
-
* /
|
|
12
|
-
* /
|
|
13
|
-
* /
|
|
6
|
+
* /memory add <class> <summary> — Add a new memory record
|
|
7
|
+
* /memory add <class> <summary> --detail <text> --tags <tag,tag>
|
|
8
|
+
* /memory search [query] — Search memory records
|
|
9
|
+
* /memory search --cls <class> — Filter by class
|
|
10
|
+
* /memory link <fromId> <toId> <relation> --yes — Link two records
|
|
11
|
+
* /memory get <id> — Show a single record with provenance
|
|
12
|
+
* /memory list [class] — List all records (optionally by class)
|
|
13
|
+
* /memory remove <id> --yes — Delete a record
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
@@ -118,7 +118,7 @@ export const recallCommand: SlashCommand = {
|
|
|
118
118
|
|
|
119
119
|
default: {
|
|
120
120
|
const usage = [
|
|
121
|
-
'Usage: /
|
|
121
|
+
'Usage: /memory <subcommand>',
|
|
122
122
|
' add <class> <summary> [--scope <session|project|team>] [--detail <text>] [--tags <t,t>] [--session <id>] [--task <id>] [--file <path>]',
|
|
123
123
|
` classes: ${VALID_CLASSES.join(', ')}`,
|
|
124
124
|
' capture incident <id|latest> — Capture a forensics incident as durable memory',
|
|
@@ -44,7 +44,7 @@ function formatNextQuestion(question: ProjectPlanningQuestion | undefined): stri
|
|
|
44
44
|
if (!question) return 'No next question recorded.';
|
|
45
45
|
const lines = [`Next question: ${question.prompt}`];
|
|
46
46
|
if (question.recommendedAnswer) lines.push(`Recommended answer: ${question.recommendedAnswer}`);
|
|
47
|
-
lines.push('Answer in the prompt
|
|
47
|
+
lines.push('Answer in the main prompt or review planning state with /plan status.');
|
|
48
48
|
return lines.join('\n');
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -52,8 +52,8 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
|
|
|
52
52
|
registry.register({
|
|
53
53
|
name: 'plan',
|
|
54
54
|
description: 'Inspect or seed Agent workspace planning state',
|
|
55
|
-
usage: '[
|
|
56
|
-
argsHint: '[
|
|
55
|
+
usage: '[approve --yes | list | show <id> | mode | explain | override <strategy> --yes | status | clear --yes | <planning goal>]',
|
|
56
|
+
argsHint: '[approve|status|list|<goal>]',
|
|
57
57
|
async handler(args, ctx) {
|
|
58
58
|
const planManager = requirePlanManager(ctx);
|
|
59
59
|
const sessionLineageTracker = requireSessionLineageTracker(ctx);
|
|
@@ -74,7 +74,6 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
|
|
|
74
74
|
|
|
75
75
|
const projectPlanningService = ctx.workspace.projectPlanningService;
|
|
76
76
|
const projectId = ctx.workspace.projectPlanningProjectId;
|
|
77
|
-
const openProjectPlanningPanel = () => ctx.showPanel?.('project-planning');
|
|
78
77
|
|
|
79
78
|
if (args.length === 0) {
|
|
80
79
|
if (projectPlanningService && projectId) {
|
|
@@ -92,7 +91,6 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
|
|
|
92
91
|
const planningNamespace = String(
|
|
93
92
|
status[['knowledge', 'SpaceId'].join('') as keyof typeof status] ?? `project:${status.projectId}`,
|
|
94
93
|
);
|
|
95
|
-
openProjectPlanningPanel();
|
|
96
94
|
ctx.print(
|
|
97
95
|
`Project planning: ${evaluation.readiness}\n` +
|
|
98
96
|
`Project: ${status.projectId}\n` +
|
|
@@ -113,8 +111,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
|
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
if (args[0] === 'panel') {
|
|
116
|
-
|
|
117
|
-
ctx.print('Opened project planning panel.');
|
|
114
|
+
ctx.print('Planning panels are not part of the Agent workspace. Use /plan status or /plan list.');
|
|
118
115
|
return;
|
|
119
116
|
}
|
|
120
117
|
|
|
@@ -146,7 +143,6 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
|
|
|
146
143
|
},
|
|
147
144
|
});
|
|
148
145
|
const evaluation = await projectPlanningService.evaluate({ projectId });
|
|
149
|
-
openProjectPlanningPanel();
|
|
150
146
|
ctx.print(`Project planning approved. Readiness: ${evaluation.readiness}. State: ${result.state?.id ?? 'current'}.`);
|
|
151
147
|
return;
|
|
152
148
|
}
|
|
@@ -208,7 +204,6 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
|
|
|
208
204
|
? await persistEvaluatedNextQuestion(projectPlanningService, projectId, result.state, initialEvaluation)
|
|
209
205
|
: { state: result.state, evaluation: initialEvaluation };
|
|
210
206
|
sessionLineageTracker.setOriginalTask(taskDescription.slice(0, 200));
|
|
211
|
-
openProjectPlanningPanel();
|
|
212
207
|
|
|
213
208
|
ctx.print(
|
|
214
209
|
`Project planning seeded: "${state?.goal ?? taskDescription}"\n` +
|
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
ProviderAccountSnapshot,
|
|
5
5
|
} from '@/runtime/index.ts';
|
|
6
6
|
import {
|
|
7
|
-
openCommandPanel,
|
|
8
7
|
requireOperatorClient,
|
|
9
8
|
} from './runtime-services.ts';
|
|
10
9
|
|
|
@@ -25,11 +24,11 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
|
|
|
25
24
|
name: 'accounts',
|
|
26
25
|
aliases: ['account'],
|
|
27
26
|
description: 'Review provider auth routes, subscription windows, and billing-path safety',
|
|
28
|
-
usage: '[review|
|
|
27
|
+
usage: '[review|show <provider>|routes <provider>|repair <provider>]',
|
|
29
28
|
async handler(args, ctx) {
|
|
30
29
|
const sub = (args[0] ?? 'review').toLowerCase();
|
|
31
30
|
if (sub === 'panel' || sub === 'open') {
|
|
32
|
-
|
|
31
|
+
ctx.print('Provider account panels are not part of the Agent workspace. Use /accounts review.');
|
|
33
32
|
return;
|
|
34
33
|
}
|
|
35
34
|
const snapshot = await loadProviderAccountSnapshot(ctx);
|
|
@@ -1,20 +1,59 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { networkInterfaces } from 'node:os';
|
|
4
|
+
import {
|
|
5
|
+
buildCompanionConnectionInfo,
|
|
6
|
+
encodeConnectionPayload,
|
|
7
|
+
formatConnectionBlock,
|
|
8
|
+
generateQrMatrix,
|
|
9
|
+
getOrCreateCompanionToken,
|
|
10
|
+
renderQrToString,
|
|
11
|
+
} from '@pellux/goodvibes-sdk/platform/pairing';
|
|
12
|
+
import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../../config/surface.ts';
|
|
13
|
+
import { resolveRuntimeEndpointBinding } from '../../cli/endpoints.ts';
|
|
14
|
+
import { requirePlatform, requireShellPaths } from './runtime-services.ts';
|
|
15
|
+
|
|
16
|
+
function getLocalNetworkIp(): string {
|
|
17
|
+
try {
|
|
18
|
+
const nets = networkInterfaces();
|
|
19
|
+
for (const name of Object.keys(nets)) {
|
|
20
|
+
for (const netInfo of nets[name] ?? []) {
|
|
21
|
+
if (netInfo.family === 'IPv4' && !netInfo.internal) return netInfo.address;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
return '127.0.0.1';
|
|
26
|
+
}
|
|
27
|
+
return '127.0.0.1';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function urlHostForBindHost(host: string): string {
|
|
31
|
+
if (host === '0.0.0.0' || host === '::') return getLocalNetworkIp();
|
|
32
|
+
return host || '127.0.0.1';
|
|
33
|
+
}
|
|
3
34
|
|
|
4
|
-
/**
|
|
5
|
-
* Register the /qrcode command.
|
|
6
|
-
*
|
|
7
|
-
* Opens the QR Code panel which displays a scannable QR code for
|
|
8
|
-
* companion app pairing, along with connection URL, token, and username.
|
|
9
|
-
*/
|
|
10
35
|
export function registerQrcodeRuntimeCommands(registry: CommandRegistry): void {
|
|
11
36
|
registry.register({
|
|
12
37
|
name: 'qrcode',
|
|
13
38
|
aliases: ['qr', 'pair'],
|
|
14
|
-
description: '
|
|
39
|
+
description: 'Print companion pairing details and a QR code',
|
|
15
40
|
usage: '',
|
|
16
41
|
handler(_args, ctx) {
|
|
17
|
-
|
|
42
|
+
const shellPaths = requireShellPaths(ctx);
|
|
43
|
+
const configManager = requirePlatform(ctx).configManager;
|
|
44
|
+
const daemonHomeDir = join(shellPaths.homeDirectory, '.goodvibes', 'daemon');
|
|
45
|
+
const tokenRecord = getOrCreateCompanionToken(GOODVIBES_AGENT_PAIRING_SURFACE, { daemonHomeDir });
|
|
46
|
+
const binding = resolveRuntimeEndpointBinding(configManager, 'controlPlane');
|
|
47
|
+
const daemonUrl = `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
|
|
48
|
+
const info = buildCompanionConnectionInfo({
|
|
49
|
+
daemonUrl,
|
|
50
|
+
token: tokenRecord.token,
|
|
51
|
+
username: 'admin',
|
|
52
|
+
surface: GOODVIBES_AGENT_PAIRING_SURFACE,
|
|
53
|
+
});
|
|
54
|
+
const payload = encodeConnectionPayload(info);
|
|
55
|
+
const qr = renderQrToString(generateQrMatrix(payload));
|
|
56
|
+
ctx.print([formatConnectionBlock(info, payload), '', qr].join('\n'));
|
|
18
57
|
},
|
|
19
58
|
});
|
|
20
59
|
}
|
|
@@ -18,11 +18,11 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
|
|
|
18
18
|
|
|
19
19
|
const pathArg = commandArgs[0];
|
|
20
20
|
if (!pathArg) {
|
|
21
|
-
context.print('[
|
|
21
|
+
context.print('[memory] Usage: /memory export <path> [--scope <scope>] [--cls <class>] --yes');
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
if (!parsed.yes) {
|
|
25
|
-
requireYesFlag(context, `export durable memory bundle to ${pathArg}`, '/
|
|
25
|
+
requireYesFlag(context, `export durable memory bundle to ${pathArg}`, '/memory export <path> [--scope <scope>] [--cls <class>] --yes');
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -31,7 +31,7 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
|
|
|
31
31
|
if (scopeIdx !== -1 && commandArgs[scopeIdx + 1]) {
|
|
32
32
|
const scope = commandArgs[scopeIdx + 1];
|
|
33
33
|
if (!isValidScope(scope)) {
|
|
34
|
-
context.print(`[
|
|
34
|
+
context.print(`[memory] Unknown scope "${scope}". Valid: ${VALID_SCOPES.join(', ')}`);
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
filter.scope = scope;
|
|
@@ -41,7 +41,7 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
|
|
|
41
41
|
if (clsIdx !== -1 && commandArgs[clsIdx + 1]) {
|
|
42
42
|
const cls = commandArgs[clsIdx + 1];
|
|
43
43
|
if (!isValidClass(cls)) {
|
|
44
|
-
context.print(`[
|
|
44
|
+
context.print(`[memory] Unknown class "${cls}". Valid: ${VALID_CLASSES.join(', ')}`);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
filter.cls = cls;
|
|
@@ -51,7 +51,7 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
|
|
|
51
51
|
const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
|
|
52
52
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
53
53
|
writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
|
|
54
|
-
context.print(`[
|
|
54
|
+
context.print(`[memory] Exported ${bundle.recordCount} record(s) and ${bundle.linkCount} link(s) to ${targetPath}`);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export async function handleRecallImport(args: string[], context: CommandContext): Promise<void> {
|
|
@@ -64,11 +64,11 @@ export async function handleRecallImport(args: string[], context: CommandContext
|
|
|
64
64
|
|
|
65
65
|
const pathArg = commandArgs[0];
|
|
66
66
|
if (!pathArg) {
|
|
67
|
-
context.print('[
|
|
67
|
+
context.print('[memory] Usage: /memory import <path> --yes');
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
if (!parsed.yes) {
|
|
71
|
-
requireYesFlag(context, `import durable memory bundle from ${pathArg}`, '/
|
|
71
|
+
requireYesFlag(context, `import durable memory bundle from ${pathArg}`, '/memory import <path> --yes');
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -77,12 +77,12 @@ export async function handleRecallImport(args: string[], context: CommandContext
|
|
|
77
77
|
try {
|
|
78
78
|
bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as MemoryBundle;
|
|
79
79
|
} catch (error) {
|
|
80
|
-
context.print(`[
|
|
80
|
+
context.print(`[memory] Failed to read memory bundle: ${summarizeError(error)}`);
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const result = await memory.importBundle(bundle);
|
|
85
|
-
context.print(`[
|
|
85
|
+
context.print(`[memory] Imported bundle from ${targetPath}`);
|
|
86
86
|
context.print(` Records: imported=${result.importedRecords} skipped=${result.skippedRecords}`);
|
|
87
87
|
context.print(` Links: imported=${result.importedLinks}`);
|
|
88
88
|
}
|
|
@@ -106,30 +106,30 @@ export function handleRecallHandoffExport(args: string[], context: CommandContex
|
|
|
106
106
|
}
|
|
107
107
|
const pathArg = commandArgs[0];
|
|
108
108
|
if (!pathArg) {
|
|
109
|
-
context.print('[
|
|
109
|
+
context.print('[memory] Usage: /memory handoff-export <path> [--scope <scope>] --yes');
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
if (!parsed.yes) {
|
|
113
|
-
requireYesFlag(context, `export memory handoff bundle to ${pathArg}`, '/
|
|
113
|
+
requireYesFlag(context, `export memory handoff bundle to ${pathArg}`, '/memory handoff-export <path> [--scope <scope>] --yes');
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
const scopeIdx = commandArgs.indexOf('--scope');
|
|
117
117
|
const scopeRaw = scopeIdx !== -1 ? commandArgs[scopeIdx + 1] : 'team';
|
|
118
118
|
if (!scopeRaw || !isValidScope(scopeRaw)) {
|
|
119
|
-
context.print(`[
|
|
119
|
+
context.print(`[memory] Unknown scope "${scopeRaw ?? ''}". Valid: ${VALID_SCOPES.join(', ')}`);
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
122
|
const bundle = memory.exportBundle({ scope: scopeRaw });
|
|
123
123
|
const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
|
|
124
124
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
125
125
|
writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
|
|
126
|
-
context.print(`[
|
|
126
|
+
context.print(`[memory] Exported ${scopeRaw} handoff bundle to ${targetPath}`);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
export function handleRecallHandoffInspect(args: string[], context: CommandContext): void {
|
|
130
130
|
const pathArg = args[0];
|
|
131
131
|
if (!pathArg) {
|
|
132
|
-
context.print('[
|
|
132
|
+
context.print('[memory] Usage: /memory handoff-inspect <path>');
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
135
|
const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
|
|
@@ -137,7 +137,7 @@ export function handleRecallHandoffInspect(args: string[], context: CommandConte
|
|
|
137
137
|
const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as MemoryBundle;
|
|
138
138
|
context.print(inspectBundle(bundle));
|
|
139
139
|
} catch (error) {
|
|
140
|
-
context.print(`[
|
|
140
|
+
context.print(`[memory] Failed to inspect handoff bundle: ${summarizeError(error)}`);
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -146,7 +146,7 @@ export async function handleRecallHandoffImport(args: string[], context: Command
|
|
|
146
146
|
const commandArgs = [...parsed.rest];
|
|
147
147
|
const pathArg = commandArgs[0];
|
|
148
148
|
if (!pathArg) {
|
|
149
|
-
context.print('[
|
|
149
|
+
context.print('[memory] Usage: /memory handoff-import <path> --yes');
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
152
|
await handleRecallImport([pathArg, ...(parsed.yes ? ['--yes'] : [])], context);
|
|
@@ -17,7 +17,7 @@ export async function handleRecallAdd(args: string[], context: CommandContext):
|
|
|
17
17
|
|
|
18
18
|
const cls = args[0];
|
|
19
19
|
if (!cls || !isValidClass(cls)) {
|
|
20
|
-
context.print(`[
|
|
20
|
+
context.print(`[memory] Invalid class "${cls ?? ''}". Valid: ${VALID_CLASSES.join(', ')}`);
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -36,7 +36,7 @@ export async function handleRecallAdd(args: string[], context: CommandContext):
|
|
|
36
36
|
const scope = scopeRaw && isValidScope(scopeRaw) ? scopeRaw : 'project';
|
|
37
37
|
|
|
38
38
|
if (scopeRaw && !isValidScope(scopeRaw)) {
|
|
39
|
-
context.print(`[
|
|
39
|
+
context.print(`[memory] Invalid scope "${scopeRaw}". Valid: ${VALID_SCOPES.join(', ')}`);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -55,12 +55,12 @@ export async function handleRecallAdd(args: string[], context: CommandContext):
|
|
|
55
55
|
}
|
|
56
56
|
const summary = summaryTokens.join(' ');
|
|
57
57
|
if (!summary.trim()) {
|
|
58
|
-
context.print('[
|
|
58
|
+
context.print('[memory] Usage: /memory add <class> <summary> [--detail <text>] [--tags <t1,t2>]');
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const record = await memory.add({ scope, cls, summary, detail, tags, provenance });
|
|
63
|
-
context.print(`[
|
|
63
|
+
context.print(`[memory] Added ${cls}: ${record.id}`);
|
|
64
64
|
context.print(` Scope: ${record.scope}`);
|
|
65
65
|
context.print(` Summary: ${record.summary}`);
|
|
66
66
|
if (record.tags.length) context.print(` Tags: ${record.tags.join(', ')}`);
|
|
@@ -76,7 +76,7 @@ export async function handleRecallCapture(args: string[], context: CommandContex
|
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
78
78
|
if (!context.extensions.forensicsRegistry) {
|
|
79
|
-
context.print('[
|
|
79
|
+
context.print('[memory] Forensics registry not available.');
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -87,66 +87,66 @@ export async function handleRecallCapture(args: string[], context: CommandContex
|
|
|
87
87
|
? context.extensions.forensicsRegistry.latest()
|
|
88
88
|
: context.extensions.forensicsRegistry.getById(requestedId);
|
|
89
89
|
if (!report) {
|
|
90
|
-
context.print(`[
|
|
90
|
+
context.print(`[memory] Incident not found: ${requestedId ?? 'latest'}`);
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
const bundle = context.extensions.forensicsRegistry.buildBundle(report.id);
|
|
94
94
|
if (!bundle) {
|
|
95
|
-
context.print(`[
|
|
95
|
+
context.print(`[memory] Failed to build incident bundle: ${report.id}`);
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
const record = await memory.add(buildIncidentMemoryAddOptions(bundle));
|
|
99
|
-
context.print(`[
|
|
99
|
+
context.print(`[memory] Captured incident ${report.id} into memory as ${record.id}`);
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
if (target === 'policy') {
|
|
104
104
|
const review = context.extensions.policyRuntimeState?.getSnapshot().lastPreflightReview;
|
|
105
105
|
if (!review) {
|
|
106
|
-
context.print('[
|
|
106
|
+
context.print('[memory] No policy preflight review is available to capture.');
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
const record = await memory.add(buildPolicyPreflightMemoryAddOptions(review));
|
|
110
|
-
context.print(`[
|
|
110
|
+
context.print(`[memory] Captured policy preflight into memory as ${record.id}`);
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
if (target === 'mcp') {
|
|
115
115
|
const serverName = args[1];
|
|
116
116
|
if (!serverName) {
|
|
117
|
-
context.print('[
|
|
117
|
+
context.print('[memory] Usage: /memory capture mcp <server>');
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
const server = context.extensions.mcpRegistry.listServerSecurity().find((entry) => entry.name === serverName);
|
|
121
121
|
if (!server) {
|
|
122
|
-
context.print(`[
|
|
122
|
+
context.print(`[memory] MCP server not found: ${serverName}`);
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
const record = await memory.add(buildMcpSecurityMemoryAddOptions(server));
|
|
126
|
-
context.print(`[
|
|
126
|
+
context.print(`[memory] Captured MCP server ${server.name} into memory as ${record.id}`);
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
if (target === 'plugin') {
|
|
131
131
|
if (!pluginManager) {
|
|
132
|
-
context.print('[
|
|
132
|
+
context.print('[memory] Plugin manager not available.');
|
|
133
133
|
return;
|
|
134
134
|
}
|
|
135
135
|
const pluginName = args[1];
|
|
136
136
|
if (!pluginName) {
|
|
137
|
-
context.print('[
|
|
137
|
+
context.print('[memory] Usage: /memory capture plugin <name>');
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
const plugin = pluginManager.list().find((entry) => entry.name === pluginName);
|
|
141
141
|
if (!plugin) {
|
|
142
|
-
context.print(`[
|
|
142
|
+
context.print(`[memory] Plugin not found: ${pluginName}`);
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
const quarantineReason = pluginManager.getQuarantineRecord(plugin.name)?.reason;
|
|
146
146
|
const record = await memory.add(buildPluginSecurityMemoryAddOptions(plugin, quarantineReason));
|
|
147
|
-
context.print(`[
|
|
147
|
+
context.print(`[memory] Captured plugin ${plugin.name} into memory as ${record.id}`);
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
context.print('[
|
|
151
|
+
context.print('[memory] Usage: /memory capture incident <id|latest> | /memory capture policy | /memory capture mcp <server> | /memory capture plugin <name>');
|
|
152
152
|
}
|