open-agents-ai 0.187.183 → 0.187.185
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/index.js +94 -14
- package/dist/postinstall-daemon.cjs +486 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2652,14 +2652,16 @@ var init_glob_find = __esm({
|
|
|
2652
2652
|
});
|
|
2653
2653
|
|
|
2654
2654
|
// packages/execution/dist/tools/web-fetch.js
|
|
2655
|
-
var DEFAULT_MAX_LENGTH, SHORT_CONTENT_THRESHOLD, WebFetchTool;
|
|
2655
|
+
var DEFAULT_MAX_LENGTH, SHORT_CONTENT_THRESHOLD, CACHE_TTL_MS, WebFetchTool;
|
|
2656
2656
|
var init_web_fetch = __esm({
|
|
2657
2657
|
"packages/execution/dist/tools/web-fetch.js"() {
|
|
2658
2658
|
"use strict";
|
|
2659
2659
|
DEFAULT_MAX_LENGTH = 5e3;
|
|
2660
2660
|
SHORT_CONTENT_THRESHOLD = 200;
|
|
2661
|
+
CACHE_TTL_MS = 6e4;
|
|
2661
2662
|
WebFetchTool = class {
|
|
2662
2663
|
name = "web_fetch";
|
|
2664
|
+
_fetchCache = /* @__PURE__ */ new Map();
|
|
2663
2665
|
description = "Fetch a single web page and return its text content (HTML stripped to plain text). FASTEST web tool \u2014 use this for reading any single URL: documentation, articles, README files, API references, Stack Overflow answers. Limitations: no JavaScript rendering (SPAs/React apps return empty), no link following, no cookies/auth, no structured data extraction. If the page is blank or incomplete, switch to web_crawl with strategy='playwright'. For scraping/extracting structured data (prices, listings, tables), use web_crawl instead. For search engine queries, use web_search instead. For interactive browser sessions (login, form filling, clicking), use browser_action instead.";
|
|
2664
2666
|
parameters = {
|
|
2665
2667
|
type: "object",
|
|
@@ -2679,6 +2681,19 @@ var init_web_fetch = __esm({
|
|
|
2679
2681
|
const url = args["url"];
|
|
2680
2682
|
const maxLength = args["max_length"] ?? DEFAULT_MAX_LENGTH;
|
|
2681
2683
|
const start2 = performance.now();
|
|
2684
|
+
const cached = this._fetchCache.get(url);
|
|
2685
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
2686
|
+
const slicedCache = cached.text.length > maxLength ? `${cached.text.slice(0, maxLength)}
|
|
2687
|
+
|
|
2688
|
+
[Content truncated to ${maxLength} characters]` : cached.text;
|
|
2689
|
+
return {
|
|
2690
|
+
success: true,
|
|
2691
|
+
output: `[CACHED \u2014 already fetched this URL in this session; the content below is the full response. Do NOT re-fetch the same URL. If the content seems short, the page may just be short or require JavaScript \u2014 use browser_action with a headless browser instead.]
|
|
2692
|
+
|
|
2693
|
+
` + slicedCache,
|
|
2694
|
+
durationMs: performance.now() - start2
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2682
2697
|
try {
|
|
2683
2698
|
const response = await fetch(url, {
|
|
2684
2699
|
headers: {
|
|
@@ -2697,6 +2712,7 @@ var init_web_fetch = __esm({
|
|
|
2697
2712
|
}
|
|
2698
2713
|
const html = await response.text();
|
|
2699
2714
|
const text = this.#stripHtml(html);
|
|
2715
|
+
this._fetchCache.set(url, { text, fetchedAt: Date.now() });
|
|
2700
2716
|
if (text.length < SHORT_CONTENT_THRESHOLD) {
|
|
2701
2717
|
return {
|
|
2702
2718
|
success: true,
|
|
@@ -2711,7 +2727,7 @@ var init_web_fetch = __esm({
|
|
|
2711
2727
|
success: true,
|
|
2712
2728
|
output: truncated ? `${text.slice(0, maxLength)}
|
|
2713
2729
|
|
|
2714
|
-
[Content truncated to ${maxLength} characters]` : text,
|
|
2730
|
+
[Content truncated to ${maxLength} characters. The full response is cached for this session \u2014 subsequent calls to the same URL return the cached content.]` : text,
|
|
2715
2731
|
durationMs: performance.now() - start2
|
|
2716
2732
|
};
|
|
2717
2733
|
} catch (error) {
|
|
@@ -265405,6 +265421,35 @@ function cleanForStorage(text) {
|
|
|
265405
265421
|
out = out.replace(/\s+/g, " ").trim();
|
|
265406
265422
|
return out;
|
|
265407
265423
|
}
|
|
265424
|
+
function extractTaskCompleteSummary(args) {
|
|
265425
|
+
if (args == null)
|
|
265426
|
+
return "";
|
|
265427
|
+
if (typeof args === "string")
|
|
265428
|
+
return stripThinkTags(args);
|
|
265429
|
+
if (typeof args === "object") {
|
|
265430
|
+
const obj = args;
|
|
265431
|
+
if (typeof obj.summary === "string" && obj.summary.trim().length > 0) {
|
|
265432
|
+
return stripThinkTags(obj.summary);
|
|
265433
|
+
}
|
|
265434
|
+
for (const alt of ["text", "message", "result", "output", "content", "response", "answer"]) {
|
|
265435
|
+
if (typeof obj[alt] === "string" && obj[alt].trim().length > 0) {
|
|
265436
|
+
return stripThinkTags(obj[alt]);
|
|
265437
|
+
}
|
|
265438
|
+
}
|
|
265439
|
+
if (typeof obj[""] === "string" && obj[""].trim().length > 0) {
|
|
265440
|
+
let value2 = obj[""];
|
|
265441
|
+
value2 = value2.replace(/^[^>]*>\s*\n?/, "");
|
|
265442
|
+
value2 = value2.replace(/\s*<\/[^>]+>\s*$/, "");
|
|
265443
|
+
return stripThinkTags(value2.trim());
|
|
265444
|
+
}
|
|
265445
|
+
const stringValues = Object.values(obj).filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
265446
|
+
if (stringValues.length > 0) {
|
|
265447
|
+
const longest = stringValues.reduce((a2, b) => a2.length >= b.length ? a2 : b);
|
|
265448
|
+
return stripThinkTags(longest);
|
|
265449
|
+
}
|
|
265450
|
+
}
|
|
265451
|
+
return "";
|
|
265452
|
+
}
|
|
265408
265453
|
var SIGNPOST_RE, NEW_TASK_RE, RESTORED_BLOCK_RE, SYSTEM_BLOCK_RE, THINK_BLOCK_RE, ANSI_ESCAPE_RE;
|
|
265409
265454
|
var init_textSanitize = __esm({
|
|
265410
265455
|
"packages/orchestrator/dist/textSanitize.js"() {
|
|
@@ -266103,6 +266148,15 @@ import { join as join63 } from "node:path";
|
|
|
266103
266148
|
import { mkdirSync as mkdirSync24, existsSync as existsSync47 } from "node:fs";
|
|
266104
266149
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
266105
266150
|
import { createHash as createHash3 } from "node:crypto";
|
|
266151
|
+
function sanitizeImportance(raw, fallback = 5) {
|
|
266152
|
+
if (typeof raw !== "number" || !Number.isFinite(raw))
|
|
266153
|
+
return fallback;
|
|
266154
|
+
if (raw < 0)
|
|
266155
|
+
return 0;
|
|
266156
|
+
if (raw > 10)
|
|
266157
|
+
return 10;
|
|
266158
|
+
return raw;
|
|
266159
|
+
}
|
|
266106
266160
|
function autoImportance(toolName, modality, content) {
|
|
266107
266161
|
if (toolName === "task_complete")
|
|
266108
266162
|
return 9;
|
|
@@ -266203,7 +266257,8 @@ var init_episodeStore = __esm({
|
|
|
266203
266257
|
const now = Date.now();
|
|
266204
266258
|
const contentHash = createHash3("sha256").update(ep.content).digest("hex").slice(0, 16);
|
|
266205
266259
|
const modality = ep.modality ?? "text";
|
|
266206
|
-
const
|
|
266260
|
+
const rawImportance = ep.importance ?? autoImportance(ep.toolName ?? null, modality, ep.content);
|
|
266261
|
+
const importance = sanitizeImportance(rawImportance);
|
|
266207
266262
|
const decayClass = ep.decayClass ?? autoDecayClass(ep.toolName ?? null, modality, ep.content);
|
|
266208
266263
|
const existing = this.db.prepare("SELECT id FROM episodes WHERE content_hash = ? AND session_id = ? LIMIT 1").get(contentHash, ep.sessionId ?? null);
|
|
266209
266264
|
if (existing)
|
|
@@ -266335,9 +266390,12 @@ var init_episodeStore = __esm({
|
|
|
266335
266390
|
contentHash: row.content_hash,
|
|
266336
266391
|
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
266337
266392
|
embedding: row.embedding ? new Float32Array(new Uint8Array(row.embedding).buffer) : null,
|
|
266338
|
-
|
|
266393
|
+
// Belt-and-braces: rows from pre-fix DBs may have null importance
|
|
266394
|
+
// (from legacy inserts with NaN that SQLite coerced to NULL).
|
|
266395
|
+
// Re-sanitize on read so downstream code always sees a valid number.
|
|
266396
|
+
importance: sanitizeImportance(row.importance),
|
|
266339
266397
|
decayClass: row.decay_class,
|
|
266340
|
-
strength: row.strength,
|
|
266398
|
+
strength: typeof row.strength === "number" && Number.isFinite(row.strength) ? row.strength : 1,
|
|
266341
266399
|
lastRetrieved: row.last_retrieved,
|
|
266342
266400
|
gist: row.gist,
|
|
266343
266401
|
sourceEpisodeId: row.source_episode_id
|
|
@@ -269441,7 +269499,7 @@ ${sr.result.output}`;
|
|
|
269441
269499
|
messages2.push(this.buildToolMessage(output, matchTc.id));
|
|
269442
269500
|
if (matchTc.name === "task_complete") {
|
|
269443
269501
|
completed = true;
|
|
269444
|
-
summary = matchTc.arguments
|
|
269502
|
+
summary = extractTaskCompleteSummary(matchTc.arguments);
|
|
269445
269503
|
if (summary && !this._assistantTextEmitted) {
|
|
269446
269504
|
this.emit({ type: "assistant_text", content: summary, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
269447
269505
|
this._assistantTextEmitted = true;
|
|
@@ -269460,7 +269518,7 @@ ${sr.result.output}`;
|
|
|
269460
269518
|
messages2.push(this.buildToolMessage(r2.output, r2.tc.id));
|
|
269461
269519
|
if (r2.tc.name === "task_complete") {
|
|
269462
269520
|
completed = true;
|
|
269463
|
-
summary = r2.tc.arguments
|
|
269521
|
+
summary = extractTaskCompleteSummary(r2.tc.arguments);
|
|
269464
269522
|
if (summary && !this._assistantTextEmitted) {
|
|
269465
269523
|
this.emit({ type: "assistant_text", content: summary, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
269466
269524
|
this._assistantTextEmitted = true;
|
|
@@ -269489,7 +269547,7 @@ ${sr.result.output}`;
|
|
|
269489
269547
|
messages2.push(this.buildToolMessage(r2.output, r2.tc.id));
|
|
269490
269548
|
if (r2.tc.name === "task_complete") {
|
|
269491
269549
|
completed = true;
|
|
269492
|
-
summary = r2.tc.arguments
|
|
269550
|
+
summary = extractTaskCompleteSummary(r2.tc.arguments);
|
|
269493
269551
|
if (summary && !this._assistantTextEmitted) {
|
|
269494
269552
|
this.emit({ type: "assistant_text", content: summary, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
269495
269553
|
this._assistantTextEmitted = true;
|
|
@@ -269889,7 +269947,7 @@ Full content available via: repl_exec(code="data = retrieve('${handleId}')") or
|
|
|
269889
269947
|
messages2.push(toolMsg);
|
|
269890
269948
|
if (tc.name === "task_complete") {
|
|
269891
269949
|
completed = true;
|
|
269892
|
-
summary = tc.arguments
|
|
269950
|
+
summary = extractTaskCompleteSummary(tc.arguments);
|
|
269893
269951
|
if (summary && !this._assistantTextEmitted) {
|
|
269894
269952
|
this.emit({ type: "assistant_text", content: summary, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
269895
269953
|
this._assistantTextEmitted = true;
|
|
@@ -274045,6 +274103,7 @@ var init_coordinator = __esm({
|
|
|
274045
274103
|
"use strict";
|
|
274046
274104
|
init_agent_types();
|
|
274047
274105
|
init_agent_task();
|
|
274106
|
+
init_textSanitize();
|
|
274048
274107
|
}
|
|
274049
274108
|
});
|
|
274050
274109
|
|
|
@@ -307892,6 +307951,7 @@ var init_dmn_engine = __esm({
|
|
|
307892
307951
|
"packages/cli/src/tui/dmn-engine.ts"() {
|
|
307893
307952
|
"use strict";
|
|
307894
307953
|
init_dist8();
|
|
307954
|
+
init_dist8();
|
|
307895
307955
|
init_dist4();
|
|
307896
307956
|
init_project_context();
|
|
307897
307957
|
init_render();
|
|
@@ -307951,7 +308011,9 @@ var init_dmn_engine = __esm({
|
|
|
307951
308011
|
}
|
|
307952
308012
|
/** Record a task failure for Reflexion-style learning */
|
|
307953
308013
|
recordTaskFailure(summary, errorContext, category) {
|
|
307954
|
-
const
|
|
308014
|
+
const cleanSummary = cleanForStorage(summary);
|
|
308015
|
+
const cleanError = errorContext ? cleanForStorage(errorContext) : "";
|
|
308016
|
+
const reflection = cleanError ? `FAILED: ${cleanSummary.slice(0, 200)} \u2014 Error: ${cleanError.slice(0, 300)}` : `FAILED: ${cleanSummary.slice(0, 500)}`;
|
|
307955
308017
|
this.state.reflectionBuffer.push(reflection);
|
|
307956
308018
|
if (this.state.reflectionBuffer.length > 5) {
|
|
307957
308019
|
this.state.reflectionBuffer.shift();
|
|
@@ -315295,7 +315357,8 @@ function createSubAgentTool(config, repoRoot, ctxWindowSize) {
|
|
|
315295
315357
|
return { success: false, output: "", error: "task is required" };
|
|
315296
315358
|
}
|
|
315297
315359
|
const agentId = `sub-agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
315298
|
-
const
|
|
315360
|
+
const cleanedLabelTask = cleanForStorage(task) || task;
|
|
315361
|
+
const agentLabel = `sub_agent: ${cleanedLabelTask.slice(0, 40)}${cleanedLabelTask.length > 40 ? "..." : ""}`;
|
|
315299
315362
|
if (onViewRegister) {
|
|
315300
315363
|
onViewRegister(agentId, agentLabel);
|
|
315301
315364
|
}
|
|
@@ -315355,7 +315418,7 @@ function createSubAgentTool(config, repoRoot, ctxWindowSize) {
|
|
|
315355
315418
|
if (onComplete) onComplete(agentId, task, result2.completed ? 0 : 1, output2);
|
|
315356
315419
|
return output2;
|
|
315357
315420
|
});
|
|
315358
|
-
const taskId = taskManager.trackPromise(`sub_agent: ${
|
|
315421
|
+
const taskId = taskManager.trackPromise(`sub_agent: ${cleanedLabelTask.slice(0, 80)}`, promise);
|
|
315359
315422
|
return {
|
|
315360
315423
|
success: true,
|
|
315361
315424
|
output: `Sub-agent started in background: ${taskId}
|
|
@@ -320982,7 +321045,8 @@ function statusCommand(jobId, repoPath) {
|
|
|
320982
321045
|
const runtime = job.completedAt ? `${((new Date(job.completedAt).getTime() - new Date(job.startedAt).getTime()) / 1e3).toFixed(0)}s` : `${((Date.now() - new Date(job.startedAt).getTime()) / 1e3).toFixed(0)}s`;
|
|
320983
321046
|
const icon = job.status === "completed" ? "\u2713" : job.status === "failed" ? "\u2717" : "\u25CF";
|
|
320984
321047
|
console.log(`${icon} ${job.id} [${job.status}] ${runtime}`);
|
|
320985
|
-
|
|
321048
|
+
const cleanTask = cleanForStorage(job.task) || job.task;
|
|
321049
|
+
console.log(` Task: ${cleanTask.slice(0, 80)}`);
|
|
320986
321050
|
console.log(` PID: ${job.pid}`);
|
|
320987
321051
|
if (job.summary) console.log(` Summary: ${job.summary}`);
|
|
320988
321052
|
if (job.error) console.log(` Error: ${job.error}`);
|
|
@@ -321001,7 +321065,8 @@ function jobsCommand(repoPath) {
|
|
|
321001
321065
|
const job = JSON.parse(readFileSync61(join95(dir, file), "utf-8"));
|
|
321002
321066
|
const icon = job.status === "completed" ? "\u2713" : job.status === "failed" ? "\u2717" : "\u25CF";
|
|
321003
321067
|
const runtime = job.completedAt ? `${((new Date(job.completedAt).getTime() - new Date(job.startedAt).getTime()) / 1e3).toFixed(0)}s` : `${((Date.now() - new Date(job.startedAt).getTime()) / 1e3).toFixed(0)}s`;
|
|
321004
|
-
|
|
321068
|
+
const cleanListTask = cleanForStorage(job.task) || job.task;
|
|
321069
|
+
console.log(` ${icon} ${job.id} [${job.status}] ${runtime} \u2014 ${cleanListTask.slice(0, 60)}`);
|
|
321005
321070
|
} catch {
|
|
321006
321071
|
}
|
|
321007
321072
|
}
|
|
@@ -321010,6 +321075,7 @@ var init_run = __esm({
|
|
|
321010
321075
|
"packages/cli/src/commands/run.ts"() {
|
|
321011
321076
|
"use strict";
|
|
321012
321077
|
init_interactive();
|
|
321078
|
+
init_dist8();
|
|
321013
321079
|
}
|
|
321014
321080
|
});
|
|
321015
321081
|
|
|
@@ -321728,6 +321794,17 @@ async function serveCommand(opts, config) {
|
|
|
321728
321794
|
printInfo(`Starting API server on port ${port}...`);
|
|
321729
321795
|
printInfo(`Backend: ${config.backendType} (${ollamaUrl})`);
|
|
321730
321796
|
}
|
|
321797
|
+
if (isDaemon) {
|
|
321798
|
+
try {
|
|
321799
|
+
const resp = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
321800
|
+
signal: AbortSignal.timeout(1500)
|
|
321801
|
+
});
|
|
321802
|
+
if (resp.ok) {
|
|
321803
|
+
return;
|
|
321804
|
+
}
|
|
321805
|
+
} catch {
|
|
321806
|
+
}
|
|
321807
|
+
}
|
|
321731
321808
|
try {
|
|
321732
321809
|
const server = startApiServer({ port, ollamaUrl, quiet: isQuiet });
|
|
321733
321810
|
if (isDaemon) {
|
|
@@ -321755,6 +321832,9 @@ async function serveCommand(opts, config) {
|
|
|
321755
321832
|
} catch (err) {
|
|
321756
321833
|
const msg = err instanceof Error ? err.message : String(err);
|
|
321757
321834
|
if (msg.includes("EADDRINUSE")) {
|
|
321835
|
+
if (isDaemon) {
|
|
321836
|
+
return;
|
|
321837
|
+
}
|
|
321758
321838
|
if (!isQuiet) {
|
|
321759
321839
|
printError(`Port ${port} already in use. Another OA instance may be running.`);
|
|
321760
321840
|
printInfo(`Try: oa serve --port ${port + 1}`);
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/**
|
|
4
|
+
* postinstall-daemon.cjs — system-service installer for the OA API daemon.
|
|
5
|
+
*
|
|
6
|
+
* Runs after `npm install -g open-agents-ai`. Responsibilities:
|
|
7
|
+
* 1. Clean up stale nexus daemon files (preserving prior postinstall behaviour).
|
|
8
|
+
* 2. Register a per-user system service that runs `oa serve --daemon`
|
|
9
|
+
* on port 11435, surviving reboots/logins.
|
|
10
|
+
* Linux → ~/.config/systemd/user/open-agents-daemon.service
|
|
11
|
+
* macOS → ~/Library/LaunchAgents/ai.open-agents.daemon.plist
|
|
12
|
+
* Windows → Scheduled Task "OpenAgentsDaemon" (onlogon)
|
|
13
|
+
* 3. Start (or restart) the service so the daemon is live IMMEDIATELY —
|
|
14
|
+
* no need to launch `oa` in any terminal.
|
|
15
|
+
* 4. Never exit non-zero. npm install must not fail because of a service
|
|
16
|
+
* manager quirk or a permission quirk. We warn and move on.
|
|
17
|
+
*
|
|
18
|
+
* Opt-out: set env OA_SKIP_DAEMON_INSTALL=1 before `npm i -g`.
|
|
19
|
+
*
|
|
20
|
+
* ES5-safe pure CommonJS — runs on any Node version that survived preinstall.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
"use strict";
|
|
24
|
+
|
|
25
|
+
var os = require("os");
|
|
26
|
+
var path = require("path");
|
|
27
|
+
var fs = require("fs");
|
|
28
|
+
var cp = require("child_process");
|
|
29
|
+
|
|
30
|
+
var HOME = os.homedir();
|
|
31
|
+
var PLATFORM = os.platform(); // "linux", "darwin", "win32"
|
|
32
|
+
var IS_WIN = PLATFORM === "win32";
|
|
33
|
+
var IS_MAC = PLATFORM === "darwin";
|
|
34
|
+
var IS_LINUX = PLATFORM === "linux";
|
|
35
|
+
|
|
36
|
+
var PORT = parseInt(process.env.OA_PORT || "11435", 10);
|
|
37
|
+
var SERVICE_LABEL = "open-agents-daemon";
|
|
38
|
+
var LAUNCHD_LABEL = "ai.open-agents.daemon";
|
|
39
|
+
var WIN_TASK_NAME = "OpenAgentsDaemon";
|
|
40
|
+
|
|
41
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
function log(msg) {
|
|
44
|
+
// Use stdout so npm captures it in the install log.
|
|
45
|
+
process.stdout.write(" " + msg + "\n");
|
|
46
|
+
}
|
|
47
|
+
function warn(msg) {
|
|
48
|
+
process.stdout.write(" [daemon] " + msg + "\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function runQuiet(cmd, opts) {
|
|
52
|
+
try {
|
|
53
|
+
cp.execSync(cmd, Object.assign({ stdio: "pipe", timeout: 15000 }, opts || {}));
|
|
54
|
+
return true;
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function runCapture(cmd, opts) {
|
|
61
|
+
try {
|
|
62
|
+
return cp.execSync(cmd, Object.assign({ stdio: ["ignore", "pipe", "ignore"], encoding: "utf8", timeout: 15000 }, opts || {})).trim();
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hasCmd(name) {
|
|
69
|
+
if (IS_WIN) return runQuiet("where " + name);
|
|
70
|
+
return runQuiet("which " + name);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Pre-check: is the port already serving an OA daemon?
|
|
74
|
+
function tryHealth(port, cb) {
|
|
75
|
+
var http = require("http");
|
|
76
|
+
var req = http.request(
|
|
77
|
+
{ host: "127.0.0.1", port: port, path: "/health", method: "GET", timeout: 1500 },
|
|
78
|
+
function (res) {
|
|
79
|
+
cb(res.statusCode === 200);
|
|
80
|
+
res.resume();
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
req.on("error", function () { cb(false); });
|
|
84
|
+
req.on("timeout", function () { req.destroy(); cb(false); });
|
|
85
|
+
req.end();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Resolve the `oa` launcher script. On npm global install:
|
|
89
|
+
// Linux/macOS: $(npm prefix -g)/bin/oa
|
|
90
|
+
// Windows: %APPDATA%/npm/oa.cmd
|
|
91
|
+
function resolveOaBinary() {
|
|
92
|
+
// 1. If npm set npm_config_prefix, use it.
|
|
93
|
+
var prefix = process.env.npm_config_prefix || "";
|
|
94
|
+
if (!prefix) {
|
|
95
|
+
// 2. Ask npm.
|
|
96
|
+
prefix = runCapture("npm prefix -g");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
var candidates = [];
|
|
100
|
+
if (prefix) {
|
|
101
|
+
if (IS_WIN) {
|
|
102
|
+
candidates.push(path.join(prefix, "oa.cmd"));
|
|
103
|
+
candidates.push(path.join(prefix, "oa"));
|
|
104
|
+
} else {
|
|
105
|
+
candidates.push(path.join(prefix, "bin", "oa"));
|
|
106
|
+
candidates.push(path.join(prefix, "bin", "open-agents"));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 3. PATH fallback via which/where.
|
|
110
|
+
var found = runCapture(IS_WIN ? "where oa" : "which oa").split(/\r?\n/)[0];
|
|
111
|
+
if (found) candidates.push(found);
|
|
112
|
+
|
|
113
|
+
// 4. Relative to this script: <pkg>/dist/launcher.cjs
|
|
114
|
+
try {
|
|
115
|
+
candidates.push(path.resolve(__dirname, "launcher.cjs"));
|
|
116
|
+
} catch (e) {}
|
|
117
|
+
|
|
118
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
119
|
+
try {
|
|
120
|
+
if (candidates[i] && fs.existsSync(candidates[i])) return candidates[i];
|
|
121
|
+
} catch (e) {}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Find a Node binary we can use in the service ExecStart.
|
|
127
|
+
// On nvm systems this is important — /usr/bin/node is often too old.
|
|
128
|
+
function resolveNodeBinary() {
|
|
129
|
+
try {
|
|
130
|
+
if (process.execPath && fs.existsSync(process.execPath)) return process.execPath;
|
|
131
|
+
} catch (e) {}
|
|
132
|
+
var found = runCapture(IS_WIN ? "where node" : "which node").split(/\r?\n/)[0];
|
|
133
|
+
if (found) return found;
|
|
134
|
+
return "node";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Effective user for service ownership. Handle `sudo npm i -g` by preferring
|
|
138
|
+
// SUDO_USER when running as root on Linux/macOS.
|
|
139
|
+
function effectiveUser() {
|
|
140
|
+
if (!IS_WIN && process.getuid && process.getuid() === 0) {
|
|
141
|
+
var sudoUser = process.env.SUDO_USER;
|
|
142
|
+
if (sudoUser) return sudoUser;
|
|
143
|
+
return null; // Running as bare root — skip per-user service install
|
|
144
|
+
}
|
|
145
|
+
return process.env.USER || process.env.LOGNAME || os.userInfo().username;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Nexus cleanup (preserve prior postinstall behaviour) ──────────────────
|
|
149
|
+
|
|
150
|
+
function cleanNexus() {
|
|
151
|
+
try {
|
|
152
|
+
var dirs = [
|
|
153
|
+
path.join(HOME, ".open-agents", ".oa", "nexus"),
|
|
154
|
+
path.join(process.cwd(), ".oa", "nexus"),
|
|
155
|
+
];
|
|
156
|
+
dirs.forEach(function (d) {
|
|
157
|
+
var stale = path.join(d, "nexus-daemon.mjs");
|
|
158
|
+
try {
|
|
159
|
+
if (fs.existsSync(stale)) {
|
|
160
|
+
fs.unlinkSync(stale);
|
|
161
|
+
log("Cleaned stale nexus daemon: " + stale);
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {}
|
|
164
|
+
var pidFile = path.join(d, "daemon.pid");
|
|
165
|
+
try {
|
|
166
|
+
if (fs.existsSync(pidFile)) {
|
|
167
|
+
var n = parseInt(fs.readFileSync(pidFile, "utf8"), 10);
|
|
168
|
+
if (n > 0) {
|
|
169
|
+
try { process.kill(n, "SIGTERM"); } catch (e) {}
|
|
170
|
+
}
|
|
171
|
+
fs.unlinkSync(pidFile);
|
|
172
|
+
log("Killed old nexus daemon: PID " + n);
|
|
173
|
+
}
|
|
174
|
+
} catch (e) {}
|
|
175
|
+
});
|
|
176
|
+
} catch (e) {}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── Linux: systemd user unit ───────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
function installSystemd(nodeBin, oaScript, user) {
|
|
182
|
+
if (!IS_LINUX) return false;
|
|
183
|
+
if (!hasCmd("systemctl")) {
|
|
184
|
+
warn("systemctl not found — skipping systemd install.");
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
var userHome = user === process.env.USER ? HOME : path.join("/home", user);
|
|
189
|
+
try {
|
|
190
|
+
var pwdEntry = runCapture("getent passwd " + user);
|
|
191
|
+
if (pwdEntry) {
|
|
192
|
+
var parts = pwdEntry.split(":");
|
|
193
|
+
if (parts[5]) userHome = parts[5];
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {}
|
|
196
|
+
|
|
197
|
+
var unitDir = path.join(userHome, ".config", "systemd", "user");
|
|
198
|
+
var unitPath = path.join(unitDir, SERVICE_LABEL + ".service");
|
|
199
|
+
var logDir = path.join(userHome, ".open-agents");
|
|
200
|
+
|
|
201
|
+
try { fs.mkdirSync(unitDir, { recursive: true }); } catch (e) {}
|
|
202
|
+
try { fs.mkdirSync(logDir, { recursive: true }); } catch (e) {}
|
|
203
|
+
|
|
204
|
+
// The unit runs: <node> <oaScript> serve --daemon --quiet
|
|
205
|
+
// with OA_DAEMON=1 so the serve command picks the daemon path.
|
|
206
|
+
var unit = [
|
|
207
|
+
"[Unit]",
|
|
208
|
+
"Description=Open Agents API Daemon (port " + PORT + ")",
|
|
209
|
+
"Documentation=https://github.com/robit-man/open-agents",
|
|
210
|
+
"After=network-online.target",
|
|
211
|
+
"Wants=network-online.target",
|
|
212
|
+
"",
|
|
213
|
+
"[Service]",
|
|
214
|
+
"Type=simple",
|
|
215
|
+
"Environment=OA_DAEMON=1",
|
|
216
|
+
"Environment=OA_PORT=" + PORT,
|
|
217
|
+
"Environment=NODE_ENV=production",
|
|
218
|
+
"ExecStart=" + nodeBin + " " + oaScript + " serve --daemon --quiet",
|
|
219
|
+
"Restart=on-failure",
|
|
220
|
+
"RestartSec=3",
|
|
221
|
+
"StandardOutput=append:" + path.join(logDir, "daemon.log"),
|
|
222
|
+
"StandardError=append:" + path.join(logDir, "daemon.err.log"),
|
|
223
|
+
"",
|
|
224
|
+
"[Install]",
|
|
225
|
+
"WantedBy=default.target",
|
|
226
|
+
"",
|
|
227
|
+
].join("\n");
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
fs.writeFileSync(unitPath, unit, { encoding: "utf8" });
|
|
231
|
+
log("Wrote systemd user unit: " + unitPath);
|
|
232
|
+
} catch (e) {
|
|
233
|
+
warn("Failed to write systemd unit: " + (e && e.message));
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// If we're root installing for another user, we need to wrap systemctl
|
|
238
|
+
// calls in `sudo -u USER XDG_RUNTIME_DIR=/run/user/UID systemctl --user`.
|
|
239
|
+
var asUserPrefix = "";
|
|
240
|
+
if (!IS_WIN && process.getuid && process.getuid() === 0 && user !== "root") {
|
|
241
|
+
// Try to resolve UID for the target user.
|
|
242
|
+
var uid = runCapture("id -u " + user);
|
|
243
|
+
if (uid && /^\d+$/.test(uid)) {
|
|
244
|
+
// Ensure loginctl linger so user services can run without a session.
|
|
245
|
+
runQuiet("loginctl enable-linger " + user);
|
|
246
|
+
asUserPrefix = "sudo -u " + user + " XDG_RUNTIME_DIR=/run/user/" + uid + " DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/" + uid + "/bus ";
|
|
247
|
+
}
|
|
248
|
+
} else if (!IS_WIN && process.getuid && process.getuid() !== 0) {
|
|
249
|
+
// Self-install: enable-linger ourselves if we can (best-effort; needs sudo on most distros).
|
|
250
|
+
runQuiet("loginctl enable-linger " + user);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
var sysctl = asUserPrefix + "systemctl --user";
|
|
254
|
+
runQuiet(sysctl + " daemon-reload");
|
|
255
|
+
var enabled = runQuiet(sysctl + " enable " + SERVICE_LABEL + ".service");
|
|
256
|
+
var restarted = runQuiet(sysctl + " restart " + SERVICE_LABEL + ".service");
|
|
257
|
+
|
|
258
|
+
if (enabled) log("Enabled systemd unit: " + SERVICE_LABEL);
|
|
259
|
+
if (restarted) log("Started systemd unit: " + SERVICE_LABEL);
|
|
260
|
+
if (!enabled || !restarted) {
|
|
261
|
+
warn("Could not enable/start the unit via systemctl --user. The file is in place; run manually:");
|
|
262
|
+
warn(" systemctl --user daemon-reload");
|
|
263
|
+
warn(" systemctl --user enable --now " + SERVICE_LABEL + ".service");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─── macOS: launchd user agent ──────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
function installLaunchd(nodeBin, oaScript, user) {
|
|
272
|
+
if (!IS_MAC) return false;
|
|
273
|
+
|
|
274
|
+
var userHome = HOME;
|
|
275
|
+
if (user && user !== os.userInfo().username) {
|
|
276
|
+
try {
|
|
277
|
+
var home = runCapture("dscl . -read /Users/" + user + " NFSHomeDirectory").split(" ").pop();
|
|
278
|
+
if (home) userHome = home;
|
|
279
|
+
} catch (e) {}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
var agentDir = path.join(userHome, "Library", "LaunchAgents");
|
|
283
|
+
var plistPath = path.join(agentDir, LAUNCHD_LABEL + ".plist");
|
|
284
|
+
var logDir = path.join(userHome, ".open-agents");
|
|
285
|
+
|
|
286
|
+
try { fs.mkdirSync(agentDir, { recursive: true }); } catch (e) {}
|
|
287
|
+
try { fs.mkdirSync(logDir, { recursive: true }); } catch (e) {}
|
|
288
|
+
|
|
289
|
+
var plist = [
|
|
290
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
291
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
292
|
+
'<plist version="1.0">',
|
|
293
|
+
'<dict>',
|
|
294
|
+
' <key>Label</key><string>' + LAUNCHD_LABEL + '</string>',
|
|
295
|
+
' <key>ProgramArguments</key>',
|
|
296
|
+
' <array>',
|
|
297
|
+
' <string>' + nodeBin + '</string>',
|
|
298
|
+
' <string>' + oaScript + '</string>',
|
|
299
|
+
' <string>serve</string>',
|
|
300
|
+
' <string>--daemon</string>',
|
|
301
|
+
' <string>--quiet</string>',
|
|
302
|
+
' </array>',
|
|
303
|
+
' <key>EnvironmentVariables</key>',
|
|
304
|
+
' <dict>',
|
|
305
|
+
' <key>OA_DAEMON</key><string>1</string>',
|
|
306
|
+
' <key>OA_PORT</key><string>' + PORT + '</string>',
|
|
307
|
+
' <key>PATH</key><string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>',
|
|
308
|
+
' </dict>',
|
|
309
|
+
' <key>RunAtLoad</key><true/>',
|
|
310
|
+
' <key>KeepAlive</key><true/>',
|
|
311
|
+
' <key>StandardOutPath</key><string>' + path.join(logDir, "daemon.log") + '</string>',
|
|
312
|
+
' <key>StandardErrorPath</key><string>' + path.join(logDir, "daemon.err.log") + '</string>',
|
|
313
|
+
'</dict>',
|
|
314
|
+
'</plist>',
|
|
315
|
+
'',
|
|
316
|
+
].join("\n");
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
fs.writeFileSync(plistPath, plist, { encoding: "utf8" });
|
|
320
|
+
log("Wrote launchd plist: " + plistPath);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
warn("Failed to write launchd plist: " + (e && e.message));
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Unload then load (idempotent). bootout/bootstrap prefer gui/<uid> on newer macOS.
|
|
327
|
+
var uid = runCapture("id -u " + user);
|
|
328
|
+
if (uid && /^\d+$/.test(uid)) {
|
|
329
|
+
runQuiet("launchctl bootout gui/" + uid + "/" + LAUNCHD_LABEL);
|
|
330
|
+
var booted = runQuiet("launchctl bootstrap gui/" + uid + " " + plistPath);
|
|
331
|
+
if (booted) log("Loaded launchd agent: " + LAUNCHD_LABEL);
|
|
332
|
+
runQuiet("launchctl kickstart -k gui/" + uid + "/" + LAUNCHD_LABEL);
|
|
333
|
+
} else {
|
|
334
|
+
runQuiet("launchctl unload " + plistPath);
|
|
335
|
+
runQuiet("launchctl load -w " + plistPath);
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ─── Windows: Scheduled Task ────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
function installWindows(nodeBin, oaScript) {
|
|
343
|
+
if (!IS_WIN) return false;
|
|
344
|
+
if (!hasCmd("schtasks")) {
|
|
345
|
+
warn("schtasks not found — skipping Windows scheduled task install.");
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Build the task command — quote the paths.
|
|
350
|
+
var trCmd = '"' + nodeBin + '" "' + oaScript + '" serve --daemon --quiet';
|
|
351
|
+
|
|
352
|
+
// Delete any existing task first (idempotent).
|
|
353
|
+
runQuiet('schtasks /Delete /TN "' + WIN_TASK_NAME + '" /F');
|
|
354
|
+
|
|
355
|
+
var created = runQuiet(
|
|
356
|
+
'schtasks /Create /F /SC ONLOGON /RL HIGHEST /TN "' + WIN_TASK_NAME + '" /TR ' + JSON.stringify(trCmd)
|
|
357
|
+
);
|
|
358
|
+
if (!created) {
|
|
359
|
+
warn("Failed to create scheduled task. Try running `oa daemon install` from an elevated shell.");
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
log("Created scheduled task: " + WIN_TASK_NAME);
|
|
363
|
+
// Start it immediately.
|
|
364
|
+
runQuiet('schtasks /Run /TN "' + WIN_TASK_NAME + '"');
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ─── Fallback: spawn detached now (no persistence) ─────────────────────────
|
|
369
|
+
|
|
370
|
+
function spawnDetached(nodeBin, oaScript) {
|
|
371
|
+
try {
|
|
372
|
+
var child = cp.spawn(nodeBin, [oaScript, "serve", "--daemon", "--quiet"], {
|
|
373
|
+
detached: true,
|
|
374
|
+
stdio: "ignore",
|
|
375
|
+
env: Object.assign({}, process.env, { OA_DAEMON: "1", OA_PORT: String(PORT) }),
|
|
376
|
+
});
|
|
377
|
+
child.unref();
|
|
378
|
+
if (child.pid) {
|
|
379
|
+
log("Spawned detached daemon (PID " + child.pid + "). It will NOT survive reboot — install a service manager to persist.");
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
} catch (e) {
|
|
383
|
+
warn("Failed to spawn detached daemon: " + (e && e.message));
|
|
384
|
+
}
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ─── Wait for /health ───────────────────────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
function waitForHealth(timeoutMs, cb) {
|
|
391
|
+
var start = Date.now();
|
|
392
|
+
function tick() {
|
|
393
|
+
tryHealth(PORT, function (ok) {
|
|
394
|
+
if (ok) return cb(true);
|
|
395
|
+
if (Date.now() - start > timeoutMs) return cb(false);
|
|
396
|
+
setTimeout(tick, 500);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
tick();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
function main() {
|
|
405
|
+
// Always do the nexus cleanup first, regardless of opt-out.
|
|
406
|
+
cleanNexus();
|
|
407
|
+
|
|
408
|
+
if (process.env.OA_SKIP_DAEMON_INSTALL === "1") {
|
|
409
|
+
log("OA_SKIP_DAEMON_INSTALL=1 — skipping daemon service install.");
|
|
410
|
+
return safeExit(0);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Fast path: daemon already answering /health on the target port.
|
|
414
|
+
tryHealth(PORT, function (alreadyUp) {
|
|
415
|
+
var user = effectiveUser();
|
|
416
|
+
var nodeBin = resolveNodeBinary();
|
|
417
|
+
var oaScript = resolveOaBinary();
|
|
418
|
+
|
|
419
|
+
if (!oaScript) {
|
|
420
|
+
warn("Could not resolve `oa` launcher — daemon install skipped. After install completes, run `oa daemon install` manually.");
|
|
421
|
+
return safeExit(0);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (alreadyUp) {
|
|
425
|
+
log("OA daemon already responding on port " + PORT + " — reinstalling service definition so it picks up the new build.");
|
|
426
|
+
// Fall through to (re)install service files; the serve command's
|
|
427
|
+
// daemon-mode /health pre-check will no-op if the existing daemon
|
|
428
|
+
// is still healthy.
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
log("Installing OA API daemon service for port " + PORT + " ...");
|
|
432
|
+
log(" node: " + nodeBin);
|
|
433
|
+
log(" oa script: " + oaScript);
|
|
434
|
+
log(" user: " + (user || "(unknown)"));
|
|
435
|
+
|
|
436
|
+
if (!user) {
|
|
437
|
+
warn("Running as bare root with no SUDO_USER — skipping per-user service install.");
|
|
438
|
+
warn("Re-run as your user, or run: OA_SKIP_DAEMON_INSTALL=0 npm i -g open-agents-ai");
|
|
439
|
+
// Still spawn detached so the port is live right now.
|
|
440
|
+
spawnDetached(nodeBin, oaScript);
|
|
441
|
+
return safeExit(0);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
var ok = false;
|
|
445
|
+
if (IS_LINUX) {
|
|
446
|
+
ok = installSystemd(nodeBin, oaScript, user);
|
|
447
|
+
} else if (IS_MAC) {
|
|
448
|
+
ok = installLaunchd(nodeBin, oaScript, user);
|
|
449
|
+
} else if (IS_WIN) {
|
|
450
|
+
ok = installWindows(nodeBin, oaScript);
|
|
451
|
+
} else {
|
|
452
|
+
warn("Unsupported platform: " + PLATFORM + " — falling back to detached spawn.");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (!ok) {
|
|
456
|
+
// Fallback: spawn a detached child so at least the port is live NOW.
|
|
457
|
+
spawnDetached(nodeBin, oaScript);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Wait up to 15s for /health to come up, but don't fail npm install.
|
|
461
|
+
waitForHealth(15000, function (healthy) {
|
|
462
|
+
if (healthy) {
|
|
463
|
+
log("OA API daemon is live: http://127.0.0.1:" + PORT + "/health");
|
|
464
|
+
} else {
|
|
465
|
+
warn("OA API daemon did not answer /health within 15s. Check logs:");
|
|
466
|
+
if (IS_LINUX) warn(" systemctl --user status " + SERVICE_LABEL);
|
|
467
|
+
if (IS_MAC) warn(" launchctl print gui/$(id -u)/" + LAUNCHD_LABEL);
|
|
468
|
+
if (IS_WIN) warn(" schtasks /Query /TN " + WIN_TASK_NAME + " /V /FO LIST");
|
|
469
|
+
}
|
|
470
|
+
safeExit(0);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function safeExit(code) {
|
|
476
|
+
// Never exit non-zero from postinstall — it breaks `npm i -g`.
|
|
477
|
+
process.exit(typeof code === "number" ? 0 : 0);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Guard against any unhandled throw — postinstall must not crash npm.
|
|
481
|
+
try {
|
|
482
|
+
main();
|
|
483
|
+
} catch (e) {
|
|
484
|
+
warn("postinstall crashed (non-fatal): " + (e && e.message));
|
|
485
|
+
safeExit(0);
|
|
486
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-agents-ai",
|
|
3
|
-
"version": "0.187.
|
|
3
|
+
"version": "0.187.185",
|
|
4
4
|
"description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"license": "CC-BY-NC-4.0",
|
|
73
73
|
"scripts": {
|
|
74
74
|
"preinstall": "node dist/preinstall.cjs",
|
|
75
|
-
"postinstall": "node -
|
|
75
|
+
"postinstall": "node dist/postinstall-daemon.cjs"
|
|
76
76
|
},
|
|
77
77
|
"engines": {
|
|
78
78
|
"node": ">=22.0.0"
|