@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.
- package/dist/agents/agent-id.js +41 -0
- package/dist/agents/auth-profiles.js +114 -25
- package/dist/agents/identity-state.js +79 -0
- package/dist/agents/model-auth.js +1 -0
- package/dist/agents/model-fallback.js +15 -9
- package/dist/agents/model-selection.js +1 -1
- package/dist/agents/models-config.js +17 -11
- package/dist/agents/pi-embedded-runner.js +101 -9
- package/dist/agents/sandbox.js +12 -3
- package/dist/agents/skill-runner.js +29 -4
- package/dist/agents/skill-usage.js +114 -11
- package/dist/agents/skills-status.js +4 -4
- package/dist/agents/skills.js +18 -7
- package/dist/agents/subagent-registry.js +25 -11
- package/dist/agents/system-prompt.js +16 -0
- package/dist/agents/tool-policy.js +19 -3
- package/dist/agents/tools/browser-tool.js +5 -2
- package/dist/agents/tools/image-tool.js +93 -8
- package/dist/agents/tools/sessions-announce-target.js +5 -1
- package/dist/agents/workspace.js +55 -46
- package/dist/auto-reply/command-detection.js +2 -1
- package/dist/auto-reply/reply/directive-handling.js +153 -28
- package/dist/auto-reply/reply/directives.js +17 -2
- package/dist/auto-reply/reply/model-selection.js +8 -3
- package/dist/auto-reply/reply/queue.js +2 -2
- package/dist/auto-reply/reply.js +1 -1
- package/dist/auto-reply/thinking.js +15 -0
- package/dist/browser/chrome.js +1 -1
- package/dist/browser/client.js +2 -0
- package/dist/browser/config.js +6 -2
- package/dist/browser/pw-tools-core.js +3 -0
- package/dist/browser/routes/agent.js +14 -0
- package/dist/canvas-host/server.js +1 -1
- package/dist/capabilities/detector.js +245 -0
- package/dist/capabilities/registry.js +99 -0
- package/dist/channels/location.js +44 -0
- package/dist/channels/web/index.js +2 -0
- package/dist/cli/cloud-cli.js +12 -7
- package/dist/cli/credential-cli.js +139 -17
- package/dist/cli/gateway-cli.js +1 -1
- package/dist/cli/log-cli.js +25 -0
- package/dist/cli/pairing-cli.js +1 -1
- package/dist/cli/program.js +58 -6
- package/dist/cli/run-main.js +1 -1
- package/dist/cli/skills-cli.js +144 -21
- package/dist/cli/skills-hub-cli.js +59 -29
- package/dist/cli/tool-connector-cli.js +99 -24
- package/dist/cli/upstream-sync-cli.js +253 -96
- package/dist/cli/usage-cli.js +14 -0
- package/dist/commands/auth-choice-options.js +6 -1
- package/dist/commands/auth-choice.js +157 -5
- package/dist/commands/bootstrap-preset.js +10 -6
- package/dist/commands/capabilities.js +33 -6
- package/dist/commands/claude-md.js +3 -2
- package/dist/commands/config-view.js +1 -1
- package/dist/commands/configure.js +4 -4
- package/dist/commands/credential.js +497 -36
- package/dist/commands/cursor-rules.js +39 -19
- package/dist/commands/doctor.js +5 -4
- package/dist/commands/identity.js +28 -31
- package/dist/commands/init.js +15 -18
- package/dist/commands/log.js +134 -0
- package/dist/commands/models/fallbacks.js +1 -1
- package/dist/commands/models/image-fallbacks.js +1 -1
- package/dist/commands/models/list.js +1 -1
- package/dist/commands/models/scan.js +1 -1
- package/dist/commands/onboard-auth.js +27 -2
- package/dist/commands/onboard-eve-identity.js +7 -8
- package/dist/commands/onboard-non-interactive.js +4 -2
- package/dist/commands/onboard-quickstart.js +18 -11
- package/dist/commands/quest-state.js +271 -0
- package/dist/commands/quest.js +53 -13
- package/dist/commands/reset.js +1 -1
- package/dist/commands/sessions-ingest.js +5 -4
- package/dist/commands/setup.js +4 -2
- package/dist/commands/skills-manifest.js +2 -2
- package/dist/commands/status.js +179 -61
- package/dist/commands/suggestions.js +1 -1
- package/dist/commands/usage-tracking.js +32 -0
- package/dist/commands/usage-upload.js +6 -1
- package/dist/config/defaults.js +1 -3
- package/dist/config/includes.js +5 -7
- package/dist/config/io.js +88 -16
- package/dist/config/legacy.js +4 -2
- package/dist/config/paths.js +16 -0
- package/dist/config/sessions.js +9 -5
- package/dist/config/zod-schema.js +4 -3
- package/dist/control-plane/broker/broker.js +1022 -0
- package/dist/control-plane/compaction.js +282 -0
- package/dist/control-plane/factory.js +31 -0
- package/dist/control-plane/index.js +10 -0
- package/dist/control-plane/odu/agents.js +192 -0
- package/dist/control-plane/odu/interaction-tools.js +208 -0
- package/dist/control-plane/odu/prompt-loader.js +95 -0
- package/dist/control-plane/odu/runtime.js +479 -0
- package/dist/control-plane/odu/types.js +6 -0
- package/dist/control-plane/odu-control-plane.js +316 -0
- package/dist/control-plane/single-agent.js +249 -0
- package/dist/control-plane/types.js +11 -0
- package/dist/credentials/store.js +449 -0
- package/dist/gateway/server-browser.js +5 -4
- package/dist/gateway/server-methods/cron.js +11 -1
- package/dist/gateway/server.js +14 -7
- package/dist/infra/bonjour.js +1 -1
- package/dist/infra/event-log.js +8 -2
- package/dist/infra/path-env.js +1 -2
- package/dist/infra/provider-usage.auth.js +5 -3
- package/dist/infra/provider-usage.fetch.claude.js +16 -6
- package/dist/infra/provider-usage.fetch.minimax.js +8 -3
- package/dist/infra/provider-usage.js +9 -5
- package/dist/infra/restart.js +2 -2
- package/dist/infra/usage-settings.js +78 -0
- package/dist/infra/usage-suggestions.js +17 -5
- package/dist/infra/usage-upload.js +38 -1
- package/dist/infra/voicewake.js +2 -2
- package/dist/logging/redact.js +109 -0
- package/dist/markdown/fences.js +58 -0
- package/dist/media/image-ops.js +3 -1
- package/dist/memory/embeddings.js +146 -0
- package/dist/memory/index.js +3 -0
- package/dist/memory/internal.js +163 -0
- package/dist/pairing/pairing-store.js +218 -0
- package/dist/plugins/cli.js +42 -0
- package/dist/plugins/discovery.js +253 -0
- package/dist/plugins/install.js +181 -0
- package/dist/plugins/loader.js +290 -0
- package/dist/plugins/registry.js +105 -0
- package/dist/plugins/status.js +29 -0
- package/dist/plugins/tools.js +39 -0
- package/dist/plugins/types.js +1 -0
- package/dist/providers/github-copilot-auth.js +1 -1
- package/dist/routing/resolve-route.js +144 -0
- package/dist/routing/session-key.js +65 -0
- package/dist/sessions/send-policy.js +5 -5
- package/dist/slack/monitor.js +22 -1
- package/dist/telegram/reaction-level.js +2 -1
- package/dist/utils/provider-utils.js +28 -0
- package/dist/utils.js +4 -3
- package/dist/wizard/onboarding.js +29 -7
- package/package.json +4 -29
- package/patches/@mariozechner__pi-ai.patch +215 -0
- package/patches/playwright-core@1.57.0.patch +13 -0
- package/patches/qrcode-terminal.patch +12 -0
- package/scripts/postinstall.js +202 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveStateDir } from "../config/paths.js";
|
|
4
|
+
function listAgentIds(stateDir) {
|
|
5
|
+
const agentsDir = path.join(stateDir, "agents");
|
|
6
|
+
try {
|
|
7
|
+
const entries = fs.readdirSync(agentsDir, { withFileTypes: true });
|
|
8
|
+
return entries
|
|
9
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
|
|
10
|
+
.map((entry) => entry.name)
|
|
11
|
+
.sort();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function resolveAgentId(env = process.env) {
|
|
18
|
+
const override = env.NEXUS_AGENT_ID?.trim();
|
|
19
|
+
const stateDir = resolveStateDir(env);
|
|
20
|
+
const available = listAgentIds(stateDir);
|
|
21
|
+
if (override) {
|
|
22
|
+
return {
|
|
23
|
+
ok: true,
|
|
24
|
+
agentId: override,
|
|
25
|
+
source: "env",
|
|
26
|
+
available,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (available.length === 1) {
|
|
30
|
+
return {
|
|
31
|
+
ok: true,
|
|
32
|
+
agentId: available[0],
|
|
33
|
+
source: "auto",
|
|
34
|
+
available,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (available.length > 1) {
|
|
38
|
+
return { ok: false, reason: "multiple", available };
|
|
39
|
+
}
|
|
40
|
+
return { ok: true, agentId: "default", source: "default", available };
|
|
41
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import lockfile from "proper-lockfile";
|
|
4
|
+
import { buildCredentialIndex, ensureCredentialIndexSync, listCredentialEntriesSync, readCredentialRecordSync, resolveDefaultEnvVar, resolveOAuthBundle, resolveCredentialIndexPath, resolveCredentialValue, writeCredentialIndexSync, writeCredentialRecordSync, } from "../credentials/store.js";
|
|
4
5
|
import { createSubsystemLogger } from "../logging.js";
|
|
5
|
-
import { buildCredentialIndex, ensureCredentialIndexSync, listCredentialEntriesSync, readCredentialRecordSync, resolveCredentialIndexPath, resolveCredentialValue, writeCredentialIndexSync, writeCredentialRecordSync, } from "../credentials/store.js";
|
|
6
6
|
import { refreshChutesTokens } from "./chutes-oauth.js";
|
|
7
7
|
import { normalizeProviderId } from "./model-selection.js";
|
|
8
8
|
export const CLAUDE_CLI_PROFILE_ID = "anthropic:claude-cli";
|
|
@@ -22,17 +22,24 @@ function parseProfileId(profileId) {
|
|
|
22
22
|
const parts = profileId.split(":");
|
|
23
23
|
if (parts.length === 1)
|
|
24
24
|
return { provider: profileId, account: "default" };
|
|
25
|
-
return {
|
|
25
|
+
return {
|
|
26
|
+
provider: parts[0] ?? profileId,
|
|
27
|
+
account: parts.slice(1).join(":") || "default",
|
|
28
|
+
};
|
|
26
29
|
}
|
|
27
30
|
function isEmailLike(value) {
|
|
28
31
|
const trimmed = value.trim();
|
|
29
|
-
return Boolean(trimmed
|
|
32
|
+
return Boolean(trimmed?.includes("@") && trimmed.includes("."));
|
|
30
33
|
}
|
|
31
34
|
function pickPreferredEntry(entries) {
|
|
32
35
|
if (entries.length === 0)
|
|
33
36
|
return null;
|
|
34
37
|
const byType = (type) => entries.find((entry) => entry.record.type === type);
|
|
35
|
-
return byType("api_key") ??
|
|
38
|
+
return (byType("api_key") ??
|
|
39
|
+
byType("token") ??
|
|
40
|
+
byType("oauth") ??
|
|
41
|
+
entries[0] ??
|
|
42
|
+
null);
|
|
36
43
|
}
|
|
37
44
|
function resolveCredentialEmail(entry) {
|
|
38
45
|
const metaEmail = entry.record.metadata?.email;
|
|
@@ -113,7 +120,8 @@ export function loadAuthProfileStore() {
|
|
|
113
120
|
rebuilt.order = existing.order;
|
|
114
121
|
rebuilt.lastGood = existing.lastGood;
|
|
115
122
|
rebuilt.usageStats = existing.usageStats;
|
|
116
|
-
const servicesChanged = JSON.stringify(rebuilt.services) !==
|
|
123
|
+
const servicesChanged = JSON.stringify(rebuilt.services) !==
|
|
124
|
+
JSON.stringify(existing.services ?? {});
|
|
117
125
|
if (servicesChanged) {
|
|
118
126
|
writeIndex(rebuilt);
|
|
119
127
|
}
|
|
@@ -166,6 +174,31 @@ export async function setAuthProfileOrder(params) {
|
|
|
166
174
|
function resolveAuthIdForProvider(entry) {
|
|
167
175
|
return entry.authId;
|
|
168
176
|
}
|
|
177
|
+
function buildAuthPayload(credential) {
|
|
178
|
+
if (credential.type === "api_key") {
|
|
179
|
+
return { value: credential.key ?? "" };
|
|
180
|
+
}
|
|
181
|
+
if (credential.type === "token") {
|
|
182
|
+
const value = credential.token ?? "";
|
|
183
|
+
if (!credential.expires)
|
|
184
|
+
return { value };
|
|
185
|
+
return {
|
|
186
|
+
value: JSON.stringify({
|
|
187
|
+
token: value,
|
|
188
|
+
expiresAt: credential.expires,
|
|
189
|
+
}),
|
|
190
|
+
format: "json",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
value: JSON.stringify({
|
|
195
|
+
accessToken: credential.access,
|
|
196
|
+
refreshToken: credential.refresh,
|
|
197
|
+
expiresAt: credential.expires,
|
|
198
|
+
}),
|
|
199
|
+
format: "json",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
169
202
|
export function upsertAuthProfile(params) {
|
|
170
203
|
const { provider, account } = parseProfileId(params.profileId);
|
|
171
204
|
const authId = resolveAuthIdForProvider({
|
|
@@ -176,33 +209,43 @@ export function upsertAuthProfile(params) {
|
|
|
176
209
|
record: {
|
|
177
210
|
owner: "user",
|
|
178
211
|
type: params.credential.type,
|
|
179
|
-
storage: { provider: "
|
|
212
|
+
storage: { provider: "env", var: "NEXUS_DUMMY" },
|
|
180
213
|
},
|
|
181
214
|
});
|
|
182
215
|
const record = {
|
|
183
216
|
owner: "user",
|
|
184
217
|
type: params.credential.type,
|
|
185
218
|
configuredAt: new Date().toISOString(),
|
|
186
|
-
storage: { provider: "
|
|
219
|
+
storage: { provider: "env", var: "NEXUS_DUMMY" },
|
|
187
220
|
metadata: {
|
|
188
221
|
addedBy: "nexus upsertAuthProfile",
|
|
189
222
|
},
|
|
223
|
+
...(params.credential.type !== "api_key" && params.credential.expires
|
|
224
|
+
? { expiresAt: params.credential.expires }
|
|
225
|
+
: {}),
|
|
190
226
|
};
|
|
191
227
|
if (params.credential.type === "api_key") {
|
|
192
228
|
record.key = params.credential.key;
|
|
193
229
|
}
|
|
194
230
|
else if (params.credential.type === "token") {
|
|
195
231
|
record.token = params.credential.token;
|
|
196
|
-
record.expiresAt = params.credential.expires;
|
|
197
232
|
}
|
|
198
233
|
else {
|
|
199
234
|
record.accessToken = params.credential.access;
|
|
200
235
|
record.refreshToken = params.credential.refresh;
|
|
201
|
-
record.expiresAt = params.credential.expires;
|
|
202
236
|
}
|
|
203
|
-
const
|
|
237
|
+
const payload = buildAuthPayload(params.credential);
|
|
238
|
+
const envVar = resolveDefaultEnvVar({
|
|
239
|
+
service: provider,
|
|
240
|
+
type: params.credential.type,
|
|
241
|
+
});
|
|
242
|
+
record.storage = {
|
|
243
|
+
provider: "env",
|
|
244
|
+
var: envVar,
|
|
245
|
+
...(payload.format ? { format: payload.format } : {}),
|
|
246
|
+
};
|
|
204
247
|
const allowKeychain = process.env.NEXUS_KEYCHAIN_ENABLED === "1";
|
|
205
|
-
if (
|
|
248
|
+
if (payload.value && allowKeychain && process.platform === "darwin") {
|
|
206
249
|
const keychainService = `nexus.${provider}`;
|
|
207
250
|
const keychainAccount = account;
|
|
208
251
|
try {
|
|
@@ -214,18 +257,26 @@ export function upsertAuthProfile(params) {
|
|
|
214
257
|
"-a",
|
|
215
258
|
keychainAccount,
|
|
216
259
|
"-w",
|
|
217
|
-
|
|
260
|
+
payload.value,
|
|
218
261
|
]);
|
|
219
|
-
record.storage = {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
262
|
+
record.storage = {
|
|
263
|
+
provider: "keychain",
|
|
264
|
+
service: keychainService,
|
|
265
|
+
account: keychainAccount,
|
|
266
|
+
...(payload.format ? { format: payload.format } : {}),
|
|
267
|
+
};
|
|
224
268
|
}
|
|
225
269
|
catch (err) {
|
|
226
|
-
|
|
270
|
+
process.env[envVar] = payload.value;
|
|
271
|
+
log.warn("keychain write failed; using env credential fallback", {
|
|
272
|
+
err: String(err),
|
|
273
|
+
envVar,
|
|
274
|
+
});
|
|
227
275
|
}
|
|
228
276
|
}
|
|
277
|
+
else if (payload.value) {
|
|
278
|
+
process.env[envVar] = payload.value;
|
|
279
|
+
}
|
|
229
280
|
writeCredentialRecordSync(provider, account, authId, record);
|
|
230
281
|
const index = readIndex();
|
|
231
282
|
index.lastUpdated = new Date().toISOString();
|
|
@@ -302,7 +353,10 @@ export async function clearAuthProfileCooldown(params) {
|
|
|
302
353
|
export async function markAuthProfileGood(params) {
|
|
303
354
|
const providerKey = normalizeProviderId(params.provider);
|
|
304
355
|
const updated = await updateIndexWithLock((index) => {
|
|
305
|
-
index.lastGood = {
|
|
356
|
+
index.lastGood = {
|
|
357
|
+
...index.lastGood,
|
|
358
|
+
[providerKey]: params.profileId,
|
|
359
|
+
};
|
|
306
360
|
return true;
|
|
307
361
|
});
|
|
308
362
|
if (updated) {
|
|
@@ -337,17 +391,52 @@ export async function resolveApiKeyForProfile(params) {
|
|
|
337
391
|
const providerKey = normalizeProviderId(cred.provider);
|
|
338
392
|
if (providerKey === "chutes" && record.type === "oauth") {
|
|
339
393
|
try {
|
|
394
|
+
const bundle = await resolveOAuthBundle(record);
|
|
395
|
+
const accessToken = bundle.accessToken ?? record.accessToken;
|
|
396
|
+
const refreshToken = bundle.refreshToken ?? record.refreshToken;
|
|
397
|
+
const oauthExpires = bundle.expiresAt !== undefined ? bundle.expiresAt : record.expiresAt;
|
|
398
|
+
const oauthExpiresAt = typeof oauthExpires === "number"
|
|
399
|
+
? oauthExpires
|
|
400
|
+
: Number.parseInt(String(oauthExpires), 10);
|
|
401
|
+
if (!accessToken || !refreshToken) {
|
|
402
|
+
throw new Error("Missing OAuth refresh token for chutes.");
|
|
403
|
+
}
|
|
340
404
|
const refreshed = await refreshChutesTokens({
|
|
341
405
|
credential: {
|
|
342
|
-
access:
|
|
343
|
-
refresh:
|
|
344
|
-
expires:
|
|
406
|
+
access: accessToken,
|
|
407
|
+
refresh: refreshToken,
|
|
408
|
+
expires: oauthExpiresAt,
|
|
345
409
|
email: cred.email,
|
|
346
|
-
clientId: typeof record.metadata?.clientId === "string"
|
|
410
|
+
clientId: typeof record.metadata?.clientId === "string"
|
|
411
|
+
? record.metadata.clientId
|
|
412
|
+
: undefined,
|
|
347
413
|
},
|
|
348
414
|
});
|
|
349
|
-
|
|
350
|
-
|
|
415
|
+
const refreshedPayload = JSON.stringify({
|
|
416
|
+
accessToken: refreshed.access,
|
|
417
|
+
refreshToken: refreshed.refresh,
|
|
418
|
+
expiresAt: refreshed.expires,
|
|
419
|
+
});
|
|
420
|
+
if (record.storage.provider === "keychain") {
|
|
421
|
+
execFileSync("security", [
|
|
422
|
+
"add-generic-password",
|
|
423
|
+
"-U",
|
|
424
|
+
"-s",
|
|
425
|
+
record.storage.service,
|
|
426
|
+
"-a",
|
|
427
|
+
record.storage.account,
|
|
428
|
+
"-w",
|
|
429
|
+
refreshedPayload,
|
|
430
|
+
]);
|
|
431
|
+
record.storage = { ...record.storage, format: "json" };
|
|
432
|
+
}
|
|
433
|
+
else if (record.storage.provider === "env") {
|
|
434
|
+
process.env[record.storage.var] = refreshedPayload;
|
|
435
|
+
record.storage = { ...record.storage, format: "json" };
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
throw new Error("Cannot persist refreshed token without keychain/env.");
|
|
439
|
+
}
|
|
351
440
|
record.expiresAt = refreshed.expires;
|
|
352
441
|
record.lastVerified = new Date().toISOString();
|
|
353
442
|
if (typeof refreshed.clientId === "string") {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveBootstrapPath, resolveStateDir } from "../config/paths.js";
|
|
4
|
+
import { resolveAgentId } from "./agent-id.js";
|
|
5
|
+
function readField(pathname, label) {
|
|
6
|
+
try {
|
|
7
|
+
const raw = fs.readFileSync(pathname, "utf-8");
|
|
8
|
+
const regex = new RegExp(`^[-*]?\\s*${label}\\s*:\\s*(.+)$`, "im");
|
|
9
|
+
const match = raw.match(regex);
|
|
10
|
+
return match?.[1]?.trim();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function hasPopulatedField(pathname) {
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(pathname, "utf-8");
|
|
19
|
+
const lines = raw.split(/\r?\n/);
|
|
20
|
+
return lines.some((line) => {
|
|
21
|
+
const match = line.match(/^[-*]?\s*[^:]+:\s*(.+)$/);
|
|
22
|
+
if (!match)
|
|
23
|
+
return false;
|
|
24
|
+
return match[1].trim().length > 0;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function resolveAgentFromState(resolution, stateDir) {
|
|
32
|
+
if (!resolution.ok) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
reason: "multiple_agents",
|
|
36
|
+
agentOptions: resolution.available,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const agentId = resolution.agentId;
|
|
40
|
+
const agentIdentityDir = path.join(stateDir, "agents", agentId, "identity");
|
|
41
|
+
const userIdentityDir = path.join(stateDir, "user", "identity");
|
|
42
|
+
const agentIdentityPath = path.join(agentIdentityDir, "IDENTITY.md");
|
|
43
|
+
const agentSoulPath = path.join(agentIdentityDir, "SOUL.md");
|
|
44
|
+
const agentMemoryPath = path.join(agentIdentityDir, "MEMORY.md");
|
|
45
|
+
const userProfilePath = path.join(userIdentityDir, "PROFILE.md");
|
|
46
|
+
const bootstrapPath = resolveBootstrapPath(undefined, stateDir);
|
|
47
|
+
const agentIdentityExists = fs.existsSync(agentIdentityPath);
|
|
48
|
+
const agentSoulExists = fs.existsSync(agentSoulPath);
|
|
49
|
+
const agentMemoryExists = fs.existsSync(agentMemoryPath);
|
|
50
|
+
const userProfileExists = fs.existsSync(userProfilePath);
|
|
51
|
+
const hasIdentity = hasPopulatedField(agentIdentityPath) && hasPopulatedField(userProfilePath);
|
|
52
|
+
const agentName = readField(agentIdentityPath, "Name");
|
|
53
|
+
const userName = readField(userProfilePath, "Name");
|
|
54
|
+
return {
|
|
55
|
+
ok: true,
|
|
56
|
+
snapshot: {
|
|
57
|
+
agentId,
|
|
58
|
+
agentIdSource: resolution.source,
|
|
59
|
+
agentOptions: resolution.available,
|
|
60
|
+
agentName,
|
|
61
|
+
userName,
|
|
62
|
+
agentIdentityPath,
|
|
63
|
+
agentSoulPath,
|
|
64
|
+
agentMemoryPath,
|
|
65
|
+
userProfilePath,
|
|
66
|
+
bootstrapPath,
|
|
67
|
+
agentIdentityExists,
|
|
68
|
+
agentSoulExists,
|
|
69
|
+
agentMemoryExists,
|
|
70
|
+
userProfileExists,
|
|
71
|
+
hasIdentity,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function resolveIdentitySnapshot(env = process.env) {
|
|
76
|
+
const stateDir = resolveStateDir(env);
|
|
77
|
+
const resolution = resolveAgentId(env);
|
|
78
|
+
return resolveAgentFromState(resolution, stateDir);
|
|
79
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
|
|
2
|
-
import { buildModelAliasIndex, modelKey, parseModelRef, resolveModelRefFromString, } from "./model-selection.js";
|
|
2
|
+
import { buildModelAliasIndex, modelKey, parseModelRef, resolveConfiguredModelRef, resolveModelRefFromString, } from "./model-selection.js";
|
|
3
3
|
function isAbortError(err) {
|
|
4
4
|
if (!err || typeof err !== "object")
|
|
5
5
|
return false;
|
|
@@ -78,8 +78,13 @@ function resolveImageFallbackCandidates(params) {
|
|
|
78
78
|
return candidates;
|
|
79
79
|
}
|
|
80
80
|
function resolveFallbackCandidates(params) {
|
|
81
|
-
const
|
|
82
|
-
|
|
81
|
+
const fallbackDefault = resolveConfiguredModelRef({
|
|
82
|
+
cfg: params.cfg ?? {},
|
|
83
|
+
defaultProvider: DEFAULT_PROVIDER,
|
|
84
|
+
defaultModel: DEFAULT_MODEL,
|
|
85
|
+
});
|
|
86
|
+
const provider = params.provider?.trim() || fallbackDefault.provider;
|
|
87
|
+
const model = params.model?.trim() || fallbackDefault.model;
|
|
83
88
|
const aliasIndex = buildModelAliasIndex({
|
|
84
89
|
cfg: params.cfg ?? {},
|
|
85
90
|
defaultProvider: DEFAULT_PROVIDER,
|
|
@@ -99,12 +104,13 @@ function resolveFallbackCandidates(params) {
|
|
|
99
104
|
candidates.push(candidate);
|
|
100
105
|
};
|
|
101
106
|
addCandidate({ provider, model }, false);
|
|
102
|
-
const modelFallbacks =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
const modelFallbacks = params.fallbacksOverride ??
|
|
108
|
+
(() => {
|
|
109
|
+
const model = params.cfg?.agent?.model;
|
|
110
|
+
if (model && typeof model === "object")
|
|
111
|
+
return model.fallbacks ?? [];
|
|
112
|
+
return [];
|
|
113
|
+
})();
|
|
108
114
|
for (const raw of modelFallbacks) {
|
|
109
115
|
const resolved = resolveModelRefFromString({
|
|
110
116
|
raw: String(raw ?? ""),
|
|
@@ -132,7 +132,7 @@ export function buildAllowedModelSet(params) {
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
const allowedCatalog = params.catalog.filter((entry) => allowedKeys.has(modelKey(entry.provider, entry.id)));
|
|
135
|
-
if (allowedCatalog.length === 0) {
|
|
135
|
+
if (allowedCatalog.length === 0 && allowedKeys.size === 0) {
|
|
136
136
|
return {
|
|
137
137
|
allowAny: true,
|
|
138
138
|
allowedCatalog: params.catalog,
|
|
@@ -83,7 +83,7 @@ function resolveMinimaxApiKeyFromStore(store) {
|
|
|
83
83
|
}
|
|
84
84
|
return undefined;
|
|
85
85
|
}
|
|
86
|
-
function
|
|
86
|
+
function _resolveImplicitProviders(params) {
|
|
87
87
|
const providers = {};
|
|
88
88
|
const minimaxEnv = resolveEnvApiKey("minimax");
|
|
89
89
|
const authStore = ensureAuthProfileStore(params.agentDir);
|
|
@@ -93,7 +93,7 @@ function resolveImplicitProviders(params) {
|
|
|
93
93
|
}
|
|
94
94
|
return providers;
|
|
95
95
|
}
|
|
96
|
-
async function
|
|
96
|
+
async function _maybeBuildCopilotProvider(params) {
|
|
97
97
|
const env = params.env ?? process.env;
|
|
98
98
|
const authStore = ensureAuthProfileStore(params.agentDir);
|
|
99
99
|
const profileIds = listProfilesForProvider(authStore, "github-copilot");
|
|
@@ -106,7 +106,10 @@ async function maybeBuildCopilotProvider(params) {
|
|
|
106
106
|
if (!selectedGithubToken && hasProfile) {
|
|
107
107
|
const profileId = profileIds[0];
|
|
108
108
|
if (profileId) {
|
|
109
|
-
const resolved = await resolveApiKeyForProfile({
|
|
109
|
+
const resolved = await resolveApiKeyForProfile({
|
|
110
|
+
store: authStore,
|
|
111
|
+
profileId,
|
|
112
|
+
});
|
|
110
113
|
if (resolved?.apiKey)
|
|
111
114
|
selectedGithubToken = resolved.apiKey;
|
|
112
115
|
}
|
|
@@ -134,17 +137,20 @@ async function maybeBuildCopilotProvider(params) {
|
|
|
134
137
|
}
|
|
135
138
|
export async function ensureNexusModelsJson(config, agentDirOverride) {
|
|
136
139
|
const cfg = config ?? loadConfig();
|
|
137
|
-
const providers = cfg.models?.providers;
|
|
138
|
-
if (!providers || Object.keys(providers).length === 0) {
|
|
139
|
-
const agentDir = agentDirOverride?.trim()
|
|
140
|
-
? agentDirOverride.trim()
|
|
141
|
-
: resolveNexusAgentDir();
|
|
142
|
-
return { agentDir, wrote: false };
|
|
143
|
-
}
|
|
144
|
-
const mode = cfg.models?.mode ?? DEFAULT_MODE;
|
|
145
140
|
const agentDir = agentDirOverride?.trim()
|
|
146
141
|
? agentDirOverride.trim()
|
|
147
142
|
: resolveNexusAgentDir();
|
|
143
|
+
const explicitProviders = cfg.models?.providers ?? {};
|
|
144
|
+
const implicitProviders = _resolveImplicitProviders({ agentDir });
|
|
145
|
+
const copilotProvider = await _maybeBuildCopilotProvider({ agentDir });
|
|
146
|
+
if (copilotProvider && !explicitProviders["github-copilot"]) {
|
|
147
|
+
implicitProviders["github-copilot"] = copilotProvider;
|
|
148
|
+
}
|
|
149
|
+
const providers = { ...implicitProviders, ...explicitProviders };
|
|
150
|
+
if (Object.keys(providers).length === 0) {
|
|
151
|
+
return { agentDir, wrote: false };
|
|
152
|
+
}
|
|
153
|
+
const mode = cfg.models?.mode ?? DEFAULT_MODE;
|
|
148
154
|
const targetPath = path.join(agentDir, "models.json");
|
|
149
155
|
let mergedProviders = providers;
|
|
150
156
|
let existingRaw = "";
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
|
+
import { streamSimple } from "@mariozechner/pi-ai";
|
|
3
4
|
import { buildSystemPrompt, createAgentSession, discoverAuthStorage, discoverModels, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
|
|
4
5
|
import { formatToolAggregate } from "../auto-reply/tool-meta.js";
|
|
5
6
|
import { getMachineDisplayName } from "../infra/machine-name.js";
|
|
6
7
|
import { createSubsystemLogger } from "../logging.js";
|
|
7
8
|
import { splitMediaFromOutput } from "../media/parse.js";
|
|
8
9
|
import { enqueueCommandInLane, } from "../process/command-queue.js";
|
|
10
|
+
import { resolveTelegramReactionLevel } from "../telegram/reaction-level.js";
|
|
9
11
|
import { resolveUserPath } from "../utils.js";
|
|
10
12
|
import { resolveNexusAgentDir } from "./agent-paths.js";
|
|
11
13
|
import { markAuthProfileCooldown, markAuthProfileGood, markAuthProfileUsed, } from "./auth-profiles.js";
|
|
@@ -18,7 +20,6 @@ import { extractAssistantText } from "./pi-embedded-utils.js";
|
|
|
18
20
|
import { toToolDefinitions } from "./pi-tool-definition-adapter.js";
|
|
19
21
|
import { createNexusCodingTools } from "./pi-tools.js";
|
|
20
22
|
import { resolveSandboxContext } from "./sandbox.js";
|
|
21
|
-
import { resolveTelegramReactionLevel } from "../telegram/reaction-level.js";
|
|
22
23
|
import { applySkillEnvOverrides, applySkillEnvOverridesFromSnapshot, buildWorkspaceSkillSnapshot, loadWorkspaceSkillEntries, } from "./skills.js";
|
|
23
24
|
import { buildAgentSystemPromptAppend } from "./system-prompt.js";
|
|
24
25
|
import { normalizeUsage } from "./usage.js";
|
|
@@ -46,7 +47,7 @@ const GOOGLE_SCHEMA_UNSUPPORTED_KEYWORDS = new Set([
|
|
|
46
47
|
"minProperties",
|
|
47
48
|
"maxProperties",
|
|
48
49
|
]);
|
|
49
|
-
function
|
|
50
|
+
function _isAntigravityClaudeModel(provider, modelId) {
|
|
50
51
|
if (provider !== "google-antigravity")
|
|
51
52
|
return false;
|
|
52
53
|
return modelId.trim().toLowerCase().includes("claude");
|
|
@@ -69,8 +70,9 @@ function findUnsupportedSchemaKeywords(schema, path) {
|
|
|
69
70
|
}
|
|
70
71
|
return violations;
|
|
71
72
|
}
|
|
72
|
-
function
|
|
73
|
-
if (params.provider !== "google-antigravity" &&
|
|
73
|
+
function _logToolSchemasForGoogle(params) {
|
|
74
|
+
if (params.provider !== "google-antigravity" &&
|
|
75
|
+
params.provider !== "google-gemini-cli") {
|
|
74
76
|
return;
|
|
75
77
|
}
|
|
76
78
|
const toolNames = params.tools.map((tool, index) => `${index}:${tool.name}`);
|
|
@@ -91,6 +93,89 @@ function logToolSchemasForGoogle(params) {
|
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve provider-specific extra params from model config.
|
|
98
|
+
*
|
|
99
|
+
* Example config:
|
|
100
|
+
* agent.models["anthropic/claude-sonnet-4-5"].params.temperature = 0.7
|
|
101
|
+
* agent.models["openai/gpt-4.1-mini"].params.maxTokens = 8192
|
|
102
|
+
*/
|
|
103
|
+
export function resolveExtraParams(params) {
|
|
104
|
+
const modelKey = `${params.provider}/${params.modelId}`;
|
|
105
|
+
const modelConfig = params.cfg?.agent?.models?.[modelKey];
|
|
106
|
+
let extraParams = modelConfig?.params ? { ...modelConfig.params } : undefined;
|
|
107
|
+
// Auto-enable thinking for ZAI GLM-4.x models when not explicitly configured.
|
|
108
|
+
// Skip if user explicitly disabled thinking via --thinking off.
|
|
109
|
+
if (params.provider === "zai" && params.thinkLevel !== "off") {
|
|
110
|
+
const modelIdLower = params.modelId.toLowerCase();
|
|
111
|
+
const isGlm4 = modelIdLower.includes("glm-4");
|
|
112
|
+
if (isGlm4) {
|
|
113
|
+
const hasThinkingConfig = extraParams?.thinking !== undefined;
|
|
114
|
+
if (!hasThinkingConfig) {
|
|
115
|
+
// GLM-4.7 supports preserved thinking (reasoning kept across turns).
|
|
116
|
+
// GLM-4.5/4.6 use interleaved thinking (reasoning cleared each turn).
|
|
117
|
+
const isGlm47 = modelIdLower.includes("glm-4.7");
|
|
118
|
+
const clearThinking = !isGlm47;
|
|
119
|
+
extraParams = {
|
|
120
|
+
...extraParams,
|
|
121
|
+
thinking: {
|
|
122
|
+
type: "enabled",
|
|
123
|
+
clear_thinking: clearThinking,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
log.debug(`auto-enabled thinking for ${modelKey}: type=enabled, clear_thinking=${clearThinking}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return extraParams;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a wrapped streamFn that injects extra params (like temperature) from config.
|
|
134
|
+
*
|
|
135
|
+
* This wraps the default streamSimple with config-driven params for each model.
|
|
136
|
+
*/
|
|
137
|
+
function createStreamFnWithExtraParams(extraParams) {
|
|
138
|
+
if (!extraParams || Object.keys(extraParams).length === 0) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const streamParams = {};
|
|
142
|
+
if (typeof extraParams.temperature === "number") {
|
|
143
|
+
streamParams.temperature = extraParams.temperature;
|
|
144
|
+
}
|
|
145
|
+
if (typeof extraParams.maxTokens === "number") {
|
|
146
|
+
streamParams.maxTokens = extraParams.maxTokens;
|
|
147
|
+
}
|
|
148
|
+
if (Object.keys(streamParams).length === 0) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
log.debug(`creating streamFn wrapper with params: ${JSON.stringify(streamParams)}`);
|
|
152
|
+
const wrappedStreamFn = (model, context, options) => {
|
|
153
|
+
const mergedOptions = {
|
|
154
|
+
...streamParams,
|
|
155
|
+
...options,
|
|
156
|
+
};
|
|
157
|
+
return streamSimple(model, context, mergedOptions);
|
|
158
|
+
};
|
|
159
|
+
return wrappedStreamFn;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Apply extra params (like temperature) to an agent's streamFn.
|
|
163
|
+
*
|
|
164
|
+
* Call this after createAgentSession to wire up config-driven model params.
|
|
165
|
+
*/
|
|
166
|
+
function applyExtraParamsToAgent(agent, cfg, provider, modelId, thinkLevel) {
|
|
167
|
+
const extraParams = resolveExtraParams({
|
|
168
|
+
cfg,
|
|
169
|
+
provider,
|
|
170
|
+
modelId,
|
|
171
|
+
thinkLevel,
|
|
172
|
+
});
|
|
173
|
+
const wrappedStreamFn = createStreamFnWithExtraParams(extraParams);
|
|
174
|
+
if (wrappedStreamFn) {
|
|
175
|
+
log.debug(`applying extraParams to agent streamFn for ${provider}/${modelId}`);
|
|
176
|
+
agent.streamFn = wrappedStreamFn;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
94
179
|
/**
|
|
95
180
|
* Limits conversation history to the last N user turns (and their associated
|
|
96
181
|
* assistant responses). This reduces token usage for long-running DM sessions.
|
|
@@ -136,7 +221,9 @@ export function getDmHistoryLimitFromSessionKey(sessionKey, config, messageChann
|
|
|
136
221
|
const raw = sessionKey?.trim() ?? "";
|
|
137
222
|
if (!raw)
|
|
138
223
|
return undefined;
|
|
139
|
-
if (raw.startsWith("group:") ||
|
|
224
|
+
if (raw.startsWith("group:") ||
|
|
225
|
+
raw.includes(":group:") ||
|
|
226
|
+
raw.includes(":channel:")) {
|
|
140
227
|
return undefined;
|
|
141
228
|
}
|
|
142
229
|
const parts = raw.split(":").filter(Boolean);
|
|
@@ -176,7 +263,7 @@ export function getDmHistoryLimitFromSessionKey(sessionKey, config, messageChann
|
|
|
176
263
|
return undefined;
|
|
177
264
|
}
|
|
178
265
|
}
|
|
179
|
-
function
|
|
266
|
+
function _resolveReactionGuidance(params) {
|
|
180
267
|
if (!params.config)
|
|
181
268
|
return undefined;
|
|
182
269
|
const rawChannel = params.messageChannel ?? params.messageProvider ?? "";
|
|
@@ -488,6 +575,7 @@ export async function compactEmbeddedPiSession(params) {
|
|
|
488
575
|
skills: promptSkills,
|
|
489
576
|
contextFiles,
|
|
490
577
|
});
|
|
578
|
+
applyExtraParamsToAgent(session.agent, params.config, provider, modelId, params.thinkLevel);
|
|
491
579
|
try {
|
|
492
580
|
const prior = await sanitizeSessionMessagesImages(session.messages, "session:history");
|
|
493
581
|
if (prior.length > 0) {
|
|
@@ -705,6 +793,7 @@ export async function runEmbeddedPiAgent(params) {
|
|
|
705
793
|
skills: promptSkills,
|
|
706
794
|
contextFiles,
|
|
707
795
|
});
|
|
796
|
+
applyExtraParamsToAgent(session.agent, params.config, provider, modelId, thinkLevel);
|
|
708
797
|
const prior = await sanitizeSessionMessagesImages(session.messages, "session:history");
|
|
709
798
|
if (prior.length > 0) {
|
|
710
799
|
session.agent.replaceMessages(prior);
|
|
@@ -837,7 +926,7 @@ export async function runEmbeddedPiAgent(params) {
|
|
|
837
926
|
if (shouldRotate) {
|
|
838
927
|
// Mark current profile for cooldown before rotating
|
|
839
928
|
if (lastProfileId) {
|
|
840
|
-
markAuthProfileCooldown({
|
|
929
|
+
void markAuthProfileCooldown({
|
|
841
930
|
store: authStore,
|
|
842
931
|
profileId: lastProfileId,
|
|
843
932
|
});
|
|
@@ -906,13 +995,16 @@ export async function runEmbeddedPiAgent(params) {
|
|
|
906
995
|
.filter((p) => p.text || p.mediaUrl || (p.mediaUrls && p.mediaUrls.length > 0));
|
|
907
996
|
log.debug(`embedded run done: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - started} aborted=${aborted}`);
|
|
908
997
|
if (lastProfileId) {
|
|
909
|
-
markAuthProfileGood({
|
|
998
|
+
void markAuthProfileGood({
|
|
910
999
|
store: authStore,
|
|
911
1000
|
provider,
|
|
912
1001
|
profileId: lastProfileId,
|
|
913
1002
|
});
|
|
914
1003
|
// Track usage for round-robin rotation
|
|
915
|
-
markAuthProfileUsed({
|
|
1004
|
+
void markAuthProfileUsed({
|
|
1005
|
+
store: authStore,
|
|
1006
|
+
profileId: lastProfileId,
|
|
1007
|
+
});
|
|
916
1008
|
}
|
|
917
1009
|
return {
|
|
918
1010
|
payloads: payloads.length ? payloads : undefined,
|