@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
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { startBrowserBridgeServer, stopBrowserBridgeServer, } from "../browser/bridge-server.js";
7
- import { resolveProfile } from "../browser/config.js";
7
+ import { resolveProfile, } from "../browser/config.js";
8
8
  import { DEFAULT_NEXUS_BROWSER_COLOR } from "../browser/constants.js";
9
9
  import { STATE_DIR_NEXUS } from "../config/config.js";
10
10
  import { defaultRuntime } from "../runtime.js";
@@ -27,7 +27,14 @@ const DEFAULT_TOOL_ALLOW = [
27
27
  "sessions_send",
28
28
  "sessions_spawn",
29
29
  ];
30
- const DEFAULT_TOOL_DENY = ["browser", "canvas", "nodes", "cron", "discord", "gateway"];
30
+ const DEFAULT_TOOL_DENY = [
31
+ "browser",
32
+ "canvas",
33
+ "nodes",
34
+ "cron",
35
+ "discord",
36
+ "gateway",
37
+ ];
31
38
  export const DEFAULT_SANDBOX_BROWSER_IMAGE = "nexus-sandbox-browser:bookworm-slim";
32
39
  export const DEFAULT_SANDBOX_COMMON_IMAGE = "nexus-sandbox-common:bookworm-slim";
33
40
  const DEFAULT_SANDBOX_BROWSER_PREFIX = "nexus-sbx-browser-";
