@intent-systems/nexus 2026.1.5-3 → 2026.1.5-5

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.
Files changed (144) hide show
  1. package/dist/agents/agent-id.js +41 -0
  2. package/dist/agents/auth-profiles.js +114 -25
  3. package/dist/agents/identity-state.js +79 -0
  4. package/dist/agents/model-auth.js +1 -0
  5. package/dist/agents/model-fallback.js +15 -9
  6. package/dist/agents/model-selection.js +1 -1
  7. package/dist/agents/models-config.js +17 -11
  8. package/dist/agents/pi-embedded-runner.js +101 -9
  9. package/dist/agents/sandbox.js +12 -3
  10. package/dist/agents/skill-runner.js +29 -4
  11. package/dist/agents/skill-usage.js +114 -11
  12. package/dist/agents/skills-status.js +4 -4
  13. package/dist/agents/skills.js +18 -7
  14. package/dist/agents/subagent-registry.js +25 -11
  15. package/dist/agents/system-prompt.js +16 -0
  16. package/dist/agents/tool-policy.js +19 -3
  17. package/dist/agents/tools/browser-tool.js +5 -2
  18. package/dist/agents/tools/image-tool.js +93 -8
  19. package/dist/agents/tools/sessions-announce-target.js +5 -1
  20. package/dist/agents/workspace.js +55 -46
  21. package/dist/auto-reply/command-detection.js +2 -1
  22. package/dist/auto-reply/reply/directive-handling.js +153 -28
  23. package/dist/auto-reply/reply/directives.js +17 -2
  24. package/dist/auto-reply/reply/model-selection.js +8 -3
  25. package/dist/auto-reply/reply/queue.js +2 -2
  26. package/dist/auto-reply/reply.js +1 -1
  27. package/dist/auto-reply/thinking.js +15 -0
  28. package/dist/browser/chrome.js +1 -1
  29. package/dist/browser/client.js +2 -0
  30. package/dist/browser/config.js +6 -2
  31. package/dist/browser/pw-tools-core.js +3 -0
  32. package/dist/browser/routes/agent.js +14 -0
  33. package/dist/canvas-host/server.js +1 -1
  34. package/dist/capabilities/detector.js +245 -0
  35. package/dist/capabilities/registry.js +99 -0
  36. package/dist/channels/location.js +44 -0
  37. package/dist/channels/web/index.js +2 -0
  38. package/dist/cli/cloud-cli.js +12 -7
  39. package/dist/cli/credential-cli.js +139 -17
  40. package/dist/cli/gateway-cli.js +1 -1
  41. package/dist/cli/log-cli.js +25 -0
  42. package/dist/cli/pairing-cli.js +1 -1
  43. package/dist/cli/program.js +58 -6
  44. package/dist/cli/run-main.js +1 -1
  45. package/dist/cli/skills-cli.js +144 -21
  46. package/dist/cli/skills-hub-cli.js +59 -29
  47. package/dist/cli/tool-connector-cli.js +99 -24
  48. package/dist/cli/upstream-sync-cli.js +253 -96
  49. package/dist/cli/usage-cli.js +14 -0
  50. package/dist/commands/auth-choice-options.js +6 -1
  51. package/dist/commands/auth-choice.js +157 -5
  52. package/dist/commands/bootstrap-preset.js +10 -6
  53. package/dist/commands/capabilities.js +33 -6
  54. package/dist/commands/claude-md.js +3 -2
  55. package/dist/commands/config-view.js +1 -1
  56. package/dist/commands/configure.js +4 -4
  57. package/dist/commands/credential.js +497 -36
  58. package/dist/commands/cursor-rules.js +39 -19
  59. package/dist/commands/doctor.js +5 -4
  60. package/dist/commands/identity.js +28 -31
  61. package/dist/commands/init.js +15 -18
  62. package/dist/commands/log.js +134 -0
  63. package/dist/commands/models/fallbacks.js +1 -1
  64. package/dist/commands/models/image-fallbacks.js +1 -1
  65. package/dist/commands/models/list.js +1 -1
  66. package/dist/commands/models/scan.js +1 -1
  67. package/dist/commands/onboard-auth.js +27 -2
  68. package/dist/commands/onboard-eve-identity.js +7 -8
  69. package/dist/commands/onboard-non-interactive.js +4 -2
  70. package/dist/commands/onboard-quickstart.js +18 -11
  71. package/dist/commands/quest-state.js +271 -0
  72. package/dist/commands/quest.js +53 -13
  73. package/dist/commands/reset.js +1 -1
  74. package/dist/commands/sessions-ingest.js +5 -4
  75. package/dist/commands/setup.js +4 -2
  76. package/dist/commands/skills-manifest.js +2 -2
  77. package/dist/commands/status.js +179 -61
  78. package/dist/commands/suggestions.js +1 -1
  79. package/dist/commands/usage-tracking.js +32 -0
  80. package/dist/commands/usage-upload.js +6 -1
  81. package/dist/config/defaults.js +1 -3
  82. package/dist/config/includes.js +5 -7
  83. package/dist/config/io.js +88 -16
  84. package/dist/config/legacy.js +4 -2
  85. package/dist/config/paths.js +16 -0
  86. package/dist/config/sessions.js +9 -5
  87. package/dist/config/zod-schema.js +4 -3
  88. package/dist/control-plane/broker/broker.js +1022 -0
  89. package/dist/control-plane/compaction.js +282 -0
  90. package/dist/control-plane/factory.js +31 -0
  91. package/dist/control-plane/index.js +10 -0
  92. package/dist/control-plane/odu/agents.js +192 -0
  93. package/dist/control-plane/odu/interaction-tools.js +208 -0
  94. package/dist/control-plane/odu/prompt-loader.js +95 -0
  95. package/dist/control-plane/odu/runtime.js +479 -0
  96. package/dist/control-plane/odu/types.js +6 -0
  97. package/dist/control-plane/odu-control-plane.js +316 -0
  98. package/dist/control-plane/single-agent.js +249 -0
  99. package/dist/control-plane/types.js +11 -0
  100. package/dist/credentials/store.js +449 -0
  101. package/dist/gateway/server-browser.js +5 -4
  102. package/dist/gateway/server-methods/cron.js +11 -1
  103. package/dist/gateway/server.js +14 -7
  104. package/dist/infra/bonjour.js +1 -1
  105. package/dist/infra/event-log.js +8 -2
  106. package/dist/infra/path-env.js +1 -2
  107. package/dist/infra/provider-usage.auth.js +5 -3
  108. package/dist/infra/provider-usage.fetch.claude.js +16 -6
  109. package/dist/infra/provider-usage.fetch.minimax.js +8 -3
  110. package/dist/infra/provider-usage.js +9 -5
  111. package/dist/infra/restart.js +2 -2
  112. package/dist/infra/usage-settings.js +78 -0
  113. package/dist/infra/usage-suggestions.js +17 -5
  114. package/dist/infra/usage-upload.js +38 -1
  115. package/dist/infra/voicewake.js +2 -2
  116. package/dist/logging/redact.js +109 -0
  117. package/dist/markdown/fences.js +58 -0
  118. package/dist/media/image-ops.js +3 -1
  119. package/dist/memory/embeddings.js +146 -0
  120. package/dist/memory/index.js +3 -0
  121. package/dist/memory/internal.js +163 -0
  122. package/dist/pairing/pairing-store.js +218 -0
  123. package/dist/plugins/cli.js +42 -0
  124. package/dist/plugins/discovery.js +253 -0
  125. package/dist/plugins/install.js +181 -0
  126. package/dist/plugins/loader.js +290 -0
  127. package/dist/plugins/registry.js +105 -0
  128. package/dist/plugins/status.js +29 -0
  129. package/dist/plugins/tools.js +39 -0
  130. package/dist/plugins/types.js +1 -0
  131. package/dist/providers/github-copilot-auth.js +1 -1
  132. package/dist/routing/resolve-route.js +144 -0
  133. package/dist/routing/session-key.js +65 -0
  134. package/dist/sessions/send-policy.js +5 -5
  135. package/dist/slack/monitor.js +22 -1
  136. package/dist/telegram/reaction-level.js +2 -1
  137. package/dist/utils/provider-utils.js +28 -0
  138. package/dist/utils.js +4 -3
  139. package/dist/wizard/onboarding.js +29 -7
  140. package/package.json +4 -29
  141. package/patches/@mariozechner__pi-ai.patch +215 -0
  142. package/patches/playwright-core@1.57.0.patch +13 -0
  143. package/patches/qrcode-terminal.patch +12 -0
  144. package/scripts/postinstall.js +202 -0
