palmier 0.2.0 → 0.2.2
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/CLAUDE.md +5 -1
- package/README.md +135 -45
- package/dist/agents/agent.d.ts +26 -0
- package/dist/agents/agent.js +32 -0
- package/dist/agents/claude.d.ts +8 -0
- package/dist/agents/claude.js +35 -0
- package/dist/agents/codex.d.ts +8 -0
- package/dist/agents/codex.js +41 -0
- package/dist/agents/gemini.d.ts +8 -0
- package/dist/agents/gemini.js +39 -0
- package/dist/agents/openclaw.d.ts +8 -0
- package/dist/agents/openclaw.js +25 -0
- package/dist/agents/shared-prompt.d.ts +11 -0
- package/dist/agents/shared-prompt.js +26 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +19 -0
- package/dist/commands/info.d.ts +5 -0
- package/dist/commands/info.js +40 -0
- package/dist/commands/init.d.ts +7 -2
- package/dist/commands/init.js +139 -49
- package/dist/commands/mcpserver.d.ts +2 -0
- package/dist/commands/mcpserver.js +75 -0
- package/dist/commands/pair.d.ts +6 -0
- package/dist/commands/pair.js +140 -0
- package/dist/commands/plan-generation.md +32 -0
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +258 -114
- package/dist/commands/serve.d.ts +1 -1
- package/dist/commands/serve.js +16 -228
- package/dist/commands/sessions.d.ts +4 -0
- package/dist/commands/sessions.js +30 -0
- package/dist/commands/task-generation.md +1 -1
- package/dist/config.d.ts +5 -5
- package/dist/config.js +24 -6
- package/dist/index.js +58 -5
- package/dist/nats-client.d.ts +3 -3
- package/dist/nats-client.js +2 -2
- package/dist/rpc-handler.d.ts +6 -0
- package/dist/rpc-handler.js +367 -0
- package/dist/session-store.d.ts +12 -0
- package/dist/session-store.js +57 -0
- package/dist/spawn-command.d.ts +26 -0
- package/dist/spawn-command.js +48 -0
- package/dist/systemd.d.ts +2 -2
- package/dist/task.d.ts +45 -2
- package/dist/task.js +155 -14
- package/dist/transports/http-transport.d.ts +6 -0
- package/dist/transports/http-transport.js +243 -0
- package/dist/transports/nats-transport.d.ts +6 -0
- package/dist/transports/nats-transport.js +69 -0
- package/dist/types.d.ts +30 -13
- package/package.json +4 -3
- package/src/agents/agent.ts +62 -0
- package/src/agents/claude.ts +39 -0
- package/src/agents/codex.ts +46 -0
- package/src/agents/gemini.ts +43 -0
- package/src/agents/openclaw.ts +29 -0
- package/src/agents/shared-prompt.ts +26 -0
- package/src/commands/agents.ts +20 -0
- package/src/commands/info.ts +44 -0
- package/src/commands/init.ts +229 -121
- package/src/commands/mcpserver.ts +92 -0
- package/src/commands/pair.ts +163 -0
- package/src/commands/plan-generation.md +32 -0
- package/src/commands/run.ts +323 -129
- package/src/commands/serve.ts +26 -287
- package/src/commands/sessions.ts +32 -0
- package/src/config.ts +30 -10
- package/src/index.ts +67 -6
- package/src/nats-client.ts +4 -4
- package/src/rpc-handler.ts +421 -0
- package/src/session-store.ts +68 -0
- package/src/spawn-command.ts +78 -0
- package/src/systemd.ts +2 -2
- package/src/task.ts +166 -16
- package/src/transports/http-transport.ts +290 -0
- package/src/transports/nats-transport.ts +82 -0
- package/src/types.ts +36 -13
- package/src/commands/task-generation.md +0 -28
package/dist/commands/run.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { spawnCommand } from "../spawn-command.js";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
5
|
import { connectNats } from "../nats-client.js";
|
|
6
|
-
import { parseTaskFile, getTaskDir } from "../task.js";
|
|
6
|
+
import { parseTaskFile, getTaskDir, writeTaskFile, writeTaskStatus, readTaskStatus, appendHistory } from "../task.js";
|
|
7
|
+
import { getAgent } from "../agents/agent.js";
|
|
8
|
+
import { TASK_SUCCESS_MARKER, TASK_FAILURE_MARKER, TASK_REPORT_PREFIX, TASK_PERMISSION_PREFIX } from "../agents/shared-prompt.js";
|
|
7
9
|
import { StringCodec } from "nats";
|
|
10
|
+
/**
|
|
11
|
+
* Write a time-stamped RESULT file with frontmatter.
|
|
12
|
+
* Always generated, even for abort/fail.
|
|
13
|
+
*/
|
|
14
|
+
function writeResult(taskDir, taskName, taskSnapshotName, runningState, startTime, endTime, output, reportFiles, requiredPermissions) {
|
|
15
|
+
const resultFileName = `RESULT-${endTime}.md`;
|
|
16
|
+
const reportLine = reportFiles.length > 0 ? `\nreport_files: ${reportFiles.join(", ")}` : "";
|
|
17
|
+
const permLines = requiredPermissions.map((p) => `\nrequired_permission: ${p.name} | ${p.description}`).join("");
|
|
18
|
+
const content = `---\ntask_name: ${taskName}\nrunning_state: ${runningState}\nstart_time: ${startTime}\nend_time: ${endTime}\ntask_file: ${taskSnapshotName}${reportLine}${permLines}\n---\n${output}`;
|
|
19
|
+
fs.writeFileSync(path.join(taskDir, resultFileName), content, "utf-8");
|
|
20
|
+
return resultFileName;
|
|
21
|
+
}
|
|
8
22
|
/**
|
|
9
23
|
* Execute a task by ID.
|
|
10
24
|
*/
|
|
@@ -12,17 +26,17 @@ export async function runCommand(taskId) {
|
|
|
12
26
|
const config = loadConfig();
|
|
13
27
|
const taskDir = getTaskDir(config.projectRoot, taskId);
|
|
14
28
|
const task = parseTaskFile(taskDir);
|
|
15
|
-
|
|
29
|
+
const mode = config.mode ?? "nats";
|
|
30
|
+
const useNats = mode === "nats" || mode === "auto";
|
|
31
|
+
const useHttp = mode === "lan" || mode === "auto";
|
|
32
|
+
console.log(`Running task: ${taskId} (mode: ${mode})`);
|
|
16
33
|
let nc;
|
|
17
|
-
|
|
18
|
-
const
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
const taskName = task.frontmatter.name;
|
|
36
|
+
// Snapshot the task file at run time
|
|
37
|
+
const taskSnapshotName = `TASK-${startTime}.md`;
|
|
38
|
+
fs.copyFileSync(path.join(taskDir, "TASK.md"), path.join(taskDir, taskSnapshotName));
|
|
19
39
|
const cleanup = async () => {
|
|
20
|
-
if (confirmKv) {
|
|
21
|
-
try {
|
|
22
|
-
await confirmKv.delete(confirmKey);
|
|
23
|
-
}
|
|
24
|
-
catch { /* may not exist */ }
|
|
25
|
-
}
|
|
26
40
|
if (nc && !nc.isClosed()) {
|
|
27
41
|
await nc.drain();
|
|
28
42
|
}
|
|
@@ -30,51 +44,104 @@ export async function runCommand(taskId) {
|
|
|
30
44
|
// Handle signals
|
|
31
45
|
const onSignal = async () => {
|
|
32
46
|
console.log("Received signal, cleaning up...");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
47
|
+
const endTime = Date.now();
|
|
48
|
+
const resultFileName = writeResult(taskDir, taskName, taskSnapshotName, "abort", startTime, endTime, "", [], []);
|
|
49
|
+
appendHistory(config.projectRoot, { task_id: taskId, result_file: resultFileName });
|
|
50
|
+
await publishTaskEvent(nc, config, taskDir, taskId, "abort", useHttp, taskName);
|
|
36
51
|
await cleanup();
|
|
37
52
|
process.exit(1);
|
|
38
53
|
};
|
|
39
54
|
process.on("SIGINT", onSignal);
|
|
40
55
|
process.on("SIGTERM", onSignal);
|
|
41
|
-
let eventKv;
|
|
42
56
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// If requires_confirmation, ask user via NATS KV
|
|
57
|
+
if (useNats) {
|
|
58
|
+
nc = await connectNats(config);
|
|
59
|
+
}
|
|
60
|
+
// Mark as started immediately
|
|
61
|
+
await publishTaskEvent(nc, config, taskDir, taskId, "start", useHttp, taskName);
|
|
62
|
+
// If requires_confirmation, notify clients and wait
|
|
50
63
|
if (task.frontmatter.requires_confirmation) {
|
|
51
|
-
|
|
52
|
-
const
|
|
64
|
+
const confirmed = await requestConfirmation(nc, config, task, taskDir, useHttp);
|
|
65
|
+
const resolvedStatus = confirmed ? "confirmed" : "aborted";
|
|
66
|
+
await publishConfirmResolved(nc, config, taskId, resolvedStatus, useHttp);
|
|
53
67
|
if (!confirmed) {
|
|
54
68
|
console.log("Task aborted by user.");
|
|
55
|
-
|
|
69
|
+
const endTime = Date.now();
|
|
70
|
+
const resultFileName = writeResult(taskDir, taskName, taskSnapshotName, "abort", startTime, endTime, "", [], []);
|
|
71
|
+
appendHistory(config.projectRoot, { task_id: taskId, result_file: resultFileName });
|
|
72
|
+
await publishTaskEvent(nc, config, taskDir, taskId, "abort", useHttp, taskName);
|
|
56
73
|
await cleanup();
|
|
57
74
|
return;
|
|
58
75
|
}
|
|
59
76
|
console.log("Task confirmed by user.");
|
|
60
77
|
}
|
|
61
|
-
//
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
// Execution loop: retry on permission failure if user grants
|
|
79
|
+
const guiEnv = getGuiEnv();
|
|
80
|
+
const agent = getAgent(task.frontmatter.agent);
|
|
81
|
+
let lastOutput = "";
|
|
82
|
+
let lastOutcome = "fail";
|
|
83
|
+
let lastReportFiles = [];
|
|
84
|
+
let lastRequiredPermissions = [];
|
|
85
|
+
let lastEndTime = Date.now();
|
|
86
|
+
let retryPrompt;
|
|
87
|
+
let transientPermissions = [];
|
|
88
|
+
// eslint-disable-next-line no-constant-condition
|
|
89
|
+
while (true) {
|
|
90
|
+
const { command, args } = agent.getTaskRunCommandLine(task, retryPrompt, transientPermissions);
|
|
91
|
+
const result = await spawnCommand(command, args, {
|
|
92
|
+
cwd: taskDir,
|
|
93
|
+
env: {
|
|
94
|
+
...guiEnv,
|
|
95
|
+
PALMIER_TASK_ID: task.frontmatter.id,
|
|
96
|
+
},
|
|
97
|
+
echoStdout: true,
|
|
98
|
+
forwardSignals: true,
|
|
99
|
+
resolveOnFailure: true,
|
|
100
|
+
});
|
|
101
|
+
lastOutput = result.output;
|
|
102
|
+
lastEndTime = Date.now();
|
|
103
|
+
lastOutcome = result.exitCode !== 0 ? "fail" : parseTaskOutcome(lastOutput);
|
|
104
|
+
lastReportFiles = parseReportFiles(lastOutput);
|
|
105
|
+
lastRequiredPermissions = parsePermissions(lastOutput);
|
|
106
|
+
// If failed with permission requirements, ask user to grant
|
|
107
|
+
if (lastOutcome === "fail" && lastRequiredPermissions.length > 0) {
|
|
108
|
+
const response = await requestPermission(nc, config, task, taskDir, lastRequiredPermissions, useHttp);
|
|
109
|
+
await publishPermissionResolved(nc, config, taskId, response, useHttp);
|
|
110
|
+
if (response === "aborted") {
|
|
111
|
+
console.log("Permission request aborted by user.");
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
const newPerms = lastRequiredPermissions.filter((rp) => !task.frontmatter.permissions?.some((ep) => ep.name === rp.name)
|
|
115
|
+
&& !transientPermissions.some((ep) => ep.name === rp.name));
|
|
116
|
+
if (response === "granted_all") {
|
|
117
|
+
// Persist permissions to task frontmatter for all future runs
|
|
118
|
+
task.frontmatter.permissions = [...(task.frontmatter.permissions ?? []), ...newPerms];
|
|
119
|
+
writeTaskFile(taskDir, task);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// "granted" — allow for this run only
|
|
123
|
+
transientPermissions = [...transientPermissions, ...newPerms];
|
|
124
|
+
}
|
|
125
|
+
console.log(`Permissions granted, retrying task ${taskId}...`);
|
|
126
|
+
retryPrompt = "Permissions granted, please continue.";
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Normal completion (success or non-permission failure)
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
// Write result and history once after the loop
|
|
133
|
+
const resultFileName = writeResult(taskDir, taskName, taskSnapshotName, lastOutcome, startTime, lastEndTime, lastOutput, lastReportFiles, lastRequiredPermissions);
|
|
134
|
+
appendHistory(config.projectRoot, { task_id: taskId, result_file: resultFileName });
|
|
135
|
+
await publishTaskEvent(nc, config, taskDir, taskId, lastOutcome, useHttp, taskName);
|
|
71
136
|
console.log(`Task ${taskId} completed.`);
|
|
72
137
|
}
|
|
73
138
|
catch (err) {
|
|
74
139
|
console.error(`Task ${taskId} failed:`, err);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
140
|
+
const endTime = Date.now();
|
|
141
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
142
|
+
const resultFileName = writeResult(taskDir, taskName, taskSnapshotName, "fail", startTime, endTime, errorMsg, [], []);
|
|
143
|
+
appendHistory(config.projectRoot, { task_id: taskId, result_file: resultFileName });
|
|
144
|
+
await publishTaskEvent(nc, config, taskDir, taskId, "fail", useHttp, taskName);
|
|
78
145
|
process.exitCode = 1;
|
|
79
146
|
}
|
|
80
147
|
finally {
|
|
@@ -82,90 +149,167 @@ export async function runCommand(taskId) {
|
|
|
82
149
|
}
|
|
83
150
|
}
|
|
84
151
|
const sc = StringCodec();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
task_id: task.frontmatter.id,
|
|
96
|
-
agent_id: config.agentId,
|
|
97
|
-
user_id: config.userId,
|
|
98
|
-
details: {
|
|
99
|
-
prompt: task.frontmatter.user_prompt,
|
|
100
|
-
},
|
|
101
|
-
status: "pending",
|
|
102
|
-
};
|
|
103
|
-
await kv.put(kvKey, sc.encode(JSON.stringify(payload)));
|
|
104
|
-
// Watch AFTER writing — the initial history replay delivers the "pending" entry (skipped),
|
|
105
|
-
// then the iterator stays open for live updates (confirmed/aborted).
|
|
106
|
-
const watch = await kv.watch({ key: kvKey });
|
|
107
|
-
for await (const entry of watch) {
|
|
108
|
-
if (entry.operation === "DEL" || entry.operation === "PURGE") {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
152
|
+
/**
|
|
153
|
+
* Write status.json and notify connected clients via NATS and/or HTTP SSE.
|
|
154
|
+
*/
|
|
155
|
+
async function publishHostEvent(nc, config, taskId, payload, useHttp) {
|
|
156
|
+
const subject = `host-event.${config.hostId}.${taskId}`;
|
|
157
|
+
if (nc) {
|
|
158
|
+
nc.publish(subject, sc.encode(JSON.stringify(payload)));
|
|
159
|
+
console.log(`[nats] ${subject} →`, payload);
|
|
160
|
+
}
|
|
161
|
+
if (useHttp && config.directPort) {
|
|
111
162
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
163
|
+
await fetch(`http://localhost:${config.directPort}/internal/event`, {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: { "Content-Type": "application/json" },
|
|
166
|
+
body: JSON.stringify({ task_id: taskId, ...payload }),
|
|
167
|
+
});
|
|
168
|
+
console.log(`[http] host-event: ${taskId} →`, payload);
|
|
118
169
|
}
|
|
119
|
-
catch {
|
|
120
|
-
|
|
170
|
+
catch (err) {
|
|
171
|
+
console.error(`[http] Failed to push event:`, err);
|
|
121
172
|
}
|
|
122
173
|
}
|
|
123
|
-
return false;
|
|
124
174
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
175
|
+
async function publishTaskEvent(nc, config, taskDir, taskId, eventType, useHttp, taskName) {
|
|
176
|
+
writeTaskStatus(taskDir, {
|
|
177
|
+
running_state: eventType,
|
|
178
|
+
time_stamp: Date.now(),
|
|
179
|
+
});
|
|
180
|
+
const payload = { event_type: "running-state", running_state: eventType };
|
|
181
|
+
if (taskName)
|
|
182
|
+
payload.name = taskName;
|
|
183
|
+
await publishHostEvent(nc, config, taskId, payload, useHttp);
|
|
127
184
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
reject(new Error(`Task process exited with code ${exitCode}`));
|
|
164
|
-
}
|
|
185
|
+
/**
|
|
186
|
+
* Notify clients that a confirmation request has been resolved.
|
|
187
|
+
*/
|
|
188
|
+
async function publishConfirmResolved(nc, config, taskId, status, useHttp) {
|
|
189
|
+
await publishHostEvent(nc, config, taskId, {
|
|
190
|
+
event_type: "confirm-resolved",
|
|
191
|
+
host_id: config.hostId,
|
|
192
|
+
status,
|
|
193
|
+
}, useHttp);
|
|
194
|
+
}
|
|
195
|
+
async function requestPermission(nc, config, task, taskDir, requiredPermissions, useHttp) {
|
|
196
|
+
const taskId = task.frontmatter.id;
|
|
197
|
+
const statusPath = path.join(taskDir, "status.json");
|
|
198
|
+
const currentStatus = readTaskStatus(taskDir);
|
|
199
|
+
writeTaskStatus(taskDir, { ...currentStatus, pending_permission: requiredPermissions });
|
|
200
|
+
await publishHostEvent(nc, config, taskId, {
|
|
201
|
+
event_type: "permission-request",
|
|
202
|
+
host_id: config.hostId,
|
|
203
|
+
required_permissions: requiredPermissions,
|
|
204
|
+
name: task.frontmatter.name,
|
|
205
|
+
}, useHttp);
|
|
206
|
+
return new Promise((resolve) => {
|
|
207
|
+
const watcher = fs.watch(statusPath, () => {
|
|
208
|
+
const status = readTaskStatus(taskDir);
|
|
209
|
+
if (!status || status.user_input === undefined)
|
|
210
|
+
return;
|
|
211
|
+
watcher.close();
|
|
212
|
+
const response = status.user_input;
|
|
213
|
+
writeTaskStatus(taskDir, {
|
|
214
|
+
running_state: response === "aborted" ? "abort" : "start",
|
|
215
|
+
time_stamp: Date.now(),
|
|
216
|
+
});
|
|
217
|
+
resolve(response);
|
|
165
218
|
});
|
|
166
|
-
|
|
167
|
-
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async function publishPermissionResolved(nc, config, taskId, status, useHttp) {
|
|
222
|
+
await publishHostEvent(nc, config, taskId, {
|
|
223
|
+
event_type: "permission-resolved",
|
|
224
|
+
host_id: config.hostId,
|
|
225
|
+
status,
|
|
226
|
+
}, useHttp);
|
|
227
|
+
}
|
|
228
|
+
async function requestConfirmation(nc, config, task, taskDir, useHttp) {
|
|
229
|
+
const taskId = task.frontmatter.id;
|
|
230
|
+
const statusPath = path.join(taskDir, "status.json");
|
|
231
|
+
// Flag that we're awaiting user confirmation
|
|
232
|
+
const currentStatus = readTaskStatus(taskDir);
|
|
233
|
+
writeTaskStatus(taskDir, { ...currentStatus, pending_confirmation: true });
|
|
234
|
+
// Publish confirmation request via NATS and/or HTTP SSE
|
|
235
|
+
await publishHostEvent(nc, config, taskId, {
|
|
236
|
+
event_type: "confirm-request",
|
|
237
|
+
host_id: config.hostId,
|
|
238
|
+
}, useHttp);
|
|
239
|
+
// Wait for task.user_input RPC to set user_input in status.json
|
|
240
|
+
return new Promise((resolve) => {
|
|
241
|
+
const watcher = fs.watch(statusPath, () => {
|
|
242
|
+
const status = readTaskStatus(taskDir);
|
|
243
|
+
if (!status || status.user_input === undefined)
|
|
244
|
+
return; // still pending
|
|
245
|
+
watcher.close();
|
|
246
|
+
const confirmed = status.user_input === "confirmed";
|
|
247
|
+
// Clear pending_confirmation/user_input and update running_state
|
|
248
|
+
writeTaskStatus(taskDir, {
|
|
249
|
+
running_state: confirmed ? "start" : "abort",
|
|
250
|
+
time_stamp: Date.now(),
|
|
251
|
+
});
|
|
252
|
+
resolve(confirmed);
|
|
168
253
|
});
|
|
169
254
|
});
|
|
170
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Extract report file names from agent output.
|
|
258
|
+
* Looks for lines matching: [PALMIER_REPORT] <filename>
|
|
259
|
+
*/
|
|
260
|
+
function parseReportFiles(output) {
|
|
261
|
+
const regex = new RegExp(`^\\${TASK_REPORT_PREFIX}\\s+(.+)$`, "gm");
|
|
262
|
+
const files = [];
|
|
263
|
+
let match;
|
|
264
|
+
while ((match = regex.exec(output)) !== null) {
|
|
265
|
+
const name = match[1].trim();
|
|
266
|
+
if (name)
|
|
267
|
+
files.push(name);
|
|
268
|
+
}
|
|
269
|
+
return files;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Extract required permissions from agent output.
|
|
273
|
+
* Looks for lines matching: [PALMIER_PERMISSION] <tool> | <description>
|
|
274
|
+
*/
|
|
275
|
+
function parsePermissions(output) {
|
|
276
|
+
const regex = new RegExp(`^\\${TASK_PERMISSION_PREFIX}\\s+(.+)$`, "gm");
|
|
277
|
+
const perms = [];
|
|
278
|
+
let match;
|
|
279
|
+
while ((match = regex.exec(output)) !== null) {
|
|
280
|
+
const raw = match[1].trim();
|
|
281
|
+
const sep = raw.indexOf("|");
|
|
282
|
+
if (sep !== -1) {
|
|
283
|
+
perms.push({ name: raw.slice(0, sep).trim(), description: raw.slice(sep + 1).trim() });
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
perms.push({ name: raw, description: "" });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return perms;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Parse the agent's output for success/failure markers.
|
|
293
|
+
* Falls back to "finish" if no marker is found.
|
|
294
|
+
*/
|
|
295
|
+
function parseTaskOutcome(output) {
|
|
296
|
+
const lastChunk = output.slice(-500);
|
|
297
|
+
if (lastChunk.includes(TASK_FAILURE_MARKER))
|
|
298
|
+
return "fail";
|
|
299
|
+
if (lastChunk.includes(TASK_SUCCESS_MARKER))
|
|
300
|
+
return "finish";
|
|
301
|
+
return "finish";
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Return env vars for the default physical GUI session (:0).
|
|
305
|
+
*/
|
|
306
|
+
function getGuiEnv() {
|
|
307
|
+
const uid = process.getuid?.();
|
|
308
|
+
const runtimeDir = process.env.XDG_RUNTIME_DIR ||
|
|
309
|
+
(uid !== undefined ? `/run/user/${uid}` : "");
|
|
310
|
+
return {
|
|
311
|
+
DISPLAY: ":0",
|
|
312
|
+
...(runtimeDir ? { XDG_RUNTIME_DIR: runtimeDir } : {}),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
171
315
|
//# sourceMappingURL=run.js.map
|
package/dist/commands/serve.d.ts
CHANGED