palmier 0.4.4 → 0.4.6

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 (65) hide show
  1. package/README.md +32 -33
  2. package/dist/agents/agent-instructions.md +4 -11
  3. package/dist/agents/agent.d.ts +2 -2
  4. package/dist/agents/claude.d.ts +1 -1
  5. package/dist/agents/claude.js +6 -6
  6. package/dist/agents/codex.d.ts +1 -1
  7. package/dist/agents/codex.js +5 -5
  8. package/dist/agents/copilot.d.ts +1 -1
  9. package/dist/agents/copilot.js +5 -5
  10. package/dist/agents/gemini.d.ts +1 -1
  11. package/dist/agents/gemini.js +7 -7
  12. package/dist/agents/openclaw.d.ts +1 -1
  13. package/dist/agents/openclaw.js +3 -3
  14. package/dist/agents/shared-prompt.d.ts +2 -4
  15. package/dist/agents/shared-prompt.js +9 -4
  16. package/dist/commands/init.js +31 -2
  17. package/dist/commands/pair.d.ts +1 -1
  18. package/dist/commands/pair.js +12 -15
  19. package/dist/commands/run.js +33 -54
  20. package/dist/commands/serve.d.ts +1 -1
  21. package/dist/commands/serve.js +9 -2
  22. package/dist/events.d.ts +2 -2
  23. package/dist/events.js +15 -16
  24. package/dist/index.js +0 -25
  25. package/dist/pending-requests.d.ts +27 -0
  26. package/dist/pending-requests.js +39 -0
  27. package/dist/rpc-handler.js +15 -8
  28. package/dist/transports/http-transport.d.ts +4 -2
  29. package/dist/transports/http-transport.js +226 -77
  30. package/dist/types.d.ts +7 -16
  31. package/package.json +1 -1
  32. package/src/agents/agent-instructions.md +4 -11
  33. package/src/agents/agent.ts +2 -2
  34. package/src/agents/claude.ts +5 -5
  35. package/src/agents/codex.ts +4 -4
  36. package/src/agents/copilot.ts +5 -5
  37. package/src/agents/gemini.ts +6 -6
  38. package/src/agents/openclaw.ts +3 -3
  39. package/src/agents/shared-prompt.ts +12 -6
  40. package/src/commands/init.ts +34 -3
  41. package/src/commands/pair.ts +11 -14
  42. package/src/commands/run.ts +31 -68
  43. package/src/commands/serve.ts +11 -2
  44. package/src/events.ts +14 -15
  45. package/src/index.ts +0 -26
  46. package/src/pending-requests.ts +55 -0
  47. package/src/rpc-handler.ts +15 -9
  48. package/src/transports/http-transport.ts +235 -135
  49. package/src/types.ts +10 -16
  50. package/test/agent-output-parsing.test.ts +1 -14
  51. package/dist/commands/lan.d.ts +0 -8
  52. package/dist/commands/lan.js +0 -44
  53. package/dist/commands/notify.d.ts +0 -9
  54. package/dist/commands/notify.js +0 -43
  55. package/dist/commands/request-input.d.ts +0 -10
  56. package/dist/commands/request-input.js +0 -49
  57. package/dist/lan-lock.d.ts +0 -7
  58. package/dist/lan-lock.js +0 -18
  59. package/dist/user-input.d.ts +0 -15
  60. package/dist/user-input.js +0 -50
  61. package/src/commands/lan.ts +0 -48
  62. package/src/commands/notify.ts +0 -44
  63. package/src/commands/request-input.ts +0 -51
  64. package/src/lan-lock.ts +0 -16
  65. package/src/user-input.ts +0 -67
@@ -9,22 +9,21 @@ import { getAgent } from "../agents/agent.js";
9
9
  import { getPlatform } from "../platform/index.js";
10
10
  import { TASK_SUCCESS_MARKER, TASK_FAILURE_MARKER, TASK_REPORT_PREFIX, TASK_PERMISSION_PREFIX } from "../agents/shared-prompt.js";
11
11
  import { publishHostEvent } from "../events.js";
12
- import { waitForUserInput } from "../user-input.js";
13
12
  /**
14
- * Invoke the agent CLI with a retry loop for permissions and user input.
13
+ * Invoke the agent CLI with a continuation loop for permissions and user input.
15
14
  *
16
15
  * Both standard and command-triggered execution use this.
17
16
  * The `invokeTask` is the ParsedTask whose prompt is passed to the agent
18
17
  * (for command-triggered mode this is the per-line augmented task).
19
18
  */
