@pellux/goodvibes-agent 1.0.31 → 1.0.34
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 +59 -2
- package/README.md +72 -58
- package/dist/package/main.js +7572 -2430
- package/docs/README.md +25 -21
- package/docs/channels-remote-and-api.md +6 -2
- package/docs/connected-host.md +27 -6
- package/docs/getting-started.md +87 -66
- package/docs/knowledge-artifacts-and-multimodal.md +16 -4
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +3 -2
- package/docs/release-and-publishing.md +15 -11
- package/docs/tools-and-commands.md +150 -128
- package/docs/voice-and-live-tts.md +1 -1
- package/package.json +8 -3
- package/release/live-verification/live-verification.json +148 -0
- package/release/live-verification/live-verification.md +187 -0
- package/release/performance-snapshot.json +57 -0
- package/release/release-notes.md +19 -0
- package/release/release-readiness.json +581 -0
- package/src/agent/harness-control.ts +42 -3
- package/src/cli/agent-knowledge-command.ts +5 -5
- package/src/cli/agent-knowledge-format.ts +11 -0
- package/src/cli/agent-knowledge-runtime.ts +92 -13
- package/src/cli/bundle-command.ts +5 -4
- package/src/cli/entrypoint.ts +5 -2
- package/src/cli/external-runtime.ts +2 -15
- package/src/cli/management.ts +4 -3
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/knowledge.ts +2 -2
- package/src/runtime/bootstrap.ts +10 -18
- package/src/runtime/connected-host-auth.ts +16 -0
- package/src/tools/agent-analysis-registry-policy.ts +2 -9
- package/src/tools/agent-channel-send-tool.ts +3 -9
- package/src/tools/agent-context-policy.ts +1 -5
- package/src/tools/agent-find-policy.ts +1 -4
- package/src/tools/agent-harness-channel-metadata.ts +177 -0
- package/src/tools/agent-harness-cli-metadata.ts +4 -1
- package/src/tools/agent-harness-command-catalog.ts +10 -3
- package/src/tools/agent-harness-connected-host-status.ts +9 -3
- package/src/tools/agent-harness-delegation-posture.ts +216 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +57 -22
- package/src/tools/agent-harness-mcp-metadata.ts +248 -0
- package/src/tools/agent-harness-media-posture.ts +282 -0
- package/src/tools/agent-harness-metadata.ts +44 -9
- package/src/tools/agent-harness-model-routing.ts +501 -0
- package/src/tools/agent-harness-model-tool-catalog.ts +7 -2
- package/src/tools/agent-harness-notification-metadata.ts +217 -0
- package/src/tools/agent-harness-operator-methods.ts +285 -0
- package/src/tools/agent-harness-pairing-posture.ts +265 -0
- package/src/tools/agent-harness-panel-metadata.ts +26 -12
- package/src/tools/agent-harness-provider-account-metadata.ts +205 -0
- package/src/tools/agent-harness-release-evidence.ts +364 -0
- package/src/tools/agent-harness-release-readiness.ts +298 -0
- package/src/tools/agent-harness-security-posture.ts +648 -0
- package/src/tools/agent-harness-service-posture.ts +207 -0
- package/src/tools/agent-harness-session-metadata.ts +284 -0
- package/src/tools/agent-harness-setup-posture.ts +295 -0
- package/src/tools/agent-harness-tool-schema.ts +104 -27
- package/src/tools/agent-harness-tool.ts +251 -235
- package/src/tools/agent-harness-ui-surface-metadata.ts +20 -12
- package/src/tools/agent-harness-workspace-actions.ts +260 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +4 -10
- package/src/tools/agent-knowledge-tool.ts +120 -25
- package/src/tools/agent-local-registry-tool.ts +3 -7
- package/src/tools/agent-media-generate-tool.ts +2 -8
- package/src/tools/agent-notify-tool.ts +3 -8
- package/src/tools/agent-operator-action-tool.ts +4 -10
- package/src/tools/agent-operator-briefing-tool.ts +1 -6
- package/src/tools/agent-read-policy.ts +1 -4
- package/src/tools/agent-reminder-schedule-tool.ts +4 -9
- package/src/tools/agent-tool-policy-guard.ts +15 -51
- package/src/tools/agent-web-search-policy.ts +1 -4
- package/src/tools/agent-work-plan-tool.ts +1 -6
- package/src/version.ts +2 -2
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import {
|
|
3
|
+
buildCliServicePosture,
|
|
4
|
+
type CliServiceEndpointPosture,
|
|
5
|
+
type CliServicePosture,
|
|
6
|
+
} from '../cli/service-posture.ts';
|
|
7
|
+
import type { RuntimeEndpointId } from '../cli/endpoints.ts';
|
|
8
|
+
|
|
9
|
+
export interface AgentHarnessServicePostureArgs {
|
|
10
|
+
readonly endpointId?: unknown;
|
|
11
|
+
readonly target?: unknown;
|
|
12
|
+
readonly query?: unknown;
|
|
13
|
+
readonly includeParameters?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type ServiceEndpointLookupSource = 'endpointId' | 'target' | 'query';
|
|
17
|
+
|
|
18
|
+
type ServiceEndpointResolution =
|
|
19
|
+
| { readonly status: 'found'; readonly endpoint: Record<string, unknown> }
|
|
20
|
+
| { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
|
|
21
|
+
| { readonly status: 'missing_lookup'; readonly usage: string };
|
|
22
|
+
|
|
23
|
+
const SERVICE_ENDPOINT_IDS: readonly RuntimeEndpointId[] = ['controlPlane', 'httpListener', 'web'];
|
|
24
|
+
|
|
25
|
+
function readString(value: unknown): string {
|
|
26
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveHomeDirectory(context: CommandContext): string {
|
|
30
|
+
return context.workspace.shellPaths?.homeDirectory
|
|
31
|
+
?? context.platform.configManager.getHomeDirectory()
|
|
32
|
+
?? '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveWorkingDirectory(context: CommandContext): string {
|
|
36
|
+
return context.workspace.shellPaths?.workingDirectory
|
|
37
|
+
?? context.platform.configManager.getWorkingDirectory()
|
|
38
|
+
?? '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function servicePostureRuntime(context: CommandContext) {
|
|
42
|
+
return {
|
|
43
|
+
configManager: context.platform.configManager,
|
|
44
|
+
workingDirectory: resolveWorkingDirectory(context),
|
|
45
|
+
homeDirectory: resolveHomeDirectory(context),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function servicePostureOptions(args: AgentHarnessServicePostureArgs) {
|
|
50
|
+
const includeDetails = args.includeParameters === true;
|
|
51
|
+
return {
|
|
52
|
+
probe: includeDetails,
|
|
53
|
+
logTailBytes: includeDetails ? 4096 : 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function summarizeLog(posture: CliServicePosture, includeTail: boolean): Record<string, unknown> {
|
|
58
|
+
return {
|
|
59
|
+
path: posture.log.path,
|
|
60
|
+
exists: posture.log.exists,
|
|
61
|
+
size: posture.log.size,
|
|
62
|
+
modifiedAt: posture.log.modifiedAt,
|
|
63
|
+
readError: posture.log.readError ?? null,
|
|
64
|
+
...(includeTail && posture.log.tail !== undefined ? { tail: posture.log.tail } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function describeEndpoint(
|
|
69
|
+
endpoint: CliServiceEndpointPosture,
|
|
70
|
+
options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {},
|
|
71
|
+
): Record<string, unknown> {
|
|
72
|
+
return {
|
|
73
|
+
id: endpoint.id,
|
|
74
|
+
label: endpoint.label,
|
|
75
|
+
enabled: endpoint.enabled,
|
|
76
|
+
binding: endpoint.binding,
|
|
77
|
+
bindPosture: endpoint.bindPosture,
|
|
78
|
+
networkFacing: endpoint.networkFacing,
|
|
79
|
+
...(endpoint.reachable !== undefined ? { reachable: endpoint.reachable } : {}),
|
|
80
|
+
...(options.lookup ? { lookup: options.lookup } : {}),
|
|
81
|
+
...(options.includeParameters ? {
|
|
82
|
+
policy: {
|
|
83
|
+
effect: 'read-only',
|
|
84
|
+
modelOperation: 'Inspect endpoint binding, network-facing posture, and optional reachability only.',
|
|
85
|
+
lifecycle: 'GoodVibes Agent does not start, stop, restart, install, expose, or mutate connected-host listeners.',
|
|
86
|
+
settings: 'Use agent_harness settings/get_setting for read-only inspection of endpoint settings; connected-host lifecycle/listener settings stay locked in Agent.',
|
|
87
|
+
},
|
|
88
|
+
} : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function describeEndpointCandidate(endpoint: CliServiceEndpointPosture): Record<string, unknown> {
|
|
93
|
+
return {
|
|
94
|
+
endpointId: endpoint.id,
|
|
95
|
+
label: endpoint.label,
|
|
96
|
+
enabled: endpoint.enabled,
|
|
97
|
+
binding: endpoint.binding,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function endpointLookupFromArgs(args: AgentHarnessServicePostureArgs): { readonly source: ServiceEndpointLookupSource; readonly input: string } | null {
|
|
102
|
+
const endpointId = readString(args.endpointId);
|
|
103
|
+
if (endpointId) return { source: 'endpointId', input: endpointId };
|
|
104
|
+
const target = readString(args.target);
|
|
105
|
+
if (target) return { source: 'target', input: target };
|
|
106
|
+
const query = readString(args.query);
|
|
107
|
+
return query ? { source: 'query', input: query } : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function endpointSearchText(endpoint: CliServiceEndpointPosture): string {
|
|
111
|
+
return [
|
|
112
|
+
endpoint.id,
|
|
113
|
+
endpoint.label,
|
|
114
|
+
endpoint.binding.hostMode,
|
|
115
|
+
endpoint.binding.configuredHost,
|
|
116
|
+
endpoint.binding.host,
|
|
117
|
+
String(endpoint.binding.port),
|
|
118
|
+
endpoint.bindPosture.kind,
|
|
119
|
+
endpoint.bindPosture.label,
|
|
120
|
+
].join('\n').toLowerCase();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveEndpoint(
|
|
124
|
+
posture: CliServicePosture,
|
|
125
|
+
args: AgentHarnessServicePostureArgs,
|
|
126
|
+
options: { readonly includeParameters?: boolean } = {},
|
|
127
|
+
): ServiceEndpointResolution {
|
|
128
|
+
const lookup = endpointLookupFromArgs(args);
|
|
129
|
+
if (!lookup) {
|
|
130
|
+
return {
|
|
131
|
+
status: 'missing_lookup',
|
|
132
|
+
usage: 'service_endpoint requires endpointId, target, or query. Valid endpoint ids are controlPlane, httpListener, and web.',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const endpoints = posture.endpoints;
|
|
136
|
+
const normalized = lookup.input.toLowerCase();
|
|
137
|
+
const exact = endpoints.find((endpoint) => endpoint.id === lookup.input);
|
|
138
|
+
if (exact) {
|
|
139
|
+
return { status: 'found', endpoint: describeEndpoint(exact, { includeParameters: options.includeParameters, lookup: { ...lookup, resolvedBy: 'id' } }) };
|
|
140
|
+
}
|
|
141
|
+
const insensitive = endpoints.find((endpoint) => endpoint.id.toLowerCase() === normalized);
|
|
142
|
+
if (insensitive) {
|
|
143
|
+
return { status: 'found', endpoint: describeEndpoint(insensitive, { includeParameters: options.includeParameters, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } }) };
|
|
144
|
+
}
|
|
145
|
+
const label = endpoints.find((endpoint) => endpoint.label.toLowerCase() === normalized);
|
|
146
|
+
if (label) {
|
|
147
|
+
return { status: 'found', endpoint: describeEndpoint(label, { includeParameters: options.includeParameters, lookup: { ...lookup, resolvedBy: 'label' } }) };
|
|
148
|
+
}
|
|
149
|
+
const searched = endpoints.filter((endpoint) => endpointSearchText(endpoint).includes(normalized));
|
|
150
|
+
if (searched.length === 1) {
|
|
151
|
+
return { status: 'found', endpoint: describeEndpoint(searched[0]!, { includeParameters: options.includeParameters, lookup: { ...lookup, resolvedBy: 'search' } }) };
|
|
152
|
+
}
|
|
153
|
+
if (searched.length > 1) {
|
|
154
|
+
return {
|
|
155
|
+
status: 'ambiguous',
|
|
156
|
+
input: lookup.input,
|
|
157
|
+
candidates: searched.map(describeEndpointCandidate),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
status: 'missing_lookup',
|
|
162
|
+
usage: `Unknown service endpoint ${lookup.input}. Valid endpoint ids are controlPlane, httpListener, and web.`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function servicePostureCatalogStatus(): Record<string, unknown> {
|
|
167
|
+
return {
|
|
168
|
+
modes: ['service_posture', 'service_endpoint'],
|
|
169
|
+
endpointIds: SERVICE_ENDPOINT_IDS,
|
|
170
|
+
readOnly: true,
|
|
171
|
+
lifecycle: 'Connected-host and listener lifecycle stay outside GoodVibes Agent.',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function servicePostureSummary(
|
|
176
|
+
context: CommandContext,
|
|
177
|
+
args: AgentHarnessServicePostureArgs,
|
|
178
|
+
): Promise<Record<string, unknown>> {
|
|
179
|
+
const includeDetails = args.includeParameters === true;
|
|
180
|
+
const posture = await buildCliServicePosture(servicePostureRuntime(context), servicePostureOptions(args));
|
|
181
|
+
return {
|
|
182
|
+
ownership: 'external-connected-host',
|
|
183
|
+
readOnly: true,
|
|
184
|
+
lifecycle: 'GoodVibes Agent reports connected-host/service posture but does not start, stop, restart, install, expose, or mutate host listeners.',
|
|
185
|
+
config: posture.config,
|
|
186
|
+
managed: posture.managed,
|
|
187
|
+
endpoints: posture.endpoints.map((endpoint) => describeEndpoint(endpoint, { includeParameters: includeDetails })),
|
|
188
|
+
log: summarizeLog(posture, includeDetails),
|
|
189
|
+
issues: posture.issues,
|
|
190
|
+
...(includeDetails ? { modelAccess: {
|
|
191
|
+
endpointLookup: 'Use mode:"service_endpoint" with endpointId, target, or query to inspect one endpoint.',
|
|
192
|
+
settings: 'Use mode:"settings" with includeHidden:true for endpoint setting descriptors. Host-owned listener settings remain read-only.',
|
|
193
|
+
liveHostStatus: 'Use mode:"connected_host_status" for SDK compatibility, token posture, and Agent Knowledge route readiness.',
|
|
194
|
+
} } : {}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function describeHarnessServiceEndpoint(
|
|
199
|
+
context: CommandContext,
|
|
200
|
+
args: AgentHarnessServicePostureArgs,
|
|
201
|
+
): Promise<ServiceEndpointResolution> {
|
|
202
|
+
const posture = await buildCliServicePosture(servicePostureRuntime(context), {
|
|
203
|
+
...servicePostureOptions(args),
|
|
204
|
+
probe: true,
|
|
205
|
+
});
|
|
206
|
+
return resolveEndpoint(posture, args, { includeParameters: true });
|
|
207
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
|
|
3
|
+
export interface AgentHarnessSessionArgs {
|
|
4
|
+
readonly sessionId?: unknown;
|
|
5
|
+
readonly target?: unknown;
|
|
6
|
+
readonly query?: unknown;
|
|
7
|
+
readonly includeParameters?: unknown;
|
|
8
|
+
readonly limit?: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type SessionResolution =
|
|
12
|
+
| { readonly status: 'found'; readonly session: Record<string, unknown> }
|
|
13
|
+
| { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
|
|
14
|
+
| { readonly status: 'missing_lookup'; readonly usage: string };
|
|
15
|
+
|
|
16
|
+
interface SessionSearchResult {
|
|
17
|
+
readonly session: SessionInfoLike;
|
|
18
|
+
readonly matchCount: number;
|
|
19
|
+
readonly snippets: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SessionInfoLike {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly title: string;
|
|
25
|
+
readonly model: string;
|
|
26
|
+
readonly provider: string;
|
|
27
|
+
readonly timestamp: number;
|
|
28
|
+
readonly messageCount: number;
|
|
29
|
+
readonly filePath: string;
|
|
30
|
+
readonly returnContext?: {
|
|
31
|
+
readonly activeTasks?: number;
|
|
32
|
+
readonly blockedTasks?: number;
|
|
33
|
+
readonly pendingApprovals?: number;
|
|
34
|
+
readonly openPanels?: readonly string[];
|
|
35
|
+
readonly remoteRunners?: readonly unknown[];
|
|
36
|
+
readonly worktreePaths?: readonly string[];
|
|
37
|
+
} | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readString(value: unknown): string {
|
|
41
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readLimit(value: unknown, fallback: number): number {
|
|
45
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
46
|
+
if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
|
|
47
|
+
return Math.max(1, Math.min(500, Math.trunc(parsed)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readSessionLookup(args: AgentHarnessSessionArgs): { readonly source: 'sessionId' | 'target' | 'query'; readonly input: string } | null {
|
|
51
|
+
const sessionId = readString(args.sessionId);
|
|
52
|
+
if (sessionId) return { source: 'sessionId', input: sessionId };
|
|
53
|
+
const target = readString(args.target);
|
|
54
|
+
if (target) return { source: 'target', input: target };
|
|
55
|
+
const query = readString(args.query);
|
|
56
|
+
return query ? { source: 'query', input: query } : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function safeIso(timestamp: number): string | null {
|
|
60
|
+
return Number.isFinite(timestamp) && timestamp > 0 ? new Date(timestamp).toISOString() : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function sessionSearchText(session: SessionInfoLike): string {
|
|
64
|
+
return [
|
|
65
|
+
session.name,
|
|
66
|
+
session.title,
|
|
67
|
+
session.model,
|
|
68
|
+
session.provider,
|
|
69
|
+
session.filePath,
|
|
70
|
+
].join('\n').toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function returnContextSummary(session: SessionInfoLike): Record<string, unknown> | undefined {
|
|
74
|
+
const context = session.returnContext;
|
|
75
|
+
if (!context) return undefined;
|
|
76
|
+
return {
|
|
77
|
+
activeTasks: context.activeTasks ?? 0,
|
|
78
|
+
blockedTasks: context.blockedTasks ?? 0,
|
|
79
|
+
pendingApprovals: context.pendingApprovals ?? 0,
|
|
80
|
+
savedOpenPanelsIgnored: context.openPanels?.length ?? 0,
|
|
81
|
+
remoteRunners: context.remoteRunners?.length ?? 0,
|
|
82
|
+
worktreePaths: context.worktreePaths?.length ?? 0,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function describeSessionCandidate(session: SessionInfoLike, currentSessionId: string): Record<string, unknown> {
|
|
87
|
+
return {
|
|
88
|
+
sessionId: session.name,
|
|
89
|
+
title: session.title || '(untitled)',
|
|
90
|
+
messageCount: session.messageCount,
|
|
91
|
+
active: session.name === currentSessionId,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function describeSession(
|
|
96
|
+
session: SessionInfoLike,
|
|
97
|
+
options: {
|
|
98
|
+
readonly currentSessionId: string;
|
|
99
|
+
readonly includeParameters?: boolean;
|
|
100
|
+
readonly search?: { readonly matchCount: number; readonly snippets: readonly string[] };
|
|
101
|
+
readonly lookup?: Record<string, unknown>;
|
|
102
|
+
},
|
|
103
|
+
): Record<string, unknown> {
|
|
104
|
+
const posture = returnContextSummary(session);
|
|
105
|
+
return {
|
|
106
|
+
sessionId: session.name,
|
|
107
|
+
title: session.title || '(untitled)',
|
|
108
|
+
timestamp: safeIso(session.timestamp),
|
|
109
|
+
messageCount: session.messageCount,
|
|
110
|
+
model: session.model || null,
|
|
111
|
+
provider: session.provider || null,
|
|
112
|
+
active: session.name === options.currentSessionId,
|
|
113
|
+
...(posture ? { returnContext: posture } : {}),
|
|
114
|
+
...(options.search ? {
|
|
115
|
+
search: {
|
|
116
|
+
matchCount: options.search.matchCount,
|
|
117
|
+
...(options.includeParameters ? { snippets: options.search.snippets.slice(0, 3) } : {}),
|
|
118
|
+
},
|
|
119
|
+
} : {}),
|
|
120
|
+
...(options.includeParameters ? {
|
|
121
|
+
filePath: session.filePath,
|
|
122
|
+
modelRoutes: {
|
|
123
|
+
inspectSessions: 'agent_harness mode:"sessions"',
|
|
124
|
+
inspectSession: 'agent_harness mode:"session"',
|
|
125
|
+
resumeSession: '/session resume <session-id-or-name>',
|
|
126
|
+
saveSession: '/session save [name]',
|
|
127
|
+
exportSession: '/session export <session-id> markdown',
|
|
128
|
+
deleteSession: '/session delete <session-id> --yes',
|
|
129
|
+
},
|
|
130
|
+
} : {}),
|
|
131
|
+
...(options.lookup ? { lookup: options.lookup } : {}),
|
|
132
|
+
...(options.includeParameters ? {
|
|
133
|
+
policy: {
|
|
134
|
+
effect: 'read-only',
|
|
135
|
+
values: 'Session posture returns saved-session metadata, search counts, optional snippets, return-context counts, bookmark counts, and saved bookmark file counts.',
|
|
136
|
+
mutation: 'Session save, rename, fork, resume, export, delete, bookmark toggles, and bookmark file writes remain visible workspace or slash-command flows with confirmation where mutating.',
|
|
137
|
+
},
|
|
138
|
+
} : {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function currentSession(context: CommandContext): Record<string, unknown> {
|
|
143
|
+
const runtime = context.session.runtime;
|
|
144
|
+
const conversationManager = context.session.conversationManager;
|
|
145
|
+
if (!runtime || !conversationManager) {
|
|
146
|
+
return {
|
|
147
|
+
status: 'unavailable',
|
|
148
|
+
sessionId: null,
|
|
149
|
+
title: '(unavailable)',
|
|
150
|
+
messageCount: 0,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const transcriptIndex = conversationManager.getTranscriptEventIndex();
|
|
154
|
+
return {
|
|
155
|
+
sessionId: runtime.sessionId,
|
|
156
|
+
title: conversationManager.title || '(untitled)',
|
|
157
|
+
messageCount: conversationManager.getMessageCount(),
|
|
158
|
+
model: runtime.model,
|
|
159
|
+
provider: runtime.provider,
|
|
160
|
+
transcript: {
|
|
161
|
+
events: transcriptIndex.events.length,
|
|
162
|
+
groups: transcriptIndex.groups.length,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function bookmarkSummary(context: CommandContext): Record<string, unknown> {
|
|
168
|
+
const manager = context.workspace.bookmarkManager;
|
|
169
|
+
if (!manager) {
|
|
170
|
+
return {
|
|
171
|
+
status: 'unavailable',
|
|
172
|
+
bookmarks: 0,
|
|
173
|
+
savedFiles: 0,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
status: 'available',
|
|
178
|
+
bookmarks: manager.list().length,
|
|
179
|
+
savedFiles: manager.listSavedFiles().length,
|
|
180
|
+
modelRoutes: {
|
|
181
|
+
visibleBookmarkPicker: 'agent_harness mode:"open_ui_surface" surfaceId:"bookmarks" confirm:true explicitUserRequest:"..."',
|
|
182
|
+
command: '/bookmarks',
|
|
183
|
+
importIntoKnowledge: 'agent_knowledge_ingest sourceKind:"bookmarks_file" confirm:true explicitUserRequest:"..."',
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function sessionCatalogStatus(context: CommandContext): Record<string, unknown> {
|
|
189
|
+
const manager = context.session.sessionManager;
|
|
190
|
+
const runtime = context.session.runtime;
|
|
191
|
+
return {
|
|
192
|
+
modes: ['sessions', 'session'],
|
|
193
|
+
savedSessions: manager?.list().length ?? 0,
|
|
194
|
+
currentSessionId: runtime?.sessionId ?? null,
|
|
195
|
+
readOnly: true,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function sessionSummary(context: CommandContext, args: AgentHarnessSessionArgs): Record<string, unknown> {
|
|
200
|
+
const manager = context.session.sessionManager;
|
|
201
|
+
const runtime = context.session.runtime;
|
|
202
|
+
if (!manager || !runtime) {
|
|
203
|
+
return {
|
|
204
|
+
status: 'unavailable',
|
|
205
|
+
sessions: [],
|
|
206
|
+
returned: 0,
|
|
207
|
+
total: 0,
|
|
208
|
+
current: currentSession(context),
|
|
209
|
+
bookmarks: bookmarkSummary(context),
|
|
210
|
+
policy: 'Session runtime or session manager is unavailable in this Agent context.',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const currentSessionId = runtime.sessionId;
|
|
214
|
+
const query = readString(args.query);
|
|
215
|
+
const searchResults: readonly SessionSearchResult[] = query ? manager.search(query) : [];
|
|
216
|
+
const searchByName = new Map(searchResults.map((result) => [result.session.name, result]));
|
|
217
|
+
const sessions = query
|
|
218
|
+
? searchResults.map((result) => result.session)
|
|
219
|
+
: manager.list().filter((session) => !query || sessionSearchText(session).includes(query.toLowerCase()));
|
|
220
|
+
const limited = sessions.slice(0, readLimit(args.limit, 100));
|
|
221
|
+
return {
|
|
222
|
+
status: 'available',
|
|
223
|
+
current: currentSession(context),
|
|
224
|
+
bookmarks: bookmarkSummary(context),
|
|
225
|
+
sessions: limited.map((session) => {
|
|
226
|
+
const search = searchByName.get(session.name);
|
|
227
|
+
return describeSession(session, {
|
|
228
|
+
currentSessionId,
|
|
229
|
+
includeParameters: args.includeParameters === true,
|
|
230
|
+
...(search ? { search: { matchCount: search.matchCount, snippets: search.snippets } } : {}),
|
|
231
|
+
});
|
|
232
|
+
}),
|
|
233
|
+
returned: limited.length,
|
|
234
|
+
total: sessions.length,
|
|
235
|
+
savedSessions: manager.list().length,
|
|
236
|
+
policy: 'Read-only session and bookmark posture. Save, resume, rename, fork, export, delete, bookmark toggles, and bookmark file writes remain visible workspace or slash-command flows.',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function describeHarnessSession(context: CommandContext, args: AgentHarnessSessionArgs): SessionResolution {
|
|
241
|
+
const manager = context.session.sessionManager;
|
|
242
|
+
if (!manager) {
|
|
243
|
+
return {
|
|
244
|
+
status: 'missing_lookup',
|
|
245
|
+
usage: 'Session manager is unavailable in this Agent context.',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const lookup = readSessionLookup(args);
|
|
249
|
+
if (!lookup) {
|
|
250
|
+
return {
|
|
251
|
+
status: 'missing_lookup',
|
|
252
|
+
usage: 'session requires sessionId, target, or query. Use mode:"sessions" to inspect saved session ids.',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const sessions = manager.list();
|
|
256
|
+
const currentSessionId = context.session.runtime.sessionId;
|
|
257
|
+
const exact = sessions.find((session) => session.name === lookup.input);
|
|
258
|
+
if (exact) return { status: 'found', session: describeSession(exact, { currentSessionId, includeParameters: true, lookup: { ...lookup, resolvedBy: 'id' } }) };
|
|
259
|
+
const normalized = lookup.input.toLowerCase();
|
|
260
|
+
const insensitive = sessions.find((session) => session.name.toLowerCase() === normalized);
|
|
261
|
+
if (insensitive) return { status: 'found', session: describeSession(insensitive, { currentSessionId, includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } }) };
|
|
262
|
+
const prefix = sessions.filter((session) => session.name.toLowerCase().startsWith(normalized));
|
|
263
|
+
if (prefix.length === 1) return { status: 'found', session: describeSession(prefix[0]!, { currentSessionId, includeParameters: true, lookup: { ...lookup, resolvedBy: 'prefix' } }) };
|
|
264
|
+
if (prefix.length > 1) {
|
|
265
|
+
return {
|
|
266
|
+
status: 'ambiguous',
|
|
267
|
+
input: lookup.input,
|
|
268
|
+
candidates: prefix.slice(0, 8).map((session) => describeSessionCandidate(session, currentSessionId)),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const searched = sessions.filter((session) => sessionSearchText(session).includes(normalized));
|
|
272
|
+
if (searched.length === 1) return { status: 'found', session: describeSession(searched[0]!, { currentSessionId, includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }) };
|
|
273
|
+
if (searched.length > 1) {
|
|
274
|
+
return {
|
|
275
|
+
status: 'ambiguous',
|
|
276
|
+
input: lookup.input,
|
|
277
|
+
candidates: searched.slice(0, 8).map((session) => describeSessionCandidate(session, currentSessionId)),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
status: 'missing_lookup',
|
|
282
|
+
usage: `Unknown session ${lookup.input}. Use mode:"sessions" to inspect saved session ids.`,
|
|
283
|
+
};
|
|
284
|
+
}
|