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/lan.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import { loadConfig, CONFIG_DIR } from "../config.js";
|
|
3
|
-
import { createRpcHandler } from "../rpc-handler.js";
|
|
4
|
-
import { startHttpTransport, detectLanIp } from "../transports/http-transport.js";
|
|
5
|
-
import { generatePairingCode } from "./pair.js";
|
|
6
|
-
import { LAN_LOCKFILE } from "../lan-lock.js";
|
|
7
|
-
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
8
|
-
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
9
|
-
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
10
|
-
function writeLockfile(port) {
|
|
11
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
12
|
-
fs.writeFileSync(LAN_LOCKFILE, JSON.stringify({ port, pid: process.pid }), "utf-8");
|
|
13
|
-
}
|
|
14
|
-
function removeLockfile() {
|
|
15
|
-
try {
|
|
16
|
-
fs.unlinkSync(LAN_LOCKFILE);
|
|
17
|
-
}
|
|
18
|
-
catch { /* ignore */ }
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Start an on-demand LAN server for direct HTTP connections.
|
|
22
|
-
* Generates a pairing code and displays it — no separate `palmier pair` needed.
|
|
23
|
-
*/
|
|
24
|
-
export async function lanCommand(opts) {
|
|
25
|
-
const config = loadConfig();
|
|
26
|
-
const port = opts.port;
|
|
27
|
-
const ip = detectLanIp();
|
|
28
|
-
const code = generatePairingCode();
|
|
29
|
-
const handleRpc = createRpcHandler(config);
|
|
30
|
-
// Write lockfile so other palmier processes can discover us
|
|
31
|
-
writeLockfile(port);
|
|
32
|
-
// Clean up on exit
|
|
33
|
-
process.on("SIGINT", () => { removeLockfile(); process.exit(0); });
|
|
34
|
-
process.on("SIGTERM", () => { removeLockfile(); process.exit(0); });
|
|
35
|
-
process.on("exit", removeLockfile);
|
|
36
|
-
// Start the HTTP transport with the pre-generated pairing code
|
|
37
|
-
await startHttpTransport(config, handleRpc, port, code, () => {
|
|
38
|
-
console.log(`\n${bold("Palmier LAN Server")}\n`);
|
|
39
|
-
console.log(` ${cyan("Open the app at:")} ${bold(`http://${ip}:${port}`)}\n`);
|
|
40
|
-
console.log(` ${cyan("Pairing code:")} ${bold(code)}\n`);
|
|
41
|
-
console.log(` ${dim("Press Ctrl+C to stop.")}\n`);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
//# sourceMappingURL=lan.js.map
|
package/dist/commands/notify.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { StringCodec } from "nats";
|
|
2
|
-
import { loadConfig } from "../config.js";
|
|
3
|
-
import { connectNats } from "../nats-client.js";
|
|
4
|
-
/**
|
|
5
|
-
* Send a push notification to the user via NATS.
|
|
6
|
-
* Usage: palmier notify --title "Title" --body "Body text"
|
|
7
|
-
*/
|
|
8
|
-
export async function notifyCommand(opts) {
|
|
9
|
-
const config = loadConfig();
|
|
10
|
-
const nc = await connectNats(config);
|
|
11
|
-
if (!nc) {
|
|
12
|
-
console.error("Error: NATS connection required for push notifications.");
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
const sc = StringCodec();
|
|
16
|
-
const payload = {
|
|
17
|
-
hostId: config.hostId,
|
|
18
|
-
title: opts.title,
|
|
19
|
-
body: opts.body,
|
|
20
|
-
};
|
|
21
|
-
try {
|
|
22
|
-
const subject = `host.${config.hostId}.push.send`;
|
|
23
|
-
const reply = await nc.request(subject, sc.encode(JSON.stringify(payload)), {
|
|
24
|
-
timeout: 15_000,
|
|
25
|
-
});
|
|
26
|
-
const result = JSON.parse(sc.decode(reply.data));
|
|
27
|
-
if (result.ok) {
|
|
28
|
-
console.log("Push notification sent successfully.");
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
console.error(`Failed to send push notification: ${result.error}`);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
console.error(`Error sending push notification: ${err}`);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
await nc.drain();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=notify.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Request input from the user and print responses to stdout.
|
|
3
|
-
* Usage: palmier request-input --description "Question 1" --description "Question 2"
|
|
4
|
-
*
|
|
5
|
-
* Requires PALMIER_TASK_ID and PALMIER_RUN_DIR environment variables.
|
|
6
|
-
*/
|
|
7
|
-
export declare function requestInputCommand(opts: {
|
|
8
|
-
description: string[];
|
|
9
|
-
}): Promise<void>;
|
|
10
|
-
//# sourceMappingURL=request-input.d.ts.map
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { loadConfig } from "../config.js";
|
|
2
|
-
import { connectNats } from "../nats-client.js";
|
|
3
|
-
import { getTaskDir, parseTaskFile, appendRunMessage } from "../task.js";
|
|
4
|
-
import { requestUserInput, publishInputResolved } from "../user-input.js";
|
|
5
|
-
/**
|
|
6
|
-
* Request input from the user and print responses to stdout.
|
|
7
|
-
* Usage: palmier request-input --description "Question 1" --description "Question 2"
|
|
8
|
-
*
|
|
9
|
-
* Requires PALMIER_TASK_ID and PALMIER_RUN_DIR environment variables.
|
|
10
|
-
*/
|
|
11
|
-
export async function requestInputCommand(opts) {
|
|
12
|
-
const taskId = process.env.PALMIER_TASK_ID;
|
|
13
|
-
if (!taskId) {
|
|
14
|
-
console.error("Error: PALMIER_TASK_ID environment variable is not set.");
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
const config = loadConfig();
|
|
18
|
-
const nc = await connectNats(config);
|
|
19
|
-
const taskDir = getTaskDir(config.projectRoot, taskId);
|
|
20
|
-
const task = parseTaskFile(taskDir);
|
|
21
|
-
const runId = process.env.PALMIER_RUN_DIR?.split(/[/\\]/).pop();
|
|
22
|
-
try {
|
|
23
|
-
const response = await requestUserInput(nc, config, taskId, task.frontmatter.name, taskDir, opts.description);
|
|
24
|
-
await publishInputResolved(nc, config, taskId, response === "aborted" ? "aborted" : "provided");
|
|
25
|
-
if (response === "aborted") {
|
|
26
|
-
if (runId) {
|
|
27
|
-
appendRunMessage(taskDir, runId, { role: "user", time: Date.now(), content: "Input request aborted.", type: "input" });
|
|
28
|
-
}
|
|
29
|
-
console.error("User aborted the input request.");
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
if (runId) {
|
|
33
|
-
const lines = opts.description.map((desc, i) => `**${desc}** ${response[i]}`);
|
|
34
|
-
appendRunMessage(taskDir, runId, { role: "user", time: Date.now(), content: lines.join("\n"), type: "input" });
|
|
35
|
-
}
|
|
36
|
-
for (let i = 0; i < opts.description.length; i++) {
|
|
37
|
-
console.log(response[i]);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
41
|
-
console.error(`Error requesting user input: ${err}`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
finally {
|
|
45
|
-
if (nc)
|
|
46
|
-
await nc.drain();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=request-input.js.map
|
package/dist/lan-lock.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare const LAN_LOCKFILE: string;
|
|
2
|
-
/**
|
|
3
|
-
* Read the LAN lockfile to determine if `palmier lan` is running.
|
|
4
|
-
* Returns the port number, or null if not running.
|
|
5
|
-
*/
|
|
6
|
-
export declare function getLanPort(): number | null;
|
|
7
|
-
//# sourceMappingURL=lan-lock.d.ts.map
|
package/dist/lan-lock.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { CONFIG_DIR } from "./config.js";
|
|
4
|
-
export const LAN_LOCKFILE = path.join(CONFIG_DIR, "lan.json");
|
|
5
|
-
/**
|
|
6
|
-
* Read the LAN lockfile to determine if `palmier lan` is running.
|
|
7
|
-
* Returns the port number, or null if not running.
|
|
8
|
-
*/
|
|
9
|
-
export function getLanPort() {
|
|
10
|
-
try {
|
|
11
|
-
const raw = fs.readFileSync(LAN_LOCKFILE, "utf-8");
|
|
12
|
-
return JSON.parse(raw).port;
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=lan-lock.js.map
|
package/dist/user-input.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { HostConfig } from "./types.js";
|
|
2
|
-
import type { NatsConnection } from "nats";
|
|
3
|
-
/**
|
|
4
|
-
* Watch status.json until user_input is populated by an RPC call, then resolve.
|
|
5
|
-
*/
|
|
6
|
-
export declare function waitForUserInput(taskDir: string): Promise<string[]>;
|
|
7
|
-
/**
|
|
8
|
-
* Send an input-request event and wait for the user's response.
|
|
9
|
-
*/
|
|
10
|
-
export declare function requestUserInput(nc: NatsConnection | undefined, config: HostConfig, taskId: string, taskName: string, taskDir: string, inputDescriptions: string[]): Promise<string[] | "aborted">;
|
|
11
|
-
/**
|
|
12
|
-
* Notify clients that an input request has been resolved.
|
|
13
|
-
*/
|
|
14
|
-
export declare function publishInputResolved(nc: NatsConnection | undefined, config: HostConfig, taskId: string, status: "provided" | "aborted"): Promise<void>;
|
|
15
|
-
//# sourceMappingURL=user-input.d.ts.map
|
package/dist/user-input.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { readTaskStatus, writeTaskStatus } from "./task.js";
|
|
4
|
-
import { publishHostEvent } from "./events.js";
|
|
5
|
-
/**
|
|
6
|
-
* Watch status.json until user_input is populated by an RPC call, then resolve.
|
|
7
|
-
*/
|
|
8
|
-
export function waitForUserInput(taskDir) {
|
|
9
|
-
const statusPath = path.join(taskDir, "status.json");
|
|
10
|
-
return new Promise((resolve) => {
|
|
11
|
-
const watcher = fs.watch(statusPath, () => {
|
|
12
|
-
const status = readTaskStatus(taskDir);
|
|
13
|
-
if (!status || !status.user_input?.length)
|
|
14
|
-
return;
|
|
15
|
-
watcher.close();
|
|
16
|
-
resolve(status.user_input);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Send an input-request event and wait for the user's response.
|
|
22
|
-
*/
|
|
23
|
-
export async function requestUserInput(nc, config, taskId, taskName, taskDir, inputDescriptions) {
|
|
24
|
-
const currentStatus = readTaskStatus(taskDir);
|
|
25
|
-
writeTaskStatus(taskDir, { ...currentStatus, pending_input: inputDescriptions });
|
|
26
|
-
await publishHostEvent(nc, config.hostId, taskId, {
|
|
27
|
-
event_type: "input-request",
|
|
28
|
-
host_id: config.hostId,
|
|
29
|
-
input_descriptions: inputDescriptions,
|
|
30
|
-
name: taskName,
|
|
31
|
-
});
|
|
32
|
-
const userInput = await waitForUserInput(taskDir);
|
|
33
|
-
if (userInput.length === 1 && userInput[0] === "aborted") {
|
|
34
|
-
writeTaskStatus(taskDir, { running_state: "aborted", time_stamp: Date.now() });
|
|
35
|
-
return "aborted";
|
|
36
|
-
}
|
|
37
|
-
writeTaskStatus(taskDir, { running_state: "started", time_stamp: Date.now() });
|
|
38
|
-
return userInput;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Notify clients that an input request has been resolved.
|
|
42
|
-
*/
|
|
43
|
-
export async function publishInputResolved(nc, config, taskId, status) {
|
|
44
|
-
await publishHostEvent(nc, config.hostId, taskId, {
|
|
45
|
-
event_type: "input-resolved",
|
|
46
|
-
host_id: config.hostId,
|
|
47
|
-
status,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=user-input.js.map
|
package/src/commands/lan.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import { loadConfig, CONFIG_DIR } from "../config.js";
|
|
3
|
-
import { createRpcHandler } from "../rpc-handler.js";
|
|
4
|
-
import { startHttpTransport, detectLanIp } from "../transports/http-transport.js";
|
|
5
|
-
import { generatePairingCode } from "./pair.js";
|
|
6
|
-
import { LAN_LOCKFILE } from "../lan-lock.js";
|
|
7
|
-
|
|
8
|
-
const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
|
|
9
|
-
const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
|
|
10
|
-
const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
|
|
11
|
-
|
|
12
|
-
function writeLockfile(port: number): void {
|
|
13
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
14
|
-
fs.writeFileSync(LAN_LOCKFILE, JSON.stringify({ port, pid: process.pid }), "utf-8");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function removeLockfile(): void {
|
|
18
|
-
try { fs.unlinkSync(LAN_LOCKFILE); } catch { /* ignore */ }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Start an on-demand LAN server for direct HTTP connections.
|
|
23
|
-
* Generates a pairing code and displays it — no separate `palmier pair` needed.
|
|
24
|
-
*/
|
|
25
|
-
export async function lanCommand(opts: { port: number }): Promise<void> {
|
|
26
|
-
const config = loadConfig();
|
|
27
|
-
const port = opts.port;
|
|
28
|
-
const ip = detectLanIp();
|
|
29
|
-
const code = generatePairingCode();
|
|
30
|
-
|
|
31
|
-
const handleRpc = createRpcHandler(config);
|
|
32
|
-
|
|
33
|
-
// Write lockfile so other palmier processes can discover us
|
|
34
|
-
writeLockfile(port);
|
|
35
|
-
|
|
36
|
-
// Clean up on exit
|
|
37
|
-
process.on("SIGINT", () => { removeLockfile(); process.exit(0); });
|
|
38
|
-
process.on("SIGTERM", () => { removeLockfile(); process.exit(0); });
|
|
39
|
-
process.on("exit", removeLockfile);
|
|
40
|
-
|
|
41
|
-
// Start the HTTP transport with the pre-generated pairing code
|
|
42
|
-
await startHttpTransport(config, handleRpc, port, code, () => {
|
|
43
|
-
console.log(`\n${bold("Palmier LAN Server")}\n`);
|
|
44
|
-
console.log(` ${cyan("Open the app at:")} ${bold(`http://${ip}:${port}`)}\n`);
|
|
45
|
-
console.log(` ${cyan("Pairing code:")} ${bold(code)}\n`);
|
|
46
|
-
console.log(` ${dim("Press Ctrl+C to stop.")}\n`);
|
|
47
|
-
});
|
|
48
|
-
}
|
package/src/commands/notify.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { StringCodec } from "nats";
|
|
2
|
-
import { loadConfig } from "../config.js";
|
|
3
|
-
import { connectNats } from "../nats-client.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Send a push notification to the user via NATS.
|
|
7
|
-
* Usage: palmier notify --title "Title" --body "Body text"
|
|
8
|
-
*/
|
|
9
|
-
export async function notifyCommand(opts: { title: string; body: string }): Promise<void> {
|
|
10
|
-
const config = loadConfig();
|
|
11
|
-
const nc = await connectNats(config);
|
|
12
|
-
|
|
13
|
-
if (!nc) {
|
|
14
|
-
console.error("Error: NATS connection required for push notifications.");
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const sc = StringCodec();
|
|
19
|
-
const payload = {
|
|
20
|
-
hostId: config.hostId,
|
|
21
|
-
title: opts.title,
|
|
22
|
-
body: opts.body,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const subject = `host.${config.hostId}.push.send`;
|
|
27
|
-
const reply = await nc.request(subject, sc.encode(JSON.stringify(payload)), {
|
|
28
|
-
timeout: 15_000,
|
|
29
|
-
});
|
|
30
|
-
const result = JSON.parse(sc.decode(reply.data)) as { ok?: boolean; error?: string };
|
|
31
|
-
|
|
32
|
-
if (result.ok) {
|
|
33
|
-
console.log("Push notification sent successfully.");
|
|
34
|
-
} else {
|
|
35
|
-
console.error(`Failed to send push notification: ${result.error}`);
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
} catch (err) {
|
|
39
|
-
console.error(`Error sending push notification: ${err}`);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
} finally {
|
|
42
|
-
await nc.drain();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { loadConfig } from "../config.js";
|
|
2
|
-
import { connectNats } from "../nats-client.js";
|
|
3
|
-
import { getTaskDir, parseTaskFile, appendRunMessage } from "../task.js";
|
|
4
|
-
import { requestUserInput, publishInputResolved } from "../user-input.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Request input from the user and print responses to stdout.
|
|
8
|
-
* Usage: palmier request-input --description "Question 1" --description "Question 2"
|
|
9
|
-
*
|
|
10
|
-
* Requires PALMIER_TASK_ID and PALMIER_RUN_DIR environment variables.
|
|
11
|
-
*/
|
|
12
|
-
export async function requestInputCommand(opts: { description: string[] }): Promise<void> {
|
|
13
|
-
const taskId = process.env.PALMIER_TASK_ID;
|
|
14
|
-
if (!taskId) {
|
|
15
|
-
console.error("Error: PALMIER_TASK_ID environment variable is not set.");
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const config = loadConfig();
|
|
20
|
-
const nc = await connectNats(config);
|
|
21
|
-
const taskDir = getTaskDir(config.projectRoot, taskId);
|
|
22
|
-
const task = parseTaskFile(taskDir);
|
|
23
|
-
const runId = process.env.PALMIER_RUN_DIR?.split(/[/\\]/).pop();
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const response = await requestUserInput(nc, config, taskId, task.frontmatter.name, taskDir, opts.description);
|
|
27
|
-
await publishInputResolved(nc, config, taskId, response === "aborted" ? "aborted" : "provided");
|
|
28
|
-
|
|
29
|
-
if (response === "aborted") {
|
|
30
|
-
if (runId) {
|
|
31
|
-
appendRunMessage(taskDir, runId, { role: "user", time: Date.now(), content: "Input request aborted.", type: "input" });
|
|
32
|
-
}
|
|
33
|
-
console.error("User aborted the input request.");
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (runId) {
|
|
38
|
-
const lines = opts.description.map((desc, i) => `**${desc}** ${response[i]}`);
|
|
39
|
-
appendRunMessage(taskDir, runId, { role: "user", time: Date.now(), content: lines.join("\n"), type: "input" });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
for (let i = 0; i < opts.description.length; i++) {
|
|
43
|
-
console.log(response[i]);
|
|
44
|
-
}
|
|
45
|
-
} catch (err) {
|
|
46
|
-
console.error(`Error requesting user input: ${err}`);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
} finally {
|
|
49
|
-
if (nc) await nc.drain();
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/lan-lock.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { CONFIG_DIR } from "./config.js";
|
|
4
|
-
|
|
5
|
-
export const LAN_LOCKFILE = path.join(CONFIG_DIR, "lan.json");
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Read the LAN lockfile to determine if `palmier lan` is running.
|
|
9
|
-
* Returns the port number, or null if not running.
|
|
10
|
-
*/
|
|
11
|
-
export function getLanPort(): number | null {
|
|
12
|
-
try {
|
|
13
|
-
const raw = fs.readFileSync(LAN_LOCKFILE, "utf-8");
|
|
14
|
-
return (JSON.parse(raw) as { port: number }).port;
|
|
15
|
-
} catch { return null; }
|
|
16
|
-
}
|
package/src/user-input.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { readTaskStatus, writeTaskStatus } from "./task.js";
|
|
4
|
-
import { publishHostEvent } from "./events.js";
|
|
5
|
-
import type { HostConfig } from "./types.js";
|
|
6
|
-
import type { NatsConnection } from "nats";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Watch status.json until user_input is populated by an RPC call, then resolve.
|
|
10
|
-
*/
|
|
11
|
-
export function waitForUserInput(taskDir: string): Promise<string[]> {
|
|
12
|
-
const statusPath = path.join(taskDir, "status.json");
|
|
13
|
-
return new Promise<string[]>((resolve) => {
|
|
14
|
-
const watcher = fs.watch(statusPath, () => {
|
|
15
|
-
const status = readTaskStatus(taskDir);
|
|
16
|
-
if (!status || !status.user_input?.length) return;
|
|
17
|
-
watcher.close();
|
|
18
|
-
resolve(status.user_input);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Send an input-request event and wait for the user's response.
|
|
25
|
-
*/
|
|
26
|
-
export async function requestUserInput(
|
|
27
|
-
nc: NatsConnection | undefined,
|
|
28
|
-
config: HostConfig,
|
|
29
|
-
taskId: string,
|
|
30
|
-
taskName: string,
|
|
31
|
-
taskDir: string,
|
|
32
|
-
inputDescriptions: string[],
|
|
33
|
-
): Promise<string[] | "aborted"> {
|
|
34
|
-
const currentStatus = readTaskStatus(taskDir)!;
|
|
35
|
-
writeTaskStatus(taskDir, { ...currentStatus, pending_input: inputDescriptions });
|
|
36
|
-
|
|
37
|
-
await publishHostEvent(nc, config.hostId, taskId, {
|
|
38
|
-
event_type: "input-request",
|
|
39
|
-
host_id: config.hostId,
|
|
40
|
-
input_descriptions: inputDescriptions,
|
|
41
|
-
name: taskName,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const userInput = await waitForUserInput(taskDir);
|
|
45
|
-
if (userInput.length === 1 && userInput[0] === "aborted") {
|
|
46
|
-
writeTaskStatus(taskDir, { running_state: "aborted", time_stamp: Date.now() });
|
|
47
|
-
return "aborted";
|
|
48
|
-
}
|
|
49
|
-
writeTaskStatus(taskDir, { running_state: "started", time_stamp: Date.now() });
|
|
50
|
-
return userInput;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Notify clients that an input request has been resolved.
|
|
55
|
-
*/
|
|
56
|
-
export async function publishInputResolved(
|
|
57
|
-
nc: NatsConnection | undefined,
|
|
58
|
-
config: HostConfig,
|
|
59
|
-
taskId: string,
|
|
60
|
-
status: "provided" | "aborted",
|
|
61
|
-
): Promise<void> {
|
|
62
|
-
await publishHostEvent(nc, config.hostId, taskId, {
|
|
63
|
-
event_type: "input-resolved",
|
|
64
|
-
host_id: config.hostId,
|
|
65
|
-
status,
|
|
66
|
-
});
|
|
67
|
-
}
|