20
- async function invokeAgentWithRetry(ctx, invokeTask) {
21
- let retryPrompt;
19
+ async function invokeAgentWithContinuation(ctx, invokeTask) {
20
+ let followupPrompt;
22
21
  // eslint-disable-next-line no-constant-condition
23
22
  while (true) {
24
- const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(invokeTask, retryPrompt, ctx.transientPermissions);
23
+ const { command, args, stdin } = ctx.agent.getTaskRunCommandLine(invokeTask, followupPrompt, ctx.transientPermissions);
25
24
  const result = await spawnCommand(command, args, {
26
25
  cwd: getRunDir(ctx.taskDir, ctx.runId),
27
- env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId) },
26
+ env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ?? 7400) },
28
27
  echoStdout: true,
29
28
  resolveOnFailure: true,
30
29
  stdin,
@@ -39,10 +38,9 @@ async function invokeAgentWithRetry(ctx, invokeTask) {
39
38
  content: stripPalmierMarkers(result.output),
40
39
  attachments: reportFiles.length > 0 ? reportFiles : undefined,
41
40
  });
42
- // Permission retry
43
- if (outcome === "failed" && requiredPermissions.length > 0) {
44
- const response = await requestPermission(ctx.nc, ctx.config, ctx.task, ctx.taskDir, requiredPermissions);
45
- await publishPermissionResolved(ctx.nc, ctx.config, ctx.taskId, response);
41
+ // Permission handling — agent requested permissions
42
+ if (requiredPermissions.length > 0) {
43
+ const response = await requestPermission(ctx.config, ctx.task, ctx.taskDir, requiredPermissions);
46
44
  if (response === "aborted") {
47
45
  await appendAndNotify(ctx, {
48
46
  role: "user",
@@ -69,10 +67,13 @@ async function invokeAgentWithRetry(ctx, invokeTask) {
69
67
  else {
70
68
  ctx.transientPermissions = [...ctx.transientPermissions, ...newPerms];
71
69
  }
72
- retryPrompt = "Permissions granted, please continue.";
73
- continue;
70
+ // If the agent actually failed, retry with the new permissions
71
+ if (outcome === "failed") {
72
+ followupPrompt = "Permissions granted, please continue.";
73
+ continue;
74
+ }
74
75
  }
75
- // Normal completion (success or non-retryable failure)
76
+ // Normal completion (success or terminal failure)
76
77
  return { outcome };
77
78
  }
78
79
  }
@@ -141,9 +142,7 @@ export async function runCommand(taskId) {
141
142
  await publishHostEvent(nc, config.hostId, taskId, { event_type: "result-updated", run_id: runId });
142
143
  // If requires_confirmation, notify clients and wait
143
144
  if (task.frontmatter.requires_confirmation) {
144
- const confirmed = await requestConfirmation(nc, config, task, taskDir);
145
- const resolvedStatus = confirmed ? "confirmed" : "aborted";
146
- await publishConfirmResolved(nc, config, taskId, resolvedStatus);
145
+ const confirmed = await requestConfirmation(config, task, taskDir);
147
146
  if (!confirmed) {
148
147
  console.log("Task aborted by user.");
149
148
  appendRunMessage(taskDir, runId, { role: "status", time: Date.now(), content: "", type: "aborted" });
@@ -177,7 +176,7 @@ export async function runCommand(taskId) {
177
176
  time: Date.now(),
178
177
  content: task.body || task.frontmatter.user_prompt,
179
178
  });
180
- const result = await invokeAgentWithRetry(ctx, task);
179
+ const result = await invokeAgentWithContinuation(ctx, task);
181
180
  const outcome = resolveOutcome(taskDir, result.outcome);
182
181
  appendRunMessage(taskDir, runId, { role: "status", time: Date.now(), content: "", type: outcome });
183
182
  await publishTaskEvent(nc, config, taskDir, taskId, outcome, taskName, runId);
@@ -217,7 +216,7 @@ async function runCommandTriggeredMode(ctx) {
217
216
  console.log(`[command-triggered] Spawning: ${commandStr}`);
218
217
  const child = spawnStreamingCommand(commandStr, {
219
218
  cwd: getRunDir(ctx.taskDir, ctx.runId),
220
- env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId) },
219
+ env: { ...ctx.guiEnv, PALMIER_TASK_ID: ctx.task.frontmatter.id, PALMIER_RUN_DIR: getRunDir(ctx.taskDir, ctx.runId), PALMIER_HTTP_PORT: String(ctx.config.httpPort ?? 7400) },
221
220
  });
222
221
  let linesProcessed = 0;
223
222
  let invocationsSucceeded = 0;
@@ -255,7 +254,7 @@ async function runCommandTriggeredMode(ctx) {
255
254
  frontmatter: { ...ctx.task.frontmatter, user_prompt: perLinePrompt },
256
255
  body: "",
257
256
  };
258
- const result = await invokeAgentWithRetry(ctx, perLineTask);
257
+ const result = await invokeAgentWithContinuation(ctx, perLineTask);
259
258
  if (result.outcome === "finished") {
260
259
  invocationsSucceeded++;
261
260
  }
@@ -333,49 +332,29 @@ async function publishTaskEvent(nc, config, taskDir, taskId, eventType, taskName
333
332
  payload.run_id = runId;
334
333
  await publishHostEvent(nc, config.hostId, taskId, payload);
335
334
  }
336
- /**
337
- * Notify clients that a confirmation request has been resolved.
338
- */
339
- async function publishConfirmResolved(nc, config, taskId, status) {
340
- await publishHostEvent(nc, config.hostId, taskId, {
341
- event_type: "confirm-resolved",
342
- host_id: config.hostId,
343
- status,
335
+ async function requestPermission(config, task, taskDir, requiredPermissions) {
336
+ const port = config.httpPort ?? 7400;
337
+ const params = new URLSearchParams({
338
+ taskId: task.frontmatter.id,
339
+ taskName: task.frontmatter.name,
340
+ permissions: JSON.stringify(requiredPermissions),
344
341
  });
345
- }
346
- async function requestPermission(nc, config, task, taskDir, requiredPermissions) {
347
- const currentStatus = readTaskStatus(taskDir);
348
- writeTaskStatus(taskDir, { ...currentStatus, pending_permission: requiredPermissions });
349
- await publishHostEvent(nc, config.hostId, task.frontmatter.id, {
350
- event_type: "permission-request",
351
- host_id: config.hostId,
352
- required_permissions: requiredPermissions,
353
- name: task.frontmatter.name,
354
- });
355
- const userInput = await waitForUserInput(taskDir);
356
- const response = userInput[0];
342
+ const res = await fetch(`http://localhost:${port}/request-permission?${params}`);
343
+ const { response } = await res.json();
357
344
  writeTaskStatus(taskDir, {
358
345
  running_state: response === "aborted" ? "aborted" : "started",
359
346
  time_stamp: Date.now(),
360
347
  });
361
348
  return response;
362
349
  }
363
- async function publishPermissionResolved(nc, config, taskId, status) {
364
- await publishHostEvent(nc, config.hostId, taskId, {
365
- event_type: "permission-resolved",
366
- host_id: config.hostId,
367
- status,
368
- });
369
- }
370
- async function requestConfirmation(nc, config, task, taskDir) {
371
- const currentStatus = readTaskStatus(taskDir);
372
- writeTaskStatus(taskDir, { ...currentStatus, pending_confirmation: true });
373
- await publishHostEvent(nc, config.hostId, task.frontmatter.id, {
374
- event_type: "confirm-request",
375
- host_id: config.hostId,
350
+ async function requestConfirmation(config, task, taskDir) {
351
+ const port = config.httpPort ?? 7400;
352
+ const params = new URLSearchParams({
353
+ taskId: task.frontmatter.id,
354
+ taskName: task.frontmatter.name,
376
355
  });
377
- const userInput = await waitForUserInput(taskDir);
378
- const confirmed = userInput[0] === "confirmed";
356
+ const res = await fetch(`http://localhost:${port}/request-confirmation?${params}`);
357
+ const { confirmed } = await res.json();
379
358
  writeTaskStatus(taskDir, {
380
359
  running_state: confirmed ? "started" : "aborted",
381
360
  time_stamp: Date.now(),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Start the persistent RPC handler (NATS only).
2
+ * Start the persistent RPC handler (NATS + HTTP).
3
3
  */
4
4
  export declare function serveCommand(): Promise<void>;
5
5
  //# sourceMappingURL=serve.d.ts.map
@@ -4,6 +4,7 @@ import { loadConfig } from "../config.js";
4
4
  import { connectNats } from "../nats-client.js";
5
5
  import { createRpcHandler } from "../rpc-handler.js";
6
6
  import { startNatsTransport } from "../transports/nats-transport.js";
7
+ import { startHttpTransport } from "../transports/http-transport.js";
7
8
  import { getTaskDir, readTaskStatus, writeTaskStatus, parseTaskFile, appendRunMessage } from "../task.js";
8
9
  import { publishHostEvent } from "../events.js";
9
10
  import { getPlatform } from "../platform/index.js";
@@ -69,7 +70,7 @@ async function checkStaleTasks(config, nc) {
69
70
  }
70
71
  }
71
72
  /**
72
- * Start the persistent RPC handler (NATS only).
73
+ * Start the persistent RPC handler (NATS + HTTP).
73
74
  */
74
75
  export async function serveCommand() {
75
76
  const config = loadConfig();
@@ -91,6 +92,12 @@ export async function serveCommand() {
91
92
  });
92
93
  }, POLL_INTERVAL_MS);
93
94
  const handleRpc = createRpcHandler(config, nc);
94
- await startNatsTransport(config, handleRpc, nc);
95
+ const httpPort = config.httpPort ?? 7400;
96
+ // Start NATS transport (loops forever, fire-and-forget)
97
+ if (nc) {
98
+ startNatsTransport(config, handleRpc, nc);
99
+ }
100
+ // Start HTTP transport (loops forever)
101
+ await startHttpTransport(config, handleRpc, httpPort, nc);
95
102
  }
96
103
  //# sourceMappingURL=serve.js.map
package/dist/events.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { type NatsConnection } from "nats";
2
2
  /**
3
- * Broadcast an event to connected clients via NATS and HTTP SSE (if LAN server is running).
3
+ * Broadcast an event to connected clients via NATS and HTTP SSE.
4
4
  *
5
5
  * - NATS: publishes to `host-event.{hostId}.{taskId}`
6
- * - HTTP: POSTs to the LAN server's `/internal/event` endpoint (auto-detected via lockfile)
6
+ * - HTTP: POSTs to the serve daemon's `/event` endpoint
7
7
  */
8
8
  export declare function publishHostEvent(nc: NatsConnection | undefined, hostId: string, taskId: string, payload: Record<string, unknown>): Promise<void>;
9
9
  //# sourceMappingURL=events.d.ts.map
package/dist/events.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { StringCodec } from "nats";
2
- import { getLanPort } from "./lan-lock.js";
2
+ import { loadConfig } from "./config.js";
3
3
  const sc = StringCodec();
4
4
  /**
5
- * Broadcast an event to connected clients via NATS and HTTP SSE (if LAN server is running).
5
+ * Broadcast an event to connected clients via NATS and HTTP SSE.
6
6
  *
7
7
  * - NATS: publishes to `host-event.{hostId}.{taskId}`
8
- * - HTTP: POSTs to the LAN server's `/internal/event` endpoint (auto-detected via lockfile)
8
+ * - HTTP: POSTs to the serve daemon's `/event` endpoint
9
9
  */
10
10
  export async function publishHostEvent(nc, hostId, taskId, payload) {
11
11
  const subject = `host-event.${hostId}.${taskId}`;
@@ -13,19 +13,18 @@ export async function publishHostEvent(nc, hostId, taskId, payload) {
13
13
  nc.publish(subject, sc.encode(JSON.stringify(payload)));
14
14
  console.log(`[nats] ${subject} →`, payload);
15
15
  }
16
- const lanPort = getLanPort();
17
- if (lanPort) {
18
- try {
19
- await fetch(`http://localhost:${lanPort}/internal/event`, {
20
- method: "POST",
21
- headers: { "Content-Type": "application/json" },
22
- body: JSON.stringify({ task_id: taskId, ...payload }),
23
- });
24
- console.log(`[http] host-event: ${taskId} →`, payload);
25
- }
26
- catch {
27
- // LAN server may have shut down — ignore
28
- }
16
+ const config = loadConfig();
17
+ const port = config.httpPort ?? 7400;
18
+ try {
19
+ await fetch(`http://localhost:${port}/event`, {
20
+ method: "POST",
21
+ headers: { "Content-Type": "application/json" },
22
+ body: JSON.stringify({ task_id: taskId, ...payload }),
23
+ });
24
+ console.log(`[http] host-event: ${taskId} →`, payload);
25
+ }
26
+ catch {
27
+ // Serve HTTP may not be ready yet — ignore
29
28
  }
30
29
  }
31
30
  //# sourceMappingURL=events.js.map
package/dist/index.js CHANGED
@@ -8,10 +8,7 @@ import { initCommand } from "./commands/init.js";
8
8
  import { infoCommand } from "./commands/info.js";
9
9
  import { runCommand } from "./commands/run.js";
10
10
  import { serveCommand } from "./commands/serve.js";
11
- import { notifyCommand } from "./commands/notify.js";
12
- import { requestInputCommand } from "./commands/request-input.js";
13
11
  import { pairCommand } from "./commands/pair.js";
14
- import { lanCommand } from "./commands/lan.js";
15
12
  import { restartCommand } from "./commands/restart.js";
16
13
  import { sessionsListCommand, sessionsRevokeCommand, sessionsRevokeAllCommand } from "./commands/sessions.js";
17
14
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -51,34 +48,12 @@ program
51
48
  .action(async () => {
52
49
  await restartCommand();
53
50
  });
54
- program
55
- .command("notify")
56
- .description("Send a push notification to the user")
57
- .requiredOption("--title <title>", "Notification title")
58
- .requiredOption("--body <body>", "Notification body text")
59
- .action(async (opts) => {
60
- await notifyCommand(opts);
61
- });
62
- program
63
- .command("request-input")
64
- .description("Request input from the user (requires PALMIER_TASK_ID env var)")
65
- .requiredOption("--description <desc...>", "Input descriptions to show the user")
66
- .action(async (opts) => {
67
- await requestInputCommand(opts);
68
- });
69
51
  program
70
52
  .command("pair")
71
53
  .description("Generate a pairing code for connecting a PWA client")
72
54
  .action(async () => {
73
55
  await pairCommand();
74
56
  });
75
- program
76
- .command("lan")
77
- .description("Start an on-demand LAN server for direct HTTP connections")
78
- .option("-p, --port <port>", "Port to listen on", "7400")
79
- .action(async (opts) => {
80
- await lanCommand({ port: parseInt(opts.port, 10) });
81
- });
82
57
  const sessionsCmd = program
83
58
  .command("sessions")
84
59
  .description("Manage paired client sessions");
@@ -0,0 +1,27 @@
1
+ import type { RequiredPermission } from "./types.js";
2
+ export interface PendingRequest {
3
+ type: "confirmation" | "permission" | "input";
4
+ resolve: (value: string[]) => void;
5
+ /** Permission list (for 'permission') or input descriptions (for 'input'). */
6
+ params?: RequiredPermission[] | string[];
7
+ }
8
+ /**
9
+ * Register a pending request for a task. Returns a Promise that resolves
10
+ * when `resolvePending` is called with the user's response.
11
+ * Only one pending request per task at a time.
12
+ */
13
+ export declare function registerPending(taskId: string, type: PendingRequest["type"], params?: PendingRequest["params"]): Promise<string[]>;
14
+ /**
15
+ * Resolve a pending request with the user's response.
16
+ * Returns true if a pending request was found and resolved.
17
+ */
18
+ export declare function resolvePending(taskId: string, value: string[]): boolean;
19
+ /**
20
+ * Get the current pending request for a task (if any).
21
+ */
22
+ export declare function getPending(taskId: string): PendingRequest | undefined;
23
+ /**
24
+ * Remove a pending request without resolving it.
25
+ */
26
+ export declare function removePending(taskId: string): void;
27
+ //# sourceMappingURL=pending-requests.d.ts.map
@@ -0,0 +1,39 @@
1
+ const pending = new Map();
2
+ /**
3
+ * Register a pending request for a task. Returns a Promise that resolves
4
+ * when `resolvePending` is called with the user's response.
5
+ * Only one pending request per task at a time.
6
+ */
7
+ export function registerPending(taskId, type, params) {
8
+ if (pending.has(taskId)) {
9
+ return Promise.reject(new Error(`Task ${taskId} already has a pending request`));
10
+ }
11
+ return new Promise((resolve) => {
12
+ pending.set(taskId, { type, resolve, params });
13
+ });
14
+ }
15
+ /**
16
+ * Resolve a pending request with the user's response.
17
+ * Returns true if a pending request was found and resolved.
18
+ */
19
+ export function resolvePending(taskId, value) {
20
+ const entry = pending.get(taskId);
21
+ if (!entry)
22
+ return false;
23
+ pending.delete(taskId);
24
+ entry.resolve(value);
25
+ return true;
26
+ }
27
+ /**
28
+ * Get the current pending request for a task (if any).
29
+ */
30
+ export function getPending(taskId) {
31
+ return pending.get(taskId);
32
+ }
33
+ /**
34
+ * Remove a pending request without resolving it.
35
+ */
36
+ export function removePending(taskId) {
37
+ pending.delete(taskId);
38
+ }
39
+ //# sourceMappingURL=pending-requests.js.map
@@ -5,6 +5,7 @@ import { fileURLToPath } from "url";
5
5
  import { spawn } from "child_process";
6
6
  import { parse as parseYaml } from "yaml";
7
7
  import { listTasks, parseTaskFile, writeTaskFile, getTaskDir, readTaskStatus, writeTaskStatus, readHistory, deleteHistoryEntry, appendTaskList, removeFromTaskList, appendHistory, createRunDir, appendRunMessage, getRunDir } from "./task.js";
8
+ import { resolvePending, getPending } from "./pending-requests.js";
8
9
  import { getPlatform } from "./platform/index.js";
9
10
  import { spawnCommand } from "./spawn-command.js";
10
11
  import crossSpawn from "cross-spawn";
@@ -131,8 +132,8 @@ export function createRpcHandler(config, nc) {
131
132
  };
132
133
  }
133
134
  async function handleRpc(request) {
134
- // Session token validation: always require a valid session token
135
- if (!request.sessionToken || !validateSession(request.sessionToken)) {
135
+ // Session token validation: skip for trusted localhost requests
136
+ if (!request.localhost && (!request.sessionToken || !validateSession(request.sessionToken))) {
136
137
  return { error: "Unauthorized" };
137
138
  }
138
139
  switch (request.method) {
@@ -447,7 +448,14 @@ export function createRpcHandler(config, nc) {
447
448
  if (!status) {
448
449
  return { task_id: params.id, error: "No status found" };
449
450
  }
450
- return { task_id: params.id, ...status };
451
+ const pending = getPending(params.id);
452
+ return {
453
+ task_id: params.id,
454
+ ...status,
455
+ ...(pending?.type === "confirmation" ? { pending_confirmation: true } : {}),
456
+ ...(pending?.type === "permission" ? { pending_permission: pending.params } : {}),
457
+ ...(pending?.type === "input" ? { pending_input: pending.params } : {}),
458
+ };
451
459
  }
452
460
  case "task.result": {
453
461
  const params = request.params;
@@ -494,14 +502,13 @@ export function createRpcHandler(config, nc) {
494
502
  }
495
503
  case "task.user_input": {
496
504
  const params = request.params;
497
- const taskDir = getTaskDir(config.projectRoot, params.id);
498
- const currentStatus = readTaskStatus(taskDir);
499
- if (!currentStatus?.pending_confirmation && !currentStatus?.pending_permission?.length && !currentStatus?.pending_input?.length) {
505
+ const pending = getPending(params.id);
506
+ if (!pending) {
500
507
  return { ok: false, error: "not pending" };
501
508
  }
502
- writeTaskStatus(taskDir, { ...currentStatus, user_input: params.value });
509
+ const resolved = resolvePending(params.id, params.value);
503
510
  console.log(`[task.user_input] ${params.id} → ${params.value}`);
504
- return { ok: true };
511
+ return { ok: resolved };
505
512
  }
506
513
  case "taskrun.list": {
507
514
  const params = request.params;
@@ -1,7 +1,9 @@
1
+ import { type NatsConnection } from "nats";
1
2
  import type { HostConfig, RpcMessage } from "../types.js";
2
3
  export declare function detectLanIp(): string;
3
4
  /**
4
- * Start the HTTP transport: Express-like server with RPC, SSE, and health endpoints.
5
+ * Start the HTTP transport: server with RPC, SSE, PWA proxy, pairing, and
6
+ * localhost-only agent endpoints (notify, request-input, confirmation, permission).
5
7
  */
6
- export declare function startHttpTransport(config: HostConfig, handleRpc: (req: RpcMessage) => Promise<unknown>, port: number, pairingCode?: string, onReady?: () => void): Promise<void>;
8
+ export declare function startHttpTransport(config: HostConfig, handleRpc: (req: RpcMessage) => Promise<unknown>, port: number, nc: NatsConnection | undefined, pairingCode?: string, onReady?: () => void): Promise<void>;
7
9
  //# sourceMappingURL=http-transport.d.ts.map