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.
- package/README.md +32 -33
- package/dist/agents/agent-instructions.md +4 -11
- package/dist/agents/agent.d.ts +2 -2
- package/dist/agents/claude.d.ts +1 -1
- package/dist/agents/claude.js +6 -6
- package/dist/agents/codex.d.ts +1 -1
- package/dist/agents/codex.js +5 -5
- package/dist/agents/copilot.d.ts +1 -1
- package/dist/agents/copilot.js +5 -5
- package/dist/agents/gemini.d.ts +1 -1
- package/dist/agents/gemini.js +7 -7
- package/dist/agents/openclaw.d.ts +1 -1
- package/dist/agents/openclaw.js +3 -3
- package/dist/agents/shared-prompt.d.ts +2 -4
- package/dist/agents/shared-prompt.js +9 -4
- package/dist/commands/init.js +31 -2
- package/dist/commands/pair.d.ts +1 -1
- package/dist/commands/pair.js +12 -15
- package/dist/commands/run.js +33 -54
- package/dist/commands/serve.d.ts +1 -1
- package/dist/commands/serve.js +9 -2
- package/dist/events.d.ts +2 -2
- package/dist/events.js +15 -16
- package/dist/index.js +0 -25
- package/dist/pending-requests.d.ts +27 -0
- package/dist/pending-requests.js +39 -0
- package/dist/rpc-handler.js +15 -8
- package/dist/transports/http-transport.d.ts +4 -2
- package/dist/transports/http-transport.js +226 -77
- package/dist/types.d.ts +7 -16
- package/package.json +1 -1
- package/src/agents/agent-instructions.md +4 -11
- package/src/agents/agent.ts +2 -2
- package/src/agents/claude.ts +5 -5
- package/src/agents/codex.ts +4 -4
- package/src/agents/copilot.ts +5 -5
- package/src/agents/gemini.ts +6 -6
- package/src/agents/openclaw.ts +3 -3
- package/src/agents/shared-prompt.ts +12 -6
- package/src/commands/init.ts +34 -3
- package/src/commands/pair.ts +11 -14
- package/src/commands/run.ts +31 -68
- package/src/commands/serve.ts +11 -2
- package/src/events.ts +14 -15
- package/src/index.ts +0 -26
- package/src/pending-requests.ts +55 -0
- package/src/rpc-handler.ts +15 -9
- package/src/transports/http-transport.ts +235 -135
- package/src/types.ts +10 -16
- package/test/agent-output-parsing.test.ts +1 -14
- package/dist/commands/lan.d.ts +0 -8
- package/dist/commands/lan.js +0 -44
- package/dist/commands/notify.d.ts +0 -9
- package/dist/commands/notify.js +0 -43
- package/dist/commands/request-input.d.ts +0 -10
- package/dist/commands/request-input.js +0 -49
- package/dist/lan-lock.d.ts +0 -7
- package/dist/lan-lock.js +0 -18
- package/dist/user-input.d.ts +0 -15
- package/dist/user-input.js +0 -50
- package/src/commands/lan.ts +0 -48
- package/src/commands/notify.ts +0 -44
- package/src/commands/request-input.ts +0 -51
- package/src/lan-lock.ts +0 -16
- package/src/user-input.ts +0 -67
package/dist/commands/run.js
CHANGED
|
@@ -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
|
|
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
|
|
21
|
-
let
|
|
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,
|
|
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
|
|
43
|
-
if (
|
|
44
|
-
const response = await requestPermission(ctx.
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
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
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
|
378
|
-
const 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(),
|
package/dist/commands/serve.d.ts
CHANGED
package/dist/commands/serve.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
package/dist/rpc-handler.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
|
498
|
-
|
|
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
|
-
|
|
509
|
+
const resolved = resolvePending(params.id, params.value);
|
|
503
510
|
console.log(`[task.user_input] ${params.id} → ${params.value}`);
|
|
504
|
-
return { ok:
|
|
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:
|
|
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
|