palmier 0.9.8 → 0.9.10
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/dist/agents/agent-instructions.md +5 -3
- package/dist/commands/run.js +40 -14
- package/dist/platform/windows.js +55 -1
- package/dist/pwa/assets/{index-DWvRAUiy.js → index-Bb-_PDBL.js} +2 -2
- package/dist/pwa/assets/{index-D1bIhEbd.css → index-C0lQMYcG.css} +1 -1
- package/dist/pwa/assets/{web-DL4uXOpS.js → web-BxTN9Su1.js} +1 -1
- package/dist/pwa/assets/{web-C4iZbqTC.js → web-CSpTDg3Y.js} +1 -1
- package/dist/pwa/assets/{web-CBFqJGX6.js → web-DgnrddCL.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/rpc-handler.js +32 -6
- package/dist/spawn-command.d.ts +4 -0
- package/dist/spawn-command.js +6 -4
- package/dist/task.d.ts +1 -1
- package/dist/task.js +8 -4
- package/dist/types.d.ts +3 -0
- package/package.json +1 -1
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
You are an AI agent executing a task on behalf of the user. Follow these instructions carefully.
|
|
2
2
|
|
|
3
|
+
All `[PALMIER_*]` markers below are control signals parsed by the host. They MUST be written to **stdout** (not stderr). Markers on stderr are ignored.
|
|
4
|
+
|
|
3
5
|
## Reporting Output
|
|
4
6
|
|
|
5
|
-
If you generate report or output files, print each file path on its own line using this exact format:
|
|
7
|
+
If you generate report or output files, print each file path on its own line to stdout using this exact format:
|
|
6
8
|
[PALMIER_REPORT] <filename>
|
|
7
9
|
|
|
8
10
|
## Completion
|
|
9
11
|
|
|
10
|
-
When you are done, output exactly one of these markers as the very last line (no other text on the same line):
|
|
12
|
+
When you are done, output exactly one of these markers as the very last line on stdout (no other text on the same line):
|
|
11
13
|
[PALMIER_TASK_SUCCESS]
|
|
12
14
|
[PALMIER_TASK_FAILURE]
|
|
13
15
|
|
|
14
16
|
## Permissions
|
|
15
17
|
|
|
16
|
-
Whenever a tool you are trying to use is denied or you lack the required permissions, print each required permission on its own line using this exact format:
|
|
18
|
+
Whenever a tool you are trying to use is denied or you lack the required permissions, print each required permission on its own line to stdout using this exact format:
|
|
17
19
|
[PALMIER_PERMISSION] <tool_name> | <description>
|
|
18
20
|
|
|
19
21
|
## HTTP Endpoints
|
package/dist/commands/run.js
CHANGED
|
@@ -30,8 +30,9 @@ async function sendPushNotification(nc, hostId, title, body) {
|
|
|
30
30
|
async function invokeAgentWithRetries(ctx, invokeTask) {
|
|
31
31
|
// eslint-disable-next-line no-constant-condition
|
|
32
32
|
while (true) {
|
|
33
|
-
|
|
34
|
-
let
|
|
33
|
+
let writer = beginStreamingMessage(ctx.taskDir, ctx.runId, Date.now(), "stdout");
|
|
34
|
+
let activeStream = "stdout";
|
|
35
|
+
const lineBufs = { stdout: "", stderr: "" };
|
|
35
36
|
let notifyPending = false;
|
|
36
37
|
let notifyTimer;
|
|
37
38
|
function throttledNotify() {
|
|
@@ -43,6 +44,21 @@ async function invokeAgentWithRetries(ctx, invokeTask) {
|
|
|
43
44
|
publishHostEvent(ctx.nc, ctx.config.hostId, ctx.taskId, { event_type: "result-updated", run_id: ctx.runId });
|
|
44
45
|
}, 500);
|
|
45
46
|
}
|
|
47
|
+
function emit(stream, chunk) {
|
|
48
|
+
lineBufs[stream] += chunk;
|
|
49
|
+
const lines = lineBufs[stream].split("\n");
|
|
50
|
+
lineBufs[stream] = lines.pop() ?? "";
|
|
51
|
+
const filtered = lines.filter((l) => !l.startsWith("[PALMIER"));
|
|
52
|
+
if (filtered.length === 0)
|
|
53
|
+
return;
|
|
54
|
+
if (stream !== activeStream) {
|
|
55
|
+
writer.end();
|
|
56
|
+
writer = beginStreamingMessage(ctx.taskDir, ctx.runId, Date.now(), stream);
|
|
57
|
+
activeStream = stream;
|
|
58
|
+
}
|
|
59
|
+
writer.write(filtered.join("\n") + "\n");
|
|
60
|
+
throttledNotify();
|
|
61
|
+
}
|
|
46
62
|
const { command, args, stdin, env: agentEnv } = ctx.agent.getTaskRunCommandLine(invokeTask, undefined, ctx.task.frontmatter.yolo_mode ? "yolo" : ctx.transientPermissions);
|
|
47
63
|
const result = await spawnCommand(command, args, {
|
|
48
64
|
cwd: getRunDir(ctx.taskDir, ctx.runId),
|
|
@@ -50,29 +66,39 @@ async function invokeAgentWithRetries(ctx, invokeTask) {
|
|
|
50
66
|
echoStdout: true,
|
|
51
67
|
resolveOnFailure: true,
|
|
52
68
|
stdin,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const lines = lineBuf.split("\n");
|
|
56
|
-
lineBuf = lines.pop() ?? "";
|
|
57
|
-
const filtered = lines.filter((l) => !l.startsWith("[PALMIER"));
|
|
58
|
-
if (filtered.length > 0) {
|
|
59
|
-
writer.write(filtered.join("\n") + "\n");
|
|
60
|
-
throttledNotify();
|
|
61
|
-
}
|
|
62
|
-
},
|
|
69
|
+
onStdout: (chunk) => emit("stdout", chunk),
|
|
70
|
+
onStderr: (chunk) => emit("stderr", chunk),
|
|
63
71
|
});
|
|
64
72
|
if (notifyTimer)
|
|
65
73
|
clearTimeout(notifyTimer);
|
|
66
74
|
const outcome = result.exitCode !== 0 ? "failed" : parseTaskOutcome(result.output);
|
|
67
75
|
const reportFiles = parseReportFiles(result.output);
|
|
68
76
|
const requiredPermissions = parsePermissions(result.output);
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
for (const stream of ["stdout", "stderr"]) {
|
|
78
|
+
const trailing = lineBufs[stream];
|
|
79
|
+
if (trailing && !trailing.startsWith("[PALMIER")) {
|
|
80
|
+
if (stream !== activeStream) {
|
|
81
|
+
writer.end();
|
|
82
|
+
writer = beginStreamingMessage(ctx.taskDir, ctx.runId, Date.now(), stream);
|
|
83
|
+
activeStream = stream;
|
|
84
|
+
}
|
|
85
|
+
writer.write(trailing);
|
|
86
|
+
}
|
|
71
87
|
}
|
|
72
88
|
if (requiredPermissions.length > 0) {
|
|
89
|
+
if (activeStream !== "stdout") {
|
|
90
|
+
writer.end();
|
|
91
|
+
writer = beginStreamingMessage(ctx.taskDir, ctx.runId, Date.now(), "stdout");
|
|
92
|
+
activeStream = "stdout";
|
|
93
|
+
}
|
|
73
94
|
const permLines = requiredPermissions.map((p) => `- **${p.name}** ${p.description}`).join("\n");
|
|
74
95
|
writer.write(`\n\n**Permissions requested:**\n${permLines}\n`);
|
|
75
96
|
}
|
|
97
|
+
if (reportFiles.length > 0 && activeStream !== "stdout") {
|
|
98
|
+
writer.end();
|
|
99
|
+
writer = beginStreamingMessage(ctx.taskDir, ctx.runId, Date.now(), "stdout");
|
|
100
|
+
activeStream = "stdout";
|
|
101
|
+
}
|
|
76
102
|
writer.end(reportFiles.length > 0 ? reportFiles : undefined);
|
|
77
103
|
await publishHostEvent(ctx.nc, ctx.config.hostId, ctx.taskId, { event_type: "result-updated", run_id: ctx.runId });
|
|
78
104
|
if (reportFiles.length > 0) {
|
package/dist/platform/windows.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "fs";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { execFileSync } from "child_process";
|
|
4
4
|
import { CONFIG_DIR, loadConfig } from "../config.js";
|
|
5
|
-
import { getTaskDir, readTaskStatus } from "../task.js";
|
|
5
|
+
import { getTaskDir, readTaskStatus, parseTaskFile } from "../task.js";
|
|
6
6
|
const TASK_PREFIX = "\\Palmier\\PalmierTask-";
|
|
7
7
|
const DAEMON_TASK_NAME = "PalmierDaemon";
|
|
8
8
|
const DOW_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
|
@@ -72,6 +72,52 @@ export function buildTaskXml(tr, triggers, foreground) {
|
|
|
72
72
|
function schtasksTaskName(taskId) {
|
|
73
73
|
return `${TASK_PREFIX}${taskId}`;
|
|
74
74
|
}
|
|
75
|
+
function querySchtasksStatus(tn) {
|
|
76
|
+
try {
|
|
77
|
+
const out = execFileSync("schtasks", ["/query", "/tn", tn, "/v", "/fo", "LIST"], {
|
|
78
|
+
encoding: "utf-8", windowsHide: true, stdio: ["ignore", "pipe", "pipe"],
|
|
79
|
+
});
|
|
80
|
+
const status = out.match(/^\s*Status:\s*(.+?)\s*$/im)?.[1] ?? "";
|
|
81
|
+
const lastResult = out.match(/^\s*Last Result:\s*(.+?)\s*$/im)?.[1] ?? "";
|
|
82
|
+
return { status, lastResult };
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Map common Last Result HRESULTs to a human-readable cause. */
|
|
89
|
+
function explainLastResult(lastResult, foreground) {
|
|
90
|
+
const code = lastResult.trim();
|
|
91
|
+
// Decimal forms emitted by schtasks: 267011 = 0x41303 SCHED_S_TASK_HAS_NOT_RUN.
|
|
92
|
+
if (code === "267011" || /0x0*41303/i.test(code)) {
|
|
93
|
+
return foreground
|
|
94
|
+
? "Foreground mode requires an active Windows session, but no user is logged in. Sign in to Windows and try again, or disable foreground mode for this task."
|
|
95
|
+
: "Task Scheduler reported the task did not run. Check that the daemon has permission to launch it.";
|
|
96
|
+
}
|
|
97
|
+
if (code === "0" || code === "0x0")
|
|
98
|
+
return undefined;
|
|
99
|
+
return `Task Scheduler reported Last Result=${code}.`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 2s after `schtasks /run`, confirm the action actually launched. Some failure
|
|
103
|
+
* modes — most notably foreground tasks with no interactive session — make
|
|
104
|
+
* /run return success while the action is silently skipped (Status stays
|
|
105
|
+
* "Ready", Last Result stays at 0x41303). If the run process has already
|
|
106
|
+
* written status.json by then, it clearly launched; skip the Scheduler query.
|
|
107
|
+
*/
|
|
108
|
+
async function verifyTaskLaunched(tn, taskDir, startTime, foreground) {
|
|
109
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
110
|
+
const status = readTaskStatus(taskDir);
|
|
111
|
+
if (status && status.time_stamp >= startTime)
|
|
112
|
+
return;
|
|
113
|
+
const last = querySchtasksStatus(tn);
|
|
114
|
+
if (last && /running/i.test(last.status))
|
|
115
|
+
return;
|
|
116
|
+
const explained = explainLastResult(last?.lastResult ?? "", foreground);
|
|
117
|
+
if (explained)
|
|
118
|
+
throw new Error(explained);
|
|
119
|
+
throw new Error(`Task Scheduler did not launch the task within 2s (status=${last?.status || "unknown"}, last_result=${last?.lastResult || "unknown"}).`);
|
|
120
|
+
}
|
|
75
121
|
export class WindowsPlatform {
|
|
76
122
|
installDaemon(config) {
|
|
77
123
|
const script = process.argv[1] || "palmier";
|
|
@@ -218,6 +264,13 @@ export class WindowsPlatform {
|
|
|
218
264
|
}
|
|
219
265
|
async startTask(taskId) {
|
|
220
266
|
const tn = schtasksTaskName(taskId);
|
|
267
|
+
const taskDir = getTaskDir(loadConfig().projectRoot, taskId);
|
|
268
|
+
let foreground = false;
|
|
269
|
+
try {
|
|
270
|
+
foreground = !!parseTaskFile(taskDir).frontmatter.foreground_mode;
|
|
271
|
+
}
|
|
272
|
+
catch { /* fall through; verifyTaskLaunched still detects most failures */ }
|
|
273
|
+
const startTime = Date.now();
|
|
221
274
|
try {
|
|
222
275
|
execFileSync("schtasks", ["/run", "/tn", tn], { encoding: "utf-8", windowsHide: true });
|
|
223
276
|
}
|
|
@@ -225,6 +278,7 @@ export class WindowsPlatform {
|
|
|
225
278
|
const e = err;
|
|
226
279
|
throw new Error(`Failed to start task via schtasks: ${e.stderr || e.message}`);
|
|
227
280
|
}
|
|
281
|
+
await verifyTaskLaunched(tn, taskDir, startTime, foreground);
|
|
228
282
|
}
|
|
229
283
|
async stopTask(taskId) {
|
|
230
284
|
// schtasks /end leaves agent children orphaned, so kill the process tree
|