@vellumai/assistant 0.5.9 → 0.5.11

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 (278) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +48 -48
  3. package/Dockerfile +2 -0
  4. package/README.md +1 -1
  5. package/docs/architecture/integrations.md +6 -13
  6. package/docs/architecture/memory.md +7 -12
  7. package/docs/architecture/security.md +5 -5
  8. package/docs/credential-execution-service.md +9 -9
  9. package/docs/skills.md +1 -1
  10. package/node_modules/@vellumai/credential-storage/src/index.ts +2 -2
  11. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
  12. package/openapi.yaml +7130 -0
  13. package/package.json +2 -1
  14. package/scripts/generate-openapi.ts +562 -0
  15. package/src/__tests__/acp-session.test.ts +239 -44
  16. package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
  17. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
  19. package/src/__tests__/browser-skill-endstate.test.ts +1 -1
  20. package/src/__tests__/btw-routes.test.ts +8 -0
  21. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
  22. package/src/__tests__/channel-approvals.test.ts +7 -7
  23. package/src/__tests__/channel-readiness-service.test.ts +41 -0
  24. package/src/__tests__/config-schema.test.ts +10 -2
  25. package/src/__tests__/context-memory-e2e.test.ts +2 -6
  26. package/src/__tests__/conversation-skill-tools.test.ts +1 -3
  27. package/src/__tests__/conversation-title-service.test.ts +2 -15
  28. package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
  29. package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
  30. package/src/__tests__/credential-security-e2e.test.ts +4 -4
  31. package/src/__tests__/credential-security-invariants.test.ts +3 -3
  32. package/src/__tests__/credentials-cli.test.ts +3 -3
  33. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
  34. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  35. package/src/__tests__/heartbeat-service.test.ts +35 -0
  36. package/src/__tests__/host-shell-tool.test.ts +1 -1
  37. package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
  38. package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
  39. package/src/__tests__/log-export-workspace.test.ts +1 -1
  40. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  41. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  42. package/src/__tests__/memory-recall-log-store.test.ts +182 -0
  43. package/src/__tests__/memory-recall-quality.test.ts +6 -8
  44. package/src/__tests__/memory-regressions.test.ts +53 -42
  45. package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
  46. package/src/__tests__/messaging-skill-split.test.ts +2 -17
  47. package/src/__tests__/oauth-cli.test.ts +98 -551
  48. package/src/__tests__/platform-callback-registration.test.ts +119 -0
  49. package/src/__tests__/secret-ingress-channel.test.ts +261 -0
  50. package/src/__tests__/secret-ingress-cli.test.ts +201 -0
  51. package/src/__tests__/secret-ingress-http.test.ts +312 -0
  52. package/src/__tests__/secret-ingress.test.ts +283 -0
  53. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  54. package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
  55. package/src/__tests__/skill-feature-flags.test.ts +11 -19
  56. package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
  57. package/src/__tests__/skill-load-inline-command.test.ts +3 -3
  58. package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
  59. package/src/__tests__/skill-memory.test.ts +2 -4
  60. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
  61. package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
  62. package/src/__tests__/skills.test.ts +16 -2
  63. package/src/__tests__/slack-channel-config.test.ts +1 -1
  64. package/src/__tests__/slack-skill.test.ts +5 -69
  65. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
  66. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +5 -238
  67. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -206
  68. package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
  69. package/src/__tests__/workspace-migrations-runner.test.ts +15 -7
  70. package/src/acp/client-handler.ts +113 -31
  71. package/src/acp/session-manager.ts +29 -27
  72. package/src/approvals/guardian-request-resolvers.ts +1 -1
  73. package/src/cli/AGENTS.md +73 -0
  74. package/src/cli/commands/autonomy.ts +3 -5
  75. package/src/cli/commands/credential-execution.ts +1 -2
  76. package/src/cli/commands/credentials.ts +4 -4
  77. package/src/cli/commands/memory.ts +2 -3
  78. package/src/cli/commands/oauth/__tests__/connect.test.ts +785 -0
  79. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +760 -0
  80. package/src/cli/commands/oauth/__tests__/mode.test.ts +672 -0
  81. package/src/cli/commands/oauth/__tests__/ping.test.ts +690 -0
  82. package/src/cli/commands/oauth/__tests__/status.test.ts +579 -0
  83. package/src/cli/commands/oauth/__tests__/token.test.ts +467 -0
  84. package/src/cli/commands/oauth/apps.ts +29 -11
  85. package/src/cli/commands/oauth/connect.ts +373 -0
  86. package/src/cli/commands/oauth/connections.ts +14 -493
  87. package/src/cli/commands/oauth/disconnect.ts +333 -0
  88. package/src/cli/commands/oauth/index.ts +62 -10
  89. package/src/cli/commands/oauth/mode.ts +263 -0
  90. package/src/cli/commands/oauth/ping.ts +222 -0
  91. package/src/cli/commands/oauth/providers.ts +30 -3
  92. package/src/cli/commands/oauth/request.ts +576 -0
  93. package/src/cli/commands/oauth/shared.ts +132 -0
  94. package/src/cli/commands/oauth/status.ts +202 -0
  95. package/src/cli/commands/oauth/token.ts +159 -0
  96. package/src/cli/commands/platform.ts +20 -14
  97. package/src/cli.ts +82 -17
  98. package/src/config/assistant-feature-flags.ts +74 -11
  99. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  100. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
  101. package/src/config/bundled-skills/messaging/SKILL.md +13 -36
  102. package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
  103. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  104. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  105. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  106. package/src/config/bundled-skills/settings/SKILL.md +5 -3
  107. package/src/config/bundled-skills/settings/TOOLS.json +17 -0
  108. package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
  109. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
  111. package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
  112. package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
  113. package/src/config/bundled-skills/slack/SKILL.md +58 -44
  114. package/src/config/bundled-tool-registry.ts +2 -19
  115. package/src/config/env.ts +5 -1
  116. package/src/config/feature-flag-registry.json +57 -41
  117. package/src/config/loader.ts +4 -0
  118. package/src/config/schemas/platform.ts +0 -8
  119. package/src/config/schemas/security.ts +9 -1
  120. package/src/config/schemas/services.ts +1 -1
  121. package/src/config/skill-state.ts +1 -3
  122. package/src/config/skills.ts +2 -4
  123. package/src/credential-execution/feature-gates.ts +9 -16
  124. package/src/credential-execution/process-manager.ts +12 -0
  125. package/src/daemon/config-watcher.ts +4 -0
  126. package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
  127. package/src/daemon/conversation-agent-loop.ts +49 -2
  128. package/src/daemon/conversation-memory.ts +0 -1
  129. package/src/daemon/handlers/config-slack-channel.ts +43 -1
  130. package/src/daemon/handlers/conversations.ts +41 -33
  131. package/src/daemon/lifecycle.ts +28 -5
  132. package/src/daemon/message-types/acp.ts +0 -15
  133. package/src/daemon/message-types/memory.ts +0 -1
  134. package/src/daemon/message-types/messages.ts +9 -1
  135. package/src/daemon/message-types/schedules.ts +9 -0
  136. package/src/daemon/server.ts +19 -7
  137. package/src/email/feature-gate.ts +3 -3
  138. package/src/heartbeat/heartbeat-service.ts +48 -0
  139. package/src/inbound/platform-callback-registration.ts +61 -7
  140. package/src/mcp/mcp-oauth-provider.ts +3 -3
  141. package/src/memory/app-store.ts +3 -3
  142. package/src/memory/conversation-crud.ts +124 -0
  143. package/src/memory/conversation-title-service.ts +7 -17
  144. package/src/memory/db-init.ts +8 -0
  145. package/src/memory/embedding-local.ts +47 -2
  146. package/src/memory/indexer.ts +13 -10
  147. package/src/memory/items-extractor.ts +12 -4
  148. package/src/memory/job-utils.ts +5 -0
  149. package/src/memory/jobs-store.ts +10 -2
  150. package/src/memory/journal-memory.ts +6 -2
  151. package/src/memory/llm-request-log-store.ts +88 -21
  152. package/src/memory/memory-recall-log-store.ts +128 -0
  153. package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
  154. package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
  155. package/src/memory/migrations/index.ts +2 -0
  156. package/src/memory/migrations/validate-migration-state.ts +14 -1
  157. package/src/memory/retriever.test.ts +4 -5
  158. package/src/memory/schema/infrastructure.ts +31 -0
  159. package/src/memory/schema/oauth.ts +3 -0
  160. package/src/messaging/providers/telegram-bot/adapter.ts +1 -1
  161. package/src/oauth/connect-orchestrator.ts +54 -0
  162. package/src/oauth/manual-token-connection.ts +5 -5
  163. package/src/oauth/oauth-store.ts +26 -5
  164. package/src/oauth/seed-providers.ts +10 -1
  165. package/src/permissions/checker.ts +2 -2
  166. package/src/permissions/trust-client.ts +2 -2
  167. package/src/platform/client.ts +2 -2
  168. package/src/prompts/journal-context.ts +6 -1
  169. package/src/providers/anthropic/client.ts +143 -1
  170. package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
  171. package/src/runtime/auth/route-policy.ts +0 -1
  172. package/src/runtime/btw-sidechain.ts +7 -1
  173. package/src/runtime/channel-approvals.ts +2 -2
  174. package/src/runtime/channel-readiness-service.ts +30 -7
  175. package/src/runtime/http-router.ts +31 -0
  176. package/src/runtime/http-server.ts +21 -4
  177. package/src/runtime/http-types.ts +2 -0
  178. package/src/runtime/pending-interactions.ts +21 -3
  179. package/src/runtime/routes/acp-routes.ts +46 -28
  180. package/src/runtime/routes/app-management-routes.ts +123 -0
  181. package/src/runtime/routes/app-routes.ts +31 -0
  182. package/src/runtime/routes/approval-routes.ts +108 -3
  183. package/src/runtime/routes/attachment-routes.ts +45 -0
  184. package/src/runtime/routes/avatar-routes.ts +16 -0
  185. package/src/runtime/routes/brain-graph-routes.ts +18 -0
  186. package/src/runtime/routes/btw-routes.ts +20 -0
  187. package/src/runtime/routes/call-routes.ts +81 -0
  188. package/src/runtime/routes/channel-readiness-routes.ts +48 -7
  189. package/src/runtime/routes/channel-routes.ts +18 -0
  190. package/src/runtime/routes/channel-verification-routes.ts +49 -1
  191. package/src/runtime/routes/contact-routes.ts +77 -0
  192. package/src/runtime/routes/conversation-attention-routes.ts +37 -0
  193. package/src/runtime/routes/conversation-management-routes.ts +94 -0
  194. package/src/runtime/routes/conversation-query-routes.ts +78 -0
  195. package/src/runtime/routes/conversation-routes.ts +115 -38
  196. package/src/runtime/routes/conversation-starter-routes.ts +29 -0
  197. package/src/runtime/routes/debug-routes.ts +23 -0
  198. package/src/runtime/routes/diagnostics-routes.ts +30 -0
  199. package/src/runtime/routes/documents-routes.ts +42 -0
  200. package/src/runtime/routes/events-routes.ts +10 -0
  201. package/src/runtime/routes/global-search-routes.ts +35 -0
  202. package/src/runtime/routes/guardian-action-routes.ts +47 -2
  203. package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
  204. package/src/runtime/routes/heartbeat-routes.ts +278 -0
  205. package/src/runtime/routes/host-bash-routes.ts +16 -1
  206. package/src/runtime/routes/host-cu-routes.ts +23 -1
  207. package/src/runtime/routes/host-file-routes.ts +18 -1
  208. package/src/runtime/routes/identity-routes.ts +35 -0
  209. package/src/runtime/routes/inbound-message-handler.ts +46 -25
  210. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
  211. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
  212. package/src/runtime/routes/integrations/twilio.ts +32 -22
  213. package/src/runtime/routes/invite-routes.ts +83 -0
  214. package/src/runtime/routes/log-export-routes.ts +14 -0
  215. package/src/runtime/routes/memory-item-routes.ts +99 -1
  216. package/src/runtime/routes/migration-rollback-routes.ts +25 -0
  217. package/src/runtime/routes/migration-routes.ts +40 -0
  218. package/src/runtime/routes/notification-routes.ts +20 -0
  219. package/src/runtime/routes/oauth-apps.ts +11 -3
  220. package/src/runtime/routes/pairing-routes.ts +15 -0
  221. package/src/runtime/routes/recording-routes.ts +72 -0
  222. package/src/runtime/routes/schedule-routes.ts +77 -5
  223. package/src/runtime/routes/secret-routes.ts +63 -1
  224. package/src/runtime/routes/settings-routes.ts +91 -1
  225. package/src/runtime/routes/skills-routes.ts +98 -16
  226. package/src/runtime/routes/subagents-routes.ts +38 -3
  227. package/src/runtime/routes/surface-action-routes.ts +66 -24
  228. package/src/runtime/routes/surface-content-routes.ts +20 -0
  229. package/src/runtime/routes/telemetry-routes.ts +12 -0
  230. package/src/runtime/routes/trace-event-routes.ts +25 -0
  231. package/src/runtime/routes/trust-rules-routes.ts +46 -0
  232. package/src/runtime/routes/tts-routes.ts +15 -4
  233. package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
  234. package/src/runtime/routes/usage-routes.ts +59 -0
  235. package/src/runtime/routes/watch-routes.ts +28 -0
  236. package/src/runtime/routes/work-items-routes.ts +59 -0
  237. package/src/runtime/routes/workspace-commit-routes.ts +12 -0
  238. package/src/runtime/routes/workspace-routes.ts +102 -0
  239. package/src/schedule/scheduler.ts +7 -1
  240. package/src/security/AGENTS.md +7 -0
  241. package/src/security/credential-backend.ts +1 -1
  242. package/src/security/encrypted-store.ts +3 -3
  243. package/src/security/oauth2.ts +55 -0
  244. package/src/security/secret-ingress.ts +174 -0
  245. package/src/security/secret-patterns.ts +133 -0
  246. package/src/security/secret-scanner.ts +28 -117
  247. package/src/signals/confirm.ts +12 -8
  248. package/src/signals/user-message.ts +18 -3
  249. package/src/skills/skill-memory.ts +1 -2
  250. package/src/tasks/task-runner.ts +7 -1
  251. package/src/tools/credentials/broker.ts +1 -1
  252. package/src/tools/credentials/metadata-store.ts +1 -1
  253. package/src/tools/credentials/vault.ts +2 -3
  254. package/src/tools/memory/definitions.ts +1 -1
  255. package/src/tools/memory/handlers.test.ts +2 -4
  256. package/src/tools/skills/load.ts +1 -1
  257. package/src/tools/terminal/safe-env.ts +7 -0
  258. package/src/tools/tool-manifest.ts +1 -1
  259. package/src/util/log-redact.ts +9 -34
  260. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +13 -148
  261. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +7 -145
  262. package/src/workspace/migrations/AGENTS.md +11 -0
  263. package/src/workspace/migrations/runner.ts +16 -6
  264. package/src/workspace/migrations/types.ts +7 -0
  265. package/docs/architecture/keychain-broker.md +0 -69
  266. package/src/__tests__/keychain-broker-client.test.ts +0 -800
  267. package/src/cli/commands/oauth/platform.ts +0 -525
  268. package/src/config/bundled-skills/slack/TOOLS.json +0 -272
  269. package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
  270. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
  271. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
  272. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
  273. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
  274. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
  275. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
  276. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
  277. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
  278. package/src/security/keychain-broker-client.ts +0 -446
