@mono-agent/agent-app 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { homedir } from "node:os";
4
+ import { basename, dirname, resolve } from "node:path";
5
+ import process from "node:process";
6
+ const LABEL_PREFIX = "com.mono-agent";
7
+ const MAX_FOLDER_SEGMENT = 40;
8
+ const FALLBACK_PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin";
9
+ const PATH_EXTRAS = ["/opt/homebrew/bin", "/usr/local/bin"];
10
+ /**
11
+ * A stable, launchd-legal label derived from the resolved config path. The
12
+ * folder name keeps it human-readable; an 8-char hash of the absolute config
13
+ * path disambiguates same-named folders and keeps the label deterministic so
14
+ * `start`/`stop`/`status` all address the same service.
15
+ */
16
+ export function deriveLaunchdLabel(configPath) {
17
+ const resolved = resolve(configPath);
18
+ const folder = basename(dirname(resolved))
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9-]+/gu, "-")
21
+ .replace(/-+/gu, "-")
22
+ .replace(/^-+|-+$/gu, "")
23
+ .slice(0, MAX_FOLDER_SEGMENT)
24
+ .replace(/-+$/gu, "");
25
+ const hash = createHash("sha256").update(resolved).digest("hex").slice(0, 8);
26
+ return `${LABEL_PREFIX}.${folder.length === 0 ? "agent" : folder}-${hash}`;
27
+ }
28
+ export function launchdPathsFor(label, home = homedir()) {
29
+ const launchAgentsDir = resolve(home, "Library", "LaunchAgents");
30
+ const logDir = resolve(home, ".mono-agent", "logs");
31
+ return {
32
+ launchAgentsDir,
33
+ logDir,
34
+ plistPath: resolve(launchAgentsDir, `${label}.plist`),
35
+ stdoutPath: resolve(logDir, `${label}.out.log`),
36
+ stderrPath: resolve(logDir, `${label}.err.log`),
37
+ };
38
+ }
39
+ export function escapeXml(value) {
40
+ return value
41
+ .replace(/&/gu, "&")
42
+ .replace(/</gu, "&lt;")
43
+ .replace(/>/gu, "&gt;")
44
+ .replace(/"/gu, "&quot;")
45
+ .replace(/'/gu, "&apos;");
46
+ }
47
+ /**
48
+ * The plist runs `node <cli.js> start --foreground …` (the existing blocking
49
+ * worker) in the config's directory. No secrets are written here — the worker
50
+ * loads `.env` from its working directory exactly like the foreground CLI.
51
+ */
52
+ export function buildPlistXml(input) {
53
+ const programArguments = [
54
+ input.nodePath,
55
+ input.cliPath,
56
+ "start",
57
+ "--foreground",
58
+ "--config",
59
+ input.configPath,
60
+ ...(input.envFile === undefined ? [] : ["--env-file", input.envFile]),
61
+ ...(input.port === undefined ? [] : ["--port", String(input.port)]),
62
+ ...(input.noConsole ? ["--no-console"] : []),
63
+ ];
64
+ const argsXml = programArguments.map((arg) => ` <string>${escapeXml(arg)}</string>`).join("\n");
65
+ return `<?xml version="1.0" encoding="UTF-8"?>
66
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
67
+ <plist version="1.0">
68
+ <dict>
69
+ <key>Label</key>
70
+ <string>${escapeXml(input.label)}</string>
71
+ <key>ProgramArguments</key>
72
+ <array>
73
+ ${argsXml}
74
+ </array>
75
+ <key>WorkingDirectory</key>
76
+ <string>${escapeXml(input.cwd)}</string>
77
+ <key>EnvironmentVariables</key>
78
+ <dict>
79
+ <key>PATH</key>
80
+ <string>${escapeXml(input.pathEnv)}</string>
81
+ </dict>
82
+ <key>RunAtLoad</key>
83
+ <true/>
84
+ <key>KeepAlive</key>
85
+ <dict>
86
+ <key>SuccessfulExit</key>
87
+ <false/>
88
+ </dict>
89
+ <key>StandardOutPath</key>
90
+ <string>${escapeXml(input.stdoutPath)}</string>
91
+ <key>StandardErrorPath</key>
92
+ <string>${escapeXml(input.stderrPath)}</string>
93
+ <key>ThrottleInterval</key>
94
+ <integer>10</integer>
95
+ <key>ProcessType</key>
96
+ <string>Interactive</string>
97
+ </dict>
98
+ </plist>
99
+ `;
100
+ }
101
+ /**
102
+ * PATH for the background worker: the launching shell's PATH (so the user's
103
+ * toolchain is reachable) plus Homebrew/usr-local in case launchd started from
104
+ * a bare login. Never includes anything secret.
105
+ */
106
+ export function defaultPathEnv(env = process.env) {
107
+ const current = env.PATH?.trim();
108
+ if (current === undefined || current.length === 0) {
109
+ return FALLBACK_PATH;
110
+ }
111
+ const parts = current.split(":").filter((part) => part.length > 0);
112
+ for (const extra of PATH_EXTRAS) {
113
+ if (!parts.includes(extra)) {
114
+ parts.push(extra);
115
+ }
116
+ }
117
+ return parts.join(":");
118
+ }
119
+ export function domainTarget(uid) {
120
+ return `gui/${uid}`;
121
+ }
122
+ export function serviceTarget(label, uid) {
123
+ return `gui/${uid}/${label}`;
124
+ }
125
+ export function makeLaunchctlRunner() {
126
+ return (args) => new Promise((resolvePromise) => {
127
+ const child = spawn("launchctl", [...args], { stdio: ["ignore", "pipe", "pipe"] });
128
+ let stdout = "";
129
+ let stderr = "";
130
+ child.stdout?.on("data", (chunk) => {
131
+ stdout += chunk.toString("utf8");
132
+ });
133
+ child.stderr?.on("data", (chunk) => {
134
+ stderr += chunk.toString("utf8");
135
+ });
136
+ child.on("error", (error) => {
137
+ resolvePromise({ code: 127, stdout, stderr: `${stderr}${error.message}` });
138
+ });
139
+ child.on("close", (code) => {
140
+ resolvePromise({ code: code ?? 0, stdout, stderr });
141
+ });
142
+ });
143
+ }
144
+ /** True when the service is bootstrapped in the user's gui domain. */
145
+ export async function isLoaded(runner, label, uid) {
146
+ const result = await runner(["print", serviceTarget(label, uid)]);
147
+ return result.code === 0;
148
+ }
149
+ /** Bootstrap (load + RunAtLoad-launch) the plist. Caller tolerates "already bootstrapped". */
150
+ export async function bootstrap(runner, plistPath, uid) {
151
+ return await runner(["bootstrap", domainTarget(uid), plistPath]);
152
+ }
153
+ /** Kill and restart an already-loaded service, picking up a rewritten plist. */
154
+ export async function kickstartRestart(runner, label, uid) {
155
+ return await runner(["kickstart", "-k", serviceTarget(label, uid)]);
156
+ }
157
+ /**
158
+ * Remove the service from the domain. Sends SIGTERM and — unlike a plain kill —
159
+ * prevents KeepAlive from relaunching it. Caller tolerates "not loaded".
160
+ */
161
+ export async function bootout(runner, label, uid) {
162
+ return await runner(["bootout", serviceTarget(label, uid)]);
163
+ }
164
+ //# sourceMappingURL=launchd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launchd.js","sourceRoot":"","sources":["../src/launchd.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mono-agent/agent-app",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Config-first mono-agent host: builds a responder and starts every configured communication channel, operator console, and traceability from one mono-agent.config.json.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -22,20 +22,20 @@
22
22
  "README.md"
23
23
  ],
24
24
  "dependencies": {
25
- "@mono-agent/a2a-adapter": "0.2.0",
26
- "@mono-agent/agent-contracts": "0.2.0",
27
- "@mono-agent/agent-host": "0.2.0",
28
- "@mono-agent/config": "0.2.0",
29
- "@mono-agent/cron-adapter": "0.2.0",
30
- "@mono-agent/observability": "0.2.0",
31
- "@mono-agent/openai-api-adapter": "0.2.0",
32
- "@mono-agent/operator-console": "0.2.0",
33
- "@mono-agent/runtime-adapter": "0.2.0",
34
- "@mono-agent/settings": "0.2.0",
35
- "@mono-agent/slack-adapter": "0.2.0",
36
- "@mono-agent/telegram-adapter": "0.2.0",
37
- "@mono-agent/webhook-adapter": "0.2.0",
38
- "@mono-agent/whatsapp-adapter": "0.2.0"
25
+ "@mono-agent/a2a-adapter": "0.2.2",
26
+ "@mono-agent/agent-contracts": "0.2.2",
27
+ "@mono-agent/agent-host": "0.2.2",
28
+ "@mono-agent/config": "0.2.2",
29
+ "@mono-agent/cron-adapter": "0.2.2",
30
+ "@mono-agent/observability": "0.2.2",
31
+ "@mono-agent/openai-api-adapter": "0.2.2",
32
+ "@mono-agent/operator-console": "0.2.2",
33
+ "@mono-agent/runtime-adapter": "0.2.2",
34
+ "@mono-agent/settings": "0.2.2",
35
+ "@mono-agent/slack-adapter": "0.2.2",
36
+ "@mono-agent/telegram-adapter": "0.2.2",
37
+ "@mono-agent/webhook-adapter": "0.2.2",
38
+ "@mono-agent/whatsapp-adapter": "0.2.2"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public"
@@ -32,7 +32,7 @@ my-agent/
32
32
  "effort": "medium", // none|low|medium|high|xhigh|max
33
33
  "permissionMode": "default", // default|plan|acceptEdits|bypassPermissions (CLI backends)
34
34
  "reasoningSummary": "auto", // auto|concise|detailed|off|on
35
- "maxTurns": 8, // 1-100
35
+ "maxTurns": 0, // 0 or omitted means unlimited; 1-100 caps turns
36
36
  "workspace": ".",
37
37
  "session": { "mode": "continuous", "idleTimeoutMs": 1800000 } // or "per-message"
38
38
  },
@@ -138,12 +138,14 @@ my-agent/
138
138
  },
