pi-forge 1.2.3 → 1.2.5

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 (83) hide show
  1. package/README.md +1 -1
  2. package/dist/client/assets/{CodeMirrorEditor-1gu-DS9k.js → CodeMirrorEditor-DXmxwE2Z.js} +2 -2
  3. package/dist/client/assets/{CodeMirrorEditor-1gu-DS9k.js.map → CodeMirrorEditor-DXmxwE2Z.js.map} +1 -1
  4. package/dist/client/assets/index-CMSjnWtF.js +365 -0
  5. package/dist/client/assets/index-CMSjnWtF.js.map +1 -0
  6. package/dist/client/assets/index-Cp8qEy7Q.css +1 -0
  7. package/dist/client/index.html +2 -2
  8. package/dist/client/sw.js +1 -1
  9. package/dist/client/sw.js.map +1 -1
  10. package/dist/server/agent-extensions/compaction-continuation.js +65 -0
  11. package/dist/server/agent-extensions/compaction-continuation.js.map +1 -0
  12. package/dist/server/agent-resource-loader.js +10 -0
  13. package/dist/server/agent-resource-loader.js.map +1 -1
  14. package/dist/server/ask-user-question/envelope.js +56 -0
  15. package/dist/server/ask-user-question/envelope.js.map +1 -0
  16. package/dist/server/ask-user-question/prompt-strings.js +44 -0
  17. package/dist/server/ask-user-question/prompt-strings.js.map +1 -0
  18. package/dist/server/ask-user-question/registry.js +157 -0
  19. package/dist/server/ask-user-question/registry.js.map +1 -0
  20. package/dist/server/ask-user-question/tool.js +115 -0
  21. package/dist/server/ask-user-question/tool.js.map +1 -0
  22. package/dist/server/ask-user-question/types.js +27 -0
  23. package/dist/server/ask-user-question/types.js.map +1 -0
  24. package/dist/server/ask-user-question/validate.js +135 -0
  25. package/dist/server/ask-user-question/validate.js.map +1 -0
  26. package/dist/server/config.js +10 -0
  27. package/dist/server/config.js.map +1 -1
  28. package/dist/server/index.js +16 -0
  29. package/dist/server/index.js.map +1 -1
  30. package/dist/server/mcp/tool-bridge.js +14 -8
  31. package/dist/server/mcp/tool-bridge.js.map +1 -1
  32. package/dist/server/processes/envelope.js +60 -0
  33. package/dist/server/processes/envelope.js.map +1 -0
  34. package/dist/server/processes/log-store.js +132 -0
  35. package/dist/server/processes/log-store.js.map +1 -0
  36. package/dist/server/processes/manager.js +348 -0
  37. package/dist/server/processes/manager.js.map +1 -0
  38. package/dist/server/processes/prompt-strings.js +43 -0
  39. package/dist/server/processes/prompt-strings.js.map +1 -0
  40. package/dist/server/processes/tool.js +273 -0
  41. package/dist/server/processes/tool.js.map +1 -0
  42. package/dist/server/processes/types.js +21 -0
  43. package/dist/server/processes/types.js.map +1 -0
  44. package/dist/server/processes/watches.js +59 -0
  45. package/dist/server/processes/watches.js.map +1 -0
  46. package/dist/server/quick-actions.js +141 -0
  47. package/dist/server/quick-actions.js.map +1 -0
  48. package/dist/server/routes/ask-user-question.js +129 -0
  49. package/dist/server/routes/ask-user-question.js.map +1 -0
  50. package/dist/server/routes/config.js +12 -0
  51. package/dist/server/routes/config.js.map +1 -1
  52. package/dist/server/routes/processes.js +228 -0
  53. package/dist/server/routes/processes.js.map +1 -0
  54. package/dist/server/routes/quick-actions.js +384 -0
  55. package/dist/server/routes/quick-actions.js.map +1 -0
  56. package/dist/server/routes/todos.js +67 -0
  57. package/dist/server/routes/todos.js.map +1 -0
  58. package/dist/server/session-registry.js +72 -4
  59. package/dist/server/session-registry.js.map +1 -1
  60. package/dist/server/sse-bridge.js +225 -4
  61. package/dist/server/sse-bridge.js.map +1 -1
  62. package/dist/server/todo/envelope.js +87 -0
  63. package/dist/server/todo/envelope.js.map +1 -0
  64. package/dist/server/todo/invariants.js +21 -0
  65. package/dist/server/todo/invariants.js.map +1 -0
  66. package/dist/server/todo/prompt-strings.js +29 -0
  67. package/dist/server/todo/prompt-strings.js.map +1 -0
  68. package/dist/server/todo/reducer.js +189 -0
  69. package/dist/server/todo/reducer.js.map +1 -0
  70. package/dist/server/todo/replay.js +45 -0
  71. package/dist/server/todo/replay.js.map +1 -0
  72. package/dist/server/todo/store.js +92 -0
  73. package/dist/server/todo/store.js.map +1 -0
  74. package/dist/server/todo/task-graph.js +60 -0
  75. package/dist/server/todo/task-graph.js.map +1 -0
  76. package/dist/server/todo/tool.js +95 -0
  77. package/dist/server/todo/tool.js.map +1 -0
  78. package/dist/server/todo/types.js +23 -0
  79. package/dist/server/todo/types.js.map +1 -0
  80. package/package.json +1 -1
  81. package/dist/client/assets/index-BxZV6ddv.js +0 -359
  82. package/dist/client/assets/index-BxZV6ddv.js.map +0 -1
  83. package/dist/client/assets/index-KUhxvBxw.css +0 -1
