@askexenow/exe-os 0.9.179 → 0.9.181
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/deploy/compose/init-db.sql +76 -0
- package/dist/bin/exe-forget.js +1 -1
- package/dist/bin/exe-search.js +1 -1
- package/dist/bin/stack-update.js +1 -1
- package/dist/bin/vps-health-gate.js +1 -1
- package/dist/{catchup-brief-2QLCQQL7.js → catchup-brief-RYXZY7KB.js} +1 -1
- package/dist/{chunk-WCYT54XP.js → chunk-BV7EZIL4.js} +29 -17
- package/dist/{chunk-HOGTVJOL.js → chunk-CD76FDBD.js} +9 -1
- package/dist/{chunk-ARA54W6G.js → chunk-EWINUEVJ.js} +2 -2
- package/dist/hooks/error-recall.js +1 -1
- package/dist/hooks/pre-tool-use.js +94 -15
- package/dist/hooks/prompt-submit.js +1 -1
- package/dist/hooks/session-start.js +1 -1
- package/dist/lib/hybrid-search.js +1 -1
- package/dist/mcp/register-tools.js +2 -2
- package/dist/mcp/server.js +2 -2
- package/dist/{self-query-router-37VB3PKQ.js → self-query-router-VNUCMGFX.js} +15 -5
- package/dist/{stack-update-2B2UXREV.js → stack-update-LEUGR7HP.js} +1 -1
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ CREATE SCHEMA IF NOT EXISTS gateway;
|
|
|
22
22
|
CREATE SCHEMA IF NOT EXISTS wiki;
|
|
23
23
|
CREATE SCHEMA IF NOT EXISTS crm;
|
|
24
24
|
CREATE SCHEMA IF NOT EXISTS auth;
|
|
25
|
+
CREATE SCHEMA IF NOT EXISTS billing;
|
|
25
26
|
|
|
26
27
|
-- Grant exe full access to all schemas
|
|
27
28
|
DO $$ DECLARE s text; BEGIN
|
|
@@ -123,3 +124,78 @@ CREATE TABLE IF NOT EXISTS filtered.events (
|
|
|
123
124
|
|
|
124
125
|
CREATE INDEX IF NOT EXISTS idx_filtered_events_type ON filtered.events (event_type, occurred_at DESC);
|
|
125
126
|
CREATE INDEX IF NOT EXISTS idx_filtered_events_contact ON filtered.events (contact_id);
|
|
127
|
+
|
|
128
|
+
-- ---------------------------------------------------------------------------
|
|
129
|
+
-- Billing schema — licenses, stack releases, entitlements, deploy audits.
|
|
130
|
+
-- Used by the exe-update service (update.askexe.com) to authenticate
|
|
131
|
+
-- customers and serve stack manifests.
|
|
132
|
+
-- ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
CREATE TABLE IF NOT EXISTS billing.licenses (
|
|
135
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
136
|
+
key TEXT NOT NULL UNIQUE,
|
|
137
|
+
email TEXT NOT NULL,
|
|
138
|
+
name TEXT,
|
|
139
|
+
plan TEXT NOT NULL DEFAULT 'pro',
|
|
140
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
141
|
+
device_limit INT NOT NULL DEFAULT 3,
|
|
142
|
+
employee_limit INT NOT NULL DEFAULT 5,
|
|
143
|
+
memory_limit INT NOT NULL DEFAULT 100000,
|
|
144
|
+
expires_at TIMESTAMPTZ,
|
|
145
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
146
|
+
created_by TEXT NOT NULL DEFAULT 'system'
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_billing_licenses_key ON billing.licenses (key);
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_billing_licenses_status ON billing.licenses (status);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_billing_licenses_email ON billing.licenses (email);
|
|
152
|
+
|
|
153
|
+
CREATE TABLE IF NOT EXISTS billing.stack_releases (
|
|
154
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
155
|
+
version TEXT NOT NULL UNIQUE,
|
|
156
|
+
manifest JSONB NOT NULL,
|
|
157
|
+
manifest_sha256 TEXT NOT NULL,
|
|
158
|
+
signature TEXT,
|
|
159
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
160
|
+
released_at TIMESTAMPTZ,
|
|
161
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
162
|
+
created_by TEXT NOT NULL DEFAULT 'system'
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_billing_stack_releases_status ON billing.stack_releases (status);
|
|
166
|
+
CREATE INDEX IF NOT EXISTS idx_billing_stack_releases_version ON billing.stack_releases (version);
|
|
167
|
+
|
|
168
|
+
CREATE TABLE IF NOT EXISTS billing.stack_entitlements (
|
|
169
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
170
|
+
license_key TEXT REFERENCES billing.licenses(key),
|
|
171
|
+
customer_id TEXT,
|
|
172
|
+
device_id TEXT,
|
|
173
|
+
token_hash TEXT UNIQUE,
|
|
174
|
+
stack_version TEXT NOT NULL DEFAULT '*',
|
|
175
|
+
channel TEXT DEFAULT 'stable',
|
|
176
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
177
|
+
revoked_at TIMESTAMPTZ,
|
|
178
|
+
expires_at TIMESTAMPTZ,
|
|
179
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_billing_entitlements_token ON billing.stack_entitlements (token_hash);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_billing_entitlements_status ON billing.stack_entitlements (status);
|
|
184
|
+
|
|
185
|
+
CREATE TABLE IF NOT EXISTS billing.stack_deploy_audits (
|
|
186
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
187
|
+
license_key TEXT,
|
|
188
|
+
device_id TEXT,
|
|
189
|
+
stack_version TEXT NOT NULL,
|
|
190
|
+
previous_version TEXT,
|
|
191
|
+
status TEXT NOT NULL,
|
|
192
|
+
error TEXT,
|
|
193
|
+
metadata JSONB,
|
|
194
|
+
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
195
|
+
completed_at TIMESTAMPTZ,
|
|
196
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
CREATE INDEX IF NOT EXISTS idx_billing_deploy_audits_license ON billing.stack_deploy_audits (license_key);
|
|
200
|
+
CREATE INDEX IF NOT EXISTS idx_billing_deploy_audits_status ON billing.stack_deploy_audits (status);
|
|
201
|
+
CREATE INDEX IF NOT EXISTS idx_billing_deploy_audits_started ON billing.stack_deploy_audits (started_at DESC NULLS LAST);
|
package/dist/bin/exe-forget.js
CHANGED
package/dist/bin/exe-search.js
CHANGED
package/dist/bin/stack-update.js
CHANGED
|
@@ -207,7 +207,7 @@ async function main(args) {
|
|
|
207
207
|
console.log("[health-gate] Starting rollback...");
|
|
208
208
|
restorePreDeployBackup();
|
|
209
209
|
try {
|
|
210
|
-
const { rollbackStackUpdate, defaultStackPaths } = await import("../stack-update-
|
|
210
|
+
const { rollbackStackUpdate, defaultStackPaths } = await import("../stack-update-LEUGR7HP.js");
|
|
211
211
|
const paths = defaultStackPaths();
|
|
212
212
|
await rollbackStackUpdate({
|
|
213
213
|
manifestRef: paths.manifestRef,
|
|
@@ -762,16 +762,26 @@ async function runStackUpdate(options) {
|
|
|
762
762
|
}
|
|
763
763
|
async function fetchImageCredentials(options) {
|
|
764
764
|
if (!options.imageCredentialsUrl) return null;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
765
|
+
try {
|
|
766
|
+
const res = await fetch(options.imageCredentialsUrl, {
|
|
767
|
+
method: "POST",
|
|
768
|
+
headers: {
|
|
769
|
+
"content-type": "application/json",
|
|
770
|
+
...options.manifestAuthToken ? { authorization: `Bearer ${options.manifestAuthToken}` } : {}
|
|
771
|
+
},
|
|
772
|
+
body: JSON.stringify({ deviceId: options.deviceId, licenseKey: options.licenseKey }),
|
|
773
|
+
signal: AbortSignal.timeout(1e4)
|
|
774
|
+
});
|
|
775
|
+
if (!res.ok) {
|
|
776
|
+
console.warn(`[stack-update] Image credentials unavailable (HTTP ${res.status}). Docker pull will use existing auth.`);
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
return await res.json();
|
|
780
|
+
} catch (err) {
|
|
781
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
782
|
+
console.warn(`[stack-update] Image credentials fetch failed: ${reason}. Docker pull will use existing auth.`);
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
775
785
|
}
|
|
776
786
|
function readCurrentStackVersion(lockFile) {
|
|
777
787
|
if (!existsSync(lockFile)) return void 0;
|
|
@@ -886,18 +896,20 @@ function defaultStackPaths() {
|
|
|
886
896
|
const cwdCompose = path.resolve("docker-compose.yml");
|
|
887
897
|
const cwdEnv = path.resolve(".env");
|
|
888
898
|
const packagedManifest = path.join(resolvePackageRoot(), "deploy", "stack-manifests", "v0.9.json");
|
|
889
|
-
const manifestRef = process.env.EXE_STACK_MANIFEST || (existsSync(packagedManifest) ? packagedManifest : "https://
|
|
899
|
+
const manifestRef = process.env.EXE_STACK_MANIFEST || (existsSync(packagedManifest) ? packagedManifest : "https://update.askexe.com/v1/stack-manifest.json");
|
|
900
|
+
const licenseKey = process.env.EXE_LICENSE_KEY || loadLicense() || process.env.EXE_STACK_UPDATE_TOKEN || void 0;
|
|
901
|
+
const isRemoteManifest = /^https?:\/\//.test(manifestRef);
|
|
902
|
+
const hasLicenseForRemote = !!licenseKey;
|
|
903
|
+
const updateServiceUrl = process.env.EXE_UPDATE_SERVICE_URL || "https://update.askexe.com";
|
|
890
904
|
return {
|
|
891
905
|
composeFile: process.env.EXE_STACK_COMPOSE_FILE || (existsSync(cwdCompose) ? cwdCompose : "/opt/exe-stack/docker-compose.yml"),
|
|
892
906
|
envFile: process.env.EXE_STACK_ENV_FILE || (existsSync(cwdEnv) ? cwdEnv : "/opt/exe-stack/.env"),
|
|
893
907
|
manifestRef,
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || (/^https?:\/\//.test(manifestRef) ? "https://api.askexe.com/v1/image-credentials" : void 0),
|
|
898
|
-
// License key IS the auth token for api.askexe.com — no separate update token needed.
|
|
908
|
+
auditUrl: process.env.EXE_STACK_AUDIT_URL || (isRemoteManifest || hasLicenseForRemote ? `${updateServiceUrl}/v1/deploy-audits` : void 0),
|
|
909
|
+
imageCredentialsUrl: process.env.EXE_STACK_IMAGE_CREDENTIALS_URL || (isRemoteManifest || hasLicenseForRemote ? `${updateServiceUrl}/v1/image-credentials` : void 0),
|
|
910
|
+
// License key IS the auth token for update.askexe.com — no separate update token needed.
|
|
899
911
|
// EXE_STACK_UPDATE_TOKEN kept as legacy fallback during migration.
|
|
900
|
-
manifestAuthToken:
|
|
912
|
+
manifestAuthToken: licenseKey,
|
|
901
913
|
manifestPublicKey: loadDefaultPublicKey()
|
|
902
914
|
};
|
|
903
915
|
}
|
|
@@ -201,12 +201,14 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
201
201
|
let effectiveQuery = queryText;
|
|
202
202
|
const effectiveOptions = { ...options };
|
|
203
203
|
let _isBroadQuery = false;
|
|
204
|
+
let inferredTerms = [];
|
|
204
205
|
if (config.selfQueryRouter && process.env.ANTHROPIC_API_KEY) {
|
|
205
206
|
try {
|
|
206
|
-
const { routeQuery } = await import("./self-query-router-
|
|
207
|
+
const { routeQuery } = await import("./self-query-router-VNUCMGFX.js");
|
|
207
208
|
const routed = await routeQuery(queryText, config.selfQueryModel);
|
|
208
209
|
effectiveQuery = routed.semanticQuery;
|
|
209
210
|
_isBroadQuery = routed.isBroadQuery;
|
|
211
|
+
inferredTerms = routed.inferredTerms ?? [];
|
|
210
212
|
if (routed.projectFilter && !effectiveOptions.projectName) {
|
|
211
213
|
effectiveOptions.projectName = routed.projectFilter;
|
|
212
214
|
}
|
|
@@ -266,6 +268,11 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
266
268
|
}
|
|
267
269
|
}
|
|
268
270
|
const entitySubqueries = extractEntitySubqueries(effectiveQuery);
|
|
271
|
+
if (inferredTerms.length > 0) {
|
|
272
|
+
entitySubqueries.push(...inferredTerms.filter(
|
|
273
|
+
(t) => !entitySubqueries.some((sq) => sq.toLowerCase() === t.toLowerCase())
|
|
274
|
+
));
|
|
275
|
+
}
|
|
269
276
|
const subqueryPromises = entitySubqueries.map(
|
|
270
277
|
(sq) => lightweightSearch(sq, agentId, { ...fetchOptions, limit: Math.min(fetchLimit, 20) }).catch(() => [])
|
|
271
278
|
);
|
|
@@ -444,6 +451,7 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
444
451
|
contradictionResolution: merged.length >= 2,
|
|
445
452
|
reranker: rerankedAndBlended !== null,
|
|
446
453
|
selfQueryRouter: effectiveQuery !== queryText,
|
|
454
|
+
inferredTerms: inferredTerms.length > 0 ? inferredTerms : void 0,
|
|
447
455
|
lowConfidence: finalResults[0]?._lowConfidence ?? false
|
|
448
456
|
},
|
|
449
457
|
topResults: finalResults.slice(0, 10).map((r, i) => {
|
|
@@ -183,7 +183,7 @@ import {
|
|
|
183
183
|
import {
|
|
184
184
|
hybridSearch,
|
|
185
185
|
recentRecords
|
|
186
|
-
} from "./chunk-
|
|
186
|
+
} from "./chunk-CD76FDBD.js";
|
|
187
187
|
import {
|
|
188
188
|
attachDocumentMetadata,
|
|
189
189
|
flushBatch,
|
|
@@ -5200,7 +5200,7 @@ function registerGetDaemonHealth(server) {
|
|
|
5200
5200
|
lines.push(`| Requests served | ${health.requests_served.toLocaleString()} |`);
|
|
5201
5201
|
}
|
|
5202
5202
|
try {
|
|
5203
|
-
const { getQueryCacheStats } = await import("./self-query-router-
|
|
5203
|
+
const { getQueryCacheStats } = await import("./self-query-router-VNUCMGFX.js");
|
|
5204
5204
|
const cache = getQueryCacheStats();
|
|
5205
5205
|
lines.push(`| Query cache size | ${cache.size} / ${cache.maxSize} |`);
|
|
5206
5206
|
lines.push(`| Query cache hit rate | ${cache.hitRate} |`);
|
|
@@ -90,6 +90,21 @@ function countEngineerSessions() {
|
|
|
90
90
|
return 0;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
function getWorktreeContext() {
|
|
94
|
+
const cwd = process.cwd();
|
|
95
|
+
const match = cwd.match(/\.worktrees\/([^/]+)/);
|
|
96
|
+
if (!match) return { isWorktree: false };
|
|
97
|
+
const worktreeName = match[1];
|
|
98
|
+
const worktreeRoot = cwd.slice(0, cwd.indexOf(".worktrees/") + ".worktrees/".length + worktreeName.length);
|
|
99
|
+
const repoRoot = cwd.slice(0, cwd.indexOf(".worktrees") - 1);
|
|
100
|
+
return { isWorktree: true, worktreeName, worktreeRoot, repoRoot };
|
|
101
|
+
}
|
|
102
|
+
function isMainRepoPath(filePath, ctx) {
|
|
103
|
+
return filePath.startsWith(ctx.repoRoot + "/") && !filePath.startsWith(ctx.worktreeRoot + "/") && !filePath.startsWith(ctx.worktreeRoot);
|
|
104
|
+
}
|
|
105
|
+
function rewriteToWorktree(filePath, ctx) {
|
|
106
|
+
return filePath.replace(ctx.repoRoot, ctx.worktreeRoot);
|
|
107
|
+
}
|
|
93
108
|
var MAX_INPUT_SIZE = 1e6;
|
|
94
109
|
var input = "";
|
|
95
110
|
process.stdin.setEncoding("utf8");
|
|
@@ -173,6 +188,34 @@ If MCP is down, tell the user: "MCP disconnected. Run /mcp to reconnect."`
|
|
|
173
188
|
process.exit(0);
|
|
174
189
|
}
|
|
175
190
|
}
|
|
191
|
+
const wtCtxBash = getWorktreeContext();
|
|
192
|
+
if (wtCtxBash.isWorktree) {
|
|
193
|
+
const mainPrefix = wtCtxBash.repoRoot + "/";
|
|
194
|
+
const wtPrefix = wtCtxBash.worktreeRoot + "/";
|
|
195
|
+
const writePatterns = [
|
|
196
|
+
// Redirect operators: > file, >> file
|
|
197
|
+
new RegExp(`>\\s*${wtCtxBash.repoRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`),
|
|
198
|
+
// cp/mv/rsync destination (last arg): cp src DEST
|
|
199
|
+
new RegExp(`\\b(?:cp|mv|rsync)\\s+.*\\s+${wtCtxBash.repoRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`),
|
|
200
|
+
// sed -i targeting main repo files
|
|
201
|
+
new RegExp(`\\bsed\\s+.*-i.*\\s+${wtCtxBash.repoRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`),
|
|
202
|
+
// tee targeting main repo
|
|
203
|
+
new RegExp(`\\btee\\s+.*${wtCtxBash.repoRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`)
|
|
204
|
+
];
|
|
205
|
+
const hasMainRepoWrite = writePatterns.some((p) => p.test(command)) && !command.includes(wtPrefix);
|
|
206
|
+
if (hasMainRepoWrite) {
|
|
207
|
+
const output = JSON.stringify({
|
|
208
|
+
hookSpecificOutput: { permissionDecision: "deny" },
|
|
209
|
+
systemMessage: `ACTION BLOCKED: You are in worktree "${wtCtxBash.worktreeName}" but your Bash command writes to the main repo root.
|
|
210
|
+
DENIED command contains write to: ${mainPrefix}
|
|
211
|
+
Your worktree root is: ${wtCtxBash.worktreeRoot}/
|
|
212
|
+
Rewrite your command to target ${wtCtxBash.worktreeRoot}/ instead.
|
|
213
|
+
NEVER write to the main repo from a worktree. The main repo belongs to the coordinator.`
|
|
214
|
+
});
|
|
215
|
+
process.stdout.write(output);
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
176
219
|
const cdMatch = command.match(/\bcd\s+(?:~\/|\/Users\/\w+\/)([\w-]+)/);
|
|
177
220
|
if (cdMatch) {
|
|
178
221
|
const targetDir = cdMatch[1];
|
|
@@ -195,25 +238,61 @@ If MCP is down, tell the user: "MCP disconnected. Run /mcp to reconnect."`
|
|
|
195
238
|
}
|
|
196
239
|
}
|
|
197
240
|
}
|
|
241
|
+
if (/^(Read)$/.test(data.tool_name)) {
|
|
242
|
+
const filePath = data.tool_input?.file_path ?? "";
|
|
243
|
+
const wtCtx = getWorktreeContext();
|
|
244
|
+
if (wtCtx.isWorktree && filePath && isMainRepoPath(filePath, wtCtx)) {
|
|
245
|
+
const rewritten = rewriteToWorktree(filePath, wtCtx);
|
|
246
|
+
process.stderr.write(
|
|
247
|
+
`[pre-tool-use] Worktree Read rewrite: ${filePath} \u2192 ${rewritten}
|
|
248
|
+
`
|
|
249
|
+
);
|
|
250
|
+
const output = JSON.stringify({
|
|
251
|
+
hookSpecificOutput: {
|
|
252
|
+
permissionDecision: "allow",
|
|
253
|
+
updatedInput: { file_path: rewritten }
|
|
254
|
+
},
|
|
255
|
+
systemMessage: `\u2139\uFE0F Path redirected: you are in worktree "${wtCtx.worktreeName}". Read redirected from main repo to your worktree copy: ${rewritten}`
|
|
256
|
+
});
|
|
257
|
+
process.stdout.write(output);
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (/^(Glob|Grep)$/.test(data.tool_name)) {
|
|
262
|
+
const toolPath = data.tool_input?.path ?? "";
|
|
263
|
+
const wtCtx = getWorktreeContext();
|
|
264
|
+
if (wtCtx.isWorktree && toolPath && isMainRepoPath(toolPath, wtCtx)) {
|
|
265
|
+
const rewritten = rewriteToWorktree(toolPath, wtCtx);
|
|
266
|
+
process.stderr.write(
|
|
267
|
+
`[pre-tool-use] Worktree ${data.tool_name} rewrite: ${toolPath} \u2192 ${rewritten}
|
|
268
|
+
`
|
|
269
|
+
);
|
|
270
|
+
const output = JSON.stringify({
|
|
271
|
+
hookSpecificOutput: {
|
|
272
|
+
permissionDecision: "allow",
|
|
273
|
+
updatedInput: { ...data.tool_input, path: rewritten }
|
|
274
|
+
},
|
|
275
|
+
systemMessage: `\u2139\uFE0F Path redirected: you are in worktree "${wtCtx.worktreeName}". ${data.tool_name} redirected from main repo to your worktree: ${rewritten}`
|
|
276
|
+
});
|
|
277
|
+
process.stdout.write(output);
|
|
278
|
+
process.exit(0);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
198
281
|
if (/^(Write|Edit)$/.test(data.tool_name)) {
|
|
199
282
|
const filePath = data.tool_input?.file_path ?? "";
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const output = JSON.stringify({
|
|
207
|
-
hookSpecificOutput: { permissionDecision: "deny" },
|
|
208
|
-
systemMessage: `ACTION BLOCKED: You are in worktree "${worktreeMatch[1]}" but tried to edit a file in the main repo root.
|
|
283
|
+
const wtCtx = getWorktreeContext();
|
|
284
|
+
if (wtCtx.isWorktree && filePath && isMainRepoPath(filePath, wtCtx)) {
|
|
285
|
+
const suggested = rewriteToWorktree(filePath, wtCtx);
|
|
286
|
+
const output = JSON.stringify({
|
|
287
|
+
hookSpecificOutput: { permissionDecision: "deny" },
|
|
288
|
+
systemMessage: `ACTION BLOCKED: You are in worktree "${wtCtx.worktreeName}" but tried to edit a file in the main repo root.
|
|
209
289
|
DENIED path: ${filePath}
|
|
210
|
-
Your worktree root is: ${worktreeRoot}/
|
|
211
|
-
Edit the file at: ${
|
|
290
|
+
Your worktree root is: ${wtCtx.worktreeRoot}/
|
|
291
|
+
Edit the file at: ${suggested} instead.
|
|
212
292
|
NEVER edit files outside your worktree. The main repo belongs to the coordinator.`
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
293
|
+
});
|
|
294
|
+
process.stdout.write(output);
|
|
295
|
+
process.exit(0);
|
|
217
296
|
}
|
|
218
297
|
}
|
|
219
298
|
if (/^(Write|Edit)$/.test(data.tool_name) && !canCoordinate(agent.agentId, agent.agentRole)) {
|
|
@@ -135,7 +135,7 @@ You are **${ag.agentId}** (${ag.agentRole}). Daemon is degraded \u2014 memory un
|
|
|
135
135
|
query = `last actions on ${projectName}`;
|
|
136
136
|
header = "## Resuming Session\nHere's where you left off:";
|
|
137
137
|
try {
|
|
138
|
-
const { buildCatchupBrief } = await import("../catchup-brief-
|
|
138
|
+
const { buildCatchupBrief } = await import("../catchup-brief-RYXZY7KB.js");
|
|
139
139
|
const brief = await buildCatchupBrief(
|
|
140
140
|
agentId,
|
|
141
141
|
projectName,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
registerAllTools
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-EWINUEVJ.js";
|
|
4
4
|
import "../chunk-PLNYW6PA.js";
|
|
5
5
|
import "../chunk-R4IK2YT3.js";
|
|
6
6
|
import "../chunk-HYBT5PXK.js";
|
|
@@ -56,7 +56,7 @@ import "../chunk-7EQXVRA3.js";
|
|
|
56
56
|
import "../chunk-25CVYGFX.js";
|
|
57
57
|
import "../chunk-D6EK7PYQ.js";
|
|
58
58
|
import "../chunk-CMFLJNP6.js";
|
|
59
|
-
import "../chunk-
|
|
59
|
+
import "../chunk-CD76FDBD.js";
|
|
60
60
|
import "../chunk-U2UQMLJ3.js";
|
|
61
61
|
import "../chunk-CHCA3ZM2.js";
|
|
62
62
|
import "../chunk-DOWW3EIO.js";
|
package/dist/mcp/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "../chunk-V4TZI6EO.js";
|
|
4
4
|
import {
|
|
5
5
|
registerAllTools
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-EWINUEVJ.js";
|
|
7
7
|
import {
|
|
8
8
|
initLicenseGate
|
|
9
9
|
} from "../chunk-PLNYW6PA.js";
|
|
@@ -66,7 +66,7 @@ import "../chunk-7EQXVRA3.js";
|
|
|
66
66
|
import "../chunk-25CVYGFX.js";
|
|
67
67
|
import "../chunk-D6EK7PYQ.js";
|
|
68
68
|
import "../chunk-CMFLJNP6.js";
|
|
69
|
-
import "../chunk-
|
|
69
|
+
import "../chunk-CD76FDBD.js";
|
|
70
70
|
import {
|
|
71
71
|
disposeStore,
|
|
72
72
|
initStore
|
|
@@ -26,9 +26,14 @@ var EXTRACT_TOOL = {
|
|
|
26
26
|
is_broad_query: {
|
|
27
27
|
type: "boolean",
|
|
28
28
|
description: "True if the query is exploratory/broad (e.g., 'what has the CTO been working on', 'summarize recent activity'). False if targeted (e.g., 'how did we fix the auth bug')."
|
|
29
|
+
},
|
|
30
|
+
inferred_terms: {
|
|
31
|
+
type: "array",
|
|
32
|
+
items: { type: "string" },
|
|
33
|
+
description: "Entities or concepts NOT explicitly in the query but likely relevant for retrieval. Infer related terms from context clues. E.g., 'nephew of Ivory Lee Brown' \u2192 ['NFL', 'Oklahoma', 'football']. 'the auth bug we fixed' \u2192 ['JWT', 'token', 'login']. Return empty array if no useful inference. Max 5 terms."
|
|
29
34
|
}
|
|
30
35
|
},
|
|
31
|
-
required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query"]
|
|
36
|
+
required: ["semantic_query", "project_filter", "role_filter", "time_filter", "is_broad_query", "inferred_terms"]
|
|
32
37
|
}
|
|
33
38
|
};
|
|
34
39
|
var CACHE_MAX_SIZE = Number(process.env.EXE_QUERY_CACHE_SIZE) || 200;
|
|
@@ -72,7 +77,8 @@ async function routeQuery(query, model = "claude-haiku-4-5-20251001") {
|
|
|
72
77
|
projectFilter: null,
|
|
73
78
|
roleFilter: null,
|
|
74
79
|
timeFilter: null,
|
|
75
|
-
isBroadQuery: false
|
|
80
|
+
isBroadQuery: false,
|
|
81
|
+
inferredTerms: []
|
|
76
82
|
};
|
|
77
83
|
}
|
|
78
84
|
const cacheKey = normalizeCacheKey(query);
|
|
@@ -91,7 +97,7 @@ async function routeQuery(query, model = "claude-haiku-4-5-20251001") {
|
|
|
91
97
|
const response = await client.messages.create({
|
|
92
98
|
model,
|
|
93
99
|
max_tokens: 256,
|
|
94
|
-
system: `You are a search query router. Extract metadata filters from the user's memory search query. Today is ${now}. Convert relative time references (yesterday, last week) to ISO timestamps.`,
|
|
100
|
+
system: `You are a search query router and context enricher. Extract metadata filters from the user's memory search query. Also infer related entities/concepts that aren't explicitly stated but would help retrieval (like Apple CLaRa's Query Reasoner). Today is ${now}. Convert relative time references (yesterday, last week) to ISO timestamps.`,
|
|
95
101
|
messages: [{ role: "user", content: query }],
|
|
96
102
|
tools: [EXTRACT_TOOL],
|
|
97
103
|
tool_choice: { type: "tool", name: "extract_search_filters" }
|
|
@@ -99,12 +105,15 @@ async function routeQuery(query, model = "claude-haiku-4-5-20251001") {
|
|
|
99
105
|
const toolBlock = response.content.find((b) => b.type === "tool_use");
|
|
100
106
|
if (toolBlock && toolBlock.type === "tool_use") {
|
|
101
107
|
const input = toolBlock.input;
|
|
108
|
+
const rawTerms = input.inferred_terms;
|
|
109
|
+
const inferredTerms = Array.isArray(rawTerms) ? rawTerms.filter((t) => typeof t === "string" && t.length > 0).slice(0, 5) : [];
|
|
102
110
|
const result = {
|
|
103
111
|
semanticQuery: input.semantic_query || query,
|
|
104
112
|
projectFilter: input.project_filter || null,
|
|
105
113
|
roleFilter: input.role_filter || null,
|
|
106
114
|
timeFilter: input.time_filter || null,
|
|
107
|
-
isBroadQuery: Boolean(input.is_broad_query)
|
|
115
|
+
isBroadQuery: Boolean(input.is_broad_query),
|
|
116
|
+
inferredTerms
|
|
108
117
|
};
|
|
109
118
|
evictCache();
|
|
110
119
|
queryCache.set(cacheKey, { result, timestamp: Date.now() });
|
|
@@ -121,7 +130,8 @@ async function routeQuery(query, model = "claude-haiku-4-5-20251001") {
|
|
|
121
130
|
projectFilter: null,
|
|
122
131
|
roleFilter: null,
|
|
123
132
|
timeFilter: null,
|
|
124
|
-
isBroadQuery: false
|
|
133
|
+
isBroadQuery: false,
|
|
134
|
+
inferredTerms: []
|
|
125
135
|
};
|
|
126
136
|
}
|
|
127
137
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askexenow/exe-os",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.181",
|
|
4
4
|
"description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|