@pellux/goodvibes-agent 1.5.5 → 1.5.6
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/main.js +94130 -79335
- package/docs/tools-and-commands.md +2 -1
- package/package.json +6 -3
- package/src/agent/autonomy-schedule-format.ts +1 -1
- package/src/agent/autonomy-schedule.ts +3 -3
- package/src/agent/calendar/calendar-oauth-service.ts +324 -0
- package/src/agent/calendar-subscription-registry.ts +497 -0
- package/src/agent/email/email-service.ts +57 -0
- package/src/agent/email/smtp-client.ts +30 -10
- package/src/agent/memory-prompt.ts +143 -12
- package/src/agent/operator-actions.ts +26 -22
- package/src/agent/prompt-context-receipts.ts +45 -18
- package/src/agent/reminder-schedule-format.ts +1 -1
- package/src/agent/reminder-schedule.ts +3 -3
- package/src/agent/routine-schedule-format.ts +2 -2
- package/src/agent/routine-schedule-promotion.ts +5 -5
- package/src/agent/routine-schedule-receipts.ts +2 -2
- package/src/agent/schedule-edit-format.ts +1 -1
- package/src/agent/schedule-edit.ts +5 -5
- package/src/agent/session-registration.ts +281 -0
- package/src/agent/vibe-file.ts +50 -0
- package/src/cli/local-library-command-shared.ts +146 -0
- package/src/cli/management.ts +2 -1
- package/src/cli/personas-command.ts +252 -0
- package/src/cli/resume-relaunch-notice.ts +151 -0
- package/src/cli/routines-command.ts +25 -2
- package/src/cli/service-posture.ts +3 -2
- package/src/cli/skill-bundle-command.ts +175 -0
- package/src/cli/skills-command.ts +309 -0
- package/src/cli/status.ts +43 -7
- package/src/cli/tui-startup.ts +24 -1
- package/src/config/agent-settings-policy.ts +0 -1
- package/src/config/secret-config.ts +4 -0
- package/src/core/conversation-rendering.ts +20 -11
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/system-message-noise.ts +108 -0
- package/src/core/system-message-router.ts +54 -1
- package/src/core/thinking-overlay.ts +83 -0
- package/src/input/agent-workspace-access-command-editor-submission.ts +75 -108
- package/src/input/agent-workspace-access-command-editors.ts +108 -128
- package/src/input/agent-workspace-activation.ts +15 -0
- package/src/input/agent-workspace-basic-command-editor-submission.ts +292 -513
- package/src/input/agent-workspace-basic-command-editors.ts +433 -564
- package/src/input/agent-workspace-calendar-connect-editor.ts +116 -0
- package/src/input/agent-workspace-calendar-oauth-editor.ts +152 -0
- package/src/input/agent-workspace-calendar-subscribe-editor.ts +128 -0
- package/src/input/agent-workspace-categories.ts +7 -2
- package/src/input/agent-workspace-channel-command-editor-submission.ts +38 -52
- package/src/input/agent-workspace-channel-command-editors.ts +42 -46
- package/src/input/agent-workspace-command-editor-engine.ts +133 -0
- package/src/input/agent-workspace-command-editor.ts +4 -0
- package/src/input/agent-workspace-direct-editor-submission.ts +59 -0
- package/src/input/agent-workspace-email-connect-editor.ts +162 -0
- package/src/input/agent-workspace-knowledge-command-editor-submission.ts +52 -81
- package/src/input/agent-workspace-knowledge-command-editors.ts +71 -71
- package/src/input/agent-workspace-library-command-editor-submission.ts +10 -28
- package/src/input/agent-workspace-library-command-editors.ts +16 -2
- package/src/input/agent-workspace-live-counters.ts +55 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +60 -82
- package/src/input/agent-workspace-mcp-command-editors.ts +52 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +12 -39
- package/src/input/agent-workspace-media-command-editors.ts +10 -8
- package/src/input/agent-workspace-memory-command-editor-submission.ts +76 -151
- package/src/input/agent-workspace-memory-command-editors.ts +116 -141
- package/src/input/agent-workspace-operations-command-editor-submission.ts +131 -184
- package/src/input/agent-workspace-operations-command-editors.ts +150 -162
- package/src/input/agent-workspace-provider-command-editor-submission.ts +60 -106
- package/src/input/agent-workspace-provider-command-editors.ts +58 -68
- package/src/input/agent-workspace-search.ts +8 -1
- package/src/input/agent-workspace-session-command-editor-submission.ts +104 -132
- package/src/input/agent-workspace-session-command-editors.ts +160 -195
- package/src/input/agent-workspace-settings.ts +40 -1
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +55 -71
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +65 -69
- package/src/input/agent-workspace-snapshot-builders.ts +431 -0
- package/src/input/agent-workspace-snapshot-config.ts +43 -0
- package/src/input/agent-workspace-snapshot.ts +198 -432
- package/src/input/agent-workspace-task-command-editor-submission.ts +34 -44
- package/src/input/agent-workspace-task-command-editors.ts +27 -28
- package/src/input/agent-workspace-types.ts +35 -1
- package/src/input/agent-workspace.ts +5 -14
- package/src/input/command-registry.ts +23 -1
- package/src/input/commands/calendar-connect-runtime.ts +226 -0
- package/src/input/commands/calendar-runtime.ts +110 -7
- package/src/input/commands/calendar-subscription-runtime.ts +225 -0
- package/src/input/commands/email-runtime.ts +100 -40
- package/src/input/commands/knowledge.ts +1 -1
- package/src/input/commands/network-scan-runtime.ts +75 -0
- package/src/input/commands/operator-actions-runtime.ts +9 -6
- package/src/input/commands/personas-runtime.ts +1 -1
- package/src/input/commands/schedule-runtime.ts +4 -4
- package/src/input/commands/session-content.ts +13 -1
- package/src/input/commands/session-workflow.ts +15 -20
- package/src/input/commands/session.ts +3 -1
- package/src/input/commands/shell-core.ts +20 -6
- package/src/input/commands.ts +2 -2
- package/src/input/delete-key-policy.ts +46 -0
- package/src/input/feed-context-factory.ts +10 -0
- package/src/input/handler-feed.ts +87 -0
- package/src/input/handler-modal-routes.ts +6 -1
- package/src/input/handler.ts +5 -0
- package/src/input/panel-paste-flood-guard.ts +94 -0
- package/src/input/settings-modal-types.ts +5 -3
- package/src/input/settings-modal.ts +21 -0
- package/src/main.ts +36 -36
- package/src/permissions/approval-posture.ts +141 -0
- package/src/renderer/agent-workspace-context-lines.ts +10 -2
- package/src/renderer/compositor.ts +27 -4
- package/src/renderer/diff.ts +61 -18
- package/src/renderer/fullscreen-primitives.ts +37 -18
- package/src/renderer/markdown.ts +20 -10
- package/src/renderer/modal-factory.ts +25 -15
- package/src/renderer/overlay-box.ts +23 -12
- package/src/renderer/process-indicator.ts +8 -3
- package/src/renderer/prompt-content-width.ts +16 -0
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +2 -0
- package/src/renderer/startup-theme-probe.ts +35 -0
- package/src/renderer/status-glyphs.ts +11 -15
- package/src/renderer/system-message.ts +17 -2
- package/src/renderer/term-caps.ts +318 -0
- package/src/renderer/terminal-bg-probe.ts +373 -0
- package/src/renderer/terminal-escapes.ts +24 -0
- package/src/renderer/theme-mode-config.ts +87 -0
- package/src/renderer/theme.ts +241 -0
- package/src/renderer/thinking.ts +12 -3
- package/src/renderer/tool-call.ts +7 -3
- package/src/renderer/ui-factory.ts +92 -36
- package/src/renderer/ui-primitives.ts +33 -93
- package/src/runtime/bootstrap-core.ts +15 -0
- package/src/runtime/bootstrap-hook-bridge.ts +6 -0
- package/src/runtime/bootstrap-shell.ts +2 -0
- package/src/runtime/bootstrap.ts +68 -5
- package/src/runtime/calendar-boot-refresh.ts +105 -0
- package/src/runtime/lan-scan-consent.ts +253 -0
- package/src/runtime/services.ts +127 -2
- package/src/runtime/session-spine-rest-transport.ts +160 -0
- package/src/runtime/terminal-output-guard.ts +6 -1
- package/src/runtime/ui-services.ts +3 -0
- package/src/shell/agent-workspace-fullscreen.ts +5 -0
- package/src/shell/terminal-focus-mode.ts +120 -0
- package/src/shell/ui-openers.ts +13 -4
- package/src/tools/agent-harness-autonomy-intake.ts +5 -5
- package/src/tools/agent-harness-autonomy-queue.ts +1 -1
- package/src/tools/agent-harness-background-processes.ts +2 -1
- package/src/tools/agent-harness-metadata.ts +16 -8
- package/src/tools/agent-harness-operator-methods.ts +44 -8
- package/src/tools/agent-harness-personal-ops-discovery.ts +57 -10
- package/src/tools/agent-harness-personal-ops-lanes.ts +17 -5
- package/src/tools/agent-harness-personal-ops-operations.ts +7 -7
- package/src/tools/agent-harness-personal-ops-types.ts +6 -0
- package/src/tools/agent-harness-prompt-context.ts +26 -12
- package/src/tools/agent-harness-workspace-actions.ts +4 -0
- package/src/tools/agent-local-registry-args.ts +117 -0
- package/src/tools/agent-local-registry-memory.ts +227 -0
- package/src/tools/agent-local-registry-tool.ts +19 -237
- package/src/tools/agent-operator-briefing-tool.ts +2 -2
- package/src/tools/agent-operator-method-tool.ts +13 -0
- package/src/tools/agent-policy-explanation.ts +39 -4
- package/src/tools/agent-schedule-tool.ts +5 -5
- package/src/utils/terminal-width.ts +98 -1
- package/src/version.ts +1 -1
- package/src/cli/local-library-command.ts +0 -825
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-registration.ts
|
|
3
|
+
*
|
|
4
|
+
* Raw-REST mirror of the daemon's shared-session registration surface, written
|
|
5
|
+
* verbatim in the spirit of {@link ./operator-actions.ts}: request/response only,
|
|
6
|
+
* a Bearer token, an AbortController timeout, and the SAME classifyHttpFailure
|
|
7
|
+
* semantics (404 -> route unavailable, 401/403 -> auth required, network -> host
|
|
8
|
+
* unavailable). It deliberately imports NO SDK register/client types so the
|
|
9
|
+
* committed build compiles against the pinned npm @pellux/goodvibes-sdk (0.38.0),
|
|
10
|
+
* which does not ship POST /api/sessions/register at all. Against a 0.38.0 daemon
|
|
11
|
+
* this 404-degrades honestly; against a newer daemon (the sdk-dev overlay) it
|
|
12
|
+
* registers a real kind:'agent' record.
|
|
13
|
+
*
|
|
14
|
+
* Canonical vs transport axis (the S1 TRANSPORT ⊂ CANONICAL split): the record
|
|
15
|
+
* identity kind is ALWAYS 'agent' (see {@link SESSION_REGISTER_KIND}); the
|
|
16
|
+
* participant.surfaceKind carries the transport axis ('service' for this surface)
|
|
17
|
+
* and is NEVER set to 'agent'. `kind` is intentionally absent from the caller
|
|
18
|
+
* input so an unknown kind can never be emitted.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export type JsonRecord = Record<string, unknown>;
|
|
22
|
+
|
|
23
|
+
/** Only ever emitted value for the canonical record kind. */
|
|
24
|
+
export const SESSION_REGISTER_KIND = 'agent' as const;
|
|
25
|
+
export const SESSION_REGISTER_PATH = '/api/sessions/register';
|
|
26
|
+
|
|
27
|
+
/** Default interactive-path wire timeout, mirrors external-runtime.ts fetchJson. */
|
|
28
|
+
export const SESSION_REGISTER_TIMEOUT_MS = 1500;
|
|
29
|
+
/** Best-effort close-on-shutdown budget; short so it never blocks teardown. */
|
|
30
|
+
export const SESSION_CLOSE_TIMEOUT_MS = 500;
|
|
31
|
+
|
|
32
|
+
export type SessionRegistrationFailureKind =
|
|
33
|
+
| 'auth_required'
|
|
34
|
+
| 'connected_host_unavailable'
|
|
35
|
+
| 'connected_host_route_unavailable'
|
|
36
|
+
| 'connected_host_error';
|
|
37
|
+
|
|
38
|
+
export interface SessionRegistrationConnection {
|
|
39
|
+
readonly baseUrl: string;
|
|
40
|
+
readonly token: string | null;
|
|
41
|
+
readonly tokenPath?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The participant triple carried onto the record; surfaceKind is the TRANSPORT axis. */
|
|
45
|
+
export interface SessionRegistrationParticipant {
|
|
46
|
+
readonly surfaceKind: string;
|
|
47
|
+
readonly surfaceId: string;
|
|
48
|
+
readonly externalId?: string;
|
|
49
|
+
readonly userId?: string;
|
|
50
|
+
readonly displayName?: string;
|
|
51
|
+
readonly routeId?: string;
|
|
52
|
+
readonly lastSeenAt: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Caller input for a register/heartbeat upsert. NOTE: no `kind` field — the
|
|
57
|
+
* builder always stamps SESSION_REGISTER_KIND. Omitting `title` yields a
|
|
58
|
+
* heartbeat that never renames a titled session; omitting `reopen` keeps a
|
|
59
|
+
* closed session closed.
|
|
60
|
+
*/
|
|
61
|
+
export interface SessionRegistrationInput {
|
|
62
|
+
readonly sessionId: string;
|
|
63
|
+
readonly project?: string;
|
|
64
|
+
readonly title?: string;
|
|
65
|
+
readonly participant: SessionRegistrationParticipant;
|
|
66
|
+
readonly reopen?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface SessionRegisterRequest {
|
|
70
|
+
readonly path: string;
|
|
71
|
+
readonly method: 'POST';
|
|
72
|
+
readonly body: JsonRecord;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface SessionMutationRequest {
|
|
76
|
+
readonly path: string;
|
|
77
|
+
readonly method: 'POST';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface SessionRecordSummary {
|
|
81
|
+
readonly id: string;
|
|
82
|
+
readonly kind: string;
|
|
83
|
+
readonly status: string;
|
|
84
|
+
readonly project?: string;
|
|
85
|
+
readonly title?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Honest outcome discriminator. 'still_closed' is the Wave-1 heartbeat-resurrection
|
|
90
|
+
* guard: a 200 that carries conflict:{status:'closed'} with reopened:false MUST NOT
|
|
91
|
+
* be read as 'registered' or 'reopened'.
|
|
92
|
+
*/
|
|
93
|
+
export type SessionRegisterOutcome = 'registered' | 'reopened' | 'still_closed';
|
|
94
|
+
|
|
95
|
+
export interface SessionRegisterSuccess {
|
|
96
|
+
readonly ok: true;
|
|
97
|
+
readonly outcome: SessionRegisterOutcome;
|
|
98
|
+
readonly reopened: boolean;
|
|
99
|
+
readonly conflict?: { readonly status: string };
|
|
100
|
+
readonly session?: SessionRecordSummary;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface SessionMutationFailure {
|
|
104
|
+
readonly ok: false;
|
|
105
|
+
readonly kind: SessionRegistrationFailureKind;
|
|
106
|
+
readonly status?: number;
|
|
107
|
+
readonly error: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type SessionRegisterResult = SessionRegisterSuccess | SessionMutationFailure;
|
|
111
|
+
export type SessionCloseResult = { readonly ok: true } | SessionMutationFailure;
|
|
112
|
+
|
|
113
|
+
export function isRecord(value: unknown): value is JsonRecord {
|
|
114
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function summarizeError(error: unknown): string {
|
|
118
|
+
return error instanceof Error ? error.message : String(error);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function readResponseBody(response: Response): Promise<unknown> {
|
|
122
|
+
const text = await response.text();
|
|
123
|
+
if (!text.trim()) return {};
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(text) as unknown;
|
|
126
|
+
} catch {
|
|
127
|
+
return text;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Identical failure classification to operator-actions.ts:246-266 — 401/403 is
|
|
133
|
+
* auth, 404 is a missing route (the 0.38.0 / pre-spine daemon), anything else is
|
|
134
|
+
* a generic connected-host error.
|
|
135
|
+
*/
|
|
136
|
+
function classifyHttpFailure(status: number, body: unknown): SessionMutationFailure {
|
|
137
|
+
const detail = isRecord(body) && typeof body.error === 'string' ? body.error : '';
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
status,
|
|
141
|
+
kind: status === 401 || status === 403
|
|
142
|
+
? 'auth_required'
|
|
143
|
+
: status === 404
|
|
144
|
+
? 'connected_host_route_unavailable'
|
|
145
|
+
: 'connected_host_error',
|
|
146
|
+
error: `HTTP ${status}${detail ? `: ${detail}` : ''}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function buildSessionRegisterRequest(input: SessionRegistrationInput): SessionRegisterRequest {
|
|
151
|
+
const participant: JsonRecord = {
|
|
152
|
+
surfaceKind: input.participant.surfaceKind,
|
|
153
|
+
surfaceId: input.participant.surfaceId,
|
|
154
|
+
lastSeenAt: input.participant.lastSeenAt,
|
|
155
|
+
};
|
|
156
|
+
if (input.participant.externalId) participant.externalId = input.participant.externalId;
|
|
157
|
+
if (input.participant.userId) participant.userId = input.participant.userId;
|
|
158
|
+
if (input.participant.displayName) participant.displayName = input.participant.displayName;
|
|
159
|
+
if (input.participant.routeId) participant.routeId = input.participant.routeId;
|
|
160
|
+
|
|
161
|
+
const body: JsonRecord = {
|
|
162
|
+
sessionId: input.sessionId,
|
|
163
|
+
kind: SESSION_REGISTER_KIND,
|
|
164
|
+
participant,
|
|
165
|
+
};
|
|
166
|
+
if (input.project) body.project = input.project;
|
|
167
|
+
if (input.title) body.title = input.title;
|
|
168
|
+
// reopen is sent ONLY when explicitly true — the user resume verb. A heartbeat
|
|
169
|
+
// (reopen omitted) leaves a closed session closed.
|
|
170
|
+
if (input.reopen === true) body.reopen = true;
|
|
171
|
+
|
|
172
|
+
return { path: SESSION_REGISTER_PATH, method: 'POST', body };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function buildSessionCloseRequest(sessionId: string): SessionMutationRequest {
|
|
176
|
+
return { path: `/api/sessions/${encodeURIComponent(sessionId)}/close`, method: 'POST' };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function summarizeSession(value: unknown): SessionRecordSummary | undefined {
|
|
180
|
+
if (!isRecord(value)) return undefined;
|
|
181
|
+
const id = typeof value.id === 'string' ? value.id : '';
|
|
182
|
+
if (!id) return undefined;
|
|
183
|
+
return {
|
|
184
|
+
id,
|
|
185
|
+
kind: typeof value.kind === 'string' ? value.kind : '',
|
|
186
|
+
status: typeof value.status === 'string' ? value.status : '',
|
|
187
|
+
...(typeof value.project === 'string' ? { project: value.project } : {}),
|
|
188
|
+
...(typeof value.title === 'string' ? { title: value.title } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function interpretRegisterResponse(body: unknown): SessionRegisterSuccess {
|
|
193
|
+
const record = isRecord(body) ? body : {};
|
|
194
|
+
const reopened = record.reopened === true;
|
|
195
|
+
const conflict = isRecord(record.conflict) && typeof record.conflict.status === 'string'
|
|
196
|
+
? { status: record.conflict.status }
|
|
197
|
+
: undefined;
|
|
198
|
+
const session = summarizeSession(record.session);
|
|
199
|
+
const outcome: SessionRegisterOutcome = reopened
|
|
200
|
+
? 'reopened'
|
|
201
|
+
: conflict?.status === 'closed'
|
|
202
|
+
? 'still_closed'
|
|
203
|
+
: 'registered';
|
|
204
|
+
return {
|
|
205
|
+
ok: true,
|
|
206
|
+
outcome,
|
|
207
|
+
reopened,
|
|
208
|
+
...(conflict ? { conflict } : {}),
|
|
209
|
+
...(session ? { session } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export async function postSessionRegister(
|
|
214
|
+
connection: SessionRegistrationConnection,
|
|
215
|
+
input: SessionRegistrationInput,
|
|
216
|
+
options: { readonly timeoutMs?: number } = {},
|
|
217
|
+
): Promise<SessionRegisterResult> {
|
|
218
|
+
if (!connection.token) {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
kind: 'auth_required',
|
|
222
|
+
error: connection.tokenPath
|
|
223
|
+
? `no connected-host operator token found at ${connection.tokenPath}`
|
|
224
|
+
: 'no connected-host operator token found',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const request = buildSessionRegisterRequest(input);
|
|
228
|
+
const controller = new AbortController();
|
|
229
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? SESSION_REGISTER_TIMEOUT_MS);
|
|
230
|
+
try {
|
|
231
|
+
const response = await fetch(`${connection.baseUrl}${request.path}`, {
|
|
232
|
+
method: request.method,
|
|
233
|
+
headers: {
|
|
234
|
+
authorization: `Bearer ${connection.token}`,
|
|
235
|
+
'content-type': 'application/json',
|
|
236
|
+
},
|
|
237
|
+
body: JSON.stringify(request.body),
|
|
238
|
+
signal: controller.signal,
|
|
239
|
+
});
|
|
240
|
+
const body = await readResponseBody(response);
|
|
241
|
+
if (!response.ok) return classifyHttpFailure(response.status, body);
|
|
242
|
+
return interpretRegisterResponse(body);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return { ok: false, kind: 'connected_host_unavailable', error: summarizeError(error) };
|
|
245
|
+
} finally {
|
|
246
|
+
clearTimeout(timer);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function postSessionClose(
|
|
251
|
+
connection: SessionRegistrationConnection,
|
|
252
|
+
sessionId: string,
|
|
253
|
+
options: { readonly timeoutMs?: number } = {},
|
|
254
|
+
): Promise<SessionCloseResult> {
|
|
255
|
+
if (!connection.token) {
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
kind: 'auth_required',
|
|
259
|
+
error: connection.tokenPath
|
|
260
|
+
? `no connected-host operator token found at ${connection.tokenPath}`
|
|
261
|
+
: 'no connected-host operator token found',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const request = buildSessionCloseRequest(sessionId);
|
|
265
|
+
const controller = new AbortController();
|
|
266
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? SESSION_CLOSE_TIMEOUT_MS);
|
|
267
|
+
try {
|
|
268
|
+
const response = await fetch(`${connection.baseUrl}${request.path}`, {
|
|
269
|
+
method: request.method,
|
|
270
|
+
headers: { authorization: `Bearer ${connection.token}` },
|
|
271
|
+
signal: controller.signal,
|
|
272
|
+
});
|
|
273
|
+
const body = await readResponseBody(response);
|
|
274
|
+
if (!response.ok) return classifyHttpFailure(response.status, body);
|
|
275
|
+
return { ok: true };
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return { ok: false, kind: 'connected_host_unavailable', error: summarizeError(error) };
|
|
278
|
+
} finally {
|
|
279
|
+
clearTimeout(timer);
|
|
280
|
+
}
|
|
281
|
+
}
|
package/src/agent/vibe-file.ts
CHANGED
|
@@ -4,6 +4,12 @@ import type { ShellPathService } from '@/runtime/index.ts';
|
|
|
4
4
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
5
5
|
import { assertNoSecretLikeText } from './persona-registry.ts';
|
|
6
6
|
import { parseMarkdownFrontmatter, stripMarkdownFrontmatter } from './markdown-frontmatter.ts';
|
|
7
|
+
// W6-C2 (E6): VIBE.md is now a PROJECTION of persona/constraint records in the
|
|
8
|
+
// canonical memory store, not a separate source of truth. renderVibeProjection emits
|
|
9
|
+
// the same '## VIBE.md' block from those records (caveat preserved); the file is
|
|
10
|
+
// demoted to an import/export FORMAT folded in via vibeBodyToConstraintOptions.
|
|
11
|
+
import { renderVibeProjection, vibeBodyToConstraintOptions } from '@pellux/goodvibes-sdk/platform/state';
|
|
12
|
+
import type { MemoryRegistry, MemoryScope } from '@pellux/goodvibes-sdk/platform/state';
|
|
7
13
|
|
|
8
14
|
export type AgentVibeScope = 'project' | 'global';
|
|
9
15
|
|
|
@@ -205,6 +211,50 @@ export function buildVibePrompt(shellPaths: AgentVibePaths): string | null {
|
|
|
205
211
|
return lines.join('\n');
|
|
206
212
|
}
|
|
207
213
|
|
|
214
|
+
/**
|
|
215
|
+
* W6-C2 (E6): the VIBE.md prompt block as a PROJECTION of persona/constraint records.
|
|
216
|
+
*
|
|
217
|
+
* This is the store-sourced replacement for buildVibePrompt (which reads the file
|
|
218
|
+
* directly). It renders the same '## GoodVibes Agent VIBE.md' block — including the
|
|
219
|
+
* precedence caveat — from the cls:'constraint' persona records in the canonical
|
|
220
|
+
* store, so persona instructions have a single source of truth alongside every other
|
|
221
|
+
* durable fact. Returns null when there are no persona records to project.
|
|
222
|
+
*/
|
|
223
|
+
export function buildVibeProjectionPrompt(memoryRegistry: MemoryRegistry): string | null {
|
|
224
|
+
return renderVibeProjection(memoryRegistry.getAll());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* W6-C2 (E6): fold discovered VIBE.md files into the store as persona/constraint
|
|
229
|
+
* records — the file demoted to an IMPORT FORMAT. Each bullet becomes one record so a
|
|
230
|
+
* later single-record edit changes exactly one projected line. Secret-like content is
|
|
231
|
+
* already rejected by discoverVibeFiles (readVibeCandidate → assertNoSecretLikeText),
|
|
232
|
+
* so only clean bodies reach here. Idempotent enough for boot: importing the same body
|
|
233
|
+
* twice creates duplicate records only if their summaries differ, so callers should run
|
|
234
|
+
* this as a one-time migration, not every boot (mirrors the memory fold precedent).
|
|
235
|
+
*/
|
|
236
|
+
export async function importVibeFilesIntoMemory(
|
|
237
|
+
memoryRegistry: MemoryRegistry,
|
|
238
|
+
shellPaths: AgentVibePaths,
|
|
239
|
+
): Promise<number> {
|
|
240
|
+
const snapshot = discoverVibeFiles(shellPaths);
|
|
241
|
+
let created = 0;
|
|
242
|
+
for (const file of snapshot.files) {
|
|
243
|
+
const scope: MemoryScope = file.scope === 'global' ? 'team' : 'project';
|
|
244
|
+
const name = file.frontmatter.name?.trim();
|
|
245
|
+
const options = vibeBodyToConstraintOptions(file.body, {
|
|
246
|
+
scope,
|
|
247
|
+
...(name ? { name } : {}),
|
|
248
|
+
sourceRef: file.path,
|
|
249
|
+
});
|
|
250
|
+
for (const opts of options) {
|
|
251
|
+
await memoryRegistry.add(opts);
|
|
252
|
+
created += 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return created;
|
|
256
|
+
}
|
|
257
|
+
|
|
208
258
|
export function formatVibeStatus(snapshot: AgentVibeSnapshot): string {
|
|
209
259
|
const lines: string[] = [
|
|
210
260
|
'GoodVibes Agent VIBE.md',
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { createShellPathService } from '@/runtime/index.ts';
|
|
2
|
+
import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
|
|
3
|
+
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
4
|
+
import type { CliCommandOutput } from './types.ts';
|
|
5
|
+
import type { CliCommandRuntime } from './management.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared prelude for the Agent-local library CLI commands (personas, skills,
|
|
9
|
+
* skill bundles): option parsing, success/failure envelopes, and registry
|
|
10
|
+
* accessors. Split out of local-library-command.ts (W4-H1 ruling: the file
|
|
11
|
+
* cleanly contained three independent command handlers glued together) so
|
|
12
|
+
* personas-command.ts, skills-command.ts, and skill-bundle-command.ts can
|
|
13
|
+
* share one option-parsing/output prelude without duplicating it.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface CommandSuccess<TData> {
|
|
17
|
+
readonly ok: true;
|
|
18
|
+
readonly kind: string;
|
|
19
|
+
readonly data: TData;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CommandFailure {
|
|
23
|
+
readonly ok: false;
|
|
24
|
+
readonly kind: string;
|
|
25
|
+
readonly error: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ParsedOptions {
|
|
29
|
+
readonly values: ReadonlyMap<string, string>;
|
|
30
|
+
readonly flags: ReadonlySet<string>;
|
|
31
|
+
readonly positionals: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const LOCAL_LIBRARY_VALUE_OPTIONS = [
|
|
35
|
+
'name',
|
|
36
|
+
'description',
|
|
37
|
+
'body',
|
|
38
|
+
'procedure',
|
|
39
|
+
'tags',
|
|
40
|
+
'triggers',
|
|
41
|
+
'requires-env',
|
|
42
|
+
'requires-command',
|
|
43
|
+
'requires-commands',
|
|
44
|
+
'skills',
|
|
45
|
+
'provenance',
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
export function jsonOrText(runtime: CliCommandRuntime, value: unknown, text: string): string {
|
|
49
|
+
return runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function success<TData>(runtime: CliCommandRuntime, kind: string, data: TData, text: string): CliCommandOutput {
|
|
53
|
+
const value: CommandSuccess<TData> = { ok: true, kind, data };
|
|
54
|
+
return { output: jsonOrText(runtime, value, text), exitCode: 0 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function failure(runtime: CliCommandRuntime, kind: string, error: string, exitCode: number): CliCommandOutput {
|
|
58
|
+
const value: CommandFailure = { ok: false, kind, error };
|
|
59
|
+
return {
|
|
60
|
+
output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : error,
|
|
61
|
+
exitCode,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function parseOptions(args: readonly string[], valueOptions: readonly string[] = LOCAL_LIBRARY_VALUE_OPTIONS): ParsedOptions {
|
|
66
|
+
const valued = new Set(valueOptions);
|
|
67
|
+
const values = new Map<string, string>();
|
|
68
|
+
const flags = new Set<string>();
|
|
69
|
+
const positionals: string[] = [];
|
|
70
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
71
|
+
const arg = args[index] ?? '';
|
|
72
|
+
if (arg === '--') {
|
|
73
|
+
positionals.push(...args.slice(index + 1));
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (!arg.startsWith('--')) {
|
|
77
|
+
positionals.push(arg);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const raw = arg.slice(2);
|
|
81
|
+
const equalIndex = raw.indexOf('=');
|
|
82
|
+
if (equalIndex >= 0) {
|
|
83
|
+
values.set(raw.slice(0, equalIndex), raw.slice(equalIndex + 1));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const name = raw;
|
|
87
|
+
const next = args[index + 1];
|
|
88
|
+
if (next !== undefined && (valued.has(name) || !next.startsWith('--'))) {
|
|
89
|
+
values.set(name, next);
|
|
90
|
+
index += 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
flags.add(name);
|
|
94
|
+
}
|
|
95
|
+
return { values, flags, positionals };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function optionValue(options: ParsedOptions, name: string): string | undefined {
|
|
99
|
+
const value = options.values.get(name);
|
|
100
|
+
if (value === undefined) return undefined;
|
|
101
|
+
const trimmed = value.trim();
|
|
102
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function requiredOption(options: ParsedOptions, name: string, usage: string): string {
|
|
106
|
+
const value = optionValue(options, name);
|
|
107
|
+
if (!value) throw new Error(`${usage}\nMissing --${name}.`);
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function csvOption(options: ParsedOptions, name: string): readonly string[] | undefined {
|
|
112
|
+
const value = optionValue(options, name);
|
|
113
|
+
if (value === undefined) return undefined;
|
|
114
|
+
return value.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function hasFlag(options: ParsedOptions, name: string): boolean {
|
|
118
|
+
return options.flags.has(name);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function personaRegistry(runtime: CliCommandRuntime): AgentPersonaRegistry {
|
|
122
|
+
return AgentPersonaRegistry.fromShellPaths(createShellPathService({
|
|
123
|
+
workingDirectory: runtime.workingDirectory,
|
|
124
|
+
homeDirectory: runtime.homeDirectory,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function skillRegistry(runtime: CliCommandRuntime): AgentSkillRegistry {
|
|
129
|
+
return AgentSkillRegistry.fromShellPaths(createShellPathService({
|
|
130
|
+
workingDirectory: runtime.workingDirectory,
|
|
131
|
+
homeDirectory: runtime.homeDirectory,
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function shellPaths(runtime: CliCommandRuntime): ReturnType<typeof createShellPathService> {
|
|
136
|
+
return createShellPathService({
|
|
137
|
+
workingDirectory: runtime.workingDirectory,
|
|
138
|
+
homeDirectory: runtime.homeDirectory,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function errorOutput(runtime: CliCommandRuntime, error: unknown, kind: string): CliCommandOutput {
|
|
143
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
+
const exitCode = message.startsWith('Usage:') || message.includes('\nMissing --') ? 2 : 1;
|
|
145
|
+
return failure(runtime, kind, message, exitCode);
|
|
146
|
+
}
|
package/src/cli/management.ts
CHANGED
|
@@ -29,7 +29,8 @@ import { classifyProviderSetup } from './provider-classification.ts';
|
|
|
29
29
|
import { handleBundleCommand } from './bundle-command.ts';
|
|
30
30
|
import { handleSecrets, handleSessions, handleTasks, renderPairing, renderSubscriptions } from './management-commands.ts';
|
|
31
31
|
import { handleAgentKnowledgeCommand, handleAgentKnowledgeShortcutCommand, handleCompatCommand, handleDelegateCommand } from './agent-knowledge-command.ts';
|
|
32
|
-
import { handlePersonasCommand
|
|
32
|
+
import { handlePersonasCommand } from './personas-command.ts';
|
|
33
|
+
import { handleSkillsCommand } from './skills-command.ts';
|
|
33
34
|
import { handleMemoryCommand } from './memory-command.ts';
|
|
34
35
|
import { handleProfilesCommand } from './profiles-command.ts';
|
|
35
36
|
import { handleRoutinesCommand } from './routines-command.ts';
|