@osolmaz/pi-workflows 0.15.3 → 0.16.1

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 (207) hide show
  1. package/README.md +24 -8
  2. package/dist/channels/adapter-entry.d.ts +3 -0
  3. package/dist/channels/adapter-entry.js +203 -0
  4. package/dist/channels/adapter-entry.js.map +1 -0
  5. package/dist/channels/config.d.ts +43 -0
  6. package/dist/channels/config.js +234 -0
  7. package/dist/channels/config.js.map +1 -0
  8. package/dist/channels/protocol.d.ts +94 -0
  9. package/dist/channels/protocol.js +219 -0
  10. package/dist/channels/protocol.js.map +1 -0
  11. package/dist/channels/telegram.d.ts +46 -0
  12. package/dist/channels/telegram.js +285 -0
  13. package/dist/channels/telegram.js.map +1 -0
  14. package/dist/client/activity.d.ts +2 -0
  15. package/dist/client/activity.js +6 -0
  16. package/dist/client/activity.js.map +1 -0
  17. package/dist/client/client.d.ts +102 -0
  18. package/dist/client/client.js +737 -0
  19. package/dist/client/client.js.map +1 -0
  20. package/dist/client/index.d.ts +3 -0
  21. package/dist/client/index.js +3 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/materialize.d.ts +7 -0
  24. package/dist/client/materialize.js +177 -0
  25. package/dist/client/materialize.js.map +1 -0
  26. package/dist/client/protocol.d.ts +60 -0
  27. package/dist/client/protocol.js +275 -0
  28. package/dist/client/protocol.js.map +1 -0
  29. package/dist/client/resolver.d.ts +23 -0
  30. package/dist/client/resolver.js +2 -0
  31. package/dist/client/resolver.js.map +1 -0
  32. package/dist/client/view.d.ts +144 -0
  33. package/dist/client/view.js +3 -0
  34. package/dist/client/view.js.map +1 -0
  35. package/dist/controllers/index.d.ts +1 -1
  36. package/dist/controllers/index.js.map +1 -1
  37. package/dist/controllers/sqlite.d.ts +47 -157
  38. package/dist/controllers/sqlite.js +230 -406
  39. package/dist/controllers/sqlite.js.map +1 -1
  40. package/dist/extension/index.d.ts +23 -1
  41. package/dist/extension/index.js +734 -555
  42. package/dist/extension/index.js.map +1 -1
  43. package/dist/extension/recorder.d.ts +95 -0
  44. package/dist/extension/recorder.js +530 -0
  45. package/dist/extension/recorder.js.map +1 -0
  46. package/dist/extension/remote-recorder-store.d.ts +25 -0
  47. package/dist/extension/remote-recorder-store.js +81 -0
  48. package/dist/extension/remote-recorder-store.js.map +1 -0
  49. package/dist/extension/session-events.d.ts +134 -0
  50. package/dist/extension/session-events.js +60 -0
  51. package/dist/extension/session-events.js.map +1 -0
  52. package/dist/extension/session-view.d.ts +9 -2
  53. package/dist/extension/session-view.js +117 -51
  54. package/dist/extension/session-view.js.map +1 -1
  55. package/dist/extension/step-message.d.ts +6 -4
  56. package/dist/extension/step-message.js +12 -6
  57. package/dist/extension/step-message.js.map +1 -1
  58. package/dist/extension/widget.d.ts +3 -2
  59. package/dist/extension/widget.js +41 -24
  60. package/dist/extension/widget.js.map +1 -1
  61. package/dist/extension/workflow-message-coordinator.d.ts +27 -0
  62. package/dist/extension/workflow-message-coordinator.js +294 -0
  63. package/dist/extension/workflow-message-coordinator.js.map +1 -0
  64. package/dist/host/channel-effects.d.ts +72 -0
  65. package/dist/host/channel-effects.js +271 -0
  66. package/dist/host/channel-effects.js.map +1 -0
  67. package/dist/host/channel-supervisor.d.ts +21 -0
  68. package/dist/host/channel-supervisor.js +54 -0
  69. package/dist/host/channel-supervisor.js.map +1 -0
  70. package/dist/host/child-worker-supervisor.js +1 -1
  71. package/dist/host/child-worker-supervisor.js.map +1 -1
  72. package/dist/host/resolver-entry.d.ts +2 -23
  73. package/dist/host/resolver-entry.js +1 -1
  74. package/dist/host/resolver-entry.js.map +1 -1
  75. package/dist/host/runner.d.ts +54 -8
  76. package/dist/host/runner.js +1997 -305
  77. package/dist/host/runner.js.map +1 -1
  78. package/dist/host/state.d.ts +25 -19
  79. package/dist/host/state.js +303 -89
  80. package/dist/host/state.js.map +1 -1
  81. package/dist/host/view.d.ts +83 -0
  82. package/dist/host/view.js +975 -0
  83. package/dist/host/view.js.map +1 -0
  84. package/dist/host/worker-protocol.js +1 -1
  85. package/dist/host/worker-protocol.js.map +1 -1
  86. package/dist/state/database.d.ts +1 -0
  87. package/dist/state/database.js +15 -0
  88. package/dist/state/database.js.map +1 -1
  89. package/dist/state/prune.d.ts +3 -1
  90. package/dist/state/prune.js +9 -33
  91. package/dist/state/prune.js.map +1 -1
  92. package/dist/state/schema.js +88 -98
  93. package/dist/state/schema.js.map +1 -1
  94. package/dist/state/workflow-messages.d.ts +96 -0
  95. package/dist/state/workflow-messages.js +335 -0
  96. package/dist/state/workflow-messages.js.map +1 -0
  97. package/dist/viewer/backup.d.ts +2 -0
  98. package/dist/viewer/backup.js +28 -0
  99. package/dist/viewer/backup.js.map +1 -0
  100. package/dist/viewer/cli.d.ts +4 -0
  101. package/dist/viewer/cli.js +150 -170
  102. package/dist/viewer/cli.js.map +1 -1
  103. package/dist/viewer/render.js +1 -1
  104. package/dist/viewer/render.js.map +1 -1
  105. package/dist/viewer/tui.d.ts +5 -7
  106. package/dist/viewer/tui.js +245 -108
  107. package/dist/viewer/tui.js.map +1 -1
  108. package/dist/workflows/human-decision.d.ts +2 -0
  109. package/dist/workflows/human-decision.js +8 -2
  110. package/dist/workflows/human-decision.js.map +1 -1
  111. package/dist/workflows/index.d.ts +1 -1
  112. package/dist/workflows/index.js.map +1 -1
  113. package/dist/workflows/settings.d.ts +1 -8
  114. package/dist/workflows/settings.js.map +1 -1
  115. package/dist/workflows/store.d.ts +67 -20
  116. package/dist/workflows/store.js +451 -378
  117. package/dist/workflows/store.js.map +1 -1
  118. package/dist/workflows/tool-input.d.ts +21 -0
  119. package/dist/workflows/tool-input.js +23 -1
  120. package/dist/workflows/tool-input.js.map +1 -1
  121. package/dist/workflows/types.d.ts +5 -5
  122. package/dist/workflows/workflow-message-content.d.ts +38 -0
  123. package/dist/workflows/workflow-message-content.js +157 -0
  124. package/dist/workflows/workflow-message-content.js.map +1 -0
  125. package/docs/2026-08-18-herdr-piw-plan.md +2 -1
  126. package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
  127. package/docs/2026-08-25-workflow-follow-ups.md +26 -50
  128. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  129. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
  130. package/docs/2026-09-01-unified-workflow-client-plan.md +385 -0
  131. package/docs/2026-09-02-installed-live-e2e-plan.md +239 -0
  132. package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
  133. package/docs/DEFERRED_TURNS.md +66 -294
  134. package/docs/HUMAN_DECISIONS.md +29 -30
  135. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
  136. package/docs/SQLITE_STATE.md +31 -25
  137. package/docs/WORKFLOW_HOST.md +155 -77
  138. package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
  139. package/docs/development.md +2 -1
  140. package/docs/live-replay-protocol.md +74 -132
  141. package/docs/tui-viewer.md +12 -14
  142. package/docs/workflows.md +78 -28
  143. package/herdr-plugin.toml +1 -1
  144. package/package.json +9 -3
  145. package/plugins/herdr/viewer.mjs +14 -5
  146. package/protocol/client.v1.schema.json +137 -0
  147. package/protocol/fixtures/client-v1.json +23 -0
  148. package/src/channels/adapter-entry.ts +220 -0
  149. package/src/channels/config.ts +296 -0
  150. package/src/channels/protocol.ts +333 -0
  151. package/src/channels/telegram.ts +335 -0
  152. package/src/client/activity.ts +6 -0
  153. package/src/client/client.ts +939 -0
  154. package/src/client/index.ts +25 -0
  155. package/src/client/materialize.ts +228 -0
  156. package/src/client/protocol.ts +333 -0
  157. package/src/client/resolver.ts +26 -0
  158. package/src/client/view.ts +163 -0
  159. package/src/controllers/index.ts +0 -1
  160. package/src/controllers/sqlite.ts +339 -808
  161. package/src/extension/index.ts +853 -665
  162. package/src/extension/recorder.ts +687 -0
  163. package/src/extension/remote-recorder-store.ts +126 -0
  164. package/src/extension/session-events.ts +119 -0
  165. package/src/extension/session-view.ts +157 -51
  166. package/src/extension/step-message.ts +19 -9
  167. package/src/extension/widget.ts +53 -25
  168. package/src/extension/workflow-message-coordinator.ts +334 -0
  169. package/src/host/channel-effects.ts +465 -0
  170. package/src/host/channel-supervisor.ts +73 -0
  171. package/src/host/child-worker-supervisor.ts +1 -1
  172. package/src/host/resolver-entry.ts +11 -26
  173. package/src/host/runner.ts +2639 -450
  174. package/src/host/state.ts +404 -137
  175. package/src/host/view.ts +1212 -0
  176. package/src/host/worker-protocol.ts +1 -1
  177. package/src/state/database.ts +13 -0
  178. package/src/state/prune.ts +14 -42
  179. package/src/state/schema.ts +88 -98
  180. package/src/state/workflow-messages.ts +548 -0
  181. package/src/viewer/backup.ts +29 -0
  182. package/src/viewer/cli.ts +171 -185
  183. package/src/viewer/render.ts +1 -5
  184. package/src/viewer/tui.ts +261 -124
  185. package/src/workflows/human-decision.ts +18 -2
  186. package/src/workflows/index.ts +0 -1
  187. package/src/workflows/settings.ts +1 -20
  188. package/src/workflows/store.ts +650 -511
  189. package/src/workflows/tool-input.ts +36 -1
  190. package/src/workflows/types.ts +5 -5
  191. package/src/workflows/workflow-message-content.ts +197 -0
  192. package/dist/extension/decision-channels.d.ts +0 -134
  193. package/dist/extension/decision-channels.js +0 -1307
  194. package/dist/extension/decision-channels.js.map +0 -1
  195. package/dist/host/client.d.ts +0 -48
  196. package/dist/host/client.js +0 -216
  197. package/dist/host/client.js.map +0 -1
  198. package/dist/host/protocol.d.ts +0 -38
  199. package/dist/host/protocol.js +0 -156
  200. package/dist/host/protocol.js.map +0 -1
  201. package/dist/viewer/watch.d.ts +0 -6
  202. package/dist/viewer/watch.js +0 -46
  203. package/dist/viewer/watch.js.map +0 -1
  204. package/src/extension/decision-channels.ts +0 -1826
  205. package/src/host/client.ts +0 -293
  206. package/src/host/protocol.ts +0 -196
  207. package/src/viewer/watch.ts +0 -51
