@mycadet/assistant 0.12.8 → 0.12.9

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.
Files changed (42) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +1 -1
  2. package/node_modules/@mycadet/ces-client/package.json +1 -1
  3. package/node_modules/@mycadet/ces-client/src/__tests__/ces-client.test.ts +14 -14
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +1 -1
  5. package/node_modules/@mycadet/service-contracts/package.json +1 -1
  6. package/openapi.yaml +2 -18
  7. package/package.json +2 -2
  8. package/src/__tests__/cloud-profile-guard.test.ts +21 -19
  9. package/src/__tests__/config-set-route.test.ts +12 -8
  10. package/src/__tests__/credential-health-service.test.ts +4 -2
  11. package/src/__tests__/get-skill-detail-audit.test.ts +134 -289
  12. package/src/__tests__/mcp-abort-signal.test.ts +0 -1
  13. package/src/__tests__/mcp-client-auth.test.ts +0 -1
  14. package/src/__tests__/mcp-config-secret-boundary.test.ts +44 -40
  15. package/src/__tests__/mcp-tool-annotations-risk.test.ts +0 -1
  16. package/src/__tests__/plugin-api-store-credential.test.ts +4 -1
  17. package/src/__tests__/skills-file-content-endpoint.test.ts +221 -535
  18. package/src/__tests__/skills-files-catalog-fallback.test.ts +258 -576
  19. package/src/api/events/show-cloud-login.ts +2 -2
  20. package/src/cli/commands/cloud/__tests__/callback-routes-list.test.ts +2 -2
  21. package/src/cli/commands/cloud/index.ts +1 -1
  22. package/src/cli/commands/config.ts +1 -1
  23. package/src/config/__tests__/substrate-twin-shadowing.test.ts +36 -26
  24. package/src/config/env-registry.ts +2 -9
  25. package/src/config/schemas/ingress.ts +30 -0
  26. package/src/config/schemas/llm.ts +0 -9
  27. package/src/config/seed-inference-profiles.ts +2 -2
  28. package/src/config/substrate-twin-shadowing.ts +12 -17
  29. package/src/config/sync-gated-profiles.ts +3 -4
  30. package/src/credential-execution/prompted-credential.ts +1 -10
  31. package/src/credential-health/credential-health-service.ts +8 -4
  32. package/src/daemon/credential-transcript-scrub.ts +11 -0
  33. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -0
  34. package/src/providers/inference/backfill.ts +2 -2
  35. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +100 -57
  36. package/src/runtime/routes/cloud-routes.ts +5 -5
  37. package/src/runtime/routes/conversation-query-routes.ts +275 -397
  38. package/src/runtime/routes/secret-routes.ts +2 -17
  39. package/src/tools/credentials/store.ts +1 -10
  40. package/src/util/layout.ts +479 -0
  41. package/src/util/platform.ts +11 -467
  42. package/src/workspace/migrations/127-backfill-default-provider.ts +2 -2
@@ -40,6 +40,6 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/bun": "1.2.4",
43
- "typescript": "5.7.3"
43
+ "typescript": "5.9.3"
44
44
  }
45
45
  }
@@ -19,6 +19,6 @@
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/bun": "1.2.4",
22
- "typescript": "5.7.3"
22
+ "typescript": "5.9.3"
23
23
  }
24
24
  }
@@ -141,7 +141,7 @@ describe("CesHttpCredentialClient", () => {
141
141
  Promise.resolve(
142
142
  Response.json({ account: "test-acct", value: "secret-123" }),
143
143
  ),
144
- ) as typeof fetch;
144
+ ) as unknown as typeof fetch;
145
145
 
