@askexenow/exe-os 0.9.111 → 0.9.113
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/README.md +9 -7
- package/dist/bin/agentic-ontology-backfill.js +62 -12
- package/dist/bin/agentic-reflection-backfill.js +37 -2
- package/dist/bin/agentic-semantic-label.js +37 -2
- package/dist/bin/backfill-conversations.js +61 -11
- package/dist/bin/backfill-responses.js +62 -12
- package/dist/bin/backfill-vectors.js +37 -2
- package/dist/bin/bulk-sync-postgres.js +63 -13
- package/dist/bin/cleanup-stale-review-tasks.js +83 -16
- package/dist/bin/cli.js +312 -80
- package/dist/bin/exe-agent-config.js +7 -1
- package/dist/bin/exe-agent.js +29 -3
- package/dist/bin/exe-assign.js +62 -12
- package/dist/bin/exe-boot.js +500 -151
- package/dist/bin/exe-call.js +46 -5
- package/dist/bin/exe-cloud.js +101 -16
- package/dist/bin/exe-dispatch.js +827 -27
- package/dist/bin/exe-doctor.js +61 -11
- package/dist/bin/exe-export-behaviors.js +67 -14
- package/dist/bin/exe-forget.js +62 -12
- package/dist/bin/exe-gateway.js +147 -27
- package/dist/bin/exe-heartbeat.js +83 -16
- package/dist/bin/exe-kill.js +62 -12
- package/dist/bin/exe-launch-agent.js +83 -15
- package/dist/bin/exe-new-employee.js +176 -8
- package/dist/bin/exe-pending-messages.js +83 -16
- package/dist/bin/exe-pending-notifications.js +83 -16
- package/dist/bin/exe-pending-reviews.js +83 -16
- package/dist/bin/exe-rename.js +62 -12
- package/dist/bin/exe-review.js +62 -12
- package/dist/bin/exe-search.js +62 -12
- package/dist/bin/exe-session-cleanup.js +949 -149
- package/dist/bin/exe-settings.js +10 -4
- package/dist/bin/exe-start-codex.js +537 -248
- package/dist/bin/exe-start-opencode.js +547 -168
- package/dist/bin/exe-status.js +83 -16
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +62 -12
- package/dist/bin/git-sweep.js +827 -27
- package/dist/bin/graph-backfill.js +62 -12
- package/dist/bin/graph-export.js +62 -12
- package/dist/bin/install.js +62 -4
- package/dist/bin/intercom-check.js +949 -149
- package/dist/bin/pre-publish.js +14 -2
- package/dist/bin/scan-tasks.js +827 -27
- package/dist/bin/setup.js +99 -14
- package/dist/bin/shard-migrate.js +62 -12
- package/dist/bin/stack-update.js +1 -1
- package/dist/bin/update.js +3 -3
- package/dist/gateway/index.js +586 -26
- package/dist/hooks/bug-report-worker.js +586 -26
- package/dist/hooks/codex-stop-task-finalizer.js +977 -143
- package/dist/hooks/commit-complete.js +827 -27
- package/dist/hooks/error-recall.js +62 -12
- package/dist/hooks/ingest.js +4579 -249
- package/dist/hooks/instructions-loaded.js +62 -12
- package/dist/hooks/notification.js +62 -12
- package/dist/hooks/post-compact.js +83 -16
- package/dist/hooks/post-tool-combined.js +83 -16
- package/dist/hooks/pre-compact.js +907 -107
- package/dist/hooks/pre-tool-use.js +98 -16
- package/dist/hooks/prompt-submit.js +596 -30
- package/dist/hooks/session-end.js +909 -112
- package/dist/hooks/session-start.js +112 -17
- package/dist/hooks/stop.js +82 -15
- package/dist/hooks/subagent-stop.js +83 -16
- package/dist/hooks/summary-worker.js +81 -8
- package/dist/index.js +595 -29
- package/dist/lib/agent-config.js +16 -1
- package/dist/lib/cloud-sync.js +45 -1
- package/dist/lib/consolidation.js +16 -1
- package/dist/lib/database.js +23 -0
- package/dist/lib/db.js +23 -0
- package/dist/lib/device-registry.js +23 -0
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +16 -1
- package/dist/lib/exe-daemon.js +482 -52
- package/dist/lib/hybrid-search.js +62 -12
- package/dist/lib/license.js +3 -3
- package/dist/lib/messaging.js +21 -4
- package/dist/lib/schedules.js +37 -2
- package/dist/lib/skill-learning.js +910 -41
- package/dist/lib/status-brief.js +14 -1
- package/dist/lib/store.js +62 -12
- package/dist/lib/tasks.js +843 -93
- package/dist/lib/tmux-routing.js +766 -16
- package/dist/mcp/server.js +238 -41
- package/dist/mcp/tools/create-task.js +525 -15
- package/dist/mcp/tools/deactivate-behavior.js +33 -24
- package/dist/mcp/tools/list-tasks.js +21 -4
- package/dist/mcp/tools/send-message.js +21 -4
- package/dist/mcp/tools/update-task.js +840 -93
- package/dist/runtime/index.js +913 -107
- package/dist/tui/App.js +227 -58
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -368,6 +368,7 @@ __export(agent_config_exports, {
|
|
|
368
368
|
getAgentRuntime: () => getAgentRuntime,
|
|
369
369
|
loadAgentConfig: () => loadAgentConfig,
|
|
370
370
|
saveAgentConfig: () => saveAgentConfig,
|
|
371
|
+
setAgentMcps: () => setAgentMcps,
|
|
371
372
|
setAgentRuntime: () => setAgentRuntime
|
|
372
373
|
});
|
|
373
374
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
@@ -394,7 +395,7 @@ function getAgentRuntime(agentId) {
|
|
|
394
395
|
if (orgDefault) return orgDefault;
|
|
395
396
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
396
397
|
}
|
|
397
|
-
function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
398
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
398
399
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
399
400
|
if (!knownModels) {
|
|
400
401
|
return {
|
|
@@ -409,12 +410,26 @@ function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
|
409
410
|
};
|
|
410
411
|
}
|
|
411
412
|
const config2 = loadAgentConfig();
|
|
413
|
+
const existing = config2[agentId];
|
|
412
414
|
const entry = { runtime, model };
|
|
413
415
|
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
416
|
+
if (mcps !== void 0) {
|
|
417
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
418
|
+
} else if (existing?.mcps) {
|
|
419
|
+
entry.mcps = existing.mcps;
|
|
420
|
+
}
|
|
414
421
|
config2[agentId] = entry;
|
|
415
422
|
saveAgentConfig(config2);
|
|
416
423
|
return { ok: true };
|
|
417
424
|
}
|
|
425
|
+
function setAgentMcps(agentId, mcps) {
|
|
426
|
+
const config2 = loadAgentConfig();
|
|
427
|
+
const existing = config2[agentId] ?? getAgentRuntime(agentId);
|
|
428
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
429
|
+
config2[agentId] = existing;
|
|
430
|
+
saveAgentConfig(config2);
|
|
431
|
+
return { ok: true };
|
|
432
|
+
}
|
|
418
433
|
function clearAgentRuntime(agentId) {
|
|
419
434
|
const config2 = loadAgentConfig();
|
|
420
435
|
delete config2[agentId];
|
|
@@ -2835,6 +2850,13 @@ async function ensureSchema() {
|
|
|
2835
2850
|
} catch (e) {
|
|
2836
2851
|
logCatchDebug("migration", e);
|
|
2837
2852
|
}
|
|
2853
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2854
|
+
try {
|
|
2855
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2856
|
+
} catch (e) {
|
|
2857
|
+
logCatchDebug("migration", e);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2838
2860
|
try {
|
|
2839
2861
|
await client.execute({
|
|
2840
2862
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -4051,6 +4073,22 @@ async function ensureSchema() {
|
|
|
4051
4073
|
} catch (e) {
|
|
4052
4074
|
logCatchDebug("migration", e);
|
|
4053
4075
|
}
|
|
4076
|
+
try {
|
|
4077
|
+
await client.execute({
|
|
4078
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
4079
|
+
args: []
|
|
4080
|
+
});
|
|
4081
|
+
} catch (e) {
|
|
4082
|
+
logCatchDebug("migration", e);
|
|
4083
|
+
}
|
|
4084
|
+
try {
|
|
4085
|
+
await client.execute({
|
|
4086
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
4087
|
+
args: []
|
|
4088
|
+
});
|
|
4089
|
+
} catch (e) {
|
|
4090
|
+
logCatchDebug("migration", e);
|
|
4091
|
+
}
|
|
4054
4092
|
}
|
|
4055
4093
|
async function disposeDatabase() {
|
|
4056
4094
|
if (_walCheckpointTimer) {
|
|
@@ -4102,6 +4140,23 @@ var init_database = __esm({
|
|
|
4102
4140
|
});
|
|
4103
4141
|
|
|
4104
4142
|
// src/lib/license.ts
|
|
4143
|
+
var license_exports = {};
|
|
4144
|
+
__export(license_exports, {
|
|
4145
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
4146
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
4147
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
4148
|
+
checkLicense: () => checkLicense,
|
|
4149
|
+
getCachedLicense: () => getCachedLicense,
|
|
4150
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
4151
|
+
loadDeviceId: () => loadDeviceId,
|
|
4152
|
+
loadLicense: () => loadLicense,
|
|
4153
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
4154
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
4155
|
+
saveLicense: () => saveLicense,
|
|
4156
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
4157
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
4158
|
+
validateLicense: () => validateLicense
|
|
4159
|
+
});
|
|
4105
4160
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
4106
4161
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4107
4162
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -4109,7 +4164,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
4109
4164
|
import os8 from "os";
|
|
4110
4165
|
import path10 from "path";
|
|
4111
4166
|
import { jwtVerify, importSPKI } from "jose";
|
|
4112
|
-
|
|
4167
|
+
async function fetchRetry(url, init) {
|
|
4168
|
+
try {
|
|
4169
|
+
return await fetch(url, init);
|
|
4170
|
+
} catch {
|
|
4171
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
4172
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
function loadDeviceId() {
|
|
4176
|
+
const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
|
|
4177
|
+
try {
|
|
4178
|
+
if (existsSync10(deviceJsonPath)) {
|
|
4179
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
4180
|
+
if (data.deviceId) return data.deviceId;
|
|
4181
|
+
}
|
|
4182
|
+
} catch {
|
|
4183
|
+
}
|
|
4184
|
+
try {
|
|
4185
|
+
if (existsSync10(DEVICE_ID_PATH)) {
|
|
4186
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
4187
|
+
if (id2) return id2;
|
|
4188
|
+
}
|
|
4189
|
+
} catch {
|
|
4190
|
+
}
|
|
4191
|
+
const id = randomUUID3();
|
|
4192
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
4193
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
4194
|
+
return id;
|
|
4195
|
+
}
|
|
4196
|
+
function loadLicense() {
|
|
4197
|
+
try {
|
|
4198
|
+
if (!existsSync10(LICENSE_PATH)) return null;
|
|
4199
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
4200
|
+
} catch {
|
|
4201
|
+
return null;
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
function saveLicense(apiKey) {
|
|
4205
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
4206
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
4207
|
+
}
|
|
4208
|
+
async function verifyLicenseJwt(token) {
|
|
4209
|
+
try {
|
|
4210
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4211
|
+
const { payload } = await jwtVerify(token, key, {
|
|
4212
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
4213
|
+
});
|
|
4214
|
+
const plan = payload.plan ?? "free";
|
|
4215
|
+
const email = payload.sub ?? "";
|
|
4216
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4217
|
+
return {
|
|
4218
|
+
valid: true,
|
|
4219
|
+
plan,
|
|
4220
|
+
email,
|
|
4221
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4222
|
+
deviceLimit: limits.devices,
|
|
4223
|
+
employeeLimit: limits.employees,
|
|
4224
|
+
memoryLimit: limits.memories
|
|
4225
|
+
};
|
|
4226
|
+
} catch {
|
|
4227
|
+
return null;
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
async function getCachedLicense() {
|
|
4231
|
+
try {
|
|
4232
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4233
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4234
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
4235
|
+
return await verifyLicenseJwt(raw.token);
|
|
4236
|
+
} catch {
|
|
4237
|
+
return null;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
function readCachedLicenseToken() {
|
|
4241
|
+
try {
|
|
4242
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4243
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4244
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
4245
|
+
} catch {
|
|
4246
|
+
return null;
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
function getRawCachedPlan() {
|
|
4250
|
+
try {
|
|
4251
|
+
const token = readCachedLicenseToken();
|
|
4252
|
+
if (!token) return null;
|
|
4253
|
+
const parts = token.split(".");
|
|
4254
|
+
if (parts.length !== 3) return null;
|
|
4255
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
4256
|
+
const plan = payload.plan ?? "free";
|
|
4257
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4258
|
+
process.stderr.write(
|
|
4259
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
4260
|
+
`
|
|
4261
|
+
);
|
|
4262
|
+
return {
|
|
4263
|
+
valid: true,
|
|
4264
|
+
plan,
|
|
4265
|
+
email: payload.sub ?? "",
|
|
4266
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4267
|
+
deviceLimit: limits.devices,
|
|
4268
|
+
employeeLimit: limits.employees,
|
|
4269
|
+
memoryLimit: limits.memories
|
|
4270
|
+
};
|
|
4271
|
+
} catch {
|
|
4272
|
+
return null;
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
function cacheResponse(token) {
|
|
4276
|
+
try {
|
|
4277
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
4278
|
+
} catch {
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
function loadPrismaForLicense() {
|
|
4282
|
+
if (_prismaFailed) return null;
|
|
4283
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
4284
|
+
if (!dbUrl) {
|
|
4285
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(os8.homedir(), "exe-db");
|
|
4286
|
+
if (!existsSync10(path10.join(exeDbRoot, "package.json"))) {
|
|
4287
|
+
_prismaFailed = true;
|
|
4288
|
+
return null;
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
if (!_prismaPromise) {
|
|
4292
|
+
_prismaPromise = (async () => {
|
|
4293
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4294
|
+
if (explicitPath) {
|
|
4295
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
4296
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
4297
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4298
|
+
return new Ctor2();
|
|
4299
|
+
}
|
|
4300
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path10.join(os8.homedir(), "exe-db");
|
|
4301
|
+
const req = createRequire2(path10.join(exeDbRoot, "package.json"));
|
|
4302
|
+
const entry = req.resolve("@prisma/client");
|
|
4303
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
4304
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4305
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
4306
|
+
return new Ctor();
|
|
4307
|
+
})().catch((err) => {
|
|
4308
|
+
_prismaFailed = true;
|
|
4309
|
+
_prismaPromise = null;
|
|
4310
|
+
throw err;
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
return _prismaPromise;
|
|
4314
|
+
}
|
|
4315
|
+
async function validateViaPostgres(apiKey) {
|
|
4316
|
+
const loader = loadPrismaForLicense();
|
|
4317
|
+
if (!loader) return null;
|
|
4318
|
+
try {
|
|
4319
|
+
const prisma = await loader;
|
|
4320
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
4321
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
4322
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
4323
|
+
apiKey
|
|
4324
|
+
);
|
|
4325
|
+
if (!rows || rows.length === 0) return null;
|
|
4326
|
+
const row = rows[0];
|
|
4327
|
+
if (row.status !== "active") return null;
|
|
4328
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
4329
|
+
const plan = row.plan;
|
|
4330
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4331
|
+
return {
|
|
4332
|
+
valid: true,
|
|
4333
|
+
plan,
|
|
4334
|
+
email: row.email,
|
|
4335
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
4336
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
4337
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
4338
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
4339
|
+
};
|
|
4340
|
+
} catch {
|
|
4341
|
+
return null;
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
4345
|
+
try {
|
|
4346
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4347
|
+
method: "POST",
|
|
4348
|
+
headers: { "Content-Type": "application/json" },
|
|
4349
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4350
|
+
signal: AbortSignal.timeout(1e4)
|
|
4351
|
+
});
|
|
4352
|
+
if (!res.ok) return null;
|
|
4353
|
+
const data = await res.json();
|
|
4354
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
4355
|
+
if (!data.valid) return null;
|
|
4356
|
+
if (data.token) {
|
|
4357
|
+
cacheResponse(data.token);
|
|
4358
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4359
|
+
if (verified) return verified;
|
|
4360
|
+
}
|
|
4361
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4362
|
+
return {
|
|
4363
|
+
valid: data.valid,
|
|
4364
|
+
plan: data.plan,
|
|
4365
|
+
email: data.email,
|
|
4366
|
+
expiresAt: data.expiresAt,
|
|
4367
|
+
deviceLimit: limits.devices,
|
|
4368
|
+
employeeLimit: limits.employees,
|
|
4369
|
+
memoryLimit: limits.memories
|
|
4370
|
+
};
|
|
4371
|
+
} catch {
|
|
4372
|
+
return null;
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4376
|
+
const did = deviceId ?? loadDeviceId();
|
|
4377
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
4378
|
+
if (pgResult) {
|
|
4379
|
+
try {
|
|
4380
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
4381
|
+
} catch {
|
|
4382
|
+
}
|
|
4383
|
+
return pgResult;
|
|
4384
|
+
}
|
|
4385
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
4386
|
+
if (cfResult) return cfResult;
|
|
4387
|
+
const cached = await getCachedLicense();
|
|
4388
|
+
if (cached) return cached;
|
|
4389
|
+
try {
|
|
4390
|
+
if (existsSync10(CACHE_PATH)) {
|
|
4391
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4392
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
4393
|
+
return raw.pgLicense;
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
} catch {
|
|
4397
|
+
}
|
|
4398
|
+
const rawFallback = getRawCachedPlan();
|
|
4399
|
+
if (rawFallback) return rawFallback;
|
|
4400
|
+
return { ...FREE_LICENSE, valid: false };
|
|
4401
|
+
}
|
|
4402
|
+
function getCacheAgeMs() {
|
|
4403
|
+
try {
|
|
4404
|
+
const { statSync: statSync5 } = __require("fs");
|
|
4405
|
+
const s = statSync5(CACHE_PATH);
|
|
4406
|
+
return Date.now() - s.mtimeMs;
|
|
4407
|
+
} catch {
|
|
4408
|
+
return Infinity;
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
async function checkLicense() {
|
|
4412
|
+
let key = loadLicense();
|
|
4413
|
+
if (!key) {
|
|
4414
|
+
try {
|
|
4415
|
+
const configPath = path10.join(EXE_AI_DIR, "config.json");
|
|
4416
|
+
if (existsSync10(configPath)) {
|
|
4417
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
4418
|
+
const cloud = raw.cloud;
|
|
4419
|
+
if (cloud?.apiKey) {
|
|
4420
|
+
key = cloud.apiKey;
|
|
4421
|
+
saveLicense(key);
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
} catch {
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
if (!key) return FREE_LICENSE;
|
|
4428
|
+
const cached = await getCachedLicense();
|
|
4429
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
4430
|
+
const deviceId = loadDeviceId();
|
|
4431
|
+
return validateLicense(key, deviceId);
|
|
4432
|
+
}
|
|
4433
|
+
function isFeatureAllowed(license, feature) {
|
|
4434
|
+
switch (feature) {
|
|
4435
|
+
case "cloud_sync":
|
|
4436
|
+
case "external_agents":
|
|
4437
|
+
case "wiki":
|
|
4438
|
+
return license.plan !== "free";
|
|
4439
|
+
case "unlimited_employees":
|
|
4440
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
function mirrorLicenseKey(apiKey) {
|
|
4444
|
+
const trimmed = apiKey.trim();
|
|
4445
|
+
if (!trimmed) return;
|
|
4446
|
+
saveLicense(trimmed);
|
|
4447
|
+
}
|
|
4448
|
+
async function assertVpsLicense(opts) {
|
|
4449
|
+
const env = opts?.env ?? process.env;
|
|
4450
|
+
const inProduction = env.NODE_ENV === "production";
|
|
4451
|
+
if (!opts?.force && !inProduction) {
|
|
4452
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
4453
|
+
}
|
|
4454
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
4455
|
+
if (envKey) {
|
|
4456
|
+
saveLicense(envKey);
|
|
4457
|
+
}
|
|
4458
|
+
const apiKey = envKey ?? loadLicense();
|
|
4459
|
+
if (!apiKey) {
|
|
4460
|
+
throw new Error(
|
|
4461
|
+
"License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
|
|
4462
|
+
);
|
|
4463
|
+
}
|
|
4464
|
+
const deviceId = loadDeviceId();
|
|
4465
|
+
let backendResponse = null;
|
|
4466
|
+
let explicitRejection = false;
|
|
4467
|
+
let transientFailure = false;
|
|
4468
|
+
try {
|
|
4469
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4470
|
+
method: "POST",
|
|
4471
|
+
headers: { "Content-Type": "application/json" },
|
|
4472
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4473
|
+
signal: AbortSignal.timeout(1e4)
|
|
4474
|
+
});
|
|
4475
|
+
if (res.ok) {
|
|
4476
|
+
backendResponse = await res.json();
|
|
4477
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
4478
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
4479
|
+
explicitRejection = true;
|
|
4480
|
+
} else {
|
|
4481
|
+
transientFailure = true;
|
|
4482
|
+
}
|
|
4483
|
+
} catch {
|
|
4484
|
+
transientFailure = true;
|
|
4485
|
+
}
|
|
4486
|
+
if (backendResponse?.valid) {
|
|
4487
|
+
if (backendResponse.token) {
|
|
4488
|
+
cacheResponse(backendResponse.token);
|
|
4489
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
4490
|
+
if (verified) return verified;
|
|
4491
|
+
}
|
|
4492
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
4493
|
+
return {
|
|
4494
|
+
valid: true,
|
|
4495
|
+
plan: backendResponse.plan,
|
|
4496
|
+
email: backendResponse.email,
|
|
4497
|
+
expiresAt: backendResponse.expiresAt,
|
|
4498
|
+
deviceLimit: limits.devices,
|
|
4499
|
+
employeeLimit: limits.employees,
|
|
4500
|
+
memoryLimit: limits.memories
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
if (explicitRejection) {
|
|
4504
|
+
throw new Error(
|
|
4505
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
4506
|
+
);
|
|
4507
|
+
}
|
|
4508
|
+
if (!transientFailure) {
|
|
4509
|
+
throw new Error(
|
|
4510
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
const fresh = await getCachedLicense();
|
|
4514
|
+
if (fresh && fresh.valid) return fresh;
|
|
4515
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
4516
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
4517
|
+
try {
|
|
4518
|
+
const token = readCachedLicenseToken();
|
|
4519
|
+
if (token) {
|
|
4520
|
+
const payloadB64 = token.split(".")[1];
|
|
4521
|
+
if (payloadB64) {
|
|
4522
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
4523
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
4524
|
+
if (Date.now() < expMs + graceMs) {
|
|
4525
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4526
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
4527
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
4528
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
4529
|
+
});
|
|
4530
|
+
const plan = verified.plan ?? "free";
|
|
4531
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4532
|
+
return {
|
|
4533
|
+
valid: true,
|
|
4534
|
+
plan,
|
|
4535
|
+
email: verified.sub ?? "",
|
|
4536
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
4537
|
+
deviceLimit: limits.devices,
|
|
4538
|
+
employeeLimit: limits.employees,
|
|
4539
|
+
memoryLimit: limits.memories
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
} catch {
|
|
4545
|
+
}
|
|
4546
|
+
throw new Error(
|
|
4547
|
+
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
|
|
4548
|
+
);
|
|
4549
|
+
}
|
|
4550
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
4551
|
+
if (_revalTimer) return;
|
|
4552
|
+
_revalTimer = setInterval(async () => {
|
|
4553
|
+
try {
|
|
4554
|
+
const license = await checkLicense();
|
|
4555
|
+
if (!license.valid) {
|
|
4556
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
4557
|
+
}
|
|
4558
|
+
} catch {
|
|
4559
|
+
}
|
|
4560
|
+
}, intervalMs);
|
|
4561
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
4562
|
+
_revalTimer.unref();
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
function stopLicenseRevalidation() {
|
|
4566
|
+
if (_revalTimer) {
|
|
4567
|
+
clearInterval(_revalTimer);
|
|
4568
|
+
_revalTimer = null;
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
|
|
4113
4572
|
var init_license = __esm({
|
|
4114
4573
|
"src/lib/license.ts"() {
|
|
4115
4574
|
"use strict";
|
|
@@ -4117,7 +4576,13 @@ var init_license = __esm({
|
|
|
4117
4576
|
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
4118
4577
|
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
4119
4578
|
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
4120
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
4579
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
4580
|
+
RETRY_DELAY_MS = 500;
|
|
4581
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
4582
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
4583
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
4584
|
+
-----END PUBLIC KEY-----`;
|
|
4585
|
+
LICENSE_JWT_ALG = "ES256";
|
|
4121
4586
|
PLAN_LIMITS = {
|
|
4122
4587
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
4123
4588
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -4125,6 +4590,19 @@ var init_license = __esm({
|
|
|
4125
4590
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
4126
4591
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
4127
4592
|
};
|
|
4593
|
+
FREE_LICENSE = {
|
|
4594
|
+
valid: true,
|
|
4595
|
+
plan: "free",
|
|
4596
|
+
email: "",
|
|
4597
|
+
expiresAt: null,
|
|
4598
|
+
deviceLimit: 1,
|
|
4599
|
+
employeeLimit: 1,
|
|
4600
|
+
memoryLimit: 5e3
|
|
4601
|
+
};
|
|
4602
|
+
_prismaPromise = null;
|
|
4603
|
+
_prismaFailed = false;
|
|
4604
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
4605
|
+
_revalTimer = null;
|
|
4128
4606
|
}
|
|
4129
4607
|
});
|
|
4130
4608
|
|
|
@@ -4642,6 +5120,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4642
5120
|
args: [identifier, ...scope.args]
|
|
4643
5121
|
});
|
|
4644
5122
|
if (result.rows.length === 1) return result.rows[0];
|
|
5123
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
5124
|
+
result = await client.execute({
|
|
5125
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
5126
|
+
args: [`${identifier}%`]
|
|
5127
|
+
});
|
|
5128
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5129
|
+
if (result.rows.length > 1) {
|
|
5130
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
5131
|
+
throw new Error(
|
|
5132
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
5133
|
+
);
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
4645
5136
|
result = await client.execute({
|
|
4646
5137
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
4647
5138
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -5496,12 +5987,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5496
5987
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
5497
5988
|
args: [now, taskId]
|
|
5498
5989
|
});
|
|
5499
|
-
if (
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5990
|
+
if (unblocked.rowsAffected === 0) return;
|
|
5991
|
+
const ubScope = sessionScopeFilter();
|
|
5992
|
+
const unblockedRows = await client.execute({
|
|
5993
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
5994
|
+
args: [now, ...ubScope.args]
|
|
5995
|
+
});
|
|
5996
|
+
if (baseDir) {
|
|
5505
5997
|
for (const ur of unblockedRows.rows) {
|
|
5506
5998
|
try {
|
|
5507
5999
|
const ubFile = path17.join(baseDir, String(ur.task_file));
|
|
@@ -5513,6 +6005,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5513
6005
|
}
|
|
5514
6006
|
}
|
|
5515
6007
|
}
|
|
6008
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
6009
|
+
try {
|
|
6010
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
6011
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
6012
|
+
for (const ur of unblockedRows.rows) {
|
|
6013
|
+
const assignee = String(ur.assigned_to);
|
|
6014
|
+
if (dispatched.has(assignee)) continue;
|
|
6015
|
+
dispatched.add(assignee);
|
|
6016
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
6017
|
+
}
|
|
6018
|
+
} catch {
|
|
6019
|
+
}
|
|
6020
|
+
}
|
|
5516
6021
|
}
|
|
5517
6022
|
async function findNextTask(assignedTo) {
|
|
5518
6023
|
const client = getClient();
|
|
@@ -5729,6 +6234,15 @@ __export(behaviors_exports, {
|
|
|
5729
6234
|
});
|
|
5730
6235
|
import crypto5 from "crypto";
|
|
5731
6236
|
async function storeBehavior(opts) {
|
|
6237
|
+
try {
|
|
6238
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
6239
|
+
const roster = loadEmployeesSync2();
|
|
6240
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
6241
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
6242
|
+
}
|
|
6243
|
+
} catch (e) {
|
|
6244
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
6245
|
+
}
|
|
5732
6246
|
const client = getClient();
|
|
5733
6247
|
const id = crypto5.randomUUID();
|
|
5734
6248
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5739,17 +6253,25 @@ async function storeBehavior(opts) {
|
|
|
5739
6253
|
vector = new Float32Array(vec);
|
|
5740
6254
|
} catch {
|
|
5741
6255
|
}
|
|
6256
|
+
let createdByDevice = null;
|
|
6257
|
+
try {
|
|
6258
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
6259
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
6260
|
+
} catch {
|
|
6261
|
+
}
|
|
6262
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
6263
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
5742
6264
|
await client.execute({
|
|
5743
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
5744
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
5745
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
6265
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id)
|
|
6266
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
6267
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
5746
6268
|
});
|
|
5747
6269
|
return id;
|
|
5748
6270
|
}
|
|
5749
6271
|
async function listBehaviors(agentId, projectName, limit = 30) {
|
|
5750
6272
|
const client = getClient();
|
|
5751
6273
|
const result = await client.execute({
|
|
5752
|
-
sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector
|
|
6274
|
+
sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id
|
|
5753
6275
|
FROM behaviors
|
|
5754
6276
|
WHERE agent_id = ? AND active = 1
|
|
5755
6277
|
AND (project_name IS NULL OR project_name = ?)
|
|
@@ -5778,7 +6300,10 @@ async function listBehaviors(agentId, projectName, limit = 30) {
|
|
|
5778
6300
|
active: Number(r.active),
|
|
5779
6301
|
created_at: String(r.created_at),
|
|
5780
6302
|
updated_at: String(r.updated_at),
|
|
5781
|
-
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
|
|
6303
|
+
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
|
|
6304
|
+
created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
|
|
6305
|
+
created_by_device: r.created_by_device ? String(r.created_by_device) : null,
|
|
6306
|
+
source_session_id: r.source_session_id ? String(r.source_session_id) : null
|
|
5782
6307
|
}));
|
|
5783
6308
|
}
|
|
5784
6309
|
async function listBehaviorsByDomain(agentId, domain) {
|
|
@@ -5799,7 +6324,10 @@ async function listBehaviorsByDomain(agentId, domain) {
|
|
|
5799
6324
|
active: Number(r.active),
|
|
5800
6325
|
created_at: String(r.created_at),
|
|
5801
6326
|
updated_at: String(r.updated_at),
|
|
5802
|
-
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
|
|
6327
|
+
vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
|
|
6328
|
+
created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
|
|
6329
|
+
created_by_device: r.created_by_device ? String(r.created_by_device) : null,
|
|
6330
|
+
source_session_id: r.source_session_id ? String(r.source_session_id) : null
|
|
5803
6331
|
}));
|
|
5804
6332
|
}
|
|
5805
6333
|
async function deactivateBehavior(id) {
|
|
@@ -6238,6 +6766,12 @@ async function updateTask(input) {
|
|
|
6238
6766
|
}
|
|
6239
6767
|
}
|
|
6240
6768
|
}
|
|
6769
|
+
if (input.status === "cancelled") {
|
|
6770
|
+
try {
|
|
6771
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
6772
|
+
} catch {
|
|
6773
|
+
}
|
|
6774
|
+
}
|
|
6241
6775
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6242
6776
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
6243
6777
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -6769,11 +7303,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
6769
7303
|
}
|
|
6770
7304
|
}
|
|
6771
7305
|
function resolveExeSession() {
|
|
7306
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
7307
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7308
|
+
if (fromEnv) return fromEnv;
|
|
7309
|
+
}
|
|
6772
7310
|
const mySession = getMySession();
|
|
6773
7311
|
if (!mySession) {
|
|
6774
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
6775
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
6776
|
-
}
|
|
6777
7312
|
return null;
|
|
6778
7313
|
}
|
|
6779
7314
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -6788,6 +7323,10 @@ function resolveExeSession() {
|
|
|
6788
7323
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
6789
7324
|
`
|
|
6790
7325
|
);
|
|
7326
|
+
try {
|
|
7327
|
+
registerParentExe(key, fromSessionName);
|
|
7328
|
+
} catch {
|
|
7329
|
+
}
|
|
6791
7330
|
candidate = fromSessionName;
|
|
6792
7331
|
} else {
|
|
6793
7332
|
candidate = fromCache;
|
|
@@ -8482,11 +9021,17 @@ var init_platform_procedures = __esm({
|
|
|
8482
9021
|
content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
8483
9022
|
},
|
|
8484
9023
|
{
|
|
8485
|
-
title: "
|
|
9024
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
8486
9025
|
domain: "workflow",
|
|
8487
9026
|
priority: "p1",
|
|
8488
9027
|
content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
|
|
8489
9028
|
},
|
|
9029
|
+
{
|
|
9030
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
9031
|
+
domain: "identity",
|
|
9032
|
+
priority: "p0",
|
|
9033
|
+
content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
|
|
9034
|
+
},
|
|
8490
9035
|
{
|
|
8491
9036
|
title: "Single dispatch path \u2014 create_task only",
|
|
8492
9037
|
domain: "workflow",
|
|
@@ -8520,6 +9065,12 @@ var init_platform_procedures = __esm({
|
|
|
8520
9065
|
priority: "p0",
|
|
8521
9066
|
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
8522
9067
|
},
|
|
9068
|
+
{
|
|
9069
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
9070
|
+
domain: "security",
|
|
9071
|
+
priority: "p0",
|
|
9072
|
+
content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
|
|
9073
|
+
},
|
|
8523
9074
|
{
|
|
8524
9075
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
8525
9076
|
domain: "support",
|
|
@@ -8671,7 +9222,7 @@ var init_platform_procedures = __esm({
|
|
|
8671
9222
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
8672
9223
|
domain: "tool-use",
|
|
8673
9224
|
priority: "p0",
|
|
8674
|
-
content: 'exe-os MCP tools
|
|
9225
|
+
content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
|
|
8675
9226
|
},
|
|
8676
9227
|
{
|
|
8677
9228
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -8805,10 +9356,24 @@ function stableId(memoryId, type, content) {
|
|
|
8805
9356
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
8806
9357
|
}
|
|
8807
9358
|
function cleanText(text) {
|
|
8808
|
-
|
|
9359
|
+
let cleaned = text.replace(
|
|
9360
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
9361
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
9362
|
+
);
|
|
9363
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9364
|
+
return cleaned;
|
|
8809
9365
|
}
|
|
8810
|
-
function
|
|
8811
|
-
|
|
9366
|
+
function splitSegments(text) {
|
|
9367
|
+
const cleaned = cleanText(text);
|
|
9368
|
+
const segments = cleaned.split(/(?<=[.!?:;])\s+|\n{2,}|(?<=\))\s+(?=[A-Z])|\s*[|│]\s*/).map((s) => s.trim()).filter((s) => s.length >= MIN_SEGMENT_CHARS && s.length <= MAX_SEGMENT_CHARS);
|
|
9369
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9370
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
9371
|
+
if (lines.length > 0) return lines;
|
|
9372
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9373
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
9374
|
+
}
|
|
9375
|
+
}
|
|
9376
|
+
return segments;
|
|
8812
9377
|
}
|
|
8813
9378
|
function inferCardType(sentence, toolName) {
|
|
8814
9379
|
const lower = sentence.toLowerCase();
|
|
@@ -8840,12 +9405,12 @@ function predicateFor(type) {
|
|
|
8840
9405
|
}
|
|
8841
9406
|
}
|
|
8842
9407
|
function extractMemoryCards(row) {
|
|
8843
|
-
const
|
|
9408
|
+
const segments = splitSegments(row.raw_text);
|
|
8844
9409
|
const cards = [];
|
|
8845
|
-
for (const sentence of
|
|
9410
|
+
for (const sentence of segments) {
|
|
8846
9411
|
const type = inferCardType(sentence, row.tool_name);
|
|
8847
9412
|
const subject = extractSubject(sentence, row.agent_id);
|
|
8848
|
-
const content = sentence.length >
|
|
9413
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
8849
9414
|
cards.push({
|
|
8850
9415
|
id: stableId(row.id, type, content),
|
|
8851
9416
|
memory_id: row.id,
|
|
@@ -8941,13 +9506,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
8941
9506
|
last_accessed: String(row.timestamp)
|
|
8942
9507
|
}));
|
|
8943
9508
|
}
|
|
8944
|
-
var MAX_CARDS_PER_MEMORY,
|
|
9509
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
8945
9510
|
var init_memory_cards = __esm({
|
|
8946
9511
|
"src/lib/memory-cards.ts"() {
|
|
8947
9512
|
"use strict";
|
|
8948
9513
|
init_database();
|
|
8949
|
-
MAX_CARDS_PER_MEMORY =
|
|
8950
|
-
|
|
9514
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
9515
|
+
MAX_SEGMENT_CHARS = 500;
|
|
9516
|
+
MIN_SEGMENT_CHARS = 20;
|
|
8951
9517
|
}
|
|
8952
9518
|
});
|
|
8953
9519
|
|