jinn-cli 0.8.0 → 0.9.0
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/package.json +2 -2
- package/dist/src/connectors/discord/index.d.ts +6 -0
- package/dist/src/connectors/discord/index.d.ts.map +1 -1
- package/dist/src/connectors/discord/index.js +9 -3
- package/dist/src/connectors/discord/index.js.map +1 -1
- package/dist/src/connectors/discord/threads.d.ts +1 -1
- package/dist/src/connectors/discord/threads.d.ts.map +1 -1
- package/dist/src/connectors/discord/threads.js +4 -4
- package/dist/src/connectors/discord/threads.js.map +1 -1
- package/dist/src/gateway/__tests__/org-hierarchy.test.d.ts +2 -0
- package/dist/src/gateway/__tests__/org-hierarchy.test.d.ts.map +1 -0
- package/dist/src/gateway/__tests__/org-hierarchy.test.js +161 -0
- package/dist/src/gateway/__tests__/org-hierarchy.test.js.map +1 -0
- package/dist/src/gateway/__tests__/services.test.js +139 -151
- package/dist/src/gateway/__tests__/services.test.js.map +1 -1
- package/dist/src/gateway/api.d.ts +5 -0
- package/dist/src/gateway/api.d.ts.map +1 -1
- package/dist/src/gateway/api.js +242 -64
- package/dist/src/gateway/api.js.map +1 -1
- package/dist/src/gateway/org-hierarchy.d.ts +5 -0
- package/dist/src/gateway/org-hierarchy.d.ts.map +1 -0
- package/dist/src/gateway/org-hierarchy.js +174 -0
- package/dist/src/gateway/org-hierarchy.js.map +1 -0
- package/dist/src/gateway/org.d.ts.map +1 -1
- package/dist/src/gateway/org.js +6 -0
- package/dist/src/gateway/org.js.map +1 -1
- package/dist/src/gateway/server.d.ts.map +1 -1
- package/dist/src/gateway/server.js +235 -5
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/services.d.ts +20 -9
- package/dist/src/gateway/services.d.ts.map +1 -1
- package/dist/src/gateway/services.js +73 -34
- package/dist/src/gateway/services.js.map +1 -1
- package/dist/src/sessions/context.d.ts +1 -0
- package/dist/src/sessions/context.d.ts.map +1 -1
- package/dist/src/sessions/context.js +113 -5
- package/dist/src/sessions/context.js.map +1 -1
- package/dist/src/sessions/fork.d.ts +30 -0
- package/dist/src/sessions/fork.d.ts.map +1 -0
- package/dist/src/sessions/fork.js +178 -0
- package/dist/src/sessions/fork.js.map +1 -0
- package/dist/src/sessions/manager.d.ts.map +1 -1
- package/dist/src/sessions/manager.js +8 -0
- package/dist/src/sessions/manager.js.map +1 -1
- package/dist/src/sessions/registry.d.ts +8 -0
- package/dist/src/sessions/registry.d.ts.map +1 -1
- package/dist/src/sessions/registry.js +36 -0
- package/dist/src/sessions/registry.js.map +1 -1
- package/dist/src/shared/types.d.ts +66 -0
- package/dist/src/shared/types.d.ts.map +1 -1
- package/dist/web/404.html +1 -1
- package/dist/web/_next/static/chunks/{155-d60b5aa6e01f7738.js → 155-655a087df45da990.js} +1 -1
- package/dist/web/_next/static/chunks/192-c0150e3d227be735.js +1 -0
- package/dist/web/_next/static/chunks/579-09eb1d7ab35333fc.js +1 -0
- package/dist/web/_next/static/chunks/943.7dec3c35287ad545.js +1 -0
- package/dist/web/_next/static/chunks/app/chat/page-4ba0008df3b5d032.js +1 -0
- package/dist/web/_next/static/chunks/app/cron/page-648f892629b4af65.js +1 -0
- package/dist/web/_next/static/chunks/app/kanban/{page-0aa74ece5109e58f.js → page-5e00ac0363936a53.js} +1 -1
- package/dist/web/_next/static/chunks/app/org/page-0f7617805f6f6eb7.js +1 -0
- package/dist/web/_next/static/chunks/app/settings/page-5588e1b2bb8a5196.js +1 -0
- package/dist/web/_next/static/chunks/{webpack-2e375360ad2078fe.js → webpack-21588284a59cb80f.js} +1 -1
- package/dist/web/_next/static/css/334114aa3839160f.css +1 -0
- package/dist/web/chat.html +1 -1
- package/dist/web/chat.txt +4 -4
- package/dist/web/cron.html +1 -1
- package/dist/web/cron.txt +4 -4
- package/dist/web/index.html +1 -1
- package/dist/web/index.txt +4 -4
- package/dist/web/kanban.html +1 -1
- package/dist/web/kanban.txt +4 -4
- package/dist/web/logs.html +2 -2
- package/dist/web/logs.txt +4 -4
- package/dist/web/org.html +1 -1
- package/dist/web/org.txt +4 -4
- package/dist/web/sessions.html +1 -1
- package/dist/web/sessions.txt +3 -3
- package/dist/web/settings.html +1 -1
- package/dist/web/settings.txt +4 -4
- package/dist/web/skills.html +1 -1
- package/dist/web/skills.txt +4 -4
- package/package.json +2 -2
- package/template/AGENTS.md +132 -31
- package/template/CLAUDE.md +186 -24
- package/template/skills/management/SKILL.md +32 -9
- package/dist/web/_next/static/chunks/192-8281220a22c7a155.js +0 -1
- package/dist/web/_next/static/chunks/379-11f5e4203461fd6a.js +0 -1
- package/dist/web/_next/static/chunks/943.c30215b2dbec402b.js +0 -1
- package/dist/web/_next/static/chunks/app/chat/page-3a532ea2d0f8b961.js +0 -1
- package/dist/web/_next/static/chunks/app/cron/page-9787c557594dc688.js +0 -1
- package/dist/web/_next/static/chunks/app/org/page-99d1b67b8f17e4d5.js +0 -1
- package/dist/web/_next/static/chunks/app/settings/page-c2b014fb0706aa88.js +0 -1
- package/dist/web/_next/static/css/214f7107c416b9a3.css +0 -1
- /package/dist/web/_next/static/{yhgZ2UX_j_Aier68GaRfB → VBCQXj1bVyMDiXQTiHQ-p}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{yhgZ2UX_j_Aier68GaRfB → VBCQXj1bVyMDiXQTiHQ-p}/_ssgManifest.js +0 -0
package/dist/src/gateway/api.js
CHANGED
|
@@ -5,7 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
import yaml from "js-yaml";
|
|
6
6
|
import { isInterruptibleEngine } from "../shared/types.js";
|
|
7
7
|
import { buildContext } from "../sessions/context.js";
|
|
8
|
-
import { initDb, listSessions, getSession, createSession, updateSession, deleteSession, deleteSessions, insertMessage, getMessages, enqueueQueueItem, cancelQueueItem, getQueueItems, cancelAllPendingQueueItems, listAllPendingQueueItems, getFile, } from "../sessions/registry.js";
|
|
8
|
+
import { initDb, listSessions, getSession, createSession, updateSession, deleteSession, deleteSessions, duplicateSession, insertMessage, getMessages, enqueueQueueItem, cancelQueueItem, getQueueItems, cancelAllPendingQueueItems, listAllPendingQueueItems, getFile, } from "../sessions/registry.js";
|
|
9
|
+
import { forkEngineSession } from "../sessions/fork.js";
|
|
9
10
|
import { CONFIG_PATH, CRON_RUNS, ORG_DIR, SKILLS_DIR, LOGS_DIR, TMP_DIR, FILES_DIR, } from "../shared/paths.js";
|
|
10
11
|
import { logger } from "../shared/logger.js";
|
|
11
12
|
import { getSttStatus, downloadModel, transcribe as sttTranscribe, resolveLanguages, WHISPER_LANGUAGES } from "../stt/stt.js";
|
|
@@ -185,12 +186,34 @@ function badRequest(res, message) {
|
|
|
185
186
|
function serverError(res, message) {
|
|
186
187
|
json(res, { error: message }, 500);
|
|
187
188
|
}
|
|
189
|
+
const SANITIZED_KEYS = new Set(["token", "botToken", "signingSecret", "appToken"]);
|
|
188
190
|
function deepMerge(target, source) {
|
|
189
191
|
const result = { ...target };
|
|
190
192
|
for (const key of Object.keys(source)) {
|
|
191
193
|
const sv = source[key];
|
|
192
194
|
const tv = target[key];
|
|
193
|
-
|
|
195
|
+
// Skip sanitized secret placeholders — keep original value
|
|
196
|
+
if (SANITIZED_KEYS.has(key) && sv === "***")
|
|
197
|
+
continue;
|
|
198
|
+
if (Array.isArray(sv)) {
|
|
199
|
+
// For arrays (e.g. instances), preserve secrets from matching items
|
|
200
|
+
if (Array.isArray(tv)) {
|
|
201
|
+
result[key] = sv.map((item) => {
|
|
202
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
203
|
+
const srcItem = item;
|
|
204
|
+
// Find matching target item by id
|
|
205
|
+
const matchTarget = tv.find((t) => t && typeof t === "object" && t.id === srcItem.id);
|
|
206
|
+
if (matchTarget)
|
|
207
|
+
return deepMerge(matchTarget, srcItem);
|
|
208
|
+
}
|
|
209
|
+
return item;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
result[key] = sv;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else if (sv && typeof sv === "object" && !Array.isArray(sv) && tv && typeof tv === "object" && !Array.isArray(tv)) {
|
|
194
217
|
result[key] = deepMerge(tv, sv);
|
|
195
218
|
}
|
|
196
219
|
else {
|
|
@@ -395,6 +418,45 @@ export async function handleApiRequest(req, res, context) {
|
|
|
395
418
|
context.emit("session:updated", { sessionId: params.id });
|
|
396
419
|
return json(res, { status: "reset", sessionId: params.id });
|
|
397
420
|
}
|
|
421
|
+
// POST /api/sessions/:id/duplicate — duplicate a session (snapshot fork)
|
|
422
|
+
params = matchRoute("/api/sessions/:id/duplicate", pathname);
|
|
423
|
+
if (method === "POST" && params) {
|
|
424
|
+
const source = getSession(params.id);
|
|
425
|
+
if (!source)
|
|
426
|
+
return notFound(res);
|
|
427
|
+
if (!source.engineSessionId) {
|
|
428
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
429
|
+
res.end(JSON.stringify({ error: "Session has no engine session ID — cannot duplicate" }));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
let newSessionId = null;
|
|
433
|
+
try {
|
|
434
|
+
// 1. Duplicate session + messages in the registry
|
|
435
|
+
const { session: newSession, messageCount } = duplicateSession(params.id);
|
|
436
|
+
newSessionId = newSession.id;
|
|
437
|
+
// 2. Fork the engine session (Claude/Codex/Gemini)
|
|
438
|
+
const forkResult = forkEngineSession(source.engine, source.engineSessionId, JINN_HOME);
|
|
439
|
+
// 3. Store the new engine session ID
|
|
440
|
+
updateSession(newSession.id, { engineSessionId: forkResult.engineSessionId });
|
|
441
|
+
const result = getSession(newSession.id);
|
|
442
|
+
logger.info(`Session duplicated: ${params.id} → ${newSession.id} (engine: ${forkResult.engineSessionId}, ${messageCount} messages)`);
|
|
443
|
+
context.emit("session:created", { sessionId: newSession.id });
|
|
444
|
+
return json(res, serializeSession(result, context));
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
// Clean up orphaned session if the engine fork failed after DB insert
|
|
448
|
+
if (newSessionId) {
|
|
449
|
+
try {
|
|
450
|
+
deleteSession(newSessionId);
|
|
451
|
+
}
|
|
452
|
+
catch { /* best effort */ }
|
|
453
|
+
}
|
|
454
|
+
logger.error(`Failed to duplicate session ${params.id}: ${err.message}`);
|
|
455
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
456
|
+
res.end(JSON.stringify({ error: `Duplicate failed: ${err.message}` }));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
398
460
|
// DELETE /api/sessions/:id/queue/:itemId — cancel specific item
|
|
399
461
|
const queueItemParams = matchRoute("/api/sessions/:id/queue/:itemId", pathname);
|
|
400
462
|
if (method === "DELETE" && queueItemParams) {
|
|
@@ -757,62 +819,55 @@ export async function handleApiRequest(req, res, context) {
|
|
|
757
819
|
// GET /api/org
|
|
758
820
|
if (method === "GET" && pathname === "/api/org") {
|
|
759
821
|
if (!fs.existsSync(ORG_DIR))
|
|
760
|
-
return json(res, { departments: [], employees: [] });
|
|
822
|
+
return json(res, { departments: [], employees: [], hierarchy: { root: null, sorted: [], warnings: [] } });
|
|
761
823
|
const entries = fs.readdirSync(ORG_DIR, { withFileTypes: true });
|
|
762
824
|
const departments = entries
|
|
763
825
|
.filter((e) => e.isDirectory())
|
|
764
826
|
.map((e) => e.name);
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
return json(res, { departments, employees });
|
|
827
|
+
const { scanOrg } = await import("./org.js");
|
|
828
|
+
const { resolveOrgHierarchy } = await import("./org-hierarchy.js");
|
|
829
|
+
const orgRegistry = scanOrg();
|
|
830
|
+
const hierarchy = resolveOrgHierarchy(orgRegistry);
|
|
831
|
+
const employees = hierarchy.sorted.map((name) => {
|
|
832
|
+
const node = hierarchy.nodes[name];
|
|
833
|
+
const emp = node.employee;
|
|
834
|
+
const { persona, ...rest } = emp;
|
|
835
|
+
return {
|
|
836
|
+
...rest,
|
|
837
|
+
parentName: node.parentName,
|
|
838
|
+
directReports: node.directReports,
|
|
839
|
+
depth: node.depth,
|
|
840
|
+
chain: node.chain,
|
|
841
|
+
};
|
|
842
|
+
});
|
|
843
|
+
return json(res, {
|
|
844
|
+
departments,
|
|
845
|
+
employees,
|
|
846
|
+
hierarchy: {
|
|
847
|
+
root: hierarchy.root,
|
|
848
|
+
sorted: hierarchy.sorted,
|
|
849
|
+
warnings: hierarchy.warnings,
|
|
850
|
+
},
|
|
851
|
+
});
|
|
793
852
|
}
|
|
794
853
|
// GET /api/org/employees/:name
|
|
795
854
|
params = matchRoute("/api/org/employees/:name", pathname);
|
|
796
855
|
if (method === "GET" && params) {
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
];
|
|
803
|
-
// Also search inside each department directory
|
|
804
|
-
if (fs.existsSync(ORG_DIR)) {
|
|
805
|
-
const dirs = fs.readdirSync(ORG_DIR, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
806
|
-
for (const dir of dirs) {
|
|
807
|
-
candidates.push(path.join(ORG_DIR, dir.name, `${params.name}.yaml`));
|
|
808
|
-
candidates.push(path.join(ORG_DIR, dir.name, `${params.name}.yml`));
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
const filePath = candidates.find((c) => fs.existsSync(c));
|
|
812
|
-
if (!filePath)
|
|
856
|
+
const { scanOrg } = await import("./org.js");
|
|
857
|
+
const { resolveOrgHierarchy } = await import("./org-hierarchy.js");
|
|
858
|
+
const orgRegistry = scanOrg();
|
|
859
|
+
const emp = orgRegistry.get(params.name);
|
|
860
|
+
if (!emp)
|
|
813
861
|
return notFound(res);
|
|
814
|
-
const
|
|
815
|
-
|
|
862
|
+
const hierarchy = resolveOrgHierarchy(orgRegistry);
|
|
863
|
+
const node = hierarchy.nodes[params.name];
|
|
864
|
+
return json(res, {
|
|
865
|
+
...emp,
|
|
866
|
+
parentName: node?.parentName ?? null,
|
|
867
|
+
directReports: node?.directReports ?? [],
|
|
868
|
+
depth: node?.depth ?? 0,
|
|
869
|
+
chain: node?.chain ?? [params.name],
|
|
870
|
+
});
|
|
816
871
|
}
|
|
817
872
|
// PATCH /api/org/employees/:name — update employee fields (currently only alwaysNotify)
|
|
818
873
|
params = matchRoute("/api/org/employees/:name", pathname);
|
|
@@ -830,6 +885,88 @@ export async function handleApiRequest(req, res, context) {
|
|
|
830
885
|
context.emit("org:updated", { employee: params.name });
|
|
831
886
|
return json(res, { status: "ok" });
|
|
832
887
|
}
|
|
888
|
+
// GET /api/org/services — list all cross-department services
|
|
889
|
+
if (method === "GET" && pathname === "/api/org/services") {
|
|
890
|
+
const { scanOrg } = await import("./org.js");
|
|
891
|
+
const { buildServiceRegistry } = await import("./services.js");
|
|
892
|
+
const orgRegistry = scanOrg();
|
|
893
|
+
const services = buildServiceRegistry(orgRegistry);
|
|
894
|
+
const result = Array.from(services.values()).map((entry) => ({
|
|
895
|
+
name: entry.declaration.name,
|
|
896
|
+
description: entry.declaration.description,
|
|
897
|
+
provider: {
|
|
898
|
+
name: entry.provider.name,
|
|
899
|
+
displayName: entry.provider.displayName,
|
|
900
|
+
department: entry.provider.department,
|
|
901
|
+
rank: entry.provider.rank,
|
|
902
|
+
},
|
|
903
|
+
}));
|
|
904
|
+
return json(res, { services: result });
|
|
905
|
+
}
|
|
906
|
+
// POST /api/org/cross-request — route a service request to the provider
|
|
907
|
+
if (method === "POST" && pathname === "/api/org/cross-request") {
|
|
908
|
+
const parsed = await readJsonBody(req, res);
|
|
909
|
+
if (!parsed.ok)
|
|
910
|
+
return;
|
|
911
|
+
const body = parsed.body;
|
|
912
|
+
const { fromEmployee, service, prompt, parentSessionId } = body;
|
|
913
|
+
if (!fromEmployee || !service || !prompt) {
|
|
914
|
+
return badRequest(res, "Missing required fields: fromEmployee, service, prompt");
|
|
915
|
+
}
|
|
916
|
+
const { scanOrg } = await import("./org.js");
|
|
917
|
+
const { resolveOrgHierarchy } = await import("./org-hierarchy.js");
|
|
918
|
+
const { buildServiceRegistry, buildRoutePath, resolveManagerChain } = await import("./services.js");
|
|
919
|
+
const orgRegistry = scanOrg();
|
|
920
|
+
const requester = orgRegistry.get(fromEmployee);
|
|
921
|
+
if (!requester)
|
|
922
|
+
return notFound(res);
|
|
923
|
+
const services = buildServiceRegistry(orgRegistry);
|
|
924
|
+
const entry = services.get(service);
|
|
925
|
+
if (!entry) {
|
|
926
|
+
return json(res, { error: `Service "${service}" not found` }, 404);
|
|
927
|
+
}
|
|
928
|
+
const hierarchy = resolveOrgHierarchy(orgRegistry);
|
|
929
|
+
const route = buildRoutePath(fromEmployee, entry.provider.name, hierarchy);
|
|
930
|
+
const managers = resolveManagerChain(route, hierarchy);
|
|
931
|
+
const crossBrief = `## Cross-service request
|
|
932
|
+
|
|
933
|
+
**From**: ${requester.displayName} (${requester.department})
|
|
934
|
+
**Service**: ${service} — ${entry.declaration.description}
|
|
935
|
+
|
|
936
|
+
### Request
|
|
937
|
+
${prompt}
|
|
938
|
+
|
|
939
|
+
---
|
|
940
|
+
Handle this as a priority request from a colleague.`;
|
|
941
|
+
const config = context.getConfig();
|
|
942
|
+
const session = createSession({
|
|
943
|
+
engine: entry.provider.engine || config.engines.default,
|
|
944
|
+
model: entry.provider.model || undefined,
|
|
945
|
+
source: "cross-request",
|
|
946
|
+
sourceRef: `cross:${fromEmployee}:${service}`,
|
|
947
|
+
connector: "web",
|
|
948
|
+
sessionKey: `cross:${Date.now()}`,
|
|
949
|
+
replyContext: { source: "cross-request" },
|
|
950
|
+
employee: entry.provider.name,
|
|
951
|
+
parentSessionId: parentSessionId || undefined,
|
|
952
|
+
prompt: crossBrief,
|
|
953
|
+
portalName: config.portal?.portalName,
|
|
954
|
+
title: `Cross-request: ${fromEmployee} → ${service}`,
|
|
955
|
+
});
|
|
956
|
+
insertMessage(session.id, "user", crossBrief);
|
|
957
|
+
logger.info(`Cross-request session created: ${session.id} (${fromEmployee} → ${service} → ${entry.provider.name})`);
|
|
958
|
+
return json(res, {
|
|
959
|
+
sessionId: session.id,
|
|
960
|
+
provider: {
|
|
961
|
+
name: entry.provider.name,
|
|
962
|
+
displayName: entry.provider.displayName,
|
|
963
|
+
department: entry.provider.department,
|
|
964
|
+
},
|
|
965
|
+
route,
|
|
966
|
+
managers: managers.map((m) => m.employee.name),
|
|
967
|
+
service,
|
|
968
|
+
}, 201);
|
|
969
|
+
}
|
|
833
970
|
// GET /api/org/departments/:name/board
|
|
834
971
|
params = matchRoute("/api/org/departments/:name/board", pathname);
|
|
835
972
|
if (method === "GET" && params) {
|
|
@@ -989,18 +1126,34 @@ export async function handleApiRequest(req, res, context) {
|
|
|
989
1126
|
if (method === "GET" && pathname === "/api/config") {
|
|
990
1127
|
const config = context.getConfig();
|
|
991
1128
|
// Sanitize: remove any secrets/tokens from connectors
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
{
|
|
1129
|
+
const rawConnectors = config.connectors || {};
|
|
1130
|
+
const sanitizedConnectors = {};
|
|
1131
|
+
for (const [k, v] of Object.entries(rawConnectors)) {
|
|
1132
|
+
if (k === "instances" && Array.isArray(v)) {
|
|
1133
|
+
sanitizedConnectors.instances = v.map((inst) => ({
|
|
1134
|
+
...inst,
|
|
1135
|
+
token: inst?.token ? "***" : undefined,
|
|
1136
|
+
signingSecret: inst?.signingSecret ? "***" : undefined,
|
|
1137
|
+
botToken: inst?.botToken ? "***" : undefined,
|
|
1138
|
+
appToken: inst?.appToken ? "***" : undefined,
|
|
1139
|
+
}));
|
|
1140
|
+
}
|
|
1141
|
+
else if (v && typeof v === "object") {
|
|
1142
|
+
sanitizedConnectors[k] = {
|
|
997
1143
|
...v,
|
|
998
1144
|
token: v?.token ? "***" : undefined,
|
|
999
1145
|
signingSecret: v?.signingSecret ? "***" : undefined,
|
|
1000
1146
|
botToken: v?.botToken ? "***" : undefined,
|
|
1001
1147
|
appToken: v?.appToken ? "***" : undefined,
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
sanitizedConnectors[k] = v;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const sanitized = {
|
|
1155
|
+
...config,
|
|
1156
|
+
connectors: sanitizedConnectors,
|
|
1004
1157
|
};
|
|
1005
1158
|
return json(res, sanitized);
|
|
1006
1159
|
}
|
|
@@ -1080,9 +1233,26 @@ export async function handleApiRequest(req, res, context) {
|
|
|
1080
1233
|
const lines = allLines.slice(-n);
|
|
1081
1234
|
return json(res, { lines });
|
|
1082
1235
|
}
|
|
1083
|
-
// POST /api/connectors/
|
|
1084
|
-
if (method === "POST" && pathname === "/api/connectors/
|
|
1085
|
-
|
|
1236
|
+
// POST /api/connectors/reload — stop all instance connectors and restart from config
|
|
1237
|
+
if (method === "POST" && pathname === "/api/connectors/reload") {
|
|
1238
|
+
if (!context.reloadConnectorInstances) {
|
|
1239
|
+
return json(res, { error: "Connector reload not available" }, 501);
|
|
1240
|
+
}
|
|
1241
|
+
try {
|
|
1242
|
+
const result = await context.reloadConnectorInstances();
|
|
1243
|
+
context.emit("connectors:reloaded", result);
|
|
1244
|
+
return json(res, result);
|
|
1245
|
+
}
|
|
1246
|
+
catch (err) {
|
|
1247
|
+
return json(res, { error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
// POST /api/connectors/:id/incoming — receive proxied Discord messages from primary instance
|
|
1251
|
+
// Supports both the legacy /api/connectors/discord/incoming and named instance ids
|
|
1252
|
+
params = matchRoute("/api/connectors/:id/incoming", pathname);
|
|
1253
|
+
if (method === "POST" && params && params.id) {
|
|
1254
|
+
// Try the exact instance id first, then fall back to "discord" for the legacy path
|
|
1255
|
+
const connector = context.connectors.get(params.id) ?? (params.id === "discord" ? context.connectors.get("discord") : undefined);
|
|
1086
1256
|
if (!connector)
|
|
1087
1257
|
return notFound(res);
|
|
1088
1258
|
if (!("deliverMessage" in connector)) {
|
|
@@ -1108,7 +1278,7 @@ export async function handleApiRequest(req, res, context) {
|
|
|
1108
1278
|
return att;
|
|
1109
1279
|
}));
|
|
1110
1280
|
const incomingMsg = {
|
|
1111
|
-
connector:
|
|
1281
|
+
connector: params.id,
|
|
1112
1282
|
source: "discord",
|
|
1113
1283
|
sessionKey: body.sessionKey,
|
|
1114
1284
|
channel: body.channel,
|
|
@@ -1126,9 +1296,11 @@ export async function handleApiRequest(req, res, context) {
|
|
|
1126
1296
|
connector.deliverMessage(incomingMsg);
|
|
1127
1297
|
return json(res, { status: "delivered" });
|
|
1128
1298
|
}
|
|
1129
|
-
// POST /api/connectors/
|
|
1130
|
-
|
|
1131
|
-
|
|
1299
|
+
// POST /api/connectors/:id/proxy — proxy connector operations from remote instances
|
|
1300
|
+
// Supports both the legacy /api/connectors/discord/proxy and named instance ids
|
|
1301
|
+
params = matchRoute("/api/connectors/:id/proxy", pathname);
|
|
1302
|
+
if (method === "POST" && params && params.id) {
|
|
1303
|
+
const connector = context.connectors.get(params.id) ?? (params.id === "discord" ? context.connectors.get("discord") : undefined);
|
|
1132
1304
|
if (!connector)
|
|
1133
1305
|
return notFound(res);
|
|
1134
1306
|
const _parsed = await readJsonBody(req, res);
|
|
@@ -1204,8 +1376,10 @@ export async function handleApiRequest(req, res, context) {
|
|
|
1204
1376
|
}
|
|
1205
1377
|
// GET /api/connectors — list available connectors
|
|
1206
1378
|
if (method === "GET" && pathname === "/api/connectors") {
|
|
1207
|
-
const connectors = Array.from(context.connectors.
|
|
1379
|
+
const connectors = Array.from(context.connectors.entries()).map(([instanceId, connector]) => ({
|
|
1208
1380
|
name: connector.name,
|
|
1381
|
+
instanceId,
|
|
1382
|
+
employee: connector.getEmployee?.() ?? undefined,
|
|
1209
1383
|
...connector.getHealth(),
|
|
1210
1384
|
}));
|
|
1211
1385
|
return json(res, connectors);
|
|
@@ -1718,6 +1892,9 @@ async function runWebSession(session, prompt, engine, config, context, attachmen
|
|
|
1718
1892
|
const registry = scanOrg();
|
|
1719
1893
|
employee = findEmployee(currentSession.employee, registry);
|
|
1720
1894
|
}
|
|
1895
|
+
const { scanOrg: scanOrgForHierarchy } = await import("./org.js");
|
|
1896
|
+
const { resolveOrgHierarchy } = await import("./org-hierarchy.js");
|
|
1897
|
+
const orgHierarchy = resolveOrgHierarchy(scanOrgForHierarchy());
|
|
1721
1898
|
try {
|
|
1722
1899
|
const systemPrompt = buildContext({
|
|
1723
1900
|
source: "web",
|
|
@@ -1727,6 +1904,7 @@ async function runWebSession(session, prompt, engine, config, context, attachmen
|
|
|
1727
1904
|
connectors: Array.from(context.connectors.keys()),
|
|
1728
1905
|
config,
|
|
1729
1906
|
sessionId: currentSession.id,
|
|
1907
|
+
hierarchy: orgHierarchy,
|
|
1730
1908
|
});
|
|
1731
1909
|
const engineConfig = currentSession.engine === "codex"
|
|
1732
1910
|
? config.engines.codex
|