package/README.md CHANGED
@@ -80,10 +80,16 @@ All live workflow and controller state uses one local database:
80
80
  ```
81
81
 
82
82
  Runs, decisions, queues, claims, controllers, session capture, notifications,
83
- channel transport state, effects, and large text values all live there.
84
- Viewers open the database read-only, and every write checks its actor,
85
- expected revision, and owner lease when required. See
86
- [SQLite state](docs/SQLITE_STATE.md).
83
+ channel transport state, effects, and large text values all live there. The
84
+ package-owned host is the only production process that opens the active
85
+ database. The extension, CLI, Herdr adapter, and `piw` use the same versioned
86
+ local client protocol. See [SQLite state](docs/SQLITE_STATE.md).
87
+
88
+ One host-owned workflow-message path handles steps, reminders, resumed work,
89
+ human decisions, notifications, terminal results, and follow-up prompts. The
90
+ shared extension coordinator reports exact Pi branches and model turns, so an
91
+ active workflow turn stays `running`, repeated polling cannot send the same
92
+ message twice, and a terminal result remains visible for 60 seconds.
87
93
 
88
94
  ## Quick start
89
95
 
@@ -128,6 +134,15 @@ A workflow can also expose [settings that change during a
128
134
  run](docs/2026-08-25-workflow-settings.md) and queue [normal follow-up work
129
135
  after completion](docs/2026-08-25-workflow-follow-ups.md).
130
136
 
137
+ Protected human decisions always appear in Pi. To add a private Telegram
138
+ channel, run `/workflow-channel setup` in Pi. Use `/workflow-channel status`
139
+ to inspect configured profiles and uncertain operations, and
140
+ `/workflow-channel reload` after a private configuration change. If an
141
+ external send has an uncertain result, check Telegram first, then use
142
+ `/workflow-channel recover <message-id> confirm|retry`. A retry can duplicate
143
+ a message when Telegram cannot prove the first result. See
144
+ [Human decisions](docs/HUMAN_DECISIONS.md).
145
+
131
146
  Use `expectedOutput: assistantMessage()` when a normal assistant response must
132
147
  be a node inside the graph rather than a presentation after the run. Its exact
133
148
  visible text becomes the node output after the turn settles. The helper has no
@@ -218,8 +233,9 @@ earlier `autodevise` name, and the old command and export are not retained.
218
233
 
219
234
  ## Viewers
220
235
 
221
- Runs persist in `~/.pi/agent/workflows/state.sqlite` as they execute. The
222
- viewer reads that database and re-renders on every state change:
236
+ Runs persist in `~/.pi/agent/workflows/state.sqlite` as they execute. The global
237
+ host is the only process that opens this database. Viewers use the shared client
238
+ protocol and re-render from the host's run view on every state change:
223
239
 
224
240
  ```bash
