@intent-systems/nexus 2026.1.5-4 → 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 (123) 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 +46 -15
  35. package/dist/capabilities/registry.js +2 -1
  36. package/dist/cli/cloud-cli.js +12 -7
  37. package/dist/cli/credential-cli.js +139 -17
  38. package/dist/cli/gateway-cli.js +1 -1
  39. package/dist/cli/log-cli.js +25 -0
  40. package/dist/cli/pairing-cli.js +1 -1
  41. package/dist/cli/program.js +58 -6
  42. package/dist/cli/run-main.js +1 -1
  43. package/dist/cli/skills-cli.js +144 -21
  44. package/dist/cli/skills-hub-cli.js +59 -29
  45. package/dist/cli/tool-connector-cli.js +99 -24
  46. package/dist/cli/upstream-sync-cli.js +253 -96
  47. package/dist/cli/usage-cli.js +14 -0
  48. package/dist/commands/auth-choice-options.js +6 -1
  49. package/dist/commands/auth-choice.js +157 -5
  50. package/dist/commands/bootstrap-preset.js +10 -6
  51. package/dist/commands/capabilities.js +33 -6
  52. package/dist/commands/claude-md.js +3 -2
  53. package/dist/commands/config-view.js +1 -1
  54. package/dist/commands/configure.js +4 -4
  55. package/dist/commands/credential.js +497 -36
  56. package/dist/commands/cursor-rules.js +39 -19
  57. package/dist/commands/doctor.js +5 -4
  58. package/dist/commands/identity.js +28 -31
  59. package/dist/commands/init.js +15 -18
  60. package/dist/commands/log.js +134 -0
  61. package/dist/commands/models/fallbacks.js +1 -1
  62. package/dist/commands/models/image-fallbacks.js +1 -1
  63. package/dist/commands/models/list.js +1 -1
  64. package/dist/commands/models/scan.js +1 -1
  65. package/dist/commands/onboard-auth.js +27 -2
  66. package/dist/commands/onboard-eve-identity.js +7 -8
  67. package/dist/commands/onboard-non-interactive.js +4 -2
  68. package/dist/commands/onboard-quickstart.js +18 -11
  69. package/dist/commands/quest-state.js +271 -0
  70. package/dist/commands/quest.js +53 -13
  71. package/dist/commands/reset.js +1 -1
  72. package/dist/commands/sessions-ingest.js +5 -4
  73. package/dist/commands/setup.js +4 -2
  74. package/dist/commands/skills-manifest.js +2 -2
  75. package/dist/commands/status.js +179 -61
  76. package/dist/commands/suggestions.js +1 -1
  77. package/dist/commands/usage-tracking.js +32 -0
  78. package/dist/commands/usage-upload.js +6 -1
  79. package/dist/config/defaults.js +1 -3
  80. package/dist/config/includes.js +5 -7
  81. package/dist/config/io.js +88 -16
  82. package/dist/config/legacy.js +4 -2
  83. package/dist/config/paths.js +16 -0
  84. package/dist/config/sessions.js +9 -5
  85. package/dist/config/zod-schema.js +4 -3
  86. package/dist/control-plane/broker/broker.js +131 -78
  87. package/dist/control-plane/compaction.js +3 -5
  88. package/dist/control-plane/factory.js +2 -2
  89. package/dist/control-plane/index.js +2 -2
  90. package/dist/control-plane/odu/agents.js +28 -23
  91. package/dist/control-plane/odu/interaction-tools.js +62 -50
  92. package/dist/control-plane/odu/prompt-loader.js +8 -8
  93. package/dist/control-plane/odu/runtime.js +87 -75
  94. package/dist/control-plane/odu-control-plane.js +14 -12
  95. package/dist/control-plane/single-agent.js +13 -13
  96. package/dist/credentials/store.js +133 -7
  97. package/dist/gateway/server-browser.js +5 -4
  98. package/dist/gateway/server-methods/cron.js +11 -1
  99. package/dist/gateway/server.js +14 -7
  100. package/dist/infra/bonjour.js +1 -1
  101. package/dist/infra/event-log.js +8 -2
  102. package/dist/infra/path-env.js +1 -2
  103. package/dist/infra/provider-usage.auth.js +5 -3
  104. package/dist/infra/provider-usage.fetch.claude.js +16 -6
  105. package/dist/infra/provider-usage.fetch.minimax.js +8 -3
  106. package/dist/infra/provider-usage.js +9 -5
  107. package/dist/infra/restart.js +2 -2
  108. package/dist/infra/usage-settings.js +78 -0
  109. package/dist/infra/usage-suggestions.js +17 -5
  110. package/dist/infra/usage-upload.js +38 -1
  111. package/dist/infra/voicewake.js +2 -2
  112. package/dist/media/image-ops.js +3 -1
  113. package/dist/memory/index.js +2 -381
  114. package/dist/pairing/pairing-store.js +24 -0
  115. package/dist/providers/github-copilot-auth.js +1 -1
  116. package/dist/routing/resolve-route.js +6 -6
  117. package/dist/routing/session-key.js +3 -1
  118. package/dist/sessions/send-policy.js +5 -5
  119. package/dist/slack/monitor.js +22 -1
  120. package/dist/telegram/reaction-level.js +2 -1
  121. package/dist/utils.js +4 -3
  122. package/dist/wizard/onboarding.js +29 -7
  123. package/package.json +1 -1
