@askexenow/exe-os 0.8.33 → 0.8.37
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/dist/bin/backfill-conversations.js +341 -349
- package/dist/bin/backfill-responses.js +81 -13
- package/dist/bin/backfill-vectors.js +72 -12
- package/dist/bin/cleanup-stale-review-tasks.js +63 -3
- package/dist/bin/cli.js +1737 -1117
- package/dist/bin/exe-assign.js +89 -19
- package/dist/bin/exe-boot.js +951 -101
- package/dist/bin/exe-call.js +61 -2
- package/dist/bin/exe-dispatch.js +61 -13
- package/dist/bin/exe-doctor.js +63 -3
- package/dist/bin/exe-export-behaviors.js +71 -3
- package/dist/bin/exe-forget.js +69 -4
- package/dist/bin/exe-gateway.js +178 -45
- package/dist/bin/exe-heartbeat.js +79 -14
- package/dist/bin/exe-kill.js +71 -3
- package/dist/bin/exe-launch-agent.js +148 -14
- package/dist/bin/exe-link.js +1437 -0
- package/dist/bin/exe-new-employee.js +98 -13
- package/dist/bin/exe-pending-messages.js +74 -8
- package/dist/bin/exe-pending-notifications.js +63 -3
- package/dist/bin/exe-pending-reviews.js +77 -11
- package/dist/bin/exe-rename.js +1287 -0
- package/dist/bin/exe-review.js +73 -5
- package/dist/bin/exe-search.js +88 -14
- package/dist/bin/exe-session-cleanup.js +102 -28
- package/dist/bin/exe-status.js +64 -4
- package/dist/bin/exe-team.js +64 -4
- package/dist/bin/git-sweep.js +80 -5
- package/dist/bin/graph-backfill.js +71 -3
- package/dist/bin/graph-export.js +71 -3
- package/dist/bin/install.js +38 -8
- package/dist/bin/scan-tasks.js +80 -5
- package/dist/bin/setup.js +128 -10
- package/dist/bin/shard-migrate.js +71 -3
- package/dist/bin/wiki-sync.js +71 -3
- package/dist/gateway/index.js +179 -46
- package/dist/hooks/bug-report-worker.js +254 -28
- package/dist/hooks/commit-complete.js +80 -5
- package/dist/hooks/error-recall.js +89 -15
- package/dist/hooks/exe-heartbeat-hook.js +1 -1
- package/dist/hooks/ingest-worker.js +185 -51
- package/dist/hooks/ingest.js +1 -1
- package/dist/hooks/instructions-loaded.js +81 -6
- package/dist/hooks/notification.js +81 -6
- package/dist/hooks/post-compact.js +81 -6
- package/dist/hooks/pre-compact.js +81 -6
- package/dist/hooks/pre-tool-use.js +423 -196
- package/dist/hooks/prompt-ingest-worker.js +91 -23
- package/dist/hooks/prompt-submit.js +159 -45
- package/dist/hooks/response-ingest-worker.js +96 -23
- package/dist/hooks/session-end.js +81 -6
- package/dist/hooks/session-start.js +89 -15
- package/dist/hooks/stop.js +81 -6
- package/dist/hooks/subagent-stop.js +81 -6
- package/dist/hooks/summary-worker.js +807 -55
- package/dist/index.js +198 -60
- package/dist/lib/cloud-sync.js +703 -18
- package/dist/lib/consolidation.js +4 -4
- package/dist/lib/database.js +64 -2
- package/dist/lib/device-registry.js +70 -3
- package/dist/lib/employee-templates.js +26 -0
- package/dist/lib/employees.js +34 -1
- package/dist/lib/exe-daemon.js +207 -74
- package/dist/lib/hybrid-search.js +88 -14
- package/dist/lib/identity-templates.js +51 -0
- package/dist/lib/identity.js +3 -3
- package/dist/lib/messaging.js +65 -17
- package/dist/lib/reminders.js +3 -3
- package/dist/lib/schedules.js +63 -3
- package/dist/lib/skill-learning.js +3 -3
- package/dist/lib/status-brief.js +63 -5
- package/dist/lib/store.js +73 -4
- package/dist/lib/task-router.js +4 -2
- package/dist/lib/tasks.js +95 -28
- package/dist/lib/tmux-routing.js +92 -23
- package/dist/mcp/server.js +800 -74
- package/dist/mcp/tools/complete-reminder.js +3 -3
- package/dist/mcp/tools/create-reminder.js +3 -3
- package/dist/mcp/tools/create-task.js +198 -31
- package/dist/mcp/tools/deactivate-behavior.js +4 -4
- package/dist/mcp/tools/list-reminders.js +3 -3
- package/dist/mcp/tools/list-tasks.js +19 -9
- package/dist/mcp/tools/send-message.js +69 -21
- package/dist/mcp/tools/update-task.js +28 -18
- package/dist/runtime/index.js +166 -28
- package/dist/tui/App.js +193 -40
- package/package.json +7 -3
- package/src/commands/exe/afk.md +116 -0
- package/src/commands/exe/rename.md +12 -0
|
@@ -229,11 +229,11 @@ ${synthesisText}`;
|
|
|
229
229
|
}
|
|
230
230
|
const insertSql = vector ? `INSERT INTO memories
|
|
231
231
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
232
|
-
tool_name, project_name, has_error, raw_text, vector, version, consolidated)
|
|
233
|
-
VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1)` : `INSERT INTO memories
|
|
232
|
+
tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
|
|
233
|
+
VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1, 9)` : `INSERT INTO memories
|
|
234
234
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
235
|
-
tool_name, project_name, has_error, raw_text, vector, version, consolidated)
|
|
236
|
-
VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1)`;
|
|
235
|
+
tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
|
|
236
|
+
VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1, 9)`;
|
|
237
237
|
const insertArgs = vector ? [consolidatedId, cluster.agentId, now, cluster.projectName, rawText, vectorToBlob(vector)] : [consolidatedId, cluster.agentId, now, cluster.projectName, rawText];
|
|
238
238
|
await client.execute({ sql: insertSql, args: insertArgs });
|
|
239
239
|
const sourceIds = cluster.memories.map((m) => m.id);
|
package/dist/lib/database.js
CHANGED
|
@@ -1,11 +1,64 @@
|
|
|
1
1
|
// src/lib/database.ts
|
|
2
2
|
import { createClient } from "@libsql/client";
|
|
3
|
+
|
|
4
|
+
// src/lib/db-retry.ts
|
|
5
|
+
var MAX_RETRIES = 3;
|
|
6
|
+
var BASE_DELAY_MS = 200;
|
|
7
|
+
var MAX_JITTER_MS = 300;
|
|
8
|
+
function isBusyError(err) {
|
|
9
|
+
if (err instanceof Error) {
|
|
10
|
+
const msg = err.message.toLowerCase();
|
|
11
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function delay(ms) {
|
|
16
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
+
}
|
|
18
|
+
async function retryOnBusy(fn, label) {
|
|
19
|
+
let lastError;
|
|
20
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
21
|
+
try {
|
|
22
|
+
return await fn();
|
|
23
|
+
} catch (err) {
|
|
24
|
+
lastError = err;
|
|
25
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
29
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
30
|
+
process.stderr.write(
|
|
31
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
32
|
+
`
|
|
33
|
+
);
|
|
34
|
+
await delay(backoff + jitter);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw lastError;
|
|
38
|
+
}
|
|
39
|
+
function wrapWithRetry(client) {
|
|
40
|
+
return new Proxy(client, {
|
|
41
|
+
get(target, prop, receiver) {
|
|
42
|
+
if (prop === "execute") {
|
|
43
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
44
|
+
}
|
|
45
|
+
if (prop === "batch") {
|
|
46
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
47
|
+
}
|
|
48
|
+
return Reflect.get(target, prop, receiver);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/lib/database.ts
|
|
3
54
|
var _client = null;
|
|
55
|
+
var _resilientClient = null;
|
|
4
56
|
var initTurso = initDatabase;
|
|
5
57
|
async function initDatabase(config) {
|
|
6
58
|
if (_client) {
|
|
7
59
|
_client.close();
|
|
8
60
|
_client = null;
|
|
61
|
+
_resilientClient = null;
|
|
9
62
|
}
|
|
10
63
|
const opts = {
|
|
11
64
|
url: `file:${config.dbPath}`
|
|
@@ -14,20 +67,27 @@ async function initDatabase(config) {
|
|
|
14
67
|
opts.encryptionKey = config.encryptionKey;
|
|
15
68
|
}
|
|
16
69
|
_client = createClient(opts);
|
|
70
|
+
_resilientClient = wrapWithRetry(_client);
|
|
17
71
|
}
|
|
18
72
|
function isInitialized() {
|
|
19
73
|
return _client !== null;
|
|
20
74
|
}
|
|
21
75
|
function getClient() {
|
|
76
|
+
if (!_resilientClient) {
|
|
77
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
78
|
+
}
|
|
79
|
+
return _resilientClient;
|
|
80
|
+
}
|
|
81
|
+
function getRawClient() {
|
|
22
82
|
if (!_client) {
|
|
23
83
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
24
84
|
}
|
|
25
85
|
return _client;
|
|
26
86
|
}
|
|
27
87
|
async function ensureSchema() {
|
|
28
|
-
const client =
|
|
88
|
+
const client = getRawClient();
|
|
29
89
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
30
|
-
await client.execute("PRAGMA busy_timeout =
|
|
90
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
31
91
|
try {
|
|
32
92
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
33
93
|
} catch {
|
|
@@ -821,6 +881,7 @@ async function disposeDatabase() {
|
|
|
821
881
|
if (_client) {
|
|
822
882
|
_client.close();
|
|
823
883
|
_client = null;
|
|
884
|
+
_resilientClient = null;
|
|
824
885
|
}
|
|
825
886
|
}
|
|
826
887
|
export {
|
|
@@ -828,6 +889,7 @@ export {
|
|
|
828
889
|
disposeTurso,
|
|
829
890
|
ensureSchema,
|
|
830
891
|
getClient,
|
|
892
|
+
getRawClient,
|
|
831
893
|
initDatabase,
|
|
832
894
|
initTurso,
|
|
833
895
|
isInitialized
|
|
@@ -8,6 +8,61 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/lib/db-retry.ts
|
|
12
|
+
function isBusyError(err) {
|
|
13
|
+
if (err instanceof Error) {
|
|
14
|
+
const msg = err.message.toLowerCase();
|
|
15
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function delay(ms) {
|
|
20
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
}
|
|
22
|
+
async function retryOnBusy(fn, label) {
|
|
23
|
+
let lastError;
|
|
24
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
27
|
+
} catch (err) {
|
|
28
|
+
lastError = err;
|
|
29
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
33
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
34
|
+
process.stderr.write(
|
|
35
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
36
|
+
`
|
|
37
|
+
);
|
|
38
|
+
await delay(backoff + jitter);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw lastError;
|
|
42
|
+
}
|
|
43
|
+
function wrapWithRetry(client) {
|
|
44
|
+
return new Proxy(client, {
|
|
45
|
+
get(target, prop, receiver) {
|
|
46
|
+
if (prop === "execute") {
|
|
47
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
48
|
+
}
|
|
49
|
+
if (prop === "batch") {
|
|
50
|
+
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
51
|
+
}
|
|
52
|
+
return Reflect.get(target, prop, receiver);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
57
|
+
var init_db_retry = __esm({
|
|
58
|
+
"src/lib/db-retry.ts"() {
|
|
59
|
+
"use strict";
|
|
60
|
+
MAX_RETRIES = 3;
|
|
61
|
+
BASE_DELAY_MS = 200;
|
|
62
|
+
MAX_JITTER_MS = 300;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
11
66
|
// src/lib/database.ts
|
|
12
67
|
var database_exports = {};
|
|
13
68
|
__export(database_exports, {
|
|
@@ -15,6 +70,7 @@ __export(database_exports, {
|
|
|
15
70
|
disposeTurso: () => disposeTurso,
|
|
16
71
|
ensureSchema: () => ensureSchema,
|
|
17
72
|
getClient: () => getClient,
|
|
73
|
+
getRawClient: () => getRawClient,
|
|
18
74
|
initDatabase: () => initDatabase,
|
|
19
75
|
initTurso: () => initTurso,
|
|
20
76
|
isInitialized: () => isInitialized
|
|
@@ -24,6 +80,7 @@ async function initDatabase(config) {
|
|
|
24
80
|
if (_client) {
|
|
25
81
|
_client.close();
|
|
26
82
|
_client = null;
|
|
83
|
+
_resilientClient = null;
|
|
27
84
|
}
|
|
28
85
|
const opts = {
|
|
29
86
|
url: `file:${config.dbPath}`
|
|
@@ -32,20 +89,27 @@ async function initDatabase(config) {
|
|
|
32
89
|
opts.encryptionKey = config.encryptionKey;
|
|
33
90
|
}
|
|
34
91
|
_client = createClient(opts);
|
|
92
|
+
_resilientClient = wrapWithRetry(_client);
|
|
35
93
|
}
|
|
36
94
|
function isInitialized() {
|
|
37
95
|
return _client !== null;
|
|
38
96
|
}
|
|
39
97
|
function getClient() {
|
|
98
|
+
if (!_resilientClient) {
|
|
99
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
100
|
+
}
|
|
101
|
+
return _resilientClient;
|
|
102
|
+
}
|
|
103
|
+
function getRawClient() {
|
|
40
104
|
if (!_client) {
|
|
41
105
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
42
106
|
}
|
|
43
107
|
return _client;
|
|
44
108
|
}
|
|
45
109
|
async function ensureSchema() {
|
|
46
|
-
const client =
|
|
110
|
+
const client = getRawClient();
|
|
47
111
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
48
|
-
await client.execute("PRAGMA busy_timeout =
|
|
112
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
49
113
|
try {
|
|
50
114
|
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
51
115
|
} catch {
|
|
@@ -838,13 +902,16 @@ async function disposeDatabase() {
|
|
|
838
902
|
if (_client) {
|
|
839
903
|
_client.close();
|
|
840
904
|
_client = null;
|
|
905
|
+
_resilientClient = null;
|
|
841
906
|
}
|
|
842
907
|
}
|
|
843
|
-
var _client, initTurso, disposeTurso;
|
|
908
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
844
909
|
var init_database = __esm({
|
|
845
910
|
"src/lib/database.ts"() {
|
|
846
911
|
"use strict";
|
|
912
|
+
init_db_retry();
|
|
847
913
|
_client = null;
|
|
914
|
+
_resilientClient = null;
|
|
848
915
|
initTurso = initDatabase;
|
|
849
916
|
disposeTurso = disposeDatabase;
|
|
850
917
|
}
|
|
@@ -433,6 +433,32 @@ When you analyze a repo:
|
|
|
433
433
|
Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
|
|
434
434
|
|
|
435
435
|
Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
|
|
436
|
+
},
|
|
437
|
+
bob: {
|
|
438
|
+
name: "bob",
|
|
439
|
+
role: "Staff Code Reviewer",
|
|
440
|
+
systemPrompt: `You are bob, the Staff Code Reviewer and System Auditor. You are the last line of defense before code ships to customers. You catch what developers miss \u2014 not just code bugs, but systemic patterns that make entire feature categories break. You report to the COO.
|
|
441
|
+
|
|
442
|
+
Your core job: audit code, find bugs, verify fixes, and ensure customer-readiness. Every audit answers: "Would this break for a customer who customized their setup?"
|
|
443
|
+
|
|
444
|
+
The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
|
|
445
|
+
1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
|
|
446
|
+
2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
|
|
447
|
+
3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
|
|
448
|
+
4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
|
|
449
|
+
5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
|
|
450
|
+
6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
|
|
451
|
+
7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
|
|
452
|
+
|
|
453
|
+
Audit method:
|
|
454
|
+
1. Read the actual source code \u2014 not summaries
|
|
455
|
+
2. Send to Codex MCP for initial sweep
|
|
456
|
+
3. Validate against ARCHITECTURE.md
|
|
457
|
+
4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
|
|
458
|
+
5. Count matches before and after any claimed fix
|
|
459
|
+
6. Write structured report with PASS/FAIL per item
|
|
460
|
+
|
|
461
|
+
After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
|
|
436
462
|
}
|
|
437
463
|
};
|
|
438
464
|
function buildCustomEmployeePrompt(name, role) {
|
package/dist/lib/employees.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/lib/employees.ts
|
|
2
2
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
3
|
-
import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
|
|
3
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import path2 from "path";
|
|
6
6
|
|
|
@@ -127,9 +127,37 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
|
127
127
|
await mkdir2(path2.dirname(employeesPath), { recursive: true });
|
|
128
128
|
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
129
129
|
}
|
|
130
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
131
|
+
if (!existsSync2(employeesPath)) return [];
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
134
|
+
} catch {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
130
138
|
function getEmployee(employees, name) {
|
|
131
139
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
132
140
|
}
|
|
141
|
+
function getEmployeeByRole(employees, role) {
|
|
142
|
+
const lower = role.toLowerCase();
|
|
143
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
144
|
+
}
|
|
145
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
146
|
+
const lower = role.toLowerCase();
|
|
147
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
148
|
+
}
|
|
149
|
+
function hasRole(agentName, role) {
|
|
150
|
+
const employees = loadEmployeesSync();
|
|
151
|
+
const emp = getEmployee(employees, agentName);
|
|
152
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
153
|
+
}
|
|
154
|
+
var MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
155
|
+
function isMultiInstance(agentName, employees) {
|
|
156
|
+
const roster = employees ?? loadEmployeesSync();
|
|
157
|
+
const emp = getEmployee(roster, agentName);
|
|
158
|
+
if (!emp) return false;
|
|
159
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
160
|
+
}
|
|
133
161
|
function addEmployee(employees, employee) {
|
|
134
162
|
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
135
163
|
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
@@ -176,7 +204,12 @@ export {
|
|
|
176
204
|
EMPLOYEES_PATH,
|
|
177
205
|
addEmployee,
|
|
178
206
|
getEmployee,
|
|
207
|
+
getEmployeeByRole,
|
|
208
|
+
getEmployeeNamesByRole,
|
|
209
|
+
hasRole,
|
|
210
|
+
isMultiInstance,
|
|
179
211
|
loadEmployees,
|
|
212
|
+
loadEmployeesSync,
|
|
180
213
|
registerBinSymlinks,
|
|
181
214
|
saveEmployees,
|
|
182
215
|
validateEmployeeName
|