225
241
  pi-workflows view # interactive picker, live updates
@@ -254,8 +270,8 @@ Run the same command after a pi-workflows update. `pi-workflows herdr setup`
254
270
  remains an alias for existing installations.
255
271
 
256
272
  When Pi runs inside Herdr, the workflow widget shows a `Ctrl+Shift+R piw`
257
- shortcut. The shortcut opens the exact SQLite run state and lets you choose a
258
- split, tab, or new workspace. `/piw` opens the same menu, and `/piw right`,
273
+ shortcut. The shortcut opens the exact host-owned run view and lets you choose
274
+ a split, tab, or new workspace. `/piw` opens the same menu, and `/piw right`,
259
275
  `/piw below`, `/piw left`, `/piw above`, `/piw tab`, or `/piw workspace`
260
276
  selects a placement directly. If a viewer for that run already exists,
261
277
  pi-workflows focuses it instead of opening a duplicate.
@@ -0,0 +1,3 @@
1
+ import { type ChannelAdapterLaunch } from "./protocol.js";
2
+ export declare function runChannelAdapter(): Promise<number>;
3
+ export declare function encodeChannelLaunch(launch: ChannelAdapterLaunch): string;
@@ -0,0 +1,203 @@
1
+ import { createInterface } from "node:readline";
2
+ import { fileURLToPath } from "node:url";
3
+ import { canonicalJson } from "../state/json.js";
4
+ import { errorMessage } from "../workflows/errors.js";
5
+ import { CHANNEL_ADAPTER_PROTOCOL_SCHEMA, channelStableMessageId, encodeChannelLine, parseChannelAdapterLaunch, parseChannelAdapterResponse, } from "./protocol.js";
6
+ import { TelegramAdapter } from "./telegram.js";
7
+ class AdapterHostConnection {
8
+ launch;
9
+ sequence = 0;
10
+ revision = 0;
11
+ lines = createInterface({ input: process.stdin, crlfDelay: Infinity })[Symbol.asyncIterator]();
12
+ constructor(launch) {
13
+ this.launch = launch;
14
+ }
15
+ async report(message) {
16
+ this.sequence += 1;
17
+ const full = {
18
+ schema: CHANNEL_ADAPTER_PROTOCOL_SCHEMA,
19
+ adapterEpoch: this.launch.adapterEpoch,
20
+ profile: this.launch.profile,
21
+ sequence: this.sequence,
22
+ expectedRevision: this.revision,
23
+ ...message,
24
+ };
25
+ if (!process.stdout.write(encodeChannelLine(full))) {
26
+ await new Promise((resolve) => process.stdout.once("drain", resolve));
27
+ }
28
+ const next = await this.lines.next();
29
+ if (next.done)
30
+ throw new Error("Workflow host closed the channel adapter connection");
31
+ const response = parseChannelAdapterResponse(Buffer.from(next.value, "utf8"));
32
+ if (response.sequence !== this.sequence) {
33
+ throw new Error("Workflow host returned a mismatched channel response");
34
+ }
35
+ if (response.outcome !== "accepted") {
36
+ throw new Error(response.error ?? "Workflow host rejected the channel adapter message");
37
+ }
38
+ this.revision = response.revision;
39
+ return response;
40
+ }
41
+ }
42
+ export async function runChannelAdapter() {
43
+ const launch = readLaunch();
44
+ const host = new AdapterHostConnection(launch);
45
+ const telegram = new TelegramAdapter({
46
+ profile: launch.profile,
47
+ token: launch.token,
48
+ allowedUserIds: launch.allowedUserIds,
49
+ allowedChatIds: launch.allowedChatIds,
50
+ ...(launch.apiBase === undefined ? {} : { apiBase: launch.apiBase }),
51
+ });
52
+ let cursor = 0;
53
+ let controlSequence = 0;
54
+ const controlId = (kind) => {
55
+ controlSequence += 1;
56
+ return channelStableMessageId([
57
+ launch.adapterEpoch,
58
+ kind,
59
+ String(controlSequence),
60
+ String(cursor),
61
+ ]);
62
+ };
63
+ let stopping = false;
64
+ process.once("SIGTERM", () => {
65
+ stopping = true;
66
+ });
67
+ process.once("SIGINT", () => {
68
+ stopping = true;
69
+ });
70
+ let response = await host.report({
71
+ kind: "channel.ready",
72
+ stableMessageId: controlId("ready"),
73
+ cursor,
74
+ });
75
+ while (!stopping) {
76
+ const command = response.command;
77
+ if (command === null) {
78
+ await new Promise((resolve) => setTimeout(resolve, 100));
79
+ response = await host.report({
80
+ kind: "channel.ready",
81
+ stableMessageId: controlId("ready"),
82
+ cursor,
83
+ });
84
+ continue;
85
+ }
86
+ if (command.kind === "channel.stop") {
87
+ stopping = true;
88
+ break;
89
+ }
90
+ if (command.kind === "channel.present") {
91
+ try {
92
+ const messages = await telegram.present(command.request);
93
+ response = await host.report({
94
+ kind: "channel.present",
95
+ stableMessageId: command.stableMessageId,
96
+ decisionId: command.request.decisionId,
97
+ requestDigest: command.request.requestDigest,
98
+ attemptId: command.attemptId,
99
+ state: "confirmed",
100
+ messages,
101
+ });
102
+ }
103
+ catch (error) {
104
+ response = await host.report({
105
+ kind: "channel.present",
106
+ stableMessageId: command.stableMessageId,
107
+ decisionId: command.request.decisionId,
108
+ requestDigest: command.request.requestDigest,
109
+ attemptId: command.attemptId,
110
+ state: "unknown",
111
+ messages: [],
112
+ errorCode: errorMessage(error),
113
+ });
114
+ }
115
+ continue;
116
+ }
117
+ if (command.kind === "channel.settle") {
118
+ try {
119
+ await telegram.settle(command.outcome, command.response, command.messages);
120
+ response = await host.report({
121
+ kind: "channel.settle",
122
+ stableMessageId: command.stableMessageId,
123
+ decisionId: command.request.decisionId,
124
+ requestDigest: command.request.requestDigest,
125
+ attemptId: command.attemptId,
126
+ state: "confirmed",
127
+ });
128
+ }
129
+ catch (error) {
130
+ response = await host.report({
131
+ kind: "channel.settle",
132
+ stableMessageId: command.stableMessageId,
133
+ decisionId: command.request.decisionId,
134
+ requestDigest: command.request.requestDigest,
135
+ attemptId: command.attemptId,
136
+ state: "unknown",
137
+ errorCode: errorMessage(error),
138
+ });
139
+ }
140
+ continue;
141
+ }
142
+ telegram.setRequests(command.requests);
143
+ const polled = await telegram.poll(command.cursor);
144
+ cursor = polled.cursor;
145
+ for (const answer of polled.answers) {
146
+ await host.report({
147
+ kind: "channel.answer",
148
+ stableMessageId: channelStableMessageId([
149
+ launch.profile,
150
+ answer.request.decisionId,
151
+ answer.eventId,
152
+ ]),
153
+ decisionId: answer.request.decisionId,
154
+ requestDigest: answer.request.requestDigest,
155
+ response: answer.response,
156
+ actorId: answer.actorId,
157
+ chatId: answer.chatId,
158
+ eventId: answer.eventId,
159
+ idempotencyKey: `telegram:${launch.profile}:${answer.eventId}`,
160
+ cursor,
161
+ });
162
+ }
163
+ response = await host.report({
164
+ kind: "channel.ready",
165
+ stableMessageId: controlId("ready"),
166
+ cursor,
167
+ });
168
+ }
169
+ await host.report({
170
+ kind: "channel.exiting",
171
+ stableMessageId: controlId("exiting"),
172
+ cursor,
173
+ });
174
+ return 0;
175
+ }
176
+ function readLaunch() {
177
+ const encoded = process.env.PI_WORKFLOWS_CHANNEL_LAUNCH;
178
+ if (encoded === undefined)
179
+ throw new Error("Channel adapter launch envelope is missing");
180
+ let value;
181
+ try {
182
+ value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
183
+ }
184
+ catch {
185
+ throw new Error("Channel adapter launch envelope is invalid");
186
+ }
187
+ return parseChannelAdapterLaunch(value);
188
+ }
189
+ async function main() {
190
+ try {
191
+ process.exitCode = await runChannelAdapter();
192
+ }
193
+ catch (error) {
194
+ process.stderr.write(`${errorMessage(error)}\n`);
195
+ process.exitCode = 1;
196
+ }
197
+ }
198
+ if (process.argv[1] === fileURLToPath(import.meta.url))
199
+ void main();
200
+ export function encodeChannelLaunch(launch) {
201
+ return Buffer.from(canonicalJson(launch)).toString("base64url");
202
+ }
203
+ //# sourceMappingURL=adapter-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-entry.js","sourceRoot":"","sources":["../../src/channels/adapter-entry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ export type DecisionChannelConfig = {
2
+ schema: "pi-workflows.channels.v1";
3
+ audiences: Record<string, {
4
+ channels: string[];
5
+ accept: "first-valid-answer";
6
+ }>;
7
+ telegramProfiles?: Record<string, {
8
+ credential: string;
9
+ allowedUserIds: string[];
10
+ allowedChatIds: string[];
11
+ }>;
12
+ };
13
+ export type DecisionCredentialConfig = {
14
+ schema: "pi-workflows.credentials.v1";
15
+ telegram: Record<string, {
16
+ tokenFile: string;
17
+ }>;
18
+ };
19
+ export type LoadedDecisionChannelConfig = {
20
+ channels: DecisionChannelConfig;
21
+ credentials: Record<string, string>;
22
+ configDir: string;
23
+ };
24
+ export type TelegramFetch = (input: string, init?: RequestInit) => Promise<{
25
+ ok: boolean;
26
+ status: number;
27
+ json(): Promise<unknown>;
28
+ }>;
29
+ export declare function decisionConfigDir(env?: NodeJS.ProcessEnv): string;
30
+ export declare function audienceChannels(config: DecisionChannelConfig | null, audience: string): string[];
31
+ export declare function loadDecisionChannelConfig(configDir?: string): Promise<LoadedDecisionChannelConfig | null>;
32
+ export declare function verifyTelegramTokenFile(tokenFile: string, fetchFn?: TelegramFetch, apiBase?: string): Promise<void>;
33
+ export declare function writeDecisionChannelProfile(options: {
34
+ configDir?: string;
35
+ audience: string;
36
+ profile: string;
37
+ credential: string;
38
+ tokenFile: string;
39
+ allowedUserIds: string[];
40
+ allowedChatIds: string[];
41
+ }): Promise<void>;
42
+ export declare function parseChannelConfig(value: unknown): DecisionChannelConfig;
43
+ export declare function parseCredentialConfig(value: unknown): DecisionCredentialConfig;
@@ -0,0 +1,234 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ const DEFAULT_API_BASE = "https://api.telegram.org";
6
+ const NUMERIC_ID = /^-?[0-9]+$/;
7
+ const SIMPLE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
8
+ export function decisionConfigDir(env = process.env) {
9
+ return env.PI_WORKFLOWS_CONFIG_DIR ?? path.join(os.homedir(), ".config", "pi-workflows");
10
+ }
11
+ export function audienceChannels(config, audience) {
12
+ return [...(config?.audiences[audience]?.channels ?? ["pi"])];
13
+ }
14
+ export async function loadDecisionChannelConfig(configDir = decisionConfigDir()) {
15
+ const channelPath = path.join(configDir, "channels.json");
16
+ let channelsRaw;
17
+ try {
18
+ channelsRaw = JSON.parse(await fs.readFile(channelPath, "utf8"));
19
+ }
20
+ catch (error) {
21
+ if (error.code === "ENOENT")
22
+ return null;
23
+ throw error;
24
+ }
25
+ await requirePrivateFile(channelPath, "pi-workflows channel profile");
26
+ const channels = parseChannelConfig(channelsRaw);
27
+ const profiles = Object.values(channels.telegramProfiles ?? {});
28
+ if (profiles.length === 0)
29
+ return { channels, credentials: {}, configDir };
30
+ const credentialPath = path.join(configDir, "credentials.json");
31
+ await requirePrivateFile(credentialPath, "pi-workflows credential profile");
32
+ const credentialsConfig = parseCredentialConfig(JSON.parse(await fs.readFile(credentialPath, "utf8")));
33
+ const credentials = {};
34
+ for (const profile of profiles) {
35
+ const credential = credentialsConfig.telegram[profile.credential];
36
+ if (credential === undefined) {
37
+ throw new Error(`Telegram credential ${profile.credential} is not configured`);
38
+ }
39
+ if (!path.isAbsolute(credential.tokenFile)) {
40
+ throw new Error(`Telegram credential ${profile.credential} tokenFile must be absolute`);
41
+ }
42
+ await requirePrivateFile(credential.tokenFile, `Telegram credential ${profile.credential} token file`);
43
+ const token = (await fs.readFile(credential.tokenFile, "utf8")).trim();
44
+ if (token.length === 0) {
45
+ throw new Error(`Telegram credential ${profile.credential} token file is empty`);
46
+ }
47
+ credentials[profile.credential] = token;
48
+ }
49
+ return { channels, credentials, configDir };
50
+ }
51
+ export async function verifyTelegramTokenFile(tokenFile, fetchFn, apiBase = DEFAULT_API_BASE) {
52
+ const request = fetchFn ?? fetch;
53
+ if (!path.isAbsolute(tokenFile))
54
+ throw new Error("Telegram token file path must be absolute");
55
+ await requirePrivateFile(tokenFile, "Telegram token file");
56
+ const token = (await fs.readFile(tokenFile, "utf8")).trim();
57
+ if (token.length === 0)
58
+ throw new Error("Telegram token file is empty");
59
+ let response;
60
+ try {
61
+ response = await request(`${apiBase}/bot${encodeURIComponent(token)}/getMe`, {
62
+ method: "POST",
63
+ headers: { "content-type": "application/json" },
64
+ body: "{}",
65
+ });
66
+ }
67
+ catch {
68
+ throw new Error("Telegram credential verification did not return a response");
69
+ }
70
+ if (!response.ok) {
71
+ throw new Error(`Telegram credential verification failed with HTTP ${response.status}`);
72
+ }
73
+ const body = requireRecord(await response.json(), "Telegram credential verification response");
74
+ if (body.ok !== true)
75
+ throw new Error("Telegram credential verification was rejected");
76
+ }
77
+ export async function writeDecisionChannelProfile(options) {
78
+ const configDir = options.configDir ?? decisionConfigDir();
79
+ requireSimpleId(options.audience, "Decision audience");
80
+ requireSimpleId(options.profile, "Telegram profile");
81
+ requireSimpleId(options.credential, "Telegram credential");
82
+ validateNumericIds(options.allowedUserIds, "Telegram allowed user IDs");
83
+ validateNumericIds(options.allowedChatIds, "Telegram allowed chat IDs");
84
+ if (!path.isAbsolute(options.tokenFile)) {
85
+ throw new Error("Telegram token file path must be absolute");
86
+ }
87
+ await requirePrivateFile(options.tokenFile, "Telegram token file");
88
+ await fs.mkdir(configDir, { recursive: true, mode: 0o700 });
89
+ const channelPath = path.join(configDir, "channels.json");
90
+ const credentialPath = path.join(configDir, "credentials.json");
91
+ const existingChannels = await readOptionalJson(channelPath);
92
+ const existingCredentials = await readOptionalJson(credentialPath);
93
+ const channels = existingChannels === null
94
+ ? { schema: "pi-workflows.channels.v1", audiences: {} }
95
+ : parseChannelConfig(existingChannels);
96
+ const credentials = existingCredentials === null
97
+ ? { schema: "pi-workflows.credentials.v1", telegram: {} }
98
+ : parseCredentialConfig(existingCredentials);
99
+ channels.telegramProfiles = {
100
+ ...channels.telegramProfiles,
101
+ [options.profile]: {
102
+ credential: options.credential,
103
+ allowedUserIds: [...options.allowedUserIds],
104
+ allowedChatIds: [...options.allowedChatIds],
105
+ },
106
+ };
107
+ const priorAudience = channels.audiences[options.audience];
108
+ const channelId = `telegram:${options.profile}`;
109
+ channels.audiences[options.audience] = {
110
+ channels: [...new Set([...(priorAudience?.channels ?? ["pi"]), channelId])],
111
+ accept: "first-valid-answer",
112
+ };
113
+ credentials.telegram[options.credential] = { tokenFile: options.tokenFile };
114
+ await writePrivateJson(channelPath, channels);
115
+ await writePrivateJson(credentialPath, credentials);
116
+ }
117
+ export function parseChannelConfig(value) {
118
+ const input = requireRecord(value, "Decision channel configuration");
119
+ if (input.schema !== "pi-workflows.channels.v1") {
120
+ throw new Error("Decision channel configuration schema is invalid");
121
+ }
122
+ const audiencesInput = requireRecord(input.audiences, "Decision channel audiences");
123
+ const audiences = {};
124
+ for (const [audience, raw] of Object.entries(audiencesInput)) {
125
+ requireSimpleId(audience, "Decision audience");
126
+ const record = requireRecord(raw, `Decision audience ${audience}`);
127
+ const channels = stringArray(record.channels, `Decision audience ${audience} channels`);
128
+ if (channels.length === 0)
129
+ throw new Error(`Decision audience ${audience} has no channels`);
130
+ if (record.accept !== "first-valid-answer") {
131
+ throw new Error(`Decision audience ${audience} accept policy is invalid`);
132
+ }
133
+ audiences[audience] = { channels, accept: "first-valid-answer" };
134
+ }
135
+ const telegramProfiles = {};
136
+ if (input.telegramProfiles !== undefined) {
137
+ const profiles = requireRecord(input.telegramProfiles, "Telegram profiles");
138
+ for (const [name, raw] of Object.entries(profiles)) {
139
+ requireSimpleId(name, "Telegram profile");
140
+ const profile = requireRecord(raw, `Telegram profile ${name}`);
141
+ const credential = requireSimpleId(profile.credential, `Telegram profile ${name} credential`);
142
+ const allowedUserIds = stringArray(profile.allowedUserIds, `Telegram profile ${name} allowedUserIds`);
143
+ const allowedChatIds = stringArray(profile.allowedChatIds, `Telegram profile ${name} allowedChatIds`);
144
+ validateNumericIds(allowedUserIds, `Telegram profile ${name} allowedUserIds`);
145
+ validateNumericIds(allowedChatIds, `Telegram profile ${name} allowedChatIds`);
146
+ if (allowedUserIds.length === 0 || allowedChatIds.length === 0) {
147
+ throw new Error(`Telegram profile ${name} must allow at least one user and chat`);
148
+ }
149
+ telegramProfiles[name] = { credential, allowedUserIds, allowedChatIds };
150
+ }
151
+ }
152
+ for (const [audience, rule] of Object.entries(audiences)) {
153
+ for (const channel of rule.channels) {
154
+ if (channel === "pi")
155
+ continue;
156
+ if (!channel.startsWith("telegram:") ||
157
+ telegramProfiles[channel.slice("telegram:".length)] === undefined) {
158
+ throw new Error(`Decision audience ${audience} references unknown channel ${channel}`);
159
+ }
160
+ }
161
+ }
162
+ return {
163
+ schema: "pi-workflows.channels.v1",
164
+ audiences,
165
+ ...(Object.keys(telegramProfiles).length === 0 ? {} : { telegramProfiles }),
166
+ };
167
+ }
168
+ export function parseCredentialConfig(value) {
169
+ const input = requireRecord(value, "Decision credential configuration");
170
+ if (input.schema !== "pi-workflows.credentials.v1") {
171
+ throw new Error("Decision credential configuration schema is invalid");
172
+ }
173
+ const telegramInput = requireRecord(input.telegram, "Telegram credentials");
174
+ const telegram = {};
175
+ for (const [name, raw] of Object.entries(telegramInput)) {
176
+ requireSimpleId(name, "Telegram credential");
177
+ const credential = requireRecord(raw, `Telegram credential ${name}`);
178
+ telegram[name] = { tokenFile: requireString(credential.tokenFile, "Telegram tokenFile") };
179
+ }
180
+ return { schema: "pi-workflows.credentials.v1", telegram };
181
+ }
182
+ async function requirePrivateFile(filePath, label) {
183
+ const stat = await fs.stat(filePath);
184
+ if (!stat.isFile())
185
+ throw new Error(`${label} must be a file`);
186
+ if ((stat.mode & 0o077) !== 0)
187
+ throw new Error(`${label} permissions must be 0600`);
188
+ }
189
+ async function readOptionalJson(filePath) {
190
+ try {
191
+ return JSON.parse(await fs.readFile(filePath, "utf8"));
192
+ }
193
+ catch (error) {
194
+ if (error.code === "ENOENT")
195
+ return null;
196
+ throw error;
197
+ }
198
+ }
199
+ async function writePrivateJson(filePath, value) {
200
+ const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
201
+ await fs.writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
202
+ await fs.rename(temporary, filePath);
203
+ await fs.chmod(filePath, 0o600);
204
+ }
205
+ function validateNumericIds(values, label) {
206
+ if (values.some((value) => !NUMERIC_ID.test(value))) {
207
+ throw new Error(`${label} must contain only numeric IDs`);
208
+ }
209
+ }
210
+ function requireSimpleId(value, label) {
211
+ const text = requireString(value, label);
212
+ if (!SIMPLE_ID.test(text))
213
+ throw new Error(`${label} is invalid`);
214
+ return text;
215
+ }
216
+ function requireString(value, label) {
217
+ if (typeof value !== "string" || value.trim().length === 0) {
218
+ throw new Error(`${label} must be a non-empty string`);
219
+ }
220
+ return value.trim();
221
+ }
222
+ function stringArray(value, label) {
223
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
224
+ throw new Error(`${label} must be an array of strings`);
225
+ }
226
+ return [...value];
227
+ }
228
+ function requireRecord(value, label) {
229
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
230
+ throw new Error(`${label} must be an object`);
231
+ }
232
+ return value;
233
+ }
234
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/channels/config.ts"],"names":[],"mappings":"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"}