@dsh-enhanced/assistant-automations 0.1.3

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -0
  3. package/cordis.patch.yml +36 -0
  4. package/lib/artifacts.d.ts +15 -0
  5. package/lib/artifacts.d.ts.map +1 -0
  6. package/lib/artifacts.js +68 -0
  7. package/lib/artifacts.js.map +1 -0
  8. package/lib/coordinator.d.ts +126 -0
  9. package/lib/coordinator.d.ts.map +1 -0
  10. package/lib/coordinator.js +374 -0
  11. package/lib/coordinator.js.map +1 -0
  12. package/lib/index.d.ts +11 -0
  13. package/lib/index.d.ts.map +1 -0
  14. package/lib/index.js +11 -0
  15. package/lib/index.js.map +1 -0
  16. package/lib/operator.d.ts +4 -0
  17. package/lib/operator.d.ts.map +1 -0
  18. package/lib/operator.js +12 -0
  19. package/lib/operator.js.map +1 -0
  20. package/lib/proposals.d.ts +89 -0
  21. package/lib/proposals.d.ts.map +1 -0
  22. package/lib/proposals.js +499 -0
  23. package/lib/proposals.js.map +1 -0
  24. package/lib/runner.d.ts +34 -0
  25. package/lib/runner.d.ts.map +1 -0
  26. package/lib/runner.js +272 -0
  27. package/lib/runner.js.map +1 -0
  28. package/lib/schedule.d.ts +32 -0
  29. package/lib/schedule.d.ts.map +1 -0
  30. package/lib/schedule.js +245 -0
  31. package/lib/schedule.js.map +1 -0
  32. package/lib/service.d.ts +159 -0
  33. package/lib/service.d.ts.map +1 -0
  34. package/lib/service.js +445 -0
  35. package/lib/service.js.map +1 -0
  36. package/lib/sqlite.d.ts +8 -0
  37. package/lib/sqlite.d.ts.map +1 -0
  38. package/lib/sqlite.js +281 -0
  39. package/lib/sqlite.js.map +1 -0
  40. package/lib/store.d.ts +169 -0
  41. package/lib/store.d.ts.map +1 -0
  42. package/lib/store.js +1002 -0
  43. package/lib/store.js.map +1 -0
  44. package/lib/tools.d.ts +4 -0
  45. package/lib/tools.d.ts.map +1 -0
  46. package/lib/tools.js +226 -0
  47. package/lib/tools.js.map +1 -0
  48. package/lib/types.d.ts +182 -0
  49. package/lib/types.d.ts.map +1 -0
  50. package/lib/types.js +2 -0
  51. package/lib/types.js.map +1 -0
  52. package/lib/version.d.ts +2 -0
  53. package/lib/version.d.ts.map +1 -0
  54. package/lib/version.js +2 -0
  55. package/lib/version.js.map +1 -0
  56. package/package.json +106 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-enhanced contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @dsh-enhanced/assistant-automations