@@ -1,18 +1,92 @@
1
1
  import { complete, } from "@mariozechner/pi-ai";
2
2
  import { discoverAuthStorage, discoverModels, } from "@mariozechner/pi-coding-agent";
3
3
  import { Type } from "@sinclair/typebox";
4
+ import { listCredentialEntriesSync } from "../../credentials/store.js";
4
5
  import { resolveUserPath } from "../../utils.js";
5
6
  import { loadWebMedia } from "../../web/media.js";
6
- import { getApiKeyForModel } from "../model-auth.js";
7
+ import { DEFAULT_PROVIDER } from "../defaults.js";
8
+ import { getApiKeyForModel, getCustomProviderApiKey, resolveEnvApiKey, } from "../model-auth.js";
7
9
  import { runWithImageModelFallback } from "../model-fallback.js";
10
+ import { normalizeProviderId, parseModelRef } from "../model-selection.js";
8
11
  import { ensureNexusModelsJson } from "../models-config.js";
9
12
  import { extractAssistantText } from "../pi-embedded-utils.js";
10
13
  const DEFAULT_PROMPT = "Describe the image.";
11
- function ensureImageToolConfigured(cfg) {
12
- const imageModel = cfg?.agent?.imageModel;
13
- const primary = typeof imageModel === "string" ? imageModel.trim() : imageModel?.primary;
14
- const fallbacks = typeof imageModel === "object" ? (imageModel?.fallbacks ?? []) : [];
15
- return Boolean(primary?.trim() || fallbacks.length > 0);
14
+ const DEFAULT_IMAGE_MODELS = {
15
+ minimax: "minimax-vl-01",
16
+ openai: "gpt-5-mini",
17
+ anthropic: "claude-opus-4-5",
18
+ };
19
+ function normalizeImageModelConfig(value) {
20
+ if (typeof value === "string") {
21
+ const primary = value.trim();
22
+ return primary ? { primary } : null;
23
+ }
24
+ if (!value || typeof value !== "object")
25
+ return null;
26
+ const primary = typeof value.primary === "string" ? value.primary.trim() : "";
27
+ const fallbacks = Array.isArray(value.fallbacks)
28
+ ? value.fallbacks.map((raw) => String(raw ?? "").trim()).filter(Boolean)
29
+ : [];
30
+ if (!primary && fallbacks.length === 0)
31
+ return null;
32
+ return fallbacks.length > 0 ? { primary, fallbacks } : { primary };
33
+ }
34
+ function hasAuthForProvider(provider, cfg) {
35
+ const normalized = normalizeProviderId(provider);
36
+ if (resolveEnvApiKey(normalized))
37
+ return true;
38
+ if (getCustomProviderApiKey(cfg, normalized))
39
+ return true;
40
+ const entries = listCredentialEntriesSync();
41
+ return entries.some((entry) => normalizeProviderId(entry.service) === normalized);
42
+ }
43
+ function resolveImageModelFromConfig(cfg, provider) {
44
+ const providers = cfg?.models?.providers ?? {};
45
+ const entry = providers[provider];
46
+ for (const model of entry?.models ?? []) {
47
+ if (!model?.id)
48
+ continue;
49
+ if (Array.isArray(model.input) && model.input.includes("image")) {
50
+ return model.id;
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+ export function resolveImageModelConfigForTool(params) {
56
+ void params.agentDir;
57
+ const explicit = normalizeImageModelConfig(params.cfg?.agent?.imageModel);
58
+ if (explicit)
59
+ return explicit;
60
+ const rawPrimary = typeof params.cfg?.agent?.model === "string"
61
+ ? params.cfg?.agent?.model
62
+ : params.cfg?.agent?.model?.primary;
63
+ if (!rawPrimary?.trim())
64
+ return null;
65
+ const parsed = parseModelRef(rawPrimary, DEFAULT_PROVIDER);
66
+ if (!parsed)
67
+ return null;
68
+ const provider = normalizeProviderId(parsed.provider);
69
+ const configImageModel = resolveImageModelFromConfig(params.cfg, provider);
70
+ const mapped = DEFAULT_IMAGE_MODELS[provider];
71
+ const primaryModelId = configImageModel ?? mapped;
72
+ if (!primaryModelId)
73
+ return null;
74
+ if (!hasAuthForProvider(provider, params.cfg))
75
+ return null;
76
+ const fallbacks = [];
77
+ for (const fallbackProvider of ["openai", "anthropic"]) {
78
+ if (fallbackProvider === provider)
79
+ continue;
80
+ const fallbackModelId = DEFAULT_IMAGE_MODELS[fallbackProvider];
81
+ if (!fallbackModelId)
82
+ continue;
83
+ if (hasAuthForProvider(fallbackProvider, params.cfg)) {
84
+ fallbacks.push(`${fallbackProvider}/${fallbackModelId}`);
85
+ }
86
+ }
87
+ return fallbacks.length > 0
88
+ ? { primary: `${provider}/${primaryModelId}`, fallbacks }
89
+ : { primary: `${provider}/${primaryModelId}` };
16
90
  }
17
91
  function pickMaxBytes(cfg, maxBytesMb) {
18
92
  if (typeof maxBytesMb === "number" &&
@@ -80,12 +154,23 @@ async function runImagePrompt(params) {
80
154
  };
81
155
  }
82
156
  export function createImageTool(options) {
83
- if (!ensureImageToolConfigured(options?.config))
157
+ const resolvedConfig = resolveImageModelConfigForTool({
158
+ cfg: options?.config,
159
+ agentDir: options?.agentDir,
160
+ });
161
+ if (!resolvedConfig)
84
162
  return null;
85
163
  const agentDir = options?.agentDir;
86
164
  if (!agentDir?.trim()) {
87
165
  throw new Error("createImageTool requires agentDir when enabled");
88
166
  }
167
+ const cfgWithImageModel = (() => {
168
+ const cfg = options?.config ?? {};
169
+ const agent = cfg.agent ?? {};
170
+ if (normalizeImageModelConfig(agent.imageModel))
171
+ return cfg;
172
+ return { ...cfg, agent: { ...agent, imageModel: resolvedConfig } };
173
+ })();
89
174
  return {
90
175
  label: "Image",
91
176
  name: "image",
@@ -121,7 +206,7 @@ export function createImageTool(options) {
121
206
  const mimeType = media.contentType ?? "image/png";
122
207
  const base64 = media.buffer.toString("base64");
123
208
  const result = await runImagePrompt({
124
- cfg: options?.config,
209
+ cfg: cfgWithImageModel,
125
210
  agentDir,
126
211
  modelOverride,
127
212
  prompt: promptRaw,
@@ -20,7 +20,11 @@ export async function resolveAnnounceTarget(params) {
20
20
  const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
21
21
  const match = sessions.find((entry) => entry?.key === params.sessionKey) ??
22
22
  sessions.find((entry) => entry?.key === params.displayKey);
23
- const provider = typeof match?.lastProvider === "string" ? match.lastProvider : undefined;
23
+ const provider = typeof match?.lastChannel === "string"
24
+ ? match.lastChannel
25
+ : typeof match?.lastProvider === "string"
26
+ ? match.lastProvider
27
+ : undefined;
24
28
  const to = typeof match?.lastTo === "string" ? match.lastTo : undefined;
25
29
  const accountId = typeof match?.lastAccountId === "string"
26
30
  ? match.lastAccountId
@@ -2,8 +2,8 @@ import fs from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { resolveStateDir } from "../config/paths.js";
6
- import { NEXUS_ROOT, resolveUserPath } from "../utils.js";
5
+ import { resolveBootstrapPath, resolveStateDir } from "../config/paths.js";
6
+ import { MANAGED_SKILLS_DIR, NEXUS_ROOT, resolveUserPath } from "../utils.js";
7
7
  export function resolveDefaultAgentWorkspaceDir(env = process.env, homedir = os.homedir) {
8
8
  const profile = env.NEXUS_PROFILE?.trim();
9
9
  if (profile && profile.toLowerCase() !== "default") {
@@ -68,47 +68,36 @@ It does not define which tools exist; Nexus provides built-in tools internally.
68
68
 
69
69
  Add whatever else you want the assistant to know about your local toolchain.
70
70
  `;
71
- const DEFAULT_BOOTSTRAP_TEMPLATE = `# BOOTSTRAP.md - First Run Ritual
71
+ const DEFAULT_BOOTSTRAP_TEMPLATE = `# BOOTSTRAP.md - Welcome to Nexus
72
72
 
73
- Hello. I was just born.
73
+ This workspace needs identity setup before agents can help.
74
74
 
75
- ## Your mission
76
- Start a short, playful conversation and learn:
77
- - Who am I?
78
- - What am I?
79
- - Who are you?
80
- - How should I call you?
75
+ ## For humans
76
+ Start a short conversation with your AI assistant. Share:
77
+ - Your name (and how you'd like to be addressed)
78
+ - What you're hoping to accomplish with AI
79
+ - Any preferences for tone, boundaries, or style
81
80
 
82
- ## How to ask (cute + helpful)
83
- Say:
84
- "Hello! I was just born. Who am I? What am I? Who are you? How should I call you?"
81
+ The agent will write identity files as you chat (takes ~5 min).
85
82
 
86
- Then offer suggestions:
87
- - 3-5 name ideas.
88
- - 3-5 creature/vibe combos.
89
- - 5 emoji ideas.
83
+ ## For agents
84
+ You are beginning the bootstrap conversation. Goals:
90
85
 
91
- ## Write these files
92
- After the user chooses, update:
86
+ 1) Learn the user's name
87
+ Write: state/user/identity/PROFILE.md
93
88
 
94
- 1) state/agents/{agent}/identity/IDENTITY.md
95
- - Name
96
- - Creature
97
- - Vibe
98
- - Emoji
89
+ 2) Let the user name you
90
+ Write: state/agents/{agent}/identity/IDENTITY.md
99
91
 
100
- 2) state/user/identity/PROFILE.md
101
- - Name
102
- - Preferred address
103
- - Pronouns (optional)
104
- - Timezone (optional)
105
- - Notes
92
+ 3) Define personality and boundaries
93
+ Write: state/agents/{agent}/identity/SOUL.md
106
94
 
107
- 3) state/agents/{agent}/identity/SOUL.md
108
- - Values, boundaries, personality
95
+ 4) Initialize long-term memory
96
+ Write: state/agents/{agent}/identity/MEMORY.md
109
97
 
110
- 4) state/nexus/config.json
111
- Set identity.name, identity.theme, identity.emoji to match IDENTITY.md.
98
+ Be warm, curious, and conversational. Don't rush a checklist.
99
+
100
+ When complete, run \`nexus status\` to show what's unlocked.
112
101
  `;
113
102
  const DEFAULT_IDENTITY_TEMPLATE = `# IDENTITY.md - Agent Identity
114
103
 
@@ -125,6 +114,11 @@ const DEFAULT_USER_TEMPLATE = `# PROFILE.md - User Profile
125
114
  - Timezone (optional):
126
115
  - Notes:
127
116
  `;
117
+ const DEFAULT_MEMORY_TEMPLATE = `# MEMORY.md - Long-Term Memory
118
+
119
+ This file stores distilled, long-term insights about the user and context.
120
+ Keep it concise and updated as you learn important facts.
121
+ `;
128
122
  const TEMPLATE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../docs/templates");
129
123
  function stripFrontMatter(content) {
130
124
  if (!content.startsWith("---"))
@@ -165,42 +159,52 @@ export async function ensureAgentWorkspace(params) {
165
159
  ? params.dir.trim()
166
160
  : DEFAULT_AGENT_WORKSPACE_DIR;
167
161
  const dir = resolveUserPath(rawDir);
162
+ const createHomeLayout = params?.createHomeLayout ?? false;
168
163
  await fs.mkdir(dir, { recursive: true });
169
- await fs.mkdir(path.join(dir, "memory"), { recursive: true });
170
- await fs.mkdir(path.join(dir, "projects"), { recursive: true });
164
+ if (createHomeLayout) {
165
+ await fs.mkdir(path.join(dir, "memory"), { recursive: true });
166
+ await fs.mkdir(path.join(dir, "projects"), { recursive: true });
167
+ }
168
+ await fs.mkdir(NEXUS_ROOT, { recursive: true });
171
169
  const stateDir = resolveStateDir();
172
170
  const nexusStateDir = path.join(stateDir, "nexus");
173
171
  const agentId = process.env.NEXUS_AGENT_ID?.trim() || "default";
174
172
  const agentIdentityDir = path.join(stateDir, "agents", agentId, "identity");
175
173
  const userIdentityDir = path.join(stateDir, "user", "identity");
176
- const skillsStateDir = path.join(nexusStateDir, "skills");
177
- const onboardingDir = path.join(nexusStateDir, "onboarding");
174
+ const skillsStateDir = MANAGED_SKILLS_DIR;
178
175
  const credentialsDir = path.join(stateDir, "credentials");
179
176
  await fs.mkdir(nexusStateDir, { recursive: true });
180
177
  await fs.mkdir(agentIdentityDir, { recursive: true });
181
178
  await fs.mkdir(userIdentityDir, { recursive: true });
182
179
  await fs.mkdir(skillsStateDir, { recursive: true });
183
- await fs.mkdir(onboardingDir, { recursive: true });
184
180
  await fs.mkdir(credentialsDir, { recursive: true });
185
181
  const agentsPath = path.join(NEXUS_ROOT, DEFAULT_AGENTS_FILENAME);
186
182
  const soulPath = path.join(agentIdentityDir, DEFAULT_SOUL_FILENAME);
187
183
  const toolsPath = path.join(dir, DEFAULT_TOOLS_FILENAME);
188
184
  const identityPath = path.join(agentIdentityDir, DEFAULT_IDENTITY_FILENAME);
185
+ const memoryPath = path.join(agentIdentityDir, "MEMORY.md");
189
186
  const userPath = path.join(userIdentityDir, DEFAULT_USER_FILENAME);
190
- const bootstrapPath = path.join(onboardingDir, DEFAULT_BOOTSTRAP_FILENAME);
187
+ const bootstrapPath = resolveBootstrapPath();
191
188
  const agentsTemplate = await loadTemplate(DEFAULT_AGENTS_FILENAME, DEFAULT_AGENTS_TEMPLATE);
192
189
  await writeFileIfMissing(agentsPath, agentsTemplate);
193
190
  if (params?.ensureBootstrapFiles) {
191
+ const bootstrapMode = params.bootstrapMode ?? "full";
194
192
  const soulTemplate = await loadTemplate(DEFAULT_SOUL_FILENAME, DEFAULT_SOUL_TEMPLATE);
195
193
  const toolsTemplate = await loadTemplate(DEFAULT_TOOLS_FILENAME, DEFAULT_TOOLS_TEMPLATE);
196
194
  const identityTemplate = await loadTemplate(DEFAULT_IDENTITY_FILENAME, DEFAULT_IDENTITY_TEMPLATE);
197
195
  const userTemplate = await loadTemplate(DEFAULT_USER_FILENAME, DEFAULT_USER_TEMPLATE);
196
+ const memoryTemplate = DEFAULT_MEMORY_TEMPLATE;
198
197
  const bootstrapTemplate = await loadTemplate(DEFAULT_BOOTSTRAP_FILENAME, DEFAULT_BOOTSTRAP_TEMPLATE);
199
- await writeFileIfMissing(soulPath, soulTemplate);
200
- await writeFileIfMissing(toolsPath, toolsTemplate);
201
- await writeFileIfMissing(identityPath, identityTemplate);
202
- await writeFileIfMissing(userPath, userTemplate);
198
+ if (createHomeLayout) {
199
+ await writeFileIfMissing(toolsPath, toolsTemplate);
200
+ }
203
201
  await writeFileIfMissing(bootstrapPath, bootstrapTemplate);
202
+ await writeFileIfMissing(soulPath, soulTemplate);
203
+ if (bootstrapMode === "full") {
204
+ await writeFileIfMissing(identityPath, identityTemplate);
205
+ await writeFileIfMissing(memoryPath, memoryTemplate);
206
+ await writeFileIfMissing(userPath, userTemplate);
207
+ }
204
208
  }
205
209
  return {
206
210
  dir,
@@ -208,6 +212,7 @@ export async function ensureAgentWorkspace(params) {
208
212
  soulPath,
209
213
  toolsPath,
210
214
  identityPath,
215
+ memoryPath,
211
216
  userPath,
212
217
  bootstrapPath,
213
218
  };
@@ -218,7 +223,7 @@ export async function loadWorkspaceBootstrapFiles(dir) {
218
223
  const agentId = process.env.NEXUS_AGENT_ID?.trim() || "default";
219
224
  const agentIdentityDir = path.join(stateDir, "agents", agentId, "identity");
220
225
  const userIdentityDir = path.join(stateDir, "user", "identity");
221
- const onboardingDir = path.join(stateDir, "nexus", "onboarding");
226
+ const bootstrapPath = resolveBootstrapPath();
222
227
  const entries = [
223
228
  {
224
229
  name: DEFAULT_AGENTS_FILENAME,
@@ -236,13 +241,17 @@ export async function loadWorkspaceBootstrapFiles(dir) {
236
241
  name: DEFAULT_IDENTITY_FILENAME,
237
242
  filePath: path.join(agentIdentityDir, DEFAULT_IDENTITY_FILENAME),
238
243
  },
244
+ {
245
+ name: "MEMORY.md",
246
+ filePath: path.join(agentIdentityDir, "MEMORY.md"),
247
+ },
239
248
  {
240
249
  name: DEFAULT_USER_FILENAME,
241
250
  filePath: path.join(userIdentityDir, DEFAULT_USER_FILENAME),
242
251
  },
243
252
  {
244
253
  name: DEFAULT_BOOTSTRAP_FILENAME,
245
- filePath: path.join(onboardingDir, DEFAULT_BOOTSTRAP_FILENAME),
254
+ filePath: bootstrapPath,
246
255
  },
247
256
  ];
248
257
  const result = [];
@@ -1,10 +1,11 @@
1
- const CONTROL_COMMAND_RE = /(?:^|\s)\/(?:status|help|thinking|think|t|verbose|v|elevated|elev|model|queue|activation|send|restart|reset|new|compact)(?=$|\s|:)\b/i;
1
+ const CONTROL_COMMAND_RE = /(?:^|\s)\/(?:status|help|thinking|think|t|reasoning|reason|verbose|v|elevated|elev|model|queue|activation|send|restart|reset|new|compact)(?=$|\s|:)\b/i;
2
2
  const CONTROL_COMMAND_EXACT = new Set([
3
3
  "/help",
4
4
  "/status",
5
5
  "/restart",
6
6
  "/activation",
7
7
  "/send",
8
+ "/reasoning",
8
9
  "/reset",
9
10
  "/new",
10
11
  "/compact",
@@ -1,35 +1,45 @@
1
+ import { resolveNexusAgentDir } from "../../agents/agent-paths.js";
2
+ import { loadModelCatalog } from "../../agents/model-catalog.js";
1
3
  import { resolveAuthProfileDisplayLabel, resolveAuthStorePathForDisplay, } from "../../agents/auth-profiles.js";
2
4
  import { lookupContextTokens } from "../../agents/context.js";
3
5
  import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER, } from "../../agents/defaults.js";
4
6
  import { ensureAuthProfileStore, getCustomProviderApiKey, resolveAuthProfileOrder, resolveEnvApiKey, } from "../../agents/model-auth.js";
5
- import { buildModelAliasIndex, modelKey, normalizeProviderId, resolveConfiguredModelRef, resolveModelRefFromString, } from "../../agents/model-selection.js";
6
- import { resolveNexusAgentDir } from "../../agents/agent-paths.js";
7
+ import { buildModelAliasIndex, modelKey, normalizeProviderId, resolveConfiguredModelRef, resolveModelRefFromString, resolveThinkingDefault, } from "../../agents/model-selection.js";
7
8
  import { saveSessionStore } from "../../config/sessions.js";
8
9
  import { enqueueSystemEvent } from "../../infra/system-events.js";
9
10
  import { DEFAULT_AGENT_ID } from "../../routing/session-key.js";
10
11
  import { shortenHomePath } from "../../utils.js";
11
12
  import { extractModelDirective } from "../model.js";
12
- import { extractElevatedDirective, extractStatusDirective, extractThinkDirective, extractVerboseDirective, } from "./directives.js";
13
+ import { extractElevatedDirective, extractReasoningDirective, extractStatusDirective, extractThinkDirective, extractVerboseDirective, } from "./directives.js";
13
14
  import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
14
15
  import { resolveModelDirectiveSelection, } from "./model-selection.js";
15
16
  import { extractQueueDirective, } from "./queue.js";
16
17
  const SYSTEM_MARK = "⚙️";
17
18
  const formatOptionsLine = (options) => `Options: ${options}.`;
18
- const withOptions = (line, options) => `${line}\n${formatOptionsLine(options)}`;
19
- const formatElevatedRuntimeHint = () => `${SYSTEM_MARK} Runtime is direct; sandboxing does not apply.`;
19
+ const _withOptions = (line, options) => `${line}\n${formatOptionsLine(options)}`;
20
+ const _formatElevatedRuntimeHint = () => `${SYSTEM_MARK} Runtime is direct; sandboxing does not apply.`;
20
21
  const formatPath = (value) => shortenHomePath(value);
21
22
  const activeAgentId = DEFAULT_AGENT_ID;
22
- const formatElevatedEvent = (level) => level === "on"
23
+ const _formatElevatedEvent = (level) => level === "on"
23
24
  ? "Elevated ON — exec runs on host; set elevated:false to stay sandboxed."
24
25
  : "Elevated OFF — exec stays in sandbox.";
25
- const formatReasoningEvent = (level) => {
26
+ const _formatReasoningEvent = (level) => {
26
27
  if (level === "stream")
27
28
  return "Reasoning STREAM — emit live <think>.";
28
29
  if (level === "on")
29
30
  return "Reasoning ON — include <think>.";
30
31
  return "Reasoning OFF — hide <think>.";
31
32
  };
32
- function formatElevatedUnavailableText(params) {
33
+ const _formatListWithOr = (items) => {
34
+ if (items.length === 0)
35
+ return "";
36
+ if (items.length === 1)
37
+ return items[0] ?? "";
38
+ if (items.length === 2)
39
+ return `${items[0]} or ${items[1]}`;
40
+ return `${items.slice(0, -1).join(", ")} or ${items[items.length - 1]}`;
41
+ };
42
+ function _formatElevatedUnavailableText(params) {
33
43
  const lines = [];
34
44
  lines.push(`elevated is not available right now (runtime=${params.runtimeSandboxed ? "sandboxed" : "direct"}).`);
35
45
  const failures = params.failures ?? [];
@@ -140,6 +150,15 @@ const MODEL_PICK_PROVIDER_PREFERENCE = [
140
150
  "xai",
141
151
  "lmstudio",
142
152
  ];
153
+ const XHIGH_THINKING_MODELS = [
154
+ "openai/gpt-5.2",
155
+ "openai-codex/gpt-5.2-codex",
156
+ "openai-codex/gpt-5.1-codex",
157
+ ];
158
+ function isXhighThinkingAllowed(provider, model) {
159
+ const key = `${normalizeProviderId(provider)}/${model}`;
160
+ return XHIGH_THINKING_MODELS.includes(key);
161
+ }
143
162
  function normalizeModelFamilyId(id) {
144
163
  const trimmed = id.trim();
145
164
  if (!trimmed)
@@ -161,7 +180,7 @@ function sortProvidersForPicker(providers) {
161
180
  return a.localeCompare(b);
162
181
  });
163
182
  }
164
- function buildModelPickerItems(catalog) {
183
+ function _buildModelPickerItems(catalog) {
165
184
  const byModel = new Map();
166
185
  for (const entry of catalog) {
167
186
  const provider = normalizeProviderId(entry.provider);
@@ -183,7 +202,7 @@ function buildModelPickerItems(catalog) {
183
202
  out.sort((a, b) => a.model.toLowerCase().localeCompare(b.model.toLowerCase()));
184
203
  return out;
185
204
  }
186
- function pickProviderForModel(params) {
205
+ function _pickProviderForModel(params) {
187
206
  const preferred = params.preferredProvider
188
207
  ? normalizeProviderId(params.preferredProvider)
189
208
  : undefined;
@@ -201,7 +220,7 @@ function pickProviderForModel(params) {
201
220
  model: params.item.providerModels[first] ?? params.item.model,
202
221
  };
203
222
  }
204
- function resolveProviderEndpointLabel(provider, cfg) {
223
+ function _resolveProviderEndpointLabel(provider, cfg) {
205
224
  const normalized = normalizeProviderId(provider);
206
225
  const providers = (cfg.models?.providers ?? {});
207
226
  const entry = providers[normalized];
@@ -214,7 +233,8 @@ function resolveProviderEndpointLabel(provider, cfg) {
214
233
  }
215
234
  export function parseInlineDirectives(body) {
216
235
  const { cleaned: thinkCleaned, thinkLevel, rawLevel: rawThinkLevel, hasDirective: hasThinkDirective, } = extractThinkDirective(body);
217
- const { cleaned: verboseCleaned, verboseLevel, rawLevel: rawVerboseLevel, hasDirective: hasVerboseDirective, } = extractVerboseDirective(thinkCleaned);
236
+ const { cleaned: reasoningCleaned, reasoningLevel, rawLevel: rawReasoningLevel, hasDirective: hasReasoningDirective, } = extractReasoningDirective(thinkCleaned);
237
+ const { cleaned: verboseCleaned, verboseLevel, rawLevel: rawVerboseLevel, hasDirective: hasVerboseDirective, } = extractVerboseDirective(reasoningCleaned);
218
238
  const { cleaned: elevatedCleaned, elevatedLevel, rawLevel: rawElevatedLevel, hasDirective: hasElevatedDirective, } = extractElevatedDirective(verboseCleaned);
219
239
  const { cleaned: statusCleaned, hasDirective: hasStatusDirective } = extractStatusDirective(elevatedCleaned);
220
240
  const { cleaned: modelCleaned, rawModel, rawProfile, hasDirective: hasModelDirective, } = extractModelDirective(statusCleaned);
@@ -224,6 +244,9 @@ export function parseInlineDirectives(body) {
224
244
  hasThinkDirective,
225
245
  thinkLevel,
226
246
  rawThinkLevel,
247
+ hasReasoningDirective,
248
+ reasoningLevel,
249
+ rawReasoningLevel,
227
250
  hasVerboseDirective,
228
251
  verboseLevel,
229
252
  rawVerboseLevel,
@@ -250,6 +273,7 @@ export function parseInlineDirectives(body) {
250
273
  export function isDirectiveOnly(params) {
251
274
  const { directives, cleanedBody, ctx, cfg, isGroup } = params;
252
275
  if (!directives.hasThinkDirective &&
276
+ !directives.hasReasoningDirective &&
253
277
  !directives.hasVerboseDirective &&
254
278
  !directives.hasElevatedDirective &&
255
279
  !directives.hasModelDirective &&
@@ -260,22 +284,37 @@ export function isDirectiveOnly(params) {
260
284
  return noMentions.length === 0;
261
285
  }
262
286
  export async function handleDirectiveOnly(params) {
263
- const { directives, sessionEntry, sessionStore, sessionKey, storePath, elevatedEnabled, elevatedAllowed, defaultProvider, defaultModel, aliasIndex, allowedModelKeys, allowedModelCatalog, resetModelOverride, initialModelLabel, formatModelSwitchEvent, } = params;
287
+ const { directives, sessionEntry, sessionStore, sessionKey, storePath, elevatedEnabled, elevatedAllowed, defaultProvider, defaultModel, aliasIndex, allowedModelKeys, allowedModelCatalog, resetModelOverride, provider, model, initialModelLabel, formatModelSwitchEvent, } = params;
264
288
  if (directives.hasModelDirective) {
265
- const modelDirective = directives.rawModelDirective?.trim().toLowerCase();
266
- const isModelListAlias = modelDirective === "status" || modelDirective === "list";
267
- if (!directives.rawModelDirective || isModelListAlias) {
289
+ const rawModelDirective = directives.rawModelDirective?.trim();
290
+ const modelDirective = rawModelDirective?.toLowerCase();
291
+ const isModelStatus = modelDirective === "status";
292
+ const isModelList = modelDirective === "list";
293
+ if (!rawModelDirective || isModelList) {
294
+ if (allowedModelCatalog.length === 0) {
295
+ return { text: "No models available." };
296
+ }
297
+ const items = _buildModelPickerItems(allowedModelCatalog);
298
+ const lines = ["Pick: /model <#> or /model <provider/model>"];
299
+ for (const [idx, item] of items.entries()) {
300
+ lines.push(`${idx + 1}) ${item.model} — ${item.providers.join(", ")}`);
301
+ }
302
+ return { text: lines.join("\n") };
303
+ }
304
+ if (isModelStatus) {
268
305
  if (allowedModelCatalog.length === 0) {
269
306
  return { text: "No models available." };
270
307
  }
271
308
  const agentDir = resolveNexusAgentDir();
272
309
  const modelsPath = `${agentDir}/models.json`;
273
310
  const authByProvider = new Map();
311
+ const endpointByProvider = new Map();
274
312
  for (const entry of allowedModelCatalog) {
275
313
  if (authByProvider.has(entry.provider))
276
314
  continue;
277
315
  const auth = await resolveAuthLabel(entry.provider, params.cfg, modelsPath);
278
316
  authByProvider.set(entry.provider, formatAuthLabel(auth));
317
+ endpointByProvider.set(entry.provider, _resolveProviderEndpointLabel(entry.provider, params.cfg));
279
318
  }
280
319
  const current = `${params.provider}/${params.model}`;
281
320
  const defaultLabel = `${defaultProvider}/${defaultModel}`;
@@ -288,6 +327,16 @@ export async function handleDirectiveOnly(params) {
288
327
  if (resetModelOverride) {
289
328
  lines.push(`(previous selection reset to default)`);
290
329
  }
330
+ const providers = Array.from(authByProvider.keys()).sort((a, b) => a.localeCompare(b));
331
+ for (const provider of providers) {
332
+ const authLabel = authByProvider.get(provider) ?? "missing";
333
+ const endpoint = endpointByProvider.get(provider);
334
+ const endpointSuffix = endpoint?.endpoint
335
+ ? ` endpoint: ${endpoint.endpoint}`
336
+ : "";
337
+ const apiSuffix = endpoint?.api ? ` api: ${endpoint.api}` : "";
338
+ lines.push(`[${provider}]${endpointSuffix}${apiSuffix} auth: ${authLabel}`);
339
+ }
291
340
  for (const entry of allowedModelCatalog) {
292
341
  const label = `${entry.provider}/${entry.id}`;
293
342
  const aliases = aliasIndex.byKey.get(label);
@@ -305,9 +354,41 @@ export async function handleDirectiveOnly(params) {
305
354
  throw new Error("Auth profile override requires a model selection.");
306
355
  }
307
356
  }
308
- if (directives.hasThinkDirective && !directives.thinkLevel) {
357
+ if (directives.hasThinkDirective &&
358
+ directives.rawThinkLevel &&
359
+ !directives.thinkLevel) {
360
+ return {
361
+ text: `Unrecognized thinking level "${directives.rawThinkLevel ?? ""}". Valid levels: off, minimal, low, medium, high, xhigh.`,
362
+ };
363
+ }
364
+ if (directives.hasThinkDirective && !directives.rawThinkLevel) {
365
+ const catalog = allowedModelCatalog.length > 0
366
+ ? allowedModelCatalog
367
+ : await loadModelCatalog({ config: params.cfg });
368
+ const current = sessionEntry?.thinkingLevel ??
369
+ resolveThinkingDefault({
370
+ cfg: params.cfg,
371
+ provider,
372
+ model,
373
+ catalog,
374
+ });
375
+ const options = isXhighThinkingAllowed(provider, model)
376
+ ? "off, minimal, low, medium, high, xhigh"
377
+ : "off, minimal, low, medium, high";
378
+ return {
379
+ text: `Current thinking level: ${current}.\n${formatOptionsLine(options)}`,
380
+ };
381
+ }
382
+ if (directives.hasThinkDirective && directives.thinkLevel === "xhigh") {
383
+ if (!isXhighThinkingAllowed(provider, model)) {
384
+ return {
385
+ text: `Thinking level "xhigh" is only supported for ${_formatListWithOr(XHIGH_THINKING_MODELS)}.`,
386
+ };
387
+ }
388
+ }
389
+ if (directives.hasReasoningDirective && !directives.reasoningLevel) {
309
390
  return {
310
- text: `Unrecognized thinking level "${directives.rawThinkLevel ?? ""}". Valid levels: off, minimal, low, medium, high.`,
391
+ text: `Unrecognized reasoning level "${directives.rawReasoningLevel ?? ""}". Valid levels: off, on, stream.`,
311
392
  };
312
393
  }
313
394
  if (directives.hasVerboseDirective && !directives.verboseLevel) {
@@ -359,17 +440,44 @@ export async function handleDirectiveOnly(params) {
359
440
  let modelSelection;
360
441
  let profileOverride;
361
442
  if (directives.hasModelDirective && directives.rawModelDirective) {
362
- const resolved = resolveModelDirectiveSelection({
363
- raw: directives.rawModelDirective,
364
- defaultProvider,
365
- defaultModel,
366
- aliasIndex,
367
- allowedModelKeys,
368
- });
369
- if (resolved.error) {
370
- return { text: resolved.error };
443
+ const rawDirective = directives.rawModelDirective.trim();
444
+ const parsedIndex = Number.parseInt(rawDirective, 10);
445
+ if (Number.isFinite(parsedIndex) && String(parsedIndex) === rawDirective) {
446
+ const items = _buildModelPickerItems(allowedModelCatalog);
447
+ const item = items[parsedIndex - 1];
448
+ if (!item) {
449
+ return {
450
+ text: `Unrecognized model "${rawDirective}". Use /model to list available models.`,
451
+ };
452
+ }
453
+ const picked = _pickProviderForModel({
454
+ item,
455
+ preferredProvider: provider,
456
+ });
457
+ if (picked) {
458
+ const alias = aliasIndex.byKey.get(modelKey(picked.provider, picked.model))?.[0];
459
+ modelSelection = {
460
+ provider: picked.provider,
461
+ model: picked.model,
462
+ isDefault: picked.provider === defaultProvider &&
463
+ picked.model === defaultModel,
464
+ ...(alias ? { alias } : undefined),
465
+ };
466
+ }
467
+ }
468
+ if (!modelSelection) {
469
+ const resolved = resolveModelDirectiveSelection({
470
+ raw: rawDirective,
471
+ defaultProvider,
472
+ defaultModel,
473
+ aliasIndex,
474
+ allowedModelKeys,
475
+ });
476
+ if (resolved.error) {
477
+ return { text: resolved.error };
478
+ }
479
+ modelSelection = resolved.selection;
371
480
  }
372
- modelSelection = resolved.selection;
373
481
  if (modelSelection) {
374
482
  if (directives.rawModelProfile) {
375
483
  const profileResolved = resolveProfileOverride({
@@ -453,6 +561,20 @@ export async function handleDirectiveOnly(params) {
453
561
  await saveSessionStore(storePath, sessionStore);
454
562
  }
455
563
  }
564
+ if (directives.hasElevatedDirective && directives.elevatedLevel && sessionKey) {
565
+ enqueueSystemEvent(_formatElevatedEvent(directives.elevatedLevel), {
566
+ contextKey: `elevated:${directives.elevatedLevel}`,
567
+ sessionKey,
568
+ });
569
+ }
570
+ if (directives.hasReasoningDirective &&
571
+ directives.reasoningLevel &&
572
+ sessionKey) {
573
+ enqueueSystemEvent(_formatReasoningEvent(directives.reasoningLevel), {
574
+ contextKey: `reasoning:${directives.reasoningLevel}`,
575
+ sessionKey,
576
+ });
577
+ }
456
578
  const parts = [];
457
579
  if (directives.hasThinkDirective && directives.thinkLevel) {
458
580
  parts.push(directives.thinkLevel === "off"
@@ -469,6 +591,9 @@ export async function handleDirectiveOnly(params) {
469
591
  ? `${SYSTEM_MARK} Elevated mode disabled.`
470
592
  : `${SYSTEM_MARK} Elevated mode enabled.`);
471
593
  }
594
+ if (directives.hasReasoningDirective && directives.reasoningLevel) {
595
+ parts.push(`${SYSTEM_MARK} Reasoning ${directives.reasoningLevel.toUpperCase()}.`);
596
+ }
472
597
  if (modelSelection) {
473
598
  const label = `${modelSelection.provider}/${modelSelection.model}`;
474
599
  const labelWithAlias = modelSelection.alias