@askexenow/exe-os 0.9.110 → 0.9.112

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 (79) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +8 -1
  2. package/dist/bin/agentic-reflection-backfill.js +8 -1
  3. package/dist/bin/agentic-semantic-label.js +8 -1
  4. package/dist/bin/backfill-conversations.js +8 -1
  5. package/dist/bin/backfill-responses.js +8 -1
  6. package/dist/bin/backfill-vectors.js +8 -1
  7. package/dist/bin/bulk-sync-postgres.js +8 -1
  8. package/dist/bin/cleanup-stale-review-tasks.js +8 -1
  9. package/dist/bin/cli.js +19 -4
  10. package/dist/bin/exe-agent.js +1 -1
  11. package/dist/bin/exe-assign.js +8 -1
  12. package/dist/bin/exe-boot.js +19 -4
  13. package/dist/bin/exe-call.js +1 -1
  14. package/dist/bin/exe-cloud.js +8 -1
  15. package/dist/bin/exe-dispatch.js +460 -5
  16. package/dist/bin/exe-doctor.js +8 -1
  17. package/dist/bin/exe-export-behaviors.js +13 -3
  18. package/dist/bin/exe-forget.js +8 -1
  19. package/dist/bin/exe-gateway.js +19 -4
  20. package/dist/bin/exe-heartbeat.js +8 -1
  21. package/dist/bin/exe-kill.js +8 -1
  22. package/dist/bin/exe-launch-agent.js +13 -3
  23. package/dist/bin/exe-new-employee.js +1 -1
  24. package/dist/bin/exe-pending-messages.js +8 -1
  25. package/dist/bin/exe-pending-notifications.js +8 -1
  26. package/dist/bin/exe-pending-reviews.js +8 -1
  27. package/dist/bin/exe-rename.js +8 -1
  28. package/dist/bin/exe-review.js +8 -1
  29. package/dist/bin/exe-search.js +8 -1
  30. package/dist/bin/exe-session-cleanup.js +460 -5
  31. package/dist/bin/exe-start-codex.js +13 -3
  32. package/dist/bin/exe-start-opencode.js +13 -3
  33. package/dist/bin/exe-status.js +8 -1
  34. package/dist/bin/exe-team.js +8 -1
  35. package/dist/bin/git-sweep.js +460 -5
  36. package/dist/bin/graph-backfill.js +8 -1
  37. package/dist/bin/graph-export.js +8 -1
  38. package/dist/bin/intercom-check.js +460 -5
  39. package/dist/bin/pre-publish.js +1 -1
  40. package/dist/bin/scan-tasks.js +460 -5
  41. package/dist/bin/setup.js +8 -1
  42. package/dist/bin/shard-migrate.js +8 -1
  43. package/dist/gateway/index.js +460 -5
  44. package/dist/hooks/bug-report-worker.js +460 -5
  45. package/dist/hooks/codex-stop-task-finalizer.js +467 -5
  46. package/dist/hooks/commit-complete.js +460 -5
  47. package/dist/hooks/error-recall.js +8 -1
  48. package/dist/hooks/ingest.js +8 -1
  49. package/dist/hooks/instructions-loaded.js +8 -1
  50. package/dist/hooks/notification.js +8 -1
  51. package/dist/hooks/post-compact.js +8 -1
  52. package/dist/hooks/post-tool-combined.js +8 -1
  53. package/dist/hooks/pre-compact.js +460 -5
  54. package/dist/hooks/pre-tool-use.js +8 -1
  55. package/dist/hooks/prompt-submit.js +469 -8
  56. package/dist/hooks/session-end.js +460 -5
  57. package/dist/hooks/session-start.js +8 -1
  58. package/dist/hooks/stop.js +8 -1
  59. package/dist/hooks/subagent-stop.js +8 -1
  60. package/dist/hooks/summary-worker.js +8 -1
  61. package/dist/index.js +469 -8
  62. package/dist/lib/cloud-sync.js +7 -0
  63. package/dist/lib/database.js +7 -0
  64. package/dist/lib/db.js +7 -0
  65. package/dist/lib/device-registry.js +7 -0
  66. package/dist/lib/employee-templates.js +1 -1
  67. package/dist/lib/exe-daemon.js +216 -12
  68. package/dist/lib/hybrid-search.js +8 -1
  69. package/dist/lib/schedules.js +8 -1
  70. package/dist/lib/skill-learning.js +488 -7
  71. package/dist/lib/store.js +8 -1
  72. package/dist/lib/tasks.js +452 -4
  73. package/dist/lib/tmux-routing.js +452 -4
  74. package/dist/mcp/server.js +69 -11
  75. package/dist/mcp/tools/create-task.js +452 -4
  76. package/dist/mcp/tools/update-task.js +452 -4
  77. package/dist/runtime/index.js +469 -8
  78. package/dist/tui/App.js +19 -4
  79. package/package.json +1 -1