2
+
3
+ 面向个人助理的单机、冷启动可恢复调度器。它把 automation 定义、稳定 occurrence、task、attempt、run 和 duty lease 写入 SQLite;进程重启后从账本继续,而不是把内存 timer 当作事实源。每次执行创建一个新的 DSH rc.8 Agent,固定 workspace、preset 身份、provider/model、输出预算、工具白名单和工具调用上限。
4
+
5
+ 执行前会先解析并挂载 automation 固定的 Agent preset,再在该 Agent 的最终工具平面上校验 immutable allowlist;preset 只提供 scoped tool 时也能被正确发现,preset 额外暴露未批准工具则整次 setup fail closed。最终工具集合非空时,provider 通过 `@dsh-enhanced/llm-route-capabilities` 声明 `native`/`bridge` 即放行;未发布任何声明的 route 默认按 `unknownRouteToolCalls: allow` 准入,设为 `deny` 则只放行 `toolCapableProviders`。provider 明确声明 `none` 时始终拒绝,并且发生在 followup 和任何模型调用之前。
6
+
7
+ ## 安装
8
+
9
+ 本插件要求 `@dsh-enhanced/assistant-policy` 先提供 `ctx.assistantPolicy`,并要求运行中的 DSH profile 已有 agent loop、LLM、session 和 tools 服务:
10
+
11
+ ```sh
12
+ dsh plugin --profile web add @dsh-enhanced/assistant-policy
13
+ dsh plugin --profile web add @dsh-enhanced/assistant-automations
14
+ dsh --profile web --dump-config
15
+ ```
16
+
17
+ 默认 `schedulerEnabled: false`,且 assistant-policy 默认拒绝,所以安装后不会在后台调用模型。确认规则和预算后再同时开启调度器与精确的 background allow 规则:
18
+
19
+ ```yaml
20
+ - id: dsh-enhanced-assistant-policy
21
+ name: '@dsh-enhanced/assistant-policy'
22
+ config:
23
+ databasePath: !!js dshHomePath('assistant-policy/policy.sqlite')
24
+ rules:
25
+ - id: allow-primary-automation-tools
26
+ effect: allow
27
+ subject: { kind: agent, id: primary, workspace: /absolute/workspace }
28
+ actions: [execute]
29
+ resource: { kind: tool, id: automation_* }
30
+ context: { initiators: [foreground] }
31
+ - id: allow-primary-automation-service
32
+ effect: allow
33
+ subject: { kind: agent, id: primary, workspace: /absolute/workspace }
34
+ actions: [history, list, propose, run-dry]
35
+ resource: { kind: automation, id: "*" }
36
+ context: { initiators: [foreground] }
37
+ - id: allow-reviewed-background-automations
38
+ effect: allow
39
+ subject: { kind: background, id: auto-daily-review, workspace: /absolute/workspace }
40
+ actions: [execute]
41
+ resource: { kind: automation, id: auto-daily-review }
42
+ context: { initiators: [background] }
43
+ budgets:
44
+ - id: assistant-automations-proposals
45
+ metric: automation-runs
46
+ limit: 8
47
+ periodMs: 86400000
48
+ scope: workspace
49
+ - id: dsh-enhanced-assistant-automations
50
+ name: '@dsh-enhanced/assistant-automations'
51
+ config:
52
+ databasePath: !!js dshHomePath('assistant-automations/state.sqlite')
53
+ runsPath: !!js dshHomePath('assistant-automations/runs')
54
+ schedulerEnabled: true
55
+ ```
56
+
57
+ 生产使用还必须由 systemd、launchd、Docker restart policy 或等价 supervisor 拉起 DSH。插件能恢复停机期间的账本状态,但不能在宿主进程没有运行时主动执行。
58
+
59
+ ## 工具与审批
60
+
61
+ - `automation_create`:只提交 ID、名称、prompt、schedule 和 `allowed_tools`;workspace/preset/owner/投递绑定从当前 Agent 与 Delivery 派生,provider/model、执行上限、策略和预算来自可信配置,不会直接创建。
62
+ - `automation_list`:只读、有限输出;不回显 prompt、principal、数据库路径或运行目录。
63
+ - `automation_manage`:以 `expected_version` CAS 提交 pause/resume/delete 提案。
64
+ - `automation_run`:创建幂等 manual occurrence,并强制 `dryRun: true`、空工具白名单、无 delivery。
65
+ - `automation_history`:读取有限 occurrence/run 摘要,不暴露 artifact 的宿主绝对路径。
66
+
67
+ 模型发起的 create/pause/resume/delete 全部走 `assistant-policy`。模型工具 schema 不接受 principal、workspace、preset、provider/model、投递绑定、预算、TTL、执行上限或 retry/misfire/overlap 等 authority selector。存在 Delivery 时,服务通过当前 Agent 的已认证 owner binding 派生 principal/workspace/binding;没有 Delivery 的 headless 调用必须由可信宿主 API 显式提供 principal。提案 TTL 只来自配置。
68
+
69
+ 本地数据库保存完整目标快照和不可变 dispatch route,policy 只保存摘要与 diff hash;可信通道或 UI 再调用 `ctx.assistantAutomations.decideProposal(...)`。批准时会重新校验 Policy 的完整 tuple 以及 lifecycle version;任何 proposal/requester/principal/action/resource/summary/diff/expiry/version 不匹配都会持久化为 `conflicted`,不会应用变更。
70
+
71
+ 支持严格五字段 cron、IANA timezone、一次性 `at` 和 anchored `every`。`skip`、`latest`、`bounded-replay` 明确处理 misfire;catch-up 有硬上限。cron 在 DST 春季缺口自然跳过不存在的本地时间,在秋季重叠会产生两个不同 UTC occurrence。
72
+
73
+ ## 可靠性语义
74
+
75
+ SQLite occurrence id 由 automation、trigger 类型和 scheduledAt/eventId 稳定派生并唯一约束。task 只在短事务中 claim;全局 duty lease 使用单调 fencing token,旧 owner 无法 heartbeat 或提交新 owner 的结果。过期但尚未启动的 `claimed` task 可安全重排;已经 `running` 的 task 默认变成 `unknown`。只有 immutable definition 明确声明 `retrySafety: idempotent` 且还有 retry 配额时,才会自动重排。
76
+
77
+ 这不等于“外部副作用 exactly once”。模型可能已经调用外部工具,而进程在 run commit 前崩溃;默认的 `unknown` 正是对这段不可判定窗口的诚实记录。`overlap` 支持 `skip`、`queue-one` 和 `cancel-previous`,取消是请求而不是远端副作用回滚保证。
78
+
79
+ 完整 JSON artifact 先通过同目录临时文件、fsync、atomic rename 写入,再提交 terminal run;SQLite 只保存有限 preview 和相对 artifact ref。execution 与 delivery 是两个状态域:若可选的 `assistant-delivery` 存在,带固定 `deliveryBindingId` 的成功 run 会以 `automation:<occurrenceId>:<bindingId>` 幂等写入它的 outbox;投递失败不会把成功 execution 改写成失败,后续 tick 只重试同一 enqueue。定义还可带有界的 `deliverySuppressExact`;空输出或精确匹配值会在同一账本中进入 terminal `suppressed`,不会送到 Delivery。该字段必须同时绑定 `deliveryBindingId`,Heartbeat 用它抑制 `HEARTBEAT_OK`。
80
+
81
+ terminal run 还会在同一 SQLite 事务中写入不可变、有限大小的成长证据。稳定 situation 使用 `automation:<automationId>`,可编辑名称只进入展示 detail;`succeeded`、`failed`、`timed_out` 进入独立 evidence outbox,`cancelled`、`unknown` 则持久化为 `suppressed`。可选的 `assistant-evolution` 暂不可用或抛错时不会改变 run 终态,leader 后续以 `automation-run:<runId>` 重放同一条证据。runner 只有返回 exact session 时,证据才会携带 session;rule/version 还要求 Evolution 能查到 guidance 成功注入后持久化的同 session、scope、automation exposure receipt。计划中的 active rule 或 setup 前失败不会被伪装成因果证据。
82
+
83
+ ## 配置
84
+
85
+ | 字段 | 默认值 | 作用 |
86
+ |---|---:|---|
87
+ | `databasePath` | `$DSH_HOME/assistant-automations/state.sqlite` | automation/occurrence/task/attempt/run/lease/proposal 账本 |
88
+ | `runsPath` | `$DSH_HOME/assistant-automations/runs` | 私有 JSON run artifacts |
89
+ | `schedulerEnabled` | `false` | 是否启动常驻调度 tick |
90
+ | `tickIntervalMs` | `5000` | 唤醒频率;不承担调度事实源角色 |
91
+ | `dutyLeaseMs` | `15000` | 单机多进程 owner lease |
92
+ | `taskLeaseMs` | `30000` | claim/running lease |
93
+ | `misfireGraceMs` | `60000` | 超过该窗口按 misfire policy 处理 |
94
+ | `maxCatchUp` | `100` | 单 tick materialize/recovery 上限 |
95
+ | `maxConcurrency` | `1` | 同时 fresh Agent 数量 |
96
+ | `maxArtifactBytes` | `1048576` | 单个 run artifact 上限 |
97
+ | `defaultProposalTtlMs` | `900000` | 默认提案有效期 |
98
+ | `reconcileIntervalMs` | `15000` | 提交“会话结束后才被批准”的提案的轮询间隔;即使 `schedulerEnabled: false` 也会运行;`0` 关闭定时器 |
99
+ | `reconcileLimit` | `50` | 每轮 reconcile 检查的本地待决提案上限 |
100
+ | `allowUnbudgetedExecution` | `false` | 是否允许无人值守 execution 不绑定 Policy budget;默认 fail closed |
101
+ | `proposalDefaults` | 见 patch | 模型提案使用的可信 provider/model、工具上限、execution bounds、策略和必填 budget |
102
+ | `toolCapableProviders` | `[deepseek-official]` | 尚未发布 registry metadata 的上游原生工具 provider 审计列表;在 `unknownRouteToolCalls: deny` 下作为白名单生效 |
103
+ | `unknownRouteToolCalls` | `allow` | 未发布任何工具能力声明的 route 如何准入;`deny` 切换为严格白名单。provider 显式声明 `none` 时两种取值都 fail closed |
104
+
105
+ ## 延迟审批的提交闭环
106
+
107
+ `automation_create` / `automation_manage` 只创建提案。审批可能发生在原 turn 结束之后(例如几分钟后在飞书卡片上点击),因此本插件会周期性调用 `reconcileProposals()`:读取 policy 账本中该提案的**终态**,再走与 `decideProposal` 相同的 `createApproved`/`changeApproved` 路径。
108
+
109
+ 该定时器**独立于 scheduler**:即使 `schedulerEnabled: false`,批准一个 automation 也必须生效,否则审批会永远悬空。
110
+
111
+ - **不推断批准**:pending 仍然是 pending。
112
+ - **幂等**:重复 reconcile 不会重复创建 automation 或重复结算。
113
+ - **冲突不丢决定**:版本已变化时提案降级为 `conflicted`。
114
+ - **无跨库孤儿或复活**:本地 creation intent 先冻结绝对 `notAfter`,再由 Policy 在同一 `BEGIN IMMEDIATE` 中按完整 tuple 原子 recover-or-create。attach 前崩溃后,deadline 前只会恢复/创建同一个 proposal;既有终态即使 deadline 已过也会精确结算;deadline 后仍缺失则写永久 tombstone 并将本地 intent 置为 `conflicted`,普通 propose 也无法再生成孤儿审批卡。
115
+ - **有界公平**:每轮仍为 pending 的记录会持久化轮转,终态记录不会因固定 limit 长期饿死。
116
+
117
+ `automation_pending` 用于列出仍在等待审批的提案(仅有界元数据,不含 prompt、principal 或主机路径),避免对同一变更重复提案。
118
+
119
+ ## 权限与数据
120
+
121
+ - 文件系统:只读写显式 `databasePath`、SQLite WAL/SHM 和 `runsPath`;目录 `0700`,数据库/artifact `0600`。automation 的 workspace 只作为 DSH session 身份,本插件不自行扫描工作区。
122
+ - 网络:插件不直接发网络请求;它会通过宿主 DSH LLM provider 调用已批准的模型。被白名单允许的工具可能拥有自己的网络权限。
123
+ - 消息投递:`assistant-delivery` 是可选 peer;只有 automation 固定了 `deliveryBindingId` 且后台 send 策略允许时才会 enqueue。该服务不存在时,run 保持 `deliveryStatus: pending`,完整结果仍可从本地 artifact 获取。
124
+ - 子进程、凭据、浏览器、安装脚本:本插件自身无。白名单工具的独立权限仍然生效,assistant-policy 只能进一步拒绝,不能替代 OS/container 隔离。
125
+ - 模型成本:每个 occurrence 可产生一个或多个 LLM step。默认要求 immutable `budgetId`/`budgetAmount`,并在 Agent 创建前预留 Policy budget;只有显式设置 `allowUnbudgetedExecution: true` 才能绕过。runner 会通过 Policy 的只读配置接口证明该 budget 的 metric 精确为 `automation-runs`,其他单位在 reserve 和 Agent 创建前 fail closed。reservation 幂等键同时绑定 automation、occurrence、metric 和 budget,terminal/replayed reservation 不会重复执行。
126
+ - 每次执行使用可信配置的固定 per-run cost,并始终按完整 `budgetAmount` finalize(通常设为 `1`,budget limit 表示周期内允许的运行次数);provider usage 只保留为执行证据/审计,不参与退款或换算。因此 input/cache/reasoning 的不可知上界和沉默 CLI/ACP route 都不会突破执行前已预留的硬上限。只有能确定 Agent 尚未提交时才 release。
127
+ - 保留与备份:删除 run artifacts 不改变账本,但会失去完整输出;备份时一致性复制 SQLite(含 WAL)和 runs 目录。prompt、输出与 usage 可能含私人信息。
128
+
129
+ ## 范围边界
130
+
131
+ P0 是单机 scheduler,不支持共享网络文件系统、多主共识、DAG、任意 shell sensor、heartbeat、webhook/file watch、自己实现消息 transport、浏览器或 ingest。官方 DSH session-local schedule 也没有被复制为第二套 daemon;timer 只唤醒本协调器。
132
+
133
+ ## 兼容性
134
+
135
+ 已针对 DeepSeek Harness `0.1.0-rc.8`(源码提交 `141eb6fef83422698aef7a981029e843e8161534`)验证。详见仓库[兼容性基线](../../docs/compatibility.md)。
@@ -0,0 +1,36 @@
1
+ - insert:
2
+ - id: dsh-enhanced-assistant-automations
3
+ name: '@dsh-enhanced/assistant-automations'
4
+ inject:
5
+ - assistantPolicy
6
+ config:
7
+ databasePath: !!js dshHomePath('assistant-automations/state.sqlite')
8
+ runsPath: !!js dshHomePath('assistant-automations/runs')
9
+ schedulerEnabled: false
10
+ tickIntervalMs: 5000
11
+ dutyLeaseMs: 15000
12
+ taskLeaseMs: 30000
13
+ misfireGraceMs: 60000
14
+ maxCatchUp: 100
15
+ maxConcurrency: 1
16
+ maxArtifactBytes: 1048576
17
+ defaultProposalTtlMs: 900000
18
+ reconcileIntervalMs: 15000
19
+ reconcileLimit: 50
20
+ allowUnbudgetedExecution: false
21
+ proposalDefaults:
22
+ provider: deepseek-official
23
+ model: deepseek-chat
24
+ allowedTools: []
25
+ timeoutMs: 60000
26
+ maxOutputTokens: 512
27
+ maxToolCalls: 0
28
+ misfireKind: latest
29
+ misfireLimit: 1
30
+ overlap: skip
31
+ retrySafety: never
32
+ maxRetries: 0
33
+ budgetId: assistant-automations-proposals
34
+ budgetAmount: 1
35
+ toolCapableProviders:
36
+ - deepseek-official
@@ -0,0 +1,15 @@
1
+ export type AutomationArtifactErrorCode = 'idempotency-conflict' | 'invalid-id' | 'invalid-path' | 'too-large';
2
+ export declare class AutomationArtifactError extends Error {
3
+ readonly code: AutomationArtifactErrorCode;
4
+ constructor(code: AutomationArtifactErrorCode, message: string);
5
+ }
6
+ export declare class AutomationArtifactStore {
7
+ private readonly rootPath;
8
+ private readonly maxBytes;
9
+ constructor(options: {
10
+ rootPath: string;
11
+ maxBytes: number;
12
+ });
13
+ write(occurrenceId: string, value: unknown): string;
14
+ }
15
+ //# sourceMappingURL=artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,2BAA2B,GAAG,sBAAsB,GAAG,YAAY,GAAG,cAAc,GAAG,WAAW,CAAA;AAE9G,qBAAa,uBAAwB,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,2BAA2B;gBAAjC,IAAI,EAAE,2BAA2B,EAAE,OAAO,EAAE,MAAM;CAIxE;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;gBAErB,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAa3D,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM;CAoCpD"}
@@ -0,0 +1,68 @@
1
+ import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
2
+ import { isAbsolute, join } from 'node:path';
3
+ export class AutomationArtifactError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.code = code;
8
+ this.name = 'AutomationArtifactError';
9
+ }
10
+ }
11
+ export class AutomationArtifactStore {
12
+ rootPath;
13
+ maxBytes;
14
+ constructor(options) {
15
+ if (!isAbsolute(options.rootPath)) {
16
+ throw new AutomationArtifactError('invalid-path', 'artifact root must be absolute');
17
+ }
18
+ if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 128 || options.maxBytes > 16 * 1024 * 1024) {
19
+ throw new AutomationArtifactError('too-large', 'artifact byte limit is invalid');
20
+ }
21
+ this.rootPath = options.rootPath;
22
+ this.maxBytes = options.maxBytes;
23
+ mkdirSync(this.rootPath, { recursive: true, mode: 0o700 });
24
+ chmodSync(this.rootPath, 0o700);
25
+ }
26
+ write(occurrenceId, value) {
27
+ if (!/^occ-[A-Za-z0-9_-]{1,190}$/.test(occurrenceId)) {
28
+ throw new AutomationArtifactError('invalid-id', 'artifact occurrence id is invalid');
29
+ }
30
+ const bytes = Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
31
+ if (bytes.byteLength > this.maxBytes) {
32
+ throw new AutomationArtifactError('too-large', `artifact exceeds ${this.maxBytes} bytes`);
33
+ }
34
+ const ref = `${occurrenceId}.json`;
35
+ const target = join(this.rootPath, ref);
36
+ if (existsSync(target)) {
37
+ if (readFileSync(target).equals(bytes))
38
+ return ref;
39
+ throw new AutomationArtifactError('idempotency-conflict', 'artifact id was reused with different content');
40
+ }
41
+ const temporary = join(this.rootPath, `.${occurrenceId}.${process.pid}.${Date.now()}.tmp`);
42
+ let descriptor;
43
+ try {
44
+ descriptor = openSync(temporary, 'wx', 0o600);
45
+ writeFileSync(descriptor, bytes);
46
+ fsyncSync(descriptor);
47
+ closeSync(descriptor);
48
+ descriptor = undefined;
49
+ renameSync(temporary, target);
50
+ chmodSync(target, 0o600);
51
+ const directory = openSync(this.rootPath, 'r');
52
+ try {
53
+ fsyncSync(directory);
54
+ }
55
+ finally {
56
+ closeSync(directory);
57
+ }
58
+ return ref;
59
+ }
60
+ finally {
61
+ if (descriptor !== undefined)
62
+ closeSync(descriptor);
63
+ if (existsSync(temporary))
64
+ unlinkSync(temporary);
65
+ }
66
+ }
67
+ }
68
+ //# sourceMappingURL=artifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ import type { AutomationArtifactStore } from './artifacts.js';
2
+ import { type AutomationStore } from './store.js';
3
+ import type { AutomationOccurrence, AutomationRecord, AutomationRunStatus, AutomationTask } from './types.js';
4
+ export interface AutomationRunnerInput {
5
+ automation: AutomationRecord;
6
+ occurrence: AutomationOccurrence;
7
+ task: AutomationTask;
8
+ sessionId: string;
9
+ signal: AbortSignal;
10
+ }
11
+ export interface AutomationRunnerResult {
12
+ outcome: AutomationRunStatus;
13
+ sessionId?: string;
14
+ output: string;
15
+ usage: Readonly<Record<string, unknown>>;
16
+ }
17
+ export interface AutomationRunner {
18
+ run(input: AutomationRunnerInput): Promise<AutomationRunnerResult>;
19
+ }
20
+ export interface AutomationDeliveryDispatcher {
21
+ enqueueBackground(input: {
22
+ sourceId: string;
23
+ workspace: string;
24
+ bindingId: string;
25
+ idempotencyKey: string;
26
+ text: string;
27
+ format?: 'markdown' | 'plain';
28
+ }): {
29
+ id: string;
30
+ status: string;
31
+ };
32
+ }
33
+ /**
34
+ * Optional one-way sink for finished-run outcomes, used to feed behavioural
35
+ * learning. Structural interface rather than a service import, so the scheduler
36
+ * stays independent of whether any learning plugin is installed.
37
+ */
38
+ export interface AutomationOutcomeRecorder {
39
+ recordAutomationOutcome(input: {
40
+ situation: string;
41
+ outcome: 'succeeded' | 'failed';
42
+ detail: string;
43
+ idempotencyKey: string;
44
+ occurredAt: number;
45
+ workspace?: string;
46
+ agentPreset?: string;
47
+ automationId?: string;
48
+ runId?: string;
49
+ sessionId?: string;
50
+ ruleId?: string;
51
+ guidanceVersion?: number;
52
+ }): void;
53
+ /** Query an authoritative receipt written only after guidance was injected. */
54
+ captureAutomationExposure?(input: {
55
+ workspace: string;
56
+ agentPreset: string;
57
+ automationId: string;
58
+ sessionId: string;
59
+ }): Promise<{
60
+ ruleId: string;
61
+ guidanceVersion: number;
62
+ } | undefined> | {
63
+ ruleId: string;
64
+ guidanceVersion: number;
65
+ } | undefined;
66
+ }
67
+ export declare class AutomationRunnerAmbiguousError extends Error {
68
+ constructor(message: string, options?: ErrorOptions);
69
+ }
70
+ export interface AutomationCoordinatorOptions {
71
+ store: AutomationStore;
72
+ artifacts: AutomationArtifactStore;
73
+ runner: AutomationRunner;
74
+ ownerId: string;
75
+ now?: () => number;
76
+ dutyLeaseMs: number;
77
+ taskLeaseMs: number;
78
+ misfireGraceMs: number;
79
+ maxCatchUp: number;
80
+ maxConcurrency: number;
81
+ tickIntervalMs?: number;
82
+ }
83
+ export declare class AutomationCoordinator {
84
+ private readonly store;
85
+ private readonly artifacts;
86
+ private readonly runner;
87
+ private readonly ownerId;
88
+ private readonly now;
89
+ private readonly dutyLeaseMs;
90
+ private readonly taskLeaseMs;
91
+ private readonly misfireGraceMs;
92
+ private readonly maxCatchUp;
93
+ private readonly maxConcurrency;
94
+ private readonly tickIntervalMs;
95
+ private readonly active;
96
+ private delivery;
97
+ private outcomeRecorder;
98
+ private fencingToken;
99
+ private timer;
100
+ private stopped;
101
+ constructor(options: AutomationCoordinatorOptions);
102
+ start(): void;
103
+ tick(): Promise<void>;
104
+ setDeliveryDispatcher(delivery: AutomationDeliveryDispatcher | undefined): void;
105
+ /**
106
+ * Attach an optional sink for finished-run outcomes.
107
+ *
108
+ * Kept optional and one-way so the scheduler never depends on a learning
109
+ * plugin: if no recorder is attached, or the recorder throws, runs proceed
110
+ * unchanged. Evidence collection must never be able to break execution.
111
+ */
112
+ setOutcomeRecorder(recorder: AutomationOutcomeRecorder | undefined): void;
113
+ whenIdle(): Promise<void>;
114
+ cancel(taskId: string): Promise<void>;
115
+ stop(): Promise<void>;
116
+ private ensureDuty;
117
+ private launch;
118
+ private abortRequestedActive;
119
+ private execute;
120
+ private getExposure;
121
+ private dispatchPendingEvidence;
122
+ private dispatchRunEvidence;
123
+ private dispatchPendingDeliveries;
124
+ private dispatchRunDelivery;
125
+ }
126
+ //# sourceMappingURL=coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinator.d.ts","sourceRoot":"","sources":["../src/coordinator.ts"],"names":[],"mappings":"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"}