@askexenow/exe-os 0.9.9 → 0.9.10
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/cli.js +143 -124
- package/dist/bin/exe-boot.js +152 -133
- package/dist/bin/exe-dispatch.js +143 -124
- package/dist/bin/exe-gateway.js +143 -124
- package/dist/bin/exe-session-cleanup.js +143 -124
- package/dist/bin/git-sweep.js +143 -124
- package/dist/bin/scan-tasks.js +152 -133
- package/dist/gateway/index.js +143 -124
- package/dist/hooks/bug-report-worker.js +143 -124
- package/dist/hooks/commit-complete.js +143 -124
- package/dist/hooks/ingest-worker.js +82 -63
- package/dist/hooks/pre-compact.js +143 -124
- package/dist/hooks/prompt-submit.js +82 -63
- package/dist/hooks/session-end.js +143 -124
- package/dist/index.js +143 -124
- package/dist/lib/exe-daemon.js +151 -130
- package/dist/lib/tasks.js +143 -124
- package/dist/lib/tmux-routing.js +143 -124
- package/dist/mcp/server.js +82 -63
- package/dist/mcp/tools/create-task.js +143 -124
- package/dist/mcp/tools/update-task.js +143 -124
- package/dist/runtime/index.js +143 -124
- package/dist/tui/App.js +143 -124
- package/package.json +4 -4
package/dist/bin/exe-boot.js
CHANGED
|
@@ -4755,11 +4755,124 @@ var init_session_kill_telemetry = __esm({
|
|
|
4755
4755
|
}
|
|
4756
4756
|
});
|
|
4757
4757
|
|
|
4758
|
+
// src/lib/project-name.ts
|
|
4759
|
+
var project_name_exports = {};
|
|
4760
|
+
__export(project_name_exports, {
|
|
4761
|
+
_resetCache: () => _resetCache,
|
|
4762
|
+
getProjectName: () => getProjectName
|
|
4763
|
+
});
|
|
4764
|
+
import { execSync as execSync5 } from "child_process";
|
|
4765
|
+
import path14 from "path";
|
|
4766
|
+
function getProjectName(cwd) {
|
|
4767
|
+
const dir = cwd ?? process.cwd();
|
|
4768
|
+
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4769
|
+
try {
|
|
4770
|
+
let repoRoot;
|
|
4771
|
+
try {
|
|
4772
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4773
|
+
cwd: dir,
|
|
4774
|
+
encoding: "utf8",
|
|
4775
|
+
timeout: 2e3,
|
|
4776
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4777
|
+
}).trim();
|
|
4778
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
4779
|
+
} catch {
|
|
4780
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
4781
|
+
cwd: dir,
|
|
4782
|
+
encoding: "utf8",
|
|
4783
|
+
timeout: 2e3,
|
|
4784
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4785
|
+
}).trim();
|
|
4786
|
+
}
|
|
4787
|
+
_cached2 = path14.basename(repoRoot);
|
|
4788
|
+
_cachedCwd = dir;
|
|
4789
|
+
return _cached2;
|
|
4790
|
+
} catch {
|
|
4791
|
+
_cached2 = path14.basename(dir);
|
|
4792
|
+
_cachedCwd = dir;
|
|
4793
|
+
return _cached2;
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
function _resetCache() {
|
|
4797
|
+
_cached2 = null;
|
|
4798
|
+
_cachedCwd = null;
|
|
4799
|
+
}
|
|
4800
|
+
var _cached2, _cachedCwd;
|
|
4801
|
+
var init_project_name = __esm({
|
|
4802
|
+
"src/lib/project-name.ts"() {
|
|
4803
|
+
"use strict";
|
|
4804
|
+
_cached2 = null;
|
|
4805
|
+
_cachedCwd = null;
|
|
4806
|
+
}
|
|
4807
|
+
});
|
|
4808
|
+
|
|
4809
|
+
// src/lib/session-scope.ts
|
|
4810
|
+
var session_scope_exports = {};
|
|
4811
|
+
__export(session_scope_exports, {
|
|
4812
|
+
assertSessionScope: () => assertSessionScope,
|
|
4813
|
+
findSessionForProject: () => findSessionForProject,
|
|
4814
|
+
getSessionProject: () => getSessionProject
|
|
4815
|
+
});
|
|
4816
|
+
function getSessionProject(sessionName) {
|
|
4817
|
+
const sessions = listSessions();
|
|
4818
|
+
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
4819
|
+
if (!entry) return null;
|
|
4820
|
+
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
4821
|
+
return parts[parts.length - 1] ?? null;
|
|
4822
|
+
}
|
|
4823
|
+
function findSessionForProject(projectName) {
|
|
4824
|
+
const sessions = listSessions();
|
|
4825
|
+
for (const s of sessions) {
|
|
4826
|
+
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
4827
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
4828
|
+
}
|
|
4829
|
+
return null;
|
|
4830
|
+
}
|
|
4831
|
+
function assertSessionScope(actionType, targetProject) {
|
|
4832
|
+
try {
|
|
4833
|
+
const currentProject = getProjectName();
|
|
4834
|
+
const exeSession = resolveExeSession();
|
|
4835
|
+
if (!exeSession) {
|
|
4836
|
+
return { allowed: true, reason: "no_session" };
|
|
4837
|
+
}
|
|
4838
|
+
if (currentProject === targetProject) {
|
|
4839
|
+
return {
|
|
4840
|
+
allowed: true,
|
|
4841
|
+
reason: "same_session",
|
|
4842
|
+
currentProject,
|
|
4843
|
+
targetProject
|
|
4844
|
+
};
|
|
4845
|
+
}
|
|
4846
|
+
process.stderr.write(
|
|
4847
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
4848
|
+
`
|
|
4849
|
+
);
|
|
4850
|
+
return {
|
|
4851
|
+
allowed: false,
|
|
4852
|
+
reason: "cross_session_denied",
|
|
4853
|
+
currentProject,
|
|
4854
|
+
targetProject,
|
|
4855
|
+
targetSession: findSessionForProject(targetProject)?.windowName
|
|
4856
|
+
};
|
|
4857
|
+
} catch {
|
|
4858
|
+
return { allowed: true, reason: "no_session" };
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
var init_session_scope = __esm({
|
|
4862
|
+
"src/lib/session-scope.ts"() {
|
|
4863
|
+
"use strict";
|
|
4864
|
+
init_session_registry();
|
|
4865
|
+
init_project_name();
|
|
4866
|
+
init_tmux_routing();
|
|
4867
|
+
init_employees();
|
|
4868
|
+
}
|
|
4869
|
+
});
|
|
4870
|
+
|
|
4758
4871
|
// src/lib/tasks-crud.ts
|
|
4759
4872
|
import crypto4 from "crypto";
|
|
4760
|
-
import
|
|
4873
|
+
import path15 from "path";
|
|
4761
4874
|
import os10 from "os";
|
|
4762
|
-
import { execSync as
|
|
4875
|
+
import { execSync as execSync6 } from "child_process";
|
|
4763
4876
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
4764
4877
|
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
4765
4878
|
async function writeCheckpoint(input) {
|
|
@@ -4881,9 +4994,24 @@ async function createTaskCore(input) {
|
|
|
4881
4994
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4882
4995
|
const slug = slugify(input.title);
|
|
4883
4996
|
let earlySessionScope = null;
|
|
4997
|
+
let scopeMismatchWarning;
|
|
4884
4998
|
try {
|
|
4885
4999
|
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
4886
|
-
|
|
5000
|
+
const resolved = resolveExeSession2();
|
|
5001
|
+
if (resolved && input.projectName) {
|
|
5002
|
+
const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
|
|
5003
|
+
const sessionProject = getSessionProject2(resolved);
|
|
5004
|
+
if (sessionProject && sessionProject !== input.projectName) {
|
|
5005
|
+
scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
|
|
5006
|
+
process.stderr.write(`[create_task] ${scopeMismatchWarning}
|
|
5007
|
+
`);
|
|
5008
|
+
earlySessionScope = null;
|
|
5009
|
+
} else {
|
|
5010
|
+
earlySessionScope = resolved;
|
|
5011
|
+
}
|
|
5012
|
+
} else {
|
|
5013
|
+
earlySessionScope = resolved;
|
|
5014
|
+
}
|
|
4887
5015
|
} catch {
|
|
4888
5016
|
}
|
|
4889
5017
|
const scope = earlySessionScope ?? "default";
|
|
@@ -4934,10 +5062,14 @@ async function createTaskCore(input) {
|
|
|
4934
5062
|
${laneWarning}` : laneWarning;
|
|
4935
5063
|
}
|
|
4936
5064
|
}
|
|
5065
|
+
if (scopeMismatchWarning) {
|
|
5066
|
+
warning = warning ? `${warning}
|
|
5067
|
+
${scopeMismatchWarning}` : scopeMismatchWarning;
|
|
5068
|
+
}
|
|
4937
5069
|
if (input.baseDir) {
|
|
4938
5070
|
try {
|
|
4939
|
-
await mkdir4(
|
|
4940
|
-
await mkdir4(
|
|
5071
|
+
await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
5072
|
+
await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
4941
5073
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
4942
5074
|
await ensureGitignoreExe(input.baseDir);
|
|
4943
5075
|
} catch {
|
|
@@ -4973,9 +5105,9 @@ ${laneWarning}` : laneWarning;
|
|
|
4973
5105
|
});
|
|
4974
5106
|
if (input.baseDir) {
|
|
4975
5107
|
try {
|
|
4976
|
-
const EXE_OS_DIR =
|
|
4977
|
-
const mdPath =
|
|
4978
|
-
const mdDir =
|
|
5108
|
+
const EXE_OS_DIR = path15.join(os10.homedir(), ".exe-os");
|
|
5109
|
+
const mdPath = path15.join(EXE_OS_DIR, taskFile);
|
|
5110
|
+
const mdDir = path15.dirname(mdPath);
|
|
4979
5111
|
if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
|
|
4980
5112
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
4981
5113
|
const mdContent = `# ${input.title}
|
|
@@ -5080,14 +5212,14 @@ function isTmuxSessionAlive(identifier) {
|
|
|
5080
5212
|
if (!identifier || identifier === "unknown") return true;
|
|
5081
5213
|
try {
|
|
5082
5214
|
if (identifier.startsWith("%")) {
|
|
5083
|
-
const output =
|
|
5215
|
+
const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
|
|
5084
5216
|
timeout: 2e3,
|
|
5085
5217
|
encoding: "utf8",
|
|
5086
5218
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5087
5219
|
});
|
|
5088
5220
|
return output.split("\n").some((l) => l.trim() === identifier);
|
|
5089
5221
|
} else {
|
|
5090
|
-
|
|
5222
|
+
execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
5091
5223
|
timeout: 2e3,
|
|
5092
5224
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5093
5225
|
});
|
|
@@ -5096,7 +5228,7 @@ function isTmuxSessionAlive(identifier) {
|
|
|
5096
5228
|
} catch {
|
|
5097
5229
|
if (identifier.startsWith("%")) return true;
|
|
5098
5230
|
try {
|
|
5099
|
-
|
|
5231
|
+
execSync6("tmux list-sessions", {
|
|
5100
5232
|
timeout: 2e3,
|
|
5101
5233
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5102
5234
|
});
|
|
@@ -5111,12 +5243,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
5111
5243
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
5112
5244
|
try {
|
|
5113
5245
|
const since = new Date(taskCreatedAt).toISOString();
|
|
5114
|
-
const branch =
|
|
5246
|
+
const branch = execSync6(
|
|
5115
5247
|
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
5116
5248
|
{ encoding: "utf8", timeout: 3e3 }
|
|
5117
5249
|
).trim();
|
|
5118
5250
|
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
5119
|
-
const commitCount =
|
|
5251
|
+
const commitCount = execSync6(
|
|
5120
5252
|
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
5121
5253
|
{ encoding: "utf8", timeout: 5e3 }
|
|
5122
5254
|
).trim();
|
|
@@ -5276,7 +5408,7 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
5276
5408
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
5277
5409
|
}
|
|
5278
5410
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
5279
|
-
const archPath =
|
|
5411
|
+
const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
5280
5412
|
try {
|
|
5281
5413
|
if (existsSync14(archPath)) return;
|
|
5282
5414
|
const template = [
|
|
@@ -5311,7 +5443,7 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
5311
5443
|
}
|
|
5312
5444
|
}
|
|
5313
5445
|
async function ensureGitignoreExe(baseDir) {
|
|
5314
|
-
const gitignorePath =
|
|
5446
|
+
const gitignorePath = path15.join(baseDir, ".gitignore");
|
|
5315
5447
|
try {
|
|
5316
5448
|
if (existsSync14(gitignorePath)) {
|
|
5317
5449
|
const content = readFileSync11(gitignorePath, "utf-8");
|
|
@@ -5345,7 +5477,7 @@ var init_tasks_crud = __esm({
|
|
|
5345
5477
|
});
|
|
5346
5478
|
|
|
5347
5479
|
// src/lib/tasks-review.ts
|
|
5348
|
-
import
|
|
5480
|
+
import path16 from "path";
|
|
5349
5481
|
import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
5350
5482
|
async function countPendingReviews(sessionScope) {
|
|
5351
5483
|
const client = getClient();
|
|
@@ -5511,11 +5643,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
5511
5643
|
);
|
|
5512
5644
|
}
|
|
5513
5645
|
try {
|
|
5514
|
-
const cacheDir =
|
|
5646
|
+
const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
|
|
5515
5647
|
if (existsSync15(cacheDir)) {
|
|
5516
5648
|
for (const f of readdirSync3(cacheDir)) {
|
|
5517
5649
|
if (f.startsWith("review-notified-")) {
|
|
5518
|
-
unlinkSync4(
|
|
5650
|
+
unlinkSync4(path16.join(cacheDir, f));
|
|
5519
5651
|
}
|
|
5520
5652
|
}
|
|
5521
5653
|
}
|
|
@@ -5537,7 +5669,7 @@ var init_tasks_review = __esm({
|
|
|
5537
5669
|
});
|
|
5538
5670
|
|
|
5539
5671
|
// src/lib/tasks-chain.ts
|
|
5540
|
-
import
|
|
5672
|
+
import path17 from "path";
|
|
5541
5673
|
import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
5542
5674
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
5543
5675
|
const client = getClient();
|
|
@@ -5554,7 +5686,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5554
5686
|
});
|
|
5555
5687
|
for (const ur of unblockedRows.rows) {
|
|
5556
5688
|
try {
|
|
5557
|
-
const ubFile =
|
|
5689
|
+
const ubFile = path17.join(baseDir, String(ur.task_file));
|
|
5558
5690
|
let ubContent = await readFile4(ubFile, "utf-8");
|
|
5559
5691
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
5560
5692
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -5621,119 +5753,6 @@ var init_tasks_chain = __esm({
|
|
|
5621
5753
|
}
|
|
5622
5754
|
});
|
|
5623
5755
|
|
|
5624
|
-
// src/lib/project-name.ts
|
|
5625
|
-
var project_name_exports = {};
|
|
5626
|
-
__export(project_name_exports, {
|
|
5627
|
-
_resetCache: () => _resetCache,
|
|
5628
|
-
getProjectName: () => getProjectName
|
|
5629
|
-
});
|
|
5630
|
-
import { execSync as execSync6 } from "child_process";
|
|
5631
|
-
import path17 from "path";
|
|
5632
|
-
function getProjectName(cwd) {
|
|
5633
|
-
const dir = cwd ?? process.cwd();
|
|
5634
|
-
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
5635
|
-
try {
|
|
5636
|
-
let repoRoot;
|
|
5637
|
-
try {
|
|
5638
|
-
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
5639
|
-
cwd: dir,
|
|
5640
|
-
encoding: "utf8",
|
|
5641
|
-
timeout: 2e3,
|
|
5642
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5643
|
-
}).trim();
|
|
5644
|
-
repoRoot = path17.dirname(gitCommonDir);
|
|
5645
|
-
} catch {
|
|
5646
|
-
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
5647
|
-
cwd: dir,
|
|
5648
|
-
encoding: "utf8",
|
|
5649
|
-
timeout: 2e3,
|
|
5650
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5651
|
-
}).trim();
|
|
5652
|
-
}
|
|
5653
|
-
_cached2 = path17.basename(repoRoot);
|
|
5654
|
-
_cachedCwd = dir;
|
|
5655
|
-
return _cached2;
|
|
5656
|
-
} catch {
|
|
5657
|
-
_cached2 = path17.basename(dir);
|
|
5658
|
-
_cachedCwd = dir;
|
|
5659
|
-
return _cached2;
|
|
5660
|
-
}
|
|
5661
|
-
}
|
|
5662
|
-
function _resetCache() {
|
|
5663
|
-
_cached2 = null;
|
|
5664
|
-
_cachedCwd = null;
|
|
5665
|
-
}
|
|
5666
|
-
var _cached2, _cachedCwd;
|
|
5667
|
-
var init_project_name = __esm({
|
|
5668
|
-
"src/lib/project-name.ts"() {
|
|
5669
|
-
"use strict";
|
|
5670
|
-
_cached2 = null;
|
|
5671
|
-
_cachedCwd = null;
|
|
5672
|
-
}
|
|
5673
|
-
});
|
|
5674
|
-
|
|
5675
|
-
// src/lib/session-scope.ts
|
|
5676
|
-
var session_scope_exports = {};
|
|
5677
|
-
__export(session_scope_exports, {
|
|
5678
|
-
assertSessionScope: () => assertSessionScope,
|
|
5679
|
-
findSessionForProject: () => findSessionForProject,
|
|
5680
|
-
getSessionProject: () => getSessionProject
|
|
5681
|
-
});
|
|
5682
|
-
function getSessionProject(sessionName) {
|
|
5683
|
-
const sessions = listSessions();
|
|
5684
|
-
const entry = sessions.find((s) => s.windowName === sessionName);
|
|
5685
|
-
if (!entry) return null;
|
|
5686
|
-
const parts = entry.projectDir.split("/").filter(Boolean);
|
|
5687
|
-
return parts[parts.length - 1] ?? null;
|
|
5688
|
-
}
|
|
5689
|
-
function findSessionForProject(projectName) {
|
|
5690
|
-
const sessions = listSessions();
|
|
5691
|
-
for (const s of sessions) {
|
|
5692
|
-
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
5693
|
-
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
5694
|
-
}
|
|
5695
|
-
return null;
|
|
5696
|
-
}
|
|
5697
|
-
function assertSessionScope(actionType, targetProject) {
|
|
5698
|
-
try {
|
|
5699
|
-
const currentProject = getProjectName();
|
|
5700
|
-
const exeSession = resolveExeSession();
|
|
5701
|
-
if (!exeSession) {
|
|
5702
|
-
return { allowed: true, reason: "no_session" };
|
|
5703
|
-
}
|
|
5704
|
-
if (currentProject === targetProject) {
|
|
5705
|
-
return {
|
|
5706
|
-
allowed: true,
|
|
5707
|
-
reason: "same_session",
|
|
5708
|
-
currentProject,
|
|
5709
|
-
targetProject
|
|
5710
|
-
};
|
|
5711
|
-
}
|
|
5712
|
-
process.stderr.write(
|
|
5713
|
-
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
5714
|
-
`
|
|
5715
|
-
);
|
|
5716
|
-
return {
|
|
5717
|
-
allowed: false,
|
|
5718
|
-
reason: "cross_session_denied",
|
|
5719
|
-
currentProject,
|
|
5720
|
-
targetProject,
|
|
5721
|
-
targetSession: findSessionForProject(targetProject)?.windowName
|
|
5722
|
-
};
|
|
5723
|
-
} catch {
|
|
5724
|
-
return { allowed: true, reason: "no_session" };
|
|
5725
|
-
}
|
|
5726
|
-
}
|
|
5727
|
-
var init_session_scope = __esm({
|
|
5728
|
-
"src/lib/session-scope.ts"() {
|
|
5729
|
-
"use strict";
|
|
5730
|
-
init_session_registry();
|
|
5731
|
-
init_project_name();
|
|
5732
|
-
init_tmux_routing();
|
|
5733
|
-
init_employees();
|
|
5734
|
-
}
|
|
5735
|
-
});
|
|
5736
|
-
|
|
5737
5756
|
// src/lib/tasks-notify.ts
|
|
5738
5757
|
async function dispatchTaskToEmployee(input) {
|
|
5739
5758
|
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|