@@ -0,0 +1,273 @@
1
+ import { Type } from "typebox";
2
+ import { config } from "../config.js";
3
+ import { buildListMessage, buildStartMessage, err, ok } from "./envelope.js";
4
+ import { processManager } from "./manager.js";
5
+ import { PROMPT_GUIDELINES, PROMPT_SNIPPET, TOOL_DESCRIPTION } from "./prompt-strings.js";
6
+ import { TOOL_LABEL, TOOL_NAME, } from "./types.js";
7
+ /**
8
+ * JSON Schema for `process` tool params. Mirrors `@aliou/pi-processes`'s
9
+ * TypeBox schema field-for-field so an agent prompt authored
10
+ * against the plugin sees the same input surface.
11
+ */
12
+ const inputSchema = {
13
+ type: "object",
14
+ required: ["action"],
15
+ properties: {
16
+ action: {
17
+ type: "string",
18
+ enum: ["start", "list", "output", "logs", "kill", "clear", "write"],
19
+ },
20
+ command: { type: "string", description: "Command to run (required for start)" },
21
+ name: {
22
+ type: "string",
23
+ description: "Friendly name for the process (required for start, e.g. 'backend-dev', 'test-runner')",
24
+ },
25
+ id: {
26
+ type: "string",
27
+ description: "Process ID, returned by start and list actions (required for output/kill/logs/write)",
28
+ },
29
+ input: {
30
+ type: "string",
31
+ description: "Data to write to process stdin (required for write action)",
32
+ },
33
+ end: {
34
+ type: "boolean",
35
+ description: "Close stdin after writing (optional for write action, use for programs reading until EOF)",
36
+ },
37
+ alertOnSuccess: {
38
+ type: "boolean",
39
+ description: "Get a turn to react when process completes successfully (default: false). Use for builds/tests where you need confirmation.",
40
+ },
41
+ alertOnFailure: {
42
+ type: "boolean",
43
+ description: "Get a turn to react when process fails/crashes (default: true). Use to be alerted of unexpected failures.",
44
+ },
45
+ alertOnKill: {
46
+ type: "boolean",
47
+ description: "Get a turn to react when process is killed by external signal (default: false). Note: killing via tool never triggers a turn.",
48
+ },
49
+ logWatches: {
50
+ type: "array",
51
+ items: {
52
+ type: "object",
53
+ required: ["pattern"],
54
+ additionalProperties: false,
55
+ properties: {
56
+ pattern: {
57
+ type: "string",
58
+ description: "Regular expression pattern to match against process output lines",
59
+ },
60
+ stream: {
61
+ type: "string",
62
+ enum: ["stdout", "stderr", "both"],
63
+ description: "Which stream to watch (default: both). Use stdout/stderr to reduce noise.",
64
+ },
65
+ repeat: {
66
+ type: "boolean",
67
+ description: "Trigger every time this pattern matches (default: false, one-time)",
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ };
74
+ function validateLogWatches(watches) {
75
+ if (watches === undefined)
76
+ return null;
77
+ if (!Array.isArray(watches))
78
+ return "Invalid parameter: logWatches must be an array";
79
+ for (const [i, w] of watches.entries()) {
80
+ if (typeof w.pattern !== "string" || w.pattern.trim().length === 0) {
81
+ return `Invalid logWatches[${i}].pattern: expected non-empty string`;
82
+ }
83
+ try {
84
+ new RegExp(w.pattern);
85
+ }
86
+ catch (e) {
87
+ return `Invalid logWatches[${i}].pattern: ${e instanceof Error ? e.message : "invalid regex"}`;
88
+ }
89
+ if (w.stream !== undefined && !["stdout", "stderr", "both"].includes(w.stream)) {
90
+ return `Invalid logWatches[${i}].stream: expected stdout, stderr, or both`;
91
+ }
92
+ if (w.repeat !== undefined && typeof w.repeat !== "boolean") {
93
+ return `Invalid logWatches[${i}].repeat: expected boolean`;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ /**
99
+ * Build the `process` tool for one session. Bound to the
100
+ * sessionId + workspace path so `start` knows the right cwd and
101
+ * the manager keys state correctly.
102
+ *
103
+ * Contract-compatible with `@aliou/pi-processes`. Implementation
104
+ * is independent; see `manager.ts` for the spawn/lifecycle code
105
+ * and `docs/processes.md` for the cross-reference.
106
+ */
107
+ export function createProcessTool(sessionId, workspacePath) {
108
+ return {
109
+ name: TOOL_NAME,
110
+ label: TOOL_LABEL,
111
+ description: TOOL_DESCRIPTION,
112
+ promptSnippet: PROMPT_SNIPPET,
113
+ promptGuidelines: PROMPT_GUIDELINES,
114
+ parameters: Type.Unsafe(inputSchema),
115
+ async execute(_toolCallId, params) {
116
+ const p = params;
117
+ switch (p.action) {
118
+ case "start":
119
+ return executeStart(sessionId, workspacePath, p);
120
+ case "list":
121
+ return executeList(sessionId);
122
+ case "output":
123
+ return executeOutput(sessionId, p);
124
+ case "logs":
125
+ return executeLogs(sessionId, p);
126
+ case "kill":
127
+ return executeKill(sessionId, p);
128
+ case "clear":
129
+ return executeClear(sessionId);
130
+ case "write":
131
+ return executeWrite(sessionId, p);
132
+ default:
133
+ return err(p.action, `Unknown action: ${String(p.action)}`);
134
+ }
135
+ },
136
+ };
137
+ }
138
+ function executeStart(sessionId, workspacePath, p) {
139
+ // Defense in depth — the client also hides the tool under
140
+ // MINIMAL_UI, but a stale tab or scripted caller could still
141
+ // invoke it. Refuse at the tool boundary.
142
+ if (config.minimalUi) {
143
+ return err("start", "process.start is disabled under MINIMAL_UI");
144
+ }
145
+ if (p.name === undefined || p.name.length === 0) {
146
+ return err("start", "Missing required parameter: name");
147
+ }
148
+ if (p.command === undefined || p.command.length === 0) {
149
+ return err("start", "Missing required parameter: command");
150
+ }
151
+ const watchErr = validateLogWatches(p.logWatches);
152
+ if (watchErr !== null)
153
+ return err("start", watchErr);
154
+ let info;
155
+ try {
156
+ const startOpts = {};
157
+ if (p.alertOnSuccess !== undefined)
158
+ startOpts.alertOnSuccess = p.alertOnSuccess;
159
+ if (p.alertOnFailure !== undefined)
160
+ startOpts.alertOnFailure = p.alertOnFailure;
161
+ if (p.alertOnKill !== undefined)
162
+ startOpts.alertOnKill = p.alertOnKill;
163
+ if (p.logWatches !== undefined)
164
+ startOpts.logWatches = p.logWatches;
165
+ info = processManager.start(sessionId, p.name, p.command, workspacePath, startOpts);
166
+ }
167
+ catch (e) {
168
+ return err("start", `Failed to start: ${e instanceof Error ? e.message : String(e)}`);
169
+ }
170
+ return ok({
171
+ action: "start",
172
+ success: true,
173
+ message: buildStartMessage(info),
174
+ process: info,
175
+ });
176
+ }
177
+ function executeList(sessionId) {
178
+ const processes = processManager.list(sessionId);
179
+ return ok({
180
+ action: "list",
181
+ success: true,
182
+ message: buildListMessage(processes),
183
+ processes,
184
+ });
185
+ }
186
+ function executeOutput(sessionId, p) {
187
+ if (p.id === undefined || p.id.length === 0) {
188
+ return err("output", "Missing required parameter: id");
189
+ }
190
+ const out = processManager.output(sessionId, p.id);
191
+ if (out === undefined)
192
+ return err("output", `Process not found: ${p.id}`);
193
+ const stdoutTail = out.stdout.slice(Math.max(0, out.stdout.length - 50)).join("\n");
194
+ const stderrTail = out.stderr.slice(Math.max(0, out.stderr.length - 50)).join("\n");
195
+ const parts = [`Status: ${out.status}`];
196
+ if (stdoutTail.length > 0)
197
+ parts.push("--- stdout ---", stdoutTail);
198
+ if (stderrTail.length > 0)
199
+ parts.push("--- stderr ---", stderrTail);
200
+ return ok({
201
+ action: "output",
202
+ success: true,
203
+ message: parts.join("\n"),
204
+ output: out,
205
+ });
206
+ }
207
+ function executeLogs(sessionId, p) {
208
+ if (p.id === undefined || p.id.length === 0) {
209
+ return err("logs", "Missing required parameter: id");
210
+ }
211
+ const files = processManager.logFiles(sessionId, p.id);
212
+ if (files === undefined)
213
+ return err("logs", `Process not found: ${p.id}`);
214
+ return ok({
215
+ action: "logs",
216
+ success: true,
217
+ message: `Log files for ${p.id}:\n stdout: ${files.stdoutFile}\n stderr: ${files.stderrFile}\n\nUse the read tool to inspect them.`,
218
+ logFiles: files,
219
+ });
220
+ }
221
+ async function executeKill(sessionId, p) {
222
+ if (p.id === undefined || p.id.length === 0) {
223
+ return err("kill", "Missing required parameter: id");
224
+ }
225
+ const result = await processManager.kill(sessionId, p.id);
226
+ if (!result.ok) {
227
+ if (result.reason === "not_found") {
228
+ return err("kill", `Process not found: ${p.id}`);
229
+ }
230
+ return err("kill", `Failed to kill ${p.id}: ${result.reason}`);
231
+ }
232
+ return ok({
233
+ action: "kill",
234
+ success: true,
235
+ message: `Killed "${result.info.name}" (${result.info.id})`,
236
+ process: result.info,
237
+ });
238
+ }
239
+ function executeClear(sessionId) {
240
+ const cleared = processManager.clear(sessionId);
241
+ return ok({
242
+ action: "clear",
243
+ success: true,
244
+ message: `Cleared ${cleared} finished process(es)`,
245
+ cleared,
246
+ });
247
+ }
248
+ async function executeWrite(sessionId, p) {
249
+ if (p.id === undefined || p.id.length === 0) {
250
+ return err("write", "Missing required parameter: id");
251
+ }
252
+ if (p.input === undefined) {
253
+ return err("write", "Missing required parameter: input");
254
+ }
255
+ const result = await processManager.write(sessionId, p.id, p.input, p.end === true);
256
+ if (!result.ok) {
257
+ const reason = result.reason;
258
+ const message = reason === "not_found"
259
+ ? `Process not found: ${p.id}`
260
+ : reason === "process_exited"
261
+ ? `Process ${p.id} has already exited`
262
+ : reason === "stdin_closed"
263
+ ? `stdin for ${p.id} is already closed`
264
+ : `Failed to write to ${p.id}`;
265
+ return err("write", message);
266
+ }
267
+ return ok({
268
+ action: "write",
269
+ success: true,
270
+ message: p.end === true ? `Wrote to ${p.id} and closed stdin` : `Wrote to ${p.id}`,
271
+ });
272
+ }
273
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/processes/tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shape definitions for the `process` tool. The wire contract —
3
+ * tool name (`process`), action enum (`start | list | output |
4
+ * logs | kill | clear | write`), per-process `ProcessInfo`
5
+ * fields, status state machine, log-watch shape, and
6
+ * `ProcessesDetails` envelope — is contract-compatible with
7
+ * `@aliou/pi-processes`. An agent prompt authored against the
8
+ * plugin works against this implementation unchanged.
9
+ *
10
+ * Implementation is independent; types and validation rules were
11
+ * derived from the plugin's published schema and tests. See
12
+ * `docs/processes.md` for the cross-reference.
13
+ */
14
+ export const TOOL_NAME = "process";
15
+ export const TOOL_LABEL = "Process";
16
+ export const LIVE_STATUSES = new Set([
17
+ "running",
18
+ "terminating",
19
+ "terminate_timeout",
20
+ ]);
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/processes/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC;AACnC,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC;AAYpC,MAAM,CAAC,MAAM,aAAa,GAA+B,IAAI,GAAG,CAAC;IAC/D,SAAS;IACT,aAAa;IACb,mBAAmB;CACpB,CAAC,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Compile the agent-supplied watches. Validation lives at the
3
+ * tool boundary (see tool.ts); this function trusts shape +
4
+ * regex validity and just normalises defaults.
5
+ */
6
+ export function compileWatches(input) {
7
+ if (input === undefined)
8
+ return [];
9
+ return input.map((w, i) => ({
10
+ index: i,
11
+ pattern: w.pattern,
12
+ stream: w.stream ?? "both",
13
+ repeat: w.repeat === true,
14
+ regex: new RegExp(w.pattern),
15
+ fired: false,
16
+ }));
17
+ }
18
+ /**
19
+ * Run a freshly-emitted line against the compiled set. Returns
20
+ * the watches that matched AND are eligible to fire (skipping
21
+ * single-fire watches that already fired). Mutates `fired` for
22
+ * matched single-fire watches so the same line stream of repeats
23
+ * doesn't keep alerting.
24
+ */
25
+ export function evaluateWatches(watches, source, line) {
26
+ const hits = [];
27
+ for (const w of watches) {
28
+ if (w.stream !== "both" && w.stream !== source)
29
+ continue;
30
+ if (!w.repeat && w.fired)
31
+ continue;
32
+ if (!w.regex.test(line))
33
+ continue;
34
+ if (!w.repeat)
35
+ w.fired = true;
36
+ hits.push(w);
37
+ }
38
+ return hits;
39
+ }
40
+ /**
41
+ * Build the wire shape for a single match. Used by the manager
42
+ * when it fans out a `process_watch_matched` event.
43
+ */
44
+ export function buildMatchEvent(watch, processId, processName, processCommand, source, line) {
45
+ return {
46
+ processId,
47
+ processName,
48
+ processCommand,
49
+ source,
50
+ line,
51
+ watch: {
52
+ index: watch.index,
53
+ pattern: watch.pattern,
54
+ stream: watch.stream,
55
+ repeat: watch.repeat,
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=watches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watches.js","sourceRoot":"","sources":["../../src/processes/watches.ts"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAsC;IACnE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1B,KAAK,EAAE,CAAC;QACR,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,MAAM;QAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,KAAK,IAAI;QACzB,KAAK,EAAE,IAAI,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QAC5B,KAAK,EAAE,KAAK;KACb,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAwB,EACxB,MAA2B,EAC3B,IAAY;IAEZ,MAAM,IAAI,GAAoB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;YAAE,SAAS;QACzD,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK;YAAE,SAAS;QACnC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,IAAI,CAAC,CAAC,CAAC,MAAM;YAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAoB,EACpB,SAAiB,EACjB,WAAmB,EACnB,cAAsB,EACtB,MAA2B,EAC3B,IAAY;IAEZ,OAAO;QACL,SAAS;QACT,WAAW;QACX,cAAc;QACd,MAAM;QACN,IAAI;QACJ,KAAK,EAAE;YACL,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,MAAM,EAAE,KAAK,CAAC,MAAM;SACrB;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,141 @@
1
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { config } from "./config.js";
5
+ export class QuickActionNotFoundError extends Error {
6
+ constructor(id) {
7
+ super(`quick action not found: ${id}`);
8
+ this.name = "QuickActionNotFoundError";
9
+ }
10
+ }
11
+ /** Hard cap on a single command string. Mirrors the system-prompt
12
+ * addendum cap — large enough for a multi-line shell snippet, small
13
+ * enough to keep the wire surface bounded. */
14
+ export const MAX_COMMAND_BYTES = 20_000;
15
+ /** Hard cap on a single prompt template. Pi compaction lives further
16
+ * down the stack, but bounding here keeps an accidental megabyte paste
17
+ * from silently landing in the composer. */
18
+ export const MAX_PROMPT_BYTES = 50_000;
19
+ /** Default command timeout (30 s) and absolute ceiling (5 min). A
20
+ * five-minute build is the upper end of "still feels like a chip";
21
+ * past that, the user should be using the terminal. */
22
+ export const DEFAULT_TIMEOUT_MS = 30_000;
23
+ export const MAX_TIMEOUT_MS = 300_000;
24
+ export function isCommandAction(a) {
25
+ return typeof a.command === "string" && a.command.length > 0;
26
+ }
27
+ export function isPromptAction(a) {
28
+ return typeof a.text === "string" && a.text.length > 0;
29
+ }
30
+ async function ensureDir() {
31
+ await mkdir(dirname(config.quickActionsFile), { recursive: true });
32
+ }
33
+ async function atomicWrite(actions) {
34
+ await ensureDir();
35
+ const target = config.quickActionsFile;
36
+ const tmp = `${target}.${randomUUID()}.tmp`;
37
+ await writeFile(tmp, JSON.stringify(actions, null, 2), { mode: 0o600 });
38
+ try {
39
+ await rename(tmp, target);
40
+ }
41
+ catch (err) {
42
+ await unlink(tmp).catch(() => undefined);
43
+ throw err;
44
+ }
45
+ }
46
+ function isAction(v) {
47
+ if (typeof v !== "object" || v === null)
48
+ return false;
49
+ const r = v;
50
+ if (typeof r.id !== "string" || typeof r.name !== "string")
51
+ return false;
52
+ const hasCmd = typeof r.command === "string" && r.command.length > 0;
53
+ const hasText = typeof r.text === "string" && r.text.length > 0;
54
+ // Drop entries that look corrupted (neither kind, or both) rather
55
+ // than surfacing them to the route layer, which would have to
56
+ // re-validate. Strict-on-read keeps the in-memory shape clean.
57
+ return hasCmd !== hasText;
58
+ }
59
+ /**
60
+ * Serialise all read-modify-write sequences over quick-actions.json
61
+ * — same pattern as projects.json. Without it, concurrent
62
+ * POST /quick-actions calls can race the rename().
63
+ */
64
+ let lock = Promise.resolve();
65
+ function withLock(fn) {
66
+ const next = lock.then(fn, fn);
67
+ lock = next.catch(() => undefined);
68
+ return next;
69
+ }
70
+ export async function readQuickActions() {
71
+ try {
72
+ const raw = await readFile(config.quickActionsFile, "utf8");
73
+ if (raw.trim().length === 0)
74
+ return [];
75
+ const parsed = JSON.parse(raw);
76
+ if (!Array.isArray(parsed))
77
+ return [];
78
+ return parsed.filter(isAction);
79
+ }
80
+ catch (err) {
81
+ if (err.code === "ENOENT")
82
+ return [];
83
+ throw err;
84
+ }
85
+ }
86
+ export async function getQuickAction(id) {
87
+ const list = await readQuickActions();
88
+ return list.find((a) => a.id === id);
89
+ }
90
+ /**
91
+ * Create a new action. The caller (route layer) is responsible for
92
+ * shape validation (one-of command/text, byte caps, etc.) — this
93
+ * function only assigns the id and persists.
94
+ */
95
+ export async function createQuickAction(input) {
96
+ return withLock(async () => {
97
+ const list = await readQuickActions();
98
+ const action = { ...input, id: randomUUID() };
99
+ list.push(action);
100
+ await atomicWrite(list);
101
+ return action;
102
+ });
103
+ }
104
+ export async function updateQuickAction(id, patch) {
105
+ return withLock(async () => {
106
+ const list = await readQuickActions();
107
+ const idx = list.findIndex((a) => a.id === id);
108
+ if (idx === -1)
109
+ throw new QuickActionNotFoundError(id);
110
+ const existing = list[idx];
111
+ if (existing === undefined)
112
+ throw new QuickActionNotFoundError(id);
113
+ // Build the merged record explicitly so a switch from command to
114
+ // prompt (or vice-versa) drops the now-unused fields rather than
115
+ // carrying them as dead weight. The caller passes the FULL desired
116
+ // shape on every update; the patch arg is union-typed for ergonomic
117
+ // partial calls but the route layer always sends the complete form.
118
+ const merged = { ...existing, ...patch, id };
119
+ if (typeof patch.command === "string" && patch.command.length > 0) {
120
+ delete merged.text;
121
+ delete merged.mode;
122
+ }
123
+ else if (typeof patch.text === "string" && patch.text.length > 0) {
124
+ delete merged.command;
125
+ delete merged.timeoutMs;
126
+ }
127
+ list[idx] = merged;
128
+ await atomicWrite(list);
129
+ return merged;
130
+ });
131
+ }
132
+ export async function deleteQuickAction(id) {
133
+ await withLock(async () => {
134
+ const list = await readQuickActions();
135
+ const next = list.filter((a) => a.id !== id);
136
+ if (next.length === list.length)
137
+ throw new QuickActionNotFoundError(id);
138
+ await atomicWrite(next);
139
+ });
140
+ }
141
+ //# sourceMappingURL=quick-actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quick-actions.js","sourceRoot":"","sources":["../src/quick-actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ import { getSession } from "../session-registry.js";
2
+ import { answerPending, getPendingForSession } from "../ask-user-question/registry.js";
3
+ import { buildResult } from "../ask-user-question/envelope.js";
4
+ import { errorSchema } from "./_schemas.js";
5
+ /**
6
+ * POST /sessions/:id/ask-user-question/answer
7
+ *
8
+ * The browser modal calls this with the user's answers (or with
9
+ * `cancelled: true` when they pick "Chat about this" / close the
10
+ * modal). Resolves the pending entry in the registry, which
11
+ * propagates back to the tool's awaiting `execute()` — the agent
12
+ * gets a clean tool result and continues.
13
+ *
14
+ * Per-call ownership is enforced by matching `requestId` against
15
+ * this session's pending list. A spoofed requestId from a session
16
+ * the caller doesn't own returns 404.
17
+ */
18
+ const answerBodySchema = {
19
+ type: "object",
20
+ required: ["requestId"],
21
+ additionalProperties: false,
22
+ properties: {
23
+ requestId: { type: "string", minLength: 1 },
24
+ cancelled: { type: "boolean" },
25
+ answers: {
26
+ type: "array",
27
+ items: {
28
+ type: "object",
29
+ required: ["questionIndex", "question", "kind"],
30
+ properties: {
31
+ questionIndex: { type: "integer", minimum: 0 },
32
+ question: { type: "string" },
33
+ kind: { type: "string", enum: ["option", "custom", "chat", "multi"] },
34
+ answer: { type: ["string", "null"] },
35
+ selected: { type: "array", items: { type: "string" } },
36
+ notes: { type: "string" },
37
+ preview: { type: "string" },
38
+ },
39
+ },
40
+ },
41
+ },
42
+ };
43
+ export const askUserQuestionRoutes = async (fastify) => {
44
+ fastify.get("/sessions/:id/ask-user-question/pending", {
45
+ schema: {
46
+ description: "List ask_user_question requests currently waiting on an answer " +
47
+ "for this session. The browser modal uses this on initial mount " +
48
+ "as a fallback to the SSE snapshot re-delivery path.",
49
+ tags: ["sessions"],
50
+ params: {
51
+ type: "object",
52
+ required: ["id"],
53
+ properties: { id: { type: "string" } },
54
+ },
55
+ response: {
56
+ 200: {
57
+ type: "object",
58
+ required: ["pending"],
59
+ properties: {
60
+ pending: {
61
+ type: "array",
62
+ items: {
63
+ type: "object",
64
+ required: ["requestId", "questions"],
65
+ properties: {
66
+ requestId: { type: "string" },
67
+ questions: { type: "array" },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ 404: errorSchema,
74
+ },
75
+ },
76
+ }, async (req, reply) => {
77
+ const live = getSession(req.params.id);
78
+ if (live === undefined) {
79
+ return reply.code(404).send({ error: "session_not_found" });
80
+ }
81
+ const pending = getPendingForSession(req.params.id).map((p) => ({
82
+ requestId: p.requestId,
83
+ questions: p.questions,
84
+ }));
85
+ return { pending };
86
+ });
87
+ fastify.post("/sessions/:id/ask-user-question/answer", {
88
+ schema: {
89
+ description: "Submit a user's answers to an in-flight ask_user_question " +
90
+ "tool call. Pass `cancelled: true` (with or without partial " +
91
+ "answers) when the user dismissed the modal or picked the " +
92
+ "'Chat about this' escape. The tool's execute() resolves " +
93
+ "with the constructed envelope; the agent then continues.",
94
+ tags: ["sessions"],
95
+ params: {
96
+ type: "object",
97
+ required: ["id"],
98
+ properties: { id: { type: "string" } },
99
+ },
100
+ body: answerBodySchema,
101
+ response: {
102
+ 204: { type: "null" },
103
+ 404: errorSchema,
104
+ },
105
+ },
106
+ }, async (req, reply) => {
107
+ const live = getSession(req.params.id);
108
+ if (live === undefined) {
109
+ return reply.code(404).send({ error: "session_not_found" });
110
+ }
111
+ const cancelled = req.body.cancelled === true;
112
+ const answers = Array.isArray(req.body.answers) ? req.body.answers : [];
113
+ // Total question count is whatever the registry has on file —
114
+ // the envelope's "partial cancel" summary line reads it from
115
+ // here to phrase correctly. Look it up before answering since
116
+ // the registry entry vanishes on resolve.
117
+ const pending = getPendingForSession(req.params.id).find((p) => p.requestId === req.body.requestId);
118
+ const questionCount = pending?.questions.length ?? answers.length;
119
+ const envelope = buildResult(answers, { cancelled, questionCount });
120
+ const ok = answerPending(req.body.requestId, req.params.id, envelope);
121
+ if (!ok) {
122
+ // Either unknown requestId or one that belongs to another
123
+ // session (defense against cross-session spoofing).
124
+ return reply.code(404).send({ error: "request_not_found" });
125
+ }
126
+ return reply.code(204).send();
127
+ });
128
+ };
129
+ //# sourceMappingURL=ask-user-question.js.map