@vellumai/assistant 0.10.3-dev.202606291709.2b4d83d → 0.10.3-dev.202606291850.7e0a3f7
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/openapi.yaml +246 -6
- package/package.json +1 -1
- package/src/__tests__/clawhub.test.ts +4 -0
- package/src/__tests__/conversation-app-control-instantiation.test.ts +1 -0
- package/src/__tests__/conversation-skill-tools.test.ts +153 -0
- package/src/__tests__/injector-v3-suppression.test.ts +5 -1
- package/src/__tests__/install-meta.test.ts +104 -0
- package/src/__tests__/managed-store.test.ts +307 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +8 -3
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +489 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +1 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +1 -0
- package/src/__tests__/skill-version-hash.test.ts +32 -0
- package/src/__tests__/slim-skill-category.test.ts +33 -1
- package/src/__tests__/tool-executor.test.ts +22 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +1 -0
- package/src/cli/lib/install-from-github.ts +11 -0
- package/src/config/__tests__/proc-to-skills.test.ts +21 -0
- package/src/config/bundled-skills/skill-management/TOOLS.json +45 -0
- package/src/config/bundled-skills/skill-management/tools/find-similar.ts +12 -0
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/memory-v3-gate.ts +16 -0
- package/src/config/schemas/memory-lifecycle.ts +9 -0
- package/src/daemon/conversation-agent-loop.ts +17 -0
- package/src/daemon/conversation-skill-tools.ts +25 -1
- package/src/daemon/conversation-tool-setup.ts +1 -0
- package/src/daemon/conversation.ts +1 -0
- package/src/daemon/daemon-status.ts +31 -0
- package/src/daemon/handlers/skills.ts +8 -0
- package/src/daemon/lifecycle.ts +17 -14
- package/src/daemon/message-types/skills.ts +20 -2
- package/src/daemon/process-message.ts +12 -0
- package/src/daemon/shutdown-handlers.ts +9 -9
- package/src/daemon/tool-setup-types.ts +17 -0
- package/src/daemon/wake-conversation-ops.ts +8 -1
- package/src/memory/__tests__/memory-retrospective-job.test.ts +114 -2
- package/src/memory/memory-retrospective-constants.ts +10 -0
- package/src/memory/memory-retrospective-job.ts +71 -9
- package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +17 -1
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +20 -1
- package/src/memory/v2/prompts/consolidation.ts +19 -8
- package/src/permissions/checker.test.ts +319 -1
- package/src/permissions/checker.ts +52 -0
- package/src/permissions/types.ts +21 -0
- package/src/persistence/embeddings/embed.ts +19 -5
- package/src/persistence/jobs-worker.ts +1 -0
- package/src/platform/sync-identity.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/candidate-match.test.ts +316 -0
- package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +263 -3
- package/src/plugins/defaults/memory/v3/candidate-match.ts +197 -0
- package/src/plugins/defaults/memory/v3/maintain-job.ts +205 -3
- package/src/runtime/__tests__/background-job-runner.test.ts +17 -0
- package/src/runtime/background-job-runner.ts +11 -0
- package/src/runtime/routes/channel-readiness-routes.ts +7 -3
- package/src/runtime/routes/skills-routes.ts +11 -1
- package/src/skills/catalog-install.ts +1 -0
- package/src/skills/clawhub.ts +1 -0
- package/src/skills/install-meta.ts +44 -0
- package/src/skills/managed-store.ts +100 -2
- package/src/skills/skillssh-registry.ts +1 -0
- package/src/skills/version-hash.ts +20 -0
- package/src/tools/policy-context.ts +21 -0
- package/src/tools/skills/find-similar-skills.test.ts +277 -0
- package/src/tools/skills/find-similar-skills.ts +120 -0
- package/src/tools/skills/scaffold-managed.ts +123 -4
- package/src/tools/types.ts +11 -0
- package/src/daemon/server.ts +0 -89
package/openapi.yaml
CHANGED
|
@@ -4152,6 +4152,15 @@ paths:
|
|
|
4152
4152
|
properties:
|
|
4153
4153
|
channel:
|
|
4154
4154
|
type: string
|
|
4155
|
+
enum:
|
|
4156
|
+
- telegram
|
|
4157
|
+
- phone
|
|
4158
|
+
- vellum
|
|
4159
|
+
- whatsapp
|
|
4160
|
+
- slack
|
|
4161
|
+
- email
|
|
4162
|
+
- platform
|
|
4163
|
+
- a2a
|
|
4155
4164
|
ready:
|
|
4156
4165
|
type: boolean
|
|
4157
4166
|
setupStatus:
|
|
@@ -4230,14 +4239,20 @@ paths:
|
|
|
4230
4239
|
type: object
|
|
4231
4240
|
properties:
|
|
4232
4241
|
channel:
|
|
4233
|
-
type: string
|
|
4234
4242
|
description: Optional channel ID to refresh
|
|
4243
|
+
type: string
|
|
4244
|
+
enum:
|
|
4245
|
+
- telegram
|
|
4246
|
+
- phone
|
|
4247
|
+
- vellum
|
|
4248
|
+
- whatsapp
|
|
4249
|
+
- slack
|
|
4250
|
+
- email
|
|
4251
|
+
- platform
|
|
4252
|
+
- a2a
|
|
4235
4253
|
includeRemote:
|
|
4236
|
-
type: boolean
|
|
4237
4254
|
description: Include remote checks (default true)
|
|
4238
|
-
|
|
4239
|
-
- channel
|
|
4240
|
-
- includeRemote
|
|
4255
|
+
type: boolean
|
|
4241
4256
|
responses:
|
|
4242
4257
|
"200":
|
|
4243
4258
|
description: Successful response
|
|
@@ -4255,6 +4270,15 @@ paths:
|
|
|
4255
4270
|
properties:
|
|
4256
4271
|
channel:
|
|
4257
4272
|
type: string
|
|
4273
|
+
enum:
|
|
4274
|
+
- telegram
|
|
4275
|
+
- phone
|
|
4276
|
+
- vellum
|
|
4277
|
+
- whatsapp
|
|
4278
|
+
- slack
|
|
4279
|
+
- email
|
|
4280
|
+
- platform
|
|
4281
|
+
- a2a
|
|
4258
4282
|
ready:
|
|
4259
4283
|
type: boolean
|
|
4260
4284
|
setupStatus:
|
|
@@ -24289,7 +24313,7 @@ paths:
|
|
|
24289
24313
|
required: false
|
|
24290
24314
|
schema:
|
|
24291
24315
|
type: string
|
|
24292
|
-
description: Filter by skill origin (e.g. 'vellum', 'clawhub', 'skillssh', 'custom').
|
|
24316
|
+
description: Filter by skill origin (e.g. 'vellum', 'clawhub', 'skillssh', 'custom', 'assistant-memory').
|
|
24293
24317
|
- name: kind
|
|
24294
24318
|
in: query
|
|
24295
24319
|
required: false
|
|
@@ -24591,6 +24615,60 @@ paths:
|
|
|
24591
24615
|
- category
|
|
24592
24616
|
- origin
|
|
24593
24617
|
additionalProperties: false
|
|
24618
|
+
- type: object
|
|
24619
|
+
properties:
|
|
24620
|
+
id:
|
|
24621
|
+
type: string
|
|
24622
|
+
name:
|
|
24623
|
+
type: string
|
|
24624
|
+
description:
|
|
24625
|
+
type: string
|
|
24626
|
+
icon:
|
|
24627
|
+
type: string
|
|
24628
|
+
emoji:
|
|
24629
|
+
type: string
|
|
24630
|
+
kind:
|
|
24631
|
+
type: string
|
|
24632
|
+
enum:
|
|
24633
|
+
- bundled
|
|
24634
|
+
- installed
|
|
24635
|
+
- catalog
|
|
24636
|
+
status:
|
|
24637
|
+
type: string
|
|
24638
|
+
enum:
|
|
24639
|
+
- enabled
|
|
24640
|
+
- disabled
|
|
24641
|
+
- available
|
|
24642
|
+
category:
|
|
24643
|
+
type: string
|
|
24644
|
+
owner:
|
|
24645
|
+
type: object
|
|
24646
|
+
properties:
|
|
24647
|
+
kind:
|
|
24648
|
+
type: string
|
|
24649
|
+
enum:
|
|
24650
|
+
- skill
|
|
24651
|
+
- mcp
|
|
24652
|
+
- plugin
|
|
24653
|
+
- workspace
|
|
24654
|
+
id:
|
|
24655
|
+
type: string
|
|
24656
|
+
required:
|
|
24657
|
+
- kind
|
|
24658
|
+
- id
|
|
24659
|
+
additionalProperties: false
|
|
24660
|
+
origin:
|
|
24661
|
+
type: string
|
|
24662
|
+
const: assistant-memory
|
|
24663
|
+
required:
|
|
24664
|
+
- id
|
|
24665
|
+
- name
|
|
24666
|
+
- description
|
|
24667
|
+
- kind
|
|
24668
|
+
- status
|
|
24669
|
+
- category
|
|
24670
|
+
- origin
|
|
24671
|
+
additionalProperties: false
|
|
24594
24672
|
type: object
|
|
24595
24673
|
description: Skill objects
|
|
24596
24674
|
categoryCounts:
|
|
@@ -24976,6 +25054,60 @@ paths:
|
|
|
24976
25054
|
- category
|
|
24977
25055
|
- origin
|
|
24978
25056
|
additionalProperties: false
|
|
25057
|
+
- type: object
|
|
25058
|
+
properties:
|
|
25059
|
+
id:
|
|
25060
|
+
type: string
|
|
25061
|
+
name:
|
|
25062
|
+
type: string
|
|
25063
|
+
description:
|
|
25064
|
+
type: string
|
|
25065
|
+
icon:
|
|
25066
|
+
type: string
|
|
25067
|
+
emoji:
|
|
25068
|
+
type: string
|
|
25069
|
+
kind:
|
|
25070
|
+
type: string
|
|
25071
|
+
enum:
|
|
25072
|
+
- bundled
|
|
25073
|
+
- installed
|
|
25074
|
+
- catalog
|
|
25075
|
+
status:
|
|
25076
|
+
type: string
|
|
25077
|
+
enum:
|
|
25078
|
+
- enabled
|
|
25079
|
+
- disabled
|
|
25080
|
+
- available
|
|
25081
|
+
category:
|
|
25082
|
+
type: string
|
|
25083
|
+
owner:
|
|
25084
|
+
type: object
|
|
25085
|
+
properties:
|
|
25086
|
+
kind:
|
|
25087
|
+
type: string
|
|
25088
|
+
enum:
|
|
25089
|
+
- skill
|
|
25090
|
+
- mcp
|
|
25091
|
+
- plugin
|
|
25092
|
+
- workspace
|
|
25093
|
+
id:
|
|
25094
|
+
type: string
|
|
25095
|
+
required:
|
|
25096
|
+
- kind
|
|
25097
|
+
- id
|
|
25098
|
+
additionalProperties: false
|
|
25099
|
+
origin:
|
|
25100
|
+
type: string
|
|
25101
|
+
const: assistant-memory
|
|
25102
|
+
required:
|
|
25103
|
+
- id
|
|
25104
|
+
- name
|
|
25105
|
+
- description
|
|
25106
|
+
- kind
|
|
25107
|
+
- status
|
|
25108
|
+
- category
|
|
25109
|
+
- origin
|
|
25110
|
+
additionalProperties: false
|
|
24979
25111
|
description: Skill detail object
|
|
24980
25112
|
type: object
|
|
24981
25113
|
required:
|
|
@@ -25376,6 +25508,60 @@ paths:
|
|
|
25376
25508
|
- category
|
|
25377
25509
|
- origin
|
|
25378
25510
|
additionalProperties: false
|
|
25511
|
+
- type: object
|
|
25512
|
+
properties:
|
|
25513
|
+
id:
|
|
25514
|
+
type: string
|
|
25515
|
+
name:
|
|
25516
|
+
type: string
|
|
25517
|
+
description:
|
|
25518
|
+
type: string
|
|
25519
|
+
icon:
|
|
25520
|
+
type: string
|
|
25521
|
+
emoji:
|
|
25522
|
+
type: string
|
|
25523
|
+
kind:
|
|
25524
|
+
type: string
|
|
25525
|
+
enum:
|
|
25526
|
+
- bundled
|
|
25527
|
+
- installed
|
|
25528
|
+
- catalog
|
|
25529
|
+
status:
|
|
25530
|
+
type: string
|
|
25531
|
+
enum:
|
|
25532
|
+
- enabled
|
|
25533
|
+
- disabled
|
|
25534
|
+
- available
|
|
25535
|
+
category:
|
|
25536
|
+
type: string
|
|
25537
|
+
owner:
|
|
25538
|
+
type: object
|
|
25539
|
+
properties:
|
|
25540
|
+
kind:
|
|
25541
|
+
type: string
|
|
25542
|
+
enum:
|
|
25543
|
+
- skill
|
|
25544
|
+
- mcp
|
|
25545
|
+
- plugin
|
|
25546
|
+
- workspace
|
|
25547
|
+
id:
|
|
25548
|
+
type: string
|
|
25549
|
+
required:
|
|
25550
|
+
- kind
|
|
25551
|
+
- id
|
|
25552
|
+
additionalProperties: false
|
|
25553
|
+
origin:
|
|
25554
|
+
type: string
|
|
25555
|
+
const: assistant-memory
|
|
25556
|
+
required:
|
|
25557
|
+
- id
|
|
25558
|
+
- name
|
|
25559
|
+
- description
|
|
25560
|
+
- kind
|
|
25561
|
+
- status
|
|
25562
|
+
- category
|
|
25563
|
+
- origin
|
|
25564
|
+
additionalProperties: false
|
|
25379
25565
|
description: Skill metadata
|
|
25380
25566
|
type: object
|
|
25381
25567
|
files:
|
|
@@ -25963,6 +26149,60 @@ paths:
|
|
|
25963
26149
|
- category
|
|
25964
26150
|
- origin
|
|
25965
26151
|
additionalProperties: false
|
|
26152
|
+
- type: object
|
|
26153
|
+
properties:
|
|
26154
|
+
id:
|
|
26155
|
+
type: string
|
|
26156
|
+
name:
|
|
26157
|
+
type: string
|
|
26158
|
+
description:
|
|
26159
|
+
type: string
|
|
26160
|
+
icon:
|
|
26161
|
+
type: string
|
|
26162
|
+
emoji:
|
|
26163
|
+
type: string
|
|
26164
|
+
kind:
|
|
26165
|
+
type: string
|
|
26166
|
+
enum:
|
|
26167
|
+
- bundled
|
|
26168
|
+
- installed
|
|
26169
|
+
- catalog
|
|
26170
|
+
status:
|
|
26171
|
+
type: string
|
|
26172
|
+
enum:
|
|
26173
|
+
- enabled
|
|
26174
|
+
- disabled
|
|
26175
|
+
- available
|
|
26176
|
+
category:
|
|
26177
|
+
type: string
|
|
26178
|
+
owner:
|
|
26179
|
+
type: object
|
|
26180
|
+
properties:
|
|
26181
|
+
kind:
|
|
26182
|
+
type: string
|
|
26183
|
+
enum:
|
|
26184
|
+
- skill
|
|
26185
|
+
- mcp
|
|
26186
|
+
- plugin
|
|
26187
|
+
- workspace
|
|
26188
|
+
id:
|
|
26189
|
+
type: string
|
|
26190
|
+
required:
|
|
26191
|
+
- kind
|
|
26192
|
+
- id
|
|
26193
|
+
additionalProperties: false
|
|
26194
|
+
origin:
|
|
26195
|
+
type: string
|
|
26196
|
+
const: assistant-memory
|
|
26197
|
+
required:
|
|
26198
|
+
- id
|
|
26199
|
+
- name
|
|
26200
|
+
- description
|
|
26201
|
+
- kind
|
|
26202
|
+
- status
|
|
26203
|
+
- category
|
|
26204
|
+
- origin
|
|
26205
|
+
additionalProperties: false
|
|
25966
26206
|
type: object
|
|
25967
26207
|
description: Skill objects matching the search query
|
|
25968
26208
|
required:
|
package/package.json
CHANGED
|
@@ -130,6 +130,10 @@ describe("clawhubInstall staging", () => {
|
|
|
130
130
|
"# Staged Skill\n",
|
|
131
131
|
);
|
|
132
132
|
expect(existsSync(join(stagedSkillDir, "install-meta.json"))).toBe(true);
|
|
133
|
+
const meta = JSON.parse(
|
|
134
|
+
readFileSync(join(stagedSkillDir, "install-meta.json"), "utf-8"),
|
|
135
|
+
) as SkillInstallMeta;
|
|
136
|
+
expect(meta.author).toBe("user");
|
|
133
137
|
} finally {
|
|
134
138
|
Bun.spawn = originalSpawn;
|
|
135
139
|
rmSync(projectRoot, { recursive: true, force: true });
|
|
@@ -201,6 +201,7 @@ mock.module("../skills/catalog-cache.js", () => ({
|
|
|
201
201
|
}));
|
|
202
202
|
mock.module("../skills/install-meta.js", () => ({
|
|
203
203
|
readInstallMeta: () => null,
|
|
204
|
+
touchSkillLastUsed: () => false,
|
|
204
205
|
}));
|
|
205
206
|
mock.module("../telemetry/skill-loaded-events-store.js", () => ({
|
|
206
207
|
recordSkillLoadedEvent: () => {},
|
|
@@ -38,6 +38,8 @@ let recordedSkillLoadedEvents: SkillLoadedEventRecord[] = [];
|
|
|
38
38
|
let mockRecordSkillLoadedFailure = false;
|
|
39
39
|
/** Skill IDs for which registerSkillTools throws (simulates a different-owner tool name collision). */
|
|
40
40
|
let mockRegisterFailures: Set<string> = new Set();
|
|
41
|
+
/** `(skillDir, today)` pairs captured by the mocked touchSkillLastUsed. */
|
|
42
|
+
let recordedLastUsedTouches: Array<{ skillDir: string; today: string }> = [];
|
|
41
43
|
|
|
42
44
|
// ---------------------------------------------------------------------------
|
|
43
45
|
// Mocks — must be set up before importing the module under test
|
|
@@ -249,6 +251,10 @@ mock.module("../skills/install-meta.js", () => ({
|
|
|
249
251
|
const skillId = parts[parts.length - 1];
|
|
250
252
|
return mockInstallMetas[skillId] ?? null;
|
|
251
253
|
},
|
|
254
|
+
touchSkillLastUsed: (skillDir: string, today: string) => {
|
|
255
|
+
recordedLastUsedTouches.push({ skillDir, today });
|
|
256
|
+
return true;
|
|
257
|
+
},
|
|
252
258
|
}));
|
|
253
259
|
|
|
254
260
|
mock.module("../skills/catalog-cache.js", () => ({
|
|
@@ -1174,6 +1180,153 @@ describe("skill_loaded telemetry recording", () => {
|
|
|
1174
1180
|
});
|
|
1175
1181
|
});
|
|
1176
1182
|
|
|
1183
|
+
// ---------------------------------------------------------------------------
|
|
1184
|
+
// lastUsedAt stamping on managed-skill load
|
|
1185
|
+
// ---------------------------------------------------------------------------
|
|
1186
|
+
|
|
1187
|
+
describe("lastUsedAt stamping on managed-skill load", () => {
|
|
1188
|
+
let sessionState: Map<string, string>;
|
|
1189
|
+
|
|
1190
|
+
// The projection layer computes `today` from the local clock; mirror that
|
|
1191
|
+
// expression so the assertion stays correct regardless of the run date.
|
|
1192
|
+
const expectedToday = new Date().toLocaleDateString("en-CA");
|
|
1193
|
+
|
|
1194
|
+
beforeEach(() => {
|
|
1195
|
+
mockCatalog = [];
|
|
1196
|
+
mockManifests = {};
|
|
1197
|
+
mockRegisteredTools = new Map();
|
|
1198
|
+
mockUnregisteredSkillIds = [];
|
|
1199
|
+
mockSkillRefCount = new Map();
|
|
1200
|
+
mockVersionHashes = {};
|
|
1201
|
+
mockVersionHashErrors = new Set();
|
|
1202
|
+
mockInstallMetas = {};
|
|
1203
|
+
recordedSkillLoadedEvents = [];
|
|
1204
|
+
recordedLastUsedTouches = [];
|
|
1205
|
+
sessionState = new Map<string, string>();
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
test("managed skill load stamps lastUsedAt with today's date and its directory", () => {
|
|
1209
|
+
mockCatalog = [makeSkill("homemade")];
|
|
1210
|
+
mockManifests = { homemade: makeManifest(["hm_run"]) };
|
|
1211
|
+
mockInstallMetas = {
|
|
1212
|
+
homemade: { origin: "custom", installedAt: "2026-01-01T00:00:00Z" },
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
const history: Message[] = [
|
|
1216
|
+
...skillLoadMessages('<loaded_skill id="homemade" />'),
|
|
1217
|
+
];
|
|
1218
|
+
projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
|
|
1219
|
+
|
|
1220
|
+
expect(recordedLastUsedTouches).toEqual([
|
|
1221
|
+
{ skillDir: "/skills/homemade", today: expectedToday },
|
|
1222
|
+
]);
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
test("stamps managed skills even when telemetry is off (ungated)", () => {
|
|
1226
|
+
// origin: "custom" is suppressed for telemetry but must still be stamped.
|
|
1227
|
+
mockCatalog = [makeSkill("homemade")];
|
|
1228
|
+
mockManifests = { homemade: makeManifest(["hm_run"]) };
|
|
1229
|
+
mockInstallMetas = {
|
|
1230
|
+
homemade: { origin: "custom", installedAt: "2026-01-01T00:00:00Z" },
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
const history: Message[] = [
|
|
1234
|
+
...skillLoadMessages('<loaded_skill id="homemade" />'),
|
|
1235
|
+
];
|
|
1236
|
+
// No telemetry option passed.
|
|
1237
|
+
projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
|
|
1238
|
+
|
|
1239
|
+
expect(recordedSkillLoadedEvents).toHaveLength(0);
|
|
1240
|
+
expect(recordedLastUsedTouches).toHaveLength(1);
|
|
1241
|
+
expect(recordedLastUsedTouches[0].skillDir).toBe("/skills/homemade");
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
test("instruction-only managed skill (no TOOLS.json) is stamped", () => {
|
|
1245
|
+
mockCatalog = [makeSkill("catalog-skill")];
|
|
1246
|
+
// No manifest — instruction-only managed skill.
|
|
1247
|
+
mockInstallMetas = {
|
|
1248
|
+
"catalog-skill": {
|
|
1249
|
+
origin: "vellum",
|
|
1250
|
+
installedAt: "2026-01-01T00:00:00Z",
|
|
1251
|
+
},
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1254
|
+
const history: Message[] = [
|
|
1255
|
+
...skillLoadMessages('<loaded_skill id="catalog-skill" />'),
|
|
1256
|
+
];
|
|
1257
|
+
projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
|
|
1258
|
+
|
|
1259
|
+
expect(recordedLastUsedTouches).toEqual([
|
|
1260
|
+
{ skillDir: "/skills/catalog-skill", today: expectedToday },
|
|
1261
|
+
]);
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
test("bundled skills are NOT stamped", () => {
|
|
1265
|
+
mockCatalog = [makeSkill("notes", undefined, "bundled")];
|
|
1266
|
+
mockManifests = { notes: makeManifest(["notes_add"]) };
|
|
1267
|
+
|
|
1268
|
+
const history: Message[] = [
|
|
1269
|
+
...skillLoadMessages('<loaded_skill id="notes" />'),
|
|
1270
|
+
];
|
|
1271
|
+
projectSkillTools(history, {
|
|
1272
|
+
previouslyActiveSkillIds: sessionState,
|
|
1273
|
+
telemetry: { conversationId: "conv-xyz", attribution: null },
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
// Telemetry still fires for the bundled skill, but no usage stamp.
|
|
1277
|
+
expect(recordedSkillLoadedEvents).toHaveLength(1);
|
|
1278
|
+
expect(recordedLastUsedTouches).toHaveLength(0);
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
test("workspace skills are NOT stamped", () => {
|
|
1282
|
+
mockCatalog = [makeSkill("local-helper", undefined, "workspace")];
|
|
1283
|
+
mockManifests = { "local-helper": makeManifest(["lh_run"]) };
|
|
1284
|
+
|
|
1285
|
+
const history: Message[] = [
|
|
1286
|
+
...skillLoadMessages('<loaded_skill id="local-helper" />'),
|
|
1287
|
+
];
|
|
1288
|
+
projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
|
|
1289
|
+
|
|
1290
|
+
expect(recordedLastUsedTouches).toHaveLength(0);
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
test("a still-active skill on a later turn does not re-stamp", () => {
|
|
1294
|
+
mockCatalog = [makeSkill("homemade")];
|
|
1295
|
+
mockManifests = { homemade: makeManifest(["hm_run"]) };
|
|
1296
|
+
mockInstallMetas = {
|
|
1297
|
+
homemade: { origin: "custom", installedAt: "2026-01-01T00:00:00Z" },
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
const history: Message[] = [
|
|
1301
|
+
...skillLoadMessages('<loaded_skill id="homemade" />'),
|
|
1302
|
+
];
|
|
1303
|
+
projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
|
|
1304
|
+
projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
|
|
1305
|
+
|
|
1306
|
+
// Stamping rides the same load events as telemetry — once per activation.
|
|
1307
|
+
expect(recordedLastUsedTouches).toHaveLength(1);
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
test("version-hash change re-registers and re-stamps the managed skill", () => {
|
|
1311
|
+
mockCatalog = [makeSkill("homemade")];
|
|
1312
|
+
mockManifests = { homemade: makeManifest(["hm_run"]) };
|
|
1313
|
+
mockInstallMetas = {
|
|
1314
|
+
homemade: { origin: "custom", installedAt: "2026-01-01T00:00:00Z" },
|
|
1315
|
+
};
|
|
1316
|
+
mockVersionHashes = { homemade: "v1:hash-aaa" };
|
|
1317
|
+
|
|
1318
|
+
const history: Message[] = [
|
|
1319
|
+
...skillLoadMessages('<loaded_skill id="homemade" />'),
|
|
1320
|
+
];
|
|
1321
|
+
projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
|
|
1322
|
+
|
|
1323
|
+
mockVersionHashes = { homemade: "v1:hash-bbb" };
|
|
1324
|
+
projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
|
|
1325
|
+
|
|
1326
|
+
expect(recordedLastUsedTouches).toHaveLength(2);
|
|
1327
|
+
});
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1177
1330
|
// ---------------------------------------------------------------------------
|
|
1178
1331
|
// resolveTools callback integration tests
|
|
1179
1332
|
// ---------------------------------------------------------------------------
|
|
@@ -55,10 +55,14 @@ mock.module("../plugins/injector-registry.js", () => ({
|
|
|
55
55
|
}));
|
|
56
56
|
|
|
57
57
|
// `applyRuntimeInjections` reads the v3-live gate (`config.memory.v3.live`)
|
|
58
|
-
// via `isMemoryV3Live`; drive it directly through this slot per-test.
|
|
58
|
+
// via `isMemoryV3Live`; drive it directly through this slot per-test. The
|
|
59
|
+
// import graph also pulls `isProcToSkillsActive` (the permission policy-context
|
|
60
|
+
// precomputes the proc-to-skills gate), so the wholesale mock must expose it —
|
|
61
|
+
// keyed off the same v3-live slot.
|
|
59
62
|
let memoryV3LiveSlot = false;
|
|
60
63
|
mock.module("../config/memory-v3-gate.js", () => ({
|
|
61
64
|
isMemoryV3Live: () => memoryV3LiveSlot,
|
|
65
|
+
isProcToSkillsActive: () => false,
|
|
62
66
|
}));
|
|
63
67
|
|
|
64
68
|
const { applyRuntimeInjections } =
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
computeSkillHash,
|
|
16
16
|
readInstallMeta,
|
|
17
17
|
type SkillInstallMeta,
|
|
18
|
+
touchSkillLastUsed,
|
|
18
19
|
writeInstallMeta,
|
|
19
20
|
} from "../skills/install-meta.js";
|
|
20
21
|
|
|
@@ -108,6 +109,36 @@ describe("writeInstallMeta", () => {
|
|
|
108
109
|
expect(parsed.contentHash).toBe("v2:abc123def456");
|
|
109
110
|
});
|
|
110
111
|
|
|
112
|
+
test("round-trips optional author and lastUsedAt fields", () => {
|
|
113
|
+
const dir = makeTempDir();
|
|
114
|
+
const meta: SkillInstallMeta = {
|
|
115
|
+
origin: "custom",
|
|
116
|
+
installedAt: "2026-01-15T10:30:00.000Z",
|
|
117
|
+
author: "user",
|
|
118
|
+
lastUsedAt: "2026-02-01",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
writeInstallMeta(dir, meta);
|
|
122
|
+
|
|
123
|
+
const result = readInstallMeta(dir);
|
|
124
|
+
expect(result).not.toBeNull();
|
|
125
|
+
expect(result!.author).toBe("user");
|
|
126
|
+
expect(result!.lastUsedAt).toBe("2026-02-01");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("leaves author and lastUsedAt unset when omitted", () => {
|
|
130
|
+
const dir = makeTempDir();
|
|
131
|
+
writeInstallMeta(dir, {
|
|
132
|
+
origin: "custom",
|
|
133
|
+
installedAt: "2026-01-15T10:30:00.000Z",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const result = readInstallMeta(dir);
|
|
137
|
+
expect(result).not.toBeNull();
|
|
138
|
+
expect(result!.author).toBeUndefined();
|
|
139
|
+
expect(result!.lastUsedAt).toBeUndefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
111
142
|
test("overwrites existing install-meta.json", () => {
|
|
112
143
|
const dir = makeTempDir();
|
|
113
144
|
writeInstallMeta(dir, {
|
|
@@ -327,6 +358,79 @@ describe("readInstallMeta", () => {
|
|
|
327
358
|
});
|
|
328
359
|
});
|
|
329
360
|
|
|
361
|
+
// ─── touchSkillLastUsed ─────────────────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
describe("touchSkillLastUsed", () => {
|
|
364
|
+
function lastUsedDate(dir: string): string | undefined {
|
|
365
|
+
return readInstallMeta(dir)?.lastUsedAt?.slice(0, 10);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
test("returns false and writes nothing when install metadata is absent", () => {
|
|
369
|
+
const dir = makeTempDir();
|
|
370
|
+
expect(touchSkillLastUsed(dir, "2026-06-22")).toBe(false);
|
|
371
|
+
expect(existsSync(join(dir, "install-meta.json"))).toBe(false);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("first stamp writes today's date and returns true", () => {
|
|
375
|
+
const dir = makeTempDir();
|
|
376
|
+
writeInstallMeta(dir, {
|
|
377
|
+
origin: "custom",
|
|
378
|
+
installedAt: "2026-01-15T10:30:00.000Z",
|
|
379
|
+
author: "assistant",
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
expect(touchSkillLastUsed(dir, "2026-06-22")).toBe(true);
|
|
383
|
+
expect(lastUsedDate(dir)).toBe("2026-06-22");
|
|
384
|
+
// Existing fields are preserved.
|
|
385
|
+
const meta = readInstallMeta(dir);
|
|
386
|
+
expect(meta!.origin).toBe("custom");
|
|
387
|
+
expect(meta!.author).toBe("assistant");
|
|
388
|
+
expect(meta!.installedAt).toBe("2026-01-15T10:30:00.000Z");
|
|
389
|
+
// The stamp is a full ISO timestamp, not a bare date.
|
|
390
|
+
expect(meta!.lastUsedAt).toBe("2026-06-22T00:00:00.000Z");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("second stamp on the same day is a no-op (debounced)", () => {
|
|
394
|
+
const dir = makeTempDir();
|
|
395
|
+
writeInstallMeta(dir, {
|
|
396
|
+
origin: "custom",
|
|
397
|
+
installedAt: "2026-01-15T10:30:00.000Z",
|
|
398
|
+
lastUsedAt: "2026-06-22T08:00:00.000Z",
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
expect(touchSkillLastUsed(dir, "2026-06-22")).toBe(false);
|
|
402
|
+
// The existing timestamp is untouched.
|
|
403
|
+
expect(readInstallMeta(dir)!.lastUsedAt).toBe("2026-06-22T08:00:00.000Z");
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("stamp on a later day rewrites lastUsedAt", () => {
|
|
407
|
+
const dir = makeTempDir();
|
|
408
|
+
writeInstallMeta(dir, {
|
|
409
|
+
origin: "custom",
|
|
410
|
+
installedAt: "2026-01-15T10:30:00.000Z",
|
|
411
|
+
lastUsedAt: "2026-06-22T08:00:00.000Z",
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
expect(touchSkillLastUsed(dir, "2026-06-23")).toBe(true);
|
|
415
|
+
expect(lastUsedDate(dir)).toBe("2026-06-23");
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("stamps over a malformed install-meta.json via legacy version.json fallback", () => {
|
|
419
|
+
const dir = makeTempDir();
|
|
420
|
+
writeFileSync(join(dir, "install-meta.json"), "{bad json", "utf-8");
|
|
421
|
+
writeFileSync(
|
|
422
|
+
join(dir, "version.json"),
|
|
423
|
+
JSON.stringify({ version: "1.0.0" }),
|
|
424
|
+
"utf-8",
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
expect(touchSkillLastUsed(dir, "2026-06-22")).toBe(true);
|
|
428
|
+
const meta = readInstallMeta(dir);
|
|
429
|
+
expect(meta!.origin).toBe("vellum");
|
|
430
|
+
expect(meta!.lastUsedAt).toBe("2026-06-22T00:00:00.000Z");
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
330
434
|
// ─── computeSkillHash ───────────────────────────────────────────────────────
|
|
331
435
|
|
|
332
436
|
describe("computeSkillHash", () => {
|