@@ -7,6 +7,8 @@
7
7
  * the same pattern as other gateway-forwarded control-plane endpoints.
8
8
  */
9
9
 
10
+ import { z } from "zod";
11
+
10
12
  import { getWorkspaceDir } from "../../util/platform.js";
11
13
  import { getWorkspaceGitService } from "../../workspace/git-service.js";
12
14
  import { httpError } from "../http-errors.js";
@@ -17,6 +19,16 @@ export function workspaceCommitRouteDefinitions(): RouteDefinition[] {
17
19
  {
18
20
  endpoint: "admin/workspace-commit",
19
21
  method: "POST",
22
+ summary: "Commit workspace changes",
23
+ description:
24
+ "Create a git commit in the workspace directory with all pending changes.",
25
+ tags: ["admin"],
26
+ requestBody: z.object({
27
+ message: z.string().describe("Commit message"),
28
+ }),
29
+ responseBody: z.object({
30
+ ok: z.boolean(),
31
+ }),
20
32
  handler: async ({ req }) => {
21
33
  let body: unknown;
22
34
  try {
@@ -16,6 +16,8 @@ import {
16
16
  } from "node:fs";
17
17
  import { basename, dirname, join } from "node:path";
18
18
 
19
+ import { z } from "zod";
20
+
19
21
  import { getWorkspaceDir } from "../../util/platform.js";
20
22
  import { httpError } from "../http-errors.js";
21
23
  import type { RouteContext, RouteDefinition } from "../http-router.js";
@@ -382,36 +384,136 @@ export function workspaceRouteDefinitions(): RouteDefinition[] {
382
384
  {
383
385
  endpoint: "workspace/tree",
384
386
  method: "GET",
387
+ summary: "List workspace directory",
388
+ description: "Return directory entries for a workspace path.",
389
+ tags: ["workspace"],
390
+ queryParams: [
391
+ {
392
+ name: "path",
393
+ schema: { type: "string" },
394
+ description: "Relative path (default root)",
395
+ },
396
+ {
397
+ name: "showHidden",
398
+ schema: { type: "string" },
399
+ description: "Include dotfiles (true/false)",
400
+ },
401
+ ],
402
+ responseBody: z.object({
403
+ path: z.string(),
404
+ entries: z.array(z.unknown()).describe("Directory entry objects"),
405
+ }),
385
406
  handler: (ctx) => handleWorkspaceTree(ctx),
386
407
  },
387
408
  {
388
409
  endpoint: "workspace/file/content",
389
410
  method: "GET",
411
+ summary: "Get workspace file content",
412
+ description: "Return raw file bytes with HTTP range support.",
413
+ tags: ["workspace"],
414
+ queryParams: [
415
+ {
416
+ name: "path",
417
+ schema: { type: "string" },
418
+ description: "Relative file path (required)",
419
+ },
420
+ {
421
+ name: "showHidden",
422
+ schema: { type: "string" },
423
+ description: "Allow hidden files (true/false)",
424
+ },
425
+ ],
390
426
  handler: (ctx) => handleWorkspaceFileContent(ctx),
391
427
  },
392
428
  {
393
429
  endpoint: "workspace/file",
394
430
  method: "GET",
431
+ summary: "Get workspace file metadata",
432
+ description:
433
+ "Return file metadata and inline text content (if small enough).",
434
+ tags: ["workspace"],
435
+ queryParams: [
436
+ {
437
+ name: "path",
438
+ schema: { type: "string" },
439
+ description: "Relative file path (required)",
440
+ },
441
+ {
442
+ name: "showHidden",
443
+ schema: { type: "string" },
444
+ description: "Allow hidden files (true/false)",
445
+ },
446
+ ],
447
+ responseBody: z.object({
448
+ path: z.string(),
449
+ name: z.string(),
450
+ size: z.number(),
451
+ mimeType: z.string(),
452
+ modifiedAt: z.string(),
453
+ content: z.string().describe("Inline text content or null"),
454
+ isBinary: z.boolean(),
455
+ }),
395
456
  handler: (ctx) => handleWorkspaceFile(ctx),
396
457
  },
397
458
  {
398
459
  endpoint: "workspace/write",
399
460
  method: "POST",
461
+ summary: "Write workspace file",
462
+ description: "Create or overwrite a file in the workspace.",
463
+ tags: ["workspace"],
464
+ requestBody: z.object({
465
+ path: z.string().describe("Relative file path"),
466
+ content: z.string().describe("File content").optional(),
467
+ encoding: z
468
+ .string()
469
+ .describe("Content encoding (base64 or utf-8)")
470
+ .optional(),
471
+ }),
472
+ responseBody: z.object({
473
+ path: z.string(),
474
+ size: z.number(),
475
+ }),
400
476
  handler: (ctx) => handleWorkspaceWrite(ctx),
401
477
  },
402
478
  {
403
479
  endpoint: "workspace/mkdir",
404
480
  method: "POST",
481
+ summary: "Create workspace directory",
482
+ description: "Create directories recursively in the workspace.",
483
+ tags: ["workspace"],
484
+ requestBody: z.object({
485
+ path: z.string().describe("Relative directory path"),
486
+ }),
487
+ responseBody: z.object({
488
+ path: z.string(),
489
+ }),
405
490
  handler: (ctx) => handleWorkspaceMkdir(ctx),
406
491
  },
407
492
  {
408
493
  endpoint: "workspace/rename",
409
494
  method: "POST",
495
+ summary: "Rename workspace entry",
496
+ description: "Rename or move a file or directory in the workspace.",
497
+ tags: ["workspace"],
498
+ requestBody: z.object({
499
+ oldPath: z.string().describe("Current relative path"),
500
+ newPath: z.string().describe("New relative path"),
501
+ }),
502
+ responseBody: z.object({
503
+ oldPath: z.string(),
504
+ newPath: z.string(),
505
+ }),
410
506
  handler: (ctx) => handleWorkspaceRename(ctx),
411
507
  },
412
508
  {
413
509
  endpoint: "workspace/delete",
414
510
  method: "POST",
511
+ summary: "Delete workspace entry",
512
+ description: "Delete a file or directory from the workspace.",
513
+ tags: ["workspace"],
514
+ requestBody: z.object({
515
+ path: z.string().describe("Relative path to delete"),
516
+ }),
415
517
  handler: (ctx) => handleWorkspaceDelete(ctx),
416
518
  },
417
519
  ];
@@ -188,7 +188,7 @@ async function runScheduleOnce(
188
188
  );
189
189
  const { runTask } = await import("../tasks/task-runner.js");
190
190
  const result = await runTask(
191
- { taskId, workingDir: process.cwd(), source: "schedule" },
191
+ { taskId, workingDir: process.cwd(), source: "schedule", scheduleJobId: job.id },
192
192
  processMessage as (
193
193
  conversationId: string,
194
194
  message: string,
@@ -196,6 +196,12 @@ async function runScheduleOnce(
196
196
  ) => Promise<void>,
197
197
  );
198
198
 
199
+ onScheduleConversationCreated?.({
200
+ conversationId: result.conversationId,
201
+ scheduleJobId: job.id,
202
+ title: result.status === "failed" ? `${job.name}: Error` : job.name,
203
+ });
204
+
199
205
  // Track the schedule run using the task's conversation
200
206
  const runId = createScheduleRun(job.id, result.conversationId);
201
207
  if (result.status === "failed") {
@@ -0,0 +1,7 @@
1
+ # Security — Agent Instructions
2
+
3
+ ## Integration API Key Patterns
4
+
5
+ When adding a new third-party integration, check whether the service uses a recognizable API key prefix (e.g., `lin_api_`, `sk-ant-`, `ghp_`). If it does, add a corresponding entry to `PREFIX_PATTERNS` in `secret-patterns.ts`. This is the single source of truth for prefix-based secret detection — ingress blocking, tool output scanning, and log redaction all consume this list.
6
+
7
+ OAuth-only services with opaque access tokens (no fixed prefix) do not need a pattern.
@@ -30,7 +30,7 @@ export interface CredentialListResult {
30
30
  // ---------------------------------------------------------------------------
31
31
 
32
32
  export interface CredentialBackend {
33
- /** Human-readable name for logging (e.g. "keychain", "encrypted-store"). */
33
+ /** Human-readable name for logging (e.g. "encrypted-store"). */
34
34
  readonly name: string;
35
35
 
36
36
  /** Whether this backend is currently reachable. Sync and cheap. */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Encrypted-at-rest key storage -- fallback for systems without OS keychain.
2
+ * Encrypted-at-rest key storage.
3
3
  *
4
4
  * v2 stores use a cryptographically random 32-byte `store.key` file as the
5
5
  * AES-256-GCM key directly (no key derivation). The key file lives alongside
@@ -8,7 +8,7 @@
8
8
  * v1 stores (legacy) derived the AES key from machine-specific entropy via
9
9
  * PBKDF2. Existing v1 stores are automatically migrated to v2 on first access.
10
10
  *
11
- * Provides the same get/set/delete interface as `keychain.ts`.
11
+ * Provides the standard get/set/delete credential storage interface.
12
12
  */
13
13
 
14
14
  import {
@@ -418,7 +418,7 @@ function decrypt(entry: EncryptedEntry, key: Buffer): string {
418
418
  }
419
419
 
420
420
  // ---------------------------------------------------------------------------
421
- // Public API -- matches keychain.ts interface
421
+ // Public API
422
422
  // ---------------------------------------------------------------------------
423
423
 
424
424
  /**
@@ -289,6 +289,15 @@ function startLoopbackServerAndWaitForCode(
289
289
  let boundRedirectUri = "";
290
290
 
291
291
  const server: Server = createServer((req, res) => {
292
+ log.info(
293
+ {
294
+ method: req.method,
295
+ path: new URL(req.url ?? "/", "http://127.0.0.1").pathname,
296
+ settled,
297
+ },
298
+ "oauth2 loopback: received request",
299
+ );
300
+
292
301
  if (settled) {
293
302
  res.writeHead(400, { "Content-Type": "text/html" });
294
303
  res.end(renderLoopbackPage("Authorization already completed", false));
@@ -298,6 +307,10 @@ function startLoopbackServerAndWaitForCode(
298
307
  const url = new URL(req.url ?? "/", `http://127.0.0.1`);
299
308
 
300
309
  if (url.pathname !== LOOPBACK_CALLBACK_PATH) {
310
+ log.info(
311
+ { pathname: url.pathname },
312
+ "oauth2 loopback: non-callback path, returning 404",
313
+ );
301
314
  res.writeHead(404, { "Content-Type": "text/plain" });
302
315
  res.end("Not found");
303
316
  return;
@@ -308,6 +321,7 @@ function startLoopbackServerAndWaitForCode(
308
321
  const error = url.searchParams.get("error");
309
322
 
310
323
  if (callbackState !== state) {
324
+ log.warn("oauth2 loopback: state mismatch in callback");
311
325
  res.writeHead(400, { "Content-Type": "text/html" });
312
326
  res.end(renderLoopbackPage("Invalid state parameter", false));
313
327
  return;
@@ -317,6 +331,10 @@ function startLoopbackServerAndWaitForCode(
317
331
 
318
332
  if (error) {
319
333
  const errorDesc = url.searchParams.get("error_description") ?? error;
334
+ log.error(
335
+ { error, errorDesc },
336
+ "oauth2 loopback: authorization denied by user/provider",
337
+ );
320
338
  res.writeHead(200, { "Content-Type": "text/html" });
321
339
  res.end(
322
340
  renderLoopbackPage(`Authorization failed: ${errorDesc}`, false),
@@ -327,6 +345,7 @@ function startLoopbackServerAndWaitForCode(
327
345
  }
328
346
 
329
347
  if (!code) {
348
+ log.error("oauth2 loopback: callback missing authorization code");
330
349
  res.writeHead(400, { "Content-Type": "text/html" });
331
350
  res.end(renderLoopbackPage("Missing authorization code", false));
332
351
  cleanup();
@@ -334,6 +353,9 @@ function startLoopbackServerAndWaitForCode(
334
353
  return;
335
354
  }
336
355
 
356
+ log.info(
357
+ "oauth2 loopback: authorization code received, exchanging for tokens",
358
+ );
337
359
  res.writeHead(200, { "Content-Type": "text/html" });
338
360
  res.end(
339
361
  renderLoopbackPage(
@@ -347,6 +369,10 @@ function startLoopbackServerAndWaitForCode(
347
369
 
348
370
  const timeout = setTimeout(() => {
349
371
  if (!settled) {
372
+ log.warn(
373
+ { timeoutMs: LOOPBACK_TIMEOUT_MS, state },
374
+ "oauth2 loopback: callback timed out — no authorization code received",
375
+ );
350
376
  settled = true;
351
377
  cleanup();
352
378
  reject(new Error("OAuth2 loopback callback timed out"));
@@ -359,10 +385,20 @@ function startLoopbackServerAndWaitForCode(
359
385
  server.close();
360
386
  }
361
387
 
388
+ log.info(
389
+ { requestedPort: loopbackPort ?? "random" },
390
+ "oauth2 loopback: binding server",
391
+ );
392
+
362
393
  server.listen(loopbackPort ?? 0, "localhost", () => {
363
394
  const addr = server.address() as { port: number };
364
395
  boundRedirectUri = `http://localhost:${addr.port}${LOOPBACK_CALLBACK_PATH}`;
365
396
 
397
+ log.info(
398
+ { port: addr.port, redirectUri: boundRedirectUri },
399
+ "oauth2 loopback: server listening",
400
+ );
401
+
366
402
  const authParams = new URLSearchParams({
367
403
  ...config.extraParams,
368
404
  client_id: config.clientId,
@@ -375,10 +411,19 @@ function startLoopbackServerAndWaitForCode(
375
411
  });
376
412
 
377
413
  const authUrl = `${config.authUrl}?${authParams}`;
414
+ log.info(
415
+ { authUrlLength: authUrl.length, state },
416
+ "oauth2 loopback: built auth URL, calling openUrl callback",
417
+ );
378
418
  callbacks.openUrl(authUrl);
419
+ log.info("oauth2 loopback: openUrl callback returned");
379
420
  });
380
421
 
381
422
  server.on("error", (err) => {
423
+ log.error(
424
+ { err: err.message, loopbackPort },
425
+ "oauth2 loopback: server error",
426
+ );
382
427
  if (!settled) {
383
428
  settled = true;
384
429
  cleanup();
@@ -687,6 +732,16 @@ export async function startOAuth2Flow(
687
732
  const transport =
688
733
  options?.callbackTransport ?? (hasPublicUrl ? "gateway" : "loopback");
689
734
 
735
+ log.info(
736
+ {
737
+ transport,
738
+ hasPublicUrl,
739
+ explicitTransport: options?.callbackTransport,
740
+ loopbackPort: options?.loopbackPort,
741
+ },
742
+ "startOAuth2Flow: resolved transport",
743
+ );
744
+
690
745
  if (transport === "gateway") {
691
746
  if (!hasPublicUrl) {
692
747
  throw new Error(
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Ingress secret detection for user messages.
3
+ *
4
+ * Consumes `PREFIX_PATTERNS` from `secret-patterns.ts` — the single source
5
+ * of truth for prefix-based secret detection. This module intentionally
6
+ * does NOT import `scanText()` or any entropy/encoding logic to avoid
7
+ * false positives on legitimate user input.
8
+ */
9
+
10
+ import { getConfig } from "../config/loader.js";
11
+ import { isAllowlisted } from "./secret-allowlist.js";
12
+ import { PREFIX_PATTERNS } from "./secret-patterns.js";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export interface IngressCheckResult {
19
+ blocked: boolean;
20
+ detectedTypes: string[];
21
+ userNotice?: string;
22
+ }
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Placeholder detection (inline — not imported from secret-scanner.ts)
26
+ // ---------------------------------------------------------------------------
27
+
28
+ const KNOWN_PLACEHOLDERS = new Set([
29
+ "your-api-key-here",
30
+ "your_api_key_here",
31
+ "insert-your-key-here",
32
+ "insert_your_key_here",
33
+ "replace-with-your-key",
34
+ "replace_with_your_key",
35
+ "xxx",
36
+ "xxxxxxxx",
37
+ "test",
38
+ "example",
39
+ "sample",
40
+ "demo",
41
+ "placeholder",
42
+ "changeme",
43
+ "CHANGEME",
44
+ "TODO",
45
+ "FIXME",
46
+ "your-token-here",
47
+ "your_token_here",
48
+ "my-api-key",
49
+ "my_api_key",
50
+ ]);
51
+
52
+ const PLACEHOLDER_PREFIXES = [
53
+ "sk-test-",
54
+ "sk_test_",
55
+ "fake_",
56
+ "fake-",
57
+ "dummy_",
58
+ "dummy-",
59
+ "test_",
60
+ "test-",
61
+ "example_",
62
+ "example-",
63
+ "sample_",
64
+ "sample-",
65
+ "mock_",
66
+ "mock-",
67
+ ];
68
+
69
+ /**
70
+ * Check if the text immediately before a matched value indicates
71
+ * a placeholder context (e.g. "fake_", "test_").
72
+ */
73
+ function isPlaceholderContext(preContext: string): boolean {
74
+ const lower = preContext.toLowerCase();
75
+ for (const prefix of PLACEHOLDER_PREFIXES) {
76
+ if (lower.endsWith(prefix)) return true;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ /**
82
+ * Check if a matched value is a placeholder/test value that should not
83
+ * trigger blocking.
84
+ */
85
+ function isPlaceholder(value: string): boolean {
86
+ const lower = value.toLowerCase();
87
+
88
+ // Known placeholder values
89
+ if (KNOWN_PLACEHOLDERS.has(lower)) return true;
90
+
91
+ // Placeholder prefixes
92
+ for (const prefix of PLACEHOLDER_PREFIXES) {
93
+ if (lower.startsWith(prefix)) return true;
94
+ }
95
+
96
+ // Repeated characters in the variable portion (e.g. "AKIA" + "X" x 16)
97
+ // Strip known prefixes to isolate the variable part
98
+ const variablePart = value
99
+ .replace(
100
+ /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
101
+ "",
102
+ )
103
+ .replace(/[^A-Za-z0-9]/g, "");
104
+ if (variablePart.length >= 8) {
105
+ const firstChar = variablePart[0];
106
+ if (variablePart.split("").every((c) => c === firstChar)) return true;
107
+ }
108
+
109
+ return false;
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // Public API
114
+ // ---------------------------------------------------------------------------
115
+
116
+ /**
117
+ * Check user message content for high-confidence secret patterns.
118
+ *
119
+ * Returns `{ blocked: true, detectedTypes, userNotice }` if secrets are
120
+ * found and blocking is enabled, otherwise `{ blocked: false }`.
121
+ */
122
+ export function checkIngressForSecrets(content: string): IngressCheckResult {
123
+ const config = getConfig();
124
+ const secretDetection = config?.secretDetection;
125
+
126
+ // Bail if secret detection config is missing or entirely disabled
127
+ if (!secretDetection?.enabled) {
128
+ return { blocked: false, detectedTypes: [] };
129
+ }
130
+
131
+ // Bail if ingress blocking is disabled
132
+ if (!secretDetection.blockIngress) {
133
+ return { blocked: false, detectedTypes: [] };
134
+ }
135
+
136
+ const detectedTypes: string[] = [];
137
+
138
+ for (const { label, regex } of PREFIX_PATTERNS) {
139
+ // Use a global version to find all matches
140
+ const globalRegex = new RegExp(regex.source, "g");
141
+ let match: RegExpExecArray | null;
142
+
143
+ while ((match = globalRegex.exec(content)) !== null) {
144
+ const value = match[0];
145
+
146
+ // Skip placeholders and test values (check both the match and
147
+ // a small window before it for placeholder prefixes like "fake_")
148
+ const contextStart = Math.max(0, match.index - 10);
149
+ const preContext = content.slice(contextStart, match.index);
150
+ if (isPlaceholder(value) || isPlaceholderContext(preContext)) continue;
151
+
152
+ // Skip user-allowlisted values
153
+ if (isAllowlisted(value)) continue;
154
+
155
+ if (!detectedTypes.includes(label)) {
156
+ detectedTypes.push(label);
157
+ }
158
+ }
159
+ }
160
+
161
+ if (detectedTypes.length === 0) {
162
+ return { blocked: false, detectedTypes: [] };
163
+ }
164
+
165
+ return {
166
+ blocked: true,
167
+ detectedTypes,
168
+ userNotice:
169
+ `Message blocked: detected ` +
170
+ `${detectedTypes.length === 1 ? "a potential credential" : "potential credentials"} ` +
171
+ `(${detectedTypes.join(", ")}). ` +
172
+ `Use the secure credential prompt to provide sensitive values safely.`,
173
+ };
174
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Shared prefix-based secret patterns — the single source of truth.
3
+ *
4
+ * Ingress blocking, tool output scanning, and log redaction all consume
5
+ * this list. When adding a new integration, add its API key pattern here.
6
+ *
7
+ * This module is intentionally data-only: no imports, no entropy logic,
8
+ * no config — safe for hot-path consumers like log serializers.
9
+ */
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Types
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export interface SecretPrefixPattern {
16
+ /** Human-readable label shown in block notices and redaction tags. */
17
+ label: string;
18
+ /**
19
+ * Regex that matches the token value. Must NOT include the `g` flag or
20
+ * capture groups — consumers add those as needed.
21
+ */
22
+ regex: RegExp;
23
+ }
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Prefix patterns
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * High-confidence, prefix-based secret patterns.
31
+ *
32
+ * Each entry matches a known API key / token format by its distinctive
33
+ * prefix. Patterns that require surrounding context (entropy analysis,
34
+ * keyword proximity, URL structure) do NOT belong here — they stay in
35
+ * `secret-scanner.ts` as scanner-only patterns.
36
+ *
37
+ * **When adding a new third-party integration, add its API key pattern
38
+ * here.** If the service uses only opaque OAuth access tokens (no fixed
39
+ * prefix), no pattern is needed.
40
+ */
41
+ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
42
+ // -- AWS --
43
+ { label: "AWS Access Key", regex: /AKIA[0-9A-Z]{16}/ },
44
+
45
+ // -- GitHub --
46
+ { label: "GitHub Token", regex: /gh[pousr]_[A-Za-z0-9_]{36,}/ },
47
+ { label: "GitHub Fine-Grained PAT", regex: /github_pat_[A-Za-z0-9_]{22,}/ },
48
+
49
+ // -- GitLab --
50
+ { label: "GitLab Token", regex: /glpat-[A-Za-z0-9\-_]{20,}/ },
51
+
52
+ // -- Stripe --
53
+ { label: "Stripe Secret Key", regex: /sk_live_[A-Za-z0-9]{24,}/ },
54
+ { label: "Stripe Restricted Key", regex: /rk_live_[A-Za-z0-9]{24,}/ },
55
+
56
+ // -- Slack --
57
+ {
58
+ label: "Slack Bot Token",
59
+ regex: /xoxb-[0-9]{10,}-[0-9]{10,}-[A-Za-z0-9]{24,}/,
60
+ },
61
+ {
62
+ label: "Slack User Token",
63
+ regex: /xoxp-[0-9]{10,}-[0-9]{10,}-[0-9]{10,}-[a-f0-9]{32}/,
64
+ },
65
+ {
66
+ label: "Slack App Token",
67
+ regex: /xapp-[0-9]+-[A-Za-z0-9]+-[0-9]+-[A-Za-z0-9]+/,
68
+ },
69
+
70
+ // -- Telegram --
71
+ {
72
+ label: "Telegram Bot Token",
73
+ // Format: <bot_id>:<secret> where bot_id is 8–10 digits, secret is 35 chars
74
+ regex: /[0-9]{8,10}:[A-Za-z0-9_-]{35}/,
75
+ },
76
+
77
+ // -- Anthropic --
78
+ { label: "Anthropic API Key", regex: /sk-ant-[A-Za-z0-9\-_]{80,}/ },
79
+
80
+ // -- OpenAI --
81
+ {
82
+ label: "OpenAI API Key",
83
+ regex: /sk-[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9]{20}/,
84
+ },
85
+ { label: "OpenAI Project Key", regex: /sk-proj-[A-Za-z0-9\-_]{40,}/ },
86
+
87
+ // -- Google --
88
+ { label: "Google API Key", regex: /AIza[A-Za-z0-9\-_]{35}/ },
89
+ {
90
+ label: "Google OAuth Client Secret",
91
+ regex: /GOCSPX-[A-Za-z0-9\-_]{28}/,
92
+ },
93
+
94
+ // -- Twilio --
95
+ { label: "Twilio API Key", regex: /SK[0-9a-f]{32}/ },
96
+
97
+ // -- SendGrid --
98
+ {
99
+ label: "SendGrid API Key",
100
+ regex: /SG\.[A-Za-z0-9\-_]{22}\.[A-Za-z0-9\-_]{43}/,
101
+ },
102
+
103
+ // -- Mailgun --
104
+ { label: "Mailgun API Key", regex: /key-[A-Za-z0-9]{32}/ },
105
+
106
+ // -- npm --
107
+ { label: "npm Token", regex: /npm_[A-Za-z0-9]{36}/ },
108
+
109
+ // -- PyPI --
110
+ { label: "PyPI API Token", regex: /pypi-[A-Za-z0-9\-_]{50,}/ },
111
+
112
+ // -- Private keys --
113
+ {
114
+ label: "Private Key",
115
+ regex:
116
+ /-----BEGIN (?:RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY(?:\s+BLOCK)?-----/,
117
+ },
118
+
119
+ // -- Linear --
120
+ { label: "Linear API Key", regex: /lin_api_[A-Za-z0-9]{32,}/ },
121
+
122
+ // -- Notion --
123
+ { label: "Notion Integration Token", regex: /ntn_[A-Za-z0-9]{40,}/ },
124
+
125
+ // -- OpenRouter --
126
+ { label: "OpenRouter API Key", regex: /sk-or-v1-[A-Za-z0-9\-_]{40,}/ },
127
+
128
+ // -- Fireworks --
129
+ { label: "Fireworks API Key", regex: /fw_[A-Za-z0-9]{32,}/ },
130
+
131
+ // -- Perplexity --
132
+ { label: "Perplexity API Key", regex: /pplx-[A-Za-z0-9]{40,}/ },
133
+ ];