@@ -2549,6 +2549,13 @@ async function ensureSchema() {
2549
2549
  } catch (e) {
2550
2550
  logCatchDebug("migration", e);
2551
2551
  }
2552
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2553
+ try {
2554
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2555
+ } catch (e) {
2556
+ logCatchDebug("migration", e);
2557
+ }
2558
+ }
2552
2559
  try {
2553
2560
  await client.execute({
2554
2561
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3816,6 +3823,23 @@ var init_database = __esm({
3816
3823
  });
3817
3824
 
3818
3825
  // src/lib/license.ts
3826
+ var license_exports = {};
3827
+ __export(license_exports, {
3828
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
3829
+ PLAN_LIMITS: () => PLAN_LIMITS,
3830
+ assertVpsLicense: () => assertVpsLicense,
3831
+ checkLicense: () => checkLicense,
3832
+ getCachedLicense: () => getCachedLicense,
3833
+ isFeatureAllowed: () => isFeatureAllowed,
3834
+ loadDeviceId: () => loadDeviceId,
3835
+ loadLicense: () => loadLicense,
3836
+ mirrorLicenseKey: () => mirrorLicenseKey,
3837
+ readCachedLicenseToken: () => readCachedLicenseToken,
3838
+ saveLicense: () => saveLicense,
3839
+ startLicenseRevalidation: () => startLicenseRevalidation,
3840
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
3841
+ validateLicense: () => validateLicense
3842
+ });
3819
3843
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
3820
3844
  import { randomUUID as randomUUID2 } from "crypto";
3821
3845
  import { createRequire as createRequire2 } from "module";
@@ -3823,7 +3847,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3823
3847
  import os7 from "os";
3824
3848
  import path9 from "path";
3825
3849
  import { jwtVerify, importSPKI } from "jose";
3826
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
3850
+ async function fetchRetry(url, init) {
3851
+ try {
3852
+ return await fetch(url, init);
3853
+ } catch {
3854
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
3855
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
3856
+ }
3857
+ }
3858
+ function loadDeviceId() {
3859
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
3860
+ try {
3861
+ if (existsSync10(deviceJsonPath)) {
3862
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
3863
+ if (data.deviceId) return data.deviceId;
3864
+ }
3865
+ } catch {
3866
+ }
3867
+ try {
3868
+ if (existsSync10(DEVICE_ID_PATH)) {
3869
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
3870
+ if (id2) return id2;
3871
+ }
3872
+ } catch {
3873
+ }
3874
+ const id = randomUUID2();
3875
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
3876
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
3877
+ return id;
3878
+ }
3879
+ function loadLicense() {
3880
+ try {
3881
+ if (!existsSync10(LICENSE_PATH)) return null;
3882
+ return readFileSync8(LICENSE_PATH, "utf8").trim();
3883
+ } catch {
3884
+ return null;
3885
+ }
3886
+ }
3887
+ function saveLicense(apiKey) {
3888
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
3889
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3890
+ }
3891
+ async function verifyLicenseJwt(token) {
3892
+ try {
3893
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
3894
+ const { payload } = await jwtVerify(token, key, {
3895
+ algorithms: [LICENSE_JWT_ALG]
3896
+ });
3897
+ const plan = payload.plan ?? "free";
3898
+ const email = payload.sub ?? "";
3899
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3900
+ return {
3901
+ valid: true,
3902
+ plan,
3903
+ email,
3904
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
3905
+ deviceLimit: limits.devices,
3906
+ employeeLimit: limits.employees,
3907
+ memoryLimit: limits.memories
3908
+ };
3909
+ } catch {
3910
+ return null;
3911
+ }
3912
+ }
3913
+ async function getCachedLicense() {
3914
+ try {
3915
+ if (!existsSync10(CACHE_PATH)) return null;
3916
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3917
+ if (!raw.token || typeof raw.token !== "string") return null;
3918
+ return await verifyLicenseJwt(raw.token);
3919
+ } catch {
3920
+ return null;
3921
+ }
3922
+ }
3923
+ function readCachedLicenseToken() {
3924
+ try {
3925
+ if (!existsSync10(CACHE_PATH)) return null;
3926
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3927
+ return typeof raw.token === "string" ? raw.token : null;
3928
+ } catch {
3929
+ return null;
3930
+ }
3931
+ }
3932
+ function getRawCachedPlan() {
3933
+ try {
3934
+ const token = readCachedLicenseToken();
3935
+ if (!token) return null;
3936
+ const parts = token.split(".");
3937
+ if (parts.length !== 3) return null;
3938
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3939
+ const plan = payload.plan ?? "free";
3940
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3941
+ process.stderr.write(
3942
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
3943
+ `
3944
+ );
3945
+ return {
3946
+ valid: true,
3947
+ plan,
3948
+ email: payload.sub ?? "",
3949
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
3950
+ deviceLimit: limits.devices,
3951
+ employeeLimit: limits.employees,
3952
+ memoryLimit: limits.memories
3953
+ };
3954
+ } catch {
3955
+ return null;
3956
+ }
3957
+ }
3958
+ function cacheResponse(token) {
3959
+ try {
3960
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
3961
+ } catch {
3962
+ }
3963
+ }
3964
+ function loadPrismaForLicense() {
3965
+ if (_prismaFailed) return null;
3966
+ const dbUrl = process.env.DATABASE_URL;
3967
+ if (!dbUrl) {
3968
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
3969
+ if (!existsSync10(path9.join(exeDbRoot, "package.json"))) {
3970
+ _prismaFailed = true;
3971
+ return null;
3972
+ }
3973
+ }
3974
+ if (!_prismaPromise) {
3975
+ _prismaPromise = (async () => {
3976
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
3977
+ if (explicitPath) {
3978
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
3979
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
3980
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
3981
+ return new Ctor2();
3982
+ }
3983
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
3984
+ const req = createRequire2(path9.join(exeDbRoot, "package.json"));
3985
+ const entry = req.resolve("@prisma/client");
3986
+ const mod = await import(pathToFileURL2(entry).href);
3987
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
3988
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
3989
+ return new Ctor();
3990
+ })().catch((err) => {
3991
+ _prismaFailed = true;
3992
+ _prismaPromise = null;
3993
+ throw err;
3994
+ });
3995
+ }
3996
+ return _prismaPromise;
3997
+ }
3998
+ async function validateViaPostgres(apiKey) {
3999
+ const loader = loadPrismaForLicense();
4000
+ if (!loader) return null;
4001
+ try {
4002
+ const prisma = await loader;
4003
+ const rows = await prisma.$queryRawUnsafe(
4004
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
4005
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
4006
+ apiKey
4007
+ );
4008
+ if (!rows || rows.length === 0) return null;
4009
+ const row = rows[0];
4010
+ if (row.status !== "active") return null;
4011
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
4012
+ const plan = row.plan;
4013
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4014
+ return {
4015
+ valid: true,
4016
+ plan,
4017
+ email: row.email,
4018
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
4019
+ deviceLimit: row.device_limit ?? limits.devices,
4020
+ employeeLimit: row.employee_limit ?? limits.employees,
4021
+ memoryLimit: row.memory_limit ?? limits.memories
4022
+ };
4023
+ } catch {
4024
+ return null;
4025
+ }
4026
+ }
4027
+ async function validateViaCFWorker(apiKey, deviceId) {
4028
+ try {
4029
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4030
+ method: "POST",
4031
+ headers: { "Content-Type": "application/json" },
4032
+ body: JSON.stringify({ apiKey, deviceId }),
4033
+ signal: AbortSignal.timeout(1e4)
4034
+ });
4035
+ if (!res.ok) return null;
4036
+ const data = await res.json();
4037
+ if (data.error === "device_limit_exceeded") return null;
4038
+ if (!data.valid) return null;
4039
+ if (data.token) {
4040
+ cacheResponse(data.token);
4041
+ const verified = await verifyLicenseJwt(data.token);
4042
+ if (verified) return verified;
4043
+ }
4044
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4045
+ return {
4046
+ valid: data.valid,
4047
+ plan: data.plan,
4048
+ email: data.email,
4049
+ expiresAt: data.expiresAt,
4050
+ deviceLimit: limits.devices,
4051
+ employeeLimit: limits.employees,
4052
+ memoryLimit: limits.memories
4053
+ };
4054
+ } catch {
4055
+ return null;
4056
+ }
4057
+ }
4058
+ async function validateLicense(apiKey, deviceId) {
4059
+ const did = deviceId ?? loadDeviceId();
4060
+ const pgResult = await validateViaPostgres(apiKey);
4061
+ if (pgResult) {
4062
+ try {
4063
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
4064
+ } catch {
4065
+ }
4066
+ return pgResult;
4067
+ }
4068
+ const cfResult = await validateViaCFWorker(apiKey, did);
4069
+ if (cfResult) return cfResult;
4070
+ const cached = await getCachedLicense();
4071
+ if (cached) return cached;
4072
+ try {
4073
+ if (existsSync10(CACHE_PATH)) {
4074
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4075
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
4076
+ return raw.pgLicense;
4077
+ }
4078
+ }
4079
+ } catch {
4080
+ }
4081
+ const rawFallback = getRawCachedPlan();
4082
+ if (rawFallback) return rawFallback;
4083
+ return { ...FREE_LICENSE, valid: false };
4084
+ }
4085
+ function getCacheAgeMs() {
4086
+ try {
4087
+ const { statSync: statSync5 } = __require("fs");
4088
+ const s = statSync5(CACHE_PATH);
4089
+ return Date.now() - s.mtimeMs;
4090
+ } catch {
4091
+ return Infinity;
4092
+ }
4093
+ }
4094
+ async function checkLicense() {
4095
+ let key = loadLicense();
4096
+ if (!key) {
4097
+ try {
4098
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
4099
+ if (existsSync10(configPath)) {
4100
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
4101
+ const cloud = raw.cloud;
4102
+ if (cloud?.apiKey) {
4103
+ key = cloud.apiKey;
4104
+ saveLicense(key);
4105
+ }
4106
+ }
4107
+ } catch {
4108
+ }
4109
+ }
4110
+ if (!key) return FREE_LICENSE;
4111
+ const cached = await getCachedLicense();
4112
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
4113
+ const deviceId = loadDeviceId();
4114
+ return validateLicense(key, deviceId);
4115
+ }
4116
+ function isFeatureAllowed(license, feature) {
4117
+ switch (feature) {
4118
+ case "cloud_sync":
4119
+ case "external_agents":
4120
+ case "wiki":
4121
+ return license.plan !== "free";
4122
+ case "unlimited_employees":
4123
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
4124
+ }
4125
+ }
4126
+ function mirrorLicenseKey(apiKey) {
4127
+ const trimmed = apiKey.trim();
4128
+ if (!trimmed) return;
4129
+ saveLicense(trimmed);
4130
+ }
4131
+ async function assertVpsLicense(opts) {
4132
+ const env = opts?.env ?? process.env;
4133
+ const inProduction = env.NODE_ENV === "production";
4134
+ if (!opts?.force && !inProduction) {
4135
+ return { ...FREE_LICENSE, plan: "free" };
4136
+ }
4137
+ const envKey = env.EXE_LICENSE_KEY?.trim();
4138
+ if (envKey) {
4139
+ saveLicense(envKey);
4140
+ }
4141
+ const apiKey = envKey ?? loadLicense();
4142
+ if (!apiKey) {
4143
+ throw new Error(
4144
+ "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."
4145
+ );
4146
+ }
4147
+ const deviceId = loadDeviceId();
4148
+ let backendResponse = null;
4149
+ let explicitRejection = false;
4150
+ let transientFailure = false;
4151
+ try {
4152
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4153
+ method: "POST",
4154
+ headers: { "Content-Type": "application/json" },
4155
+ body: JSON.stringify({ apiKey, deviceId }),
4156
+ signal: AbortSignal.timeout(1e4)
4157
+ });
4158
+ if (res.ok) {
4159
+ backendResponse = await res.json();
4160
+ if (!backendResponse.valid) explicitRejection = true;
4161
+ } else if (res.status === 401 || res.status === 403) {
4162
+ explicitRejection = true;
4163
+ } else {
4164
+ transientFailure = true;
4165
+ }
4166
+ } catch {
4167
+ transientFailure = true;
4168
+ }
4169
+ if (backendResponse?.valid) {
4170
+ if (backendResponse.token) {
4171
+ cacheResponse(backendResponse.token);
4172
+ const verified = await verifyLicenseJwt(backendResponse.token);
4173
+ if (verified) return verified;
4174
+ }
4175
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
4176
+ return {
4177
+ valid: true,
4178
+ plan: backendResponse.plan,
4179
+ email: backendResponse.email,
4180
+ expiresAt: backendResponse.expiresAt,
4181
+ deviceLimit: limits.devices,
4182
+ employeeLimit: limits.employees,
4183
+ memoryLimit: limits.memories
4184
+ };
4185
+ }
4186
+ if (explicitRejection) {
4187
+ throw new Error(
4188
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
4189
+ );
4190
+ }
4191
+ if (!transientFailure) {
4192
+ throw new Error(
4193
+ "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
4194
+ );
4195
+ }
4196
+ const fresh = await getCachedLicense();
4197
+ if (fresh && fresh.valid) return fresh;
4198
+ const graceDays = opts?.offlineGraceDays ?? 7;
4199
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
4200
+ try {
4201
+ const token = readCachedLicenseToken();
4202
+ if (token) {
4203
+ const payloadB64 = token.split(".")[1];
4204
+ if (payloadB64) {
4205
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
4206
+ const expMs = (payload.exp ?? 0) * 1e3;
4207
+ if (Date.now() < expMs + graceMs) {
4208
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4209
+ const { payload: verified } = await jwtVerify(token, key, {
4210
+ algorithms: [LICENSE_JWT_ALG],
4211
+ clockTolerance: graceDays * 24 * 60 * 60
4212
+ });
4213
+ const plan = verified.plan ?? "free";
4214
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4215
+ return {
4216
+ valid: true,
4217
+ plan,
4218
+ email: verified.sub ?? "",
4219
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
4220
+ deviceLimit: limits.devices,
4221
+ employeeLimit: limits.employees,
4222
+ memoryLimit: limits.memories
4223
+ };
4224
+ }
4225
+ }
4226
+ }
4227
+ } catch {
4228
+ }
4229
+ throw new Error(
4230
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
4231
+ );
4232
+ }
4233
+ function startLicenseRevalidation(intervalMs = 36e5) {
4234
+ if (_revalTimer) return;
4235
+ _revalTimer = setInterval(async () => {
4236
+ try {
4237
+ const license = await checkLicense();
4238
+ if (!license.valid) {
4239
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
4240
+ }
4241
+ } catch {
4242
+ }
4243
+ }, intervalMs);
4244
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
4245
+ _revalTimer.unref();
4246
+ }
4247
+ }
4248
+ function stopLicenseRevalidation() {
4249
+ if (_revalTimer) {
4250
+ clearInterval(_revalTimer);
4251
+ _revalTimer = null;
4252
+ }
4253
+ }
4254
+ 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;
3827
4255
  var init_license = __esm({
3828
4256
  "src/lib/license.ts"() {
3829
4257
  "use strict";
@@ -3832,6 +4260,12 @@ var init_license = __esm({
3832
4260
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3833
4261
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3834
4262
  API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4263
+ RETRY_DELAY_MS = 500;
4264
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4265
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4266
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
4267
+ -----END PUBLIC KEY-----`;
4268
+ LICENSE_JWT_ALG = "ES256";
3835
4269
  PLAN_LIMITS = {
3836
4270
  free: { devices: 1, employees: 1, memories: 5e3 },
3837
4271
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -3839,6 +4273,19 @@ var init_license = __esm({
3839
4273
  agency: { devices: 50, employees: 100, memories: 1e7 },
3840
4274
  enterprise: { devices: -1, employees: -1, memories: -1 }
3841
4275
  };
4276
+ FREE_LICENSE = {
4277
+ valid: true,
4278
+ plan: "free",
4279
+ email: "",
4280
+ expiresAt: null,
4281
+ deviceLimit: 1,
4282
+ employeeLimit: 1,
4283
+ memoryLimit: 5e3
4284
+ };
4285
+ _prismaPromise = null;
4286
+ _prismaFailed = false;
4287
+ CACHE_MAX_AGE_MS = 36e5;
4288
+ _revalTimer = null;
3842
4289
  }
3843
4290
  });
3844
4291
 
@@ -5423,10 +5870,18 @@ async function storeBehavior(opts) {
5423
5870
  vector = new Float32Array(vec);
5424
5871
  } catch {
5425
5872
  }
5873
+ let createdByDevice = null;
5874
+ try {
5875
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
5876
+ createdByDevice = loadDeviceId2() ?? null;
5877
+ } catch {
5878
+ }
5879
+ const createdByAgent = process.env.AGENT_ID ?? null;
5880
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
5426
5881
  await client.execute({
5427
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
5428
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
5429
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
5882
+ 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)
5883
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
5884
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
5430
5885
  });
5431
5886
  return id;
5432
5887
  }
@@ -8324,7 +8779,7 @@ var init_platform_procedures = __esm({
8324
8779
  title: "MCP tool dispatch \u2014 all tools use action parameter",
8325
8780
  domain: "tool-use",
8326
8781
  priority: "p0",
8327
- 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.'
8782
+ 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.'
8328
8783
  },
8329
8784
  {
8330
8785
  title: "MCP tools \u2014 memory, decision, and search",
@@ -1811,6 +1811,13 @@ async function ensureSchema() {
1811
1811
  } catch (e) {
1812
1812
  logCatchDebug("migration", e);
1813
1813
  }
1814
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
1815
+ try {
1816
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
1817
+ } catch (e) {
1818
+ logCatchDebug("migration", e);
1819
+ }
1820
+ }
1814
1821
  try {
1815
1822
  await client.execute({
1816
1823
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3782,7 +3789,7 @@ var init_platform_procedures = __esm({
3782
3789
  title: "MCP tool dispatch \u2014 all tools use action parameter",
3783
3790
  domain: "tool-use",
3784
3791
  priority: "p0",
3785
- 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.'
3792
+ 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.'
3786
3793
  },
3787
3794
  {
3788
3795
  title: "MCP tools \u2014 memory, decision, and search",
@@ -2026,6 +2026,13 @@ async function ensureSchema() {
2026
2026
  } catch (e) {
2027
2027
  logCatchDebug("migration", e);
2028
2028
  }
2029
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2030
+ try {
2031
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2032
+ } catch (e) {
2033
+ logCatchDebug("migration", e);
2034
+ }
2035
+ }
2029
2036
  try {
2030
2037
  await client.execute({
2031
2038
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -4554,7 +4561,7 @@ var init_platform_procedures = __esm({
4554
4561
  title: "MCP tool dispatch \u2014 all tools use action parameter",
4555
4562
  domain: "tool-use",
4556
4563
  priority: "p0",
4557
- 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.'
4564
+ 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.'
4558
4565
  },
4559
4566
  {
4560
4567
  title: "MCP tools \u2014 memory, decision, and search",