146
146
  const client = createCesHttpCredentialClient(
147
147
  createTestConfig(),
@@ -156,7 +156,7 @@ describe("CesHttpCredentialClient", () => {
156
156
  test("get returns not-found on 404", async () => {
157
157
  globalThis.fetch = mock(() =>
158
158
  Promise.resolve(new Response(null, { status: 404 })),
159
- ) as typeof fetch;
159
+ ) as unknown as typeof fetch;
160
160
 
161
161
  const client = createCesHttpCredentialClient(
162
162
  createTestConfig(),
@@ -171,7 +171,7 @@ describe("CesHttpCredentialClient", () => {
171
171
  test("get returns unreachable on 500", async () => {
172
172
  globalThis.fetch = mock(() =>
173
173
  Promise.resolve(new Response("internal error", { status: 500 })),
174
- ) as typeof fetch;
174
+ ) as unknown as typeof fetch;
175
175
 
176
176
  const client = createCesHttpCredentialClient(
177
177
  createTestConfig(),
@@ -186,7 +186,7 @@ describe("CesHttpCredentialClient", () => {
186
186
  test("get returns unreachable on network error", async () => {
187
187
  globalThis.fetch = mock(() =>
188
188
  Promise.reject(new Error("ECONNREFUSED")),
189
- ) as typeof fetch;
189
+ ) as unknown as typeof fetch;
190
190
 
191
191
  const client = createCesHttpCredentialClient(
192
192
  createTestConfig(),
@@ -201,7 +201,7 @@ describe("CesHttpCredentialClient", () => {
201
201
  test("delete returns deleted on 200", async () => {
202
202
  globalThis.fetch = mock(() =>
203
203
  Promise.resolve(Response.json({ ok: true, account: "acct" })),
204
- ) as typeof fetch;
204
+ ) as unknown as typeof fetch;
205
205
 
206
206
  const client = createCesHttpCredentialClient(
207
207
  createTestConfig(),
@@ -215,7 +215,7 @@ describe("CesHttpCredentialClient", () => {
215
215
  test("delete returns not-found on 404", async () => {
216
216
  globalThis.fetch = mock(() =>
217
217
  Promise.resolve(new Response(null, { status: 404 })),
218
- ) as typeof fetch;
218
+ ) as unknown as typeof fetch;
219
219
 
220
220
  const client = createCesHttpCredentialClient(
221
221
  createTestConfig(),
@@ -229,7 +229,7 @@ describe("CesHttpCredentialClient", () => {
229
229
  test("delete returns error on network failure", async () => {
230
230
  globalThis.fetch = mock(() =>
231
231
  Promise.reject(new Error("network down")),
232
- ) as typeof fetch;
232
+ ) as unknown as typeof fetch;
233
233
 
234
234
  const client = createCesHttpCredentialClient(
235
235
  createTestConfig(),
@@ -243,7 +243,7 @@ describe("CesHttpCredentialClient", () => {
243
243
  test("list returns accounts on 200", async () => {
244
244
  globalThis.fetch = mock(() =>
245
245
  Promise.resolve(Response.json({ accounts: ["a", "b", "c"] })),
246
- ) as typeof fetch;
246
+ ) as unknown as typeof fetch;
247
247
 
248
248
  const client = createCesHttpCredentialClient(
249
249
  createTestConfig(),
@@ -258,7 +258,7 @@ describe("CesHttpCredentialClient", () => {
258
258
  test("list returns unreachable on error", async () => {
259
259
  globalThis.fetch = mock(() =>
260
260
  Promise.reject(new Error("timeout")),
261
- ) as typeof fetch;
261
+ ) as unknown as typeof fetch;
262
262
 
263
263
  const client = createCesHttpCredentialClient(
264
264
  createTestConfig(),
@@ -273,7 +273,7 @@ describe("CesHttpCredentialClient", () => {
273
273
  test("bulkSet returns all-failed when request fails", async () => {
274
274
  globalThis.fetch = mock(() =>
275
275
  Promise.reject(new Error("network")),
276
- ) as typeof fetch;
276
+ ) as unknown as typeof fetch;
277
277
 
278
278
  const client = createCesHttpCredentialClient(
279
279
  createTestConfig(),
@@ -311,7 +311,7 @@ describe("fetchCesLogExport", () => {
311
311
  headers: { "Content-Type": "application/gzip" },
312
312
  }),
313
313
  ),
314
- ) as typeof fetch;
314
+ ) as unknown as typeof fetch;
315
315
 
316
316
  const result = await fetchCesLogExport({
317
317
  baseUrl: "http://ces-test:8090",
@@ -327,7 +327,7 @@ describe("fetchCesLogExport", () => {
327
327
  test("returns error on non-OK status", async () => {
328
328
  globalThis.fetch = mock(() =>
329
329
  Promise.resolve(new Response("forbidden", { status: 403 })),
330
- ) as typeof fetch;
330
+ ) as unknown as typeof fetch;
331
331
 
332
332
  const result = await fetchCesLogExport({
333
333
  baseUrl: "http://ces-test:8090",
@@ -343,7 +343,7 @@ describe("fetchCesLogExport", () => {
343
343
  test("returns error on network failure", async () => {
344
344
  globalThis.fetch = mock(() =>
345
345
  Promise.reject(new Error("connection refused")),
346
- ) as typeof fetch;
346
+ ) as unknown as typeof fetch;
347
347
 
348
348
  const result = await fetchCesLogExport({
349
349
  baseUrl: "http://ces-test:8090",
@@ -361,7 +361,7 @@ describe("fetchCesLogExport", () => {
361
361
  globalThis.fetch = mock((url: string | URL | Request) => {
362
362
  capturedUrl = typeof url === "string" ? url : url.toString();
363
363
  return Promise.resolve(new Response(new ArrayBuffer(0), { status: 200 }));
364
- }) as typeof fetch;
364
+ }) as unknown as typeof fetch;
365
365
 
366
366
  await fetchCesLogExport(
367
367
  { baseUrl: "http://ces-test:8090", serviceToken: "tok" },
@@ -40,6 +40,6 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/bun": "1.2.4",
43
- "typescript": "5.7.3"
43
+ "typescript": "5.9.3"
44
44
  }
45
45
  }
@@ -40,6 +40,6 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/bun": "1.2.4",
43
- "typescript": "5.7.3"
43
+ "typescript": "5.9.3"
44
44
  }
45
45
  }
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.1.0
4
4
  info:
5
5
  title: Cadet Assistant API
6
- version: 0.12.8
6
+ version: 0.12.9
7
7
  description: Auto-generated OpenAPI specification for the Cadet Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
@@ -32738,7 +32738,7 @@ paths:
32738
32738
  summary: Register a cloud callback route
32739
32739
  description: Registers a callback route with the cloud gateway for inbound provider webhooks.
32740
32740
  tags:
32741
- - platform
32741
+ - cloud
32742
32742
  requestBody:
32743
32743
  required: true
32744
32744
  content:
@@ -45141,14 +45141,6 @@ components:
45141
45141
  type: string
45142
45142
  minLength: 1
45143
45143
  - type: "null"
45144
- supportsVision:
45145
- anyOf:
45146
- - type: boolean
45147
- - type: "null"
45148
- invariant:
45149
- anyOf:
45150
- - type: boolean
45151
- - type: "null"
45152
45144
  additionalProperties: {}
45153
45145
  InputModalities:
45154
45146
  type: object
@@ -45501,10 +45493,6 @@ components:
45501
45493
  readOnly: true
45502
45494
  type: string
45503
45495
  minLength: 1
45504
- supportsVision:
45505
- type: boolean
45506
- invariant:
45507
- type: boolean
45508
45496
  ConnectionProvider:
45509
45497
  type: string
45510
45498
  enum:
@@ -46285,10 +46273,6 @@ components:
46285
46273
  readOnly: true
46286
46274
  type: string
46287
46275
  minLength: 1
46288
- supportsVision:
46289
- type: boolean
46290
- invariant:
46291
- type: boolean
46292
46276
  additionalProperties: false
46293
46277
  InputModalitiesOutput:
46294
46278
  type: object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mycadet/assistant",
3
- "version": "0.12.8",
3
+ "version": "0.12.9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -20,7 +20,7 @@
20
20
  "probe:skill-retrieval": "bun run scripts/probe-skill-retrieval.ts",
21
21
  "format": "prettier --write .",
22
22
  "format:check": "prettier --check .",
23
- "lint": "bun run lint:skills && bun run lint:openapi && eslint --max-warnings 3006",
23
+ "lint": "bun run lint:skills && bun run lint:openapi && eslint --max-warnings 2956",
24
24
  "lint:unused": "knip --include files,dependencies,unlisted",
25
25
  "lint:circular": "bun run scripts/check-circular-deps.ts",
26
26
  "lint:unused:production": "knip --production --include exports",
@@ -592,12 +592,12 @@ describe("PATCH /v1/config — cloud profile deletion guard", () => {
592
592
  });
593
593
 
594
594
  expect(result).toHaveProperty("ingress");
595
- expect(committedRaw()).toEqual({
596
- ingress: {
597
- publicBaseUrl: "https://manual.example.test",
598
- publicBaseUrlManagedBy: "tunnel",
599
- },
600
- });
595
+ const committed = committedRaw() as Record<string, any>;
596
+ expect(committed.ingress.publicBaseUrl).toBe("https://manual.example.test");
597
+ expect(committed.ingress.publicBaseUrlManagedBy).toBe("tunnel");
598
+ expect("lastTunnel" in committed.ingress).toBe(false);
599
+ expect("pairingTunnel" in committed.ingress).toBe(false);
600
+ expect("assistantId" in committed.ingress).toBe(false);
601
601
  });
602
602
 
603
603
  test("allows patches that modify a cloud profile (non-null)", async () => {
@@ -756,7 +756,7 @@ describe("cloud-profile invariant guard — rejected writes", () => {
756
756
  patchRoute.handler({
757
757
  body: { llm: { profiles: { balanced: "junk" } } },
758
758
  }),
759
- ).rejects.toThrow('Cannot delete or replace cloud profile "balanced".');
759
+ ).rejects.toThrow("expected object, received string");
760
760
  expectNothingCommitted();
761
761
  });
762
762
 
@@ -765,7 +765,7 @@ describe("cloud-profile invariant guard — rejected writes", () => {
765
765
  patchRoute.handler({
766
766
  body: { llm: { profiles: { balanced: ["disabled"] } } },
767
767
  }),
768
- ).rejects.toThrow('Cannot delete or replace cloud profile "balanced".');
768
+ ).rejects.toThrow("expected object, received array");
769
769
  expectNothingCommitted();
770
770
  });
771
771
 
@@ -806,10 +806,7 @@ describe("cloud-profile invariant guard — rejected writes", () => {
806
806
  patchRoute.handler({
807
807
  body: { llm: { profiles: { balanced: { status: "weird" } } } },
808
808
  }),
809
- ).rejects.toThrow(
810
- 'Cannot set status "weird" on cloud profile "balanced". ' +
811
- 'Only re-enabling (status "active") is allowed.',
812
- );
809
+ ).rejects.toThrow("llm.profiles.balanced.status: Invalid option");
813
810
  expectNothingCommitted();
814
811
  });
815
812
 
@@ -818,7 +815,7 @@ describe("cloud-profile invariant guard — rejected writes", () => {
818
815
  setRoute.handler({
819
816
  body: { path: "llm.profiles.balanced.status", value: 123 },
820
817
  }),
821
- ).rejects.toThrow('Cannot set status 123 on cloud profile "balanced"');
818
+ ).rejects.toThrow("llm.profiles.balanced.status: Invalid option");
822
819
  expectNothingCommitted();
823
820
  });
824
821
  });
@@ -939,12 +936,12 @@ describe("default-profile invariant guard — allowed writes", () => {
939
936
 
940
937
  test("PATCH non-profile llm writes are unaffected", async () => {
941
938
  const result = await patchRoute.handler({
942
- body: { llm: { activeProfile: "custom" } },
939
+ body: { llm: { activeProfile: "balanced" } },
943
940
  });
944
941
  expect(result).toHaveProperty("llm");
945
942
  expectOneCommitCycle();
946
943
  expect((committedRaw() as Record<string, any>).llm.activeProfile).toBe(
947
- "custom",
944
+ "balanced",
948
945
  );
949
946
  // The invariant profiles are untouched by an unrelated llm write.
950
947
  expect(savedProfile("balanced")).toEqual(
@@ -1004,12 +1001,13 @@ describe("wire-only profile keys are stripped from writes", () => {
1004
1001
  expect(profile.model).toBe("claude-sonnet");
1005
1002
  });
1006
1003
 
1007
- test("SET of a wire-only leaf path is dropped without writing", async () => {
1004
+ test("SET of a wire-only leaf path never persists the key", async () => {
1008
1005
  const result = await setRoute.handler({
1009
1006
  body: { path: "llm.profiles.balanced.supportsVision", value: true },
1010
1007
  });
1011
1008
  expect(result).toEqual({ ok: true });
1012
- expectNothingCommitted();
1009
+ expectOneCommitCycle();
1010
+ expect("supportsVision" in savedProfile("balanced")).toBe(false);
1013
1011
  });
1014
1012
 
1015
1013
  // -------------------------------------------------------------------------
@@ -1425,7 +1423,11 @@ describe("PATCH /v1/config — provider-only services scrub legacy mode", () =>
1425
1423
  string,
1426
1424
  Record<string, Record<string, unknown>>
1427
1425
  >;
1428
- expect(raw.services["image-generation"]).toEqual({ provider: "cadet" });
1429
- expect(raw.services["web-search"]).toEqual({ provider: "cadet" });
1426
+ // The section schemas are closed: the parse strips the legacy `mode`
1427
+ // key and materializes the section's defaults around the user's values.
1428
+ expect("mode" in raw.services["image-generation"]!).toBe(false);
1429
+ expect(raw.services["image-generation"]!.provider).toBe("cadet");
1430
+ expect("mode" in raw.services["web-search"]!).toBe(false);
1431
+ expect(raw.services["web-search"]!.provider).toBe("cadet");
1430
1432
  });
1431
1433
  });
@@ -85,13 +85,13 @@ describe("config_set route - request validation", () => {
85
85
  });
86
86
 
87
87
  test("accepts body with explicit null value", async () => {
88
- seedRawConfig({ heartbeat: { activeHoursStart: 9 } });
88
+ seedRawConfig({ heartbeat: { timezone: "America/New_York" } });
89
89
  const result = await configSetRoute.handler({
90
- body: { path: "heartbeat.activeHoursStart", value: null },
90
+ body: { path: "heartbeat.timezone", value: null },
91
91
  });
92
92
  expect(result).toEqual({ ok: true });
93
93
  const heartbeat = loadRawConfig().heartbeat as Record<string, unknown>;
94
- expect(heartbeat.activeHoursStart).toBeNull();
94
+ expect(heartbeat.timezone).toBeNull();
95
95
  });
96
96
 
97
97
  test("rejects body missing path field", async () => {
@@ -192,12 +192,16 @@ describe("config_set route - request validation", () => {
192
192
  body: { path: "memory.cleanup.conversationRetentionDays", value: 30 },
193
193
  });
194
194
  const saved = loadRawConfig();
195
- expect(saved.llm).toEqual({ default: { provider: "anthropic" } });
196
- expect(saved.calls).toEqual({ enabled: true });
197
- expect(saved.heartbeat).toEqual({
198
- activeHoursStart: 9,
199
- activeHoursEnd: 22,
195
+ // The write is schema-canonical: the parse materializes section defaults
196
+ // around what the user set. The contract under test — a nested set may
197
+ // not drop or mangle unrelated top-level keys — is asserted leaf-wise.
198
+ expect((saved.llm as Record<string, unknown>).default).toEqual({
199
+ provider: "anthropic",
200
200
  });
201
+ expect((saved.calls as Record<string, unknown>).enabled).toBe(true);
202
+ const heartbeat = saved.heartbeat as Record<string, unknown>;
203
+ expect(heartbeat.activeHoursStart).toBe(9);
204
+ expect(heartbeat.activeHoursEnd).toBe(22);
201
205
  });
202
206
  });
203
207
 
@@ -780,8 +780,10 @@ describe("credential-health-service", () => {
780
780
  // carries the repair.
781
781
  const assistantKeyResult = checkAssistantApiKey;
782
782
 
783
- test("is not reported when the workspace was never connected", async () => {
784
- expect(await assistantKeyResult()).toBeNull();
783
+ test("is missing_token when no key is stored, connected or not", async () => {
784
+ const result = await assistantKeyResult();
785
+ expect(result!.status).toBe("missing_token");
786
+ expect(result!.canAutoRecover).toBe(true);
785
787
  });
786
788
 
787
789
  test("is healthy when the cloud accepts the stored key", async () => {