@messenger-agent/client 0.24.0-alpha.2 → 0.24.0-alpha.4
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/args.js +5 -303
- package/dist/auto-upgrade.js +2 -184
- package/dist/config-file.js +1 -100
- package/dist/control.js +2 -152
- package/dist/exec.js +2 -37
- package/dist/index.js +5 -157
- package/dist/install.js +1 -142
- package/dist/maintenance.js +2 -194
- package/dist/paths.js +1 -20
- package/dist/runtime.js +3 -241
- package/dist/service.js +3 -208
- package/dist/supervisor.js +1 -282
- package/package.json +5 -5
package/dist/maintenance.js
CHANGED
|
@@ -1,194 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
|
-
const activeStatuses = new Set(["scheduled", "waiting", "running", "restarting", "stopping"]);
|
|
5
|
-
export class MaintenanceScheduler {
|
|
6
|
-
options;
|
|
7
|
-
task;
|
|
8
|
-
timer;
|
|
9
|
-
pollIntervalMs;
|
|
10
|
-
maxWaitMs;
|
|
11
|
-
now;
|
|
12
|
-
constructor(options) {
|
|
13
|
-
this.options = options;
|
|
14
|
-
this.pollIntervalMs = options.pollIntervalMs ?? 1000;
|
|
15
|
-
this.maxWaitMs = options.maxWaitMs ?? 60 * 60 * 1000;
|
|
16
|
-
this.now = options.now ?? Date.now;
|
|
17
|
-
}
|
|
18
|
-
async start() {
|
|
19
|
-
this.task = await this.readState();
|
|
20
|
-
if (!this.task)
|
|
21
|
-
return;
|
|
22
|
-
if (this.task.status === "restarting" || this.task.status === "stopping") {
|
|
23
|
-
const now = this.isoNow();
|
|
24
|
-
this.task = {
|
|
25
|
-
...this.task,
|
|
26
|
-
status: "completed",
|
|
27
|
-
updatedAt: now,
|
|
28
|
-
completedAt: now,
|
|
29
|
-
message: "Client service completed the maintenance operation",
|
|
30
|
-
};
|
|
31
|
-
await this.writeState();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (this.task.status === "running") {
|
|
35
|
-
await this.finish("failed", "Client service exited before the maintenance operation completed");
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (this.task.status === "scheduled" || this.task.status === "waiting")
|
|
39
|
-
this.arm();
|
|
40
|
-
}
|
|
41
|
-
async schedule(options) {
|
|
42
|
-
if (!Number.isInteger(options.delaySeconds) || options.delaySeconds < 1 || options.delaySeconds > 86_400) {
|
|
43
|
-
throw new Error("--delay must be an integer between 1 and 86400 seconds");
|
|
44
|
-
}
|
|
45
|
-
if (this.task &&
|
|
46
|
-
activeStatuses.has(this.task.status) &&
|
|
47
|
-
!(options.source !== "automatic" && this.task.source === "automatic" && isPending(this.task))) {
|
|
48
|
-
throw new Error(`Maintenance task ${this.task.id} is already ${this.task.status}`);
|
|
49
|
-
}
|
|
50
|
-
const nowMs = this.now();
|
|
51
|
-
const now = new Date(nowMs).toISOString();
|
|
52
|
-
this.task = {
|
|
53
|
-
id: randomUUID(),
|
|
54
|
-
operation: options.operation,
|
|
55
|
-
status: "scheduled",
|
|
56
|
-
allowWaiting: options.allowWaiting,
|
|
57
|
-
createdAt: now,
|
|
58
|
-
notBefore: new Date(nowMs + options.delaySeconds * 1000).toISOString(),
|
|
59
|
-
updatedAt: now,
|
|
60
|
-
source: options.source ?? "manual",
|
|
61
|
-
};
|
|
62
|
-
await this.writeState();
|
|
63
|
-
this.arm();
|
|
64
|
-
return this.task;
|
|
65
|
-
}
|
|
66
|
-
getTask() {
|
|
67
|
-
return this.task ? structuredClone(this.task) : undefined;
|
|
68
|
-
}
|
|
69
|
-
async cancel() {
|
|
70
|
-
if (!this.task || (this.task.status !== "scheduled" && this.task.status !== "waiting")) {
|
|
71
|
-
throw new Error("No cancellable maintenance task");
|
|
72
|
-
}
|
|
73
|
-
if (this.timer)
|
|
74
|
-
clearTimeout(this.timer);
|
|
75
|
-
const now = this.isoNow();
|
|
76
|
-
this.task = {
|
|
77
|
-
...this.task,
|
|
78
|
-
status: "cancelled",
|
|
79
|
-
updatedAt: now,
|
|
80
|
-
completedAt: now,
|
|
81
|
-
message: "Cancelled by user",
|
|
82
|
-
};
|
|
83
|
-
await this.writeState();
|
|
84
|
-
return this.task;
|
|
85
|
-
}
|
|
86
|
-
stop() {
|
|
87
|
-
if (this.timer)
|
|
88
|
-
clearTimeout(this.timer);
|
|
89
|
-
this.timer = undefined;
|
|
90
|
-
}
|
|
91
|
-
async checkNow() {
|
|
92
|
-
if (this.timer)
|
|
93
|
-
clearTimeout(this.timer);
|
|
94
|
-
this.timer = undefined;
|
|
95
|
-
await this.tick();
|
|
96
|
-
}
|
|
97
|
-
arm(delayMs) {
|
|
98
|
-
if (!this.task || (this.task.status !== "scheduled" && this.task.status !== "waiting"))
|
|
99
|
-
return;
|
|
100
|
-
if (this.timer)
|
|
101
|
-
clearTimeout(this.timer);
|
|
102
|
-
const untilNotBefore = Math.max(0, Date.parse(this.task.notBefore) - this.now());
|
|
103
|
-
this.timer = setTimeout(() => this.tick(), delayMs ?? untilNotBefore);
|
|
104
|
-
this.timer.unref();
|
|
105
|
-
}
|
|
106
|
-
async tick() {
|
|
107
|
-
this.timer = undefined;
|
|
108
|
-
const task = this.task;
|
|
109
|
-
if (!task || (task.status !== "scheduled" && task.status !== "waiting"))
|
|
110
|
-
return;
|
|
111
|
-
if (this.now() < Date.parse(task.notBefore)) {
|
|
112
|
-
this.arm();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
try {
|
|
116
|
-
const activity = await this.options.getActivity();
|
|
117
|
-
const unavailable = Object.entries(activity.agents)
|
|
118
|
-
.filter(([, status]) => !status.available)
|
|
119
|
-
.map(([name]) => name);
|
|
120
|
-
const blockers = [];
|
|
121
|
-
if (unavailable.length > 0)
|
|
122
|
-
blockers.push(`unavailable agents: ${unavailable.join(", ")}`);
|
|
123
|
-
if (activity.active > 0)
|
|
124
|
-
blockers.push(`${activity.active} active session(s)`);
|
|
125
|
-
if (!task.allowWaiting && activity.waiting > 0)
|
|
126
|
-
blockers.push(`${activity.waiting} waiting session(s)`);
|
|
127
|
-
if (blockers.length > 0) {
|
|
128
|
-
if (task.source !== "automatic" && this.now() - Date.parse(task.notBefore) >= this.maxWaitMs) {
|
|
129
|
-
await this.finish("failed", `Timed out waiting for an idle client: ${blockers.join("; ")}`);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
await this.updateWaiting(`Waiting for an idle client: ${blockers.join("; ")}`);
|
|
133
|
-
this.arm(this.pollIntervalMs);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const now = this.isoNow();
|
|
137
|
-
this.task = { ...task, status: "running", updatedAt: now, message: "Maintenance operation is running" };
|
|
138
|
-
await this.writeState();
|
|
139
|
-
await this.options.execute(task.operation, (status) => this.markServiceExit(status));
|
|
140
|
-
await this.finish("completed", "Maintenance operation completed");
|
|
141
|
-
}
|
|
142
|
-
catch (err) {
|
|
143
|
-
await this.finish("failed", err instanceof Error ? err.message : String(err));
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
async updateWaiting(message) {
|
|
147
|
-
if (!this.task)
|
|
148
|
-
return;
|
|
149
|
-
this.task = { ...this.task, status: "waiting", updatedAt: this.isoNow(), message };
|
|
150
|
-
await this.writeState();
|
|
151
|
-
}
|
|
152
|
-
async markServiceExit(status) {
|
|
153
|
-
if (!this.task || this.task.status !== "running")
|
|
154
|
-
return;
|
|
155
|
-
this.task = {
|
|
156
|
-
...this.task,
|
|
157
|
-
status,
|
|
158
|
-
updatedAt: this.isoNow(),
|
|
159
|
-
message: `Maintenance operation is ${status === "restarting" ? "restarting" : "stopping"} the client service`,
|
|
160
|
-
};
|
|
161
|
-
await this.writeState();
|
|
162
|
-
}
|
|
163
|
-
async finish(status, message) {
|
|
164
|
-
if (!this.task)
|
|
165
|
-
return;
|
|
166
|
-
const now = this.isoNow();
|
|
167
|
-
this.task = { ...this.task, status, updatedAt: now, completedAt: now, message };
|
|
168
|
-
await this.writeState();
|
|
169
|
-
}
|
|
170
|
-
isoNow() {
|
|
171
|
-
return new Date(this.now()).toISOString();
|
|
172
|
-
}
|
|
173
|
-
async readState() {
|
|
174
|
-
try {
|
|
175
|
-
return JSON.parse(await readFile(this.options.statePath, "utf8"));
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
if (err.code === "ENOENT")
|
|
179
|
-
return undefined;
|
|
180
|
-
throw err;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
async writeState() {
|
|
184
|
-
if (!this.task)
|
|
185
|
-
return;
|
|
186
|
-
await mkdir(dirname(this.options.statePath), { recursive: true, mode: 0o700 });
|
|
187
|
-
const temporaryPath = `${this.options.statePath}.tmp`;
|
|
188
|
-
await writeFile(temporaryPath, `${JSON.stringify(this.task, null, 2)}\n`, { mode: 0o600 });
|
|
189
|
-
await rename(temporaryPath, this.options.statePath);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function isPending(task) {
|
|
193
|
-
return task.status === "scheduled" || task.status === "waiting";
|
|
194
|
-
}
|
|
1
|
+
import{randomUUID as o}from"node:crypto";import{mkdir as h,readFile as c,rename as u,writeFile as d}from"node:fs/promises";import{dirname as l}from"node:path";const w=new Set(["scheduled","waiting","running","restarting","stopping"]);class p{options;task;timer;pollIntervalMs;maxWaitMs;now;constructor(t){this.options=t,this.pollIntervalMs=t.pollIntervalMs??1e3,this.maxWaitMs=t.maxWaitMs??3600*1e3,this.now=t.now??Date.now}async start(){if(this.task=await this.readState(),!!this.task){if(this.task.status==="restarting"||this.task.status==="stopping"){const t=this.isoNow();this.task={...this.task,status:"completed",updatedAt:t,completedAt:t,message:"Client service completed the maintenance operation"},await this.writeState();return}if(this.task.status==="running"){await this.finish("failed","Client service exited before the maintenance operation completed");return}(this.task.status==="scheduled"||this.task.status==="waiting")&&this.arm()}}async schedule(t){if(!Number.isInteger(t.delaySeconds)||t.delaySeconds<1||t.delaySeconds>86400)throw new Error("--delay must be an integer between 1 and 86400 seconds");if(this.task&&w.has(this.task.status)&&!(t.source!=="automatic"&&this.task.source==="automatic"&&m(this.task)))throw new Error(`Maintenance task ${this.task.id} is already ${this.task.status}`);const i=this.now(),s=new Date(i).toISOString();return this.task={id:o(),operation:t.operation,status:"scheduled",allowWaiting:t.allowWaiting,createdAt:s,notBefore:new Date(i+t.delaySeconds*1e3).toISOString(),updatedAt:s,source:t.source??"manual"},await this.writeState(),this.arm(),this.task}getTask(){return this.task?structuredClone(this.task):void 0}async cancel(){if(!this.task||this.task.status!=="scheduled"&&this.task.status!=="waiting")throw new Error("No cancellable maintenance task");this.timer&&clearTimeout(this.timer);const t=this.isoNow();return this.task={...this.task,status:"cancelled",updatedAt:t,completedAt:t,message:"Cancelled by user"},await this.writeState(),this.task}stop(){this.timer&&clearTimeout(this.timer),this.timer=void 0}async checkNow(){this.timer&&clearTimeout(this.timer),this.timer=void 0,await this.tick()}arm(t){if(!this.task||this.task.status!=="scheduled"&&this.task.status!=="waiting")return;this.timer&&clearTimeout(this.timer);const i=Math.max(0,Date.parse(this.task.notBefore)-this.now());this.timer=setTimeout(()=>this.tick(),t??i),this.timer.unref()}async tick(){this.timer=void 0;const t=this.task;if(!(!t||t.status!=="scheduled"&&t.status!=="waiting")){if(this.now()<Date.parse(t.notBefore)){this.arm();return}try{const i=await this.options.getActivity(),s=Object.entries(i.agents).filter(([,a])=>!a.available).map(([a])=>a),e=[];if(s.length>0&&e.push(`unavailable agents: ${s.join(", ")}`),i.active>0&&e.push(`${i.active} active session(s)`),!t.allowWaiting&&i.waiting>0&&e.push(`${i.waiting} waiting session(s)`),e.length>0){if(t.source!=="automatic"&&this.now()-Date.parse(t.notBefore)>=this.maxWaitMs){await this.finish("failed",`Timed out waiting for an idle client: ${e.join("; ")}`);return}await this.updateWaiting(`Waiting for an idle client: ${e.join("; ")}`),this.arm(this.pollIntervalMs);return}const r=this.isoNow();this.task={...t,status:"running",updatedAt:r,message:"Maintenance operation is running"},await this.writeState(),await this.options.execute(t.operation,a=>this.markServiceExit(a)),await this.finish("completed","Maintenance operation completed")}catch(i){await this.finish("failed",i instanceof Error?i.message:String(i))}}}async updateWaiting(t){this.task&&(this.task={...this.task,status:"waiting",updatedAt:this.isoNow(),message:t},await this.writeState())}async markServiceExit(t){!this.task||this.task.status!=="running"||(this.task={...this.task,status:t,updatedAt:this.isoNow(),message:`Maintenance operation is ${t==="restarting"?"restarting":"stopping"} the client service`},await this.writeState())}async finish(t,i){if(!this.task)return;const s=this.isoNow();this.task={...this.task,status:t,updatedAt:s,completedAt:s,message:i},await this.writeState()}isoNow(){return new Date(this.now()).toISOString()}async readState(){try{return JSON.parse(await c(this.options.statePath,"utf8"))}catch(t){if(t.code==="ENOENT")return;throw t}}async writeState(){if(!this.task)return;await h(l(this.options.statePath),{recursive:!0,mode:448});const t=`${this.options.statePath}.tmp`;await d(t,`${JSON.stringify(this.task,null,2)}
|
|
2
|
+
`,{mode:384}),await u(t,this.options.statePath)}}function m(n){return n.status==="scheduled"||n.status==="waiting"}export{p as MaintenanceScheduler};
|
package/dist/paths.js
CHANGED
|
@@ -1,20 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
export const defaultConfigPath = join(homedir(), ".coding-agent", "config.yaml");
|
|
4
|
-
export const defaultDataDir = join(homedir(), ".coding-agent", "data");
|
|
5
|
-
export const defaultRuntimeDir = join(homedir(), ".coding-agent", "runtime");
|
|
6
|
-
export const defaultBinDir = join(homedir(), ".coding-agent", "bin");
|
|
7
|
-
export const defaultWorkspacePath = join(homedir(), "messenger-workspace");
|
|
8
|
-
export function expandHome(path) {
|
|
9
|
-
if (path === "~")
|
|
10
|
-
return homedir();
|
|
11
|
-
if (path.startsWith("~/"))
|
|
12
|
-
return join(homedir(), path.slice(2));
|
|
13
|
-
return path;
|
|
14
|
-
}
|
|
15
|
-
export function resolvePath(path) {
|
|
16
|
-
return resolve(expandHome(path));
|
|
17
|
-
}
|
|
18
|
-
export function configBaseDir(configPath) {
|
|
19
|
-
return dirname(configPath);
|
|
20
|
-
}
|
|
1
|
+
import{homedir as t}from"node:os";import{dirname as r,join as n,resolve as o}from"node:path";const f=n(t(),".coding-agent","config.yaml"),s=n(t(),".coding-agent","data"),u=n(t(),".coding-agent","runtime"),d=n(t(),".coding-agent","bin"),g=n(t(),"messenger-workspace");function i(e){return e==="~"?t():e.startsWith("~/")?n(t(),e.slice(2)):e}function m(e){return o(i(e))}function p(e){return r(e)}export{p as configBaseDir,d as defaultBinDir,f as defaultConfigPath,s as defaultDataDir,u as defaultRuntimeDir,g as defaultWorkspacePath,i as expandHome,m as resolvePath};
|
package/dist/runtime.js
CHANGED
|
@@ -1,241 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { runCommand } from "./exec.js";
|
|
6
|
-
import { defaultBinDir, defaultRuntimeDir } from "./paths.js";
|
|
7
|
-
const packageName = "@messenger-agent/client";
|
|
8
|
-
export const releaseChannels = ["latest", "beta", "alpha"];
|
|
9
|
-
export function normalizeReleaseChannel(value) {
|
|
10
|
-
if (value === undefined || value === null || value === "")
|
|
11
|
-
return undefined;
|
|
12
|
-
if (value === "stable")
|
|
13
|
-
return "latest";
|
|
14
|
-
if (value === "latest" || value === "beta" || value === "alpha")
|
|
15
|
-
return value;
|
|
16
|
-
throw new Error(`Unknown release channel: ${String(value)} (expected latest, beta, or alpha)`);
|
|
17
|
-
}
|
|
18
|
-
export function parseReleaseChannel(value) {
|
|
19
|
-
return value === "stable" ? "latest" : value === "latest" || value === "beta" || value === "alpha" ? value : undefined;
|
|
20
|
-
}
|
|
21
|
-
export function normalizeUpgradeVersion(version, explicitChannel) {
|
|
22
|
-
const versionChannel = parseReleaseChannel(version);
|
|
23
|
-
const channel = explicitChannel ?? versionChannel;
|
|
24
|
-
return { version: versionChannel ? (channel ?? versionChannel) : version, channel };
|
|
25
|
-
}
|
|
26
|
-
export function isPrereleaseVersion(version) {
|
|
27
|
-
return /^\d+\.\d+\.\d+-/.test(version);
|
|
28
|
-
}
|
|
29
|
-
export function compareSemverVersions(left, right) {
|
|
30
|
-
const [leftVersion] = left.split("+", 1);
|
|
31
|
-
const [rightVersion] = right.split("+", 1);
|
|
32
|
-
const leftMatch = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(leftVersion ?? "");
|
|
33
|
-
const rightMatch = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(rightVersion ?? "");
|
|
34
|
-
if (!leftMatch || !rightMatch) {
|
|
35
|
-
throw new Error(`Automatic upgrades require semantic versions, received: ${left} and ${right}`);
|
|
36
|
-
}
|
|
37
|
-
for (let index = 1; index <= 3; index += 1) {
|
|
38
|
-
const difference = Number(leftMatch[index]) - Number(rightMatch[index]);
|
|
39
|
-
if (difference !== 0)
|
|
40
|
-
return Math.sign(difference);
|
|
41
|
-
}
|
|
42
|
-
return comparePrereleaseIdentifiers(leftMatch[4], rightMatch[4]);
|
|
43
|
-
}
|
|
44
|
-
function comparePrereleaseIdentifiers(left, right) {
|
|
45
|
-
if (!left && !right)
|
|
46
|
-
return 0;
|
|
47
|
-
if (!left)
|
|
48
|
-
return 1;
|
|
49
|
-
if (!right)
|
|
50
|
-
return -1;
|
|
51
|
-
const leftParts = left.split(".");
|
|
52
|
-
const rightParts = right.split(".");
|
|
53
|
-
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
54
|
-
const leftPart = leftParts[index];
|
|
55
|
-
const rightPart = rightParts[index];
|
|
56
|
-
if (leftPart === undefined)
|
|
57
|
-
return -1;
|
|
58
|
-
if (rightPart === undefined)
|
|
59
|
-
return 1;
|
|
60
|
-
const leftNumeric = /^\d+$/.test(leftPart);
|
|
61
|
-
const rightNumeric = /^\d+$/.test(rightPart);
|
|
62
|
-
if (leftNumeric && rightNumeric) {
|
|
63
|
-
const difference = Number(leftPart) - Number(rightPart);
|
|
64
|
-
if (difference !== 0)
|
|
65
|
-
return Math.sign(difference);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (leftNumeric)
|
|
69
|
-
return -1;
|
|
70
|
-
if (rightNumeric)
|
|
71
|
-
return 1;
|
|
72
|
-
if (leftPart !== rightPart)
|
|
73
|
-
return leftPart < rightPart ? -1 : 1;
|
|
74
|
-
}
|
|
75
|
-
return 0;
|
|
76
|
-
}
|
|
77
|
-
export async function currentPackageVersion() {
|
|
78
|
-
const packagePath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
79
|
-
const packageJson = JSON.parse(await readFile(packagePath, "utf8"));
|
|
80
|
-
return packageJson.version ?? "latest";
|
|
81
|
-
}
|
|
82
|
-
export function npmExecutablePath(nodeExecutable = process.execPath) {
|
|
83
|
-
return join(dirname(nodeExecutable), "npm");
|
|
84
|
-
}
|
|
85
|
-
export function npmProcessPath(nodeExecutable = process.execPath, currentPath = process.env.PATH) {
|
|
86
|
-
const nodeDirectory = dirname(nodeExecutable);
|
|
87
|
-
return currentPath ? `${nodeDirectory}${delimiter}${currentPath}` : nodeDirectory;
|
|
88
|
-
}
|
|
89
|
-
export function currentBundledSkillsDir(moduleUrl = import.meta.url) {
|
|
90
|
-
const moduleDir = dirname(fileURLToPath(moduleUrl));
|
|
91
|
-
return basename(moduleDir) === "src" ? join(moduleDir, "..", "assets", "skills") : join(moduleDir, "assets", "skills");
|
|
92
|
-
}
|
|
93
|
-
export function defaultAgentHomes() {
|
|
94
|
-
return [
|
|
95
|
-
process.env.CODEX_HOME ?? join(homedir(), ".codex"),
|
|
96
|
-
process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"),
|
|
97
|
-
];
|
|
98
|
-
}
|
|
99
|
-
async function removePath(path) {
|
|
100
|
-
await rm(path, { recursive: true, force: true });
|
|
101
|
-
}
|
|
102
|
-
async function replaceSymlink(target, linkPath) {
|
|
103
|
-
const temporaryLink = `${linkPath}.tmp-${process.pid}`;
|
|
104
|
-
await removePath(temporaryLink);
|
|
105
|
-
await symlink(target, temporaryLink, "dir");
|
|
106
|
-
await rename(temporaryLink, linkPath);
|
|
107
|
-
}
|
|
108
|
-
async function isSymlink(path) {
|
|
109
|
-
try {
|
|
110
|
-
return (await lstat(path)).isSymbolicLink();
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
export async function installRuntime(options) {
|
|
117
|
-
const runtimeDir = options.runtimeDir ?? defaultRuntimeDir;
|
|
118
|
-
const binDir = options.binDir ?? defaultBinDir;
|
|
119
|
-
const channel = parseReleaseChannel(options.version);
|
|
120
|
-
const version = options.version === "current"
|
|
121
|
-
? await currentPackageVersion()
|
|
122
|
-
: channel
|
|
123
|
-
? await resolveChannelPackageVersion(channel)
|
|
124
|
-
: options.version;
|
|
125
|
-
const releaseName = version.replaceAll("/", "_").replaceAll(":", "_");
|
|
126
|
-
const releasesDir = join(runtimeDir, "releases");
|
|
127
|
-
const releaseDir = join(releasesDir, releaseName);
|
|
128
|
-
const currentLink = join(runtimeDir, "current");
|
|
129
|
-
const packageEntry = join(releaseDir, "node_modules", "@messenger-agent", "client", "dist", "index.js");
|
|
130
|
-
const currentPackageEntry = join(currentLink, "node_modules", "@messenger-agent", "client", "dist", "index.js");
|
|
131
|
-
const skillsSourceDir = options.skillsSourceDir ??
|
|
132
|
-
join(releaseDir, "node_modules", "@messenger-agent", "client", "dist", "assets", "skills");
|
|
133
|
-
const wrapperPath = join(binDir, "coding-agent-client-service");
|
|
134
|
-
const cliWrapperPath = join(binDir, "coding-agent");
|
|
135
|
-
await mkdir(releasesDir, { recursive: true, mode: 0o700 });
|
|
136
|
-
await mkdir(binDir, { recursive: true, mode: 0o700 });
|
|
137
|
-
if (!(await pathExists(packageEntry))) {
|
|
138
|
-
const temporaryReleaseDir = `${releaseDir}.tmp-${process.pid}`;
|
|
139
|
-
await removePath(temporaryReleaseDir);
|
|
140
|
-
await mkdir(temporaryReleaseDir, { recursive: true, mode: 0o700 });
|
|
141
|
-
try {
|
|
142
|
-
await runCommand(npmExecutablePath(), ["install", "--prefix", temporaryReleaseDir, "--omit=dev", "--verbose", `${packageName}@${version}`], { env: { PATH: npmProcessPath() } });
|
|
143
|
-
await removePath(releaseDir);
|
|
144
|
-
await rename(temporaryReleaseDir, releaseDir);
|
|
145
|
-
}
|
|
146
|
-
catch (err) {
|
|
147
|
-
await removePath(temporaryReleaseDir);
|
|
148
|
-
throw err;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
const [defaultCodexHome, defaultClaudeHome] = defaultAgentHomes();
|
|
152
|
-
await syncBundledSkills(skillsSourceDir, [
|
|
153
|
-
options.codexHome ?? defaultCodexHome,
|
|
154
|
-
options.claudeHome ?? defaultClaudeHome,
|
|
155
|
-
]);
|
|
156
|
-
if (!(await isSymlink(currentLink))) {
|
|
157
|
-
await removePath(currentLink);
|
|
158
|
-
}
|
|
159
|
-
await replaceSymlink(releaseDir, currentLink);
|
|
160
|
-
await writeFile(wrapperPath, [
|
|
161
|
-
"#!/bin/sh",
|
|
162
|
-
"set -eu",
|
|
163
|
-
`export AGENT_CONFIG_PATH=${shellSingleQuote(options.configPath)}`,
|
|
164
|
-
`exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(packageEntry)} run-service --config ${shellSingleQuote(options.configPath)}`,
|
|
165
|
-
"",
|
|
166
|
-
].join("\n"), { mode: 0o700 });
|
|
167
|
-
await writeFile(cliWrapperPath, [
|
|
168
|
-
"#!/bin/sh",
|
|
169
|
-
"set -eu",
|
|
170
|
-
`export AGENT_CONFIG_PATH=${shellSingleQuote(options.configPath)}`,
|
|
171
|
-
`exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(currentPackageEntry)} "$@"`,
|
|
172
|
-
"",
|
|
173
|
-
].join("\n"), { mode: 0o700 });
|
|
174
|
-
return { releaseDir, currentLink, wrapperPath, cliWrapperPath, version };
|
|
175
|
-
}
|
|
176
|
-
export async function resolveChannelPackageVersion(channel) {
|
|
177
|
-
const result = await runCommand(npmExecutablePath(), ["view", `${packageName}@${channel}`, "version", "--json"], {
|
|
178
|
-
allowFailure: true,
|
|
179
|
-
env: { PATH: npmProcessPath() },
|
|
180
|
-
});
|
|
181
|
-
if (result.status !== 0) {
|
|
182
|
-
throw new Error(`Unable to resolve the ${channel} coding-agent version: ${result.stderr || result.stdout}`);
|
|
183
|
-
}
|
|
184
|
-
const value = JSON.parse(result.stdout);
|
|
185
|
-
if (typeof value !== "string" || !value)
|
|
186
|
-
throw new Error("Registry returned an invalid coding-agent version");
|
|
187
|
-
return value;
|
|
188
|
-
}
|
|
189
|
-
async function pathExists(path) {
|
|
190
|
-
try {
|
|
191
|
-
await access(path);
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
export async function syncBundledSkills(skillsSourceDir, agentHomes) {
|
|
199
|
-
const skillName = "manage-coding-agent-client";
|
|
200
|
-
const source = join(skillsSourceDir, skillName);
|
|
201
|
-
const sourceExists = await isDirectory(source);
|
|
202
|
-
for (const home of new Set(agentHomes)) {
|
|
203
|
-
const skillsDir = join(home, "skills");
|
|
204
|
-
const target = join(skillsDir, skillName);
|
|
205
|
-
const temporary = join(skillsDir, `.${skillName}.tmp-${process.pid}`);
|
|
206
|
-
const backup = join(skillsDir, `.${skillName}.old-${process.pid}`);
|
|
207
|
-
await rm(temporary, { recursive: true, force: true });
|
|
208
|
-
await rm(backup, { recursive: true, force: true });
|
|
209
|
-
if (!sourceExists) {
|
|
210
|
-
await rm(target, { recursive: true, force: true });
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
await mkdir(skillsDir, { recursive: true, mode: 0o700 });
|
|
214
|
-
await cp(source, temporary, { recursive: true });
|
|
215
|
-
await rename(target, backup).catch((err) => {
|
|
216
|
-
if (err.code !== "ENOENT")
|
|
217
|
-
throw err;
|
|
218
|
-
});
|
|
219
|
-
try {
|
|
220
|
-
await rename(temporary, target);
|
|
221
|
-
await rm(backup, { recursive: true, force: true });
|
|
222
|
-
}
|
|
223
|
-
catch (err) {
|
|
224
|
-
await rename(backup, target).catch(() => undefined);
|
|
225
|
-
throw err;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
async function isDirectory(path) {
|
|
230
|
-
try {
|
|
231
|
-
return (await lstat(path)).isDirectory();
|
|
232
|
-
}
|
|
233
|
-
catch (err) {
|
|
234
|
-
if (err.code === "ENOENT")
|
|
235
|
-
return false;
|
|
236
|
-
throw err;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
function shellSingleQuote(value) {
|
|
240
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
241
|
-
}
|
|
1
|
+
import{access as O,cp as R,lstat as k,mkdir as g,readFile as j,rename as p,rm as h,symlink as V,writeFile as P}from"node:fs/promises";import{homedir as v}from"node:os";import{basename as F,delimiter as M,dirname as x,join as n}from"node:path";import{fileURLToPath as b}from"node:url";import{runCommand as D}from"./exec.js";import{defaultBinDir as G,defaultRuntimeDir as I}from"./paths.js";const N="@messenger-agent/client",ne=["latest","beta","alpha"];function ie(e){if(!(e==null||e==="")){if(e==="stable")return"latest";if(e==="latest"||e==="beta"||e==="alpha")return e;throw new Error(`Unknown release channel: ${String(e)} (expected latest, beta, or alpha)`)}}function E(e){return e==="stable"?"latest":e==="latest"||e==="beta"||e==="alpha"?e:void 0}function ae(e,t){const r=E(e),o=t??r;return{version:r?o??r:e,channel:o}}function se(e){return/^\d+\.\d+\.\d+-/.test(e)}function oe(e,t){const[r]=e.split("+",1),[o]=t.split("+",1),s=/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(r??""),c=/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(o??"");if(!s||!c)throw new Error(`Automatic upgrades require semantic versions, received: ${e} and ${t}`);for(let i=1;i<=3;i+=1){const a=Number(s[i])-Number(c[i]);if(a!==0)return Math.sign(a)}return L(s[4],c[4])}function L(e,t){if(!e&&!t)return 0;if(!e)return 1;if(!t)return-1;const r=e.split("."),o=t.split(".");for(let s=0;s<Math.max(r.length,o.length);s+=1){const c=r[s],i=o[s];if(c===void 0)return-1;if(i===void 0)return 1;const a=/^\d+$/.test(c),u=/^\d+$/.test(i);if(a&&u){const l=Number(c)-Number(i);if(l!==0)return Math.sign(l);continue}if(a)return-1;if(u)return 1;if(c!==i)return c<i?-1:1}return 0}async function U(){const e=n(x(b(import.meta.url)),"..","package.json");return JSON.parse(await j(e,"utf8")).version??"latest"}function A(e=process.execPath){return n(x(e),"npm")}function C(e=process.execPath,t=process.env.PATH){const r=x(e);return t?`${r}${M}${t}`:r}function ce(e=import.meta.url){const t=x(b(e));return F(t)==="src"?n(t,"..","assets","skills"):n(t,"assets","skills")}function z(){return[process.env.CODEX_HOME??n(v(),".codex"),process.env.CLAUDE_CONFIG_DIR??n(v(),".claude")]}async function w(e){await h(e,{recursive:!0,force:!0})}async function J(e,t){const r=`${t}.tmp-${process.pid}`;await w(r),await V(e,r,"dir"),await p(r,t)}async function B(e){try{return(await k(e)).isSymbolicLink()}catch{return!1}}async function ue(e){const t=e.runtimeDir??I,r=e.binDir??G,o=E(e.version),s=e.version==="current"?await U():o?await Z(o):e.version,c=s.replaceAll("/","_").replaceAll(":","_"),i=n(t,"releases"),a=n(i,c),u=n(t,"current"),l=n(a,"node_modules","@messenger-agent","client","dist","index.js"),f=n(u,"node_modules","@messenger-agent","client","dist","index.js"),S=e.skillsSourceDir??n(a,"node_modules","@messenger-agent","client","dist","assets","skills"),$=n(r,"coding-agent-client-service"),y=n(r,"coding-agent");if(await g(i,{recursive:!0,mode:448}),await g(r,{recursive:!0,mode:448}),!await q(l)){const m=`${a}.tmp-${process.pid}`;await w(m),await g(m,{recursive:!0,mode:448});try{await D(A(),["install","--prefix",m,"--omit=dev","--verbose",`${N}@${s}`],{env:{PATH:C()}}),await w(a),await p(m,a)}catch(T){throw await w(m),T}}const[_,H]=z();return await Q(S,[e.codexHome??_,e.claudeHome??H]),await B(u)||await w(u),await J(a,u),await P($,["#!/bin/sh","set -eu",`export AGENT_CONFIG_PATH=${d(e.configPath)}`,`exec ${d(process.execPath)} ${d(l)} run-service --config ${d(e.configPath)}`,""].join(`
|
|
2
|
+
`),{mode:448}),await P(y,["#!/bin/sh","set -eu",`export AGENT_CONFIG_PATH=${d(e.configPath)}`,`exec ${d(process.execPath)} ${d(f)} "$@"`,""].join(`
|
|
3
|
+
`),{mode:448}),{releaseDir:a,currentLink:u,wrapperPath:$,cliWrapperPath:y,version:s}}async function Z(e){const t=await D(A(),["view",`${N}@${e}`,"version","--json"],{allowFailure:!0,env:{PATH:C()}});if(t.status!==0)throw new Error(`Unable to resolve the ${e} coding-agent version: ${t.stderr||t.stdout}`);const r=JSON.parse(t.stdout);if(typeof r!="string"||!r)throw new Error("Registry returned an invalid coding-agent version");return r}async function q(e){try{return await O(e),!0}catch{return!1}}async function Q(e,t){const r="manage-coding-agent-client",o=n(e,r),s=await W(o);for(const c of new Set(t)){const i=n(c,"skills"),a=n(i,r),u=n(i,`.${r}.tmp-${process.pid}`),l=n(i,`.${r}.old-${process.pid}`);if(await h(u,{recursive:!0,force:!0}),await h(l,{recursive:!0,force:!0}),!s){await h(a,{recursive:!0,force:!0});continue}await g(i,{recursive:!0,mode:448}),await R(o,u,{recursive:!0}),await p(a,l).catch(f=>{if(f.code!=="ENOENT")throw f});try{await p(u,a),await h(l,{recursive:!0,force:!0})}catch(f){throw await p(l,a).catch(()=>{}),f}}}async function W(e){try{return(await k(e)).isDirectory()}catch(t){if(t.code==="ENOENT")return!1;throw t}}function d(e){return`'${e.replaceAll("'","'\\''")}'`}export{oe as compareSemverVersions,ce as currentBundledSkillsDir,U as currentPackageVersion,z as defaultAgentHomes,ue as installRuntime,se as isPrereleaseVersion,ie as normalizeReleaseChannel,ae as normalizeUpgradeVersion,A as npmExecutablePath,C as npmProcessPath,E as parseReleaseChannel,ne as releaseChannels,Z as resolveChannelPackageVersion,Q as syncBundledSkills};
|