139
139
 
140
140
  "telegram": {
141
+ "enabled": true, // opt-in; defaults to false (off → "disabled")
141
142
  "botToken": "...",
142
143
  "allowedChatIds": ["123456789"], // or "allowAllChats": true
143
144
  "allowAllChats": false
144
145
  },
145
146
 
146
147
  "slack": {
148
+ "enabled": true, // opt-in; defaults to false (off → "disabled")
147
149
  "botToken": "xoxb-...", // Socket Mode app
148
150
  "appToken": "xapp-...",
149
151
  "allowedChannelIds": ["C0123"], // or "allowAllChannels": true
@@ -154,6 +156,7 @@ my-agent/
154
156
  },
155
157
 
156
158
  "whatsapp": {
159
+ "enabled": true, // opt-in; defaults to false (off → "disabled")
157
160
  "allowedChatJids": ["123@s.whatsapp.net"], // or "allowAllChats": true
158
161
  "allowAllChats": false,
159
162
  "groupMode": "mention", // mention | any (group trigger rule)
@@ -187,6 +190,7 @@ my-agent/
187
190
  },
188
191
 
189
192
  "cron": {
193
+ "dir": "cron", // optional: folder of *.md jobs (frontmatter + prompt body), default "cron"
190
194
  "jobs": [
191
195
  {
192
196
  "id": "daily",
@@ -197,6 +201,7 @@ my-agent/
197
201
  "conversationId": "cron-daily" // optional: share memory/history across ticks
198
202
  }
199
203
  ]
204
+ // Jobs here merge with cron/*.md files (duplicate ids error).
200
205
  // Overlapping ticks of the same job are skipped, never queued.
201
206
  }
202
207
  }
@@ -26,7 +26,7 @@ Every framework capability and how a composed agent reaches it. Use this to answ
26
26
  | Identity + optional soul documents | config | `context.identityPath`, `context.soulPath` |
27
27
  | Selected skills from a skills root | config | `context.skillsRoot`, `context.selectedSkills` |
28
28
  | Per-skill byte cap | config | `context.skillMaxBytes` |
29
- | Conversation history (in-memory, capped) | auto | sized from `runtime.maxTurns`; custom store via code |
29
+ | Conversation history (in-memory; unlimited unless turns are capped) | auto | sized from `runtime.maxTurns`; custom store via code |
30
30
  | Markdown memory (single-file / per-conversation, capped) | config | `memory.mode: "markdown"`, `path`, `maxBytes`, `scope` |
31
31
  | Journal memory (daily notes always in context) | config | `memory.mode: "journal"`, `path` |
32
32
  | Host summaries appended after runs | config | `memory.writeMode: "append-host-summary"` |