palmier 0.9.6 → 0.9.8
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 +28 -13
- package/dist/agents/agent.d.ts +0 -1
- package/dist/agents/agent.js +0 -1
- package/dist/agents/aider.d.ts +0 -1
- package/dist/agents/aider.js +0 -1
- package/dist/agents/claude.d.ts +0 -1
- package/dist/agents/claude.js +0 -1
- package/dist/agents/cline.d.ts +0 -1
- package/dist/agents/cline.js +0 -1
- package/dist/agents/codex.d.ts +0 -1
- package/dist/agents/codex.js +0 -1
- package/dist/agents/copilot.d.ts +0 -1
- package/dist/agents/copilot.js +0 -1
- package/dist/agents/cursor.d.ts +0 -1
- package/dist/agents/cursor.js +0 -1
- package/dist/agents/deepagents.d.ts +0 -1
- package/dist/agents/deepagents.js +0 -1
- package/dist/agents/droid.d.ts +0 -1
- package/dist/agents/droid.js +0 -1
- package/dist/agents/gemini.d.ts +0 -1
- package/dist/agents/gemini.js +0 -1
- package/dist/agents/goose.d.ts +0 -1
- package/dist/agents/goose.js +0 -1
- package/dist/agents/hermes.d.ts +0 -1
- package/dist/agents/hermes.js +0 -1
- package/dist/agents/kimi.d.ts +0 -1
- package/dist/agents/kimi.js +0 -1
- package/dist/agents/kiro.d.ts +0 -1
- package/dist/agents/kiro.js +0 -1
- package/dist/agents/openclaw.d.ts +0 -1
- package/dist/agents/openclaw.js +0 -1
- package/dist/agents/opencode.d.ts +0 -1
- package/dist/agents/opencode.js +0 -1
- package/dist/agents/qoder.d.ts +0 -1
- package/dist/agents/qoder.js +0 -1
- package/dist/agents/qwen.d.ts +0 -1
- package/dist/agents/qwen.js +0 -1
- package/dist/agents/shared-prompt.d.ts +0 -1
- package/dist/agents/shared-prompt.js +0 -1
- package/dist/client-store.d.ts +0 -1
- package/dist/client-store.js +0 -1
- package/dist/commands/clients.d.ts +0 -1
- package/dist/commands/clients.js +0 -1
- package/dist/commands/info.d.ts +0 -1
- package/dist/commands/info.js +0 -1
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.js +1 -2
- package/dist/commands/pair.d.ts +0 -1
- package/dist/commands/pair.js +0 -1
- package/dist/commands/restart.d.ts +0 -1
- package/dist/commands/restart.js +0 -1
- package/dist/commands/run.d.ts +0 -1
- package/dist/commands/run.js +19 -3
- package/dist/commands/serve.d.ts +0 -1
- package/dist/commands/serve.js +0 -1
- package/dist/commands/uninstall.d.ts +0 -1
- package/dist/commands/uninstall.js +0 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/event-queues.d.ts +0 -1
- package/dist/event-queues.js +0 -1
- package/dist/events.d.ts +0 -1
- package/dist/events.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/linked-device.d.ts +0 -1
- package/dist/linked-device.js +0 -1
- package/dist/mcp-handler.d.ts +0 -1
- package/dist/mcp-handler.js +0 -1
- package/dist/mcp-tools.d.ts +0 -1
- package/dist/mcp-tools.js +0 -1
- package/dist/nats-client.d.ts +0 -1
- package/dist/nats-client.js +0 -1
- package/dist/network.d.ts +0 -1
- package/dist/network.js +0 -1
- package/dist/notification-store.d.ts +0 -1
- package/dist/notification-store.js +0 -1
- package/dist/pending-requests.d.ts +0 -1
- package/dist/pending-requests.js +0 -1
- package/dist/platform/index.d.ts +0 -1
- package/dist/platform/index.js +0 -1
- package/dist/platform/linux.d.ts +0 -1
- package/dist/platform/linux.js +0 -1
- package/dist/platform/macos.d.ts +0 -1
- package/dist/platform/macos.js +0 -1
- package/dist/platform/platform.d.ts +0 -1
- package/dist/platform/platform.js +0 -1
- package/dist/platform/windows.d.ts +0 -1
- package/dist/platform/windows.js +0 -1
- package/dist/pwa/assets/{index-MLEFUP3r.js → index-DWvRAUiy.js} +31 -31
- package/dist/pwa/assets/{web-B1sKCc7e.js → web-C4iZbqTC.js} +1 -1
- package/dist/pwa/assets/{web-ETD-8ZHd.js → web-CBFqJGX6.js} +1 -1
- package/dist/pwa/assets/{web-B4xEa6WO.js → web-DL4uXOpS.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/dist/rpc-handler.d.ts +0 -1
- package/dist/rpc-handler.js +0 -1
- package/dist/sms-store.d.ts +0 -1
- package/dist/sms-store.js +0 -1
- package/dist/spawn-command.d.ts +0 -1
- package/dist/spawn-command.js +0 -1
- package/dist/task.d.ts +0 -1
- package/dist/task.js +0 -1
- package/dist/transports/http-transport.d.ts +0 -1
- package/dist/transports/http-transport.js +0 -1
- package/dist/transports/nats-transport.d.ts +0 -1
- package/dist/transports/nats-transport.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/dist/update-checker.d.ts +0 -1
- package/dist/update-checker.js +0 -1
- package/package.json +11 -1
- package/.github/workflows/ci.yml +0 -16
- package/.github/workflows/publish.yml +0 -37
- package/CLAUDE.md +0 -22
- package/dist/pwa/apple-touch-icon.png +0 -0
- package/dist/pwa/manifest.webmanifest +0 -1
- package/dist/pwa/pwa-192x192.png +0 -0
- package/dist/pwa/pwa-512x512.png +0 -0
- package/dist/pwa/registerSW.js +0 -1
- package/dist/pwa/service-worker.js +0 -2
- package/palmier-server/.github/workflows/ci.yml +0 -21
- package/palmier-server/.github/workflows/deploy.yml +0 -38
- package/palmier-server/CLAUDE.md +0 -17
- package/palmier-server/PRODUCTION.md +0 -358
- package/palmier-server/README.md +0 -231
- package/palmier-server/nats.conf +0 -19
- package/palmier-server/package.json +0 -15
- package/palmier-server/pnpm-lock.yaml +0 -7639
- package/palmier-server/pnpm-workspace.yaml +0 -3
- package/palmier-server/pwa/index.html +0 -16
- package/palmier-server/pwa/logo/logo_20260421.png +0 -0
- package/palmier-server/pwa/package.json +0 -34
- package/palmier-server/pwa/public/apple-touch-icon.png +0 -0
- package/palmier-server/pwa/public/favicon.ico +0 -0
- package/palmier-server/pwa/public/pwa-192x192.png +0 -0
- package/palmier-server/pwa/public/pwa-512x512.png +0 -0
- package/palmier-server/pwa/src/App.css +0 -3012
- package/palmier-server/pwa/src/App.tsx +0 -59
- package/palmier-server/pwa/src/agentLabels.ts +0 -11
- package/palmier-server/pwa/src/api.ts +0 -67
- package/palmier-server/pwa/src/components/CapabilityToggles.tsx +0 -170
- package/palmier-server/pwa/src/components/ConnectionStatusIcon.tsx +0 -113
- package/palmier-server/pwa/src/components/HostMenu.tsx +0 -429
- package/palmier-server/pwa/src/components/PermissionsDialog.tsx +0 -34
- package/palmier-server/pwa/src/components/PullToRefreshIndicator.tsx +0 -46
- package/palmier-server/pwa/src/components/RunDetailView.tsx +0 -343
- package/palmier-server/pwa/src/components/SessionComposer.tsx +0 -157
- package/palmier-server/pwa/src/components/SessionsView.tsx +0 -326
- package/palmier-server/pwa/src/components/SwipeToDeleteRow.tsx +0 -170
- package/palmier-server/pwa/src/components/TabBar.tsx +0 -40
- package/palmier-server/pwa/src/components/TaskCard.tsx +0 -255
- package/palmier-server/pwa/src/components/TaskForm.tsx +0 -766
- package/palmier-server/pwa/src/components/TasksView.tsx +0 -179
- package/palmier-server/pwa/src/constants.ts +0 -2
- package/palmier-server/pwa/src/contexts/HostConnectionContext.tsx +0 -432
- package/palmier-server/pwa/src/contexts/HostStoreContext.tsx +0 -124
- package/palmier-server/pwa/src/draftGuard.ts +0 -24
- package/palmier-server/pwa/src/formatTime.ts +0 -44
- package/palmier-server/pwa/src/hooks/useBackClose.ts +0 -75
- package/palmier-server/pwa/src/hooks/useMediaQuery.ts +0 -17
- package/palmier-server/pwa/src/hooks/usePullToRefresh.ts +0 -102
- package/palmier-server/pwa/src/hooks/usePushSubscription.ts +0 -77
- package/palmier-server/pwa/src/main.tsx +0 -14
- package/palmier-server/pwa/src/native/Device.ts +0 -49
- package/palmier-server/pwa/src/pages/Dashboard.tsx +0 -542
- package/palmier-server/pwa/src/pages/PairHost.tsx +0 -232
- package/palmier-server/pwa/src/pages/PairSetup.tsx +0 -134
- package/palmier-server/pwa/src/service-worker.ts +0 -142
- package/palmier-server/pwa/src/types.ts +0 -75
- package/palmier-server/pwa/src/vite-env.d.ts +0 -11
- package/palmier-server/pwa/tsconfig.json +0 -21
- package/palmier-server/pwa/tsconfig.node.json +0 -19
- package/palmier-server/pwa/vite.config.ts +0 -47
- package/palmier-server/server/.env.example +0 -20
- package/palmier-server/server/package.json +0 -36
- package/palmier-server/server/src/db.ts +0 -44
- package/palmier-server/server/src/fcm.ts +0 -74
- package/palmier-server/server/src/index.ts +0 -688
- package/palmier-server/server/src/nats-jwt.ts +0 -299
- package/palmier-server/server/src/nats-setup.ts +0 -48
- package/palmier-server/server/src/nats.ts +0 -33
- package/palmier-server/server/src/notify.ts +0 -34
- package/palmier-server/server/src/push.ts +0 -68
- package/palmier-server/server/src/routes/device.ts +0 -224
- package/palmier-server/server/src/routes/fcm.ts +0 -64
- package/palmier-server/server/src/routes/hosts.ts +0 -56
- package/palmier-server/server/src/routes/push.ts +0 -101
- package/palmier-server/server/tsconfig.json +0 -20
- package/palmier-server/spec.md +0 -533
- package/src/agents/agent-instructions.md +0 -28
- package/src/agents/agent.ts +0 -114
- package/src/agents/aider.ts +0 -35
- package/src/agents/claude.ts +0 -39
- package/src/agents/cline.ts +0 -35
- package/src/agents/codex.ts +0 -40
- package/src/agents/copilot.ts +0 -37
- package/src/agents/cursor.ts +0 -36
- package/src/agents/deepagents.ts +0 -36
- package/src/agents/droid.ts +0 -35
- package/src/agents/gemini.ts +0 -43
- package/src/agents/goose.ts +0 -33
- package/src/agents/hermes.ts +0 -36
- package/src/agents/kimi.ts +0 -35
- package/src/agents/kiro.ts +0 -36
- package/src/agents/openclaw.ts +0 -29
- package/src/agents/opencode.ts +0 -36
- package/src/agents/qoder.ts +0 -36
- package/src/agents/qwen.ts +0 -32
- package/src/agents/shared-prompt.ts +0 -30
- package/src/client-store.ts +0 -68
- package/src/commands/clients.ts +0 -29
- package/src/commands/info.ts +0 -29
- package/src/commands/init.ts +0 -165
- package/src/commands/pair.ts +0 -137
- package/src/commands/restart.ts +0 -6
- package/src/commands/run.ts +0 -608
- package/src/commands/serve.ts +0 -211
- package/src/commands/uninstall.ts +0 -9
- package/src/config.ts +0 -36
- package/src/cross-spawn.d.ts +0 -5
- package/src/event-queues.ts +0 -41
- package/src/events.ts +0 -29
- package/src/index.ts +0 -111
- package/src/linked-device.ts +0 -52
- package/src/mcp-handler.ts +0 -200
- package/src/mcp-tools.ts +0 -839
- package/src/nats-client.ts +0 -19
- package/src/network.ts +0 -96
- package/src/notification-store.ts +0 -30
- package/src/pending-requests.ts +0 -73
- package/src/platform/index.ts +0 -20
- package/src/platform/linux.ts +0 -296
- package/src/platform/macos.ts +0 -329
- package/src/platform/platform.ts +0 -31
- package/src/platform/windows.ts +0 -299
- package/src/rpc-handler.ts +0 -691
- package/src/sms-store.ts +0 -28
- package/src/spawn-command.ts +0 -123
- package/src/task.ts +0 -343
- package/src/transports/http-transport.ts +0 -478
- package/src/transports/nats-transport.ts +0 -76
- package/src/types.ts +0 -89
- package/src/update-checker.ts +0 -40
- package/test/agent-instructions.test.ts +0 -209
- package/test/agent-output-parsing.test.ts +0 -74
- package/test/linux-cron.test.ts +0 -41
- package/test/macos-plist.test.ts +0 -112
- package/test/notification-store.test.ts +0 -57
- package/test/pairing.test.ts +0 -35
- package/test/result-state.test.ts +0 -110
- package/test/task-parsing.test.ts +0 -82
- package/test/taskrun-messages.test.ts +0 -224
- package/test/tsconfig.json +0 -9
- package/test/windows-xml.test.ts +0 -89
- package/tsconfig.json +0 -19
package/src/commands/serve.ts
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { loadConfig } from "../config.js";
|
|
4
|
-
import { connectNats } from "../nats-client.js";
|
|
5
|
-
import { createRpcHandler } from "../rpc-handler.js";
|
|
6
|
-
import { startNatsTransport } from "../transports/nats-transport.js";
|
|
7
|
-
import { startHttpTransport } from "../transports/http-transport.js";
|
|
8
|
-
import { getTaskDir, readTaskStatus, writeTaskStatus, parseTaskFile, appendRunMessage, listTasks, readFollowupStatus, deleteFollowupStatus } from "../task.js";
|
|
9
|
-
import { publishHostEvent } from "../events.js";
|
|
10
|
-
import { getPlatform } from "../platform/index.js";
|
|
11
|
-
import { detectAgents } from "../agents/agent.js";
|
|
12
|
-
import { saveConfig } from "../config.js";
|
|
13
|
-
import type { HostConfig } from "../types.js";
|
|
14
|
-
import { CONFIG_DIR } from "../config.js";
|
|
15
|
-
import { StringCodec, type NatsConnection } from "nats";
|
|
16
|
-
import { addNotification } from "../notification-store.js";
|
|
17
|
-
import { addSmsMessage } from "../sms-store.js";
|
|
18
|
-
import { enqueueEvent } from "../event-queues.js";
|
|
19
|
-
|
|
20
|
-
const POLL_INTERVAL_MS = 30_000;
|
|
21
|
-
const DAEMON_PID_FILE = path.join(CONFIG_DIR, "daemon.pid");
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Reconcile tasks stuck in "started" whose process is no longer alive, and
|
|
25
|
-
* clean up OS scheduler units for one-off tasks that have already terminated.
|
|
26
|
-
* The system scheduler (Task Scheduler / systemd) is the authoritative source.
|
|
27
|
-
*/
|
|
28
|
-
async function checkStaleTasks(
|
|
29
|
-
config: HostConfig,
|
|
30
|
-
nc: NatsConnection | undefined,
|
|
31
|
-
): Promise<void> {
|
|
32
|
-
const tasksRoot = path.join(config.projectRoot, "tasks");
|
|
33
|
-
if (!fs.existsSync(tasksRoot)) return;
|
|
34
|
-
|
|
35
|
-
const platform = getPlatform();
|
|
36
|
-
const taskIds = fs.readdirSync(tasksRoot).filter((f) =>
|
|
37
|
-
fs.statSync(path.join(tasksRoot, f)).isDirectory()
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
for (const taskId of taskIds) {
|
|
41
|
-
const taskDir = getTaskDir(config.projectRoot, taskId);
|
|
42
|
-
const status = readTaskStatus(taskDir);
|
|
43
|
-
if (!status) continue;
|
|
44
|
-
|
|
45
|
-
let task;
|
|
46
|
-
try { task = parseTaskFile(taskDir); } catch { continue; }
|
|
47
|
-
|
|
48
|
-
if (status.running_state === "started" && !platform.isTaskRunning(taskId)) {
|
|
49
|
-
console.log(`[monitor] Task ${taskId} process exited unexpectedly, marking as failed.`);
|
|
50
|
-
const endTime = Date.now();
|
|
51
|
-
writeTaskStatus(taskDir, { running_state: "failed", time_stamp: endTime });
|
|
52
|
-
|
|
53
|
-
const runId = fs.readdirSync(taskDir)
|
|
54
|
-
.filter((f) => /^\d+$/.test(f) && fs.existsSync(path.join(taskDir, f, "TASKRUN.md")))
|
|
55
|
-
.sort()
|
|
56
|
-
.pop();
|
|
57
|
-
|
|
58
|
-
if (runId) {
|
|
59
|
-
appendRunMessage(taskDir, runId, {
|
|
60
|
-
role: "status",
|
|
61
|
-
time: endTime,
|
|
62
|
-
content: "",
|
|
63
|
-
type: "failed",
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
await publishHostEvent(nc, config.hostId, taskId, {
|
|
68
|
-
event_type: "running-state",
|
|
69
|
-
running_state: "failed",
|
|
70
|
-
name: task.frontmatter.name || taskId,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (task.frontmatter.one_off && status.running_state !== "started") {
|
|
75
|
-
try { platform.removeTaskTimer(taskId); } catch { /* best-effort */ }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Reconcile orphaned follow-ups: if a run has a persisted follow-up PID
|
|
79
|
-
// but that process is no longer alive, clear the file and mark the run
|
|
80
|
-
// as failed so the UI doesn't claim it's still running.
|
|
81
|
-
const runIds = fs.readdirSync(taskDir).filter((f) =>
|
|
82
|
-
/^\d+$/.test(f) && fs.existsSync(path.join(taskDir, f, "TASKRUN.md"))
|
|
83
|
-
);
|
|
84
|
-
for (const runId of runIds) {
|
|
85
|
-
const runDir = path.join(taskDir, runId);
|
|
86
|
-
const followup = readFollowupStatus(runDir);
|
|
87
|
-
if (!followup) continue;
|
|
88
|
-
try {
|
|
89
|
-
process.kill(followup.pid, 0);
|
|
90
|
-
} catch {
|
|
91
|
-
deleteFollowupStatus(runDir);
|
|
92
|
-
appendRunMessage(taskDir, runId, {
|
|
93
|
-
role: "status",
|
|
94
|
-
time: Date.now(),
|
|
95
|
-
content: "",
|
|
96
|
-
type: "failed",
|
|
97
|
-
});
|
|
98
|
-
await publishHostEvent(nc, config.hostId, taskId, { event_type: "result-updated", run_id: runId });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export async function serveCommand(): Promise<void> {
|
|
105
|
-
const config = loadConfig();
|
|
106
|
-
|
|
107
|
-
// PID file lets `palmier restart` find us regardless of how we were started
|
|
108
|
-
fs.writeFileSync(DAEMON_PID_FILE, String(process.pid), "utf-8");
|
|
109
|
-
|
|
110
|
-
console.log("Starting...");
|
|
111
|
-
|
|
112
|
-
const agents = await detectAgents();
|
|
113
|
-
config.agents = agents;
|
|
114
|
-
saveConfig(config);
|
|
115
|
-
console.log(`Detected agents: ${agents.map((a) => a.key).join(", ") || "none"}`);
|
|
116
|
-
|
|
117
|
-
let nc: NatsConnection | undefined;
|
|
118
|
-
try {
|
|
119
|
-
nc = await connectNats(config);
|
|
120
|
-
console.log("[nats] Connected");
|
|
121
|
-
} catch (err) {
|
|
122
|
-
console.warn(`[nats] Connection failed (server mode unavailable): ${err}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
await checkStaleTasks(config, nc);
|
|
126
|
-
|
|
127
|
-
// Reinstall scheduler entries for all tasks (recovery after init/reinstall)
|
|
128
|
-
const platform = getPlatform();
|
|
129
|
-
const allTasks = listTasks(config.projectRoot);
|
|
130
|
-
for (const task of allTasks) {
|
|
131
|
-
try {
|
|
132
|
-
platform.installTaskTimer(config, task);
|
|
133
|
-
} catch (err) {
|
|
134
|
-
console.error(`Warning: failed to install timer for task ${task.frontmatter.id}: ${err}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
setInterval(() => {
|
|
139
|
-
checkStaleTasks(config, nc).catch((err) => {
|
|
140
|
-
console.error("[monitor] Error checking stale tasks:", err);
|
|
141
|
-
});
|
|
142
|
-
}, POLL_INTERVAL_MS);
|
|
143
|
-
|
|
144
|
-
const handleRpc = createRpcHandler(config, nc);
|
|
145
|
-
const httpPort = config.httpPort ?? 7256;
|
|
146
|
-
|
|
147
|
-
if (nc) {
|
|
148
|
-
startNatsTransport(config, handleRpc, nc);
|
|
149
|
-
|
|
150
|
-
const sc = StringCodec();
|
|
151
|
-
|
|
152
|
-
// Match phone numbers regardless of formatting; letters preserved for shortcodes.
|
|
153
|
-
function normalizeSender(raw: string): string {
|
|
154
|
-
return raw.replace(/[\s\-()+]/g, "").toLowerCase();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function dispatchDeviceEvent(scheduleType: "on_new_notification" | "on_new_sms", payload: string, parsed?: unknown): void {
|
|
158
|
-
for (const task of listTasks(config.projectRoot)) {
|
|
159
|
-
if (task.frontmatter.schedule_type !== scheduleType) continue;
|
|
160
|
-
if (!task.frontmatter.schedule_enabled) continue;
|
|
161
|
-
if (scheduleType === "on_new_notification" && task.frontmatter.schedule_values && task.frontmatter.schedule_values.length > 0) {
|
|
162
|
-
const pkg = (parsed as { packageName?: string } | undefined)?.packageName;
|
|
163
|
-
if (!pkg || !task.frontmatter.schedule_values.includes(pkg)) continue;
|
|
164
|
-
}
|
|
165
|
-
if (scheduleType === "on_new_sms" && task.frontmatter.schedule_values && task.frontmatter.schedule_values.length > 0) {
|
|
166
|
-
const sender = (parsed as { sender?: string } | undefined)?.sender;
|
|
167
|
-
const normalizedSender = sender ? normalizeSender(sender) : "";
|
|
168
|
-
if (!normalizedSender || !task.frontmatter.schedule_values.some((s) => normalizeSender(s) === normalizedSender)) continue;
|
|
169
|
-
}
|
|
170
|
-
const { shouldStart } = enqueueEvent(task.frontmatter.id, payload);
|
|
171
|
-
if (shouldStart) {
|
|
172
|
-
platform.startTask(task.frontmatter.id).catch((err) => {
|
|
173
|
-
console.error(`[event-trigger] Failed to start ${task.frontmatter.id}:`, err);
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const notifSub = nc.subscribe(`host.${config.hostId}.device.notifications`);
|
|
180
|
-
(async () => {
|
|
181
|
-
for await (const msg of notifSub) {
|
|
182
|
-
const raw = sc.decode(msg.data);
|
|
183
|
-
let parsed: unknown;
|
|
184
|
-
try {
|
|
185
|
-
parsed = JSON.parse(raw);
|
|
186
|
-
addNotification({ ...(parsed as object), receivedAt: Date.now() } as Parameters<typeof addNotification>[0]);
|
|
187
|
-
} catch (err) {
|
|
188
|
-
console.error("[nats] Failed to parse device notification:", err);
|
|
189
|
-
}
|
|
190
|
-
dispatchDeviceEvent("on_new_notification", raw, parsed);
|
|
191
|
-
}
|
|
192
|
-
})();
|
|
193
|
-
|
|
194
|
-
const smsSub = nc.subscribe(`host.${config.hostId}.device.sms`);
|
|
195
|
-
(async () => {
|
|
196
|
-
for await (const msg of smsSub) {
|
|
197
|
-
const raw = sc.decode(msg.data);
|
|
198
|
-
let parsed: unknown;
|
|
199
|
-
try {
|
|
200
|
-
parsed = JSON.parse(raw);
|
|
201
|
-
addSmsMessage({ ...(parsed as object), receivedAt: Date.now() } as Parameters<typeof addSmsMessage>[0]);
|
|
202
|
-
} catch (err) {
|
|
203
|
-
console.error("[nats] Failed to parse device SMS:", err);
|
|
204
|
-
}
|
|
205
|
-
dispatchDeviceEvent("on_new_sms", raw, parsed);
|
|
206
|
-
}
|
|
207
|
-
})();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
await startHttpTransport(config, handleRpc, httpPort, nc);
|
|
211
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { getPlatform } from "../platform/index.js";
|
|
2
|
-
|
|
3
|
-
export async function uninstallCommand(): Promise<void> {
|
|
4
|
-
const platform = getPlatform();
|
|
5
|
-
platform.uninstallDaemon();
|
|
6
|
-
|
|
7
|
-
console.log("\nTo uninstall the package: npm uninstall -g palmier");
|
|
8
|
-
console.log("To also remove configuration and task data, see https://github.com/caihongxu/palmier#uninstalling");
|
|
9
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import type { HostConfig } from "./types.js";
|
|
5
|
-
|
|
6
|
-
const CONFIG_DIR = path.join(homedir(), ".config", "palmier");
|
|
7
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, "host.json");
|
|
8
|
-
|
|
9
|
-
export function loadConfig(): HostConfig {
|
|
10
|
-
if (!fs.existsSync(CONFIG_FILE)) {
|
|
11
|
-
throw new Error(
|
|
12
|
-
"Host not provisioned. Run `palmier init` first.\n" +
|
|
13
|
-
`Expected config at: ${CONFIG_FILE}`
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
18
|
-
const config = JSON.parse(raw) as HostConfig;
|
|
19
|
-
|
|
20
|
-
if (!config.hostId) {
|
|
21
|
-
throw new Error("Invalid host config: missing hostId");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (!config.natsUrl || !config.natsJwt || !config.natsNkeySeed) {
|
|
25
|
-
throw new Error("Invalid host config: missing NATS JWT credentials. Re-run palmier init.");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return config;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function saveConfig(config: HostConfig): void {
|
|
32
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
33
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export { CONFIG_DIR, CONFIG_FILE };
|
package/src/cross-spawn.d.ts
DELETED
package/src/event-queues.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-task in-memory event queues for on_new_notification / on_new_sms schedules.
|
|
3
|
-
* The daemon owns the NATS subscription and populates these queues; the
|
|
4
|
-
* `palmier run` process drains via /task-event/pop.
|
|
5
|
-
*
|
|
6
|
-
* Invariants:
|
|
7
|
-
* - popEvent clears activeRuns atomically when the queue empties, so a
|
|
8
|
-
* fresh startTask cannot race the tearing-down run.
|
|
9
|
-
* - enqueueEvent returns shouldStart=true only on the idle→active edge.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const MAX_QUEUE_SIZE = 100;
|
|
13
|
-
|
|
14
|
-
const queues = new Map<string, string[]>();
|
|
15
|
-
const activeRuns = new Set<string>();
|
|
16
|
-
|
|
17
|
-
export function enqueueEvent(taskId: string, payload: string): { shouldStart: boolean } {
|
|
18
|
-
const queue = queues.get(taskId) ?? [];
|
|
19
|
-
if (queue.length >= MAX_QUEUE_SIZE) queue.shift();
|
|
20
|
-
queue.push(payload);
|
|
21
|
-
queues.set(taskId, queue);
|
|
22
|
-
|
|
23
|
-
if (activeRuns.has(taskId)) return { shouldStart: false };
|
|
24
|
-
activeRuns.add(taskId);
|
|
25
|
-
return { shouldStart: true };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function popEvent(taskId: string): { event: string } | { empty: true } {
|
|
29
|
-
const queue = queues.get(taskId);
|
|
30
|
-
if (queue && queue.length > 0) {
|
|
31
|
-
return { event: queue.shift()! };
|
|
32
|
-
}
|
|
33
|
-
activeRuns.delete(taskId);
|
|
34
|
-
return { empty: true };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Remove any state for a task (called from task.delete). */
|
|
38
|
-
export function clearTaskQueue(taskId: string): void {
|
|
39
|
-
queues.delete(taskId);
|
|
40
|
-
activeRuns.delete(taskId);
|
|
41
|
-
}
|
package/src/events.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { StringCodec, type NatsConnection } from "nats";
|
|
2
|
-
import { loadConfig } from "./config.js";
|
|
3
|
-
|
|
4
|
-
const sc = StringCodec();
|
|
5
|
-
|
|
6
|
-
export async function publishHostEvent(
|
|
7
|
-
nc: NatsConnection | undefined,
|
|
8
|
-
hostId: string,
|
|
9
|
-
taskId: string,
|
|
10
|
-
payload: Record<string, unknown>,
|
|
11
|
-
): Promise<void> {
|
|
12
|
-
const subject = `host-event.${hostId}.${taskId}`;
|
|
13
|
-
|
|
14
|
-
if (nc) {
|
|
15
|
-
nc.publish(subject, sc.encode(JSON.stringify(payload)));
|
|
16
|
-
console.log(`[nats] ${subject} →`, payload);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const config = loadConfig();
|
|
20
|
-
const port = config.httpPort ?? 7256;
|
|
21
|
-
try {
|
|
22
|
-
await fetch(`http://localhost:${port}/event`, {
|
|
23
|
-
method: "POST",
|
|
24
|
-
headers: { "Content-Type": "application/json" },
|
|
25
|
-
body: JSON.stringify({ task_id: taskId, ...payload }),
|
|
26
|
-
});
|
|
27
|
-
console.log(`[http] host-event: ${taskId} →`, payload);
|
|
28
|
-
} catch { /* serve HTTP may not be ready yet */ }
|
|
29
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import "dotenv/config";
|
|
4
|
-
import { readFileSync } from "fs";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
import { dirname, join } from "path";
|
|
7
|
-
import { Command } from "commander";
|
|
8
|
-
import { initCommand } from "./commands/init.js";
|
|
9
|
-
import { infoCommand } from "./commands/info.js";
|
|
10
|
-
import { runCommand } from "./commands/run.js";
|
|
11
|
-
import { serveCommand } from "./commands/serve.js";
|
|
12
|
-
|
|
13
|
-
import { pairCommand } from "./commands/pair.js";
|
|
14
|
-
import { restartCommand } from "./commands/restart.js";
|
|
15
|
-
import { clientsListCommand, clientsRevokeCommand, clientsRevokeAllCommand } from "./commands/clients.js";
|
|
16
|
-
import { uninstallCommand } from "./commands/uninstall.js";
|
|
17
|
-
|
|
18
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
20
|
-
|
|
21
|
-
const program = new Command();
|
|
22
|
-
|
|
23
|
-
program
|
|
24
|
-
.name("palmier")
|
|
25
|
-
.description("Palmier host CLI")
|
|
26
|
-
.version(pkg.version);
|
|
27
|
-
|
|
28
|
-
program
|
|
29
|
-
.command("init")
|
|
30
|
-
.description("Provision this host")
|
|
31
|
-
.action(async () => {
|
|
32
|
-
await initCommand();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
program
|
|
36
|
-
.command("info")
|
|
37
|
-
.description("Show host connection info")
|
|
38
|
-
.action(async () => {
|
|
39
|
-
await infoCommand();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
program
|
|
43
|
-
.command("run <task-id>")
|
|
44
|
-
.description("Execute a task by ID")
|
|
45
|
-
.action(async (taskId: string) => {
|
|
46
|
-
await runCommand(taskId);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
program
|
|
50
|
-
.command("serve")
|
|
51
|
-
.description("Start the persistent RPC handler")
|
|
52
|
-
.action(async () => {
|
|
53
|
-
await serveCommand();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
program
|
|
57
|
-
.command("restart")
|
|
58
|
-
.description("Restart the palmier serve daemon")
|
|
59
|
-
.action(async () => {
|
|
60
|
-
await restartCommand();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
program
|
|
65
|
-
.command("pair")
|
|
66
|
-
.description("Generate a pairing code for connecting a PWA client")
|
|
67
|
-
.action(async () => {
|
|
68
|
-
await pairCommand();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const clientsCmd = program
|
|
73
|
-
.command("clients")
|
|
74
|
-
.description("Manage paired clients");
|
|
75
|
-
|
|
76
|
-
clientsCmd
|
|
77
|
-
.command("list")
|
|
78
|
-
.description("List active clients")
|
|
79
|
-
.action(async () => {
|
|
80
|
-
await clientsListCommand();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
clientsCmd
|
|
84
|
-
.command("revoke <token>")
|
|
85
|
-
.description("Revoke a client by token")
|
|
86
|
-
.action(async (token: string) => {
|
|
87
|
-
await clientsRevokeCommand(token);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
clientsCmd
|
|
91
|
-
.command("revoke-all")
|
|
92
|
-
.description("Revoke all clients")
|
|
93
|
-
.action(async () => {
|
|
94
|
-
await clientsRevokeAllCommand();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
program
|
|
98
|
-
.command("uninstall")
|
|
99
|
-
.description("Stop the daemon and remove all scheduled tasks")
|
|
100
|
-
.action(async () => {
|
|
101
|
-
await uninstallCommand();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (process.argv.length <= 2) {
|
|
105
|
-
process.argv.push("serve");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
109
|
-
console.error(err);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
});
|
package/src/linked-device.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { CONFIG_DIR } from "./config.js";
|
|
4
|
-
|
|
5
|
-
const LINKED_DEVICE_FILE = path.join(CONFIG_DIR, "linked-device.json");
|
|
6
|
-
|
|
7
|
-
export interface LinkedDevice {
|
|
8
|
-
clientToken: string;
|
|
9
|
-
fcmToken: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function read(): LinkedDevice | null {
|
|
13
|
-
try {
|
|
14
|
-
if (!fs.existsSync(LINKED_DEVICE_FILE)) return null;
|
|
15
|
-
const raw = fs.readFileSync(LINKED_DEVICE_FILE, "utf-8");
|
|
16
|
-
const parsed = JSON.parse(raw) as Partial<LinkedDevice>;
|
|
17
|
-
if (!parsed?.clientToken || !parsed?.fcmToken) return null;
|
|
18
|
-
return { clientToken: parsed.clientToken, fcmToken: parsed.fcmToken };
|
|
19
|
-
} catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function write(device: LinkedDevice | null): void {
|
|
25
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
26
|
-
if (!device) {
|
|
27
|
-
if (fs.existsSync(LINKED_DEVICE_FILE)) fs.unlinkSync(LINKED_DEVICE_FILE);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
fs.writeFileSync(LINKED_DEVICE_FILE, JSON.stringify(device, null, 2), "utf-8");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function getLinkedDevice(): LinkedDevice | null {
|
|
34
|
-
return read();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function setLinkedDevice(clientToken: string, fcmToken: string): void {
|
|
38
|
-
write({ clientToken, fcmToken });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function clearLinkedDevice(): void {
|
|
42
|
-
write(null);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function clearLinkedDeviceIfMatches(clientToken: string): boolean {
|
|
46
|
-
const current = read();
|
|
47
|
-
if (current?.clientToken === clientToken) {
|
|
48
|
-
write(null);
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
52
|
-
}
|
package/src/mcp-handler.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
import { agentTools, agentToolMap, agentResources, agentResourceMap, ToolError, type ToolContext } from "./mcp-tools.js";
|
|
3
|
-
|
|
4
|
-
interface JsonRpcRequest {
|
|
5
|
-
jsonrpc: string;
|
|
6
|
-
id?: string | number | null;
|
|
7
|
-
method: string;
|
|
8
|
-
params?: Record<string, unknown>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface McpResponse {
|
|
12
|
-
body: object;
|
|
13
|
-
sessionId?: string;
|
|
14
|
-
/** If true, the HTTP transport should keep the response open as an SSE stream for server-initiated notifications. */
|
|
15
|
-
stream?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** sessionId → subscribed resource URIs */
|
|
19
|
-
const resourceSubscriptions = new Map<string, Set<string>>();
|
|
20
|
-
|
|
21
|
-
export function getResourceSubscriptions(): Map<string, Set<string>> {
|
|
22
|
-
return resourceSubscriptions;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
|
-
const sessionAgents = new Map<string, { agentName: string; expiresAt: number }>();
|
|
27
|
-
|
|
28
|
-
export function getAgentName(sessionId: string): string | undefined {
|
|
29
|
-
const entry = sessionAgents.get(sessionId);
|
|
30
|
-
if (!entry) return undefined;
|
|
31
|
-
if (Date.now() > entry.expiresAt) {
|
|
32
|
-
sessionAgents.delete(sessionId);
|
|
33
|
-
return undefined;
|
|
34
|
-
}
|
|
35
|
-
return entry.agentName;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function pruneExpiredSessions(): void {
|
|
39
|
-
const now = Date.now();
|
|
40
|
-
for (const [id, entry] of sessionAgents) {
|
|
41
|
-
if (now > entry.expiresAt) {
|
|
42
|
-
sessionAgents.delete(id);
|
|
43
|
-
resourceSubscriptions.delete(id);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function rpcError(id: string | number | null, code: number, message: string): object {
|
|
49
|
-
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function rpcResult(id: string | number | null, result: unknown): object {
|
|
53
|
-
return { jsonrpc: "2.0", id, result };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export async function handleMcpRequest(body: string, sessionId: string | undefined, ctx: ToolContext): Promise<McpResponse> {
|
|
57
|
-
let req: JsonRpcRequest;
|
|
58
|
-
try {
|
|
59
|
-
req = JSON.parse(body);
|
|
60
|
-
} catch {
|
|
61
|
-
return { body: rpcError(null, -32700, "Parse error") };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const id = req.id ?? null;
|
|
65
|
-
|
|
66
|
-
if (req.jsonrpc !== "2.0") {
|
|
67
|
-
return { body: rpcError(id, -32600, "Invalid Request: missing jsonrpc 2.0") };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const agent = sessionId ? getAgentName(sessionId) : undefined;
|
|
71
|
-
const sid = sessionId?.slice(0, 8) ?? "none";
|
|
72
|
-
const logPrefix = agent ? `[mcp] [${sid}] [${agent}]` : `[mcp] [${sid}]`;
|
|
73
|
-
console.log(`${logPrefix} ${req.method}${req.method === "tools/call" ? ` → ${req.params?.name}` : ""}`);
|
|
74
|
-
|
|
75
|
-
switch (req.method) {
|
|
76
|
-
case "initialize": {
|
|
77
|
-
const newSessionId = randomUUID();
|
|
78
|
-
const clientInfo = req.params?.clientInfo as { name?: string; version?: string } | undefined;
|
|
79
|
-
const agentName = clientInfo
|
|
80
|
-
? `${clientInfo.name || "unknown"}${clientInfo.version ? ` ${clientInfo.version}` : ""}`
|
|
81
|
-
: undefined;
|
|
82
|
-
|
|
83
|
-
if (agentName) {
|
|
84
|
-
sessionAgents.set(newSessionId, { agentName, expiresAt: Date.now() + SESSION_TTL_MS });
|
|
85
|
-
pruneExpiredSessions();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
console.log(`[mcp] [${newSessionId.slice(0, 8)}] Session initialized${agentName ? ` (${agentName})` : ""}`);
|
|
89
|
-
return {
|
|
90
|
-
body: rpcResult(id, {
|
|
91
|
-
protocolVersion: "2025-03-26",
|
|
92
|
-
capabilities: { tools: {}, resources: { subscribe: true } },
|
|
93
|
-
serverInfo: { name: "palmier", version: "1.0.0" },
|
|
94
|
-
}),
|
|
95
|
-
sessionId: newSessionId,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
case "tools/list": {
|
|
100
|
-
return {
|
|
101
|
-
body: rpcResult(id, {
|
|
102
|
-
tools: agentTools.map((t) => ({
|
|
103
|
-
name: t.name,
|
|
104
|
-
description: t.description.join(" "),
|
|
105
|
-
inputSchema: t.inputSchema,
|
|
106
|
-
})),
|
|
107
|
-
}),
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
case "tools/call": {
|
|
112
|
-
const name = req.params?.name as string | undefined;
|
|
113
|
-
const args = (req.params?.arguments ?? {}) as Record<string, unknown>;
|
|
114
|
-
|
|
115
|
-
if (!name) return { body: rpcError(id, -32602, "Missing params.name") };
|
|
116
|
-
|
|
117
|
-
const tool = agentToolMap.get(name);
|
|
118
|
-
if (!tool) return { body: rpcError(id, -32602, `Unknown tool: ${name}`) };
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const result = await tool.handler(args, ctx);
|
|
122
|
-
console.log(`${logPrefix} tools/call ${name} done:`, JSON.stringify(result).slice(0, 200));
|
|
123
|
-
return {
|
|
124
|
-
body: rpcResult(id, {
|
|
125
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
126
|
-
}),
|
|
127
|
-
};
|
|
128
|
-
} catch (err: any) {
|
|
129
|
-
const message = err instanceof ToolError ? err.message : String(err);
|
|
130
|
-
console.error(`${logPrefix} tools/call ${name} error:`, message);
|
|
131
|
-
return {
|
|
132
|
-
body: rpcResult(id, {
|
|
133
|
-
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
134
|
-
isError: true,
|
|
135
|
-
}),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
case "resources/list": {
|
|
141
|
-
return {
|
|
142
|
-
body: rpcResult(id, {
|
|
143
|
-
resources: agentResources.map((r) => ({
|
|
144
|
-
uri: r.uri,
|
|
145
|
-
name: r.name,
|
|
146
|
-
description: r.description[0],
|
|
147
|
-
mimeType: r.mimeType,
|
|
148
|
-
})),
|
|
149
|
-
}),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
case "resources/read": {
|
|
154
|
-
const uri = req.params?.uri as string;
|
|
155
|
-
const resource = agentResourceMap.get(uri);
|
|
156
|
-
if (!resource) {
|
|
157
|
-
return { body: rpcError(id, -32602, `Unknown resource: ${uri}`) };
|
|
158
|
-
}
|
|
159
|
-
console.log(`${logPrefix} resources/read ${uri}`);
|
|
160
|
-
const content = resource.read();
|
|
161
|
-
console.log(`${logPrefix} resources/read ${uri} done: ${JSON.stringify(content).slice(0, 200)}`);
|
|
162
|
-
return {
|
|
163
|
-
body: rpcResult(id, {
|
|
164
|
-
contents: [{
|
|
165
|
-
uri: resource.uri,
|
|
166
|
-
mimeType: resource.mimeType,
|
|
167
|
-
text: JSON.stringify(content),
|
|
168
|
-
}],
|
|
169
|
-
}),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
case "resources/subscribe": {
|
|
174
|
-
const uri = req.params?.uri as string;
|
|
175
|
-
if (!agentResourceMap.has(uri)) {
|
|
176
|
-
return { body: rpcError(id, -32602, `Unknown resource: ${uri}`) };
|
|
177
|
-
}
|
|
178
|
-
if (!sessionId) {
|
|
179
|
-
return { body: rpcError(id, -32600, "Session required for subscriptions") };
|
|
180
|
-
}
|
|
181
|
-
if (!resourceSubscriptions.has(sessionId)) {
|
|
182
|
-
resourceSubscriptions.set(sessionId, new Set());
|
|
183
|
-
}
|
|
184
|
-
resourceSubscriptions.get(sessionId)!.add(uri);
|
|
185
|
-
return { body: rpcResult(id, {}), stream: true };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
case "resources/unsubscribe": {
|
|
189
|
-
const uri = req.params?.uri as string;
|
|
190
|
-
if (sessionId) {
|
|
191
|
-
resourceSubscriptions.get(sessionId)?.delete(uri);
|
|
192
|
-
}
|
|
193
|
-
return { body: rpcResult(id, {}) };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
default:
|
|
197
|
-
console.warn(`${logPrefix} Unknown method: ${req.method}`);
|
|
198
|
-
return { body: rpcError(id, -32601, `Method not found: ${req.method}`) };
|
|
199
|
-
}
|
|
200
|
-
}
|