@vellumai/assistant 0.5.2 → 0.5.4
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 +109 -0
- package/docs/architecture/memory.md +105 -0
- package/docs/skills.md +100 -0
- package/package.json +1 -1
- package/src/__tests__/archive-recall.test.ts +560 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -0
- package/src/__tests__/conversation-agent-loop.test.ts +7 -0
- package/src/__tests__/conversation-clear-safety.test.ts +259 -0
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +474 -0
- package/src/__tests__/conversation-wipe.test.ts +226 -0
- package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
- package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +3 -0
- package/src/__tests__/inline-command-runner.test.ts +311 -0
- package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
- package/src/__tests__/list-messages-attachments.test.ts +96 -0
- package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
- package/src/__tests__/memory-brief-time.test.ts +285 -0
- package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
- package/src/__tests__/memory-chunk-archive.test.ts +400 -0
- package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
- package/src/__tests__/memory-episode-archive.test.ts +370 -0
- package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
- package/src/__tests__/memory-observation-archive.test.ts +375 -0
- package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
- package/src/__tests__/memory-recall-quality.test.ts +2 -2
- package/src/__tests__/memory-reducer-job.test.ts +538 -0
- package/src/__tests__/memory-reducer-scheduling.test.ts +473 -0
- package/src/__tests__/memory-reducer-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +707 -0
- package/src/__tests__/memory-reducer.test.ts +704 -0
- package/src/__tests__/memory-regressions.test.ts +30 -8
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- package/src/__tests__/parse-identity-fields.test.ts +129 -0
- package/src/__tests__/simplified-memory-e2e.test.ts +666 -0
- package/src/__tests__/simplified-memory-runtime.test.ts +616 -0
- package/src/__tests__/skill-load-inline-command.test.ts +598 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
- package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
- package/src/__tests__/skills-transitive-hash.test.ts +333 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +4 -4
- package/src/cli/commands/conversations.ts +18 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +8 -8
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/raw-config-utils.ts +28 -0
- package/src/config/schema.ts +12 -0
- package/src/config/schemas/memory-simplified.ts +101 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/skills.ts +50 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +8 -3
- package/src/daemon/conversation-agent-loop.ts +71 -1
- package/src/daemon/conversation-lifecycle.ts +11 -1
- package/src/daemon/conversation-memory.ts +117 -0
- package/src/daemon/conversation-runtime-assembly.ts +3 -1
- package/src/daemon/conversation-surfaces.ts +31 -8
- package/src/daemon/conversation.ts +40 -23
- package/src/daemon/handlers/config-embeddings.ts +10 -2
- package/src/daemon/handlers/config-model.ts +0 -9
- package/src/daemon/handlers/conversations.ts +11 -0
- package/src/daemon/handlers/identity.ts +12 -1
- package/src/daemon/lifecycle.ts +52 -1
- package/src/daemon/message-types/conversations.ts +0 -1
- package/src/daemon/server.ts +1 -1
- package/src/followups/followup-store.ts +47 -1
- package/src/memory/archive-recall.ts +516 -0
- package/src/memory/archive-store.ts +400 -0
- package/src/memory/brief-formatting.ts +33 -0
- package/src/memory/brief-open-loops.ts +266 -0
- package/src/memory/brief-time.ts +162 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-crud.ts +455 -101
- package/src/memory/conversation-key-store.ts +33 -4
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +106 -15
- package/src/memory/job-handlers/backfill-simplified-memory.ts +462 -0
- package/src/memory/job-handlers/conversation-starters.ts +9 -3
- package/src/memory/job-handlers/embedding.test.ts +1 -0
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-handlers/reduce-conversation-memory.ts +229 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +8 -0
- package/src/memory/jobs-worker.ts +20 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +49 -14
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +9 -1
- package/src/memory/migrations/141-rename-verification-table.ts +8 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +7 -2
- package/src/memory/migrations/174-rename-thread-starters-table.ts +8 -0
- package/src/memory/migrations/185-memory-brief-state.ts +52 -0
- package/src/memory/migrations/186-memory-archive.ts +109 -0
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
- package/src/memory/migrations/188-schedule-quiet-flag.ts +13 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-scheduler.ts +242 -0
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +106 -0
- package/src/memory/reducer.ts +467 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/memory-archive.ts +121 -0
- package/src/memory/schema/memory-brief.ts +55 -0
- package/src/memory/search/semantic.ts +17 -4
- package/src/oauth/oauth-store.ts +3 -1
- package/src/permissions/checker.ts +89 -6
- package/src/permissions/defaults.ts +14 -0
- package/src/runtime/auth/route-policy.ts +10 -1
- package/src/runtime/routes/conversation-management-routes.ts +94 -2
- package/src/runtime/routes/conversation-query-routes.ts +7 -0
- package/src/runtime/routes/conversation-routes.ts +52 -5
- package/src/runtime/routes/guardian-bootstrap-routes.ts +19 -7
- package/src/runtime/routes/identity-routes.ts +2 -35
- package/src/runtime/routes/llm-context-normalization.ts +14 -1
- package/src/runtime/routes/memory-item-routes.ts +90 -5
- package/src/runtime/routes/secret-routes.ts +3 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/schedule/schedule-store.ts +28 -0
- package/src/schedule/scheduler.ts +6 -2
- package/src/skills/inline-command-expansions.ts +204 -0
- package/src/skills/inline-command-render.ts +127 -0
- package/src/skills/inline-command-runner.ts +242 -0
- package/src/skills/transitive-version-hash.ts +88 -0
- package/src/tasks/task-store.ts +43 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -1
- package/src/tools/filesystem/edit.ts +6 -1
- package/src/tools/filesystem/read.ts +6 -1
- package/src/tools/filesystem/write.ts +6 -1
- package/src/tools/memory/handlers.ts +129 -1
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +5 -1
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/skills/load.ts +140 -6
- package/src/util/platform.ts +18 -0
- package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +1 -1
- package/src/workspace/migrations/registry.ts +1 -1
package/src/tasks/task-store.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { desc, eq, inArray } from "drizzle-orm";
|
|
1
|
+
import { asc, desc, eq, inArray, or } from "drizzle-orm";
|
|
2
2
|
|
|
3
3
|
import { getDb } from "../memory/db.js";
|
|
4
4
|
import { taskRuns, tasks, workItems } from "../memory/schema.js";
|
|
@@ -139,3 +139,45 @@ export function getTaskRun(id: string): TaskRun | undefined {
|
|
|
139
139
|
const db = getDb();
|
|
140
140
|
return db.select().from(taskRuns).where(eq(taskRuns.id, id)).get();
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
// ── Brief Helpers ─────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Lightweight read-only projection of a work item used by the brief compiler.
|
|
147
|
+
* Avoids pulling the full WorkItem type with all its tool/approval fields.
|
|
148
|
+
*/
|
|
149
|
+
export interface ActionableWorkItem {
|
|
150
|
+
id: string;
|
|
151
|
+
taskId: string;
|
|
152
|
+
title: string;
|
|
153
|
+
status: string;
|
|
154
|
+
priorityTier: number;
|
|
155
|
+
updatedAt: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Return actionable work items — those that are queued, running, or
|
|
160
|
+
* awaiting review. Ordered by priority (high first) then most-recently-updated.
|
|
161
|
+
*/
|
|
162
|
+
export function getActionableWorkItems(): ActionableWorkItem[] {
|
|
163
|
+
const db = getDb();
|
|
164
|
+
return db
|
|
165
|
+
.select({
|
|
166
|
+
id: workItems.id,
|
|
167
|
+
taskId: workItems.taskId,
|
|
168
|
+
title: workItems.title,
|
|
169
|
+
status: workItems.status,
|
|
170
|
+
priorityTier: workItems.priorityTier,
|
|
171
|
+
updatedAt: workItems.updatedAt,
|
|
172
|
+
})
|
|
173
|
+
.from(workItems)
|
|
174
|
+
.where(
|
|
175
|
+
or(
|
|
176
|
+
eq(workItems.status, "queued"),
|
|
177
|
+
eq(workItems.status, "running"),
|
|
178
|
+
eq(workItems.status, "awaiting_review"),
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
.orderBy(asc(workItems.priorityTier), desc(workItems.updatedAt))
|
|
182
|
+
.all();
|
|
183
|
+
}
|
|
@@ -193,7 +193,7 @@ export class UsageTelemetryReporter {
|
|
|
193
193
|
const organizationId = getPlatformOrganizationId() || undefined;
|
|
194
194
|
const userId = getPlatformUserId() || undefined;
|
|
195
195
|
const payload = {
|
|
196
|
-
|
|
196
|
+
device_id: getDeviceId(),
|
|
197
197
|
assistant_id: assistantId,
|
|
198
198
|
app_version: APP_VERSION,
|
|
199
199
|
...(organizationId ? { organization_id: organizationId } : {}),
|
|
@@ -38,8 +38,13 @@ class FileEditTool implements Tool {
|
|
|
38
38
|
description:
|
|
39
39
|
"Replace all occurrences of old_string instead of requiring a unique match (default: false)",
|
|
40
40
|
},
|
|
41
|
+
activity: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description:
|
|
44
|
+
"Brief non-technical explanation of what you are doing and why, shown to the user as a status update.",
|
|
45
|
+
},
|
|
41
46
|
},
|
|
42
|
-
required: ["path", "old_string", "new_string"],
|
|
47
|
+
required: ["path", "old_string", "new_string", "activity"],
|
|
43
48
|
},
|
|
44
49
|
};
|
|
45
50
|
}
|
|
@@ -38,8 +38,13 @@ class FileReadTool implements Tool {
|
|
|
38
38
|
type: "number",
|
|
39
39
|
description: "Maximum number of lines to read",
|
|
40
40
|
},
|
|
41
|
+
activity: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description:
|
|
44
|
+
"Brief non-technical explanation of what you are doing and why, shown to the user as a status update.",
|
|
45
|
+
},
|
|
41
46
|
},
|
|
42
|
-
required: ["path"],
|
|
47
|
+
required: ["path", "activity"],
|
|
43
48
|
},
|
|
44
49
|
};
|
|
45
50
|
}
|
|
@@ -28,8 +28,13 @@ class FileWriteTool implements Tool {
|
|
|
28
28
|
type: "string",
|
|
29
29
|
description: "The content to write to the file",
|
|
30
30
|
},
|
|
31
|
+
activity: {
|
|
32
|
+
type: "string",
|
|
33
|
+
description:
|
|
34
|
+
"Brief non-technical explanation of what you are doing and why, shown to the user as a status update.",
|
|
35
|
+
},
|
|
31
36
|
},
|
|
32
|
-
required: ["path", "content"],
|
|
37
|
+
required: ["path", "content", "activity"],
|
|
33
38
|
},
|
|
34
39
|
};
|
|
35
40
|
}
|
|
@@ -2,6 +2,8 @@ import { and, eq, ne } from "drizzle-orm";
|
|
|
2
2
|
import { v4 as uuid } from "uuid";
|
|
3
3
|
|
|
4
4
|
import type { AssistantConfig } from "../../config/types.js";
|
|
5
|
+
import { buildArchiveRecall } from "../../memory/archive-recall.js";
|
|
6
|
+
import { insertObservation } from "../../memory/archive-store.js";
|
|
5
7
|
import { getDb } from "../../memory/db.js";
|
|
6
8
|
import { computeMemoryFingerprint } from "../../memory/fingerprint.js";
|
|
7
9
|
import { enqueueMemoryJob } from "../../memory/jobs-store.js";
|
|
@@ -18,7 +20,7 @@ const log = getLogger("memory-tools");
|
|
|
18
20
|
|
|
19
21
|
export async function handleMemorySave(
|
|
20
22
|
args: Record<string, unknown>,
|
|
21
|
-
|
|
23
|
+
config: AssistantConfig,
|
|
22
24
|
conversationId: string,
|
|
23
25
|
messageId: string | undefined,
|
|
24
26
|
scopeId: string = "default",
|
|
@@ -63,6 +65,19 @@ export async function handleMemorySave(
|
|
|
63
65
|
? truncate(args.subject.trim(), 80, "")
|
|
64
66
|
: inferSubjectFromStatement(statement.trim());
|
|
65
67
|
|
|
68
|
+
// When simplified memory is enabled, save directly to the simplified
|
|
69
|
+
// observation/chunk tables instead of the legacy memory_items table.
|
|
70
|
+
if (config.memory.simplified.enabled) {
|
|
71
|
+
return handleSimplifiedMemorySave(
|
|
72
|
+
kind,
|
|
73
|
+
subject,
|
|
74
|
+
statement.trim(),
|
|
75
|
+
conversationId,
|
|
76
|
+
messageId,
|
|
77
|
+
scopeId,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
try {
|
|
67
82
|
const db = getDb();
|
|
68
83
|
const id = uuid();
|
|
@@ -275,6 +290,12 @@ export async function handleMemoryRecall(
|
|
|
275
290
|
? args.scope.trim()
|
|
276
291
|
: "default";
|
|
277
292
|
|
|
293
|
+
// When simplified memory is enabled, use the archive recall path
|
|
294
|
+
// instead of the legacy hybrid retriever.
|
|
295
|
+
if (config.memory.simplified.enabled) {
|
|
296
|
+
return handleSimplifiedMemoryRecall(query.trim(), scopeId ?? "default");
|
|
297
|
+
}
|
|
298
|
+
|
|
278
299
|
// Scope policy: "conversation" means strict (only that scope),
|
|
279
300
|
// anything else allows fallback to the default scope.
|
|
280
301
|
const scopePolicyOverride: ScopePolicyOverride | undefined = scopeId
|
|
@@ -411,6 +432,113 @@ export async function handleMemoryDelete(
|
|
|
411
432
|
}
|
|
412
433
|
}
|
|
413
434
|
|
|
435
|
+
// ── Simplified memory helpers ────────────────────────────────────────
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Save a memory item as an observation + chunk in the simplified system.
|
|
439
|
+
* This is used when simplified memory is enabled instead of writing to
|
|
440
|
+
* the legacy memory_items table.
|
|
441
|
+
*/
|
|
442
|
+
function handleSimplifiedMemorySave(
|
|
443
|
+
kind: string,
|
|
444
|
+
subject: string,
|
|
445
|
+
statement: string,
|
|
446
|
+
conversationId: string,
|
|
447
|
+
messageId: string | undefined,
|
|
448
|
+
scopeId: string,
|
|
449
|
+
): ToolExecutionResult {
|
|
450
|
+
try {
|
|
451
|
+
const trimmedStatement = truncate(statement, 500, "");
|
|
452
|
+
const content = `[${kind}] ${subject}: ${trimmedStatement}`;
|
|
453
|
+
|
|
454
|
+
const result = insertObservation({
|
|
455
|
+
conversationId,
|
|
456
|
+
messageId: messageId ?? null,
|
|
457
|
+
role: "user",
|
|
458
|
+
content,
|
|
459
|
+
scopeId,
|
|
460
|
+
modality: "text",
|
|
461
|
+
source: "tool:memory_save",
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
log.debug(
|
|
465
|
+
{
|
|
466
|
+
observationId: result.observationId,
|
|
467
|
+
chunkId: result.chunkId,
|
|
468
|
+
kind,
|
|
469
|
+
subject,
|
|
470
|
+
conversationId,
|
|
471
|
+
messageId,
|
|
472
|
+
},
|
|
473
|
+
"Memory saved via simplified system",
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
content: `Saved to memory (ID: ${result.observationId}).\nKind: ${kind}\nSubject: ${subject}\nStatement: ${trimmedStatement}`,
|
|
478
|
+
isError: false,
|
|
479
|
+
};
|
|
480
|
+
} catch (err) {
|
|
481
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
482
|
+
log.error({ err }, "simplified memory_save failed");
|
|
483
|
+
return { content: `Error: Failed to save memory: ${msg}`, isError: true };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Recall memories using the simplified archive recall path instead of
|
|
489
|
+
* the legacy hybrid retriever.
|
|
490
|
+
*/
|
|
491
|
+
function handleSimplifiedMemoryRecall(
|
|
492
|
+
query: string,
|
|
493
|
+
scopeId: string,
|
|
494
|
+
): ToolExecutionResult {
|
|
495
|
+
try {
|
|
496
|
+
const recallResult = buildArchiveRecall(scopeId, query);
|
|
497
|
+
|
|
498
|
+
if (recallResult.bullets.length === 0) {
|
|
499
|
+
return {
|
|
500
|
+
content: JSON.stringify({
|
|
501
|
+
text: "No matching memories found.",
|
|
502
|
+
resultCount: 0,
|
|
503
|
+
degraded: false,
|
|
504
|
+
items: [],
|
|
505
|
+
sources: { semantic: 0, recency: 0 },
|
|
506
|
+
}),
|
|
507
|
+
isError: false,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const items = recallResult.bullets.map((b) => ({
|
|
512
|
+
id: b.sourceId,
|
|
513
|
+
type: b.source,
|
|
514
|
+
kind: b.source,
|
|
515
|
+
}));
|
|
516
|
+
|
|
517
|
+
const result = {
|
|
518
|
+
text: recallResult.text,
|
|
519
|
+
resultCount: recallResult.bullets.length,
|
|
520
|
+
degraded: false,
|
|
521
|
+
items,
|
|
522
|
+
sources: {
|
|
523
|
+
semantic: recallResult.prefetchHitCount,
|
|
524
|
+
recency: 0,
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
content: JSON.stringify(result),
|
|
530
|
+
isError: false,
|
|
531
|
+
};
|
|
532
|
+
} catch (err) {
|
|
533
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
534
|
+
log.error({ err, query }, "simplified memory_recall failed");
|
|
535
|
+
return {
|
|
536
|
+
content: `Error: Memory recall failed: ${msg}`,
|
|
537
|
+
isError: true,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
414
542
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
415
543
|
|
|
416
544
|
function inferSubjectFromStatement(statement: string): string {
|
|
@@ -142,10 +142,17 @@ export class PermissionChecker {
|
|
|
142
142
|
// is the owner - prompting makes no sense when there is no client.
|
|
143
143
|
// Exception: requireFreshApproval tools cannot be auto-approved -
|
|
144
144
|
// without a human present, bundle installation must be denied.
|
|
145
|
+
// Exception: inline-command skill loads (skill_load_dynamic:*) must
|
|
146
|
+
// never be silently auto-approved — they execute embedded commands
|
|
147
|
+
// and require explicit human review or a pinned trust rule.
|
|
148
|
+
const isDynamicSkillLoad =
|
|
149
|
+
result.matchedRule?.pattern.startsWith("skill_load_dynamic:") ===
|
|
150
|
+
true;
|
|
145
151
|
if (
|
|
146
152
|
context.isInteractive === false &&
|
|
147
153
|
context.trustClass === "guardian" &&
|
|
148
|
-
!context.requireFreshApproval
|
|
154
|
+
!context.requireFreshApproval &&
|
|
155
|
+
!isDynamicSkillLoad
|
|
149
156
|
) {
|
|
150
157
|
log.info(
|
|
151
158
|
{ toolName: name, riskLevel },
|
|
@@ -41,6 +41,7 @@ export async function executeScheduleCreate(
|
|
|
41
41
|
const routingHints = input.routing_hints as
|
|
42
42
|
| Record<string, unknown>
|
|
43
43
|
| undefined;
|
|
44
|
+
const quiet = (input.quiet as boolean) ?? false;
|
|
44
45
|
|
|
45
46
|
if (!name || typeof name !== "string") {
|
|
46
47
|
return {
|
|
@@ -112,6 +113,7 @@ export async function executeScheduleCreate(
|
|
|
112
113
|
mode,
|
|
113
114
|
routingIntent: routingIntent as RoutingIntent | undefined,
|
|
114
115
|
routingHints,
|
|
116
|
+
quiet,
|
|
115
117
|
});
|
|
116
118
|
|
|
117
119
|
const fireDate = formatLocalDate(job.nextRunAt);
|
|
@@ -187,6 +189,7 @@ export async function executeScheduleCreate(
|
|
|
187
189
|
mode,
|
|
188
190
|
routingIntent: routingIntent as RoutingIntent | undefined,
|
|
189
191
|
routingHints,
|
|
192
|
+
quiet,
|
|
190
193
|
});
|
|
191
194
|
|
|
192
195
|
const scheduleDescription =
|
|
@@ -62,7 +62,11 @@ export async function executeScheduleList(
|
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
lines.push(
|
|
65
|
+
lines.push(
|
|
66
|
+
` Enabled: ${job.enabled}`,
|
|
67
|
+
` Quiet: ${job.quiet}`,
|
|
68
|
+
` Message: ${job.message}`,
|
|
69
|
+
);
|
|
66
70
|
|
|
67
71
|
if (!oneShot) {
|
|
68
72
|
lines.push(` Next run: ${formatLocalDate(job.nextRunAt)}`);
|
|
@@ -97,6 +97,11 @@ export async function executeScheduleUpdate(
|
|
|
97
97
|
updates.routingHints = input.routing_hints;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// Quiet mode
|
|
101
|
+
if (input.quiet !== undefined) {
|
|
102
|
+
updates.quiet = input.quiet;
|
|
103
|
+
}
|
|
104
|
+
|
|
100
105
|
// Auto-detect syntax when expression changes without explicit syntax
|
|
101
106
|
if (input.expression !== undefined || input.syntax !== undefined) {
|
|
102
107
|
const resolved = normalizeScheduleSyntax({
|
|
@@ -159,6 +164,7 @@ export async function executeScheduleUpdate(
|
|
|
159
164
|
mode?: ScheduleMode;
|
|
160
165
|
routingIntent?: RoutingIntent;
|
|
161
166
|
routingHints?: Record<string, unknown>;
|
|
167
|
+
quiet?: boolean;
|
|
162
168
|
},
|
|
163
169
|
);
|
|
164
170
|
|
package/src/tools/skills/load.ts
CHANGED
|
@@ -21,12 +21,24 @@ import {
|
|
|
21
21
|
indexCatalogById,
|
|
22
22
|
validateIncludes,
|
|
23
23
|
} from "../../skills/include-graph.js";
|
|
24
|
+
import { renderInlineCommands } from "../../skills/inline-command-render.js";
|
|
24
25
|
import { parseToolManifestFile } from "../../skills/tool-manifest.js";
|
|
25
26
|
import { computeSkillVersionHash } from "../../skills/version-hash.js";
|
|
26
27
|
import { getLogger } from "../../util/logger.js";
|
|
28
|
+
import { getWorkspaceDirDisplay } from "../../util/platform.js";
|
|
27
29
|
import { registerTool } from "../registry.js";
|
|
28
30
|
import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
29
31
|
|
|
32
|
+
/** Canonical feature flag key for inline skill command expansion. */
|
|
33
|
+
const INLINE_COMMANDS_FLAG_KEY = "feature_flags.inline-skill-commands.enabled";
|
|
34
|
+
|
|
35
|
+
/** Skill sources eligible for inline command expansion in v1. */
|
|
36
|
+
const INLINE_COMMAND_ELIGIBLE_SOURCES = new Set([
|
|
37
|
+
"bundled",
|
|
38
|
+
"managed",
|
|
39
|
+
"workspace",
|
|
40
|
+
]);
|
|
41
|
+
|
|
30
42
|
const log = getLogger("skill-load");
|
|
31
43
|
|
|
32
44
|
/**
|
|
@@ -76,7 +88,9 @@ function formatToolSchemas(
|
|
|
76
88
|
|
|
77
89
|
for (const tool of manifest.tools) {
|
|
78
90
|
lines.push(`${toolHeadingLevel} ${tool.name}`);
|
|
79
|
-
lines.push(
|
|
91
|
+
lines.push(
|
|
92
|
+
tool.description.replaceAll("{workspaceDir}", getWorkspaceDirDisplay()),
|
|
93
|
+
);
|
|
80
94
|
|
|
81
95
|
const schema = tool.input_schema;
|
|
82
96
|
const properties = schema.properties as
|
|
@@ -96,7 +110,7 @@ function formatToolSchemas(
|
|
|
96
110
|
: "optional";
|
|
97
111
|
const descPart =
|
|
98
112
|
typeof paramDef.description === "string"
|
|
99
|
-
? `: ${paramDef.description}`
|
|
113
|
+
? `: ${paramDef.description.replaceAll("{workspaceDir}", getWorkspaceDirDisplay())}`
|
|
100
114
|
: "";
|
|
101
115
|
lines.push(
|
|
102
116
|
`- ${paramName} (${paramType}, ${requiredLabel})${descPart}`,
|
|
@@ -113,7 +127,7 @@ function formatToolSchemas(
|
|
|
113
127
|
export class SkillLoadTool implements Tool {
|
|
114
128
|
name = "skill_load";
|
|
115
129
|
description =
|
|
116
|
-
"Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and workspace skills
|
|
130
|
+
"Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and custom workspace skills.";
|
|
117
131
|
category = "skills";
|
|
118
132
|
defaultRiskLevel = RiskLevel.Low;
|
|
119
133
|
|
|
@@ -136,7 +150,7 @@ export class SkillLoadTool implements Tool {
|
|
|
136
150
|
|
|
137
151
|
async execute(
|
|
138
152
|
input: Record<string, unknown>,
|
|
139
|
-
|
|
153
|
+
context: ToolContext,
|
|
140
154
|
): Promise<ToolExecutionResult> {
|
|
141
155
|
const selector = input.skill;
|
|
142
156
|
if (typeof selector !== "string" || selector.trim().length === 0) {
|
|
@@ -279,7 +293,63 @@ export class SkillLoadTool implements Tool {
|
|
|
279
293
|
}
|
|
280
294
|
}
|
|
281
295
|
|
|
282
|
-
|
|
296
|
+
let body = skill.body.length > 0 ? skill.body : "(No body content)";
|
|
297
|
+
|
|
298
|
+
// ── Inline command expansion ──────────────────────────────────────────
|
|
299
|
+
const hasInlineCommands =
|
|
300
|
+
skill.inlineCommandExpansions && skill.inlineCommandExpansions.length > 0;
|
|
301
|
+
|
|
302
|
+
if (hasInlineCommands) {
|
|
303
|
+
const inlineFlagEnabled = isAssistantFeatureFlagEnabled(
|
|
304
|
+
INLINE_COMMANDS_FLAG_KEY,
|
|
305
|
+
config,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
if (!inlineFlagEnabled) {
|
|
309
|
+
// Feature flag is off: fail closed instead of leaving live tokens in
|
|
310
|
+
// the prompt that the LLM might try to interpret.
|
|
311
|
+
return {
|
|
312
|
+
content: `Error: skill "${skill.id}" contains inline command expansions but the inline-skill-commands feature flag is disabled. Enable the flag to use this skill.`,
|
|
313
|
+
isError: true,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (skill.source === "extra") {
|
|
318
|
+
// Third-party extra roots are out of scope for inline command
|
|
319
|
+
// expansion in v1. Reject explicitly so the failure is clear.
|
|
320
|
+
return {
|
|
321
|
+
content: `Error: skill "${skill.id}" contains inline command expansions but inline commands are not supported for third-party (extra) skill sources.`,
|
|
322
|
+
isError: true,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!INLINE_COMMAND_ELIGIBLE_SOURCES.has(skill.source)) {
|
|
327
|
+
// Defensive: reject any other unknown sources that somehow have
|
|
328
|
+
// inline commands. Should not happen with current SkillSource values,
|
|
329
|
+
// but fail closed if a new source type is added without updating this.
|
|
330
|
+
return {
|
|
331
|
+
content: `Error: skill "${skill.id}" contains inline command expansions but source "${skill.source}" is not eligible for inline command expansion.`,
|
|
332
|
+
isError: true,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Render inline commands by executing each through the sandbox runner
|
|
337
|
+
const renderResult = await renderInlineCommands(
|
|
338
|
+
body,
|
|
339
|
+
skill.inlineCommandExpansions!,
|
|
340
|
+
context.workingDir,
|
|
341
|
+
);
|
|
342
|
+
body = renderResult.renderedBody;
|
|
343
|
+
|
|
344
|
+
log.info(
|
|
345
|
+
{
|
|
346
|
+
skillId: skill.id,
|
|
347
|
+
expandedCount: renderResult.expandedCount,
|
|
348
|
+
failedCount: renderResult.failedCount,
|
|
349
|
+
},
|
|
350
|
+
"Rendered inline command expansions",
|
|
351
|
+
);
|
|
352
|
+
}
|
|
283
353
|
|
|
284
354
|
// Build reference file listing (if any)
|
|
285
355
|
const referenceListing = listReferenceFiles(skill.directoryPath);
|
|
@@ -313,8 +383,72 @@ export class SkillLoadTool implements Tool {
|
|
|
313
383
|
// Load the included skill's body content
|
|
314
384
|
const childLoaded = loadSkillBySelector(childId);
|
|
315
385
|
if (childLoaded.skill && childLoaded.skill.body.length > 0) {
|
|
386
|
+
let childBody = childLoaded.skill.body;
|
|
387
|
+
|
|
388
|
+
// ── Inline command expansion for included child skill ─────────
|
|
389
|
+
const childHasInlineCommands =
|
|
390
|
+
childLoaded.skill.inlineCommandExpansions &&
|
|
391
|
+
childLoaded.skill.inlineCommandExpansions.length > 0;
|
|
392
|
+
|
|
393
|
+
if (childHasInlineCommands) {
|
|
394
|
+
const childInlineFlagEnabled = isAssistantFeatureFlagEnabled(
|
|
395
|
+
INLINE_COMMANDS_FLAG_KEY,
|
|
396
|
+
config,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Fail closed: if the flag is off, reject the entire skill_load
|
|
400
|
+
// just like we do for root skills. Leaving raw !`...` tokens in
|
|
401
|
+
// the prompt would violate the documented fail-closed contract.
|
|
402
|
+
if (!childInlineFlagEnabled) {
|
|
403
|
+
return {
|
|
404
|
+
content: `Error: included skill "${childId}" contains inline command expansions but the inline-skill-commands feature flag is disabled. Enable the flag to use skill "${skill.id}".`,
|
|
405
|
+
isError: true,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (childLoaded.skill.source === "extra") {
|
|
410
|
+
return {
|
|
411
|
+
content: `Error: included skill "${childId}" contains inline command expansions but inline commands are not supported for third-party (extra) skill sources.`,
|
|
412
|
+
isError: true,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (
|
|
417
|
+
!INLINE_COMMAND_ELIGIBLE_SOURCES.has(childLoaded.skill.source)
|
|
418
|
+
) {
|
|
419
|
+
return {
|
|
420
|
+
content: `Error: included skill "${childId}" contains inline command expansions but source "${childLoaded.skill.source}" is not eligible for inline command expansion.`,
|
|
421
|
+
isError: true,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
const childRenderResult = await renderInlineCommands(
|
|
427
|
+
childBody,
|
|
428
|
+
childLoaded.skill.inlineCommandExpansions!,
|
|
429
|
+
context.workingDir,
|
|
430
|
+
);
|
|
431
|
+
childBody = childRenderResult.renderedBody;
|
|
432
|
+
|
|
433
|
+
log.info(
|
|
434
|
+
{
|
|
435
|
+
skillId: childId,
|
|
436
|
+
parentSkillId: skill.id,
|
|
437
|
+
expandedCount: childRenderResult.expandedCount,
|
|
438
|
+
failedCount: childRenderResult.failedCount,
|
|
439
|
+
},
|
|
440
|
+
"Rendered inline command expansions for included skill",
|
|
441
|
+
);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
log.warn(
|
|
444
|
+
{ err, skillId: childId, parentSkillId: skill.id },
|
|
445
|
+
"Failed to render inline commands for included skill, using raw body",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
316
450
|
includedBodies.push(
|
|
317
|
-
`--- Included Skill: ${childLoaded.skill.displayName} (${childId}) ---\n${
|
|
451
|
+
`--- Included Skill: ${childLoaded.skill.displayName} (${childId}) ---\n${childBody}`,
|
|
318
452
|
);
|
|
319
453
|
|
|
320
454
|
// List reference files for the included skill
|
package/src/util/platform.ts
CHANGED
|
@@ -366,6 +366,24 @@ export function getWorkspaceDir(): string {
|
|
|
366
366
|
return join(getRootDir(), "workspace");
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Returns a display-friendly workspace path for embedding in agent-facing text
|
|
371
|
+
* (skill bodies, tool descriptions). Replaces the home directory prefix with `~`
|
|
372
|
+
* so paths stay concise and portable across machines.
|
|
373
|
+
*
|
|
374
|
+
* Examples:
|
|
375
|
+
* /Users/sidd/.vellum/workspace → ~/.vellum/workspace
|
|
376
|
+
* /data/.vellum/workspace → /data/.vellum/workspace
|
|
377
|
+
*/
|
|
378
|
+
export function getWorkspaceDirDisplay(): string {
|
|
379
|
+
const abs = getWorkspaceDir();
|
|
380
|
+
const home = homedir();
|
|
381
|
+
if (abs.startsWith(home + "/") || abs === home) {
|
|
382
|
+
return "~" + abs.slice(home.length);
|
|
383
|
+
}
|
|
384
|
+
return abs;
|
|
385
|
+
}
|
|
386
|
+
|
|
369
387
|
/** Returns ~/.vellum/workspace/config.json */
|
|
370
388
|
export function getWorkspaceConfigPath(): string {
|
|
371
389
|
return join(getWorkspaceDir(), "config.json");
|
package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts}
RENAMED
|
@@ -11,7 +11,7 @@ import { getExternalAssistantId } from "../../runtime/auth/external-assistant-id
|
|
|
11
11
|
import type { WorkspaceMigration } from "./types.js";
|
|
12
12
|
|
|
13
13
|
export const backfillInstallationIdMigration: WorkspaceMigration = {
|
|
14
|
-
id: "
|
|
14
|
+
id: "011-backfill-installation-id",
|
|
15
15
|
description:
|
|
16
16
|
"Backfill installationId into lockfile from SQLite checkpoint and clean up stale row",
|
|
17
17
|
run(_workspaceDir: string): void {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { avatarRenameMigration } from "./001-avatar-rename.js";
|
|
2
|
-
import { backfillInstallationIdMigration } from "./002-backfill-installation-id.js";
|
|
3
2
|
import { seedDeviceIdMigration } from "./003-seed-device-id.js";
|
|
4
3
|
import { extractCollectUsageDataMigration } from "./004-extract-collect-usage-data.js";
|
|
5
4
|
import { addSendDiagnosticsMigration } from "./005-add-send-diagnostics.js";
|
|
@@ -8,6 +7,7 @@ import { webSearchProviderRenameMigration } from "./007-web-search-provider-rena
|
|
|
8
7
|
import { voiceTimeoutAndMaxStepsMigration } from "./008-voice-timeout-and-max-steps.js";
|
|
9
8
|
import { backfillConversationDiskViewMigration } from "./009-backfill-conversation-disk-view.js";
|
|
10
9
|
import { appDirRenameMigration } from "./010-app-dir-rename.js";
|
|
10
|
+
import { backfillInstallationIdMigration } from "./011-backfill-installation-id.js";
|
|
11
11
|
import { renameConversationDiskViewDirsMigration } from "./012-rename-conversation-disk-view-dirs.js";
|
|
12
12
|
import { repairConversationDiskViewMigration } from "./013-repair-conversation-disk-view.js";
|
|
13
13
|
import type { WorkspaceMigration } from "./types.js";
|