@askexenow/exe-os 0.9.112 → 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 +54 -11
- package/dist/bin/agentic-reflection-backfill.js +29 -1
- package/dist/bin/agentic-semantic-label.js +29 -1
- package/dist/bin/backfill-conversations.js +53 -10
- package/dist/bin/backfill-responses.js +54 -11
- package/dist/bin/backfill-vectors.js +29 -1
- package/dist/bin/bulk-sync-postgres.js +55 -12
- package/dist/bin/cleanup-stale-review-tasks.js +75 -15
- package/dist/bin/cli.js +293 -76
- package/dist/bin/exe-agent-config.js +7 -1
- package/dist/bin/exe-agent.js +28 -2
- package/dist/bin/exe-assign.js +54 -11
- package/dist/bin/exe-boot.js +481 -147
- package/dist/bin/exe-call.js +45 -4
- package/dist/bin/exe-cloud.js +93 -15
- package/dist/bin/exe-dispatch.js +369 -24
- package/dist/bin/exe-doctor.js +53 -10
- package/dist/bin/exe-export-behaviors.js +54 -11
- package/dist/bin/exe-forget.js +54 -11
- package/dist/bin/exe-gateway.js +128 -23
- package/dist/bin/exe-heartbeat.js +75 -15
- package/dist/bin/exe-kill.js +54 -11
- package/dist/bin/exe-launch-agent.js +70 -12
- package/dist/bin/exe-new-employee.js +175 -7
- package/dist/bin/exe-pending-messages.js +75 -15
- package/dist/bin/exe-pending-notifications.js +75 -15
- package/dist/bin/exe-pending-reviews.js +75 -15
- package/dist/bin/exe-rename.js +54 -11
- package/dist/bin/exe-review.js +54 -11
- package/dist/bin/exe-search.js +54 -11
- package/dist/bin/exe-session-cleanup.js +491 -146
- package/dist/bin/exe-settings.js +10 -4
- package/dist/bin/exe-start-codex.js +524 -245
- package/dist/bin/exe-start-opencode.js +534 -165
- package/dist/bin/exe-status.js +75 -15
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +54 -11
- package/dist/bin/git-sweep.js +369 -24
- package/dist/bin/graph-backfill.js +54 -11
- package/dist/bin/graph-export.js +54 -11
- package/dist/bin/install.js +62 -4
- package/dist/bin/intercom-check.js +491 -146
- package/dist/bin/pre-publish.js +13 -1
- package/dist/bin/scan-tasks.js +369 -24
- package/dist/bin/setup.js +91 -13
- package/dist/bin/shard-migrate.js +54 -11
- package/dist/bin/stack-update.js +1 -1
- package/dist/bin/update.js +3 -3
- package/dist/gateway/index.js +128 -23
- package/dist/hooks/bug-report-worker.js +128 -23
- package/dist/hooks/codex-stop-task-finalizer.js +512 -140
- package/dist/hooks/commit-complete.js +369 -24
- package/dist/hooks/error-recall.js +54 -11
- package/dist/hooks/ingest.js +4575 -252
- package/dist/hooks/instructions-loaded.js +54 -11
- package/dist/hooks/notification.js +54 -11
- package/dist/hooks/post-compact.js +75 -15
- package/dist/hooks/post-tool-combined.js +75 -15
- package/dist/hooks/pre-compact.js +449 -104
- package/dist/hooks/pre-tool-use.js +90 -15
- package/dist/hooks/prompt-submit.js +129 -24
- package/dist/hooks/session-end.js +451 -109
- package/dist/hooks/session-start.js +104 -16
- package/dist/hooks/stop.js +74 -14
- package/dist/hooks/subagent-stop.js +75 -15
- package/dist/hooks/summary-worker.js +73 -7
- package/dist/index.js +128 -23
- package/dist/lib/agent-config.js +16 -1
- package/dist/lib/cloud-sync.js +38 -1
- package/dist/lib/consolidation.js +16 -1
- package/dist/lib/database.js +16 -0
- package/dist/lib/db.js +16 -0
- package/dist/lib/device-registry.js +16 -0
- package/dist/lib/employee-templates.js +29 -3
- package/dist/lib/employees.js +16 -1
- package/dist/lib/exe-daemon.js +268 -42
- package/dist/lib/hybrid-search.js +54 -11
- package/dist/lib/license.js +3 -3
- package/dist/lib/messaging.js +21 -4
- package/dist/lib/schedules.js +29 -1
- package/dist/lib/skill-learning.js +458 -70
- package/dist/lib/status-brief.js +14 -1
- package/dist/lib/store.js +54 -11
- package/dist/lib/tasks.js +393 -91
- package/dist/lib/tmux-routing.js +316 -14
- package/dist/mcp/server.js +169 -30
- package/dist/mcp/tools/create-task.js +75 -13
- 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 +390 -91
- package/dist/runtime/index.js +446 -101
- package/dist/tui/App.js +208 -54
- package/package.json +1 -1
|
@@ -423,6 +423,7 @@ __export(agent_config_exports, {
|
|
|
423
423
|
getAgentRuntime: () => getAgentRuntime,
|
|
424
424
|
loadAgentConfig: () => loadAgentConfig,
|
|
425
425
|
saveAgentConfig: () => saveAgentConfig,
|
|
426
|
+
setAgentMcps: () => setAgentMcps,
|
|
426
427
|
setAgentRuntime: () => setAgentRuntime
|
|
427
428
|
});
|
|
428
429
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
@@ -449,7 +450,7 @@ function getAgentRuntime(agentId) {
|
|
|
449
450
|
if (orgDefault) return orgDefault;
|
|
450
451
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
451
452
|
}
|
|
452
|
-
function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
453
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
453
454
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
454
455
|
if (!knownModels) {
|
|
455
456
|
return {
|
|
@@ -464,12 +465,26 @@ function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
|
464
465
|
};
|
|
465
466
|
}
|
|
466
467
|
const config = loadAgentConfig();
|
|
468
|
+
const existing = config[agentId];
|
|
467
469
|
const entry = { runtime, model };
|
|
468
470
|
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
471
|
+
if (mcps !== void 0) {
|
|
472
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
473
|
+
} else if (existing?.mcps) {
|
|
474
|
+
entry.mcps = existing.mcps;
|
|
475
|
+
}
|
|
469
476
|
config[agentId] = entry;
|
|
470
477
|
saveAgentConfig(config);
|
|
471
478
|
return { ok: true };
|
|
472
479
|
}
|
|
480
|
+
function setAgentMcps(agentId, mcps) {
|
|
481
|
+
const config = loadAgentConfig();
|
|
482
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
483
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
484
|
+
config[agentId] = existing;
|
|
485
|
+
saveAgentConfig(config);
|
|
486
|
+
return { ok: true };
|
|
487
|
+
}
|
|
473
488
|
function clearAgentRuntime(agentId) {
|
|
474
489
|
const config = loadAgentConfig();
|
|
475
490
|
delete config[agentId];
|
|
@@ -3659,6 +3674,22 @@ async function ensureSchema() {
|
|
|
3659
3674
|
} catch (e) {
|
|
3660
3675
|
logCatchDebug("migration", e);
|
|
3661
3676
|
}
|
|
3677
|
+
try {
|
|
3678
|
+
await client.execute({
|
|
3679
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3680
|
+
args: []
|
|
3681
|
+
});
|
|
3682
|
+
} catch (e) {
|
|
3683
|
+
logCatchDebug("migration", e);
|
|
3684
|
+
}
|
|
3685
|
+
try {
|
|
3686
|
+
await client.execute({
|
|
3687
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3688
|
+
args: []
|
|
3689
|
+
});
|
|
3690
|
+
} catch (e) {
|
|
3691
|
+
logCatchDebug("migration", e);
|
|
3692
|
+
}
|
|
3662
3693
|
}
|
|
3663
3694
|
async function disposeDatabase() {
|
|
3664
3695
|
if (_walCheckpointTimer) {
|
|
@@ -4782,11 +4813,17 @@ var init_platform_procedures = __esm({
|
|
|
4782
4813
|
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."
|
|
4783
4814
|
},
|
|
4784
4815
|
{
|
|
4785
|
-
title: "
|
|
4816
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
4786
4817
|
domain: "workflow",
|
|
4787
4818
|
priority: "p1",
|
|
4788
4819
|
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."
|
|
4789
4820
|
},
|
|
4821
|
+
{
|
|
4822
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
4823
|
+
domain: "identity",
|
|
4824
|
+
priority: "p0",
|
|
4825
|
+
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."
|
|
4826
|
+
},
|
|
4790
4827
|
{
|
|
4791
4828
|
title: "Single dispatch path \u2014 create_task only",
|
|
4792
4829
|
domain: "workflow",
|
|
@@ -4820,6 +4857,12 @@ var init_platform_procedures = __esm({
|
|
|
4820
4857
|
priority: "p0",
|
|
4821
4858
|
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."
|
|
4822
4859
|
},
|
|
4860
|
+
{
|
|
4861
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
4862
|
+
domain: "security",
|
|
4863
|
+
priority: "p0",
|
|
4864
|
+
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."
|
|
4865
|
+
},
|
|
4823
4866
|
{
|
|
4824
4867
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
4825
4868
|
domain: "support",
|
|
@@ -5105,10 +5148,24 @@ function stableId(memoryId, type, content) {
|
|
|
5105
5148
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
5106
5149
|
}
|
|
5107
5150
|
function cleanText(text) {
|
|
5108
|
-
|
|
5151
|
+
let cleaned = text.replace(
|
|
5152
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
5153
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
5154
|
+
);
|
|
5155
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
5156
|
+
return cleaned;
|
|
5109
5157
|
}
|
|
5110
|
-
function
|
|
5111
|
-
|
|
5158
|
+
function splitSegments(text) {
|
|
5159
|
+
const cleaned = cleanText(text);
|
|
5160
|
+
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);
|
|
5161
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5162
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
5163
|
+
if (lines.length > 0) return lines;
|
|
5164
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5165
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
return segments;
|
|
5112
5169
|
}
|
|
5113
5170
|
function inferCardType(sentence, toolName) {
|
|
5114
5171
|
const lower = sentence.toLowerCase();
|
|
@@ -5140,12 +5197,12 @@ function predicateFor(type) {
|
|
|
5140
5197
|
}
|
|
5141
5198
|
}
|
|
5142
5199
|
function extractMemoryCards(row) {
|
|
5143
|
-
const
|
|
5200
|
+
const segments = splitSegments(row.raw_text);
|
|
5144
5201
|
const cards = [];
|
|
5145
|
-
for (const sentence of
|
|
5202
|
+
for (const sentence of segments) {
|
|
5146
5203
|
const type = inferCardType(sentence, row.tool_name);
|
|
5147
5204
|
const subject = extractSubject(sentence, row.agent_id);
|
|
5148
|
-
const content = sentence.length >
|
|
5205
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
5149
5206
|
cards.push({
|
|
5150
5207
|
id: stableId(row.id, type, content),
|
|
5151
5208
|
memory_id: row.id,
|
|
@@ -5241,13 +5298,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
5241
5298
|
last_accessed: String(row.timestamp)
|
|
5242
5299
|
}));
|
|
5243
5300
|
}
|
|
5244
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5301
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
5245
5302
|
var init_memory_cards = __esm({
|
|
5246
5303
|
"src/lib/memory-cards.ts"() {
|
|
5247
5304
|
"use strict";
|
|
5248
5305
|
init_database();
|
|
5249
|
-
MAX_CARDS_PER_MEMORY =
|
|
5250
|
-
|
|
5306
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5307
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5308
|
+
MIN_SEGMENT_CHARS = 20;
|
|
5251
5309
|
}
|
|
5252
5310
|
});
|
|
5253
5311
|
|
|
@@ -7559,7 +7617,7 @@ async function hybridSearch(queryText, agentId, options) {
|
|
|
7559
7617
|
try {
|
|
7560
7618
|
const client = getClient();
|
|
7561
7619
|
void client.execute({
|
|
7562
|
-
sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1 WHERE id IN (${placeholders})`,
|
|
7620
|
+
sql: `UPDATE memories SET last_accessed = ?, retrieval_count = COALESCE(retrieval_count, 0) + 1, strength = MIN(1.0, COALESCE(strength, 1.0) + 0.1) WHERE id IN (${placeholders})`,
|
|
7563
7621
|
args: [now, ...ids]
|
|
7564
7622
|
}).catch(() => {
|
|
7565
7623
|
});
|
|
@@ -8475,7 +8533,7 @@ var init_license = __esm({
|
|
|
8475
8533
|
LICENSE_PATH = path15.join(EXE_AI_DIR, "license.key");
|
|
8476
8534
|
CACHE_PATH = path15.join(EXE_AI_DIR, "license-cache.json");
|
|
8477
8535
|
DEVICE_ID_PATH = path15.join(EXE_AI_DIR, "device-id");
|
|
8478
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
8536
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
8479
8537
|
}
|
|
8480
8538
|
});
|
|
8481
8539
|
|
|
@@ -8528,6 +8586,18 @@ function extractRootExe(name) {
|
|
|
8528
8586
|
const parts = name.split("-").filter(Boolean);
|
|
8529
8587
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
8530
8588
|
}
|
|
8589
|
+
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
8590
|
+
if (!existsSync16(SESSION_CACHE)) {
|
|
8591
|
+
mkdirSync8(SESSION_CACHE, { recursive: true });
|
|
8592
|
+
}
|
|
8593
|
+
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
8594
|
+
const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
8595
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
8596
|
+
parentExe: rootExe,
|
|
8597
|
+
dispatchedBy: dispatchedBy || rootExe,
|
|
8598
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8599
|
+
}));
|
|
8600
|
+
}
|
|
8531
8601
|
function getParentExe(sessionKey) {
|
|
8532
8602
|
try {
|
|
8533
8603
|
const data = JSON.parse(readFileSync11(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
@@ -8537,11 +8607,12 @@ function getParentExe(sessionKey) {
|
|
|
8537
8607
|
}
|
|
8538
8608
|
}
|
|
8539
8609
|
function resolveExeSession() {
|
|
8610
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
8611
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
8612
|
+
if (fromEnv) return fromEnv;
|
|
8613
|
+
}
|
|
8540
8614
|
const mySession = getMySession();
|
|
8541
8615
|
if (!mySession) {
|
|
8542
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
8543
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
8544
|
-
}
|
|
8545
8616
|
return null;
|
|
8546
8617
|
}
|
|
8547
8618
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -8556,6 +8627,10 @@ function resolveExeSession() {
|
|
|
8556
8627
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
8557
8628
|
`
|
|
8558
8629
|
);
|
|
8630
|
+
try {
|
|
8631
|
+
registerParentExe(key, fromSessionName);
|
|
8632
|
+
} catch {
|
|
8633
|
+
}
|
|
8559
8634
|
candidate = fromSessionName;
|
|
8560
8635
|
} else {
|
|
8561
8636
|
candidate = fromCache;
|
|
@@ -9403,6 +9478,19 @@ ${phase.label}. Focus: ${phase.focus}. This is guidance, not a blocker; the user
|
|
|
9403
9478
|
`;
|
|
9404
9479
|
} catch {
|
|
9405
9480
|
}
|
|
9481
|
+
if (canCoordinate2(agentId, agent.agentRole) || ["CTO", "COO"].includes(agent.agentRole ?? "")) {
|
|
9482
|
+
try {
|
|
9483
|
+
const archPath = path21.join(data.cwd || process.cwd(), "exe", "ARCHITECTURE.md");
|
|
9484
|
+
if (existsSync18(archPath)) {
|
|
9485
|
+
const archContent = readFileSync13(archPath, "utf-8").slice(0, 1500);
|
|
9486
|
+
additionalContext += `## Project Architecture
|
|
9487
|
+
${archContent}
|
|
9488
|
+
|
|
9489
|
+
`;
|
|
9490
|
+
}
|
|
9491
|
+
} catch {
|
|
9492
|
+
}
|
|
9493
|
+
}
|
|
9406
9494
|
if (memories.length > 0) {
|
|
9407
9495
|
const brief = memories.map(
|
|
9408
9496
|
(m) => `[${m.timestamp}] ${m.tool_name}: ${m.raw_text.slice(0, 200)}`
|
package/dist/hooks/stop.js
CHANGED
|
@@ -3581,6 +3581,22 @@ async function ensureSchema() {
|
|
|
3581
3581
|
} catch (e) {
|
|
3582
3582
|
logCatchDebug("migration", e);
|
|
3583
3583
|
}
|
|
3584
|
+
try {
|
|
3585
|
+
await client.execute({
|
|
3586
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3587
|
+
args: []
|
|
3588
|
+
});
|
|
3589
|
+
} catch (e) {
|
|
3590
|
+
logCatchDebug("migration", e);
|
|
3591
|
+
}
|
|
3592
|
+
try {
|
|
3593
|
+
await client.execute({
|
|
3594
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3595
|
+
args: []
|
|
3596
|
+
});
|
|
3597
|
+
} catch (e) {
|
|
3598
|
+
logCatchDebug("migration", e);
|
|
3599
|
+
}
|
|
3584
3600
|
}
|
|
3585
3601
|
async function disposeDatabase() {
|
|
3586
3602
|
if (_walCheckpointTimer) {
|
|
@@ -3647,7 +3663,7 @@ var init_license = __esm({
|
|
|
3647
3663
|
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3648
3664
|
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3649
3665
|
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
3650
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
3666
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
3651
3667
|
}
|
|
3652
3668
|
});
|
|
3653
3669
|
|
|
@@ -3700,6 +3716,18 @@ function extractRootExe(name) {
|
|
|
3700
3716
|
const parts = name.split("-").filter(Boolean);
|
|
3701
3717
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3702
3718
|
}
|
|
3719
|
+
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3720
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
3721
|
+
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
3722
|
+
}
|
|
3723
|
+
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3724
|
+
const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3725
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
3726
|
+
parentExe: rootExe,
|
|
3727
|
+
dispatchedBy: dispatchedBy || rootExe,
|
|
3728
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3729
|
+
}));
|
|
3730
|
+
}
|
|
3703
3731
|
function getParentExe(sessionKey) {
|
|
3704
3732
|
try {
|
|
3705
3733
|
const data = JSON.parse(readFileSync10(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
@@ -3709,11 +3737,12 @@ function getParentExe(sessionKey) {
|
|
|
3709
3737
|
}
|
|
3710
3738
|
}
|
|
3711
3739
|
function resolveExeSession() {
|
|
3740
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
3741
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
3742
|
+
if (fromEnv) return fromEnv;
|
|
3743
|
+
}
|
|
3712
3744
|
const mySession = getMySession();
|
|
3713
3745
|
if (!mySession) {
|
|
3714
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
3715
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
3716
|
-
}
|
|
3717
3746
|
return null;
|
|
3718
3747
|
}
|
|
3719
3748
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -3728,6 +3757,10 @@ function resolveExeSession() {
|
|
|
3728
3757
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
3729
3758
|
`
|
|
3730
3759
|
);
|
|
3760
|
+
try {
|
|
3761
|
+
registerParentExe(key, fromSessionName);
|
|
3762
|
+
} catch {
|
|
3763
|
+
}
|
|
3731
3764
|
candidate = fromSessionName;
|
|
3732
3765
|
} else {
|
|
3733
3766
|
candidate = fromCache;
|
|
@@ -4900,11 +4933,17 @@ var init_platform_procedures = __esm({
|
|
|
4900
4933
|
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."
|
|
4901
4934
|
},
|
|
4902
4935
|
{
|
|
4903
|
-
title: "
|
|
4936
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
4904
4937
|
domain: "workflow",
|
|
4905
4938
|
priority: "p1",
|
|
4906
4939
|
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."
|
|
4907
4940
|
},
|
|
4941
|
+
{
|
|
4942
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
4943
|
+
domain: "identity",
|
|
4944
|
+
priority: "p0",
|
|
4945
|
+
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."
|
|
4946
|
+
},
|
|
4908
4947
|
{
|
|
4909
4948
|
title: "Single dispatch path \u2014 create_task only",
|
|
4910
4949
|
domain: "workflow",
|
|
@@ -4938,6 +4977,12 @@ var init_platform_procedures = __esm({
|
|
|
4938
4977
|
priority: "p0",
|
|
4939
4978
|
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."
|
|
4940
4979
|
},
|
|
4980
|
+
{
|
|
4981
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
4982
|
+
domain: "security",
|
|
4983
|
+
priority: "p0",
|
|
4984
|
+
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."
|
|
4985
|
+
},
|
|
4941
4986
|
{
|
|
4942
4987
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
4943
4988
|
domain: "support",
|
|
@@ -5223,10 +5268,24 @@ function stableId(memoryId, type, content) {
|
|
|
5223
5268
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
5224
5269
|
}
|
|
5225
5270
|
function cleanText(text) {
|
|
5226
|
-
|
|
5271
|
+
let cleaned = text.replace(
|
|
5272
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
5273
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
5274
|
+
);
|
|
5275
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
5276
|
+
return cleaned;
|
|
5227
5277
|
}
|
|
5228
|
-
function
|
|
5229
|
-
|
|
5278
|
+
function splitSegments(text) {
|
|
5279
|
+
const cleaned = cleanText(text);
|
|
5280
|
+
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);
|
|
5281
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5282
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
5283
|
+
if (lines.length > 0) return lines;
|
|
5284
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5285
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
return segments;
|
|
5230
5289
|
}
|
|
5231
5290
|
function inferCardType(sentence, toolName) {
|
|
5232
5291
|
const lower = sentence.toLowerCase();
|
|
@@ -5258,12 +5317,12 @@ function predicateFor(type) {
|
|
|
5258
5317
|
}
|
|
5259
5318
|
}
|
|
5260
5319
|
function extractMemoryCards(row) {
|
|
5261
|
-
const
|
|
5320
|
+
const segments = splitSegments(row.raw_text);
|
|
5262
5321
|
const cards = [];
|
|
5263
|
-
for (const sentence of
|
|
5322
|
+
for (const sentence of segments) {
|
|
5264
5323
|
const type = inferCardType(sentence, row.tool_name);
|
|
5265
5324
|
const subject = extractSubject(sentence, row.agent_id);
|
|
5266
|
-
const content = sentence.length >
|
|
5325
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
5267
5326
|
cards.push({
|
|
5268
5327
|
id: stableId(row.id, type, content),
|
|
5269
5328
|
memory_id: row.id,
|
|
@@ -5359,13 +5418,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
5359
5418
|
last_accessed: String(row.timestamp)
|
|
5360
5419
|
}));
|
|
5361
5420
|
}
|
|
5362
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5421
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
5363
5422
|
var init_memory_cards = __esm({
|
|
5364
5423
|
"src/lib/memory-cards.ts"() {
|
|
5365
5424
|
"use strict";
|
|
5366
5425
|
init_database();
|
|
5367
|
-
MAX_CARDS_PER_MEMORY =
|
|
5368
|
-
|
|
5426
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5427
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5428
|
+
MIN_SEGMENT_CHARS = 20;
|
|
5369
5429
|
}
|
|
5370
5430
|
});
|
|
5371
5431
|
|
|
@@ -3553,6 +3553,22 @@ async function ensureSchema() {
|
|
|
3553
3553
|
} catch (e) {
|
|
3554
3554
|
logCatchDebug("migration", e);
|
|
3555
3555
|
}
|
|
3556
|
+
try {
|
|
3557
|
+
await client.execute({
|
|
3558
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
3559
|
+
args: []
|
|
3560
|
+
});
|
|
3561
|
+
} catch (e) {
|
|
3562
|
+
logCatchDebug("migration", e);
|
|
3563
|
+
}
|
|
3564
|
+
try {
|
|
3565
|
+
await client.execute({
|
|
3566
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
3567
|
+
args: []
|
|
3568
|
+
});
|
|
3569
|
+
} catch (e) {
|
|
3570
|
+
logCatchDebug("migration", e);
|
|
3571
|
+
}
|
|
3556
3572
|
}
|
|
3557
3573
|
async function disposeDatabase() {
|
|
3558
3574
|
if (_walCheckpointTimer) {
|
|
@@ -3619,7 +3635,7 @@ var init_license = __esm({
|
|
|
3619
3635
|
LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
|
|
3620
3636
|
CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
|
|
3621
3637
|
DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
|
|
3622
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
3638
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
3623
3639
|
}
|
|
3624
3640
|
});
|
|
3625
3641
|
|
|
@@ -3672,6 +3688,18 @@ function extractRootExe(name) {
|
|
|
3672
3688
|
const parts = name.split("-").filter(Boolean);
|
|
3673
3689
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3674
3690
|
}
|
|
3691
|
+
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3692
|
+
if (!existsSync12(SESSION_CACHE)) {
|
|
3693
|
+
mkdirSync7(SESSION_CACHE, { recursive: true });
|
|
3694
|
+
}
|
|
3695
|
+
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
3696
|
+
const filePath = path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
3697
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
3698
|
+
parentExe: rootExe,
|
|
3699
|
+
dispatchedBy: dispatchedBy || rootExe,
|
|
3700
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3701
|
+
}));
|
|
3702
|
+
}
|
|
3675
3703
|
function getParentExe(sessionKey) {
|
|
3676
3704
|
try {
|
|
3677
3705
|
const data = JSON.parse(readFileSync10(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
@@ -3681,11 +3709,12 @@ function getParentExe(sessionKey) {
|
|
|
3681
3709
|
}
|
|
3682
3710
|
}
|
|
3683
3711
|
function resolveExeSession() {
|
|
3712
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
3713
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
3714
|
+
if (fromEnv) return fromEnv;
|
|
3715
|
+
}
|
|
3684
3716
|
const mySession = getMySession();
|
|
3685
3717
|
if (!mySession) {
|
|
3686
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
3687
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
3688
|
-
}
|
|
3689
3718
|
return null;
|
|
3690
3719
|
}
|
|
3691
3720
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -3700,6 +3729,10 @@ function resolveExeSession() {
|
|
|
3700
3729
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
3701
3730
|
`
|
|
3702
3731
|
);
|
|
3732
|
+
try {
|
|
3733
|
+
registerParentExe(key, fromSessionName);
|
|
3734
|
+
} catch {
|
|
3735
|
+
}
|
|
3703
3736
|
candidate = fromSessionName;
|
|
3704
3737
|
} else {
|
|
3705
3738
|
candidate = fromCache;
|
|
@@ -4863,11 +4896,17 @@ var init_platform_procedures = __esm({
|
|
|
4863
4896
|
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."
|
|
4864
4897
|
},
|
|
4865
4898
|
{
|
|
4866
|
-
title: "
|
|
4899
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
4867
4900
|
domain: "workflow",
|
|
4868
4901
|
priority: "p1",
|
|
4869
4902
|
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."
|
|
4870
4903
|
},
|
|
4904
|
+
{
|
|
4905
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
4906
|
+
domain: "identity",
|
|
4907
|
+
priority: "p0",
|
|
4908
|
+
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."
|
|
4909
|
+
},
|
|
4871
4910
|
{
|
|
4872
4911
|
title: "Single dispatch path \u2014 create_task only",
|
|
4873
4912
|
domain: "workflow",
|
|
@@ -4901,6 +4940,12 @@ var init_platform_procedures = __esm({
|
|
|
4901
4940
|
priority: "p0",
|
|
4902
4941
|
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."
|
|
4903
4942
|
},
|
|
4943
|
+
{
|
|
4944
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
4945
|
+
domain: "security",
|
|
4946
|
+
priority: "p0",
|
|
4947
|
+
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."
|
|
4948
|
+
},
|
|
4904
4949
|
{
|
|
4905
4950
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
4906
4951
|
domain: "support",
|
|
@@ -5186,10 +5231,24 @@ function stableId(memoryId, type, content) {
|
|
|
5186
5231
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
5187
5232
|
}
|
|
5188
5233
|
function cleanText(text) {
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5234
|
+
let cleaned = text.replace(
|
|
5235
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
5236
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
5237
|
+
);
|
|
5238
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
5239
|
+
return cleaned;
|
|
5240
|
+
}
|
|
5241
|
+
function splitSegments(text) {
|
|
5242
|
+
const cleaned = cleanText(text);
|
|
5243
|
+
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);
|
|
5244
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5245
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
5246
|
+
if (lines.length > 0) return lines;
|
|
5247
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
5248
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
return segments;
|
|
5193
5252
|
}
|
|
5194
5253
|
function inferCardType(sentence, toolName) {
|
|
5195
5254
|
const lower = sentence.toLowerCase();
|
|
@@ -5221,12 +5280,12 @@ function predicateFor(type) {
|
|
|
5221
5280
|
}
|
|
5222
5281
|
}
|
|
5223
5282
|
function extractMemoryCards(row) {
|
|
5224
|
-
const
|
|
5283
|
+
const segments = splitSegments(row.raw_text);
|
|
5225
5284
|
const cards = [];
|
|
5226
|
-
for (const sentence of
|
|
5285
|
+
for (const sentence of segments) {
|
|
5227
5286
|
const type = inferCardType(sentence, row.tool_name);
|
|
5228
5287
|
const subject = extractSubject(sentence, row.agent_id);
|
|
5229
|
-
const content = sentence.length >
|
|
5288
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
5230
5289
|
cards.push({
|
|
5231
5290
|
id: stableId(row.id, type, content),
|
|
5232
5291
|
memory_id: row.id,
|
|
@@ -5322,13 +5381,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
5322
5381
|
last_accessed: String(row.timestamp)
|
|
5323
5382
|
}));
|
|
5324
5383
|
}
|
|
5325
|
-
var MAX_CARDS_PER_MEMORY,
|
|
5384
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
5326
5385
|
var init_memory_cards = __esm({
|
|
5327
5386
|
"src/lib/memory-cards.ts"() {
|
|
5328
5387
|
"use strict";
|
|
5329
5388
|
init_database();
|
|
5330
|
-
MAX_CARDS_PER_MEMORY =
|
|
5331
|
-
|
|
5389
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
5390
|
+
MAX_SEGMENT_CHARS = 500;
|
|
5391
|
+
MIN_SEGMENT_CHARS = 20;
|
|
5332
5392
|
}
|
|
5333
5393
|
});
|
|
5334
5394
|
|