@pellux/goodvibes-agent 1.8.0 → 1.8.2
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 +9 -0
- package/README.md +1 -1
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-44ar68xs.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-asvc2tv1.node} +0 -0
- package/dist/package/main.js +135434 -118764
- package/dist/package/{web-tree-sitter-vmqc5d26.wasm → web-tree-sitter-jbz042ba.wasm} +0 -0
- package/docs/tools-and-commands.md +9 -0
- package/package.json +3 -2
- package/src/agent/capability-registry.ts +300 -0
- package/src/agent/email/style-reply-lane.ts +13 -4
- package/src/agent/memory-consolidation-receipts.ts +79 -0
- package/src/agent/memory-prompt.ts +3 -1
- package/src/agent/operator-gateway-call.ts +134 -0
- package/src/agent/principal-attribution.ts +97 -0
- package/src/agent/prompt-context-receipts.ts +5 -1
- package/src/agent/unified-inbox.ts +17 -1
- package/src/cli/bundle-command.ts +2 -1
- package/src/cli/channel-profiles-command.ts +168 -0
- package/src/cli/ci-command.ts +214 -0
- package/src/cli/completion.ts +5 -0
- package/src/cli/connected-host-metrics.ts +165 -0
- package/src/cli/entrypoint.ts +25 -1
- package/src/cli/fleet-command.ts +178 -0
- package/src/cli/help.ts +115 -0
- package/src/cli/import-command.ts +208 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/management-commands.ts +9 -8
- package/src/cli/management.ts +42 -0
- package/src/cli/memory-command.ts +4 -3
- package/src/cli/openclaw-import.ts +382 -0
- package/src/cli/operator-command-args.ts +88 -0
- package/src/cli/parser.ts +12 -0
- package/src/cli/personas-command.ts +3 -2
- package/src/cli/principals-command.ts +230 -0
- package/src/cli/profiles-command.ts +2 -1
- package/src/cli/relay-command.ts +100 -0
- package/src/cli/routines-command.ts +4 -3
- package/src/cli/skill-bundle-command.ts +3 -2
- package/src/cli/skills-command.ts +3 -2
- package/src/cli/status.ts +114 -2
- package/src/cli/temporal-label.ts +89 -0
- package/src/cli/types.ts +7 -0
- package/src/cli/workspaces-command.ts +135 -0
- package/src/config/agent-settings-policy.ts +8 -0
- package/src/config/checkpoint-settings.ts +113 -0
- package/src/config/workspace-registration.ts +299 -0
- package/src/core/rewind-turn-anchors.ts +82 -0
- package/src/input/agent-workspace-categories.ts +1 -0
- package/src/input/agent-workspace-channel-triage.ts +27 -8
- package/src/input/agent-workspace-local-library-snapshot.ts +5 -1
- package/src/input/agent-workspace-onboarding-categories.ts +17 -1
- package/src/input/agent-workspace-snapshot.ts +25 -3
- package/src/input/agent-workspace-types.ts +5 -5
- package/src/input/commands/connected-host-admin-runtime.ts +75 -0
- package/src/input/commands.ts +2 -0
- package/src/input/settings-modal-types.ts +16 -5
- package/src/main.ts +17 -12
- package/src/permissions/approval-posture.ts +40 -5
- package/src/permissions/prompt.ts +35 -1
- package/src/renderer/settings-modal-helpers.ts +11 -0
- package/src/renderer/settings-modal.ts +21 -0
- package/src/renderer/terminal-escapes.ts +65 -12
- package/src/runtime/agent-runtime-events.ts +102 -3
- package/src/runtime/bootstrap-core.ts +111 -10
- package/src/runtime/bootstrap-external-services.ts +156 -0
- package/src/runtime/bootstrap.ts +73 -69
- package/src/runtime/context-accounting-source.ts +114 -0
- package/src/runtime/conversation-rewind-port.ts +133 -0
- package/src/runtime/index.ts +8 -28
- package/src/runtime/memory-consolidation-scheduler.ts +67 -0
- package/src/runtime/memory-consolidation-wiring.ts +56 -0
- package/src/runtime/memory-usage-wiring.ts +75 -0
- package/src/runtime/services.ts +257 -6
- package/src/runtime/tool-permission-safety.ts +18 -1
- package/src/shell/blocking-input.ts +38 -8
- package/src/shell/startup-wiring.ts +26 -1
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-harness-cli-command-policy.ts +29 -3
- package/src/tools/agent-harness-learning-auto-promote.ts +7 -3
- package/src/tools/agent-harness-memory-posture.ts +57 -2
- package/src/tools/agent-harness-metadata.ts +12 -0
- package/src/tools/agent-harness-mode-catalog.ts +1 -1
- package/src/tools/agent-harness-personal-ops-lanes.ts +8 -6
- package/src/tools/agent-harness-prompt-context.ts +3 -1
- package/src/tools/agent-harness-tool.ts +3 -2
- package/src/tools/agent-policy-explanation.ts +40 -0
- package/src/tools/tool-definition-compaction.ts +2 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { OperatorMethodInput, OperatorMethodOutput } from '@pellux/goodvibes-sdk/contracts';
|
|
2
|
+
import {
|
|
3
|
+
formatOperatorGatewayFailure,
|
|
4
|
+
invokeOperatorGatewayMethod,
|
|
5
|
+
} from '../agent/operator-gateway-call.ts';
|
|
6
|
+
import { resolveAgentConnectedHostConnection } from '../agent/routine-schedule-promotion.ts';
|
|
7
|
+
import {
|
|
8
|
+
operatorFlagValue,
|
|
9
|
+
operatorRequiredFlag,
|
|
10
|
+
parseIdentityPairs,
|
|
11
|
+
parseOperatorCommandArgs,
|
|
12
|
+
} from './operator-command-args.ts';
|
|
13
|
+
import type { CliCommandOutput } from './types.ts';
|
|
14
|
+
import type { CliCommandRuntime } from './management.ts';
|
|
15
|
+
import { appendTemporalLabel } from './temporal-label.ts';
|
|
16
|
+
|
|
17
|
+
const PRINCIPALS_ROUTE = '/api/principals';
|
|
18
|
+
const PRINCIPAL_ROUTE = '/api/principals/{principalId}';
|
|
19
|
+
const PRINCIPALS_RESOLVE_ROUTE = '/api/principals/resolve';
|
|
20
|
+
|
|
21
|
+
const PRINCIPALS_GET_USAGE = 'Usage: goodvibes-agent principals get <id>';
|
|
22
|
+
const PRINCIPALS_CREATE_USAGE = 'Usage: goodvibes-agent principals create --name <name> --kind <user|bot|service|token> [--identity channel:value,channel:value] --yes';
|
|
23
|
+
const PRINCIPALS_UPDATE_USAGE = 'Usage: goodvibes-agent principals update <id> [--name <name>] [--kind <user|bot|service|token>] [--identity channel:value,channel:value] --yes';
|
|
24
|
+
const PRINCIPALS_DELETE_USAGE = 'Usage: goodvibes-agent principals delete <id> --yes';
|
|
25
|
+
const PRINCIPALS_RESOLVE_USAGE = 'Usage: goodvibes-agent principals resolve --channel <channel> --value <value>';
|
|
26
|
+
const PRINCIPALS_USAGE = 'Usage: goodvibes-agent principals [list|get <id>|create --name <name> --kind <user|bot|service|token> --yes|update <id> [--name ..] [--kind ..] --yes|delete <id> --yes|resolve --channel <channel> --value <value>]';
|
|
27
|
+
|
|
28
|
+
const PRINCIPAL_KINDS = new Set(['user', 'bot', 'service', 'token']);
|
|
29
|
+
|
|
30
|
+
type Principal = OperatorMethodOutput<'principals.list'>['principals'][number];
|
|
31
|
+
type PrincipalKind = OperatorMethodInput<'principals.create'>['kind'];
|
|
32
|
+
|
|
33
|
+
function jsonOrText(runtime: CliCommandRuntime, value: unknown, text: string): string {
|
|
34
|
+
return runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function usageFailure(runtime: CliCommandRuntime, error: string): CliCommandOutput {
|
|
38
|
+
return {
|
|
39
|
+
output: jsonOrText(runtime, { ok: false, kind: 'invalid_principals_command', error }, error),
|
|
40
|
+
exitCode: 2,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function requirePrincipalKind(value: string, usage: string): PrincipalKind {
|
|
45
|
+
const normalized = value.trim().toLowerCase();
|
|
46
|
+
if (!PRINCIPAL_KINDS.has(normalized)) throw new Error(`${usage}\n--kind must be one of: user, bot, service, token.`);
|
|
47
|
+
return normalized as PrincipalKind;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderPrincipal(principal: Principal): string {
|
|
51
|
+
const identities = principal.identities.length > 0
|
|
52
|
+
? principal.identities.map((identity) => `${identity.channel}:${identity.value}`).join(', ')
|
|
53
|
+
: '(none)';
|
|
54
|
+
return [
|
|
55
|
+
`Principal ${principal.id}`,
|
|
56
|
+
` name ${principal.name}`,
|
|
57
|
+
` kind ${principal.kind}`,
|
|
58
|
+
` identities ${identities}`,
|
|
59
|
+
` created ${appendTemporalLabel(new Date(principal.createdAt).toISOString(), principal.createdAt)}`,
|
|
60
|
+
` updated ${appendTemporalLabel(new Date(principal.updatedAt).toISOString(), principal.updatedAt)}`,
|
|
61
|
+
].join('\n');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function renderPrincipalLine(principal: Principal): string {
|
|
65
|
+
const identities = principal.identities.length > 0
|
|
66
|
+
? principal.identities.map((identity) => `${identity.channel}:${identity.value}`).join(', ')
|
|
67
|
+
: '(none)';
|
|
68
|
+
return ` ${principal.id} ${principal.kind.padEnd(8)} ${principal.name} identities ${identities}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function renderPrincipalList(principals: readonly Principal[]): string {
|
|
72
|
+
if (principals.length === 0) {
|
|
73
|
+
return [
|
|
74
|
+
'Principals',
|
|
75
|
+
' No principals registered yet.',
|
|
76
|
+
' next goodvibes-agent principals create --name <name> --kind user --yes',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
return [`Principals (${principals.length})`, ...principals.map(renderPrincipalLine)].join('\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function handlePrincipalsList(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
|
|
83
|
+
const connection = resolveAgentConnectedHostConnection(runtime.configManager, runtime.homeDirectory);
|
|
84
|
+
const result = await invokeOperatorGatewayMethod<OperatorMethodOutput<'principals.list'>>(connection, 'principals.list', PRINCIPALS_ROUTE, {});
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
return { output: jsonOrText(runtime, result, formatOperatorGatewayFailure(result)), exitCode: 1 };
|
|
87
|
+
}
|
|
88
|
+
return { output: jsonOrText(runtime, result, renderPrincipalList(result.data.principals)), exitCode: 0 };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function handlePrincipalsGet(runtime: CliCommandRuntime, args: readonly string[]): Promise<CliCommandOutput> {
|
|
92
|
+
const parsed = parseOperatorCommandArgs(args);
|
|
93
|
+
const principalId = parsed.positionals[0];
|
|
94
|
+
if (!principalId) return usageFailure(runtime, PRINCIPALS_GET_USAGE);
|
|
95
|
+
const payload: OperatorMethodInput<'principals.get'> = { principalId };
|
|
96
|
+
const connection = resolveAgentConnectedHostConnection(runtime.configManager, runtime.homeDirectory);
|
|
97
|
+
const result = await invokeOperatorGatewayMethod<OperatorMethodOutput<'principals.get'>>(connection, 'principals.get', PRINCIPAL_ROUTE, payload);
|
|
98
|
+
if (!result.ok) {
|
|
99
|
+
return { output: jsonOrText(runtime, result, formatOperatorGatewayFailure(result)), exitCode: 1 };
|
|
100
|
+
}
|
|
101
|
+
return { output: jsonOrText(runtime, result, renderPrincipal(result.data.principal)), exitCode: 0 };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function handlePrincipalsCreate(runtime: CliCommandRuntime, args: readonly string[]): Promise<CliCommandOutput> {
|
|
105
|
+
const parsed = parseOperatorCommandArgs(args, ['name', 'kind', 'identity']);
|
|
106
|
+
let name: string;
|
|
107
|
+
let kind: PrincipalKind;
|
|
108
|
+
let identities: readonly { readonly channel: string; readonly value: string }[];
|
|
109
|
+
try {
|
|
110
|
+
name = operatorRequiredFlag(parsed, 'name', PRINCIPALS_CREATE_USAGE);
|
|
111
|
+
kind = requirePrincipalKind(operatorRequiredFlag(parsed, 'kind', PRINCIPALS_CREATE_USAGE), PRINCIPALS_CREATE_USAGE);
|
|
112
|
+
identities = parseIdentityPairs(operatorFlagValue(parsed, 'identity'));
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return usageFailure(runtime, error instanceof Error ? error.message : String(error));
|
|
115
|
+
}
|
|
116
|
+
if (!parsed.yes) {
|
|
117
|
+
return { output: `Refusing to create principal ${name} without --yes.`, exitCode: 2 };
|
|
118
|
+
}
|
|
119
|
+
const payload: OperatorMethodInput<'principals.create'> = {
|
|
120
|
+
name,
|
|
121
|
+
kind,
|
|
122
|
+
identities: identities.length > 0 ? identities : undefined,
|
|
123
|
+
};
|
|
124
|
+
const connection = resolveAgentConnectedHostConnection(runtime.configManager, runtime.homeDirectory);
|
|
125
|
+
const result = await invokeOperatorGatewayMethod<OperatorMethodOutput<'principals.create'>>(connection, 'principals.create', PRINCIPALS_ROUTE, payload);
|
|
126
|
+
if (!result.ok) {
|
|
127
|
+
return { output: jsonOrText(runtime, result, formatOperatorGatewayFailure(result)), exitCode: 1 };
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
output: jsonOrText(runtime, result, [`Created principal ${result.data.principal.id}`, renderPrincipal(result.data.principal)].join('\n')),
|
|
131
|
+
exitCode: 0,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function handlePrincipalsUpdate(runtime: CliCommandRuntime, args: readonly string[]): Promise<CliCommandOutput> {
|
|
136
|
+
const parsed = parseOperatorCommandArgs(args, ['name', 'kind', 'identity']);
|
|
137
|
+
const principalId = parsed.positionals[0];
|
|
138
|
+
if (!principalId) return usageFailure(runtime, PRINCIPALS_UPDATE_USAGE);
|
|
139
|
+
let kind: PrincipalKind | undefined;
|
|
140
|
+
let identities: readonly { readonly channel: string; readonly value: string }[];
|
|
141
|
+
try {
|
|
142
|
+
const kindValue = operatorFlagValue(parsed, 'kind');
|
|
143
|
+
kind = kindValue ? requirePrincipalKind(kindValue, PRINCIPALS_UPDATE_USAGE) : undefined;
|
|
144
|
+
identities = parseIdentityPairs(operatorFlagValue(parsed, 'identity'));
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return usageFailure(runtime, error instanceof Error ? error.message : String(error));
|
|
147
|
+
}
|
|
148
|
+
if (!parsed.yes) {
|
|
149
|
+
return { output: `Refusing to update principal ${principalId} without --yes.`, exitCode: 2 };
|
|
150
|
+
}
|
|
151
|
+
const payload: OperatorMethodInput<'principals.update'> = {
|
|
152
|
+
principalId,
|
|
153
|
+
name: operatorFlagValue(parsed, 'name'),
|
|
154
|
+
kind,
|
|
155
|
+
identities: identities.length > 0 ? identities : undefined,
|
|
156
|
+
};
|
|
157
|
+
const connection = resolveAgentConnectedHostConnection(runtime.configManager, runtime.homeDirectory);
|
|
158
|
+
const result = await invokeOperatorGatewayMethod<OperatorMethodOutput<'principals.update'>>(connection, 'principals.update', PRINCIPAL_ROUTE, payload);
|
|
159
|
+
if (!result.ok) {
|
|
160
|
+
return { output: jsonOrText(runtime, result, formatOperatorGatewayFailure(result)), exitCode: 1 };
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
output: jsonOrText(runtime, result, [`Updated principal ${result.data.principal.id}`, renderPrincipal(result.data.principal)].join('\n')),
|
|
164
|
+
exitCode: 0,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function handlePrincipalsDelete(runtime: CliCommandRuntime, args: readonly string[]): Promise<CliCommandOutput> {
|
|
169
|
+
const parsed = parseOperatorCommandArgs(args);
|
|
170
|
+
const principalId = parsed.positionals[0];
|
|
171
|
+
if (!principalId) return usageFailure(runtime, PRINCIPALS_DELETE_USAGE);
|
|
172
|
+
if (!parsed.yes) {
|
|
173
|
+
return { output: `Refusing to delete principal ${principalId} without --yes.`, exitCode: 2 };
|
|
174
|
+
}
|
|
175
|
+
const payload: OperatorMethodInput<'principals.delete'> = { principalId };
|
|
176
|
+
const connection = resolveAgentConnectedHostConnection(runtime.configManager, runtime.homeDirectory);
|
|
177
|
+
const result = await invokeOperatorGatewayMethod<OperatorMethodOutput<'principals.delete'>>(connection, 'principals.delete', PRINCIPAL_ROUTE, payload);
|
|
178
|
+
if (!result.ok) {
|
|
179
|
+
return { output: jsonOrText(runtime, result, formatOperatorGatewayFailure(result)), exitCode: 1 };
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
output: jsonOrText(runtime, result, `Deleted principal ${result.data.principalId}: ${result.data.deleted ? 'ok' : 'not found'}`),
|
|
183
|
+
exitCode: result.data.deleted ? 0 : 1,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function handlePrincipalsResolve(runtime: CliCommandRuntime, args: readonly string[]): Promise<CliCommandOutput> {
|
|
188
|
+
const parsed = parseOperatorCommandArgs(args, ['channel', 'value']);
|
|
189
|
+
let channel: string;
|
|
190
|
+
let value: string;
|
|
191
|
+
try {
|
|
192
|
+
channel = operatorRequiredFlag(parsed, 'channel', PRINCIPALS_RESOLVE_USAGE);
|
|
193
|
+
value = operatorRequiredFlag(parsed, 'value', PRINCIPALS_RESOLVE_USAGE);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return usageFailure(runtime, error instanceof Error ? error.message : String(error));
|
|
196
|
+
}
|
|
197
|
+
const payload: OperatorMethodInput<'principals.resolve'> = { channel, value };
|
|
198
|
+
const connection = resolveAgentConnectedHostConnection(runtime.configManager, runtime.homeDirectory);
|
|
199
|
+
const result = await invokeOperatorGatewayMethod<OperatorMethodOutput<'principals.resolve'>>(connection, 'principals.resolve', PRINCIPALS_RESOLVE_ROUTE, payload);
|
|
200
|
+
if (!result.ok) {
|
|
201
|
+
return { output: jsonOrText(runtime, result, formatOperatorGatewayFailure(result)), exitCode: 1 };
|
|
202
|
+
}
|
|
203
|
+
// known: false means the identity lookup found nothing for this channel/value — never assert a match in that case.
|
|
204
|
+
const text = result.data.known
|
|
205
|
+
? [`known: true`, renderPrincipal(result.data.principal)].join('\n')
|
|
206
|
+
: `known: false\n No principal is mapped to ${channel}:${value}.`;
|
|
207
|
+
return { output: jsonOrText(runtime, result, text), exitCode: 0 };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function handlePrincipalsCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
|
|
211
|
+
const [sub = 'list', ...rest] = runtime.cli.commandArgs;
|
|
212
|
+
switch (sub.toLowerCase()) {
|
|
213
|
+
case 'list':
|
|
214
|
+
return handlePrincipalsList(runtime);
|
|
215
|
+
case 'get':
|
|
216
|
+
case 'show':
|
|
217
|
+
return handlePrincipalsGet(runtime, rest);
|
|
218
|
+
case 'create':
|
|
219
|
+
return handlePrincipalsCreate(runtime, rest);
|
|
220
|
+
case 'update':
|
|
221
|
+
return handlePrincipalsUpdate(runtime, rest);
|
|
222
|
+
case 'delete':
|
|
223
|
+
case 'remove':
|
|
224
|
+
return handlePrincipalsDelete(runtime, rest);
|
|
225
|
+
case 'resolve':
|
|
226
|
+
return handlePrincipalsResolve(runtime, rest);
|
|
227
|
+
default:
|
|
228
|
+
return usageFailure(runtime, PRINCIPALS_USAGE);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from '../agent/runtime-profile.ts';
|
|
19
19
|
import { formatAgentRecordSource } from '../agent/record-labels.ts';
|
|
20
20
|
import type { CliCommandOutput, GoodVibesCliParseResult } from './types.ts';
|
|
21
|
+
import { appendTemporalLabel } from './temporal-label.ts';
|
|
21
22
|
|
|
22
23
|
interface ProfilesCommandRuntime {
|
|
23
24
|
readonly cli: GoodVibesCliParseResult;
|
|
@@ -67,7 +68,7 @@ function parseCsvFlag(args: readonly string[], names: readonly string[]): readon
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function profileLine(profile: AgentRuntimeProfileInfo): string {
|
|
70
|
-
const created = profile.createdAt ? ` created ${profile.createdAt}` : '';
|
|
71
|
+
const created = profile.createdAt ? ` created ${appendTemporalLabel(profile.createdAt, profile.createdAt)}` : '';
|
|
71
72
|
const starter = profile.starterTemplateId ? ` starter=${profile.starterTemplateId}` : '';
|
|
72
73
|
return ` ${profile.id} home ${profile.homeDirectory}${created}${starter}`;
|
|
73
74
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { FEATURE_FLAG_MAP } from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
2
|
+
import type { CliCommandOutput } from './types.ts';
|
|
3
|
+
import type { CliCommandRuntime } from './management.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `goodvibes-agent relay status|pair` — the outbound zero-knowledge relay
|
|
7
|
+
* (SDK 1.6.1, platform/relay) that lets a daemon register with a
|
|
8
|
+
* self-hostable rendezvous relay so surfaces can reach it from outside the
|
|
9
|
+
* LAN. Relay reachability (RelayReachability, the live registration
|
|
10
|
+
* lifecycle, and the relay identity keypair a pairing payload is minted
|
|
11
|
+
* from) is owned entirely by the SDK's DaemonServer facade
|
|
12
|
+
* (buildDaemonRelayReachability, started at boot in facade.ts) — the daemon
|
|
13
|
+
* process holding the relay identity's private key material.
|
|
14
|
+
*
|
|
15
|
+
* GoodVibes Agent does not host that daemon. Its product boundary is
|
|
16
|
+
* explicit and enforced elsewhere in this CLI (see parser.ts's
|
|
17
|
+
* BLOCKED_PRODUCT_COMMAND_HINTS: "GoodVibes Agent connects to an externally
|
|
18
|
+
* managed GoodVibes host and does not start or manage host processes" /
|
|
19
|
+
* "does not start listeners or expose inbound host endpoints"). Relay is the
|
|
20
|
+
* same shape of concern as those blocked commands, so this command reports
|
|
21
|
+
* what is honestly knowable from here — the connected host's imported
|
|
22
|
+
* relay.* configuration and the relay-connect feature flag's static
|
|
23
|
+
* definition — and refuses to fabricate a live registration status or a
|
|
24
|
+
* pairing payload neither of which this process can observe or mint. The
|
|
25
|
+
* SDK's operator wire ships no remote relay-status/pairing route yet (relay
|
|
26
|
+
* reuses the existing REST surface for tunneled traffic, not a status
|
|
27
|
+
* query), so there is no live check to perform even for a genuinely
|
|
28
|
+
* connected host.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const RELAY_USAGE = 'Usage: goodvibes-agent relay [status|pair]';
|
|
32
|
+
|
|
33
|
+
function jsonOrText(runtime: CliCommandRuntime, value: unknown, text: string): string {
|
|
34
|
+
return runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function relayConnectFlagSummary(): { readonly id: string; readonly name: string; readonly defaultState: string } {
|
|
38
|
+
const flag = FEATURE_FLAG_MAP.get('relay-connect');
|
|
39
|
+
return {
|
|
40
|
+
id: 'relay-connect',
|
|
41
|
+
name: flag?.name ?? 'Outbound Zero-Knowledge Relay',
|
|
42
|
+
defaultState: flag?.defaultState ?? 'disabled',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function handleRelayStatus(runtime: CliCommandRuntime): CliCommandOutput {
|
|
47
|
+
const config = runtime.configManager;
|
|
48
|
+
const enabled = config.get('relay.enabled') === true;
|
|
49
|
+
const url = String(config.get('relay.url') ?? '');
|
|
50
|
+
const rendezvousId = String(config.get('relay.rendezvousId') ?? '');
|
|
51
|
+
const label = String(config.get('relay.label') ?? '');
|
|
52
|
+
const requireStepUpForMutations = config.get('relay.requireStepUpForMutations') === true;
|
|
53
|
+
const flag = relayConnectFlagSummary();
|
|
54
|
+
|
|
55
|
+
const payload = {
|
|
56
|
+
ok: true,
|
|
57
|
+
source: 'agent-local-config' as const,
|
|
58
|
+
liveVerified: false,
|
|
59
|
+
config: { enabled, url, rendezvousId, label, requireStepUpForMutations },
|
|
60
|
+
flag,
|
|
61
|
+
note: 'This reflects goodvibes-agent\'s own imported copy of relay.* settings, not a live check of the connected GoodVibes daemon\'s actual relay registration — Agent hosts no daemon and the SDK has no remote relay-status route yet.',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const lines = [
|
|
65
|
+
'GoodVibes relay (connected-host configuration; not live-verified)',
|
|
66
|
+
` relay.enabled: ${enabled ? 'true' : 'false'}`,
|
|
67
|
+
` relay.url: ${url || '(empty)'}`,
|
|
68
|
+
` relay.rendezvousId: ${rendezvousId || '(not generated yet)'}`,
|
|
69
|
+
` relay.label: ${label || '(empty)'}`,
|
|
70
|
+
` relay.requireStepUpForMutations: ${requireStepUpForMutations ? 'true' : 'false'}`,
|
|
71
|
+
` ${flag.id} feature flag: ${flag.name} (default ${flag.defaultState})`,
|
|
72
|
+
'',
|
|
73
|
+
'disabled/registered/offline is a property of the daemon that actually holds the relay',
|
|
74
|
+
'identity and dials the relay server — that daemon is managed outside goodvibes-agent.',
|
|
75
|
+
'Check its own status/doctor output, or the connected host directly.',
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
return { output: jsonOrText(runtime, payload, lines.join('\n')), exitCode: 0 };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleRelayPair(runtime: CliCommandRuntime): CliCommandOutput {
|
|
82
|
+
const message = [
|
|
83
|
+
'GoodVibes Agent cannot mint a relay pairing payload.',
|
|
84
|
+
'A pairing payload (relay URL + rendezvous id + pinned daemon public key) is minted',
|
|
85
|
+
'in-process by the daemon that holds the relay identity\'s private key — Agent never',
|
|
86
|
+
'holds that key material because it does not host the daemon (see "goodvibes-agent',
|
|
87
|
+
'relay status"). Run the pairing step on the machine hosting the connected GoodVibes',
|
|
88
|
+
'daemon, or use that host\'s own relay pairing surface once it ships one.',
|
|
89
|
+
].join('\n');
|
|
90
|
+
const payload = { ok: false, available: false, reason: 'relay-identity-not-hosted-here', error: message };
|
|
91
|
+
return { output: jsonOrText(runtime, payload, message), exitCode: 1 };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function handleRelayCommand(runtime: CliCommandRuntime): CliCommandOutput {
|
|
95
|
+
const [sub = 'status'] = runtime.cli.commandArgs;
|
|
96
|
+
if (sub === 'status') return handleRelayStatus(runtime);
|
|
97
|
+
if (sub === 'pair' || sub === 'pairing' || sub === 'qr' || sub === 'qrcode') return handleRelayPair(runtime);
|
|
98
|
+
const message = `Unknown relay subcommand: ${sub}\n${RELAY_USAGE}`;
|
|
99
|
+
return { output: jsonOrText(runtime, { ok: false, error: message }, message), exitCode: 2 };
|
|
100
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
resolveAgentConnectedHostConnection,
|
|
10
10
|
} from '../agent/routine-schedule-promotion.ts';
|
|
11
11
|
import { parseRoutineSchedulePromotionArgs } from '../agent/routine-schedule-args.ts';
|
|
12
|
+
import { appendTemporalLabel } from './temporal-label.ts';
|
|
12
13
|
import {
|
|
13
14
|
formatRoutineScheduleCorrelation,
|
|
14
15
|
formatRoutineScheduleFailure,
|
|
@@ -245,9 +246,9 @@ function renderRoutine(routine: AgentRoutineRecord): string {
|
|
|
245
246
|
` origin ${formatAgentRecordOrigin(routine.source, routine.provenance)}`,
|
|
246
247
|
` tags ${routine.tags.join(', ') || '(none)'}`,
|
|
247
248
|
` triggers ${routine.triggers.join(', ') || '(manual)'}`,
|
|
248
|
-
` started ${routine.startCount}${routine.lastStartedAt ? `; last ${routine.lastStartedAt}` : ''}`,
|
|
249
|
-
` created ${routine.createdAt}`,
|
|
250
|
-
` updated ${routine.updatedAt}`,
|
|
249
|
+
` started ${routine.startCount}${routine.lastStartedAt ? `; last ${appendTemporalLabel(routine.lastStartedAt, routine.lastStartedAt)}` : ''}`,
|
|
250
|
+
` created ${appendTemporalLabel(routine.createdAt, routine.createdAt)}`,
|
|
251
|
+
` updated ${appendTemporalLabel(routine.updatedAt, routine.updatedAt)}`,
|
|
251
252
|
routine.staleReason ? ` stale reason ${routine.staleReason}` : '',
|
|
252
253
|
'',
|
|
253
254
|
routine.description,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '../agent/skill-registry.ts';
|
|
8
8
|
import type { CliCommandOutput } from './types.ts';
|
|
9
9
|
import type { CliCommandRuntime } from './management.ts';
|
|
10
|
+
import { appendTemporalLabel } from './temporal-label.ts';
|
|
10
11
|
import {
|
|
11
12
|
csvOption,
|
|
12
13
|
errorOutput,
|
|
@@ -60,8 +61,8 @@ function renderBundle(bundle: AgentSkillBundleRecord, skills: readonly AgentSkil
|
|
|
60
61
|
` review: ${formatAgentRecordReviewState(bundle.reviewState)}`,
|
|
61
62
|
` origin: ${formatAgentRecordOrigin(bundle.source, bundle.provenance)}`,
|
|
62
63
|
` skills: ${bundle.skillIds.join(', ')}`,
|
|
63
|
-
` created: ${bundle.createdAt}`,
|
|
64
|
-
` updated: ${bundle.updatedAt}`,
|
|
64
|
+
` created: ${appendTemporalLabel(bundle.createdAt, bundle.createdAt)}`,
|
|
65
|
+
` updated: ${appendTemporalLabel(bundle.updatedAt, bundle.updatedAt)}`,
|
|
65
66
|
bundle.staleReason ? ` stale reason: ${bundle.staleReason}` : '',
|
|
66
67
|
'',
|
|
67
68
|
bundle.description,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import type { CliCommandOutput } from './types.ts';
|
|
10
10
|
import type { CliCommandRuntime } from './management.ts';
|
|
11
11
|
import { handleSkillBundleCommand, renderBundleList } from './skill-bundle-command.ts';
|
|
12
|
+
import { appendTemporalLabel } from './temporal-label.ts';
|
|
12
13
|
import {
|
|
13
14
|
csvOption,
|
|
14
15
|
errorOutput,
|
|
@@ -114,8 +115,8 @@ function renderSkill(skill: AgentSkillRecord): string {
|
|
|
114
115
|
` origin: ${formatAgentRecordOrigin(skill.source, skill.provenance)}`,
|
|
115
116
|
` tags: ${skill.tags.join(', ') || '(none)'}`,
|
|
116
117
|
` triggers: ${skill.triggers.join(', ') || '(manual)'}`,
|
|
117
|
-
` created: ${skill.createdAt}`,
|
|
118
|
-
` updated: ${skill.updatedAt}`,
|
|
118
|
+
` created: ${appendTemporalLabel(skill.createdAt, skill.createdAt)}`,
|
|
119
|
+
` updated: ${appendTemporalLabel(skill.updatedAt, skill.updatedAt)}`,
|
|
119
120
|
skill.staleReason ? ` stale reason: ${skill.staleReason}` : '',
|
|
120
121
|
'',
|
|
121
122
|
skill.description,
|
package/src/cli/status.ts
CHANGED
|
@@ -4,10 +4,12 @@ import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
|
4
4
|
import type { GoodVibesCliOutputFormat } from './types.ts';
|
|
5
5
|
import type { CliServicePosture } from './service-posture.ts';
|
|
6
6
|
import type { CliExternalRuntimeSnapshot } from './external-runtime.ts';
|
|
7
|
+
import type { CliConnectedHostMetricsSnapshot } from './connected-host-metrics.ts';
|
|
7
8
|
import { getProviderIdFromModel } from '../config/provider-model.ts';
|
|
8
9
|
import { formatAgentRecordSource } from '../agent/record-labels.ts';
|
|
9
10
|
import { formatAgentKnowledgeFailureKind } from './agent-knowledge-format.ts';
|
|
10
11
|
import { computeApprovalPosture, type ApprovalPosture } from '../permissions/approval-posture.ts';
|
|
12
|
+
import { appendTemporalLabel } from './temporal-label.ts';
|
|
11
13
|
|
|
12
14
|
export interface CliStatusOptions {
|
|
13
15
|
readonly configManager: Pick<ConfigManager, 'get'>;
|
|
@@ -17,10 +19,23 @@ export interface CliStatusOptions {
|
|
|
17
19
|
readonly auth?: CliAuthStatus;
|
|
18
20
|
readonly service?: CliServicePosture;
|
|
19
21
|
readonly externalRuntime?: CliExternalRuntimeSnapshot;
|
|
22
|
+
readonly connectedHostMetrics?: CliConnectedHostMetricsSnapshot;
|
|
23
|
+
readonly checkpoints?: CliCheckpointsStatus;
|
|
20
24
|
readonly doctor?: boolean;
|
|
21
25
|
readonly outputFormat?: GoodVibesCliOutputFormat;
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Registered-workspaces-only checkpoint posture (owner ruling, 2026-07-10; see
|
|
30
|
+
* ../config/workspace-registration.ts and ../runtime/services.ts). Computed by the
|
|
31
|
+
* caller (entrypoint.ts already builds a ShellPathService for status/doctor)
|
|
32
|
+
* and passed in here so this module stays a pure renderer.
|
|
33
|
+
*/
|
|
34
|
+
export interface CliCheckpointsStatus {
|
|
35
|
+
readonly workspaceRegistered: boolean;
|
|
36
|
+
readonly unregisteredWorkspaceMode: 'off' | 'guarded';
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
export interface CliAuthStatus {
|
|
25
40
|
readonly userStorePath: string;
|
|
26
41
|
readonly userStorePresent: boolean;
|
|
@@ -65,6 +80,13 @@ export interface CliStatusSnapshot {
|
|
|
65
80
|
readonly lifecycle?: CliServicePosture;
|
|
66
81
|
};
|
|
67
82
|
readonly externalRuntime: CliExternalRuntimeSnapshot | null;
|
|
83
|
+
readonly connectedHostMetrics: CliConnectedHostMetricsSnapshot | null;
|
|
84
|
+
readonly relay: {
|
|
85
|
+
readonly enabled: boolean;
|
|
86
|
+
readonly url: string;
|
|
87
|
+
readonly requireStepUpForMutations: boolean;
|
|
88
|
+
readonly liveVerified: false;
|
|
89
|
+
};
|
|
68
90
|
readonly runtimeEndpoints: {
|
|
69
91
|
readonly controlPlane: ReturnType<typeof resolveRuntimeEndpointBinding> & { readonly enabled: unknown };
|
|
70
92
|
readonly httpListener: ReturnType<typeof resolveRuntimeEndpointBinding> & { readonly enabled: unknown };
|
|
@@ -75,13 +97,70 @@ export interface CliStatusSnapshot {
|
|
|
75
97
|
readonly scope: string;
|
|
76
98
|
readonly updatedAt: number | null;
|
|
77
99
|
};
|
|
100
|
+
readonly checkpoints: {
|
|
101
|
+
readonly automaticCheckpointsActive: boolean;
|
|
102
|
+
readonly workspaceRegistered: boolean;
|
|
103
|
+
readonly unregisteredWorkspaceMode: 'off' | 'guarded';
|
|
104
|
+
readonly statusLine: string;
|
|
105
|
+
} | null;
|
|
78
106
|
readonly findings: readonly CliDoctorFinding[];
|
|
79
107
|
}
|
|
80
108
|
|
|
109
|
+
/**
|
|
110
|
+
* The one honest line this surface must never omit or soften (owner ruling,
|
|
111
|
+
* 2026-07-10): whether automatic checkpoints are active for THIS working
|
|
112
|
+
* directory, and why. `"checkpoints off: workspace not registered"` is the
|
|
113
|
+
* literal wording the ruling calls for the default (unregistered, `"off"`) case.
|
|
114
|
+
*/
|
|
115
|
+
function checkpointsStatusLine(checkpoints: CliCheckpointsStatus): { active: boolean; line: string } {
|
|
116
|
+
if (checkpoints.workspaceRegistered) {
|
|
117
|
+
return { active: true, line: 'checkpoints on: workspace registered' };
|
|
118
|
+
}
|
|
119
|
+
if (checkpoints.unregisteredWorkspaceMode === 'guarded') {
|
|
120
|
+
return { active: true, line: 'checkpoints on: unregistered workspace allowed via checkpoints.unregisteredWorkspaces="guarded"' };
|
|
121
|
+
}
|
|
122
|
+
return { active: false, line: 'checkpoints off: workspace not registered' };
|
|
123
|
+
}
|
|
124
|
+
|
|
81
125
|
function yesNo(value: unknown): string {
|
|
82
126
|
return value === true ? 'yes' : 'no';
|
|
83
127
|
}
|
|
84
128
|
|
|
129
|
+
/**
|
|
130
|
+
* The connected-host runtime-metrics lines. Each non-ok state is rendered as a
|
|
131
|
+
* plain sentence naming exactly why there are no numbers to show — most
|
|
132
|
+
* importantly, a token without the read:telemetry scope reads as "not
|
|
133
|
+
* permitted", never as a wall of zeros that would misrepresent an unauthorized
|
|
134
|
+
* token as a healthy idle host.
|
|
135
|
+
*/
|
|
136
|
+
function connectedHostMetricsLines(snapshot: CliConnectedHostMetricsSnapshot | null): string[] {
|
|
137
|
+
if (!snapshot) return [' live check unavailable'];
|
|
138
|
+
switch (snapshot.status) {
|
|
139
|
+
case 'ok': {
|
|
140
|
+
const m = snapshot.metrics!;
|
|
141
|
+
return [
|
|
142
|
+
` http requests total: ${m.httpRequestsTotal}`,
|
|
143
|
+
` llm requests total: ${m.llmRequestsTotal}`,
|
|
144
|
+
` auth success/failure: ${m.authSuccessTotal}/${m.authFailureTotal}`,
|
|
145
|
+
` transport retries total: ${m.transportRetriesTotal}`,
|
|
146
|
+
` sessions active: ${m.sessionsActive}`,
|
|
147
|
+
` sse subscribers: ${m.sseSubscribers}`,
|
|
148
|
+
` telemetry buffer fill: ${m.telemetryBufferFill}`,
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
case 'scope_missing':
|
|
152
|
+
return [` not shown: the connected-host token does not include the ${snapshot.requiredScope} scope required by ${snapshot.methodId}.`];
|
|
153
|
+
case 'auth_required':
|
|
154
|
+
return [' not shown: connected-host operator token is missing or unauthenticated.'];
|
|
155
|
+
case 'route_unavailable':
|
|
156
|
+
return [` not shown: this connected host does not expose ${snapshot.route} (incompatible or older daemon).`];
|
|
157
|
+
case 'unavailable':
|
|
158
|
+
return [` not shown: connected host is not reachable${snapshot.error ? ` (${snapshot.error})` : '.'}`];
|
|
159
|
+
default:
|
|
160
|
+
return [` not shown: ${snapshot.error ?? 'runtime metrics could not be read.'}`];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
85
164
|
/** permissions.tools.* keys — mirrors PermissionsToolConfig in the SDK config schema. */
|
|
86
165
|
const PERMISSION_TOOL_KEYS = [
|
|
87
166
|
'read', 'write', 'edit', 'exec', 'find', 'fetch', 'analyze',
|
|
@@ -268,6 +347,13 @@ export function buildCliStatusSnapshot(options: CliStatusOptions): CliStatusSnap
|
|
|
268
347
|
...(options.service ? { lifecycle: options.service } : {}),
|
|
269
348
|
},
|
|
270
349
|
externalRuntime: options.externalRuntime ?? null,
|
|
350
|
+
connectedHostMetrics: options.connectedHostMetrics ?? null,
|
|
351
|
+
relay: {
|
|
352
|
+
enabled: config.get('relay.enabled') === true,
|
|
353
|
+
url: String(config.get('relay.url') ?? ''),
|
|
354
|
+
requireStepUpForMutations: config.get('relay.requireStepUpForMutations') === true,
|
|
355
|
+
liveVerified: false,
|
|
356
|
+
},
|
|
271
357
|
runtimeEndpoints: {
|
|
272
358
|
controlPlane: { enabled: config.get('controlPlane.enabled'), ...controlPlaneBinding },
|
|
273
359
|
httpListener: { enabled: config.get('danger.httpListener'), ...httpListenerBinding },
|
|
@@ -278,6 +364,14 @@ export function buildCliStatusSnapshot(options: CliStatusOptions): CliStatusSnap
|
|
|
278
364
|
scope: marker?.scope ?? 'none',
|
|
279
365
|
updatedAt: marker?.payload?.updatedAt ?? null,
|
|
280
366
|
},
|
|
367
|
+
checkpoints: options.checkpoints
|
|
368
|
+
? {
|
|
369
|
+
automaticCheckpointsActive: checkpointsStatusLine(options.checkpoints).active,
|
|
370
|
+
workspaceRegistered: options.checkpoints.workspaceRegistered,
|
|
371
|
+
unregisteredWorkspaceMode: options.checkpoints.unregisteredWorkspaceMode,
|
|
372
|
+
statusLine: checkpointsStatusLine(options.checkpoints).line,
|
|
373
|
+
}
|
|
374
|
+
: null,
|
|
281
375
|
findings,
|
|
282
376
|
};
|
|
283
377
|
}
|
|
@@ -327,6 +421,14 @@ export function renderCliStatus(options: CliStatusOptions): string {
|
|
|
327
421
|
' live check unavailable',
|
|
328
422
|
]),
|
|
329
423
|
'',
|
|
424
|
+
'Connected host metrics (runtime.metrics.get; read:telemetry):',
|
|
425
|
+
...connectedHostMetricsLines(snapshot.connectedHostMetrics),
|
|
426
|
+
'',
|
|
427
|
+
'Relay (connected host\'s imported config; not live-verified — see goodvibes-agent relay status):',
|
|
428
|
+
` relay.enabled: ${yesNo(snapshot.relay.enabled)}`,
|
|
429
|
+
` relay.url: ${snapshot.relay.url || '(empty)'}`,
|
|
430
|
+
` relay.requireStepUpForMutations: ${yesNo(snapshot.relay.requireStepUpForMutations)}`,
|
|
431
|
+
'',
|
|
330
432
|
'Agent role:',
|
|
331
433
|
' product: interactive operator TUI',
|
|
332
434
|
' host lifecycle: external',
|
|
@@ -337,7 +439,17 @@ export function renderCliStatus(options: CliStatusOptions): string {
|
|
|
337
439
|
'Onboarding',
|
|
338
440
|
` checked ${marker?.exists ? 'yes' : 'no'}`,
|
|
339
441
|
` scope ${marker?.scope ?? 'none'}`,
|
|
340
|
-
` updated ${marker?.payload ? new Date(marker.payload.updatedAt).toISOString() : 'n/a'}`,
|
|
442
|
+
` updated ${marker?.payload ? appendTemporalLabel(new Date(marker.payload.updatedAt).toISOString(), marker.payload.updatedAt) : 'n/a'}`,
|
|
443
|
+
'',
|
|
444
|
+
'Checkpoints',
|
|
445
|
+
...(snapshot.checkpoints ? [
|
|
446
|
+
` ${snapshot.checkpoints.statusLine}`,
|
|
447
|
+
` unregisteredWorkspaces mode: ${snapshot.checkpoints.unregisteredWorkspaceMode}`,
|
|
448
|
+
' next goodvibes-agent workspaces list',
|
|
449
|
+
...(snapshot.checkpoints.workspaceRegistered ? [] : [' next goodvibes-agent workspaces register --yes']),
|
|
450
|
+
] : [
|
|
451
|
+
' checkpoint posture unknown',
|
|
452
|
+
]),
|
|
341
453
|
];
|
|
342
454
|
|
|
343
455
|
if (options.doctor) {
|
|
@@ -375,7 +487,7 @@ export function renderOnboardingCliStatus(options: CliStatusOptions): string {
|
|
|
375
487
|
` scope ${marker?.scope ?? 'none'}`,
|
|
376
488
|
` origin ${marker?.payload?.source ? formatAgentRecordSource(marker.payload.source) : 'n/a'}`,
|
|
377
489
|
` mode ${marker?.payload?.mode ?? 'n/a'}`,
|
|
378
|
-
` updated ${marker?.payload ? new Date(marker.payload.updatedAt).toISOString() : 'n/a'}`,
|
|
490
|
+
` updated ${marker?.payload ? appendTemporalLabel(new Date(marker.payload.updatedAt).toISOString(), marker.payload.updatedAt) : 'n/a'}`,
|
|
379
491
|
` working directory ${options.workingDirectory}`,
|
|
380
492
|
].join('\n');
|
|
381
493
|
}
|