@@ -1,7 +1,8 @@
1
1
  import { fetchJson } from "./provider-usage.fetch.shared.js";
2
2
  import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
3
3
  function resolveClaudeWebSessionKey() {
4
- const direct = process.env.CLAUDE_AI_SESSION_KEY?.trim() ?? process.env.CLAUDE_WEB_SESSION_KEY?.trim();
4
+ const direct = process.env.CLAUDE_AI_SESSION_KEY?.trim() ??
5
+ process.env.CLAUDE_WEB_SESSION_KEY?.trim();
5
6
  if (direct?.startsWith("sk-ant-"))
6
7
  return direct;
7
8
  const cookieHeader = process.env.CLAUDE_WEB_COOKIE?.trim();
@@ -33,14 +34,18 @@ async function fetchClaudeWebUsage(sessionKey, timeoutMs, fetchFn) {
33
34
  windows.push({
34
35
  label: "5h",
35
36
  usedPercent: clampPercent(data.five_hour.utilization),
36
- resetAt: data.five_hour.resets_at ? new Date(data.five_hour.resets_at).getTime() : undefined,
37
+ resetAt: data.five_hour.resets_at
38
+ ? new Date(data.five_hour.resets_at).getTime()
39
+ : undefined,
37
40
  });
38
41
  }
39
42
  if (data.seven_day?.utilization !== undefined) {
40
43
  windows.push({
41
44
  label: "Week",
42
45
  usedPercent: clampPercent(data.seven_day.utilization),
43
- resetAt: data.seven_day.resets_at ? new Date(data.seven_day.resets_at).getTime() : undefined,
46
+ resetAt: data.seven_day.resets_at
47
+ ? new Date(data.seven_day.resets_at).getTime()
48
+ : undefined,
44
49
  });
45
50
  }
46
51
  const modelWindow = data.seven_day_sonnet || data.seven_day_opus;
@@ -82,7 +87,8 @@ export async function fetchClaudeUsage(token, timeoutMs, fetchFn) {
82
87
  // Claude CLI setup-token yields tokens that can be used for inference, but may not
83
88
  // include user:profile scope required by the OAuth usage endpoint. When a claude.ai
84
89
  // browser sessionKey is available, fall back to the web API.
85
- if (res.status === 403 && message?.includes("scope requirement user:profile")) {
90
+ if (res.status === 403 &&
91
+ message?.includes("scope requirement user:profile")) {
86
92
  const sessionKey = resolveClaudeWebSessionKey();
87
93
  if (sessionKey) {
88
94
  const web = await fetchClaudeWebUsage(sessionKey, timeoutMs, fetchFn);
@@ -104,14 +110,18 @@ export async function fetchClaudeUsage(token, timeoutMs, fetchFn) {
104
110
  windows.push({
105
111
  label: "5h",
106
112
  usedPercent: clampPercent(data.five_hour.utilization),
107
- resetAt: data.five_hour.resets_at ? new Date(data.five_hour.resets_at).getTime() : undefined,
113
+ resetAt: data.five_hour.resets_at
114
+ ? new Date(data.five_hour.resets_at).getTime()
115
+ : undefined,
108
116
  });
109
117
  }
110
118
  if (data.seven_day?.utilization !== undefined) {
111
119
  windows.push({
112
120
  label: "Week",
113
121
  usedPercent: clampPercent(data.seven_day.utilization),
114
- resetAt: data.seven_day.resets_at ? new Date(data.seven_day.resets_at).getTime() : undefined,
122
+ resetAt: data.seven_day.resets_at
123
+ ? new Date(data.seven_day.resets_at).getTime()
124
+ : undefined,
115
125
  });
116
126
  }
117
127
  const modelWindow = data.seven_day_sonnet || data.seven_day_opus;
@@ -178,8 +178,12 @@ export async function fetchMinimaxUsage(apiKey, timeoutMs, fetchFn) {
178
178
  error: "Invalid JSON",
179
179
  };
180
180
  }
181
- const baseResp = isRecord(data.base_resp) ? data.base_resp : undefined;
182
- if (baseResp && typeof baseResp.status_code === "number" && baseResp.status_code !== 0) {
181
+ const baseResp = isRecord(data.base_resp)
182
+ ? data.base_resp
183
+ : undefined;
184
+ if (baseResp &&
185
+ typeof baseResp.status_code === "number" &&
186
+ baseResp.status_code !== 0) {
183
187
  return {
184
188
  provider: "minimax",
185
189
  displayName: PROVIDER_LABELS.minimax,
@@ -197,7 +201,8 @@ export async function fetchMinimaxUsage(apiKey, timeoutMs, fetchFn) {
197
201
  error: "Unsupported response shape",
198
202
  };
199
203
  }
200
- const resetAt = parseEpoch(pickString(payload, RESET_KEYS)) ?? parseEpoch(pickNumber(payload, RESET_KEYS));
204
+ const resetAt = parseEpoch(pickString(payload, RESET_KEYS)) ??
205
+ parseEpoch(pickNumber(payload, RESET_KEYS));
201
206
  const windows = [
202
207
  {
203
208
  label: deriveWindowLabel(payload),
@@ -1,8 +1,8 @@
1
- import { resolveProviderAuths } from "./provider-usage.auth.js";
1
+ import { resolveProviderAuths, } from "./provider-usage.auth.js";
2
2
  import { fetchClaudeUsage } from "./provider-usage.fetch.claude.js";
3
3
  import { fetchMinimaxUsage } from "./provider-usage.fetch.minimax.js";
4
- import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
5
4
  import { fetchJson } from "./provider-usage.fetch.shared.js";
5
+ import { clampPercent, PROVIDER_LABELS } from "./provider-usage.shared.js";
6
6
  function resolveProviders(params) {
7
7
  if (params.providers && params.providers.length > 0) {
8
8
  return params.providers;
@@ -50,7 +50,9 @@ async function fetchZaiUsage(apiKey, timeoutMs, fetchFn) {
50
50
  const limit = data?.data?.limits?.find((entry) => entry?.type === "TOKENS_LIMIT") ??
51
51
  data?.data?.limits?.[0];
52
52
  const usedPercent = clampPercent(limit?.percentage ?? 0);
53
- const resetAt = limit?.nextResetTime ? Date.parse(limit.nextResetTime) : undefined;
53
+ const resetAt = limit?.nextResetTime
54
+ ? Date.parse(limit.nextResetTime)
55
+ : undefined;
54
56
  const windows = [];
55
57
  if (Number.isFinite(usedPercent)) {
56
58
  windows.push({
@@ -102,7 +104,7 @@ export async function loadProviderUsageSummary(params) {
102
104
  }
103
105
  return { updatedAt: now, providers: snapshots };
104
106
  }
105
- export function formatUsageSummaryLine(summary, opts = {}) {
107
+ export function formatUsageSummaryLine(summary, _opts = {}) {
106
108
  const providers = summary.providers ?? [];
107
109
  let best;
108
110
  for (const provider of providers) {
@@ -133,7 +135,9 @@ export function formatUsageReportLines(summary, opts = {}) {
133
135
  }
134
136
  for (const window of provider.windows) {
135
137
  const remaining = Math.max(0, 100 - window.usedPercent);
136
- const reset = window.resetAt ? formatResetLabel(window.resetAt, now) : null;
138
+ const reset = window.resetAt
139
+ ? formatResetLabel(window.resetAt, now)
140
+ : null;
137
141
  const extras = [provider.plan ? `plan ${provider.plan}` : null, reset]
138
142
  .filter(Boolean)
139
143
  .join(", ");
@@ -4,7 +4,7 @@ const DEFAULT_SYSTEMD_UNIT = "nexus-gateway.service";
4
4
  export function triggerNexusRestart() {
5
5
  if (process.platform !== "darwin") {
6
6
  if (process.platform === "linux") {
7
- const unit = process.env.NEXUS_SYSTEMD_UNIT || process.env.NEXUS_SYSTEMD_UNIT || DEFAULT_SYSTEMD_UNIT;
7
+ const unit = process.env.NEXUS_SYSTEMD_UNIT || DEFAULT_SYSTEMD_UNIT;
8
8
  const userRestart = spawnSync("systemctl", ["--user", "restart", unit], {
9
9
  stdio: "ignore",
10
10
  });
@@ -21,7 +21,7 @@ export function triggerNexusRestart() {
21
21
  }
22
22
  return "supervisor";
23
23
  }
24
- const label = process.env.NEXUS_LAUNCHD_LABEL || process.env.NEXUS_LAUNCHD_LABEL || DEFAULT_LAUNCHD_LABEL;
24
+ const label = process.env.NEXUS_LAUNCHD_LABEL || DEFAULT_LAUNCHD_LABEL;
25
25
  const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
26
26
  const target = uid !== undefined ? `gui/${uid}/${label}` : label;
27
27
  spawnSync("launchctl", ["kickstart", "-k", target], { stdio: "ignore" });
@@ -0,0 +1,78 @@
1
+ const SETTINGS_URL_ENV = "NEXUS_USAGE_SETTINGS_URL";
2
+ const UPLOAD_URL_ENV = "NEXUS_USAGE_UPLOAD_URL";
3
+ const UPLOAD_TOKEN_ENV = "NEXUS_USAGE_UPLOAD_TOKEN";
4
+ export function resolveUsageSettingsUrl(env = process.env) {
5
+ const override = env[SETTINGS_URL_ENV]?.trim();
6
+ if (override)
7
+ return override;
8
+ const uploadUrl = env[UPLOAD_URL_ENV]?.trim();
9
+ if (!uploadUrl)
10
+ return null;
11
+ return uploadUrl.replace(/\/api\/usage\/upload\/?$/, "/api/usage/settings");
12
+ }
13
+ function resolveAuthHeaders(env = process.env) {
14
+ const token = env[UPLOAD_TOKEN_ENV]?.trim();
15
+ if (!token)
16
+ return {};
17
+ return { Authorization: `Bearer ${token}` };
18
+ }
19
+ function parseSettingsResponse(payload) {
20
+ if (!payload || typeof payload !== "object")
21
+ return null;
22
+ const data = payload;
23
+ if (typeof data.canOptOut !== "boolean" || typeof data.optOut !== "boolean") {
24
+ return null;
25
+ }
26
+ return {
27
+ ok: typeof data.ok === "boolean" ? data.ok : true,
28
+ canOptOut: data.canOptOut,
29
+ optOut: data.optOut,
30
+ planName: typeof data.planName === "string" ? data.planName : undefined,
31
+ error: typeof data.error === "string" ? data.error : undefined,
32
+ };
33
+ }
34
+ export async function fetchUsageTrackingSettings(env = process.env) {
35
+ const url = resolveUsageSettingsUrl(env);
36
+ if (!url)
37
+ return null;
38
+ const headers = resolveAuthHeaders(env);
39
+ if (!headers.Authorization)
40
+ return null;
41
+ try {
42
+ const res = await fetch(url, { headers });
43
+ const payload = await res.json().catch(() => null);
44
+ const parsed = parseSettingsResponse(payload);
45
+ if (!parsed)
46
+ return null;
47
+ return { ...parsed, ok: res.ok && parsed.ok };
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ export async function updateUsageTrackingSettings(optOut, env = process.env) {
54
+ const url = resolveUsageSettingsUrl(env);
55
+ if (!url)
56
+ return null;
57
+ const headers = {
58
+ "Content-Type": "application/json",
59
+ ...resolveAuthHeaders(env),
60
+ };
61
+ if (!headers.Authorization)
62
+ return null;
63
+ try {
64
+ const res = await fetch(url, {
65
+ method: "POST",
66
+ headers,
67
+ body: JSON.stringify({ optOut }),
68
+ });
69
+ const payload = await res.json().catch(() => null);
70
+ const parsed = parseSettingsResponse(payload);
71
+ if (!parsed)
72
+ return null;
73
+ return { ...parsed, ok: res.ok && parsed.ok };
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
@@ -58,17 +58,26 @@ export async function loadUsageEvents(opts = {}) {
58
58
  catch {
59
59
  continue;
60
60
  }
61
- const sessionId = String(raw.session_id ?? "");
61
+ const sessionId = typeof raw.session_id === "string" || typeof raw.session_id === "number"
62
+ ? String(raw.session_id)
63
+ : "";
62
64
  const seq = Number(raw.seq ?? 0);
63
65
  const ts = Number(raw.ts ?? 0);
64
- const source = String(raw.source ?? "");
65
- if (!sessionId || !Number.isFinite(seq) || !Number.isFinite(ts) || !source) {
66
+ const source = typeof raw.source === "string" || typeof raw.source === "number"
67
+ ? String(raw.source)
68
+ : "";
69
+ if (!sessionId ||
70
+ !Number.isFinite(seq) ||
71
+ !Number.isFinite(ts) ||
72
+ !source) {
66
73
  continue;
67
74
  }
68
75
  if (opts.sinceMs && ts < opts.sinceMs) {
69
76
  continue;
70
77
  }
71
- if (opts.sources && opts.sources.length > 0 && !opts.sources.includes(source)) {
78
+ if (opts.sources &&
79
+ opts.sources.length > 0 &&
80
+ !opts.sources.includes(source)) {
72
81
  continue;
73
82
  }
74
83
  const evt = {
@@ -76,7 +85,10 @@ export async function loadUsageEvents(opts = {}) {
76
85
  seq,
77
86
  ts,
78
87
  source,
79
- eventType: String(raw.event_type ?? ""),
88
+ eventType: typeof raw.event_type === "string" ||
89
+ typeof raw.event_type === "number"
90
+ ? String(raw.event_type)
91
+ : "",
80
92
  commandPath: typeof raw.command_path === "string" ? raw.command_path : undefined,
81
93
  stream: typeof raw.stream === "string" ? raw.stream : undefined,
82
94
  data: raw.data && typeof raw.data === "object"
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import readline from "node:readline";
5
5
  import { resolveStateDir } from "../config/paths.js";
6
6
  import { resolveEventLogDir } from "./event-log.js";
7
+ import { fetchUsageTrackingSettings } from "./usage-settings.js";
7
8
  const UPLOAD_URL_ENV = "NEXUS_USAGE_UPLOAD_URL";
8
9
  const UPLOAD_TOKEN_ENV = "NEXUS_USAGE_UPLOAD_TOKEN";
9
10
  const OUTBOX_DIR_ENV = "NEXUS_USAGE_OUTBOX_DIR";
@@ -39,9 +40,34 @@ function writeJSON(pathname, data) {
39
40
  fs.mkdirSync(path.dirname(pathname), { recursive: true });
40
41
  fs.writeFileSync(pathname, JSON.stringify(data, null, 2), "utf-8");
41
42
  }
43
+ function updateTrackingState(state, settings) {
44
+ if (!settings)
45
+ return;
46
+ state.opted_out = settings.canOptOut && settings.optOut;
47
+ state.settings_checked_at = Date.now();
48
+ writeJSON(stateFilePath(), state);
49
+ }
50
+ function isTrackingDisabled(state, settings) {
51
+ if (settings)
52
+ return settings.canOptOut && settings.optOut;
53
+ return Boolean(state.opted_out);
54
+ }
55
+ function clearUsageOutbox() {
56
+ const outboxDir = resolveOutboxDir();
57
+ if (!fs.existsSync(outboxDir))
58
+ return 0;
59
+ const files = fs
60
+ .readdirSync(outboxDir)
61
+ .filter((name) => name.endsWith(".json"))
62
+ .map((name) => path.join(outboxDir, name));
63
+ for (const file of files) {
64
+ fs.unlinkSync(file);
65
+ }
66
+ return files.length;
67
+ }
42
68
  function getOrCreateUsageState() {
43
69
  const existing = readJSON(stateFilePath());
44
- if (existing && existing.anon_id && existing.anon_salt) {
70
+ if (existing?.anon_id && existing.anon_salt) {
45
71
  return existing;
46
72
  }
47
73
  const anonId = crypto.randomUUID();
@@ -260,6 +286,17 @@ export async function flushUsageOutbox() {
260
286
  }
261
287
  export async function runUsageUpload(opts = {}) {
262
288
  const limit = opts.limit && opts.limit > 0 ? opts.limit : 1000;
289
+ const state = getOrCreateUsageState();
290
+ const settings = await fetchUsageTrackingSettings();
291
+ const resolvedSettings = settings?.ok ? settings : null;
292
+ updateTrackingState(state, resolvedSettings);
293
+ if (isTrackingDisabled(state, resolvedSettings)) {
294
+ const cleared = clearUsageOutbox();
295
+ return {
296
+ status: "disabled",
297
+ cleared,
298
+ };
299
+ }
263
300
  if (!opts.onlyFlush) {
264
301
  const { batch, nextState, count } = await buildUsageUploadBatch(limit);
265
302
  if (opts.dryRun) {
@@ -2,10 +2,10 @@ import { randomUUID } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
- import { resolveConfigDir } from "../utils.js";
5
+ import { resolveStateDir } from "../config/paths.js";
6
6
  const DEFAULT_TRIGGERS = ["nexus", "claude", "computer"];
7
7
  function defaultBaseDir() {
8
- return resolveConfigDir(process.env, os.homedir);
8
+ return resolveStateDir(process.env, os.homedir);
9
9
  }
10
10
  function resolvePath(baseDir) {
11
11
  const root = baseDir ?? defaultBaseDir();
@@ -0,0 +1,109 @@
1
+ import { loadConfig } from "../config/config.js";
2
+ const DEFAULT_REDACT_MODE = "tools";
3
+ const DEFAULT_REDACT_MIN_LENGTH = 18;
4
+ const DEFAULT_REDACT_KEEP_START = 6;
5
+ const DEFAULT_REDACT_KEEP_END = 4;
6
+ const DEFAULT_REDACT_PATTERNS = [
7
+ // ENV-style assignments.
8
+ String.raw `\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1`,
9
+ // JSON fields.
10
+ String.raw `"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"`,
11
+ // CLI flags.
12
+ String.raw `--(?:api[-_]?key|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1`,
13
+ // Authorization headers.
14
+ String.raw `Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)`,
15
+ String.raw `\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b`,
16
+ // PEM blocks.
17
+ String.raw `-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----`,
18
+ // Common token prefixes.
19
+ String.raw `\b(sk-[A-Za-z0-9_-]{8,})\b`,
20
+ String.raw `\b(ghp_[A-Za-z0-9]{20,})\b`,
21
+ String.raw `\b(github_pat_[A-Za-z0-9_]{20,})\b`,
22
+ String.raw `\b(xox[baprs]-[A-Za-z0-9-]{10,})\b`,
23
+ String.raw `\b(xapp-[A-Za-z0-9-]{10,})\b`,
24
+ String.raw `\b(gsk_[A-Za-z0-9_-]{10,})\b`,
25
+ String.raw `\b(AIza[0-9A-Za-z\-_]{20,})\b`,
26
+ String.raw `\b(pplx-[A-Za-z0-9_-]{10,})\b`,
27
+ String.raw `\b(npm_[A-Za-z0-9]{10,})\b`,
28
+ String.raw `\b(\d{6,}:[A-Za-z0-9_-]{20,})\b`,
29
+ ];
30
+ function normalizeMode(value) {
31
+ return value === "off" ? "off" : DEFAULT_REDACT_MODE;
32
+ }
33
+ function parsePattern(raw) {
34
+ if (!raw.trim())
35
+ return null;
36
+ const match = raw.match(/^\/(.+)\/([gimsuy]*)$/);
37
+ try {
38
+ if (match) {
39
+ const flags = match[2].includes("g") ? match[2] : `${match[2]}g`;
40
+ return new RegExp(match[1], flags);
41
+ }
42
+ return new RegExp(raw, "gi");
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ function resolvePatterns(value) {
49
+ const source = value?.length ? value : DEFAULT_REDACT_PATTERNS;
50
+ return source.map(parsePattern).filter((re) => Boolean(re));
51
+ }
52
+ function maskToken(token) {
53
+ if (token.length < DEFAULT_REDACT_MIN_LENGTH)
54
+ return "***";
55
+ const start = token.slice(0, DEFAULT_REDACT_KEEP_START);
56
+ const end = token.slice(-DEFAULT_REDACT_KEEP_END);
57
+ return `${start}…${end}`;
58
+ }
59
+ function redactPemBlock(block) {
60
+ const lines = block.split(/\r?\n/).filter(Boolean);
61
+ if (lines.length < 2)
62
+ return "***";
63
+ return `${lines[0]}\n…redacted…\n${lines[lines.length - 1]}`;
64
+ }
65
+ function redactMatch(match, groups) {
66
+ if (match.includes("PRIVATE KEY-----"))
67
+ return redactPemBlock(match);
68
+ const token = groups
69
+ .filter((value) => typeof value === "string" && value.length > 0)
70
+ .at(-1) ?? match;
71
+ const masked = maskToken(token);
72
+ if (token === match)
73
+ return masked;
74
+ return match.replace(token, masked);
75
+ }
76
+ function redactText(text, patterns) {
77
+ let next = text;
78
+ for (const pattern of patterns) {
79
+ next = next.replace(pattern, (...args) => redactMatch(args[0], args.slice(1, args.length - 2)));
80
+ }
81
+ return next;
82
+ }
83
+ function resolveConfigRedaction() {
84
+ const cfg = loadConfig().logging;
85
+ return {
86
+ mode: normalizeMode(cfg?.redactSensitive),
87
+ patterns: cfg?.redactPatterns,
88
+ };
89
+ }
90
+ export function redactSensitiveText(text, options) {
91
+ if (!text)
92
+ return text;
93
+ const resolved = options ?? resolveConfigRedaction();
94
+ if (normalizeMode(resolved.mode) === "off")
95
+ return text;
96
+ const patterns = resolvePatterns(resolved.patterns);
97
+ if (!patterns.length)
98
+ return text;
99
+ return redactText(text, patterns);
100
+ }
101
+ export function redactToolDetail(detail) {
102
+ const resolved = resolveConfigRedaction();
103
+ if (normalizeMode(resolved.mode) !== "tools")
104
+ return detail;
105
+ return redactSensitiveText(detail, resolved);
106
+ }
107
+ export function getDefaultRedactPatterns() {
108
+ return [...DEFAULT_REDACT_PATTERNS];
109
+ }
@@ -0,0 +1,58 @@
1
+ export function parseFenceSpans(buffer) {
2
+ const spans = [];
3
+ let open;
4
+ let offset = 0;
5
+ while (offset <= buffer.length) {
6
+ const nextNewline = buffer.indexOf("\n", offset);
7
+ const lineEnd = nextNewline === -1 ? buffer.length : nextNewline;
8
+ const line = buffer.slice(offset, lineEnd);
9
+ const match = line.match(/^( {0,3})(`{3,}|~{3,})(.*)$/);
10
+ if (match) {
11
+ const indent = match[1];
12
+ const marker = match[2];
13
+ const markerChar = marker[0];
14
+ const markerLen = marker.length;
15
+ if (!open) {
16
+ open = {
17
+ start: offset,
18
+ markerChar,
19
+ markerLen,
20
+ openLine: line,
21
+ marker,
22
+ indent,
23
+ };
24
+ }
25
+ else if (open.markerChar === markerChar &&
26
+ markerLen >= open.markerLen) {
27
+ const end = nextNewline === -1 ? buffer.length : nextNewline + 1;
28
+ spans.push({
29
+ start: open.start,
30
+ end,
31
+ openLine: open.openLine,
32
+ marker: open.marker,
33
+ indent: open.indent,
34
+ });
35
+ open = undefined;
36
+ }
37
+ }
38
+ if (nextNewline === -1)
39
+ break;
40
+ offset = nextNewline + 1;
41
+ }
42
+ if (open) {
43
+ spans.push({
44
+ start: open.start,
45
+ end: buffer.length,
46
+ openLine: open.openLine,
47
+ marker: open.marker,
48
+ indent: open.indent,
49
+ });
50
+ }
51
+ return spans;
52
+ }
53
+ export function findFenceSpanAt(spans, index) {
54
+ return spans.find((span) => index > span.start && index < span.end);
55
+ }
56
+ export function isSafeFenceBreak(spans, index) {
57
+ return !findFenceSpanAt(spans, index);
58
+ }
@@ -7,7 +7,9 @@ function isBun() {
7
7
  }
8
8
  function prefersSips() {
9
9
  return (process.env.NEXUS_IMAGE_BACKEND === "sips" ||
10
- (process.env.NEXUS_IMAGE_BACKEND !== "sharp" && isBun() && process.platform === "darwin"));
10
+ (process.env.NEXUS_IMAGE_BACKEND !== "sharp" &&
11
+ isBun() &&
12
+ process.platform === "darwin"));
11
13
  }
12
14
  async function loadSharp() {
13
15
  const mod = (await import("sharp"));
@@ -0,0 +1,146 @@
1
+ import { resolveApiKeyForProvider } from "../agents/model-auth.js";
2
+ const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
3
+ const DEFAULT_LOCAL_MODEL = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";
4
+ function normalizeOpenAiModel(model) {
5
+ const trimmed = model.trim();
6
+ if (!trimmed)
7
+ return "text-embedding-3-small";
8
+ if (trimmed.startsWith("openai/"))
9
+ return trimmed.slice("openai/".length);
10
+ return trimmed;
11
+ }
12
+ async function createOpenAiEmbeddingProvider(options) {
13
+ const remote = options.remote;
14
+ const remoteApiKey = remote?.apiKey?.trim();
15
+ const remoteBaseUrl = remote?.baseUrl?.trim();
16
+ const { apiKey } = remoteApiKey
17
+ ? { apiKey: remoteApiKey }
18
+ : await resolveApiKeyForProvider({
19
+ provider: "openai",
20
+ cfg: options.config,
21
+ agentDir: options.agentDir,
22
+ });
23
+ const providerConfig = options.config.models?.providers?.openai;
24
+ const baseUrl = remoteBaseUrl || providerConfig?.baseUrl?.trim() || DEFAULT_OPENAI_BASE_URL;
25
+ const url = `${baseUrl.replace(/\/$/, "")}/embeddings`;
26
+ const headerOverrides = Object.assign({}, providerConfig?.headers, remote?.headers);
27
+ const headers = {
28
+ "Content-Type": "application/json",
29
+ Authorization: `Bearer ${apiKey}`,
30
+ ...headerOverrides,
31
+ };
32
+ const model = normalizeOpenAiModel(options.model);
33
+ const embed = async (input) => {
34
+ if (input.length === 0)
35
+ return [];
36
+ const res = await fetch(url, {
37
+ method: "POST",
38
+ headers,
39
+ body: JSON.stringify({ model, input }),
40
+ });
41
+ if (!res.ok) {
42
+ const text = await res.text();
43
+ throw new Error(`openai embeddings failed: ${res.status} ${text}`);
44
+ }
45
+ const payload = (await res.json());
46
+ const data = payload.data ?? [];
47
+ return data.map((entry) => entry.embedding ?? []);
48
+ };
49
+ return {
50
+ id: "openai",
51
+ model,
52
+ embedQuery: async (text) => {
53
+ const [vec] = await embed([text]);
54
+ return vec ?? [];
55
+ },
56
+ embedBatch: embed,
57
+ };
58
+ }
59
+ async function createLocalEmbeddingProvider(options) {
60
+ const modelPath = options.local?.modelPath?.trim() || DEFAULT_LOCAL_MODEL;
61
+ const modelCacheDir = options.local?.modelCacheDir?.trim();
62
+ // Lazy-load node-llama-cpp to keep startup light unless local is enabled.
63
+ const moduleName = "node-llama-cpp";
64
+ const { getLlama, resolveModelFile, LlamaLogLevel } = (await import(moduleName));
65
+ let llama = null;
66
+ let embeddingModel = null;
67
+ let embeddingContext = null;
68
+ const ensureContext = async () => {
69
+ if (!llama) {
70
+ llama = await getLlama({ logLevel: LlamaLogLevel.error });
71
+ }
72
+ if (!embeddingModel) {
73
+ const resolved = await resolveModelFile(modelPath, modelCacheDir || undefined);
74
+ embeddingModel = await llama.loadModel({ modelPath: resolved });
75
+ }
76
+ if (!embeddingContext) {
77
+ embeddingContext = await embeddingModel.createEmbeddingContext();
78
+ }
79
+ return embeddingContext;
80
+ };
81
+ return {
82
+ id: "local",
83
+ model: modelPath,
84
+ embedQuery: async (text) => {
85
+ const ctx = await ensureContext();
86
+ const embedding = await ctx.getEmbeddingFor(text);
87
+ return Array.from(embedding.vector);
88
+ },
89
+ embedBatch: async (texts) => {
90
+ const ctx = await ensureContext();
91
+ const embeddings = await Promise.all(texts.map(async (text) => {
92
+ const embedding = await ctx.getEmbeddingFor(text);
93
+ return Array.from(embedding.vector);
94
+ }));
95
+ return embeddings;
96
+ },
97
+ };
98
+ }
99
+ export async function createEmbeddingProvider(options) {
100
+ const requestedProvider = options.provider;
101
+ if (options.provider === "local") {
102
+ try {
103
+ const provider = await createLocalEmbeddingProvider(options);
104
+ return { provider, requestedProvider };
105
+ }
106
+ catch (err) {
107
+ const reason = formatLocalSetupError(err);
108
+ if (options.fallback === "openai") {
109
+ try {
110
+ const provider = await createOpenAiEmbeddingProvider(options);
111
+ return {
112
+ provider,
113
+ requestedProvider,
114
+ fallbackFrom: "local",
115
+ fallbackReason: reason,
116
+ };
117
+ }
118
+ catch (fallbackErr) {
119
+ throw new Error(`${reason}\n\nFallback to OpenAI failed: ${formatError(fallbackErr)}`);
120
+ }
121
+ }
122
+ throw new Error(reason);
123
+ }
124
+ }
125
+ const provider = await createOpenAiEmbeddingProvider(options);
126
+ return { provider, requestedProvider };
127
+ }
128
+ function formatError(err) {
129
+ if (err instanceof Error)
130
+ return err.message;
131
+ return String(err);
132
+ }
133
+ function formatLocalSetupError(err) {
134
+ const detail = formatError(err);
135
+ return [
136
+ "Local embeddings unavailable.",
137
+ detail ? `Reason: ${detail}` : undefined,
138
+ "To enable local embeddings:",
139
+ "1) pnpm approve-builds",
140
+ "2) select node-llama-cpp",
141
+ "3) pnpm rebuild node-llama-cpp",
142
+ 'Or set agent.memorySearch.provider = "openai" (remote).',
143
+ ]
144
+ .filter(Boolean)
145
+ .join("\n");
146
+ }
@@ -0,0 +1,3 @@
1
+ export async function getMemorySearchManager() {
2
+ return { manager: null, error: "sqlite unavailable" };
3
+ }