agent-phonon 0.2.9 → 0.2.11

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/cli.js CHANGED
@@ -1725,12 +1725,12 @@ var init_rpc = __esm({
1725
1725
  requestRaw(method, params, timeoutMs = 12e4) {
1726
1726
  const id = this.nextId++;
1727
1727
  const msg = { jsonrpc: "2.0", id, method, params };
1728
- return new Promise((resolve5, reject) => {
1728
+ return new Promise((resolve6, reject) => {
1729
1729
  const timer = timeoutMs > 0 ? setTimeout(() => {
1730
1730
  this.pending.delete(id);
1731
1731
  reject(new PhononError("errInternal", `RPC timeout: ${method}`));
1732
1732
  }, timeoutMs) : void 0;
1733
- this.pending.set(id, { resolve: resolve5, reject, timer });
1733
+ this.pending.set(id, { resolve: resolve6, reject, timer });
1734
1734
  this.transport.send(JSON.stringify(msg));
1735
1735
  });
1736
1736
  }
@@ -2186,8 +2186,8 @@ var SessionEngine = class {
2186
2186
  this.interactionWaiters.delete(requestId);
2187
2187
  }
2188
2188
  }
2189
- registerInteractionWaiter(requestId, resolve5) {
2190
- this.interactionWaiters.set(requestId, resolve5);
2189
+ registerInteractionWaiter(requestId, resolve6) {
2190
+ this.interactionWaiters.set(requestId, resolve6);
2191
2191
  }
2192
2192
  list(tenantId, filter) {
2193
2193
  const all = [];
@@ -3762,7 +3762,7 @@ ${message}`;
3762
3762
  return void 0;
3763
3763
  }
3764
3764
  run(args2, signal, environment) {
3765
- return new Promise((resolve5, reject) => {
3765
+ return new Promise((resolve6, reject) => {
3766
3766
  const child = spawn2("openclaw", args2, { cwd: this.cwd, env: { ...process.env, ...environment ?? {} } });
3767
3767
  this.current = child;
3768
3768
  let out = "";
@@ -3780,9 +3780,9 @@ ${message}`;
3780
3780
  signal?.removeEventListener("abort", onAbort);
3781
3781
  this.current = void 0;
3782
3782
  if (killed)
3783
- return resolve5(null);
3783
+ return resolve6(null);
3784
3784
  if (code === 0)
3785
- resolve5(out);
3785
+ resolve6(out);
3786
3786
  else
3787
3787
  reject(new Error(`openclaw exited ${code}: ${err.slice(0, 500)}`));
3788
3788
  });
@@ -3834,12 +3834,12 @@ var OpenClawAdapter = class {
3834
3834
  return new OpenClawSession(params.sessionId, params.model, params.cwd, openclawAgent);
3835
3835
  }
3836
3836
  probeVersion() {
3837
- return new Promise((resolve5) => {
3837
+ return new Promise((resolve6) => {
3838
3838
  const child = spawn2("openclaw", ["--version"]);
3839
3839
  let out = "";
3840
3840
  child.stdout.on("data", (d) => out += d.toString());
3841
- child.on("error", () => resolve5(null));
3842
- child.on("close", (code) => resolve5(code === 0 ? out.trim() : null));
3841
+ child.on("error", () => resolve6(null));
3842
+ child.on("close", (code) => resolve6(code === 0 ? out.trim() : null));
3843
3843
  });
3844
3844
  }
3845
3845
  };
@@ -3872,7 +3872,7 @@ var GatewayClient = class {
3872
3872
  return this.connectPromise;
3873
3873
  }
3874
3874
  doConnect() {
3875
- return new Promise((resolve5, reject) => {
3875
+ return new Promise((resolve6, reject) => {
3876
3876
  const wsUrl = this.config.baseUrl.replace(/^http/, "ws");
3877
3877
  const ws = new WebSocket(wsUrl);
3878
3878
  this.ws = ws;
@@ -3903,7 +3903,7 @@ var GatewayClient = class {
3903
3903
  } else if (frame.type === "res" && frame.ok && frame.payload?.type === "hello-ok") {
3904
3904
  handshakeDone = true;
3905
3905
  this.connected = true;
3906
- resolve5();
3906
+ resolve6();
3907
3907
  } else if (frame.type === "res" && !frame.ok) {
3908
3908
  reject(new Error(`handshake failed: ${JSON.stringify(frame.error)}`));
3909
3909
  }
@@ -3949,13 +3949,13 @@ var GatewayClient = class {
3949
3949
  async rpc(method, params, timeoutMs = 12e4) {
3950
3950
  if (!this.connected)
3951
3951
  await this.connect();
3952
- return new Promise((resolve5, reject) => {
3952
+ return new Promise((resolve6, reject) => {
3953
3953
  const id = randomUUID();
3954
3954
  const timer = setTimeout(() => {
3955
3955
  this.pending.delete(id);
3956
3956
  reject(new Error(`RPC timeout: ${method}`));
3957
3957
  }, timeoutMs);
3958
- this.pending.set(id, { resolve: resolve5, reject, timer });
3958
+ this.pending.set(id, { resolve: resolve6, reject, timer });
3959
3959
  this.ws.send(JSON.stringify({ type: "req", id, method, params }));
3960
3960
  });
3961
3961
  }
@@ -4082,14 +4082,14 @@ var GatewaySession = class {
4082
4082
 
4083
4083
  ${input}`;
4084
4084
  }
4085
- await new Promise((resolve5) => {
4085
+ await new Promise((resolve6) => {
4086
4086
  let settled = false;
4087
4087
  const finish = () => {
4088
4088
  if (settled)
4089
4089
  return;
4090
4090
  settled = true;
4091
4091
  clearTimeout(guard);
4092
- resolve5();
4092
+ resolve6();
4093
4093
  };
4094
4094
  this.activeTurn = { turnId, emit, verbosity: opts.verbosity, done: finish, acc: "" };
4095
4095
  this.gw.rpc("chat.send", {
@@ -4350,7 +4350,7 @@ var PhononClient = class {
4350
4350
  }
4351
4351
  /** 连接并完成握手,resolve 后即可接收 server 的 session.* 下发。 */
4352
4352
  connect() {
4353
- return new Promise((resolve5, reject) => {
4353
+ return new Promise((resolve6, reject) => {
4354
4354
  const ws = new WebSocket2(this.serverUrl);
4355
4355
  this.ws = ws;
4356
4356
  const transport = {
@@ -4392,7 +4392,7 @@ var PhononClient = class {
4392
4392
  if (wAck && wAck.length > 0) {
4393
4393
  conn.replayPending(wAck.map((a) => ({ sessionId: a.sessionId, fromSeq: a.lastSeq })));
4394
4394
  }
4395
- resolve5({ tenantId: welcome.tenantId });
4395
+ resolve6({ tenantId: welcome.tenantId });
4396
4396
  } catch (err) {
4397
4397
  reject(err);
4398
4398
  }
@@ -4462,12 +4462,12 @@ var HookBridge = class {
4462
4462
  this.token = opts?.token;
4463
4463
  }
4464
4464
  listen(port = 4318, host = "127.0.0.1") {
4465
- return new Promise((resolve5) => {
4465
+ return new Promise((resolve6) => {
4466
4466
  const server = createServer((req, res) => this.onRequest(req, res));
4467
4467
  this.server = server;
4468
4468
  server.listen(port, host, () => {
4469
4469
  const addr = server.address();
4470
- resolve5(typeof addr === "object" && addr ? addr.port : port);
4470
+ resolve6(typeof addr === "object" && addr ? addr.port : port);
4471
4471
  });
4472
4472
  });
4473
4473
  }
@@ -4520,10 +4520,10 @@ var HookBridge = class {
4520
4520
  });
4521
4521
  }
4522
4522
  close() {
4523
- return new Promise((resolve5) => {
4523
+ return new Promise((resolve6) => {
4524
4524
  if (!this.server)
4525
- return resolve5();
4526
- this.server.close(() => resolve5());
4525
+ return resolve6();
4526
+ this.server.close(() => resolve6());
4527
4527
  });
4528
4528
  }
4529
4529
  };
@@ -4624,7 +4624,12 @@ function fileSize(p) {
4624
4624
  import { spawn as spawn3 } from "node:child_process";
4625
4625
  import { homedir as homedir5 } from "node:os";
4626
4626
  import { join as join7 } from "node:path";
4627
- import { existsSync as existsSync7, readdirSync, statSync as statSync2 } from "node:fs";
4627
+ import { existsSync as existsSync7, readdirSync, statSync as statSync2, readFileSync as readFileSync4 } from "node:fs";
4628
+ var CODEX_FALLBACK_MODELS = [
4629
+ { id: "gpt-5.5", available: true },
4630
+ { id: "gpt-5.4", available: true },
4631
+ { id: "gpt-5.3-codex", available: true }
4632
+ ];
4628
4633
  var CAPABILITIES2 = {
4629
4634
  nativeSession: true,
4630
4635
  // exec resume <thread_id>
@@ -4674,6 +4679,60 @@ function resolveCodexSessionFile(threadId, codexHome = join7(homedir5(), ".codex
4674
4679
  hits.sort((a, b) => b.mtime - a.mtime);
4675
4680
  return hits[0].path;
4676
4681
  }
4682
+ function parseCodexConfig(configPath = join7(homedir5(), ".codex", "config.toml")) {
4683
+ if (!existsSync7(configPath))
4684
+ return {};
4685
+ const text = readFileSync4(configPath, "utf8");
4686
+ const model = text.match(/^model\s*=\s*"([^"]+)"/m)?.[1];
4687
+ const providerId = text.match(/^model_provider\s*=\s*"([^"]+)"/m)?.[1];
4688
+ let baseUrl;
4689
+ if (providerId) {
4690
+ const escaped = providerId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4691
+ const section = text.match(new RegExp(`\\[model_providers\\.${escaped}\\]([\\s\\S]*?)(?:\\n\\[|$)`))?.[1];
4692
+ baseUrl = section?.match(/^base_url\s*=\s*"([^"]+)"/m)?.[1];
4693
+ }
4694
+ return { model, providerId, baseUrl };
4695
+ }
4696
+ function normalizeOpenAiModels(data) {
4697
+ const rows = Array.isArray(data.data) ? data.data : Array.isArray(data) ? data : [];
4698
+ const out = [];
4699
+ const seen = /* @__PURE__ */ new Set();
4700
+ for (const row of rows) {
4701
+ const obj = row;
4702
+ const id = typeof obj.id === "string" ? obj.id : void 0;
4703
+ if (!id || seen.has(id))
4704
+ continue;
4705
+ seen.add(id);
4706
+ out.push({ id, ...typeof obj.name === "string" ? { displayName: obj.name } : {}, available: true });
4707
+ }
4708
+ return out;
4709
+ }
4710
+ function fetchJson(url, headers) {
4711
+ return new Promise((resolve6, reject) => {
4712
+ const u = new URL(url);
4713
+ const req = (u.protocol === "http:" ? import("node:http") : import("node:https")).then((mod) => mod.request(url, { method: "GET", headers, timeout: 5e3 }, (res) => {
4714
+ let body = "";
4715
+ res.setEncoding("utf8");
4716
+ res.on("data", (d) => body += d);
4717
+ res.on("end", () => {
4718
+ if ((res.statusCode ?? 500) < 200 || (res.statusCode ?? 500) >= 300) {
4719
+ reject(new Error(`GET ${url} failed: ${res.statusCode}`));
4720
+ return;
4721
+ }
4722
+ try {
4723
+ resolve6(JSON.parse(body));
4724
+ } catch (e) {
4725
+ reject(e);
4726
+ }
4727
+ });
4728
+ }));
4729
+ req.then((r) => {
4730
+ r.on("timeout", () => r.destroy(new Error(`GET ${url} timeout`)));
4731
+ r.on("error", reject);
4732
+ r.end();
4733
+ }, reject);
4734
+ });
4735
+ }
4677
4736
  var CodexSession = class {
4678
4737
  sessionId;
4679
4738
  model;
@@ -4723,7 +4782,7 @@ ${prompt}`;
4723
4782
  await this.run(args2, prompt, turnId, emit, opts);
4724
4783
  }
4725
4784
  run(args2, stdin, turnId, emit, opts) {
4726
- return new Promise((resolve5) => {
4785
+ return new Promise((resolve6) => {
4727
4786
  const child = spawn3(this.env.binPath ?? "codex", args2, {
4728
4787
  cwd: this.cwd,
4729
4788
  shell: process.platform === "win32",
@@ -4744,7 +4803,7 @@ ${prompt}`;
4744
4803
  } else {
4745
4804
  emit({ type: "result", sessionId: this.sessionId, turnId, seq: 0, at: (/* @__PURE__ */ new Date()).toISOString(), text, status, final: true });
4746
4805
  }
4747
- resolve5();
4806
+ resolve6();
4748
4807
  };
4749
4808
  const guard = setTimeout(() => finish("timeout", acc), 18e5);
4750
4809
  opts.signal?.addEventListener("abort", () => {
@@ -4837,6 +4896,7 @@ var CodexAdapter = class {
4837
4896
  async discoverAgents() {
4838
4897
  const version = await this.probeVersion();
4839
4898
  const available = version !== null;
4899
+ const models = await this.discoverModels();
4840
4900
  return [{
4841
4901
  agentId: "codex",
4842
4902
  displayName: "Codex",
@@ -4844,7 +4904,7 @@ var CodexAdapter = class {
4844
4904
  available,
4845
4905
  ...available ? {} : { unavailableReason: "codex CLI not found" },
4846
4906
  ...version ? { version } : {},
4847
- models: this.env.models?.length ? this.env.models : [{ id: this.env.defaultModel, available: true }],
4907
+ models,
4848
4908
  capabilities: CAPABILITIES2,
4849
4909
  scannedAt: (/* @__PURE__ */ new Date()).toISOString()
4850
4910
  }];
@@ -4852,13 +4912,35 @@ var CodexAdapter = class {
4852
4912
  async createSession(params) {
4853
4913
  return new CodexSession(params.sessionId, params.model, params.cwd, this.env);
4854
4914
  }
4915
+ async discoverModels() {
4916
+ if (this.env.models?.length)
4917
+ return this.env.models;
4918
+ const cfg = parseCodexConfig();
4919
+ const baseUrl = this.env.baseUrl ?? cfg.baseUrl;
4920
+ if (baseUrl) {
4921
+ try {
4922
+ const data = await fetchJson(`${baseUrl.replace(/\/$/, "")}/models`, this.env.apiKey ? { Authorization: `Bearer ${this.env.apiKey}` } : void 0);
4923
+ const models = normalizeOpenAiModels(data);
4924
+ if (models.length > 0)
4925
+ return models;
4926
+ } catch {
4927
+ }
4928
+ }
4929
+ const configured = this.env.defaultModel !== "default" ? this.env.defaultModel : cfg.model;
4930
+ const base = configured ? [{ id: configured, available: true }] : [];
4931
+ const seen = new Set(base.map((m) => m.id));
4932
+ for (const m of CODEX_FALLBACK_MODELS)
4933
+ if (!seen.has(m.id))
4934
+ base.push(m);
4935
+ return base.length > 0 ? base : CODEX_FALLBACK_MODELS;
4936
+ }
4855
4937
  probeVersion() {
4856
- return new Promise((resolve5) => {
4938
+ return new Promise((resolve6) => {
4857
4939
  const child = spawn3(this.env.binPath ?? "codex", ["--version"], { shell: process.platform === "win32" });
4858
4940
  let out = "";
4859
4941
  child.stdout.on("data", (d) => out += d.toString());
4860
- child.on("error", () => resolve5(null));
4861
- child.on("close", (code) => resolve5(code === 0 ? out.trim() : null));
4942
+ child.on("error", () => resolve6(null));
4943
+ child.on("close", (code) => resolve6(code === 0 ? out.trim() : null));
4862
4944
  });
4863
4945
  }
4864
4946
  };
@@ -4934,7 +5016,7 @@ ${message}`;
4934
5016
  await this.run(args2, turnId, emit, opts);
4935
5017
  }
4936
5018
  run(args2, turnId, emit, opts) {
4937
- return new Promise((resolve5) => {
5019
+ return new Promise((resolve6) => {
4938
5020
  const child = spawn4(this.bin, args2, { cwd: this.cwd, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, ...opts.environment ?? {} } });
4939
5021
  this.current = child;
4940
5022
  let buf = "";
@@ -4951,7 +5033,7 @@ ${message}`;
4951
5033
  } else {
4952
5034
  emit({ type: "result", sessionId: this.sessionId, turnId, seq: 0, at: (/* @__PURE__ */ new Date()).toISOString(), text, status, final: true });
4953
5035
  }
4954
- resolve5();
5036
+ resolve6();
4955
5037
  };
4956
5038
  const guard = setTimeout(() => finish("timeout", acc), 18e5);
4957
5039
  opts.signal?.addEventListener("abort", () => {
@@ -5082,12 +5164,12 @@ var OpenCodeAdapter = class {
5082
5164
  return new OpenCodeSession(params.sessionId, model, params.cwd, this.bin);
5083
5165
  }
5084
5166
  probeVersion() {
5085
- return new Promise((resolve5) => {
5167
+ return new Promise((resolve6) => {
5086
5168
  const child = spawn4(this.bin, ["--version"]);
5087
5169
  let out = "";
5088
5170
  child.stdout.on("data", (d) => out += d.toString());
5089
- child.on("error", () => resolve5(null));
5090
- child.on("close", (code) => resolve5(code === 0 ? out.trim() : null));
5171
+ child.on("error", () => resolve6(null));
5172
+ child.on("close", (code) => resolve6(code === 0 ? out.trim() : null));
5091
5173
  });
5092
5174
  }
5093
5175
  };
@@ -5098,7 +5180,29 @@ import { randomUUID as randomUUID3 } from "node:crypto";
5098
5180
  import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
5099
5181
  import { homedir as homedir7 } from "node:os";
5100
5182
  import { join as join9 } from "node:path";
5101
- import { existsSync as existsSync9 } from "node:fs";
5183
+ import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
5184
+ var HERMES_PROVIDER_FALLBACK_MODELS = {
5185
+ // Hermes' built-in Copilot picker exposes these account/integrator-safe model ids.
5186
+ copilot: [
5187
+ "gpt-5.4",
5188
+ "gpt-5.4-mini",
5189
+ "gpt-5-mini",
5190
+ "gpt-5.3-codex",
5191
+ "gpt-5.2-codex",
5192
+ "gpt-4.1",
5193
+ "gpt-4o",
5194
+ "gpt-4o-mini",
5195
+ "claude-sonnet-4.6",
5196
+ "claude-sonnet-4",
5197
+ "claude-sonnet-4.5",
5198
+ "claude-haiku-4.5",
5199
+ "gemini-3.1-pro-preview",
5200
+ "gemini-3-pro-preview",
5201
+ "gemini-3-flash-preview",
5202
+ "gemini-2.5-pro"
5203
+ ],
5204
+ zai: ["glm-4.5", "glm-4.5-air", "glm-4.6", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.1", "glm-5.2"]
5205
+ };
5102
5206
  var CAPABILITIES4 = {
5103
5207
  nativeSession: true,
5104
5208
  // --resume / --pass-session-id
@@ -5118,6 +5222,49 @@ var CAPABILITIES4 = {
5118
5222
  // -z 是纯文本一次性输出,非流式 → 用 final 事件
5119
5223
  limits: { maxConcurrentSessions: 4 }
5120
5224
  };
5225
+ function parseHermesConfig(configPath = join9(homedir7(), ".hermes", "config.yaml")) {
5226
+ if (!existsSync9(configPath))
5227
+ return {};
5228
+ const text = readFileSync5(configPath, "utf8");
5229
+ const modelBlock = text.match(/(?:^|\n)model:\n([\s\S]*?)(?:\n[A-Za-z_][A-Za-z0-9_-]*:|$)/)?.[1] ?? "";
5230
+ const defaultModel = modelBlock.match(/^\s+default:\s*['"]?([^'"\n]+)['"]?/m)?.[1]?.trim();
5231
+ const provider = modelBlock.match(/^\s+provider:\s*['"]?([^'"\n]+)['"]?/m)?.[1]?.trim();
5232
+ const catalogBlock = text.match(/(?:^|\n)model_catalog:\n([\s\S]*?)(?:\n[A-Za-z_][A-Za-z0-9_-]*:|$)/)?.[1] ?? "";
5233
+ const catalogUrl = catalogBlock.match(/^\s+url:\s*['"]?([^'"\n]+)['"]?/m)?.[1]?.trim();
5234
+ return { defaultModel, provider, catalogUrl };
5235
+ }
5236
+ function fetchHermesCatalogModels(url, provider) {
5237
+ return new Promise((resolve6) => {
5238
+ const u = new URL(url);
5239
+ const reqMod = u.protocol === "http:" ? import("node:http") : import("node:https");
5240
+ reqMod.then((mod) => {
5241
+ const req = mod.request(url, { method: "GET", timeout: 5e3 }, (res) => {
5242
+ let body = "";
5243
+ res.setEncoding("utf8");
5244
+ res.on("data", (d) => body += d);
5245
+ res.on("end", () => {
5246
+ try {
5247
+ const data = JSON.parse(body);
5248
+ const providers = data.providers ?? {};
5249
+ const rows = provider ? providers[provider]?.models ?? [] : Object.values(providers).flatMap((p) => p.models ?? []);
5250
+ const seen = /* @__PURE__ */ new Set();
5251
+ resolve6(rows.flatMap((m) => {
5252
+ if (!m.id || seen.has(m.id))
5253
+ return [];
5254
+ seen.add(m.id);
5255
+ return [{ id: m.id, ...m.description ? { displayName: m.description } : {}, available: true }];
5256
+ }));
5257
+ } catch {
5258
+ resolve6([]);
5259
+ }
5260
+ });
5261
+ });
5262
+ req.on("timeout", () => req.destroy());
5263
+ req.on("error", () => resolve6([]));
5264
+ req.end();
5265
+ }, () => resolve6([]));
5266
+ });
5267
+ }
5121
5268
  var HermesSession = class {
5122
5269
  sessionId;
5123
5270
  model;
@@ -5163,7 +5310,7 @@ ${prompt}`;
5163
5310
  await this.run(args2, turnId, emit, opts);
5164
5311
  }
5165
5312
  run(args2, turnId, emit, opts) {
5166
- return new Promise((resolve5) => {
5313
+ return new Promise((resolve6) => {
5167
5314
  const child = spawn5(this.env.binPath ?? "hermes", args2, {
5168
5315
  cwd: this.cwd,
5169
5316
  shell: process.platform === "win32",
@@ -5186,7 +5333,7 @@ ${prompt}`;
5186
5333
  emit({ type: "message", sessionId: this.sessionId, turnId, seq: 0, at: (/* @__PURE__ */ new Date()).toISOString(), role: "assistant", text, delta: false });
5187
5334
  emit({ type: "result", sessionId: this.sessionId, turnId, seq: 0, at: (/* @__PURE__ */ new Date()).toISOString(), text, status, final: true });
5188
5335
  }
5189
- resolve5();
5336
+ resolve6();
5190
5337
  };
5191
5338
  const guard = setTimeout(() => finish("timeout", out), 18e5);
5192
5339
  opts.signal?.addEventListener("abort", () => {
@@ -5311,6 +5458,10 @@ var HermesAdapter = class {
5311
5458
  }];
5312
5459
  }
5313
5460
  const profiles = await this.listProfiles();
5461
+ const cfg = parseHermesConfig();
5462
+ const provider = this.env.provider ?? cfg.provider;
5463
+ const catalogModels = cfg.catalogUrl ? await fetchHermesCatalogModels(cfg.catalogUrl, provider) : [];
5464
+ const providerFallbackModels = provider ? (HERMES_PROVIDER_FALLBACK_MODELS[provider] ?? []).map((id) => ({ id, available: true })) : [];
5314
5465
  const now = (/* @__PURE__ */ new Date()).toISOString();
5315
5466
  return profiles.map((p) => ({
5316
5467
  agentId: `hermes:${p.name}`,
@@ -5318,11 +5469,23 @@ var HermesAdapter = class {
5318
5469
  adapter: "hermes",
5319
5470
  available: true,
5320
5471
  ...version ? { version } : {},
5321
- models: p.model ? [{ id: p.model, available: true }] : this.env.defaultModel ? [{ id: this.env.defaultModel, available: true }] : [{ id: "default", displayName: "Hermes default", available: true }],
5472
+ models: this.modelsForProfile(p.model, [...catalogModels, ...providerFallbackModels], cfg.defaultModel),
5322
5473
  capabilities: CAPABILITIES4,
5323
5474
  scannedAt: now
5324
5475
  }));
5325
5476
  }
5477
+ modelsForProfile(profileModel, catalogModels, configDefault) {
5478
+ const preferred = profileModel ?? this.env.defaultModel ?? configDefault;
5479
+ const models = preferred ? [{ id: preferred, available: true }] : [];
5480
+ const seen = new Set(models.map((m) => m.id));
5481
+ for (const m of catalogModels) {
5482
+ if (!seen.has(m.id)) {
5483
+ models.push(m);
5484
+ seen.add(m.id);
5485
+ }
5486
+ }
5487
+ return models.length > 0 ? models : [{ id: "default", displayName: "Hermes default", available: true }];
5488
+ }
5326
5489
  async createSession(params) {
5327
5490
  const profile = params.agentId.includes(":") ? params.agentId.split(":")[1] : this.defaultProfile;
5328
5491
  const model = params.model && params.model !== "default" ? params.model : this.env.defaultModel ?? "";
@@ -5330,11 +5493,11 @@ var HermesAdapter = class {
5330
5493
  }
5331
5494
  /** 枚举 Hermes profile(去 ANSI 色解析 profile list)。 */
5332
5495
  listProfiles() {
5333
- return new Promise((resolve5) => {
5496
+ return new Promise((resolve6) => {
5334
5497
  const child = spawn5(this.env.binPath ?? "hermes", ["profile", "list"], { shell: process.platform === "win32" });
5335
5498
  let out = "";
5336
5499
  child.stdout.on("data", (d) => out += d.toString());
5337
- child.on("error", () => resolve5([{ name: this.defaultProfile }]));
5500
+ child.on("error", () => resolve6([{ name: this.defaultProfile }]));
5338
5501
  child.on("close", () => {
5339
5502
  const clean = out.replace(/\u001b\[[0-9;]*m/g, "");
5340
5503
  const names = [];
@@ -5345,17 +5508,17 @@ var HermesAdapter = class {
5345
5508
  names.push({ name: m[1], model: rest[0] || void 0 });
5346
5509
  }
5347
5510
  }
5348
- resolve5(names.length > 0 ? names : [{ name: this.defaultProfile }]);
5511
+ resolve6(names.length > 0 ? names : [{ name: this.defaultProfile }]);
5349
5512
  });
5350
5513
  });
5351
5514
  }
5352
5515
  probeVersion() {
5353
- return new Promise((resolve5) => {
5516
+ return new Promise((resolve6) => {
5354
5517
  const child = spawn5(this.env.binPath ?? "hermes", ["--version"], { shell: process.platform === "win32" });
5355
5518
  let out = "";
5356
5519
  child.stdout.on("data", (d) => out += d.toString());
5357
- child.on("error", () => resolve5(null));
5358
- child.on("close", (code) => resolve5(code === 0 ? out.trim().split("\n")[0] ?? "hermes" : null));
5520
+ child.on("error", () => resolve6(null));
5521
+ child.on("close", (code) => resolve6(code === 0 ? out.trim().split("\n")[0] ?? "hermes" : null));
5359
5522
  });
5360
5523
  }
5361
5524
  };
@@ -5578,7 +5741,7 @@ ${prompt}`;
5578
5741
  await this.run(args2, envelope, turnId, emit, opts);
5579
5742
  }
5580
5743
  run(args2, stdin, turnId, emit, opts) {
5581
- return new Promise((resolve5) => {
5744
+ return new Promise((resolve6) => {
5582
5745
  const childEnv = {};
5583
5746
  for (const [k, v] of Object.entries(process.env)) {
5584
5747
  if (k === "CLAUDECODE" || k.startsWith("CLAUDECODE_") || k.startsWith("CLAUDE_CODE_"))
@@ -5610,7 +5773,7 @@ ${prompt}`;
5610
5773
  } else {
5611
5774
  emit({ type: "result", sessionId: this.sessionId, turnId, seq: 0, at: (/* @__PURE__ */ new Date()).toISOString(), text, status, final: true });
5612
5775
  }
5613
- resolve5();
5776
+ resolve6();
5614
5777
  };
5615
5778
  const guard = setTimeout(() => finish("timeout", acc), 18e5);
5616
5779
  opts.signal?.addEventListener("abort", () => {
@@ -5727,12 +5890,12 @@ var ClaudeCodeAdapter = class {
5727
5890
  return new ClaudeCodeSession(params.sessionId, params.model, params.cwd, this.env);
5728
5891
  }
5729
5892
  probeVersion() {
5730
- return new Promise((resolve5) => {
5893
+ return new Promise((resolve6) => {
5731
5894
  const child = spawn6(this.env.binPath ?? "claude", ["--version"], { shell: process.platform === "win32" });
5732
5895
  let out = "";
5733
5896
  child.stdout.on("data", (d) => out += d.toString());
5734
- child.on("error", () => resolve5(null));
5735
- child.on("close", (code) => resolve5(code === 0 ? out.trim() : null));
5897
+ child.on("error", () => resolve6(null));
5898
+ child.on("close", (code) => resolve6(code === 0 ? out.trim() : null));
5736
5899
  });
5737
5900
  }
5738
5901
  };
@@ -6070,7 +6233,7 @@ var PhononConnection = class {
6070
6233
  };
6071
6234
 
6072
6235
  // src/config.ts
6073
- import { readFileSync as readFileSync4, existsSync as existsSync11, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, chmodSync as chmodSync2 } from "node:fs";
6236
+ import { readFileSync as readFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, chmodSync as chmodSync2 } from "node:fs";
6074
6237
  import { homedir as homedir9, hostname as hostname2 } from "node:os";
6075
6238
  import { join as join11, dirname as dirname4 } from "node:path";
6076
6239
  var DEFAULT_DIR = join11(homedir9(), ".agent-phonon");
@@ -6090,7 +6253,7 @@ function loadConfig(path = DEFAULT_CONFIG_PATH) {
6090
6253
  if (!existsSync11(path)) {
6091
6254
  throw new Error(`config not found at ${path} \u2014 run 'agent-phonon init' first`);
6092
6255
  }
6093
- const raw = JSON.parse(readFileSync4(path, "utf8"));
6256
+ const raw = JSON.parse(readFileSync6(path, "utf8"));
6094
6257
  const d = defaultConfig();
6095
6258
  return {
6096
6259
  deviceId: raw.deviceId ?? d.deviceId,
@@ -6129,7 +6292,7 @@ function redactConfig(cfg) {
6129
6292
  function readOpenClawGatewayToken() {
6130
6293
  try {
6131
6294
  const p = join11(homedir9(), ".openclaw", "openclaw.json");
6132
- const cfg = JSON.parse(readFileSync4(p, "utf8"));
6295
+ const cfg = JSON.parse(readFileSync6(p, "utf8"));
6133
6296
  return cfg?.gateway?.auth?.token;
6134
6297
  } catch {
6135
6298
  return void 0;
@@ -6138,9 +6301,9 @@ function readOpenClawGatewayToken() {
6138
6301
 
6139
6302
  // src/commands.ts
6140
6303
  import { spawnSync } from "node:child_process";
6141
- import { existsSync as existsSync12, cpSync, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync5 } from "node:fs";
6304
+ import { existsSync as existsSync12, cpSync, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync7, chmodSync as chmodSync3 } from "node:fs";
6142
6305
  import { homedir as homedir10, platform as platform2 } from "node:os";
6143
- import { join as join12, dirname as dirname5, delimiter } from "node:path";
6306
+ import { join as join12, dirname as dirname5, delimiter, resolve as resolve5 } from "node:path";
6144
6307
  import { fileURLToPath } from "node:url";
6145
6308
  function probe(bin, args2 = ["--version"]) {
6146
6309
  const r = spawnSync(bin, args2, { timeout: 8e3, shell: platform2() === "win32" });
@@ -6320,6 +6483,75 @@ function cmdAdapterList() {
6320
6483
  console.log(`- ${a.type}${a.defaultAgent ? ` (agent: ${a.defaultAgent})` : ""}${auto}`);
6321
6484
  }
6322
6485
  }
6486
+ function systemctlUser(args2) {
6487
+ const r = spawnSync("systemctl", ["--user", ...args2], { stdio: "inherit" });
6488
+ if (r.status !== 0) fail(`systemctl --user ${args2.join(" ")} failed`);
6489
+ }
6490
+ function serviceUnitPath() {
6491
+ return join12(homedir10(), ".config", "systemd", "user", "agent-phonon.service");
6492
+ }
6493
+ function serviceUnitContent() {
6494
+ const node = process.execPath;
6495
+ const cli = resolve5(process.argv[1] ?? fileURLToPath(import.meta.url));
6496
+ return `[Unit]
6497
+ Description=agent-phonon device daemon
6498
+ After=network-online.target
6499
+ Wants=network-online.target
6500
+
6501
+ [Service]
6502
+ Type=simple
6503
+ ExecStart=${node} ${cli} start
6504
+ Restart=always
6505
+ RestartSec=5
6506
+ MemoryMax=256M
6507
+
6508
+ [Install]
6509
+ WantedBy=default.target
6510
+ `;
6511
+ }
6512
+ function cmdService(sub, opts = {}) {
6513
+ if (platform2() !== "linux") {
6514
+ fail(`service ${sub ?? ""} is currently implemented for Linux systemd --user only (platform=${platform2()})`);
6515
+ }
6516
+ const unit = serviceUnitPath();
6517
+ switch (sub) {
6518
+ case "install": {
6519
+ if (existsSync12(unit) && !opts.force) fail(`${unit} already exists (use --force to overwrite)`);
6520
+ mkdirSync4(dirname5(unit), { recursive: true });
6521
+ writeFileSync4(unit, serviceUnitContent(), { mode: 420 });
6522
+ chmodSync3(unit, 420);
6523
+ systemctlUser(["daemon-reload"]);
6524
+ systemctlUser(["enable", "agent-phonon.service"]);
6525
+ console.log(`installed systemd user service \u2192 ${unit}`);
6526
+ console.log("start it with: agent-phonon service start");
6527
+ if (!existsSync12(join12(homedir10(), ".agent-phonon", "config.json"))) console.log("config not found yet; run: agent-phonon init");
6528
+ break;
6529
+ }
6530
+ case "start":
6531
+ systemctlUser(["start", "agent-phonon.service"]);
6532
+ console.log("started agent-phonon.service");
6533
+ break;
6534
+ case "stop":
6535
+ systemctlUser(["stop", "agent-phonon.service"]);
6536
+ console.log("stopped agent-phonon.service");
6537
+ break;
6538
+ case "restart":
6539
+ systemctlUser(["restart", "agent-phonon.service"]);
6540
+ console.log("restarted agent-phonon.service");
6541
+ break;
6542
+ case "status":
6543
+ systemctlUser(["status", "agent-phonon.service", "--no-pager"]);
6544
+ break;
6545
+ case "uninstall":
6546
+ systemctlUser(["disable", "--now", "agent-phonon.service"]);
6547
+ rmSync3(unit, { force: true });
6548
+ systemctlUser(["daemon-reload"]);
6549
+ console.log(`removed ${unit}`);
6550
+ break;
6551
+ default:
6552
+ fail("usage: agent-phonon service install|start|stop|restart|status|uninstall [--force]");
6553
+ }
6554
+ }
6323
6555
  function cmdPluginInstall(which) {
6324
6556
  if (which !== "openclaw") return fail(`plugin install supports: openclaw (got: ${which})`);
6325
6557
  const here = dirname5(fileURLToPath(import.meta.url));
@@ -6332,7 +6564,7 @@ function cmdPluginInstall(which) {
6332
6564
  mkdirSync4(dist, { recursive: true });
6333
6565
  cpSync(join12(pluginSrc, "dist"), join12(dist, "dist"), { recursive: true });
6334
6566
  cpSync(join12(pluginSrc, "openclaw.plugin.json"), join12(dist, "openclaw.plugin.json"));
6335
- const pkg = JSON.parse(readFileSync5(join12(pluginSrc, "package.json"), "utf8"));
6567
+ const pkg = JSON.parse(readFileSync7(join12(pluginSrc, "package.json"), "utf8"));
6336
6568
  delete pkg.devDependencies;
6337
6569
  writeFileSync4(join12(dist, "package.json"), JSON.stringify(pkg, null, 2));
6338
6570
  console.log(`exported clean plugin \u2192 ${dist}`);
@@ -6358,13 +6590,13 @@ var ObsServer = class {
6358
6590
  deps.bus.onEvent((e) => this.broadcast(e));
6359
6591
  }
6360
6592
  listen(port = 4319, host = "127.0.0.1") {
6361
- return new Promise((resolve5) => {
6593
+ return new Promise((resolve6) => {
6362
6594
  const server = createServer2((req, res) => this.handle(req, res));
6363
6595
  this.server = server;
6364
6596
  server.listen(port, host, () => {
6365
6597
  const addr = server.address();
6366
6598
  this.actualPort = typeof addr === "object" && addr ? addr.port : port;
6367
- resolve5(this.actualPort);
6599
+ resolve6(this.actualPort);
6368
6600
  });
6369
6601
  });
6370
6602
  }
@@ -6442,11 +6674,11 @@ var ObsServer = class {
6442
6674
  return this.actualPort;
6443
6675
  }
6444
6676
  close() {
6445
- return new Promise((resolve5) => {
6677
+ return new Promise((resolve6) => {
6446
6678
  for (const c of this.sseClients) c.end();
6447
6679
  this.sseClients.clear();
6448
- if (!this.server) return resolve5();
6449
- this.server.close(() => resolve5());
6680
+ if (!this.server) return resolve6();
6681
+ this.server.close(() => resolve6());
6450
6682
  });
6451
6683
  }
6452
6684
  };
@@ -6672,6 +6904,10 @@ async function main() {
6672
6904
  }
6673
6905
  break;
6674
6906
  }
6907
+ case "service": {
6908
+ cmdService(args[0], { force: flag("force") });
6909
+ break;
6910
+ }
6675
6911
  case "plugin": {
6676
6912
  const sub = args[0];
6677
6913
  if (sub === "install") cmdPluginInstall(args[1] ?? "");
@@ -6689,6 +6925,7 @@ async function main() {
6689
6925
  console.log(" adapter add <type> [opts] configure an adapter override (auto-detect covers common local agents)");
6690
6926
  console.log(" adapter list list configured + auto-detected adapters");
6691
6927
  console.log(" plugin install openclaw install OpenClaw HITL plugin");
6928
+ console.log(" service install|start|status manage Linux systemd --user service");
6692
6929
  console.log(" server add <url> [--trust-local] [--device-key <k>]");
6693
6930
  console.log(" server list");
6694
6931
  console.log(" config [--show-secrets] show config (redacted by default)");