@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.
- 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 +46 -15
- package/dist/capabilities/registry.js +2 -1
- 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 +131 -78
- package/dist/control-plane/compaction.js +3 -5
- package/dist/control-plane/factory.js +2 -2
- package/dist/control-plane/index.js +2 -2
- package/dist/control-plane/odu/agents.js +28 -23
- package/dist/control-plane/odu/interaction-tools.js +62 -50
- package/dist/control-plane/odu/prompt-loader.js +8 -8
- package/dist/control-plane/odu/runtime.js +87 -75
- package/dist/control-plane/odu-control-plane.js +14 -12
- package/dist/control-plane/single-agent.js +13 -13
- package/dist/credentials/store.js +133 -7
- 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/media/image-ops.js +3 -1
- package/dist/memory/index.js +2 -381
- package/dist/pairing/pairing-store.js +24 -0
- package/dist/providers/github-copilot-auth.js +1 -1
- package/dist/routing/resolve-route.js +6 -6
- package/dist/routing/session-key.js +3 -1
- 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.js +4 -3
- package/dist/wizard/onboarding.js +29 -7
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import { resolveStateDir } from "../config/paths.js";
|
|
6
6
|
import { resolveEventLogDir } from "./event-log.js";
|
|
7
|
+
import { fetchUsageTrackingSettings } from "./usage-settings.js";
|
|
7
8
|
const UPLOAD_URL_ENV = "NEXUS_USAGE_UPLOAD_URL";
|
|
8
9
|
const UPLOAD_TOKEN_ENV = "NEXUS_USAGE_UPLOAD_TOKEN";
|
|
9
10
|
const OUTBOX_DIR_ENV = "NEXUS_USAGE_OUTBOX_DIR";
|
|
@@ -39,9 +40,34 @@ function writeJSON(pathname, data) {
|
|
|
39
40
|
fs.mkdirSync(path.dirname(pathname), { recursive: true });
|
|
40
41
|
fs.writeFileSync(pathname, JSON.stringify(data, null, 2), "utf-8");
|
|
41
42
|
}
|
|
43
|
+
function updateTrackingState(state, settings) {
|
|
44
|
+
if (!settings)
|
|
45
|
+
return;
|
|
46
|
+
state.opted_out = settings.canOptOut && settings.optOut;
|
|
47
|
+
state.settings_checked_at = Date.now();
|
|
48
|
+
writeJSON(stateFilePath(), state);
|
|
49
|
+
}
|
|
50
|
+
function isTrackingDisabled(state, settings) {
|
|
51
|
+
if (settings)
|
|
52
|
+
return settings.canOptOut && settings.optOut;
|
|
53
|
+
return Boolean(state.opted_out);
|
|
54
|
+
}
|
|
55
|
+
function clearUsageOutbox() {
|
|
56
|
+
const outboxDir = resolveOutboxDir();
|
|
57
|
+
if (!fs.existsSync(outboxDir))
|
|
58
|
+
return 0;
|
|
59
|
+
const files = fs
|
|
60
|
+
.readdirSync(outboxDir)
|
|
61
|
+
.filter((name) => name.endsWith(".json"))
|
|
62
|
+
.map((name) => path.join(outboxDir, name));
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
fs.unlinkSync(file);
|
|
65
|
+
}
|
|
66
|
+
return files.length;
|
|
67
|
+
}
|
|
42
68
|
function getOrCreateUsageState() {
|
|
43
69
|
const existing = readJSON(stateFilePath());
|
|
44
|
-
if (existing
|
|
70
|
+
if (existing?.anon_id && existing.anon_salt) {
|
|
45
71
|
return existing;
|
|
46
72
|
}
|
|
47
73
|
const anonId = crypto.randomUUID();
|
|
@@ -260,6 +286,17 @@ export async function flushUsageOutbox() {
|
|
|
260
286
|
}
|
|
261
287
|
export async function runUsageUpload(opts = {}) {
|
|
262
288
|
const limit = opts.limit && opts.limit > 0 ? opts.limit : 1000;
|
|
289
|
+
const state = getOrCreateUsageState();
|
|
290
|
+
const settings = await fetchUsageTrackingSettings();
|
|
291
|
+
const resolvedSettings = settings?.ok ? settings : null;
|
|
292
|
+
updateTrackingState(state, resolvedSettings);
|
|
293
|
+
if (isTrackingDisabled(state, resolvedSettings)) {
|
|
294
|
+
const cleared = clearUsageOutbox();
|
|
295
|
+
return {
|
|
296
|
+
status: "disabled",
|
|
297
|
+
cleared,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
263
300
|
if (!opts.onlyFlush) {
|
|
264
301
|
const { batch, nextState, count } = await buildUsageUploadBatch(limit);
|
|
265
302
|
if (opts.dryRun) {
|
package/dist/infra/voicewake.js
CHANGED
|
@@ -2,10 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveStateDir } from "../config/paths.js";
|
|
6
6
|
const DEFAULT_TRIGGERS = ["nexus", "claude", "computer"];
|
|
7
7
|
function defaultBaseDir() {
|
|
8
|
-
return
|
|
8
|
+
return resolveStateDir(process.env, os.homedir);
|
|
9
9
|
}
|
|
10
10
|
function resolvePath(baseDir) {
|
|
11
11
|
const root = baseDir ?? defaultBaseDir();
|
package/dist/media/image-ops.js
CHANGED
|
@@ -7,7 +7,9 @@ function isBun() {
|
|
|
7
7
|
}
|
|
8
8
|
function prefersSips() {
|
|
9
9
|
return (process.env.NEXUS_IMAGE_BACKEND === "sips" ||
|
|
10
|
-
(process.env.NEXUS_IMAGE_BACKEND !== "sharp" &&
|
|
10
|
+
(process.env.NEXUS_IMAGE_BACKEND !== "sharp" &&
|
|
11
|
+
isBun() &&
|
|
12
|
+
process.platform === "darwin"));
|
|
11
13
|
}
|
|
12
14
|
async function loadSharp() {
|
|
13
15
|
const mod = (await import("sharp"));
|
package/dist/memory/index.js
CHANGED
|
@@ -1,382 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import chokidar from "chokidar";
|
|
5
|
-
import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
|
|
6
|
-
import { resolveMemorySearchConfig } from "../agents/memory-search.js";
|
|
7
|
-
import { resolveUserPath, truncateUtf16Safe } from "../utils.js";
|
|
8
|
-
import { createEmbeddingProvider, } from "./embeddings.js";
|
|
9
|
-
import { buildFileEntry, chunkMarkdown, cosineSimilarity, ensureDir, hashText, isMemoryPath, listMemoryFiles, normalizeRelPath, parseEmbedding, } from "./internal.js";
|
|
10
|
-
const require = createRequire(import.meta.url);
|
|
11
|
-
function requireNodeSqlite() {
|
|
12
|
-
const onWarning = (warning) => {
|
|
13
|
-
if (warning.name === "ExperimentalWarning" &&
|
|
14
|
-
warning.message?.includes("SQLite is an experimental feature")) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
process.stderr.write(`${warning.stack ?? warning.toString()}\n`);
|
|
18
|
-
};
|
|
19
|
-
process.on("warning", onWarning);
|
|
20
|
-
try {
|
|
21
|
-
return require("node:sqlite");
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
process.off("warning", onWarning);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
const META_KEY = "memory_index_meta_v1";
|
|
28
|
-
const SNIPPET_MAX_CHARS = 700;
|
|
29
|
-
const INDEX_CACHE = new Map();
|
|
30
|
-
export class MemoryIndexManager {
|
|
31
|
-
cacheKey;
|
|
32
|
-
cfg;
|
|
33
|
-
agentId;
|
|
34
|
-
workspaceDir;
|
|
35
|
-
settings;
|
|
36
|
-
provider;
|
|
37
|
-
requestedProvider;
|
|
38
|
-
fallbackReason;
|
|
39
|
-
db;
|
|
40
|
-
watcher = null;
|
|
41
|
-
watchTimer = null;
|
|
42
|
-
intervalTimer = null;
|
|
43
|
-
closed = false;
|
|
44
|
-
dirty = false;
|
|
45
|
-
sessionWarm = new Set();
|
|
46
|
-
syncing = null;
|
|
47
|
-
static async get(params) {
|
|
48
|
-
const { cfg, agentId } = params;
|
|
49
|
-
const settings = resolveMemorySearchConfig(cfg, agentId);
|
|
50
|
-
if (!settings)
|
|
51
|
-
return null;
|
|
52
|
-
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
|
53
|
-
const key = `${agentId}:${workspaceDir}:${JSON.stringify(settings)}`;
|
|
54
|
-
const existing = INDEX_CACHE.get(key);
|
|
55
|
-
if (existing)
|
|
56
|
-
return existing;
|
|
57
|
-
const providerResult = await createEmbeddingProvider({
|
|
58
|
-
config: cfg,
|
|
59
|
-
agentDir: resolveAgentDir(cfg, agentId),
|
|
60
|
-
provider: settings.provider,
|
|
61
|
-
remote: settings.remote,
|
|
62
|
-
model: settings.model,
|
|
63
|
-
fallback: settings.fallback,
|
|
64
|
-
local: settings.local,
|
|
65
|
-
});
|
|
66
|
-
const manager = new MemoryIndexManager({
|
|
67
|
-
cacheKey: key,
|
|
68
|
-
cfg,
|
|
69
|
-
agentId,
|
|
70
|
-
workspaceDir,
|
|
71
|
-
settings,
|
|
72
|
-
providerResult,
|
|
73
|
-
});
|
|
74
|
-
INDEX_CACHE.set(key, manager);
|
|
75
|
-
return manager;
|
|
76
|
-
}
|
|
77
|
-
constructor(params) {
|
|
78
|
-
this.cacheKey = params.cacheKey;
|
|
79
|
-
this.cfg = params.cfg;
|
|
80
|
-
this.agentId = params.agentId;
|
|
81
|
-
this.workspaceDir = params.workspaceDir;
|
|
82
|
-
this.settings = params.settings;
|
|
83
|
-
this.provider = params.providerResult.provider;
|
|
84
|
-
this.requestedProvider = params.providerResult.requestedProvider;
|
|
85
|
-
this.fallbackReason = params.providerResult.fallbackReason;
|
|
86
|
-
this.db = this.openDatabase();
|
|
87
|
-
this.ensureSchema();
|
|
88
|
-
this.ensureWatcher();
|
|
89
|
-
this.ensureIntervalSync();
|
|
90
|
-
this.dirty = true;
|
|
91
|
-
}
|
|
92
|
-
async warmSession(sessionKey) {
|
|
93
|
-
if (!this.settings.sync.onSessionStart)
|
|
94
|
-
return;
|
|
95
|
-
const key = sessionKey?.trim() || "";
|
|
96
|
-
if (key && this.sessionWarm.has(key))
|
|
97
|
-
return;
|
|
98
|
-
await this.sync({ reason: "session-start" });
|
|
99
|
-
if (key)
|
|
100
|
-
this.sessionWarm.add(key);
|
|
101
|
-
}
|
|
102
|
-
async search(query, opts) {
|
|
103
|
-
await this.warmSession(opts?.sessionKey);
|
|
104
|
-
if (this.settings.sync.onSearch && this.dirty) {
|
|
105
|
-
await this.sync({ reason: "search" });
|
|
106
|
-
}
|
|
107
|
-
const cleaned = query.trim();
|
|
108
|
-
if (!cleaned)
|
|
109
|
-
return [];
|
|
110
|
-
const queryVec = await this.provider.embedQuery(cleaned);
|
|
111
|
-
if (queryVec.length === 0)
|
|
112
|
-
return [];
|
|
113
|
-
const candidates = this.listChunks();
|
|
114
|
-
const scored = candidates
|
|
115
|
-
.map((chunk) => ({
|
|
116
|
-
chunk,
|
|
117
|
-
score: cosineSimilarity(queryVec, chunk.embedding),
|
|
118
|
-
}))
|
|
119
|
-
.filter((entry) => Number.isFinite(entry.score));
|
|
120
|
-
const minScore = opts?.minScore ?? this.settings.query.minScore;
|
|
121
|
-
const maxResults = opts?.maxResults ?? this.settings.query.maxResults;
|
|
122
|
-
return scored
|
|
123
|
-
.filter((entry) => entry.score >= minScore)
|
|
124
|
-
.sort((a, b) => b.score - a.score)
|
|
125
|
-
.slice(0, maxResults)
|
|
126
|
-
.map((entry) => ({
|
|
127
|
-
path: entry.chunk.path,
|
|
128
|
-
startLine: entry.chunk.startLine,
|
|
129
|
-
endLine: entry.chunk.endLine,
|
|
130
|
-
score: entry.score,
|
|
131
|
-
snippet: truncateUtf16Safe(entry.chunk.text, SNIPPET_MAX_CHARS),
|
|
132
|
-
}));
|
|
133
|
-
}
|
|
134
|
-
async readFile(params) {
|
|
135
|
-
const relPath = normalizeRelPath(params.relPath);
|
|
136
|
-
if (!relPath)
|
|
137
|
-
return { text: "", error: "Invalid path" };
|
|
138
|
-
if (!isMemoryPath(relPath)) {
|
|
139
|
-
return { text: "", error: "Path must be MEMORY.md or memory/*.md" };
|
|
140
|
-
}
|
|
141
|
-
const absPath = path.join(this.workspaceDir, relPath);
|
|
142
|
-
try {
|
|
143
|
-
const content = await fs.readFile(absPath, "utf-8");
|
|
144
|
-
const lines = content.split("\n");
|
|
145
|
-
const start = Math.max(1, params.from ?? 1);
|
|
146
|
-
const count = Math.max(1, params.lines ?? lines.length);
|
|
147
|
-
const slice = lines.slice(start - 1, start - 1 + count);
|
|
148
|
-
return { text: slice.join("\n"), path: relPath };
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
152
|
-
return { text: "", error: message };
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
status() {
|
|
156
|
-
const files = this.db.prepare(`SELECT COUNT(*) as c FROM files`).get();
|
|
157
|
-
const chunks = this.db.prepare(`SELECT COUNT(*) as c FROM chunks`).get();
|
|
158
|
-
return {
|
|
159
|
-
files: files?.c ?? 0,
|
|
160
|
-
chunks: chunks?.c ?? 0,
|
|
161
|
-
dirty: this.dirty,
|
|
162
|
-
workspaceDir: this.workspaceDir,
|
|
163
|
-
dbPath: this.settings.store.path,
|
|
164
|
-
provider: this.provider.id,
|
|
165
|
-
model: this.provider.model,
|
|
166
|
-
requestedProvider: this.requestedProvider,
|
|
167
|
-
fallback: this.fallbackReason ? { from: "local", reason: this.fallbackReason } : undefined,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
async close() {
|
|
171
|
-
if (this.closed)
|
|
172
|
-
return;
|
|
173
|
-
this.closed = true;
|
|
174
|
-
if (this.watchTimer) {
|
|
175
|
-
clearTimeout(this.watchTimer);
|
|
176
|
-
this.watchTimer = null;
|
|
177
|
-
}
|
|
178
|
-
if (this.intervalTimer) {
|
|
179
|
-
clearInterval(this.intervalTimer);
|
|
180
|
-
this.intervalTimer = null;
|
|
181
|
-
}
|
|
182
|
-
if (this.watcher) {
|
|
183
|
-
await this.watcher.close();
|
|
184
|
-
this.watcher = null;
|
|
185
|
-
}
|
|
186
|
-
this.db.close();
|
|
187
|
-
INDEX_CACHE.delete(this.cacheKey);
|
|
188
|
-
}
|
|
189
|
-
openDatabase() {
|
|
190
|
-
const dbPath = resolveUserPath(this.settings.store.path);
|
|
191
|
-
const dir = path.dirname(dbPath);
|
|
192
|
-
ensureDir(dir);
|
|
193
|
-
const { DatabaseSync } = requireNodeSqlite();
|
|
194
|
-
return new DatabaseSync(dbPath);
|
|
195
|
-
}
|
|
196
|
-
ensureSchema() {
|
|
197
|
-
this.db.exec(`
|
|
198
|
-
CREATE TABLE IF NOT EXISTS meta (
|
|
199
|
-
key TEXT PRIMARY KEY,
|
|
200
|
-
value TEXT NOT NULL
|
|
201
|
-
);
|
|
202
|
-
`);
|
|
203
|
-
this.db.exec(`
|
|
204
|
-
CREATE TABLE IF NOT EXISTS files (
|
|
205
|
-
path TEXT PRIMARY KEY,
|
|
206
|
-
hash TEXT NOT NULL,
|
|
207
|
-
mtime INTEGER NOT NULL,
|
|
208
|
-
size INTEGER NOT NULL
|
|
209
|
-
);
|
|
210
|
-
`);
|
|
211
|
-
this.db.exec(`
|
|
212
|
-
CREATE TABLE IF NOT EXISTS chunks (
|
|
213
|
-
id TEXT PRIMARY KEY,
|
|
214
|
-
path TEXT NOT NULL,
|
|
215
|
-
start_line INTEGER NOT NULL,
|
|
216
|
-
end_line INTEGER NOT NULL,
|
|
217
|
-
hash TEXT NOT NULL,
|
|
218
|
-
model TEXT NOT NULL,
|
|
219
|
-
text TEXT NOT NULL,
|
|
220
|
-
embedding TEXT NOT NULL,
|
|
221
|
-
updated_at INTEGER NOT NULL
|
|
222
|
-
);
|
|
223
|
-
`);
|
|
224
|
-
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_chunks_path ON chunks(path);`);
|
|
225
|
-
}
|
|
226
|
-
ensureWatcher() {
|
|
227
|
-
if (!this.settings.sync.watch || this.watcher)
|
|
228
|
-
return;
|
|
229
|
-
const watchPaths = [
|
|
230
|
-
path.join(this.workspaceDir, "MEMORY.md"),
|
|
231
|
-
path.join(this.workspaceDir, "memory"),
|
|
232
|
-
];
|
|
233
|
-
this.watcher = chokidar.watch(watchPaths, {
|
|
234
|
-
ignoreInitial: true,
|
|
235
|
-
awaitWriteFinish: {
|
|
236
|
-
stabilityThreshold: this.settings.sync.watchDebounceMs,
|
|
237
|
-
pollInterval: 100,
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
const markDirty = () => {
|
|
241
|
-
this.dirty = true;
|
|
242
|
-
this.scheduleWatchSync();
|
|
243
|
-
};
|
|
244
|
-
this.watcher.on("add", markDirty);
|
|
245
|
-
this.watcher.on("change", markDirty);
|
|
246
|
-
this.watcher.on("unlink", markDirty);
|
|
247
|
-
}
|
|
248
|
-
ensureIntervalSync() {
|
|
249
|
-
const minutes = this.settings.sync.intervalMinutes;
|
|
250
|
-
if (!minutes || minutes <= 0 || this.intervalTimer)
|
|
251
|
-
return;
|
|
252
|
-
const ms = minutes * 60 * 1000;
|
|
253
|
-
this.intervalTimer = setInterval(() => {
|
|
254
|
-
void this.sync({ reason: "interval" });
|
|
255
|
-
}, ms);
|
|
256
|
-
}
|
|
257
|
-
scheduleWatchSync() {
|
|
258
|
-
if (!this.settings.sync.watch)
|
|
259
|
-
return;
|
|
260
|
-
if (this.watchTimer)
|
|
261
|
-
clearTimeout(this.watchTimer);
|
|
262
|
-
this.watchTimer = setTimeout(() => {
|
|
263
|
-
this.watchTimer = null;
|
|
264
|
-
void this.sync({ reason: "watch" });
|
|
265
|
-
}, this.settings.sync.watchDebounceMs);
|
|
266
|
-
}
|
|
267
|
-
listChunks() {
|
|
268
|
-
const rows = this.db
|
|
269
|
-
.prepare(`SELECT path, start_line, end_line, text, embedding FROM chunks WHERE model = ?`)
|
|
270
|
-
.all(this.provider.model);
|
|
271
|
-
return rows.map((row) => ({
|
|
272
|
-
path: row.path,
|
|
273
|
-
startLine: row.start_line,
|
|
274
|
-
endLine: row.end_line,
|
|
275
|
-
text: row.text,
|
|
276
|
-
embedding: parseEmbedding(row.embedding),
|
|
277
|
-
}));
|
|
278
|
-
}
|
|
279
|
-
async sync(params) {
|
|
280
|
-
if (this.syncing)
|
|
281
|
-
return this.syncing;
|
|
282
|
-
this.syncing = this.runSync(params).finally(() => {
|
|
283
|
-
this.syncing = null;
|
|
284
|
-
});
|
|
285
|
-
return this.syncing;
|
|
286
|
-
}
|
|
287
|
-
async runSync(params) {
|
|
288
|
-
const meta = this.readMeta();
|
|
289
|
-
const needsFullReindex = params?.force ||
|
|
290
|
-
!meta ||
|
|
291
|
-
meta.model !== this.provider.model ||
|
|
292
|
-
meta.provider !== this.provider.id ||
|
|
293
|
-
meta.chunkTokens !== this.settings.chunking.tokens ||
|
|
294
|
-
meta.chunkOverlap !== this.settings.chunking.overlap;
|
|
295
|
-
if (needsFullReindex) {
|
|
296
|
-
this.resetIndex();
|
|
297
|
-
}
|
|
298
|
-
const files = await listMemoryFiles(this.workspaceDir);
|
|
299
|
-
const fileEntries = await Promise.all(files.map(async (file) => buildFileEntry(file, this.workspaceDir)));
|
|
300
|
-
const activePaths = new Set(fileEntries.map((entry) => entry.path));
|
|
301
|
-
for (const entry of fileEntries) {
|
|
302
|
-
const record = this.db.prepare(`SELECT hash FROM files WHERE path = ?`).get(entry.path);
|
|
303
|
-
if (!needsFullReindex && record?.hash === entry.hash) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
await this.indexFile(entry);
|
|
307
|
-
}
|
|
308
|
-
const staleRows = this.db.prepare(`SELECT path FROM files`).all();
|
|
309
|
-
for (const stale of staleRows) {
|
|
310
|
-
if (activePaths.has(stale.path))
|
|
311
|
-
continue;
|
|
312
|
-
this.db.prepare(`DELETE FROM files WHERE path = ?`).run(stale.path);
|
|
313
|
-
this.db.prepare(`DELETE FROM chunks WHERE path = ?`).run(stale.path);
|
|
314
|
-
}
|
|
315
|
-
this.writeMeta({
|
|
316
|
-
model: this.provider.model,
|
|
317
|
-
provider: this.provider.id,
|
|
318
|
-
chunkTokens: this.settings.chunking.tokens,
|
|
319
|
-
chunkOverlap: this.settings.chunking.overlap,
|
|
320
|
-
});
|
|
321
|
-
this.dirty = false;
|
|
322
|
-
}
|
|
323
|
-
resetIndex() {
|
|
324
|
-
this.db.exec(`DELETE FROM files`);
|
|
325
|
-
this.db.exec(`DELETE FROM chunks`);
|
|
326
|
-
}
|
|
327
|
-
readMeta() {
|
|
328
|
-
const row = this.db.prepare(`SELECT value FROM meta WHERE key = ?`).get(META_KEY);
|
|
329
|
-
if (!row?.value)
|
|
330
|
-
return null;
|
|
331
|
-
try {
|
|
332
|
-
return JSON.parse(row.value);
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
writeMeta(meta) {
|
|
339
|
-
const value = JSON.stringify(meta);
|
|
340
|
-
this.db
|
|
341
|
-
.prepare(`INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`)
|
|
342
|
-
.run(META_KEY, value);
|
|
343
|
-
}
|
|
344
|
-
async indexFile(entry) {
|
|
345
|
-
const content = await fs.readFile(entry.absPath, "utf-8");
|
|
346
|
-
const chunks = chunkMarkdown(content, this.settings.chunking);
|
|
347
|
-
const embeddings = await this.provider.embedBatch(chunks.map((chunk) => chunk.text));
|
|
348
|
-
const now = Date.now();
|
|
349
|
-
this.db.prepare(`DELETE FROM chunks WHERE path = ?`).run(entry.path);
|
|
350
|
-
for (let i = 0; i < chunks.length; i += 1) {
|
|
351
|
-
const chunk = chunks[i];
|
|
352
|
-
const embedding = embeddings[i] ?? [];
|
|
353
|
-
if (!chunk)
|
|
354
|
-
continue;
|
|
355
|
-
const id = hashText(`${entry.path}:${chunk.startLine}:${chunk.endLine}:${chunk.hash}:${this.provider.model}`);
|
|
356
|
-
this.db
|
|
357
|
-
.prepare(`INSERT INTO chunks (id, path, start_line, end_line, hash, model, text, embedding, updated_at)
|
|
358
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
359
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
360
|
-
hash=excluded.hash,
|
|
361
|
-
model=excluded.model,
|
|
362
|
-
text=excluded.text,
|
|
363
|
-
embedding=excluded.embedding,
|
|
364
|
-
updated_at=excluded.updated_at`)
|
|
365
|
-
.run(id, entry.path, chunk.startLine, chunk.endLine, chunk.hash, this.provider.model, chunk.text, JSON.stringify(embedding), now);
|
|
366
|
-
}
|
|
367
|
-
this.db
|
|
368
|
-
.prepare(`INSERT INTO files (path, hash, mtime, size) VALUES (?, ?, ?, ?)
|
|
369
|
-
ON CONFLICT(path) DO UPDATE SET hash=excluded.hash, mtime=excluded.mtime, size=excluded.size`)
|
|
370
|
-
.run(entry.path, entry.hash, entry.mtimeMs, entry.size);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
export async function getMemorySearchManager(params) {
|
|
374
|
-
try {
|
|
375
|
-
const manager = await MemoryIndexManager.get(params);
|
|
376
|
-
return { manager };
|
|
377
|
-
}
|
|
378
|
-
catch (err) {
|
|
379
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
380
|
-
return { manager: null, error: message };
|
|
381
|
-
}
|
|
1
|
+
export async function getMemorySearchManager() {
|
|
2
|
+
return { manager: null, error: "sqlite unavailable" };
|
|
382
3
|
}
|
|
@@ -192,3 +192,27 @@ export async function approveProviderPairingCode(params) {
|
|
|
192
192
|
});
|
|
193
193
|
return { id: entry.id, entry };
|
|
194
194
|
}
|
|
195
|
+
export const readChannelAllowFromStore = readProviderAllowFromStore;
|
|
196
|
+
export async function addChannelAllowFromStoreEntry(params) {
|
|
197
|
+
return addProviderAllowFromStoreEntry({
|
|
198
|
+
provider: params.channel,
|
|
199
|
+
entry: params.entry,
|
|
200
|
+
env: params.env,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
export const listChannelPairingRequests = listProviderPairingRequests;
|
|
204
|
+
export async function upsertChannelPairingRequest(params) {
|
|
205
|
+
return upsertProviderPairingRequest({
|
|
206
|
+
provider: params.channel,
|
|
207
|
+
id: params.id,
|
|
208
|
+
meta: params.meta,
|
|
209
|
+
env: params.env,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
export async function approveChannelPairingCode(params) {
|
|
213
|
+
return approveProviderPairingCode({
|
|
214
|
+
provider: params.channel,
|
|
215
|
+
code: params.code,
|
|
216
|
+
env: params.env,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { intro, note, outro, spinner } from "@clack/prompts";
|
|
2
|
-
import { ensureAuthProfileStore, upsertAuthProfile } from "../agents/auth-profiles.js";
|
|
2
|
+
import { ensureAuthProfileStore, upsertAuthProfile, } from "../agents/auth-profiles.js";
|
|
3
3
|
import { updateConfig } from "../commands/models/shared.js";
|
|
4
4
|
import { applyAuthProfileConfig } from "../commands/onboard-auth.js";
|
|
5
5
|
import { CONFIG_PATH_NEXUS } from "../config/config.js";
|
|
@@ -24,7 +24,7 @@ export function buildAgentSessionKey(params) {
|
|
|
24
24
|
return buildAgentPeerSessionKey({
|
|
25
25
|
agentId: params.agentId,
|
|
26
26
|
mainKey: DEFAULT_MAIN_KEY,
|
|
27
|
-
provider,
|
|
27
|
+
channel: provider,
|
|
28
28
|
peerKind: peer?.kind ?? "dm",
|
|
29
29
|
peerId: peer ? normalizeId(peer.id) || "unknown" : null,
|
|
30
30
|
});
|
|
@@ -52,11 +52,11 @@ function pickFirstExistingAgentId(cfg, agentId) {
|
|
|
52
52
|
return normalized;
|
|
53
53
|
return normalizeAgentId(resolveDefaultAgentId(cfg));
|
|
54
54
|
}
|
|
55
|
-
function
|
|
56
|
-
const key = normalizeToken(match?.
|
|
55
|
+
function matchesChannel(match, channel) {
|
|
56
|
+
const key = normalizeToken(match?.channel);
|
|
57
57
|
if (!key)
|
|
58
58
|
return false;
|
|
59
|
-
return key ===
|
|
59
|
+
return key === channel;
|
|
60
60
|
}
|
|
61
61
|
function matchesPeer(match, peer) {
|
|
62
62
|
const m = match?.peer;
|
|
@@ -91,7 +91,7 @@ export function resolveAgentRoute(input) {
|
|
|
91
91
|
const bindings = listBindings(input.cfg).filter((binding) => {
|
|
92
92
|
if (!binding || typeof binding !== "object")
|
|
93
93
|
return false;
|
|
94
|
-
if (!
|
|
94
|
+
if (!matchesChannel(binding.match, provider))
|
|
95
95
|
return false;
|
|
96
96
|
return matchesAccountId(binding.match?.accountId, accountId);
|
|
97
97
|
});
|
|
@@ -139,6 +139,6 @@ export function resolveAgentRoute(input) {
|
|
|
139
139
|
!b.match?.guildId &&
|
|
140
140
|
!b.match?.teamId);
|
|
141
141
|
if (anyAccountMatch)
|
|
142
|
-
return choose(anyAccountMatch.agentId, "binding.
|
|
142
|
+
return choose(anyAccountMatch.agentId, "binding.channel");
|
|
143
143
|
return choose(resolveDefaultAgentId(input.cfg), "default");
|
|
144
144
|
}
|
|
@@ -57,7 +57,9 @@ export function buildAgentPeerSessionKey(params) {
|
|
|
57
57
|
mainKey: params.mainKey,
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
-
const provider = (params.
|
|
60
|
+
const provider = (params.channel ?? params.provider ?? "")
|
|
61
|
+
.trim()
|
|
62
|
+
.toLowerCase() || "unknown";
|
|
61
63
|
const peerId = (params.peerId ?? "").trim() || "unknown";
|
|
62
64
|
return `agent:${normalizeAgentId(params.agentId)}:${provider}:${peerKind}:${peerId}`;
|
|
63
65
|
}
|
|
@@ -10,7 +10,7 @@ function normalizeMatchValue(raw) {
|
|
|
10
10
|
const value = raw?.trim().toLowerCase();
|
|
11
11
|
return value ? value : undefined;
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function deriveChannelFromKey(key) {
|
|
14
14
|
if (!key)
|
|
15
15
|
return undefined;
|
|
16
16
|
const parts = key.split(":").filter(Boolean);
|
|
@@ -35,10 +35,10 @@ export function resolveSendPolicy(params) {
|
|
|
35
35
|
const policy = params.cfg.session?.sendPolicy;
|
|
36
36
|
if (!policy)
|
|
37
37
|
return "allow";
|
|
38
|
-
const
|
|
38
|
+
const channel = normalizeMatchValue(params.provider) ??
|
|
39
39
|
normalizeMatchValue(params.entry?.provider) ??
|
|
40
40
|
normalizeMatchValue(params.entry?.lastProvider) ??
|
|
41
|
-
|
|
41
|
+
deriveChannelFromKey(params.sessionKey);
|
|
42
42
|
const chatType = normalizeMatchValue(params.chatType ?? params.entry?.chatType) ??
|
|
43
43
|
normalizeMatchValue(deriveChatTypeFromKey(params.sessionKey));
|
|
44
44
|
const sessionKey = params.sessionKey ?? "";
|
|
@@ -48,10 +48,10 @@ export function resolveSendPolicy(params) {
|
|
|
48
48
|
continue;
|
|
49
49
|
const action = normalizeSendPolicy(rule.action) ?? "allow";
|
|
50
50
|
const match = rule.match ?? {};
|
|
51
|
-
const
|
|
51
|
+
const matchChannel = normalizeMatchValue(match.channel);
|
|
52
52
|
const matchChatType = normalizeMatchValue(match.chatType);
|
|
53
53
|
const matchPrefix = normalizeMatchValue(match.keyPrefix);
|
|
54
|
-
if (
|
|
54
|
+
if (matchChannel && matchChannel !== channel)
|
|
55
55
|
continue;
|
|
56
56
|
if (matchChatType && matchChatType !== chatType)
|
|
57
57
|
continue;
|
package/dist/slack/monitor.js
CHANGED
|
@@ -27,6 +27,9 @@ function normalizeSlackSlug(raw) {
|
|
|
27
27
|
const cleaned = dashed.replace(/[^a-z0-9#@._+-]+/g, "-");
|
|
28
28
|
return cleaned.replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, "");
|
|
29
29
|
}
|
|
30
|
+
function escapeRegExp(value) {
|
|
31
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
32
|
+
}
|
|
30
33
|
function normalizeSlackSlashCommandName(raw) {
|
|
31
34
|
const trimmed = raw?.trim().toLowerCase() ?? "";
|
|
32
35
|
if (!trimmed)
|
|
@@ -34,6 +37,24 @@ function normalizeSlackSlashCommandName(raw) {
|
|
|
34
37
|
const stripped = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
|
|
35
38
|
return normalizeSlackSlug(stripped);
|
|
36
39
|
}
|
|
40
|
+
export function buildSlackSlashCommandMatcher(rawName) {
|
|
41
|
+
const normalized = normalizeSlackSlashCommandName(rawName);
|
|
42
|
+
const name = normalized || "nexus";
|
|
43
|
+
return new RegExp(`^/?${escapeRegExp(name)}$`, "i");
|
|
44
|
+
}
|
|
45
|
+
export function resolveSlackThreadTs(params) {
|
|
46
|
+
if (params.incomingThreadTs)
|
|
47
|
+
return params.incomingThreadTs;
|
|
48
|
+
if (params.replyToMode === "off")
|
|
49
|
+
return undefined;
|
|
50
|
+
if (!params.messageTs)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (params.replyToMode === "all")
|
|
53
|
+
return params.messageTs;
|
|
54
|
+
if (!params.hasReplied)
|
|
55
|
+
return params.messageTs;
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
37
58
|
function normalizeAllowList(list) {
|
|
38
59
|
return (list ?? []).map((entry) => String(entry).trim()).filter(Boolean);
|
|
39
60
|
}
|
|
@@ -62,7 +83,7 @@ function allowListMatches(params) {
|
|
|
62
83
|
function resolveSlackSlashCommandConfig(raw) {
|
|
63
84
|
const rawName = raw?.name?.trim() || "nexus";
|
|
64
85
|
const normalizedName = normalizeSlackSlashCommandName(rawName);
|
|
65
|
-
const
|
|
86
|
+
const _name = normalizedName || "nexus";
|
|
66
87
|
return {
|
|
67
88
|
enabled: raw?.enabled === true,
|
|
68
89
|
name: raw?.name?.trim() || "clawd",
|
|
@@ -5,7 +5,8 @@ export function resolveTelegramReactionLevel(params) {
|
|
|
5
5
|
const channelConfig = "channels" in params.cfg
|
|
6
6
|
? params.cfg.channels?.telegram
|
|
7
7
|
: undefined;
|
|
8
|
-
const telegramCfg = channelConfig ??
|
|
8
|
+
const telegramCfg = channelConfig ??
|
|
9
|
+
params.cfg.telegram;
|
|
9
10
|
const level = (telegramCfg?.reactionLevel ?? "ack");
|
|
10
11
|
switch (level) {
|
|
11
12
|
case "off":
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { resolveStateDir } from "./config/paths.js";
|
|
4
5
|
import { logVerbose, shouldLogVerbose } from "./globals.js";
|
|
5
6
|
export async function ensureDir(dir) {
|
|
6
7
|
await fs.promises.mkdir(dir, { recursive: true });
|
|
@@ -147,7 +148,7 @@ export function resolveNexusRoot(env = process.env, homedir = os.homedir) {
|
|
|
147
148
|
return resolveUserPath(override);
|
|
148
149
|
return path.join(homedir(), "nexus");
|
|
149
150
|
}
|
|
150
|
-
// Nexus root directory containing home
|
|
151
|
+
// Nexus root directory containing home/ and state/
|
|
151
152
|
export const NEXUS_ROOT = resolveNexusRoot();
|
|
152
|
-
// Managed skills directory
|
|
153
|
-
export const MANAGED_SKILLS_DIR = path.join(
|
|
153
|
+
// Managed skills directory lives under state/
|
|
154
|
+
export const MANAGED_SKILLS_DIR = path.join(resolveStateDir(), "skills");
|