@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.
Files changed (95) hide show
  1. package/README.md +9 -7
  2. package/dist/bin/agentic-ontology-backfill.js +62 -12
  3. package/dist/bin/agentic-reflection-backfill.js +37 -2
  4. package/dist/bin/agentic-semantic-label.js +37 -2
  5. package/dist/bin/backfill-conversations.js +61 -11
  6. package/dist/bin/backfill-responses.js +62 -12
  7. package/dist/bin/backfill-vectors.js +37 -2
  8. package/dist/bin/bulk-sync-postgres.js +63 -13
  9. package/dist/bin/cleanup-stale-review-tasks.js +83 -16
  10. package/dist/bin/cli.js +312 -80
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +29 -3
  13. package/dist/bin/exe-assign.js +62 -12
  14. package/dist/bin/exe-boot.js +500 -151
  15. package/dist/bin/exe-call.js +46 -5
  16. package/dist/bin/exe-cloud.js +101 -16
  17. package/dist/bin/exe-dispatch.js +827 -27
  18. package/dist/bin/exe-doctor.js +61 -11
  19. package/dist/bin/exe-export-behaviors.js +67 -14
  20. package/dist/bin/exe-forget.js +62 -12
  21. package/dist/bin/exe-gateway.js +147 -27
  22. package/dist/bin/exe-heartbeat.js +83 -16
  23. package/dist/bin/exe-kill.js +62 -12
  24. package/dist/bin/exe-launch-agent.js +83 -15
  25. package/dist/bin/exe-new-employee.js +176 -8
  26. package/dist/bin/exe-pending-messages.js +83 -16
  27. package/dist/bin/exe-pending-notifications.js +83 -16
  28. package/dist/bin/exe-pending-reviews.js +83 -16
  29. package/dist/bin/exe-rename.js +62 -12
  30. package/dist/bin/exe-review.js +62 -12
  31. package/dist/bin/exe-search.js +62 -12
  32. package/dist/bin/exe-session-cleanup.js +949 -149
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +537 -248
  35. package/dist/bin/exe-start-opencode.js +547 -168
  36. package/dist/bin/exe-status.js +83 -16
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +62 -12
  39. package/dist/bin/git-sweep.js +827 -27
  40. package/dist/bin/graph-backfill.js +62 -12
  41. package/dist/bin/graph-export.js +62 -12
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +949 -149
  44. package/dist/bin/pre-publish.js +14 -2
  45. package/dist/bin/scan-tasks.js +827 -27
  46. package/dist/bin/setup.js +99 -14
  47. package/dist/bin/shard-migrate.js +62 -12
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +586 -26
  51. package/dist/hooks/bug-report-worker.js +586 -26
  52. package/dist/hooks/codex-stop-task-finalizer.js +977 -143
  53. package/dist/hooks/commit-complete.js +827 -27
  54. package/dist/hooks/error-recall.js +62 -12
  55. package/dist/hooks/ingest.js +4579 -249
  56. package/dist/hooks/instructions-loaded.js +62 -12
  57. package/dist/hooks/notification.js +62 -12
  58. package/dist/hooks/post-compact.js +83 -16
  59. package/dist/hooks/post-tool-combined.js +83 -16
  60. package/dist/hooks/pre-compact.js +907 -107
  61. package/dist/hooks/pre-tool-use.js +98 -16
  62. package/dist/hooks/prompt-submit.js +596 -30
  63. package/dist/hooks/session-end.js +909 -112
  64. package/dist/hooks/session-start.js +112 -17
  65. package/dist/hooks/stop.js +82 -15
  66. package/dist/hooks/subagent-stop.js +83 -16
  67. package/dist/hooks/summary-worker.js +81 -8
  68. package/dist/index.js +595 -29
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +45 -1
  71. package/dist/lib/consolidation.js +16 -1
  72. package/dist/lib/database.js +23 -0
  73. package/dist/lib/db.js +23 -0
  74. package/dist/lib/device-registry.js +23 -0
  75. package/dist/lib/employee-templates.js +30 -4
  76. package/dist/lib/employees.js +16 -1
  77. package/dist/lib/exe-daemon.js +482 -52
  78. package/dist/lib/hybrid-search.js +62 -12
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +37 -2
  82. package/dist/lib/skill-learning.js +910 -41
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +62 -12
  85. package/dist/lib/tasks.js +843 -93
  86. package/dist/lib/tmux-routing.js +766 -16
  87. package/dist/mcp/server.js +238 -41
  88. package/dist/mcp/tools/create-task.js +525 -15
  89. package/dist/mcp/tools/deactivate-behavior.js +33 -24
  90. package/dist/mcp/tools/list-tasks.js +21 -4
  91. package/dist/mcp/tools/send-message.js +21 -4
  92. package/dist/mcp/tools/update-task.js +840 -93
  93. package/dist/runtime/index.js +913 -107
  94. package/dist/tui/App.js +227 -58
  95. 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
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
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/cloud";
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 (baseDir && unblocked.rowsAffected > 0) {
5500
- const ubScope = sessionScopeFilter();
5501
- const unblockedRows = await client.execute({
5502
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5503
- args: [now, ...ubScope.args]
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: "Customer orchestration maturity \u2014 recommend, never trap",
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 come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
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
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
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 splitSentences(text) {
8811
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
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 sentences = splitSentences(row.raw_text);
9408
+ const segments = splitSegments(row.raw_text);
8844
9409
  const cards = [];
8845
- for (const sentence of sentences) {
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 > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
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, MAX_SENTENCE_CHARS;
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 = 6;
8950
- MAX_SENTENCE_CHARS = 360;
9514
+ MAX_CARDS_PER_MEMORY = 8;
9515
+ MAX_SEGMENT_CHARS = 500;
9516
+ MIN_SEGMENT_CHARS = 20;
8951
9517
  }
8952
9518
  });
8953
9519