@vellumai/assistant 0.4.43 → 0.4.44
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/ARCHITECTURE.md +13 -14
- package/README.md +11 -12
- package/docs/architecture/integrations.md +75 -93
- package/package.json +1 -1
- package/src/__tests__/approval-routes-http.test.ts +0 -2
- package/src/__tests__/bundled-asset.test.ts +1 -1
- package/src/__tests__/checker.test.ts +31 -28
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/error-handler-friendly-messages.test.ts +46 -0
- package/src/__tests__/managed-twitter-guardrails.test.ts +5 -1
- package/src/__tests__/onboarding-template-contract.test.ts +0 -10
- package/src/__tests__/provider-fail-open-selection.test.ts +12 -2
- package/src/__tests__/send-endpoint-busy.test.ts +0 -3
- package/src/__tests__/session-confirmation-signals.test.ts +7 -45
- package/src/__tests__/starter-task-flow.test.ts +9 -19
- package/src/__tests__/system-prompt.test.ts +3 -4
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/twitter-platform-proxy-client.test.ts +43 -18
- package/src/cli/commands/amazon/index.ts +4 -39
- package/src/cli/commands/amazon/session.ts +18 -26
- package/src/cli/commands/twitter/__tests__/cli-read-routing.test.ts +58 -196
- package/src/cli/commands/twitter/__tests__/cli-routing.test.ts +26 -186
- package/src/cli/commands/twitter/__tests__/oauth-client.test.ts +1 -47
- package/src/cli/commands/twitter/index.ts +95 -835
- package/src/cli/commands/twitter/oauth-client.ts +1 -35
- package/src/cli/commands/twitter/router.ts +70 -115
- package/src/cli/commands/twitter/types.ts +30 -0
- package/src/cli/reference.ts +2 -2
- package/src/config/bundled-skills/amazon/SKILL.md +0 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -6
- package/src/config/bundled-skills/app-builder/TOOLS.json +0 -4
- package/src/config/bundled-skills/doordash/SKILL.md +0 -1
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +1 -82
- package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -28
- package/src/config/bundled-skills/doordash/lib/session.ts +21 -17
- package/src/config/bundled-skills/twitter/SKILL.md +53 -166
- package/src/config/feature-flag-registry.json +8 -0
- package/src/daemon/handlers/session-history.ts +41 -9
- package/src/daemon/lifecycle.ts +4 -17
- package/src/daemon/message-types/apps.ts +0 -25
- package/src/daemon/message-types/integrations.ts +1 -7
- package/src/daemon/message-types/sessions.ts +6 -1
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/ride-shotgun-handler.ts +33 -1
- package/src/daemon/seed-files.ts +3 -27
- package/src/daemon/server.ts +2 -18
- package/src/daemon/session-agent-loop-handlers.ts +24 -2
- package/src/daemon/session-runtime-assembly.ts +0 -7
- package/src/daemon/session-surfaces.ts +185 -33
- package/src/daemon/session.ts +2 -28
- package/src/memory/app-store.ts +0 -18
- package/src/memory/schema/infrastructure.ts +0 -8
- package/src/permissions/defaults.ts +3 -3
- package/src/prompts/system-prompt.ts +4 -5
- package/src/prompts/templates/BOOTSTRAP.md +0 -3
- package/src/providers/registry.ts +2 -4
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +2 -1
- package/src/runtime/auth/route-policy.ts +0 -4
- package/src/runtime/auth/scopes.ts +1 -0
- package/src/runtime/auth/token-service.ts +1 -1
- package/src/runtime/http-types.ts +10 -0
- package/src/runtime/middleware/error-handler.ts +14 -1
- package/src/runtime/routes/app-management-routes.ts +61 -64
- package/src/runtime/routes/brain-graph/brain-graph.html +1845 -0
- package/src/runtime/routes/brain-graph-routes.ts +4 -42
- package/src/runtime/routes/conversation-routes.ts +9 -6
- package/src/runtime/routes/diagnostics-routes.ts +91 -14
- package/src/runtime/routes/settings-routes.ts +3 -93
- package/src/tools/AGENTS.md +38 -0
- package/src/tools/apps/executors.ts +0 -6
- package/src/tools/document/editor-template.ts +10 -8
- package/src/twitter/platform-proxy-client.ts +6 -3
- package/src/util/errors.ts +12 -0
- package/src/__tests__/home-base-bootstrap.test.ts +0 -84
- package/src/__tests__/prebuilt-home-base-seed.test.ts +0 -79
- package/src/cli/commands/twitter/__tests__/cli-error-shaping.test.ts +0 -265
- package/src/cli/commands/twitter/client.ts +0 -989
- package/src/cli/commands/twitter/session.ts +0 -121
- package/src/home-base/app-link-store.ts +0 -78
- package/src/home-base/bootstrap.ts +0 -74
- package/src/home-base/prebuilt/brain-graph.html +0 -1483
- package/src/home-base/prebuilt/index.html +0 -702
- package/src/home-base/prebuilt/seed-metadata.json +0 -21
- package/src/home-base/prebuilt/seed.ts +0 -122
- package/src/home-base/prebuilt-home-base-updater.ts +0 -36
- package/src/util/cookie-session.ts +0 -98
|
@@ -156,47 +156,14 @@ export function handleGetBrainGraph(): Response {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
export function handleServeHomeBaseUI(bearerToken?: string): Response {
|
|
160
|
-
try {
|
|
161
|
-
const prebuiltDir = resolveBundledDir(
|
|
162
|
-
import.meta.dirname ?? __dirname,
|
|
163
|
-
"../../home-base/prebuilt",
|
|
164
|
-
"prebuilt",
|
|
165
|
-
);
|
|
166
|
-
let html = readFileSync(join(prebuiltDir, "index.html"), "utf-8");
|
|
167
|
-
if (bearerToken) {
|
|
168
|
-
const escapedToken = bearerToken
|
|
169
|
-
.replace(/&/g, "&")
|
|
170
|
-
.replace(/"/g, """)
|
|
171
|
-
.replace(/</g, "<")
|
|
172
|
-
.replace(/>/g, ">");
|
|
173
|
-
html = html.replace(
|
|
174
|
-
"</head>",
|
|
175
|
-
` <meta name="api-token" content="${escapedToken}">\n</head>`,
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
return new Response(html, {
|
|
179
|
-
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
180
|
-
});
|
|
181
|
-
} catch (err) {
|
|
182
|
-
return Response.json(
|
|
183
|
-
{
|
|
184
|
-
error: "Home Base UI not available",
|
|
185
|
-
detail: err instanceof Error ? err.message : String(err),
|
|
186
|
-
},
|
|
187
|
-
{ status: 500 },
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
159
|
export function handleServeBrainGraphUI(bearerToken?: string): Response {
|
|
193
160
|
try {
|
|
194
|
-
const
|
|
161
|
+
const brainGraphDir = resolveBundledDir(
|
|
195
162
|
import.meta.dirname ?? __dirname,
|
|
196
|
-
"
|
|
197
|
-
"
|
|
163
|
+
"./brain-graph",
|
|
164
|
+
"brain-graph",
|
|
198
165
|
);
|
|
199
|
-
let html = readFileSync(join(
|
|
166
|
+
let html = readFileSync(join(brainGraphDir, "brain-graph.html"), "utf-8");
|
|
200
167
|
if (bearerToken) {
|
|
201
168
|
// Inject token as a meta tag for client-side fetch authentication.
|
|
202
169
|
// HTML-escape the token value to guard against injection if the token
|
|
@@ -256,10 +223,5 @@ export function brainGraphRouteDefinitions(deps: {
|
|
|
256
223
|
method: "GET",
|
|
257
224
|
handler: () => handleServeBrainGraphUI(deps.mintUiPageToken()),
|
|
258
225
|
},
|
|
259
|
-
{
|
|
260
|
-
endpoint: "home-base-ui",
|
|
261
|
-
method: "GET",
|
|
262
|
-
handler: () => handleServeHomeBaseUI(deps.mintUiPageToken()),
|
|
263
|
-
},
|
|
264
226
|
];
|
|
265
227
|
}
|
|
@@ -331,6 +331,9 @@ export function handleListMessages(
|
|
|
331
331
|
attachments: msgAttachments,
|
|
332
332
|
...(m.toolCalls.length > 0 ? { toolCalls: m.toolCalls } : {}),
|
|
333
333
|
...(interfaces ? { interfaces } : {}),
|
|
334
|
+
...(m.surfaces.length > 0 ? { surfaces: m.surfaces } : {}),
|
|
335
|
+
...(m.textSegments.length > 0 ? { textSegments: m.textSegments } : {}),
|
|
336
|
+
...(m.contentOrder.length > 0 ? { contentOrder: m.contentOrder } : {}),
|
|
334
337
|
};
|
|
335
338
|
});
|
|
336
339
|
|
|
@@ -554,11 +557,11 @@ export async function handleSendMessage(
|
|
|
554
557
|
}
|
|
555
558
|
|
|
556
559
|
const onEvent = makeHubPublisher(smDeps, mapping.conversationId, session);
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
session.
|
|
560
|
+
// Wire sendToClient to the SSE hub so all subsystems (prompter, surface
|
|
561
|
+
// resolver, notifiers, trace emitter) can reach the HTTP client. The
|
|
562
|
+
// IPC socket removal (PR #14431) left sendToClient as a no-op; this
|
|
563
|
+
// restores the delivery path using the SSE hub instead.
|
|
564
|
+
session.updateClient(onEvent, true);
|
|
562
565
|
|
|
563
566
|
const attachments = hasAttachments
|
|
564
567
|
? smDeps.resolveAttachments(attachmentIds)
|
|
@@ -610,7 +613,7 @@ export async function handleSendMessage(
|
|
|
610
613
|
// can finish the current turn and process this queued message.
|
|
611
614
|
if (session.hasAnyPendingConfirmation()) {
|
|
612
615
|
// Emit authoritative denial state for each pending request.
|
|
613
|
-
//
|
|
616
|
+
// sendToClient (wired to the SSE hub) delivers these to the client.
|
|
614
617
|
for (const interaction of pendingInteractions.getByConversation(
|
|
615
618
|
mapping.conversationId,
|
|
616
619
|
)) {
|
|
@@ -7,9 +7,17 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createWriteStream,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
readdirSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
statSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
} from "node:fs";
|
|
11
19
|
import { homedir, tmpdir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
20
|
+
import { basename, join } from "node:path";
|
|
13
21
|
|
|
14
22
|
import archiver from "archiver";
|
|
15
23
|
import { and, desc, eq, gte, lte } from "drizzle-orm";
|
|
@@ -104,6 +112,58 @@ function redactDeep(value: unknown): unknown {
|
|
|
104
112
|
return value;
|
|
105
113
|
}
|
|
106
114
|
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Crash report discovery
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
const CRASH_REPORT_EXTENSIONS = new Set([".crash", ".ips", ".diag"]);
|
|
120
|
+
const CRASH_REPORT_TAR_GZ = ".tar.gz";
|
|
121
|
+
const CRASH_REPORT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
122
|
+
|
|
123
|
+
function findRecentCrashReports(): string[] {
|
|
124
|
+
const diagnosticReportsDir = join(
|
|
125
|
+
homedir(),
|
|
126
|
+
"Library",
|
|
127
|
+
"Logs",
|
|
128
|
+
"DiagnosticReports",
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const entries = readdirSync(diagnosticReportsDir);
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
const results: string[] = [];
|
|
135
|
+
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
// Case-insensitive prefix match for "vellum-assistant"
|
|
138
|
+
if (!entry.toLowerCase().startsWith("vellum-assistant")) continue;
|
|
139
|
+
|
|
140
|
+
// Check extension
|
|
141
|
+
const lowerEntry = entry.toLowerCase();
|
|
142
|
+
const hasValidExt =
|
|
143
|
+
CRASH_REPORT_EXTENSIONS.has(
|
|
144
|
+
lowerEntry.slice(lowerEntry.lastIndexOf(".")),
|
|
145
|
+
) || lowerEntry.endsWith(CRASH_REPORT_TAR_GZ);
|
|
146
|
+
|
|
147
|
+
if (!hasValidExt) continue;
|
|
148
|
+
|
|
149
|
+
const filePath = join(diagnosticReportsDir, entry);
|
|
150
|
+
try {
|
|
151
|
+
const stat = statSync(filePath);
|
|
152
|
+
if (!stat.isFile()) continue;
|
|
153
|
+
if (now - stat.mtimeMs > CRASH_REPORT_MAX_AGE_MS) continue;
|
|
154
|
+
results.push(filePath);
|
|
155
|
+
} catch {
|
|
156
|
+
// Skip files we can't stat
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return results;
|
|
161
|
+
} catch {
|
|
162
|
+
// Directory doesn't exist or can't be read — not an error
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
107
167
|
// ---------------------------------------------------------------------------
|
|
108
168
|
// Diagnostics export handler
|
|
109
169
|
// ---------------------------------------------------------------------------
|
|
@@ -344,6 +404,28 @@ async function handleDiagnosticsExport(body: {
|
|
|
344
404
|
|
|
345
405
|
archive.pipe(output);
|
|
346
406
|
archive.directory(tempDir, false);
|
|
407
|
+
|
|
408
|
+
// Add recent crash report files under crash-reports/.
|
|
409
|
+
// Text-based crash files (.crash, .ips, .diag) are redacted using the
|
|
410
|
+
// same patterns as conversation data. Binary archives (.tar.gz) are
|
|
411
|
+
// added as-is since they can't be meaningfully text-redacted.
|
|
412
|
+
const crashReportFiles = findRecentCrashReports();
|
|
413
|
+
for (const filePath of crashReportFiles) {
|
|
414
|
+
try {
|
|
415
|
+
const fileName = basename(filePath);
|
|
416
|
+
if (fileName.toLowerCase().endsWith(CRASH_REPORT_TAR_GZ)) {
|
|
417
|
+
archive.file(filePath, { name: "crash-reports/" + fileName });
|
|
418
|
+
} else {
|
|
419
|
+
const content = readFileSync(filePath, "utf-8");
|
|
420
|
+
archive.append(redact(content), {
|
|
421
|
+
name: "crash-reports/" + fileName,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
// Skip files that can't be read
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
347
429
|
archive.finalize();
|
|
348
430
|
});
|
|
349
431
|
|
|
@@ -382,9 +464,7 @@ const MAX_WINDOW_TITLE_LENGTH = 100;
|
|
|
382
464
|
|
|
383
465
|
function sanitizeWindowTitle(title: string | undefined): string {
|
|
384
466
|
if (!title) return "";
|
|
385
|
-
return title
|
|
386
|
-
.replace(/[<>]/g, "")
|
|
387
|
-
.slice(0, MAX_WINDOW_TITLE_LENGTH);
|
|
467
|
+
return title.replace(/[<>]/g, "").slice(0, MAX_WINDOW_TITLE_LENGTH);
|
|
388
468
|
}
|
|
389
469
|
|
|
390
470
|
interface DictationBody {
|
|
@@ -469,10 +549,7 @@ function buildCombinedDictationPrompt(
|
|
|
469
549
|
return sections.join("\n");
|
|
470
550
|
}
|
|
471
551
|
|
|
472
|
-
function buildCommandPrompt(
|
|
473
|
-
body: DictationBody,
|
|
474
|
-
stylePrompt?: string,
|
|
475
|
-
): string {
|
|
552
|
+
function buildCommandPrompt(body: DictationBody, stylePrompt?: string): string {
|
|
476
553
|
const sections = [
|
|
477
554
|
"You are a text transformation assistant. The user has selected text and given a voice command to transform it.",
|
|
478
555
|
"",
|
|
@@ -559,7 +636,10 @@ async function handleDictation(body: DictationBody): Promise<Response> {
|
|
|
559
636
|
const stylePrompt = profile.stylePrompt || undefined;
|
|
560
637
|
|
|
561
638
|
// Command mode: selected text present
|
|
562
|
-
if (
|
|
639
|
+
if (
|
|
640
|
+
body.context.selectedText &&
|
|
641
|
+
body.context.selectedText.trim().length > 0
|
|
642
|
+
) {
|
|
563
643
|
log.info({ mode: "command" }, "Command mode (selected text present)");
|
|
564
644
|
return handleCommandMode(body, profile, profileMeta, stylePrompt);
|
|
565
645
|
}
|
|
@@ -669,10 +749,7 @@ async function handleDictation(body: DictationBody): Promise<Response> {
|
|
|
669
749
|
});
|
|
670
750
|
}
|
|
671
751
|
const cleanedText = input.text?.trim() || transcription;
|
|
672
|
-
const normalizedText = applyDictionary(
|
|
673
|
-
cleanedText,
|
|
674
|
-
profile.dictionary,
|
|
675
|
-
);
|
|
752
|
+
const normalizedText = applyDictionary(cleanedText, profile.dictionary);
|
|
676
753
|
return Response.json({
|
|
677
754
|
text: normalizedText,
|
|
678
755
|
mode: "dictation",
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP route handlers for settings, identity/avatar, voice config,
|
|
3
|
-
* OAuth connect, Twitter auth,
|
|
3
|
+
* OAuth connect, Twitter auth, and workspace files.
|
|
4
4
|
*
|
|
5
5
|
* Migrated from IPC handlers:
|
|
6
6
|
* - handlers/config-voice.ts (voice_config_update)
|
|
7
7
|
* - handlers/avatar.ts (generate_avatar)
|
|
8
8
|
* - handlers/oauth-connect.ts (oauth_connect_start)
|
|
9
9
|
* - handlers/twitter-auth.ts (twitter_auth_start, twitter_auth_status)
|
|
10
|
-
* - handlers/home-base.ts (home_base_get)
|
|
11
10
|
* - handlers/workspace-files.ts (workspace_files_list, workspace_file_read)
|
|
12
11
|
* - handlers/config-tools.ts (tool_names_list, tool_permission_simulate, env_vars_request)
|
|
13
12
|
*/
|
|
@@ -29,17 +28,7 @@ import {
|
|
|
29
28
|
} from "../../config/loader.js";
|
|
30
29
|
import { loadSkillCatalog } from "../../config/skills.js";
|
|
31
30
|
import { normalizeActivationKey } from "../../daemon/handlers/config-voice.js";
|
|
32
|
-
import { getHomeBaseAppLink } from "../../home-base/app-link-store.js";
|
|
33
|
-
import {
|
|
34
|
-
bootstrapHomeBaseAppLink,
|
|
35
|
-
resolveHomeBaseAppId,
|
|
36
|
-
} from "../../home-base/bootstrap.js";
|
|
37
|
-
import {
|
|
38
|
-
getPrebuiltHomeBasePreview,
|
|
39
|
-
getPrebuiltHomeBaseTaskPayload,
|
|
40
|
-
} from "../../home-base/prebuilt/seed.js";
|
|
41
31
|
import { getPublicBaseUrl } from "../../inbound/public-ingress-urls.js";
|
|
42
|
-
import { getApp } from "../../memory/app-store.js";
|
|
43
32
|
import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
|
|
44
33
|
import {
|
|
45
34
|
getProviderProfile,
|
|
@@ -452,7 +441,8 @@ function handleTwitterAuthStatus(): Response {
|
|
|
452
441
|
)?.baseUrl as string | undefined;
|
|
453
442
|
const platformAssistantIdResolvable = !!(
|
|
454
443
|
(platformBaseUrlFromEnv || platformBaseUrlFromConfig) &&
|
|
455
|
-
|
|
444
|
+
(getSecureKey("credential:vellum:platform_assistant_id") ||
|
|
445
|
+
getPlatformAssistantId())
|
|
456
446
|
);
|
|
457
447
|
|
|
458
448
|
const managedPrerequisites = {
|
|
@@ -470,13 +460,6 @@ function handleTwitterAuthStatus(): Response {
|
|
|
470
460
|
"credential:integration:twitter:client_id",
|
|
471
461
|
);
|
|
472
462
|
|
|
473
|
-
// Strategy
|
|
474
|
-
const strategyRaw = getNestedValue(raw, "twitter.strategy") as
|
|
475
|
-
| string
|
|
476
|
-
| undefined;
|
|
477
|
-
const strategy = strategyRaw ?? "auto";
|
|
478
|
-
const strategyConfigured = strategyRaw !== undefined;
|
|
479
|
-
|
|
480
463
|
// In managed mode, connected is always false (auth goes through platform)
|
|
481
464
|
const connected = mode === "managed" ? false : !!accessToken;
|
|
482
465
|
|
|
@@ -487,8 +470,6 @@ function handleTwitterAuthStatus(): Response {
|
|
|
487
470
|
managedAvailable,
|
|
488
471
|
managedPrerequisites,
|
|
489
472
|
localClientConfigured,
|
|
490
|
-
strategy,
|
|
491
|
-
strategyConfigured,
|
|
492
473
|
});
|
|
493
474
|
} catch (err) {
|
|
494
475
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -497,66 +478,6 @@ function handleTwitterAuthStatus(): Response {
|
|
|
497
478
|
}
|
|
498
479
|
}
|
|
499
480
|
|
|
500
|
-
// ---------------------------------------------------------------------------
|
|
501
|
-
// Home base
|
|
502
|
-
// ---------------------------------------------------------------------------
|
|
503
|
-
|
|
504
|
-
function handleHomeBaseGet(ensureLinked: boolean): Response {
|
|
505
|
-
try {
|
|
506
|
-
if (ensureLinked !== false) {
|
|
507
|
-
bootstrapHomeBaseAppLink();
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
const appId = resolveHomeBaseAppId();
|
|
511
|
-
if (!appId) {
|
|
512
|
-
return Response.json({ homeBase: null });
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
const link = getHomeBaseAppLink();
|
|
516
|
-
const source = link?.source ?? "prebuilt_seed";
|
|
517
|
-
|
|
518
|
-
let preview: {
|
|
519
|
-
title: string;
|
|
520
|
-
subtitle: string;
|
|
521
|
-
description: string;
|
|
522
|
-
icon: string;
|
|
523
|
-
metrics: Array<{ label: string; value: string }>;
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
if (source === "personalized") {
|
|
527
|
-
const app = getApp(appId);
|
|
528
|
-
if (app) {
|
|
529
|
-
preview = {
|
|
530
|
-
title: app.name,
|
|
531
|
-
subtitle: "Dashboard",
|
|
532
|
-
description: app.description ?? "",
|
|
533
|
-
icon: app.icon ?? "🏠",
|
|
534
|
-
metrics: [],
|
|
535
|
-
};
|
|
536
|
-
} else {
|
|
537
|
-
preview = getPrebuiltHomeBasePreview();
|
|
538
|
-
}
|
|
539
|
-
} else {
|
|
540
|
-
preview = getPrebuiltHomeBasePreview();
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const tasks = getPrebuiltHomeBaseTaskPayload();
|
|
544
|
-
|
|
545
|
-
return Response.json({
|
|
546
|
-
homeBase: {
|
|
547
|
-
appId,
|
|
548
|
-
source,
|
|
549
|
-
starterTasks: tasks.starterTasks,
|
|
550
|
-
onboardingTasks: tasks.onboardingTasks,
|
|
551
|
-
preview,
|
|
552
|
-
},
|
|
553
|
-
});
|
|
554
|
-
} catch (err) {
|
|
555
|
-
log.error({ err }, "Failed to resolve home base metadata");
|
|
556
|
-
return Response.json({ homeBase: null });
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
481
|
// ---------------------------------------------------------------------------
|
|
561
482
|
// Workspace files (IPC-style list/read)
|
|
562
483
|
// ---------------------------------------------------------------------------
|
|
@@ -906,17 +827,6 @@ export function settingsRouteDefinitions(): RouteDefinition[] {
|
|
|
906
827
|
handler: () => handleTwitterAuthStatus(),
|
|
907
828
|
},
|
|
908
829
|
|
|
909
|
-
// Home base
|
|
910
|
-
{
|
|
911
|
-
endpoint: "home-base",
|
|
912
|
-
method: "GET",
|
|
913
|
-
policyKey: "home-base",
|
|
914
|
-
handler: ({ url }) => {
|
|
915
|
-
const ensureLinked = url.searchParams.get("ensureLinked") !== "false";
|
|
916
|
-
return handleHomeBaseGet(ensureLinked);
|
|
917
|
-
},
|
|
918
|
-
},
|
|
919
|
-
|
|
920
830
|
// Workspace files (IPC-style list/read -- distinct from workspace-routes.ts tree/file)
|
|
921
831
|
{
|
|
922
832
|
endpoint: "workspace-files",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Tools — Agent Instructions
|
|
2
|
+
|
|
3
|
+
## No New Tools Policy
|
|
4
|
+
|
|
5
|
+
**New tool registrations require approval from Team Jarvis.**
|
|
6
|
+
|
|
7
|
+
The tool registration system (`class ... implements Tool` + `registerTool()`) is being phased out in favor of skill-based approaches. Before adding a new tool, contact Team Jarvis for approval.
|
|
8
|
+
|
|
9
|
+
## Why This Policy Exists
|
|
10
|
+
|
|
11
|
+
1. **Skills are preferred** — The project direction is to teach the assistant CLI tools via skills rather than hardcoding tool implementations. Skills are progressively disclosed into context, are more portable, and are often self-contained.
|
|
12
|
+
|
|
13
|
+
2. **Context overhead** — Each registered tool adds to the system prompt and increases token usage for every conversation.
|
|
14
|
+
|
|
15
|
+
3. **Maintenance burden** — Tools require ongoing maintenance, testing, and security review. Skills can be iterated on independently.
|
|
16
|
+
|
|
17
|
+
## What To Do Instead
|
|
18
|
+
|
|
19
|
+
Instead of creating a new tool, consider:
|
|
20
|
+
|
|
21
|
+
1. **Create a skill**
|
|
22
|
+
|
|
23
|
+
2. **Use existing tools** — Many capabilities can be achieved by combining existing tools (bash, file operations, network tools) with skill instructions.
|
|
24
|
+
|
|
25
|
+
3. **External CLI tools** — If you need new functionality, consider whether it can be exposed as a CLI tool that the assistant can invoke via bash.
|
|
26
|
+
|
|
27
|
+
## If You Have Approval
|
|
28
|
+
|
|
29
|
+
If Team Jarvis has approved your new tool:
|
|
30
|
+
|
|
31
|
+
1. The pre-commit hook will block your commit by default
|
|
32
|
+
2. Use `git commit --no-verify` to bypass the hook
|
|
33
|
+
3. Include the approval context in your PR description
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Questions?
|
|
37
|
+
|
|
38
|
+
Contact Team Jarvis before shipping a new tool.
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { compileApp } from "../../bundler/app-compiler.js";
|
|
12
|
-
import { setHomeBaseAppLink } from "../../home-base/app-link-store.js";
|
|
13
12
|
import { generateAppIcon } from "../../media/app-icon-generator.js";
|
|
14
13
|
import type {
|
|
15
14
|
AppDefinition,
|
|
@@ -115,7 +114,6 @@ export interface AppCreateInput {
|
|
|
115
114
|
html?: string;
|
|
116
115
|
pages?: Record<string, string>;
|
|
117
116
|
auto_open?: boolean;
|
|
118
|
-
set_as_home_base?: boolean;
|
|
119
117
|
preview?: Record<string, unknown>;
|
|
120
118
|
/** When provided, controls multifile scaffold behavior. */
|
|
121
119
|
featureFlags?: { multifileEnabled: boolean };
|
|
@@ -240,10 +238,6 @@ render(<App />, document.getElementById('app')!);
|
|
|
240
238
|
}
|
|
241
239
|
}
|
|
242
240
|
|
|
243
|
-
if (input.set_as_home_base) {
|
|
244
|
-
setHomeBaseAppLink(app.id, "personalized");
|
|
245
|
-
}
|
|
246
|
-
|
|
247
241
|
// Emit the inline preview card via the proxy without opening a workspace panel.
|
|
248
242
|
// open_mode: "preview" signals to the client that this should be shown inline only.
|
|
249
243
|
if (autoOpen && proxyToolResolver) {
|
|
@@ -39,17 +39,19 @@ export function generateEditorHTML(
|
|
|
39
39
|
--v-text: #1D1D1F;
|
|
40
40
|
--v-text-secondary: #86868B;
|
|
41
41
|
--v-text-muted: #AEAEB2;
|
|
42
|
-
--v-accent: #
|
|
42
|
+
--v-accent: #657D5B;
|
|
43
|
+
--v-accent-hover: #516748;
|
|
43
44
|
}
|
|
44
45
|
@media (prefers-color-scheme: dark) {
|
|
45
46
|
:root {
|
|
46
|
-
--v-bg: #
|
|
47
|
-
--v-surface: #
|
|
48
|
-
--v-surface-border: #
|
|
49
|
-
--v-text: #
|
|
50
|
-
--v-text-secondary: #
|
|
51
|
-
--v-text-muted: #
|
|
52
|
-
--v-accent: #
|
|
47
|
+
--v-bg: #20201E;
|
|
48
|
+
--v-surface: #3A3A37;
|
|
49
|
+
--v-surface-border: #4A4A46;
|
|
50
|
+
--v-text: #F5F3EB;
|
|
51
|
+
--v-text-secondary: #A1A096;
|
|
52
|
+
--v-text-muted: #6B6B65;
|
|
53
|
+
--v-accent: #657D5B;
|
|
54
|
+
--v-accent-hover: #516748;
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Prerequisites:
|
|
10
10
|
* - Platform base URL (env `PLATFORM_BASE_URL` or config `platform.baseUrl`)
|
|
11
11
|
* - Auth token (secure key `credential:vellum:assistant_api_key`)
|
|
12
|
-
* - Platform assistant ID (env `PLATFORM_ASSISTANT_ID`)
|
|
12
|
+
* - Platform assistant ID (secure key store or env `PLATFORM_ASSISTANT_ID`)
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { getPlatformAssistantId, getPlatformBaseUrl } from "../config/env.js";
|
|
@@ -64,10 +64,13 @@ export function resolveAuthToken(): string | undefined {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Resolve the platform assistant ID from environment.
|
|
67
|
+
* Resolve the platform assistant ID from secure storage (local) or environment (containerized).
|
|
68
68
|
*/
|
|
69
69
|
export function resolvePlatformAssistantId(): string {
|
|
70
|
-
return
|
|
70
|
+
return (
|
|
71
|
+
getSecureKey("credential:vellum:platform_assistant_id") ||
|
|
72
|
+
getPlatformAssistantId()
|
|
73
|
+
);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
/**
|
package/src/util/errors.ts
CHANGED
|
@@ -151,6 +151,18 @@ export class ConfigError extends AssistantError {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
export class ProviderNotConfiguredError extends ConfigError {
|
|
155
|
+
constructor(
|
|
156
|
+
public readonly requestedProvider: string,
|
|
157
|
+
public readonly registeredProviders: string[],
|
|
158
|
+
) {
|
|
159
|
+
super(
|
|
160
|
+
`No providers available. Requested: "${requestedProvider}". Registered: ${registeredProviders.join(", ") || "none"}`,
|
|
161
|
+
);
|
|
162
|
+
this.name = "ProviderNotConfiguredError";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
154
166
|
export class DaemonError extends AssistantError {
|
|
155
167
|
constructor(message: string, options?: { cause?: unknown }) {
|
|
156
168
|
super(message, ErrorCode.DAEMON_ERROR, options);
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
5
|
-
|
|
6
|
-
const testRoot = mkdtempSync(join(tmpdir(), "home-base-bootstrap-test-"));
|
|
7
|
-
const dataDir = join(testRoot, "data");
|
|
8
|
-
const dbPath = join(testRoot, "assistant.db");
|
|
9
|
-
|
|
10
|
-
mock.module("../util/platform.js", () => ({
|
|
11
|
-
getDataDir: () => dataDir,
|
|
12
|
-
getDbPath: () => dbPath,
|
|
13
|
-
ensureDataDir: () => {
|
|
14
|
-
mkdirSync(dataDir, { recursive: true });
|
|
15
|
-
},
|
|
16
|
-
isMacOS: () => process.platform === "darwin",
|
|
17
|
-
isLinux: () => process.platform === "linux",
|
|
18
|
-
isWindows: () => process.platform === "win32",
|
|
19
|
-
getSocketPath: () => join(testRoot, "test.sock"),
|
|
20
|
-
getPidPath: () => join(testRoot, "test.pid"),
|
|
21
|
-
getLogPath: () => join(testRoot, "test.log"),
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
mock.module("../util/logger.js", () => ({
|
|
25
|
-
getLogger: () =>
|
|
26
|
-
new Proxy({} as Record<string, unknown>, {
|
|
27
|
-
get: () => () => {},
|
|
28
|
-
}),
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
|
-
import { getHomeBaseAppLink } from "../home-base/app-link-store.js";
|
|
32
|
-
import {
|
|
33
|
-
bootstrapHomeBaseAppLink,
|
|
34
|
-
resolveHomeBaseAppId,
|
|
35
|
-
} from "../home-base/bootstrap.js";
|
|
36
|
-
import { deleteApp } from "../memory/app-store.js";
|
|
37
|
-
import { initializeDb, resetDb } from "../memory/db.js";
|
|
38
|
-
|
|
39
|
-
describe("home base bootstrap", () => {
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
resetDb();
|
|
42
|
-
rmSync(testRoot, { recursive: true, force: true });
|
|
43
|
-
mkdirSync(testRoot, { recursive: true });
|
|
44
|
-
initializeDb();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
afterAll(() => {
|
|
48
|
-
resetDb();
|
|
49
|
-
rmSync(testRoot, { recursive: true, force: true });
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("creates a durable Home Base link on first bootstrap", () => {
|
|
53
|
-
const result = bootstrapHomeBaseAppLink();
|
|
54
|
-
expect(result).not.toBeNull();
|
|
55
|
-
const link = getHomeBaseAppLink();
|
|
56
|
-
|
|
57
|
-
expect(result!.linked).toBe(true);
|
|
58
|
-
expect(link).not.toBeNull();
|
|
59
|
-
expect(link?.appId).toBe(result!.appId);
|
|
60
|
-
expect(resolveHomeBaseAppId()).toBe(result!.appId);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("reuses existing link on repeated bootstrap calls", () => {
|
|
64
|
-
const first = bootstrapHomeBaseAppLink();
|
|
65
|
-
const second = bootstrapHomeBaseAppLink();
|
|
66
|
-
expect(first).not.toBeNull();
|
|
67
|
-
expect(second).not.toBeNull();
|
|
68
|
-
|
|
69
|
-
expect(second!.appId).toBe(first!.appId);
|
|
70
|
-
expect(second!.created).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("relinks when stored app id is stale", () => {
|
|
74
|
-
const first = bootstrapHomeBaseAppLink();
|
|
75
|
-
expect(first).not.toBeNull();
|
|
76
|
-
deleteApp(first!.appId);
|
|
77
|
-
|
|
78
|
-
const second = bootstrapHomeBaseAppLink();
|
|
79
|
-
expect(second).not.toBeNull();
|
|
80
|
-
|
|
81
|
-
expect(second!.appId).not.toBe(first!.appId);
|
|
82
|
-
expect(getHomeBaseAppLink()?.appId).toBe(second!.appId);
|
|
83
|
-
});
|
|
84
|
-
});
|