@@ -490,7 +497,9 @@ async function ensureSandboxBrowser(params) {
490
497
  ? await readDockerPort(containerName, params.cfg.browser.noVncPort)
491
498
  : null;
492
499
  const existing = BROWSER_BRIDGES.get(params.sessionKey);
493
- const existingProfile = existing ? resolveProfile(existing.bridge.state.resolved, "nexus") : null;
500
+ const existingProfile = existing
501
+ ? resolveProfile(existing.bridge.state.resolved, "nexus")
502
+ : null;
494
503
  const shouldReuse = existing &&
495
504
  existing.containerName === containerName &&
496
505
  existingProfile?.cdpPort === mappedCdp;
@@ -3,10 +3,11 @@ import path from "node:path";
3
3
  import { loadConfig } from "../config/config.js";
4
4
  import { runCommandWithTimeout } from "../process/exec.js";
5
5
  import { MANAGED_SKILLS_DIR, resolveUserPath } from "../utils.js";
6
- import { DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace.js";
7
- import { buildWorkspaceSkillStatus } from "./skills-status.js";
8
- import { getSkillMetadata, hasBinary, loadWorkspaceSkillEntries, } from "./skills.js";
9
6
  import { getSkillExecutionType, getSkillScriptConfig, getSkillToolConfig, } from "./skill-tools.js";
7
+ import { recordSkillUsage } from "./skill-usage.js";
8
+ import { getSkillMetadata, hasBinary, loadWorkspaceSkillEntries, } from "./skills.js";
9
+ import { buildWorkspaceSkillStatus } from "./skills-status.js";
10
+ import { DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace.js";
10
11
  function loadSkillContext(name) {
11
12
  const config = loadConfig();
12
13
  const workspaceDir = resolveUserPath(config.agent?.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR);
@@ -90,8 +91,18 @@ export async function runSkill(name, args) {
90
91
  if (!entry) {
91
92
  return { ok: false, error: `Skill not found: ${name}` };
92
93
  }
94
+ const startedAt = Date.now();
95
+ const record = (ok) => {
96
+ recordSkillUsage(entry.skill.name, {
97
+ ts: Date.now(),
98
+ event: "run",
99
+ ok,
100
+ durationMs: Date.now() - startedAt,
101
+ });
102
+ };
93
103
  const execType = getSkillExecutionType(entry.skill.name);
94
104
  if (execType === "prompt") {
105
+ record(false);
95
106
  return {
96
107
  ok: false,
97
108
  error: "Skill is prompt-only and has no runnable command.",
@@ -100,13 +111,19 @@ export async function runSkill(name, args) {
100
111
  if (execType === "tool") {
101
112
  const toolConfig = getSkillToolConfig(entry.skill.name);
102
113
  if (!toolConfig?.tool) {
114
+ record(false);
103
115
  return { ok: false, error: "Skill tool configuration missing." };
104
116
  }
105
117
  if (!hasBinary(toolConfig.tool)) {
118
+ record(false);
106
119
  return { ok: false, error: `Missing binary: ${toolConfig.tool}` };
107
120
  }
108
- const result = await runCommandWithTimeout([toolConfig.tool, ...args], { timeoutMs: 300_000, cwd: entry.skill.baseDir });
121
+ const result = await runCommandWithTimeout([toolConfig.tool, ...args], {
122
+ timeoutMs: 300_000,
123
+ cwd: entry.skill.baseDir,
124
+ });
109
125
  if (result.code !== 0) {
126
+ record(false);
110
127
  return {
111
128
  ok: false,
112
129
  error: result.stderr || result.stdout || "Skill command failed.",
@@ -115,6 +132,7 @@ export async function runSkill(name, args) {
115
132
  code: result.code,
116
133
  };
117
134
  }
135
+ record(true);
118
136
  return {
119
137
  ok: true,
120
138
  stdout: result.stdout,
@@ -125,9 +143,11 @@ export async function runSkill(name, args) {
125
143
  if (execType === "script") {
126
144
  const scriptConfig = getSkillScriptConfig(entry.skill.name);
127
145
  if (!scriptConfig) {
146
+ record(false);
128
147
  return { ok: false, error: "Skill script configuration missing." };
129
148
  }
130
149
  if (!hasBinary(scriptConfig.runner)) {
150
+ record(false);
131
151
  return {
132
152
  ok: false,
133
153
  error: `Missing runner: ${scriptConfig.runner}`,
@@ -135,11 +155,13 @@ export async function runSkill(name, args) {
135
155
  }
136
156
  const scriptPath = path.join(entry.skill.baseDir, scriptConfig.script);
137
157
  if (!fs.existsSync(scriptPath)) {
158
+ record(false);
138
159
  return { ok: false, error: `Script not found: ${scriptConfig.script}` };
139
160
  }
140
161
  if (scriptConfig.requiredEnv?.length) {
141
162
  const missingEnv = scriptConfig.requiredEnv.filter((envName) => !process.env[envName]);
142
163
  if (missingEnv.length > 0) {
164
+ record(false);
143
165
  return {
144
166
  ok: false,
145
167
  error: `Missing env: ${missingEnv.join(", ")}`,
@@ -148,6 +170,7 @@ export async function runSkill(name, args) {
148
170
  }
149
171
  const result = await runCommandWithTimeout([scriptConfig.runner, scriptPath, ...args], { timeoutMs: 300_000, cwd: entry.skill.baseDir });
150
172
  if (result.code !== 0) {
173
+ record(false);
151
174
  return {
152
175
  ok: false,
153
176
  error: result.stderr || result.stdout || "Skill script failed.",
@@ -156,6 +179,7 @@ export async function runSkill(name, args) {
156
179
  code: result.code,
157
180
  };
158
181
  }
182
+ record(true);
159
183
  return {
160
184
  ok: true,
161
185
  stdout: result.stdout,
@@ -163,6 +187,7 @@ export async function runSkill(name, args) {
163
187
  code: result.code,
164
188
  };
165
189
  }
190
+ record(false);
166
191
  return { ok: false, error: "Unknown skill execution type." };
167
192
  }
168
193
  export async function verifySkill(name) {
@@ -1,8 +1,10 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
1
3
  import { loadConfig } from "../config/config.js";
2
4
  import { MANAGED_SKILLS_DIR, resolveUserPath } from "../utils.js";
3
- import { DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace.js";
4
- import { buildWorkspaceSkillStatus } from "./skills-status.js";
5
5
  import { loadWorkspaceSkillEntries } from "./skills.js";
6
+ import { buildWorkspaceSkillStatus } from "./skills-status.js";
7
+ import { DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace.js";
6
8
  function loadSkillEntries() {
7
9
  const config = loadConfig();
8
10
  const workspaceDir = resolveUserPath(config.agent?.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR);
@@ -13,31 +15,132 @@ function loadSkillEntries() {
13
15
  });
14
16
  return { config, workspaceDir, managedSkillsDir, entries };
15
17
  }
16
- export async function getSkillStats(name) {
18
+ function resolveUsageLogPath(name) {
19
+ return path.join(MANAGED_SKILLS_DIR, name, "usage.log");
20
+ }
21
+ function parseUsageEntry(line) {
22
+ try {
23
+ const parsed = JSON.parse(line);
24
+ if (typeof parsed?.ts !== "number")
25
+ return null;
26
+ if (parsed.event !== "use" && parsed.event !== "run")
27
+ return null;
28
+ if (typeof parsed.ok !== "boolean")
29
+ return null;
30
+ return parsed;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ function readUsageEntries(name, sinceMs) {
37
+ const logPath = resolveUsageLogPath(name);
38
+ try {
39
+ const raw = fs.readFileSync(logPath, "utf-8");
40
+ const entries = [];
41
+ for (const line of raw.split(/\r?\n/)) {
42
+ if (!line.trim())
43
+ continue;
44
+ const entry = parseUsageEntry(line);
45
+ if (!entry)
46
+ continue;
47
+ if (sinceMs && entry.ts < sinceMs)
48
+ continue;
49
+ entries.push(entry);
50
+ }
51
+ return entries;
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
57
+ function hasUsageEntries(name) {
58
+ const logPath = resolveUsageLogPath(name);
59
+ try {
60
+ const stat = fs.statSync(logPath);
61
+ return stat.isFile() && stat.size > 0;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ export function hasSkillUsage(name) {
68
+ return hasUsageEntries(name);
69
+ }
70
+ export function readSkillUsageEntries(name, opts) {
71
+ const entries = readUsageEntries(name, opts?.sinceMs);
72
+ if (opts?.limit && opts.limit > 0) {
73
+ return entries.slice(-opts.limit);
74
+ }
75
+ return entries;
76
+ }
77
+ export function recordSkillUsage(name, entry) {
78
+ const logPath = resolveUsageLogPath(name);
79
+ try {
80
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
81
+ fs.appendFileSync(logPath, `${JSON.stringify(entry)}\n`, "utf-8");
82
+ }
83
+ catch {
84
+ // ignore logging failures
85
+ }
86
+ }
87
+ export async function getSkillStats(name, opts) {
17
88
  const { entries } = loadSkillEntries();
18
89
  const entry = entries.find((item) => item.skill.name.toLowerCase() === name.toLowerCase());
19
90
  if (!entry)
20
91
  return null;
92
+ const sinceMs = opts?.windowDays && opts.windowDays > 0
93
+ ? Date.now() - opts.windowDays * 24 * 60 * 60 * 1000
94
+ : undefined;
95
+ const entriesUsed = readUsageEntries(entry.skill.name, sinceMs);
96
+ const runs = entriesUsed.length;
97
+ const errors = entriesUsed.filter((item) => !item.ok).length;
98
+ const lastTs = entriesUsed.reduce((max, item) => Math.max(max, item.ts), 0);
99
+ const durations = entriesUsed
100
+ .map((item) => item.durationMs)
101
+ .filter((val) => typeof val === "number" && val >= 0);
102
+ const avgDurationMs = durations.length > 0
103
+ ? Math.round(durations.reduce((sum, val) => sum + val, 0) / durations.length)
104
+ : undefined;
21
105
  return {
22
- runs: 0,
23
- errors: 0,
24
- lastUsed: undefined,
25
- avgDurationMs: undefined,
106
+ runs,
107
+ errors,
108
+ lastUsed: lastTs ? new Date(lastTs).toISOString() : undefined,
109
+ avgDurationMs,
26
110
  };
27
111
  }
28
- export async function getAggregateStats() {
112
+ export async function getAggregateStats(opts) {
29
113
  const { config, workspaceDir, managedSkillsDir, entries } = loadSkillEntries();
30
114
  const status = buildWorkspaceSkillStatus(workspaceDir, {
31
115
  config,
32
116
  managedSkillsDir,
33
117
  entries,
34
118
  });
119
+ const sinceMs = opts?.windowDays && opts.windowDays > 0
120
+ ? Date.now() - opts.windowDays * 24 * 60 * 60 * 1000
121
+ : undefined;
122
+ const usageCounts = [];
123
+ let totalRuns = 0;
124
+ for (const entry of entries) {
125
+ const skillRuns = readUsageEntries(entry.skill.name, sinceMs).length;
126
+ if (skillRuns > 0) {
127
+ usageCounts.push({ name: entry.skill.name, runs: skillRuns });
128
+ }
129
+ totalRuns += skillRuns;
130
+ }
131
+ usageCounts.sort((a, b) => b.runs - a.runs);
132
+ const mostUsed = usageCounts[0];
133
+ const limit = typeof opts?.limit === "number" && opts.limit > 0 ? opts.limit : 5;
134
+ const topUsed = usageCounts.slice(0, limit);
35
135
  return {
36
- totalRuns: 0,
136
+ totalRuns,
37
137
  activeSkills: entries.length,
38
- mostUsed: undefined,
138
+ mostUsed: mostUsed
139
+ ? { name: mostUsed.name, runs: mostUsed.runs }
140
+ : undefined,
141
+ topUsed: topUsed.map((item) => ({ name: item.name, runs: item.runs })),
39
142
  readyButUnused: status.skills
40
- .filter((skill) => skill.eligible)
143
+ .filter((skill) => skill.eligible && !hasUsageEntries(skill.name))
41
144
  .map((skill) => skill.name),
42
145
  };
43
146
  }
@@ -1,5 +1,4 @@
1
- import path from "node:path";
2
- import { CONFIG_DIR } from "../utils.js";
1
+ import { MANAGED_SKILLS_DIR } from "../utils.js";
3
2
  import { getSkillMetadata, hasBinary, isBundledSkillAllowed, isConfigPathTruthy, loadWorkspaceSkillEntries, resolveBundledAllowlist, resolveConfigPath, resolveSkillConfig, resolveSkillsInstallPreferences, } from "./skills.js";
4
3
  function resolveSkillKey(entry) {
5
4
  return getSkillMetadata(entry)?.skillKey ?? entry.skill.name;
@@ -92,7 +91,8 @@ function buildSkillStatus(entry, config, prefs) {
92
91
  continue;
93
92
  if (skillConfig?.env?.[envName])
94
93
  continue;
95
- if (skillConfig?.apiKey && getSkillMetadata(entry)?.primaryEnv === envName) {
94
+ if (skillConfig?.apiKey &&
95
+ getSkillMetadata(entry)?.primaryEnv === envName) {
96
96
  continue;
97
97
  }
98
98
  missingEnv.push(envName);
@@ -146,7 +146,7 @@ function buildSkillStatus(entry, config, prefs) {
146
146
  };
147
147
  }
148
148
  export function buildWorkspaceSkillStatus(workspaceDir, opts) {
149
- const managedSkillsDir = opts?.managedSkillsDir ?? path.join(CONFIG_DIR, "skills");
149
+ const managedSkillsDir = opts?.managedSkillsDir ?? MANAGED_SKILLS_DIR;
150
150
  const skillEntries = opts?.entries ?? loadWorkspaceSkillEntries(workspaceDir, opts);
151
151
  const prefs = resolveSkillsInstallPreferences(opts?.config);
152
152
  return {
@@ -212,7 +212,9 @@ function resolveNexusMetadata(frontmatter) {
212
212
  const requiresRaw = typeof metadataObj.requires === "object" && metadataObj.requires !== null
213
213
  ? metadataObj.requires
214
214
  : undefined;
215
- const installRaw = Array.isArray(metadataObj.install) ? metadataObj.install : [];
215
+ const installRaw = Array.isArray(metadataObj.install)
216
+ ? metadataObj.install
217
+ : [];
216
218
  const install = installRaw
217
219
  .map((entry) => parseInstallSpec(entry))
218
220
  .filter((entry) => Boolean(entry));
@@ -223,11 +225,19 @@ function resolveNexusMetadata(frontmatter) {
223
225
  : undefined;
224
226
  const provides = normalizeStringList(metadataObj.provides);
225
227
  return {
226
- always: typeof metadataObj.always === "boolean" ? metadataObj.always : undefined,
228
+ always: typeof metadataObj.always === "boolean"
229
+ ? metadataObj.always
230
+ : undefined,
227
231
  emoji: typeof metadataObj.emoji === "string" ? metadataObj.emoji : undefined,
228
- homepage: typeof metadataObj.homepage === "string" ? metadataObj.homepage : undefined,
229
- skillKey: typeof metadataObj.skillKey === "string" ? metadataObj.skillKey : undefined,
230
- primaryEnv: typeof metadataObj.primaryEnv === "string" ? metadataObj.primaryEnv : undefined,
232
+ homepage: typeof metadataObj.homepage === "string"
233
+ ? metadataObj.homepage
234
+ : undefined,
235
+ skillKey: typeof metadataObj.skillKey === "string"
236
+ ? metadataObj.skillKey
237
+ : undefined,
238
+ primaryEnv: typeof metadataObj.primaryEnv === "string"
239
+ ? metadataObj.primaryEnv
240
+ : undefined,
231
241
  os: osRaw.length > 0 ? osRaw : undefined,
232
242
  type,
233
243
  provides: provides.length > 0 ? provides : undefined,
@@ -251,7 +261,7 @@ export function getSkillMetadata(entry) {
251
261
  return entry.nexus;
252
262
  }
253
263
  function resolveSkillKey(skill, entry) {
254
- return entry ? getSkillMetadata(entry)?.skillKey ?? skill.name : skill.name;
264
+ return entry ? (getSkillMetadata(entry)?.skillKey ?? skill.name) : skill.name;
255
265
  }
256
266
  function shouldIncludeSkill(params) {
257
267
  const { entry, config } = params;
@@ -289,7 +299,8 @@ function shouldIncludeSkill(params) {
289
299
  continue;
290
300
  if (skillConfig?.env?.[envName])
291
301
  continue;
292
- if (skillConfig?.apiKey && getSkillMetadata(entry)?.primaryEnv === envName) {
302
+ if (skillConfig?.apiKey &&
303
+ getSkillMetadata(entry)?.primaryEnv === envName) {
293
304
  continue;
294
305
  }
295
306
  return false;
@@ -2,26 +2,31 @@ import crypto from "node:crypto";
2
2
  import { loadConfig, STATE_DIR_NEXUS } from "../config/config.js";
3
3
  import { callGateway } from "../gateway/call.js";
4
4
  import { normalizeAgentId, parseAgentSessionKey, resolveAgentIdFromSessionKey, } from "../routing/session-key.js";
5
- import { resolveDisplaySessionKey, resolveInternalSessionKey, resolveMainSessionAlias, stripToolMessages, } from "./tools/sessions-helpers.js";
6
- import { resolveAnnounceTarget } from "./tools/sessions-announce-target.js";
5
+ import { createFileSubagentStore, } from "./subagent-registry.store.js";
7
6
  import { readLatestAssistantReply, runAgentStep } from "./tools/agent-step.js";
7
+ import { resolveAnnounceTarget } from "./tools/sessions-announce-target.js";
8
+ import { resolveDisplaySessionKey, resolveInternalSessionKey, resolveMainSessionAlias, stripToolMessages, } from "./tools/sessions-helpers.js";
8
9
  import { isAnnounceSkip } from "./tools/sessions-send-helpers.js";
9
- import { createFileSubagentStore, } from "./subagent-registry.store.js";
10
10
  function normalizeStoredEntry(entry) {
11
11
  if (!entry || typeof entry !== "object")
12
12
  return null;
13
13
  const sessionKey = typeof entry.sessionKey === "string" ? entry.sessionKey.trim() : "";
14
- const parentSessionKey = typeof entry.parentSessionKey === "string" ? entry.parentSessionKey.trim() : "";
14
+ const parentSessionKey = typeof entry.parentSessionKey === "string"
15
+ ? entry.parentSessionKey.trim()
16
+ : "";
15
17
  if (!sessionKey || !parentSessionKey)
16
18
  return null;
17
- const status = entry.status === "announced" || entry.status === "failed" || entry.status === "pending"
19
+ const status = entry.status === "announced" ||
20
+ entry.status === "failed" ||
21
+ entry.status === "pending"
18
22
  ? entry.status
19
23
  : "pending";
20
24
  const createdAt = typeof entry.createdAt === "string" && entry.createdAt.trim()
21
25
  ? entry.createdAt
22
26
  : new Date().toISOString();
23
27
  const agentId = normalizeAgentId(entry.agentId);
24
- const legacyRequester = entry.requesterProvider;
28
+ const legacyRequester = entry
29
+ .requesterProvider;
25
30
  return {
26
31
  ...entry,
27
32
  agentId,
@@ -36,8 +41,12 @@ function buildSubagentAnnouncePrompt(params) {
36
41
  const taskLine = params.task?.trim() ? params.task.trim() : "(not available)";
37
42
  const lines = [
38
43
  "Sub-agent announce step:",
39
- params.requesterSessionKey ? `Requester session: ${params.requesterSessionKey}.` : undefined,
40
- params.requesterChannel ? `Requester channel: ${params.requesterChannel}.` : undefined,
44
+ params.requesterSessionKey
45
+ ? `Requester session: ${params.requesterSessionKey}.`
46
+ : undefined,
47
+ params.requesterChannel
48
+ ? `Requester channel: ${params.requesterChannel}.`
49
+ : undefined,
41
50
  `Post target channel: ${params.announceChannel}.`,
42
51
  `Original task: ${taskLine}`,
43
52
  params.subagentReply
@@ -94,7 +103,9 @@ async function readSubagentTask(sessionKey) {
94
103
  }
95
104
  async function runSubagentAnnounceFlow(params) {
96
105
  let status = "pending";
97
- const timeoutMs = typeof params.timeoutMs === "number" && params.timeoutMs > 0 ? params.timeoutMs : 30_000;
106
+ const timeoutMs = typeof params.timeoutMs === "number" && params.timeoutMs > 0
107
+ ? params.timeoutMs
108
+ : 30_000;
98
109
  try {
99
110
  let reply = params.roundOneReply?.trim();
100
111
  if (!reply) {
@@ -140,7 +151,9 @@ async function runSubagentAnnounceFlow(params) {
140
151
  timeoutMs,
141
152
  lane: "nested",
142
153
  });
143
- if (!announceReply || !announceReply.trim() || isAnnounceSkip(announceReply)) {
154
+ if (!announceReply ||
155
+ !announceReply.trim() ||
156
+ isAnnounceSkip(announceReply)) {
144
157
  status = "announced";
145
158
  return status;
146
159
  }
@@ -178,7 +191,8 @@ async function runSubagentAnnounceFlow(params) {
178
191
  }
179
192
  }
180
193
  export function createSubagentRegistry(params) {
181
- const store = params?.store ?? createFileSubagentStore(params?.stateDir ?? STATE_DIR_NEXUS);
194
+ const store = params?.store ??
195
+ createFileSubagentStore(params?.stateDir ?? STATE_DIR_NEXUS);
182
196
  const entries = new Map();
183
197
  let initialized = false;
184
198
  let resumeInFlight = null;
@@ -82,6 +82,7 @@ export function buildAgentSystemPromptAppend(params) {
82
82
  const userTimezone = params.userTimezone?.trim();
83
83
  const userTime = params.userTime?.trim();
84
84
  const runtimeInfo = params.runtimeInfo;
85
+ const reactionGuidance = params.reactionGuidance;
85
86
  const runtimeLines = [];
86
87
  if (runtimeInfo?.host)
87
88
  runtimeLines.push(`Host: ${runtimeInfo.host}`);
@@ -124,6 +125,12 @@ export function buildAgentSystemPromptAppend(params) {
124
125
  : "",
125
126
  "TOOLS.md does not control tool availability; it is user guidance for how to use external tools.",
126
127
  "",
128
+ "## Nexus CLI Quick Reference",
129
+ "- nexus status: show overall status",
130
+ "- nexus gateway restart: restart the gateway process",
131
+ "- nexus usage upload: upload usage batches",
132
+ "Do not invent commands; run `nexus help` to list available commands.",
133
+ "",
127
134
  "## Workspace",
128
135
  `Your working directory is: ${params.workspaceDir}`,
129
136
  "Treat this directory as the single global workspace for file operations unless explicitly instructed otherwise.",
@@ -167,6 +174,15 @@ export function buildAgentSystemPromptAppend(params) {
167
174
  "- [[reply_to:<id>]] replies to a specific message id when you have it.",
168
175
  "Tags are stripped before sending; support depends on the current provider config.",
169
176
  "",
177
+ reactionGuidance && reactionGuidance.level && reactionGuidance.level !== "off"
178
+ ? "## Reactions"
179
+ : "",
180
+ reactionGuidance && reactionGuidance.level && reactionGuidance.level !== "off"
181
+ ? `Reactions are enabled for ${reactionGuidance.channel ?? "this channel"} in ${reactionGuidance.level.toUpperCase()} mode.`
182
+ : "",
183
+ reactionGuidance && reactionGuidance.level && reactionGuidance.level !== "off"
184
+ ? ""
185
+ : "",
170
186
  ];
171
187
  if (extraSystemPrompt) {
172
188
  lines.push("## Group Chat Context", extraSystemPrompt, "");
@@ -10,7 +10,12 @@ export const TOOL_GROUPS = {
10
10
  // Host/runtime execution tools
11
11
  "group:runtime": ["exec", "process"],
12
12
  // Session management tools
13
- "group:sessions": ["sessions_list", "sessions_history", "sessions_send", "sessions_spawn"],
13
+ "group:sessions": [
14
+ "sessions_list",
15
+ "sessions_history",
16
+ "sessions_send",
17
+ "sessions_spawn",
18
+ ],
14
19
  // UI helpers
15
20
  "group:ui": ["browser", "canvas"],
16
21
  // Automation + infra
@@ -43,10 +48,21 @@ const TOOL_PROFILES = {
43
48
  allow: ["sessions_list"],
44
49
  },
45
50
  coding: {
46
- allow: ["group:fs", "group:runtime", "group:sessions", "group:memory", "image"],
51
+ allow: [
52
+ "group:fs",
53
+ "group:runtime",
54
+ "group:sessions",
55
+ "group:memory",
56
+ "image",
57
+ ],
47
58
  },
48
59
  messaging: {
49
- allow: ["group:messaging", "sessions_list", "sessions_history", "sessions_send"],
60
+ allow: [
61
+ "group:messaging",
62
+ "sessions_list",
63
+ "sessions_history",
64
+ "sessions_send",
65
+ ],
50
66
  },
51
67
  full: {},
52
68
  };
@@ -1,8 +1,8 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import { browserCloseTab, browserFocusTab, browserOpenTab, browserSnapshot, browserStart, browserStatus, browserStop, browserTabs, } from "../../browser/client.js";
3
3
  import { browserAct, browserArmDialog, browserArmFileChooser, browserConsoleMessages, browserNavigate, browserPdfSave, browserScreenshotAction, } from "../../browser/client-actions.js";
4
- import { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "../../browser/constants.js";
5
4
  import { resolveBrowserConfig } from "../../browser/config.js";
5
+ import { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "../../browser/constants.js";
6
6
  import { loadConfig } from "../../config/config.js";
7
7
  import { imageResultFromFile, jsonResult, readStringParam, } from "./common.js";
8
8
  const BrowserActSchema = Type.Union([
@@ -178,11 +178,14 @@ export function createBrowserTool(opts) {
178
178
  params.maxChars > 0
179
179
  ? Math.floor(params.maxChars)
180
180
  : undefined;
181
- const resolvedMaxChars = format === "ai" ? maxChars ?? DEFAULT_AI_SNAPSHOT_MAX_CHARS : undefined;
181
+ const resolvedMaxChars = format === "ai"
182
+ ? (maxChars ?? DEFAULT_AI_SNAPSHOT_MAX_CHARS)
183
+ : undefined;
182
184
  const snapshot = await browserSnapshot(baseUrl, {
183
185
  format,
184
186
  targetId,
185
187
  limit,
188
+ maxChars: resolvedMaxChars,
186
189
  });
187
190
  if (snapshot.format === "ai") {
188
191
  return {