@vellumai/assistant 0.11.2-dev.202608060508.a7ab7f7 → 0.11.2-dev.202608060701.ede4331
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 +54 -1
- package/openapi.yaml +162 -3
- package/package.json +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
- package/src/__tests__/mtime-cache.test.ts +65 -3
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/run-due-schedules.test.ts +174 -0
- package/src/__tests__/schedule-routes.test.ts +242 -0
- package/src/__tests__/schedule-store.test.ts +371 -0
- package/src/__tests__/schedule-tools.test.ts +103 -0
- package/src/acp/failure-error.ts +3 -5
- package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
- package/src/cli/commands/__tests__/plugins.test.ts +692 -0
- package/src/cli/commands/__tests__/schedules.test.ts +136 -37
- package/src/cli/commands/plugins.help.ts +25 -2
- package/src/cli/commands/plugins.ts +289 -10
- package/src/cli/commands/schedules.ts +12 -3
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
- package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
- package/src/cli/lib/install-from-github.ts +61 -0
- package/src/cli/lib/install-from-platform.ts +13 -2
- package/src/cli/lib/plugin-surfaces.ts +124 -4
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/feature-flag-registry.json +8 -0
- package/src/daemon/lifecycle.ts +11 -0
- package/src/notifications/__tests__/broadcaster.test.ts +37 -1
- package/src/notifications/__tests__/copy-composer.test.ts +165 -0
- package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
- package/src/notifications/broadcaster.ts +30 -10
- package/src/notifications/copy-composer.ts +69 -0
- package/src/notifications/emit-signal.ts +60 -5
- package/src/notifications/events-store.ts +10 -2
- package/src/notifications/notification-utils.ts +6 -5
- package/src/notifications/signal.ts +12 -0
- package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
- package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
- package/src/persistence/schema/infrastructure.ts +3 -0
- package/src/persistence/steps.ts +2 -0
- package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
- package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
- package/src/plugins/collect-source-versions.ts +4 -27
- package/src/plugins/external-plugin-loader.ts +17 -8
- package/src/plugins/installed-plugin-dirs.ts +101 -0
- package/src/plugins/mtime-cache.ts +34 -22
- package/src/runtime/http-server.ts +12 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +59 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +89 -1
- package/src/runtime/routes/conversation-management-routes.ts +10 -4
- package/src/runtime/routes/plugins-routes.ts +56 -1
- package/src/runtime/routes/schedule-routes.ts +65 -5
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
- package/src/schedule/plugin-schedule-declarations.ts +518 -0
- package/src/schedule/plugin-schedule-reconciler.ts +605 -0
- package/src/schedule/plugin-schedules-gate.ts +23 -0
- package/src/schedule/schedule-store.ts +469 -20
- package/src/schedule/scheduler.ts +38 -0
- package/src/skills/inline-command-runner.ts +2 -7
- package/src/tools/schedule/delete.ts +13 -1
- package/src/tools/schedule/list.ts +12 -2
- package/src/tools/schedule/update.ts +46 -27
- package/src/util/ansi.ts +44 -0
- package/src/util/schedule-source-key.ts +23 -0
package/ARCHITECTURE.md
CHANGED
|
@@ -838,7 +838,7 @@ graph LR
|
|
|
838
838
|
JOBS["memory_jobs<br/>───────────────<br/>Async task queue<br/>Types: embed, extract,<br/>summarize, backfill, cleanup<br/>Status: pending → running →<br/>completed | failed"]
|
|
839
839
|
ATT["attachments<br/>───────────────<br/>base64-encoded file data<br/>mime_type, size_bytes<br/>Linked to messages via<br/>message_attachments join"]
|
|
840
840
|
REM["reminders<br/>───────────────<br/>One-time scheduled reminders<br/>label, message, fireAt<br/>mode: notify | execute<br/>status: pending → fired | cancelled<br/>routing_intent: single_channel |<br/>multi_channel | all_channels<br/>routing_hints_json (free-form)"]
|
|
841
|
-
SCHED_JOBS["cron_jobs (recurrence schedules)<br/>───────────────<br/>Recurring schedule definitions<br/>cron_expression: cron or RRULE string<br/>schedule_syntax: 'cron' | 'rrule'<br/>timezone, message, next_run_at<br/>enabled, retry_count<br/>Legacy alias: scheduleJobs"]
|
|
841
|
+
SCHED_JOBS["cron_jobs (recurrence schedules)<br/>───────────────<br/>Recurring schedule definitions<br/>cron_expression: cron or RRULE string<br/>schedule_syntax: 'cron' | 'rrule'<br/>timezone, message, next_run_at<br/>enabled, retry_count<br/>source_key: plugin-declared provenance<br/>definition_hash, user_enabled<br/>Legacy alias: scheduleJobs"]
|
|
842
842
|
SCHED_RUNS["cron_runs (schedule runs)<br/>───────────────<br/>Execution history per schedule<br/>job_id (FK → cron_jobs)<br/>status: ok | error<br/>duration_ms, output, error<br/>Legacy alias: scheduleRuns"]
|
|
843
843
|
TASKS["tasks<br/>───────────────<br/>Reusable prompt templates<br/>title, Handlebars template<br/>inputSchema, contextFlags<br/>requiredTools, status"]
|
|
844
844
|
TASK_RUNS["task_runs<br/>───────────────<br/>Execution history per task<br/>taskId (FK → tasks)<br/>conversationId, status<br/>startedAt, finishedAt, error"]
|
|
@@ -863,6 +863,59 @@ graph LR
|
|
|
863
863
|
|
|
864
864
|
---
|
|
865
865
|
|
|
866
|
+
## Plugin-Declared Schedules: Declaration → Reconciler → cron_jobs
|
|
867
|
+
|
|
868
|
+
Plugins contribute recurring schedules as a surface: directories under
|
|
869
|
+
`<pluginDir>/schedules/`, each a `<name>/` holding `config.json` plus
|
|
870
|
+
exactly one `index.md` (runs as `execute`) or `index.sh` (runs as `script`)
|
|
871
|
+
entrypoint. Ambiguity fails closed: a file sitting directly under
|
|
872
|
+
`schedules/`, zero/multiple/unsupported entrypoints, and malformed config
|
|
873
|
+
produce per-schedule `DeclarationError`s without affecting siblings
|
|
874
|
+
(`src/schedule/plugin-schedule-declarations.ts`).
|
|
875
|
+
|
|
876
|
+
A level-based reconciler (`src/schedule/plugin-schedule-reconciler.ts`)
|
|
877
|
+
converges declarations into ordinary `cron_jobs` rows keyed by the nullable
|
|
878
|
+
`source_key` column (`plugin:<plugin>/<name>`). Rows with `source_key IS
|
|
879
|
+
NULL` are imperative schedules the reconciler never touches. Triggers: a
|
|
880
|
+
startup pass in `daemon/lifecycle.ts` (after plugin init, before the
|
|
881
|
+
scheduler starts), the end of `reconcilePluginSourcesNow()` in
|
|
882
|
+
`plugins/mtime-cache.ts` (install/uninstall/upgrade and sentinel-driven
|
|
883
|
+
changes), the plugin enable/disable routes, and a 60s backstop sweep
|
|
884
|
+
registered with the HTTP server's background sweeps.
|
|
885
|
+
|
|
886
|
+
Reconcile lag never lets a disabled plugin run. The disable path writes a
|
|
887
|
+
`.disabled` sentinel that only a reconcile pass turns into disarmed rows, so
|
|
888
|
+
the scheduler re-reads the sentinel at fire time and records a skipped run
|
|
889
|
+
instead of executing a claimed row whose plugin is off. Run-now applies the
|
|
890
|
+
same boundary through `declarationExistsOnDisk`, which also covers a plugin
|
|
891
|
+
whose manifest no longer parses, a declaration directory that is gone, and a
|
|
892
|
+
plugin root or declaration directory resolving outside the tree it belongs to
|
|
893
|
+
(the same `isInsidePluginRoot` containment the loader applies before importing
|
|
894
|
+
a plugin).
|
|
895
|
+
|
|
896
|
+
A declaration that stops parsing keeps its execute row armed on the message
|
|
897
|
+
already stored in the row, but disarms its script rows: a script row fires its
|
|
898
|
+
entrypoint by absolute path, so leaving it armed would run the very `index.sh`
|
|
899
|
+
the parser rejected. Those rows re-arm on the pass after the declaration parses
|
|
900
|
+
again.
|
|
901
|
+
|
|
902
|
+
Ownership boundaries: the reconciler owns definition columns (expression,
|
|
903
|
+
timezone, message/script, retry policy, `definition_hash`); the execution
|
|
904
|
+
engine owns runtime columns (`next_run_at`, `status`, `last_*`,
|
|
905
|
+
`retry_count`) and its latches are never overridden; the user owns
|
|
906
|
+
`user_enabled`, a sticky override consulted when computing effective
|
|
907
|
+
`enabled`. Nothing ever writes to plugin files. Execution itself is
|
|
908
|
+
unchanged: declared rows fire through the same `claimDueSchedules` path as
|
|
909
|
+
imperative ones.
|
|
910
|
+
|
|
911
|
+
Consent and surfacing: CLI install/upgrade list declared schedules and
|
|
912
|
+
prompt before finalizing; unattended daemon-route installs are covered by
|
|
913
|
+
`schedule.declared` arrival notifications, with `schedule.definition_changed`
|
|
914
|
+
and `schedule.definition_error` covering upgrades and broken declarations
|
|
915
|
+
(events registered in `notifications/signal.ts`, deterministic templates in
|
|
916
|
+
`notifications/copy-composer.ts`). Settings routes, CLI, and web render
|
|
917
|
+
plugin-sourced rows read-only except the enabled toggle.
|
|
918
|
+
|
|
866
919
|
---
|
|
867
920
|
|
|
868
921
|
## Web Server — Connection Modes
|
package/openapi.yaml
CHANGED
|
@@ -24245,14 +24245,43 @@ paths:
|
|
|
24245
24245
|
description:
|
|
24246
24246
|
Registered tool names from `tools/<name>.{ts,js}` (filenames derived to tool names, e.g. `create-issue` →
|
|
24247
24247
|
`create_issue`).
|
|
24248
|
+
schedules:
|
|
24249
|
+
type: array
|
|
24250
|
+
items:
|
|
24251
|
+
type: object
|
|
24252
|
+
properties:
|
|
24253
|
+
name:
|
|
24254
|
+
type: string
|
|
24255
|
+
description: "Schedule name: the declaration directory's name."
|
|
24256
|
+
cadence:
|
|
24257
|
+
type: string
|
|
24258
|
+
description: Raw schedule `expression` string from the declaration's config.
|
|
24259
|
+
mode:
|
|
24260
|
+
type: string
|
|
24261
|
+
enum:
|
|
24262
|
+
- execute
|
|
24263
|
+
- script
|
|
24264
|
+
description: "`execute` for a markdown prompt entrypoint, `script` for `index.sh`."
|
|
24265
|
+
required:
|
|
24266
|
+
- name
|
|
24267
|
+
- cadence
|
|
24268
|
+
- mode
|
|
24269
|
+
additionalProperties: false
|
|
24270
|
+
description:
|
|
24271
|
+
Schedules declared under `schedules/`, each a `<name>/` directory with `config.json` plus one entrypoint.
|
|
24272
|
+
Display surface only; files directly under `schedules/` and unsupported declarations are
|
|
24273
|
+
omitted.
|
|
24248
24274
|
required:
|
|
24249
24275
|
- skills
|
|
24250
24276
|
- hooks
|
|
24251
24277
|
- tools
|
|
24278
|
+
- schedules
|
|
24252
24279
|
additionalProperties: false
|
|
24253
24280
|
description: Surfaces the installed copy contributes, read from its on-disk tree.
|
|
24254
24281
|
- type: "null"
|
|
24255
|
-
description:
|
|
24282
|
+
description:
|
|
24283
|
+
Surfaces the installed copy contributes (skills, hooks, tools, schedules); null when the plugin is not
|
|
24284
|
+
installed.
|
|
24256
24285
|
required:
|
|
24257
24286
|
- name
|
|
24258
24287
|
- installed
|
|
@@ -25598,6 +25627,20 @@ paths:
|
|
|
25598
25627
|
anyOf:
|
|
25599
25628
|
- type: string
|
|
25600
25629
|
- type: "null"
|
|
25630
|
+
sourceKey:
|
|
25631
|
+
anyOf:
|
|
25632
|
+
- type: string
|
|
25633
|
+
- type: "null"
|
|
25634
|
+
description:
|
|
25635
|
+
Plugin declaration this schedule mirrors (plugin:<pluginName>/<scheduleName>); null for user-created
|
|
25636
|
+
schedules
|
|
25637
|
+
userEnabled:
|
|
25638
|
+
anyOf:
|
|
25639
|
+
- type: boolean
|
|
25640
|
+
- type: "null"
|
|
25641
|
+
description:
|
|
25642
|
+
User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
|
|
25643
|
+
applies. Always null for user-created schedules.
|
|
25601
25644
|
isOneShot:
|
|
25602
25645
|
type: boolean
|
|
25603
25646
|
isDeferred:
|
|
@@ -25632,6 +25675,8 @@ paths:
|
|
|
25632
25675
|
- reuseConversation
|
|
25633
25676
|
- wakeConversationId
|
|
25634
25677
|
- workflowName
|
|
25678
|
+
- sourceKey
|
|
25679
|
+
- userEnabled
|
|
25635
25680
|
- isOneShot
|
|
25636
25681
|
- isDeferred
|
|
25637
25682
|
additionalProperties: false
|
|
@@ -25815,6 +25860,20 @@ paths:
|
|
|
25815
25860
|
anyOf:
|
|
25816
25861
|
- type: string
|
|
25817
25862
|
- type: "null"
|
|
25863
|
+
sourceKey:
|
|
25864
|
+
anyOf:
|
|
25865
|
+
- type: string
|
|
25866
|
+
- type: "null"
|
|
25867
|
+
description:
|
|
25868
|
+
Plugin declaration this schedule mirrors (plugin:<pluginName>/<scheduleName>); null for user-created
|
|
25869
|
+
schedules
|
|
25870
|
+
userEnabled:
|
|
25871
|
+
anyOf:
|
|
25872
|
+
- type: boolean
|
|
25873
|
+
- type: "null"
|
|
25874
|
+
description:
|
|
25875
|
+
User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
|
|
25876
|
+
applies. Always null for user-created schedules.
|
|
25818
25877
|
isOneShot:
|
|
25819
25878
|
type: boolean
|
|
25820
25879
|
isDeferred:
|
|
@@ -25849,6 +25908,8 @@ paths:
|
|
|
25849
25908
|
- reuseConversation
|
|
25850
25909
|
- wakeConversationId
|
|
25851
25910
|
- workflowName
|
|
25911
|
+
- sourceKey
|
|
25912
|
+
- userEnabled
|
|
25852
25913
|
- isOneShot
|
|
25853
25914
|
- isDeferred
|
|
25854
25915
|
additionalProperties: false
|
|
@@ -25984,6 +26045,20 @@ paths:
|
|
|
25984
26045
|
anyOf:
|
|
25985
26046
|
- type: string
|
|
25986
26047
|
- type: "null"
|
|
26048
|
+
sourceKey:
|
|
26049
|
+
anyOf:
|
|
26050
|
+
- type: string
|
|
26051
|
+
- type: "null"
|
|
26052
|
+
description:
|
|
26053
|
+
Plugin declaration this schedule mirrors (plugin:<pluginName>/<scheduleName>); null for user-created
|
|
26054
|
+
schedules
|
|
26055
|
+
userEnabled:
|
|
26056
|
+
anyOf:
|
|
26057
|
+
- type: boolean
|
|
26058
|
+
- type: "null"
|
|
26059
|
+
description:
|
|
26060
|
+
User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
|
|
26061
|
+
applies. Always null for user-created schedules.
|
|
25987
26062
|
isOneShot:
|
|
25988
26063
|
type: boolean
|
|
25989
26064
|
isDeferred:
|
|
@@ -26018,6 +26093,8 @@ paths:
|
|
|
26018
26093
|
- reuseConversation
|
|
26019
26094
|
- wakeConversationId
|
|
26020
26095
|
- workflowName
|
|
26096
|
+
- sourceKey
|
|
26097
|
+
- userEnabled
|
|
26021
26098
|
- isOneShot
|
|
26022
26099
|
- isDeferred
|
|
26023
26100
|
additionalProperties: false
|
|
@@ -26150,6 +26227,20 @@ paths:
|
|
|
26150
26227
|
anyOf:
|
|
26151
26228
|
- type: string
|
|
26152
26229
|
- type: "null"
|
|
26230
|
+
sourceKey:
|
|
26231
|
+
anyOf:
|
|
26232
|
+
- type: string
|
|
26233
|
+
- type: "null"
|
|
26234
|
+
description:
|
|
26235
|
+
Plugin declaration this schedule mirrors (plugin:<pluginName>/<scheduleName>); null for user-created
|
|
26236
|
+
schedules
|
|
26237
|
+
userEnabled:
|
|
26238
|
+
anyOf:
|
|
26239
|
+
- type: boolean
|
|
26240
|
+
- type: "null"
|
|
26241
|
+
description:
|
|
26242
|
+
User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
|
|
26243
|
+
applies. Always null for user-created schedules.
|
|
26153
26244
|
isOneShot:
|
|
26154
26245
|
type: boolean
|
|
26155
26246
|
isDeferred:
|
|
@@ -26184,6 +26275,8 @@ paths:
|
|
|
26184
26275
|
- reuseConversation
|
|
26185
26276
|
- wakeConversationId
|
|
26186
26277
|
- workflowName
|
|
26278
|
+
- sourceKey
|
|
26279
|
+
- userEnabled
|
|
26187
26280
|
- isOneShot
|
|
26188
26281
|
- isDeferred
|
|
26189
26282
|
additionalProperties: false
|
|
@@ -26376,6 +26469,20 @@ paths:
|
|
|
26376
26469
|
anyOf:
|
|
26377
26470
|
- type: string
|
|
26378
26471
|
- type: "null"
|
|
26472
|
+
sourceKey:
|
|
26473
|
+
anyOf:
|
|
26474
|
+
- type: string
|
|
26475
|
+
- type: "null"
|
|
26476
|
+
description:
|
|
26477
|
+
Plugin declaration this schedule mirrors (plugin:<pluginName>/<scheduleName>); null for user-created
|
|
26478
|
+
schedules
|
|
26479
|
+
userEnabled:
|
|
26480
|
+
anyOf:
|
|
26481
|
+
- type: boolean
|
|
26482
|
+
- type: "null"
|
|
26483
|
+
description:
|
|
26484
|
+
User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
|
|
26485
|
+
applies. Always null for user-created schedules.
|
|
26379
26486
|
isOneShot:
|
|
26380
26487
|
type: boolean
|
|
26381
26488
|
isDeferred:
|
|
@@ -26410,6 +26517,8 @@ paths:
|
|
|
26410
26517
|
- reuseConversation
|
|
26411
26518
|
- wakeConversationId
|
|
26412
26519
|
- workflowName
|
|
26520
|
+
- sourceKey
|
|
26521
|
+
- userEnabled
|
|
26413
26522
|
- isOneShot
|
|
26414
26523
|
- isDeferred
|
|
26415
26524
|
additionalProperties: false
|
|
@@ -26545,6 +26654,20 @@ paths:
|
|
|
26545
26654
|
anyOf:
|
|
26546
26655
|
- type: string
|
|
26547
26656
|
- type: "null"
|
|
26657
|
+
sourceKey:
|
|
26658
|
+
anyOf:
|
|
26659
|
+
- type: string
|
|
26660
|
+
- type: "null"
|
|
26661
|
+
description:
|
|
26662
|
+
Plugin declaration this schedule mirrors (plugin:<pluginName>/<scheduleName>); null for user-created
|
|
26663
|
+
schedules
|
|
26664
|
+
userEnabled:
|
|
26665
|
+
anyOf:
|
|
26666
|
+
- type: boolean
|
|
26667
|
+
- type: "null"
|
|
26668
|
+
description:
|
|
26669
|
+
User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
|
|
26670
|
+
applies. Always null for user-created schedules.
|
|
26548
26671
|
isOneShot:
|
|
26549
26672
|
type: boolean
|
|
26550
26673
|
isDeferred:
|
|
@@ -26579,6 +26702,8 @@ paths:
|
|
|
26579
26702
|
- reuseConversation
|
|
26580
26703
|
- wakeConversationId
|
|
26581
26704
|
- workflowName
|
|
26705
|
+
- sourceKey
|
|
26706
|
+
- userEnabled
|
|
26582
26707
|
- isOneShot
|
|
26583
26708
|
- isDeferred
|
|
26584
26709
|
additionalProperties: false
|
|
@@ -26590,7 +26715,9 @@ paths:
|
|
|
26590
26715
|
post:
|
|
26591
26716
|
operationId: schedules_by_id_run_post
|
|
26592
26717
|
summary: Run schedule now
|
|
26593
|
-
description:
|
|
26718
|
+
description:
|
|
26719
|
+
Trigger an immediate execution of a schedule. A plugin-sourced schedule is rejected with a 400 when its
|
|
26720
|
+
plugin is disabled or no longer declares it.
|
|
26594
26721
|
tags:
|
|
26595
26722
|
- schedules
|
|
26596
26723
|
parameters:
|
|
@@ -26714,6 +26841,20 @@ paths:
|
|
|
26714
26841
|
anyOf:
|
|
26715
26842
|
- type: string
|
|
26716
26843
|
- type: "null"
|
|
26844
|
+
sourceKey:
|
|
26845
|
+
anyOf:
|
|
26846
|
+
- type: string
|
|
26847
|
+
- type: "null"
|
|
26848
|
+
description:
|
|
26849
|
+
Plugin declaration this schedule mirrors (plugin:<pluginName>/<scheduleName>); null for user-created
|
|
26850
|
+
schedules
|
|
26851
|
+
userEnabled:
|
|
26852
|
+
anyOf:
|
|
26853
|
+
- type: boolean
|
|
26854
|
+
- type: "null"
|
|
26855
|
+
description:
|
|
26856
|
+
User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
|
|
26857
|
+
applies. Always null for user-created schedules.
|
|
26717
26858
|
isOneShot:
|
|
26718
26859
|
type: boolean
|
|
26719
26860
|
isDeferred:
|
|
@@ -26748,6 +26889,8 @@ paths:
|
|
|
26748
26889
|
- reuseConversation
|
|
26749
26890
|
- wakeConversationId
|
|
26750
26891
|
- workflowName
|
|
26892
|
+
- sourceKey
|
|
26893
|
+
- userEnabled
|
|
26751
26894
|
- isOneShot
|
|
26752
26895
|
- isDeferred
|
|
26753
26896
|
additionalProperties: false
|
|
@@ -26884,7 +27027,7 @@ paths:
|
|
|
26884
27027
|
post:
|
|
26885
27028
|
operationId: schedules_by_id_toggle_post
|
|
26886
27029
|
summary: Toggle schedule
|
|
26887
|
-
description: Enable or disable a schedule.
|
|
27030
|
+
description: Enable or disable a schedule. On a plugin-managed schedule this records the user's override (userEnabled).
|
|
26888
27031
|
tags:
|
|
26889
27032
|
- schedules
|
|
26890
27033
|
parameters:
|
|
@@ -27020,6 +27163,20 @@ paths:
|
|
|
27020
27163
|
anyOf:
|
|
27021
27164
|
- type: string
|
|
27022
27165
|
- type: "null"
|
|
27166
|
+
sourceKey:
|
|
27167
|
+
anyOf:
|
|
27168
|
+
- type: string
|
|
27169
|
+
- type: "null"
|
|
27170
|
+
description:
|
|
27171
|
+
Plugin declaration this schedule mirrors (plugin:<pluginName>/<scheduleName>); null for user-created
|
|
27172
|
+
schedules
|
|
27173
|
+
userEnabled:
|
|
27174
|
+
anyOf:
|
|
27175
|
+
- type: boolean
|
|
27176
|
+
- type: "null"
|
|
27177
|
+
description:
|
|
27178
|
+
User enable/disable override on a plugin-sourced schedule; null when the declaration's own enabled value
|
|
27179
|
+
applies. Always null for user-created schedules.
|
|
27023
27180
|
isOneShot:
|
|
27024
27181
|
type: boolean
|
|
27025
27182
|
isDeferred:
|
|
@@ -27054,6 +27211,8 @@ paths:
|
|
|
27054
27211
|
- reuseConversation
|
|
27055
27212
|
- wakeConversationId
|
|
27056
27213
|
- workflowName
|
|
27214
|
+
- sourceKey
|
|
27215
|
+
- userEnabled
|
|
27057
27216
|
- isOneShot
|
|
27058
27217
|
- isDeferred
|
|
27059
27218
|
additionalProperties: false
|
package/package.json
CHANGED
|
@@ -58,7 +58,10 @@ describe("schedule_syntax column migration", () => {
|
|
|
58
58
|
capabilities_json TEXT,
|
|
59
59
|
description TEXT NOT NULL DEFAULT '',
|
|
60
60
|
created_at INTEGER NOT NULL,
|
|
61
|
-
updated_at INTEGER NOT NULL
|
|
61
|
+
updated_at INTEGER NOT NULL,
|
|
62
|
+
source_key TEXT,
|
|
63
|
+
definition_hash TEXT,
|
|
64
|
+
user_enabled INTEGER
|
|
62
65
|
)
|
|
63
66
|
`);
|
|
64
67
|
|
|
@@ -5,7 +5,7 @@ const enforceRoutingIntentMock = mock();
|
|
|
5
5
|
const updateDecisionMock = mock();
|
|
6
6
|
const runDeterministicChecksMock = mock();
|
|
7
7
|
const createEventMock = mock();
|
|
8
|
-
const
|
|
8
|
+
const setEventDedupeKeyMock = mock();
|
|
9
9
|
const dispatchDecisionMock = mock();
|
|
10
10
|
const isPlatformClientConfiguredMock = mock();
|
|
11
11
|
|
|
@@ -77,8 +77,7 @@ mock.module("../notifications/deterministic-checks.js", () => ({
|
|
|
77
77
|
|
|
78
78
|
mock.module("../notifications/events-store.js", () => ({
|
|
79
79
|
createEvent: (...args: unknown[]) => createEventMock(...args),
|
|
80
|
-
|
|
81
|
-
updateEventDedupeKeyMock(...args),
|
|
80
|
+
setEventDedupeKey: (...args: unknown[]) => setEventDedupeKeyMock(...args),
|
|
82
81
|
}));
|
|
83
82
|
|
|
84
83
|
mock.module("../notifications/runtime-dispatch.js", () => ({
|
|
@@ -104,7 +103,7 @@ beforeEach(() => {
|
|
|
104
103
|
updateDecisionMock.mockReset();
|
|
105
104
|
runDeterministicChecksMock.mockReset();
|
|
106
105
|
createEventMock.mockReset();
|
|
107
|
-
|
|
106
|
+
setEventDedupeKeyMock.mockReset();
|
|
108
107
|
dispatchDecisionMock.mockReset();
|
|
109
108
|
isPlatformClientConfiguredMock.mockReset();
|
|
110
109
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
mkdirSync,
|
|
15
15
|
readFileSync,
|
|
16
16
|
rmSync,
|
|
17
|
+
symlinkSync,
|
|
17
18
|
utimesSync,
|
|
18
19
|
writeFileSync,
|
|
19
20
|
} from "node:fs";
|
|
@@ -507,17 +508,17 @@ describe("workspace hooks (<workspace>/hooks/)", () => {
|
|
|
507
508
|
writePackageJson(dir, { ...SIMPLE_PKG, name: "ordering-plugin" });
|
|
508
509
|
writeHook(
|
|
509
510
|
dir,
|
|
510
|
-
"
|
|
511
|
+
"conversation-deleted",
|
|
511
512
|
`export default () => ({ tag: "plugin" });`,
|
|
512
513
|
);
|
|
513
514
|
writeWorkspaceHook(
|
|
514
|
-
"
|
|
515
|
+
"conversation-deleted",
|
|
515
516
|
`export default () => ({ tag: "workspace" });`,
|
|
516
517
|
);
|
|
517
518
|
|
|
518
519
|
await populateCacheAtBoot();
|
|
519
520
|
|
|
520
|
-
const hooks = await getUserHooksFor("
|
|
521
|
+
const hooks = await getUserHooksFor("conversation-deleted");
|
|
521
522
|
expect(hooks).toHaveLength(2);
|
|
522
523
|
const results = hooks.map((fn) =>
|
|
523
524
|
(fn as unknown as () => { tag: string })(),
|
|
@@ -1004,4 +1005,65 @@ describe("plugin root validation (ATL-983)", () => {
|
|
|
1004
1005
|
// The evil plugin's init hook must NOT have run.
|
|
1005
1006
|
expect(existsSync(evilMarker)).toBe(false);
|
|
1006
1007
|
});
|
|
1008
|
+
|
|
1009
|
+
test("a symlinked plugin root escaping the plugins dir is not activated at boot", async () => {
|
|
1010
|
+
// A real plugin outside the plugins directory, linked in under a name the
|
|
1011
|
+
// boot scan reads. Hook resolution follows the link, so discovery is the
|
|
1012
|
+
// only thing between boot and code from anywhere on disk.
|
|
1013
|
+
const escapee = join(ROOT, "outside", "escapee");
|
|
1014
|
+
mkdirSync(escapee, { recursive: true });
|
|
1015
|
+
writePackageJson(escapee, { ...SIMPLE_PKG, name: "escapee" });
|
|
1016
|
+
const escapeeMarker = join(ROOT, "escapee-init.log");
|
|
1017
|
+
rmSync(escapeeMarker, { force: true });
|
|
1018
|
+
writeMarkerHook(escapee, "init", escapeeMarker, "init");
|
|
1019
|
+
writeHook(
|
|
1020
|
+
escapee,
|
|
1021
|
+
"user-prompt-submit",
|
|
1022
|
+
`export default () => ({ tag: "escapee" });`,
|
|
1023
|
+
);
|
|
1024
|
+
symlinkSync(escapee, join(PLUGINS_DIR, "escapee"));
|
|
1025
|
+
|
|
1026
|
+
// A contained plugin in the same scan, so a boot that discovered nothing
|
|
1027
|
+
// at all cannot pass this test.
|
|
1028
|
+
const keeper = freshPluginDir("keeper");
|
|
1029
|
+
writePackageJson(keeper, { ...SIMPLE_PKG, name: "keeper" });
|
|
1030
|
+
writeHook(
|
|
1031
|
+
keeper,
|
|
1032
|
+
"user-prompt-submit",
|
|
1033
|
+
`export default () => ({ tag: "keeper" });`,
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
await populateCacheAtBoot();
|
|
1037
|
+
|
|
1038
|
+
expect(
|
|
1039
|
+
(await getUserHooksFor("user-prompt-submit")).map(
|
|
1040
|
+
(fn) => (fn as unknown as () => { tag: string })().tag,
|
|
1041
|
+
),
|
|
1042
|
+
).toEqual(["keeper"]);
|
|
1043
|
+
expect(existsSync(escapeeMarker)).toBe(false);
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
test("a symlinked plugin root staying inside the plugins dir is activated at boot", async () => {
|
|
1047
|
+
// A symlinked plugin root is supported (see `plugins/source-fingerprint.ts`)
|
|
1048
|
+
// as long as it resolves inside the plugins directory. The link target sits
|
|
1049
|
+
// in a dot-prefixed store directory, which carries no package.json and is
|
|
1050
|
+
// therefore not an install of its own.
|
|
1051
|
+
const target = join(PLUGINS_DIR, ".store", "alias");
|
|
1052
|
+
mkdirSync(target, { recursive: true });
|
|
1053
|
+
writePackageJson(target, { ...SIMPLE_PKG, name: "alias" });
|
|
1054
|
+
writeHook(
|
|
1055
|
+
target,
|
|
1056
|
+
"user-prompt-submit",
|
|
1057
|
+
`export default () => ({ tag: "alias" });`,
|
|
1058
|
+
);
|
|
1059
|
+
symlinkSync(target, join(PLUGINS_DIR, "alias"));
|
|
1060
|
+
|
|
1061
|
+
await populateCacheAtBoot();
|
|
1062
|
+
|
|
1063
|
+
expect(
|
|
1064
|
+
(await getUserHooksFor("user-prompt-submit")).map(
|
|
1065
|
+
(fn) => (fn as unknown as () => { tag: string })().tag,
|
|
1066
|
+
),
|
|
1067
|
+
).toEqual(["alias"]);
|
|
1068
|
+
});
|
|
1007
1069
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* deduplicated against prior firings of the same schedule.
|
|
4
4
|
*
|
|
5
5
|
* The scheduler supplies a unique per-firing dedupeKey
|
|
6
|
-
* (`schedule:notify:<id>:<timestamp>`) so `
|
|
6
|
+
* (`schedule:notify:<id>:<timestamp>`) so `setEventDedupeKey` is never
|
|
7
7
|
* called for schedule signals and `checkDedupe` never finds a matching
|
|
8
8
|
* row when the LLM decision engine generates a stable key like
|
|
9
9
|
* `schedule:notify:<id>`.
|