@rubytech/create-maxy-code 0.1.388 → 0.1.391
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/__tests__/claude-bin.test.js +51 -0
- package/dist/__tests__/claude-upgrade-privilege.test.js +17 -0
- package/dist/__tests__/joblogic-excluded.test.js +39 -0
- package/dist/__tests__/launchd-plist.test.js +36 -1
- package/dist/__tests__/macos-darwin-branch.test.js +18 -0
- package/dist/__tests__/neo4j-datadir.test.js +14 -0
- package/dist/claude-bin.js +78 -0
- package/dist/claude-upgrade-privilege.js +21 -0
- package/dist/index.js +262 -34
- package/dist/launchd-plist.js +25 -9
- package/dist/neo4j-datadir.js +15 -0
- package/package.json +1 -1
- package/payload/platform/config/brand.json +1 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +0 -5
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +24 -5
- package/payload/platform/plugins/docs/references/admin-session.md +4 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +18 -1
- package/payload/platform/plugins/docs/references/joblogic.md +2 -0
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +2 -0
- package/payload/platform/plugins/work/PLUGIN.md +3 -0
- package/payload/platform/plugins/work/mcp/dist/__tests__/session-metering.test.d.ts +2 -0
- package/payload/platform/plugins/work/mcp/dist/__tests__/session-metering.test.d.ts.map +1 -0
- package/payload/platform/plugins/work/mcp/dist/__tests__/session-metering.test.js +98 -0
- package/payload/platform/plugins/work/mcp/dist/__tests__/session-metering.test.js.map +1 -0
- package/payload/platform/plugins/work/mcp/dist/index.js +16 -0
- package/payload/platform/plugins/work/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/work/mcp/dist/tools/session-metering.d.ts +53 -0
- package/payload/platform/plugins/work/mcp/dist/tools/session-metering.d.ts.map +1 -0
- package/payload/platform/plugins/work/mcp/dist/tools/session-metering.js +80 -0
- package/payload/platform/plugins/work/mcp/dist/tools/session-metering.js.map +1 -0
- package/payload/platform/plugins/work/mcp/package.json +2 -1
- package/payload/platform/scripts/smoke-boot-services.sh +35 -10
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/claude-host-creds.d.ts +11 -0
- package/payload/platform/services/claude-session-manager/dist/claude-host-creds.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/claude-host-creds.js +37 -0
- package/payload/platform/services/claude-session-manager/dist/claude-host-creds.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +98 -8
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pricing.d.ts +45 -0
- package/payload/platform/services/claude-session-manager/dist/pricing.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pricing.js +57 -0
- package/payload/platform/services/claude-session-manager/dist/pricing.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +7 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +5 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-metering.d.ts +38 -0
- package/payload/platform/services/claude-session-manager/dist/session-metering.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/session-metering.js +292 -0
- package/payload/platform/services/claude-session-manager/dist/session-metering.js.map +1 -0
- package/payload/platform/templates/specialists/agents/project-manager.md +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-BejIjbmU.js → AdminLoginScreens-CL27ZV86.js} +1 -1
- package/payload/server/public/assets/AdminShell-CBcSh9Yd.js +1 -0
- package/payload/server/public/assets/{Checkbox-1F1tzqca.js → Checkbox-DRIcYN9r.js} +1 -1
- package/payload/server/public/assets/{Transcript-DkGa4CQH.js → Transcript-CRc72hkG.js} +1 -1
- package/payload/server/public/assets/{admin-DqQARkjy.js → admin-iKYYnv3w.js} +1 -1
- package/payload/server/public/assets/{browser-nDtEK6RC.js → browser--8gzAe-E.js} +1 -1
- package/payload/server/public/assets/calendar-7xAT81N_.js +1 -0
- package/payload/server/public/assets/chat-B8Z9W42Z.js +1 -0
- package/payload/server/public/assets/chevron-left-DDselYau.js +1 -0
- package/payload/server/public/assets/data-Ece320dZ.js +1 -0
- package/payload/server/public/assets/{graph-DFyicKd7.js → graph-B3haz2DN.js} +2 -2
- package/payload/server/public/assets/{graph-labels-D3BQdcpD.js → graph-labels-Ba_fyw6I.js} +1 -1
- package/payload/server/public/assets/{operator-BxrjWdT9.js → operator-BYL1IFz9.js} +1 -1
- package/payload/server/public/assets/page-Bj6lB07z.js +32 -0
- package/payload/server/public/assets/page-CLKVCquw.js +1 -0
- package/payload/server/public/assets/{public-DbdqfdYq.js → public-BKJUKC76.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-BkX8HODe.js → rotate-ccw-BypBbSI4.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-4qilMyPX.css +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CsRv6MEd.js +9 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +5 -5
- package/payload/server/public/chat.html +8 -8
- package/payload/server/public/data.html +7 -7
- package/payload/server/public/graph.html +8 -8
- package/payload/server/public/index.html +10 -10
- package/payload/server/public/operator.html +10 -10
- package/payload/server/public/public.html +8 -8
- package/payload/server/server.js +327 -255
- package/payload/platform/plugins/joblogic/.claude-plugin/plugin.json +0 -21
- package/payload/platform/plugins/joblogic/PLUGIN.md +0 -182
- package/payload/platform/plugins/joblogic/lib/mcp-spawn-tee/index.js +0 -193
- package/payload/platform/plugins/joblogic/lib/mcp-spawn-tee/package.json +0 -3
- package/payload/platform/plugins/joblogic/mcp/dist/index.d.ts +0 -2
- package/payload/platform/plugins/joblogic/mcp/dist/index.d.ts.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/index.js +0 -229
- package/payload/platform/plugins/joblogic/mcp/dist/index.js.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.d.ts +0 -31
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.d.ts.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.js +0 -78
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.js.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.d.ts +0 -35
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.d.ts.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.js +0 -106
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.js.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.d.ts +0 -8
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.d.ts.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.js +0 -41
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.js.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.d.ts +0 -21
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.d.ts.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.js +0 -47
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.js.map +0 -1
- package/payload/platform/plugins/joblogic/mcp/package.json +0 -10
- package/payload/platform/plugins/joblogic/skills/joblogic/SKILL.md +0 -32
- package/payload/server/public/assets/AdminShell-D2-uBSB5.js +0 -1
- package/payload/server/public/assets/calendar-CO4Bwmho.js +0 -1
- package/payload/server/public/assets/chat-DeIge_bC.js +0 -1
- package/payload/server/public/assets/chevron-left-DhVdq0aN.js +0 -1
- package/payload/server/public/assets/data-B1GIdzHk.js +0 -1
- package/payload/server/public/assets/page-ByDLq_o1.js +0 -1
- package/payload/server/public/assets/page-D-Ep4bXd.js +0 -32
- package/payload/server/public/assets/useSubAccountSwitcher-DMbRhLPv.js +0 -9
- package/payload/server/public/assets/useSubAccountSwitcher-DS0iqSkP.css +0 -1
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { JlStore, JlEnvironment } from "./secrets.js";
|
|
2
|
-
/** Strip credential-shaped substrings and cap length before an upstream error
|
|
3
|
-
* body is put into a thrown message that may reach a tool response/transcript. */
|
|
4
|
-
export declare function sanitizeErrorBody(text: string): string;
|
|
5
|
-
export declare function identityBaseFor(env: JlEnvironment): string;
|
|
6
|
-
export declare function apiBaseFor(env: JlEnvironment): string;
|
|
7
|
-
export type FetchLike = (url: string, init: {
|
|
8
|
-
method: string;
|
|
9
|
-
headers: Record<string, string>;
|
|
10
|
-
body?: string;
|
|
11
|
-
}) => Promise<{
|
|
12
|
-
ok: boolean;
|
|
13
|
-
status: number;
|
|
14
|
-
text: () => Promise<string>;
|
|
15
|
-
}>;
|
|
16
|
-
export interface Token {
|
|
17
|
-
accessToken: string;
|
|
18
|
-
expiresIn: number;
|
|
19
|
-
}
|
|
20
|
-
export declare function acquireToken(store: JlStore, opts?: {
|
|
21
|
-
fetchImpl?: FetchLike;
|
|
22
|
-
}): Promise<Token>;
|
|
23
|
-
/** Return a valid bearer, refreshing if cached is stale or force=true.
|
|
24
|
-
* reason drives the op=token-refresh line; first acquisition logs op=token-acquire only. */
|
|
25
|
-
export declare function getBearer(store: JlStore, accountId: string, opts?: {
|
|
26
|
-
force?: boolean;
|
|
27
|
-
fetchImpl?: FetchLike;
|
|
28
|
-
}): Promise<string>;
|
|
29
|
-
/** Test seam: clear the per-process cache. */
|
|
30
|
-
export declare function _resetTokenCache(): void;
|
|
31
|
-
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAa3D;mFACmF;AACnF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAE1D;AACD,wBAAgB,UAAU,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAErD;AAED,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACrE,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAM3E,MAAM,WAAW,KAAK;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,SAAS,CAAA;CAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CA6BvG;AAQD;6FAC6F;AAC7F,wBAAsB,SAAS,CAC7B,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAA;CAAO,GACpD,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED,8CAA8C;AAC9C,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
const IDENTITY_BASE = {
|
|
2
|
-
uat: "https://uatidentityserver.joblogic.com",
|
|
3
|
-
production: "https://identityserver.joblogic.com",
|
|
4
|
-
};
|
|
5
|
-
const API_BASE = {
|
|
6
|
-
uat: "https://uatapi.joblogic.com",
|
|
7
|
-
production: "https://api.joblogic.com",
|
|
8
|
-
};
|
|
9
|
-
/** Refresh when the cached token is within this window of expiry. */
|
|
10
|
-
const REFRESH_THRESHOLD_MS = 120_000;
|
|
11
|
-
/** Strip credential-shaped substrings and cap length before an upstream error
|
|
12
|
-
* body is put into a thrown message that may reach a tool response/transcript. */
|
|
13
|
-
export function sanitizeErrorBody(text) {
|
|
14
|
-
return text
|
|
15
|
-
.replace(/client_secret=[^&\s"]*/gi, "client_secret=***")
|
|
16
|
-
.replace(/client_id=[^&\s"]*/gi, "client_id=***")
|
|
17
|
-
.replace(/Bearer\s+[A-Za-z0-9._-]+/gi, "Bearer ***")
|
|
18
|
-
.slice(0, 300);
|
|
19
|
-
}
|
|
20
|
-
export function identityBaseFor(env) {
|
|
21
|
-
return IDENTITY_BASE[env];
|
|
22
|
-
}
|
|
23
|
-
export function apiBaseFor(env) {
|
|
24
|
-
return API_BASE[env];
|
|
25
|
-
}
|
|
26
|
-
export async function acquireToken(store, opts = {}) {
|
|
27
|
-
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
28
|
-
const url = `${identityBaseFor(store.environment)}/connect/token`;
|
|
29
|
-
const body = new URLSearchParams({
|
|
30
|
-
grant_type: "client_credentials",
|
|
31
|
-
client_id: store.clientId,
|
|
32
|
-
client_secret: store.clientSecret,
|
|
33
|
-
}).toString();
|
|
34
|
-
const started = Date.now();
|
|
35
|
-
const res = await fetchImpl(url, {
|
|
36
|
-
method: "POST",
|
|
37
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
38
|
-
body,
|
|
39
|
-
});
|
|
40
|
-
const text = await res.text();
|
|
41
|
-
// The token request carried client_secret; some identity servers echo request
|
|
42
|
-
// params in a 4xx body. Strip secrets and cap length before the message can
|
|
43
|
-
// reach a tool response / transcript.
|
|
44
|
-
if (!res.ok)
|
|
45
|
-
throw new Error(`token endpoint ${res.status}: ${sanitizeErrorBody(text)}`);
|
|
46
|
-
let raw;
|
|
47
|
-
try {
|
|
48
|
-
raw = JSON.parse(text);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
throw new Error(`token endpoint non-JSON: ${text}`);
|
|
52
|
-
}
|
|
53
|
-
if (!raw.access_token)
|
|
54
|
-
throw new Error(`token endpoint missing access_token: ${text}`);
|
|
55
|
-
const expiresIn = raw.expires_in ?? 3600;
|
|
56
|
-
process.stderr.write(`[joblogic] op=token-acquire ok=true expiresInS=${expiresIn} ms=${Date.now() - started}\n`);
|
|
57
|
-
return { accessToken: raw.access_token, expiresIn };
|
|
58
|
-
}
|
|
59
|
-
const tokenCache = new Map();
|
|
60
|
-
/** Return a valid bearer, refreshing if cached is stale or force=true.
|
|
61
|
-
* reason drives the op=token-refresh line; first acquisition logs op=token-acquire only. */
|
|
62
|
-
export async function getBearer(store, accountId, opts = {}) {
|
|
63
|
-
const cached = tokenCache.get(accountId);
|
|
64
|
-
const fresh = cached && cached.expiresAt - REFRESH_THRESHOLD_MS > Date.now();
|
|
65
|
-
if (fresh && !opts.force)
|
|
66
|
-
return cached.accessToken;
|
|
67
|
-
if (cached) {
|
|
68
|
-
process.stderr.write(`[joblogic] op=token-refresh reason=${opts.force ? "401" : "pre-expiry"} ok=true\n`);
|
|
69
|
-
}
|
|
70
|
-
const tok = await acquireToken(store, { fetchImpl: opts.fetchImpl });
|
|
71
|
-
tokenCache.set(accountId, { accessToken: tok.accessToken, expiresAt: Date.now() + tok.expiresIn * 1000 });
|
|
72
|
-
return tok.accessToken;
|
|
73
|
-
}
|
|
74
|
-
/** Test seam: clear the per-process cache. */
|
|
75
|
-
export function _resetTokenCache() {
|
|
76
|
-
tokenCache.clear();
|
|
77
|
-
}
|
|
78
|
-
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { JlStore } from "./secrets.js";
|
|
2
|
-
import { type FetchLike } from "./auth.js";
|
|
3
|
-
export type AccessFaultKind = "auth" | "whitelist" | "network";
|
|
4
|
-
export declare function classifyAccessFault(status: number): AccessFaultKind;
|
|
5
|
-
export interface BaseArgs {
|
|
6
|
-
store: JlStore;
|
|
7
|
-
accountId: string;
|
|
8
|
-
method: "GET" | "POST";
|
|
9
|
-
/** path under /api/v1/, e.g. "Customer/GetAll". */
|
|
10
|
-
path: string;
|
|
11
|
-
/** entity label for the observability line. */
|
|
12
|
-
entity: string;
|
|
13
|
-
/** tool name for the observability line. */
|
|
14
|
-
tool: string;
|
|
15
|
-
/** extra query params (merged with tenantId). */
|
|
16
|
-
query?: Record<string, string>;
|
|
17
|
-
/** request body for POST. tenantId is also injected into the body for writes. */
|
|
18
|
-
body?: Record<string, unknown>;
|
|
19
|
-
}
|
|
20
|
-
/** A read or generic call. Performs one silent re-acquire on a 401. */
|
|
21
|
-
export declare function jlRequest(args: BaseArgs, opts?: {
|
|
22
|
-
fetchImpl?: FetchLike;
|
|
23
|
-
}): Promise<unknown>;
|
|
24
|
-
/** A create with idempotency. idemKey lookup short-circuits a duplicate send.
|
|
25
|
-
* platformRoot is threaded explicitly (the ledger lives under the account secrets dir). */
|
|
26
|
-
export interface JlCreateArgs extends Omit<BaseArgs, "method"> {
|
|
27
|
-
platformRoot: string;
|
|
28
|
-
idemKey: string;
|
|
29
|
-
/** field on the response carrying the new record id (default "id"). */
|
|
30
|
-
idField?: string;
|
|
31
|
-
}
|
|
32
|
-
export declare function jlCreate(args: JlCreateArgs, opts?: {
|
|
33
|
-
fetchImpl?: FetchLike;
|
|
34
|
-
}): Promise<unknown>;
|
|
35
|
-
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAA4C,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAGrF,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAInE;AAMD,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AA2BD,uEAAuE;AACvE,wBAAsB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,SAAS,CAAA;CAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAoCtG;AAED;4FAC4F;AAC5F,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,SAAS,CAAA;CAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAoCzG"}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// JobLogic REST wrapper. Injects Bearer + tenantId on every call, emits the
|
|
2
|
-
// observability lifeline, classifies any 401/403/network failure as an
|
|
3
|
-
// access-fault, and performs exactly one silent token re-acquire on a 401.
|
|
4
|
-
import { randomBytes } from "node:crypto";
|
|
5
|
-
import { apiBaseFor, getBearer, sanitizeErrorBody } from "./auth.js";
|
|
6
|
-
import { lookupIdem, recordIdem } from "./idempotency.js";
|
|
7
|
-
export function classifyAccessFault(status) {
|
|
8
|
-
if (status === 401)
|
|
9
|
-
return "auth";
|
|
10
|
-
if (status === 403)
|
|
11
|
-
return "whitelist";
|
|
12
|
-
return "network"; // status 0 / connect failure
|
|
13
|
-
}
|
|
14
|
-
function reqId() {
|
|
15
|
-
return randomBytes(6).toString("hex");
|
|
16
|
-
}
|
|
17
|
-
async function doFetch(args, accessToken, fetchImpl) {
|
|
18
|
-
const params = new URLSearchParams({ tenantId: args.store.tenantId, ...(args.query ?? {}) });
|
|
19
|
-
const url = `${apiBaseFor(args.store.environment)}/api/v1/${args.path}?${params.toString()}`;
|
|
20
|
-
const headers = { Authorization: `Bearer ${accessToken}`, Accept: "application/json" };
|
|
21
|
-
let bodyStr;
|
|
22
|
-
if (args.body !== undefined) {
|
|
23
|
-
headers["Content-Type"] = "application/json";
|
|
24
|
-
bodyStr = JSON.stringify({ tenantId: args.store.tenantId, ...args.body });
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
const res = await fetchImpl(url, { method: args.method, headers, body: bodyStr });
|
|
28
|
-
return { ok: res.ok, status: res.status, text: await res.text() };
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
// connect-level failure → network access-fault
|
|
32
|
-
process.stderr.write(`[joblogic] op=access-fault kind=network tool=${args.tool} entity=${args.entity} detail=${err.message}\n`);
|
|
33
|
-
throw new Error(`access-fault kind=network: ${err.message}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/** A read or generic call. Performs one silent re-acquire on a 401. */
|
|
37
|
-
export async function jlRequest(args, opts = {}) {
|
|
38
|
-
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
39
|
-
const id = reqId();
|
|
40
|
-
const isWrite = args.method === "POST";
|
|
41
|
-
process.stderr.write(isWrite
|
|
42
|
-
? `[joblogic] op=mutation reqId=${id} tool=${args.tool} entity=${args.entity} verb=update\n`
|
|
43
|
-
: `[joblogic] op=request reqId=${id} tool=${args.tool} entity=${args.entity}\n`);
|
|
44
|
-
let bearer = await getBearer(args.store, args.accountId, { fetchImpl });
|
|
45
|
-
const started = Date.now();
|
|
46
|
-
let res = await doFetch(args, bearer, fetchImpl);
|
|
47
|
-
if (res.status === 401) {
|
|
48
|
-
bearer = await getBearer(args.store, args.accountId, { force: true, fetchImpl });
|
|
49
|
-
res = await doFetch(args, bearer, fetchImpl);
|
|
50
|
-
}
|
|
51
|
-
const ms = Date.now() - started;
|
|
52
|
-
if (!res.ok) {
|
|
53
|
-
if (res.status === 401 || res.status === 403) {
|
|
54
|
-
process.stderr.write(`[joblogic] op=access-fault kind=${classifyAccessFault(res.status)} tool=${args.tool} entity=${args.entity}\n`);
|
|
55
|
-
}
|
|
56
|
-
process.stderr.write(`[joblogic] op=response reqId=${id} status=${res.status} ms=${ms} rows=-\n`);
|
|
57
|
-
throw new Error(`JobLogic ${args.method} ${args.path} ${res.status}: ${sanitizeErrorBody(res.text)}`);
|
|
58
|
-
}
|
|
59
|
-
let parsed;
|
|
60
|
-
try {
|
|
61
|
-
parsed = JSON.parse(res.text);
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
throw new Error(`JobLogic ${args.path} non-JSON: ${res.text}`);
|
|
65
|
-
}
|
|
66
|
-
const data = parsed.data;
|
|
67
|
-
const rows = Array.isArray(data) ? data.length : "-";
|
|
68
|
-
process.stderr.write(`[joblogic] op=response reqId=${id} status=${res.status} ms=${ms} rows=${rows}\n`);
|
|
69
|
-
return parsed;
|
|
70
|
-
}
|
|
71
|
-
export async function jlCreate(args, opts = {}) {
|
|
72
|
-
const id = reqId();
|
|
73
|
-
const hit = lookupIdem(args.platformRoot, args.accountId, args.idemKey);
|
|
74
|
-
if (hit) {
|
|
75
|
-
process.stderr.write(`[joblogic] op=idem-hit reqId=${id} tool=${args.tool} existingId=${hit.recordId}\n`);
|
|
76
|
-
return { idempotent: true, id: hit.recordId, entity: hit.entity };
|
|
77
|
-
}
|
|
78
|
-
const result = await jlRequest({
|
|
79
|
-
store: args.store,
|
|
80
|
-
accountId: args.accountId,
|
|
81
|
-
method: "POST",
|
|
82
|
-
path: args.path,
|
|
83
|
-
entity: args.entity,
|
|
84
|
-
tool: args.tool,
|
|
85
|
-
query: args.query,
|
|
86
|
-
body: args.body,
|
|
87
|
-
}, opts);
|
|
88
|
-
const writtenId = extractId(result, args.idField);
|
|
89
|
-
if (!writtenId) {
|
|
90
|
-
// A 2xx with no extractable record id means the ledger cannot be written, so
|
|
91
|
-
// a retry of the same idemKey would duplicate. Fail loudly (the response
|
|
92
|
-
// shape / idField is provisional until UAT-confirmed) rather than silently
|
|
93
|
-
// leaving an idempotency hole.
|
|
94
|
-
process.stderr.write(`[joblogic] op=idem-record-fail reqId=${id} tool=${args.tool} entity=${args.entity} reason=id-unextractable\n`);
|
|
95
|
-
throw new Error(`${args.tool}: create returned success but no '${args.idField ?? "id"}' could be extracted from the response — a retry would NOT be idempotent. Confirm the response shape / idField against UAT.`);
|
|
96
|
-
}
|
|
97
|
-
recordIdem(args.platformRoot, args.accountId, args.idemKey, args.entity, writtenId);
|
|
98
|
-
process.stderr.write(`[joblogic] op=response reqId=${id} status=created id=${writtenId}\n`);
|
|
99
|
-
return result;
|
|
100
|
-
}
|
|
101
|
-
function extractId(result, idField = "id") {
|
|
102
|
-
const r = result;
|
|
103
|
-
const v = r?.[idField] ?? r?.data?.[idField];
|
|
104
|
-
return v == null ? null : String(v);
|
|
105
|
-
}
|
|
106
|
-
//# sourceMappingURL=client.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export interface IdemRecord {
|
|
2
|
-
entity: string;
|
|
3
|
-
recordId: string;
|
|
4
|
-
ts: number;
|
|
5
|
-
}
|
|
6
|
-
export declare function lookupIdem(platformRoot: string, accountId: string, idemKey: string): IdemRecord | null;
|
|
7
|
-
export declare function recordIdem(platformRoot: string, accountId: string, idemKey: string, entity: string, recordId: string): void;
|
|
8
|
-
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/lib/idempotency.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ;AAUD,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAatG;AAED,wBAAgB,UAAU,CACxB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,IAAI,CAKN"}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// Local idempotency ledger. JobLogic exposes no idempotency header, so a create
|
|
2
|
-
// retry (tool retry, or a write whose response was lost to a timeout) would
|
|
3
|
-
// duplicate. Each create computes/accepts an idemKey; the ledger maps it to the
|
|
4
|
-
// record id JobLogic returned. A hit short-circuits the send entirely.
|
|
5
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { accountSecretsDir } from "./secrets.js";
|
|
8
|
-
/** idemKey filename safety — no path fragments. */
|
|
9
|
-
const KEY_RE = /^[A-Za-z0-9_.:-]+$/;
|
|
10
|
-
function ledgerDir(platformRoot, accountId) {
|
|
11
|
-
return join(accountSecretsDir(platformRoot, accountId), "joblogic-idem");
|
|
12
|
-
}
|
|
13
|
-
function ledgerPath(platformRoot, accountId, idemKey) {
|
|
14
|
-
if (!KEY_RE.test(idemKey))
|
|
15
|
-
throw new Error(`invalid idemKey: ${JSON.stringify(idemKey)}`);
|
|
16
|
-
return join(ledgerDir(platformRoot, accountId), `${idemKey}.json`);
|
|
17
|
-
}
|
|
18
|
-
export function lookupIdem(platformRoot, accountId, idemKey) {
|
|
19
|
-
let file;
|
|
20
|
-
try {
|
|
21
|
-
file = ledgerPath(platformRoot, accountId, idemKey);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
if (!existsSync(file))
|
|
27
|
-
return null;
|
|
28
|
-
try {
|
|
29
|
-
return JSON.parse(readFileSync(file, "utf-8"));
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
export function recordIdem(platformRoot, accountId, idemKey, entity, recordId) {
|
|
36
|
-
const file = ledgerPath(platformRoot, accountId, idemKey);
|
|
37
|
-
mkdirSync(ledgerDir(platformRoot, accountId), { recursive: true });
|
|
38
|
-
const rec = { entity, recordId, ts: Date.now() };
|
|
39
|
-
writeFileSync(file, JSON.stringify(rec), { mode: 0o600 });
|
|
40
|
-
}
|
|
41
|
-
//# sourceMappingURL=idempotency.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../src/lib/idempotency.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,4EAA4E;AAC5E,gFAAgF;AAChF,uEAAuE;AACvE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,mDAAmD;AACnD,MAAM,MAAM,GAAG,oBAAoB,CAAC;AAQpC,SAAS,SAAS,CAAC,YAAoB,EAAE,SAAiB;IACxD,OAAO,IAAI,CAAC,iBAAiB,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,eAAe,CAAC,CAAC;AAC3E,CAAC;AACD,SAAS,UAAU,CAAC,YAAoB,EAAE,SAAiB,EAAE,OAAe;IAC1E,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1F,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,GAAG,OAAO,OAAO,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,YAAoB,EAAE,SAAiB,EAAE,OAAe;IACjF,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,UAAU,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAe,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,YAAoB,EACpB,SAAiB,EACjB,OAAe,EACf,MAAc,EACd,QAAgB;IAEhB,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAC1D,SAAS,CAAC,SAAS,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,MAAM,GAAG,GAAe,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAC7D,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export type JlEnvironment = "uat" | "production";
|
|
2
|
-
export interface JlStore {
|
|
3
|
-
clientId: string;
|
|
4
|
-
clientSecret: string;
|
|
5
|
-
/** Company tenant id — required on every JobLogic API call (tenant-scoped credential). */
|
|
6
|
-
tenantId: string;
|
|
7
|
-
environment: JlEnvironment;
|
|
8
|
-
/** Sender phone (E.164) → JobLogic engineer id, for write attribution. Config, not a secret. */
|
|
9
|
-
engineerMap: Record<string, string>;
|
|
10
|
-
}
|
|
11
|
-
export declare function accountSecretsDir(platformRoot: string, accountId: string): string;
|
|
12
|
-
export declare function readStore(platformRoot: string, accountId: string): JlStore | null;
|
|
13
|
-
export declare function setCredentials(platformRoot: string, accountId: string, creds: {
|
|
14
|
-
clientId: string;
|
|
15
|
-
clientSecret: string;
|
|
16
|
-
tenantId: string;
|
|
17
|
-
environment: JlEnvironment;
|
|
18
|
-
}): void;
|
|
19
|
-
export declare function setEngineerMap(platformRoot: string, accountId: string, entries: Record<string, string>): void;
|
|
20
|
-
export declare function resolveEngineer(store: JlStore, senderPhone: string): string | null;
|
|
21
|
-
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,YAAY,CAAC;AAEjD,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,aAAa,CAAC;IAC3B,gGAAgG;IAChG,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAKD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjF;AAKD,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAUjF;AAOD,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,aAAa,CAAA;CAAE,GAC9F,IAAI,CAGN;AAED,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,IAAI,CAIN;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAElF"}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// JobLogic per-account secret store. Plain 0600 JSON in the brand-isolated,
|
|
2
|
-
// delete-protected secrets/ dir (the quickbooks + cloudflare precedent). Never
|
|
3
|
-
// thrown from on read — a missing/corrupt store yields null so module init and
|
|
4
|
-
// tool enumeration survive a no-creds account.
|
|
5
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
6
|
-
import { resolve, join } from "node:path";
|
|
7
|
-
function accountsDir(platformRoot) {
|
|
8
|
-
return resolve(platformRoot, "..", "data/accounts");
|
|
9
|
-
}
|
|
10
|
-
export function accountSecretsDir(platformRoot, accountId) {
|
|
11
|
-
return join(accountsDir(platformRoot), accountId, "secrets");
|
|
12
|
-
}
|
|
13
|
-
function storePath(platformRoot, accountId) {
|
|
14
|
-
return join(accountSecretsDir(platformRoot, accountId), "joblogic.json");
|
|
15
|
-
}
|
|
16
|
-
export function readStore(platformRoot, accountId) {
|
|
17
|
-
const file = storePath(platformRoot, accountId);
|
|
18
|
-
if (!existsSync(file))
|
|
19
|
-
return null;
|
|
20
|
-
try {
|
|
21
|
-
const parsed = JSON.parse(readFileSync(file, "utf-8"));
|
|
22
|
-
if (!parsed.clientId || !parsed.clientSecret || !parsed.tenantId || !parsed.environment)
|
|
23
|
-
return null;
|
|
24
|
-
return { engineerMap: {}, ...parsed };
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function writeStore(platformRoot, accountId, store) {
|
|
31
|
-
mkdirSync(accountSecretsDir(platformRoot, accountId), { recursive: true });
|
|
32
|
-
writeFileSync(storePath(platformRoot, accountId), JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
33
|
-
}
|
|
34
|
-
export function setCredentials(platformRoot, accountId, creds) {
|
|
35
|
-
const prev = readStore(platformRoot, accountId);
|
|
36
|
-
writeStore(platformRoot, accountId, { ...creds, engineerMap: prev?.engineerMap ?? {} });
|
|
37
|
-
}
|
|
38
|
-
export function setEngineerMap(platformRoot, accountId, entries) {
|
|
39
|
-
const prev = readStore(platformRoot, accountId);
|
|
40
|
-
if (!prev)
|
|
41
|
-
throw new Error("cannot set engineer map before credentials are set");
|
|
42
|
-
writeStore(platformRoot, accountId, { ...prev, engineerMap: { ...prev.engineerMap, ...entries } });
|
|
43
|
-
}
|
|
44
|
-
export function resolveEngineer(store, senderPhone) {
|
|
45
|
-
return store.engineerMap[senderPhone] ?? null;
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=secrets.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAc1C,SAAS,WAAW,CAAC,YAAoB;IACvC,OAAO,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;AACtD,CAAC;AACD,MAAM,UAAU,iBAAiB,CAAC,YAAoB,EAAE,SAAiB;IACvE,OAAO,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AAC/D,CAAC;AACD,SAAS,SAAS,CAAC,YAAoB,EAAE,SAAiB;IACxD,OAAO,IAAI,CAAC,iBAAiB,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,eAAe,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,YAAoB,EAAE,SAAiB;IAC/D,MAAM,IAAI,GAAG,SAAS,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAqB,CAAC;QAC3E,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QACrG,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,MAAM,EAAa,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,YAAoB,EAAE,SAAiB,EAAE,KAAc;IACzE,SAAS,CAAC,iBAAiB,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,aAAa,CAAC,SAAS,CAAC,YAAY,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACrG,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,YAAoB,EACpB,SAAiB,EACjB,KAA+F;IAE/F,MAAM,IAAI,GAAG,SAAS,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IAChD,UAAU,CAAC,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,YAAoB,EACpB,SAAiB,EACjB,OAA+B;IAE/B,MAAM,IAAI,GAAG,SAAS,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACjF,UAAU,CAAC,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO,EAAE,EAAE,CAAC,CAAC;AACrG,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc,EAAE,WAAmB;IACjE,OAAO,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC;AAChD,CAAC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@maxy/joblogic",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/index.js",
|
|
7
|
-
"scripts": { "build": "tsc", "start": "node dist/index.js" },
|
|
8
|
-
"dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "zod": "^3.24.0" },
|
|
9
|
-
"devDependencies": { "typescript": "^5.7.0", "@types/node": "^22.0.0" }
|
|
10
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: joblogic
|
|
3
|
-
description: Connect JobLogic and drive the field-service job lifecycle. Use when the operator wants to link JobLogic, read customers/sites/jobs/visits/quotes/invoices/assets/timesheets, or make writes (create a visit, log job costs, set a job status, raise an invoice) from instructions sent by field staff. Covers the one-time JobLogic Support onboarding and source-IP whitelisting, then the agent-driven read/write surface.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# JobLogic connector
|
|
7
|
-
|
|
8
|
-
JobLogic is the operator's field-service system of record. This connector is bespoke to one account: it does nothing on any other install. The agent reads and writes across the job lifecycle under one company credential. Field staff never call JobLogic themselves; they send a voice note or message to this install, the agent interprets it, and this install makes the write. So only this install's outbound IP ever touches JobLogic.
|
|
9
|
-
|
|
10
|
-
Success is: the credentials are stored, `joblogic-connection-status` acquires a token, and a read such as `joblogic-customer-list` returns the company's data from UAT.
|
|
11
|
-
|
|
12
|
-
## What the operator does once (relay this; the agent cannot do it)
|
|
13
|
-
|
|
14
|
-
The agent cannot raise a support ticket or whitelist an IP. The operator does this once with JobLogic.
|
|
15
|
-
|
|
16
|
-
- **Raise a JobLogic Support ticket** for API access. Ask for a UAT (test) account first, then production. JobLogic Support issues a `client_id` and `client_secret` (OAuth client_credentials) and tells the operator the company `tenantId`.
|
|
17
|
-
- **Whitelist the install's outbound source IP.** JobLogic filters API calls by the public IP they originate from. The install's inbound tunnel does not fix the outbound IP, and a laptop's public IP is usually dynamic. The operator resolves one of: run the install from one location with a stable ISP IP and register that IP; route the connector's outbound through a fixed-egress proxy with a static IP and register that; or confirm with JobLogic that production drops the per-IP filter. Until one holds, calls fail and the logs show `op=access-fault kind=whitelist`.
|
|
18
|
-
|
|
19
|
-
## What the agent does after that
|
|
20
|
-
|
|
21
|
-
- **Store the credentials.** Run `joblogic-credentials-set` with the `client_id`, `client_secret`, `tenantId`, and `environment` (`uat` first). They are written only to this account's secrets file, never echoed.
|
|
22
|
-
- **Confirm it works.** Run `joblogic-connection-status`. It acquires a token and reports the environment and tenant. If it fails, the logs distinguish an auth problem from a whitelist problem from a network blip.
|
|
23
|
-
- **Set up attribution.** The company credential is not per-user, so JobLogic will not auto-attribute a write to the engineer who reported it. Run `joblogic-engineer-map-set` to map each field staffer's phone number to their JobLogic engineer id. When a write comes from a message, pass the sender's phone so the connector stamps the right engineer on it.
|
|
24
|
-
- **Read and write.** Reads cover customers, sites, jobs, engineers, visits, quotes, invoices, assets, and timesheets. Writes cover creating and updating those records and the lifecycle transitions: deploy a visit, log job costs, set a job status, approve a quote, raise an invoice, add a note or attachment.
|
|
25
|
-
|
|
26
|
-
## Authorization and safety
|
|
27
|
-
|
|
28
|
-
A write is authorized by the person who messaged the install asking for it. That instruction is the authorization; the agent does not interpose a second confirmation step. The one write safety that always applies is idempotency: every create takes an idempotency key, so a retry or a write whose response was lost does not create a duplicate record. Derive the key deterministically from the write's natural details so the same instruction maps to the same key.
|
|
29
|
-
|
|
30
|
-
## Use UAT for all testing
|
|
31
|
-
|
|
32
|
-
Every read and write test runs against the UAT environment, never production. Switch `environment` to `production` only once the operator confirms UAT behaves correctly and production credentials and IP whitelisting are in place.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o as e}from"./chunk-CAM3fms7.js";import{C as t,D as n,I as r,L as i,M as a,O as o,S as s,T as c,_ as l,c as u,d,h as f,j as p,l as m,o as h,p as g,r as ee,s as _,t as te,u as ne,v,w as re,x as y,y as ie}from"./useSubAccountSwitcher-DMbRhLPv.js";async function ae(){try{let e=await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`logout`})});return e.ok?(await e.json().catch(()=>({})))?.logged_out===!0:(console.error(`[admin-ui] claude-logout http-status=${e.status}`),!1)}catch(e){return console.error(`[admin-ui] claude-logout fetch failed: ${e instanceof Error?e.message:String(e)}`),!1}}var b=e(i(),1),x=a(),S={sm:14,md:16,lg:18},C={sm:30,md:38,lg:46};function w({variant:e=`primary`,size:t=`md`,icon:n,iconPosition:r=`leading`,loading:i=!1,fullWidth:a=!1,disabled:o=!1,type:s=`button`,onClick:c,"aria-label":l,style:u,className:d,children:f}){let p=[`btn`,`btn--${e}`,t===`md`?``:`btn--${t}`,a?`btn--full`:``,d].filter(Boolean).join(` `),m=S[t],h={...e===`send`?{width:C[t],height:C[t]}:{},...u},g=Object.keys(h).length>0;return(0,x.jsxs)(`button`,{type:s,className:p,disabled:i||o,onClick:c,"aria-label":l,style:g?h:void 0,children:[(0,x.jsxs)(`span`,{className:`btn__content`,style:{visibility:i?`hidden`:`visible`},children:[n&&r===`leading`&&(0,x.jsx)(n,{size:m}),f,n&&r===`trailing`&&(0,x.jsx)(n,{size:m})]}),i&&(0,x.jsx)(`span`,{className:`btn__spinner`,children:`✱`})]})}var oe=n(`archive-restore`,[[`rect`,{width:`20`,height:`5`,x:`2`,y:`3`,rx:`1`,key:`1wp1u1`}],[`path`,{d:`M4 8v11a2 2 0 0 0 2 2h2`,key:`tvwodi`}],[`path`,{d:`M20 8v11a2 2 0 0 1-2 2h-2`,key:`1gkqxj`}],[`path`,{d:`m9 15 3-3 3 3`,key:`1pd0qc`}],[`path`,{d:`M12 12v9`,key:`192myk`}]]),T=n(`archive`,[[`rect`,{width:`20`,height:`5`,x:`2`,y:`3`,rx:`1`,key:`1wp1u1`}],[`path`,{d:`M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8`,key:`1s80jp`}],[`path`,{d:`M10 12h4`,key:`a56b0p`}]]),E=n(`bot`,[[`path`,{d:`M12 8V4H8`,key:`hb8ula`}],[`rect`,{width:`16`,height:`12`,x:`4`,y:`8`,rx:`2`,key:`enze0r`}],[`path`,{d:`M2 14h2`,key:`vft8re`}],[`path`,{d:`M20 14h2`,key:`4cs60a`}],[`path`,{d:`M15 13v2`,key:`1xurst`}],[`path`,{d:`M9 13v2`,key:`rq6x2g`}]]),se=n(`box`,[[`path`,{d:`M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z`,key:`hh9hay`}],[`path`,{d:`m3.3 7 8.7 5 8.7-5`,key:`g66t2b`}],[`path`,{d:`M12 22V12`,key:`d0xqtd`}]]),ce=n(`history`,[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`1357e3`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}],[`path`,{d:`M12 7v5l4 2`,key:`1fdv2h`}]]),D=n(`list-todo`,[[`path`,{d:`M13 5h8`,key:`a7qcls`}],[`path`,{d:`M13 12h8`,key:`h98zly`}],[`path`,{d:`M13 19h8`,key:`c3s6r1`}],[`path`,{d:`m3 17 2 2 4-4`,key:`1jhpwq`}],[`rect`,{x:`3`,y:`4`,width:`6`,height:`6`,rx:`1`,key:`cif1o7`}]]),le=n(`message-square`,[[`path`,{d:`M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z`,key:`18887p`}]]),ue=n(`plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),O=n(`sliders-horizontal`,[[`path`,{d:`M10 5H3`,key:`1qgfaw`}],[`path`,{d:`M12 19H3`,key:`yhmn1j`}],[`path`,{d:`M14 3v4`,key:`1sua03`}],[`path`,{d:`M16 17v4`,key:`1q0r14`}],[`path`,{d:`M21 12h-9`,key:`1o4lsq`}],[`path`,{d:`M21 19h-5`,key:`1rlt1p`}],[`path`,{d:`M21 5h-7`,key:`1oszz2`}],[`path`,{d:`M8 10v4`,key:`tgpxqk`}],[`path`,{d:`M8 12H3`,key:`a7s4jb`}]]),k=n(`square`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}]]),de=n(`users`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`path`,{d:`M16 3.128a4 4 0 0 1 0 7.744`,key:`16gr8j`}],[`path`,{d:`M22 21v-2a4 4 0 0 0-3-3.87`,key:`kshegd`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}]]),A=`maxy-shell-side-px`;function fe(){if(typeof window>`u`)return 0;let e=document.querySelector(`.platform > .artefact`)?.getBoundingClientRect();return e&&e.width>0?Math.round(e.width):0}function j(e){if(typeof window>`u`)return e;let t=window.innerWidth,n=Math.max(248,t-480-fe());return Math.min(Math.max(e,248),n)}function pe(){if(typeof window>`u`)return 264;try{let e=window.localStorage.getItem(A);if(!e)return 264;let t=parseInt(e,10);if(!Number.isFinite(t))return console.warn(`[admin-ui] sidebar-width-parse-failed stored=${JSON.stringify(e)} fallback=264`),264;if(t>=248)return j(t)}catch{}return 264}function me({targetSelector:e=`.platform`}){let t=(0,b.useRef)(null),[n,r]=(0,b.useState)(()=>pe()),i=(0,b.useRef)(n);(0,b.useLayoutEffect)(()=>{let t=document.querySelector(e);!t||!(t instanceof HTMLElement)||(t.style.setProperty(`--side-px`,`${n}px`),i.current=n)},[n,e]);let a=(0,b.useCallback)(()=>{r(e=>{let t=j(e);return t===e?e:t})},[]);(0,b.useEffect)(()=>{a(),window.addEventListener(`resize`,a);let t=document.querySelector(e),n=null;return t instanceof HTMLElement&&(n=new MutationObserver(a),n.observe(t,{attributes:!0,attributeFilter:[`data-artefact`,`class`]})),()=>{window.removeEventListener(`resize`,a),n?.disconnect()}},[a,e]);let o=e=>{e.preventDefault();let n=t.current;if(!n)return;n.setPointerCapture(e.pointerId),n.classList.add(`dragging`);let a=!1,o=e=>{a=!0,r(j(Math.round(e.clientX)))},s=()=>{if(n.releasePointerCapture(e.pointerId),n.classList.remove(`dragging`),window.removeEventListener(`pointermove`,o),window.removeEventListener(`pointerup`,s),!a)return;let t=i.current;try{window.localStorage.setItem(A,String(t))}catch{}console.info(`[admin-ui] sidebar-resize px=${t}`)};window.addEventListener(`pointermove`,o),window.addEventListener(`pointerup`,s)},s=()=>{let e=j(264);r(e);try{window.localStorage.removeItem(A)}catch{}console.info(`[admin-ui] sidebar-resize px=${e}`)};return(0,x.jsx)(`div`,{ref:t,className:`side-resize-handle`,role:`separator`,"aria-orientation":`vertical`,"aria-label":`Resize sidebar`,onPointerDown:o,onDoubleClick:s})}var M=r();function he({anchorRef:e,onClose:t,className:n,role:r,ariaLabel:i,children:a}){let o=(0,b.useRef)(null),[s,c]=(0,b.useState)({position:`fixed`,visibility:`hidden`}),l=(0,b.useCallback)(()=>{let t=e.current,n=o.current;if(!t||!n)return;let r=t.getBoundingClientRect(),i=n.getBoundingClientRect(),a=r.right-i.width;a<4&&(a=4);let s=window.innerHeight-r.bottom<i.height+8&&r.top>i.height+8?r.top-i.height:r.bottom;c({position:`fixed`,left:a,top:s,visibility:`visible`})},[e]);return(0,b.useLayoutEffect)(()=>{if(l(),typeof ResizeObserver>`u`)return;let e=new ResizeObserver(()=>l());return o.current&&e.observe(o.current),()=>e.disconnect()},[l]),(0,b.useEffect)(()=>{let n=e=>{e.key===`Escape`&&t()},r=n=>{let r=n.target;e.current?.contains(r)||o.current?.contains(r)||t()},i=n=>{let r=e.current;r&&n.target instanceof Node&&n.target.contains(r)&&t()},a=()=>t();return document.addEventListener(`keydown`,n),document.addEventListener(`pointerdown`,r),window.addEventListener(`scroll`,i,!0),window.addEventListener(`resize`,a),()=>{document.removeEventListener(`keydown`,n),document.removeEventListener(`pointerdown`,r),window.removeEventListener(`scroll`,i,!0),window.removeEventListener(`resize`,a)}},[e,t]),(0,M.createPortal)((0,x.jsx)(`div`,{ref:o,className:n,role:r,"aria-label":i,style:s,children:a}),document.body)}function ge({action:e,disabled:t,open:n,onToggle:r,onClose:i}){let a=(0,b.useRef)(null);return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`button`,{ref:a,type:`button`,className:`conv-action`,onClick:r,disabled:t,"aria-haspopup":`dialog`,"aria-expanded":n,"aria-label":e.label,title:e.title,children:e.icon}),n&&(0,x.jsx)(he,{anchorRef:a,onClose:i,className:e.panelClassName,role:`dialog`,ariaLabel:e.label,children:e.panel()})]})}function _e({actions:e,disabled:n,collapsed:r}){let[i,a]=(0,b.useState)(null),[o,s]=(0,b.useState)(!1),[c,l]=(0,b.useState)(null),u=(0,b.useRef)(null);(0,b.useEffect)(()=>{a(null),s(!1),l(null)},[r]);let d=(0,b.useCallback)(()=>{s(!1),l(null)},[]);return r?(0,x.jsxs)(`div`,{className:`conv-actions`,children:[(0,x.jsx)(`button`,{ref:u,type:`button`,className:`conv-action conv-actions-overflow`,onClick:()=>s(e=>!e),disabled:n,"aria-haspopup":`menu`,"aria-expanded":o,"aria-label":`Session actions`,title:`Session actions`,children:(0,x.jsx)(t,{size:12})}),o&&(0,x.jsx)(he,{anchorRef:u,onClose:d,className:`conv-actions-menu`,role:`menu`,children:e.map(e=>e.panel?(0,x.jsxs)(`div`,{className:`conv-actions-menu-disclosure`,children:[(0,x.jsxs)(`button`,{type:`button`,role:`menuitem`,"aria-haspopup":`dialog`,"aria-expanded":c===e.key,disabled:n,onClick:()=>l(t=>t===e.key?null:e.key),children:[e.icon,(0,x.jsx)(`span`,{children:e.menuLabel})]}),c===e.key&&(0,x.jsx)(`div`,{className:`conv-actions-menu-panel`,children:e.panel()})]},e.key):(0,x.jsxs)(`button`,{type:`button`,role:`menuitem`,className:e.danger?`conv-action-danger`:void 0,"aria-busy":e.busy,disabled:n,onClick:t=>{e.onClick?.(t),d()},children:[e.icon,(0,x.jsx)(`span`,{children:e.menuLabel})]},e.key))})]}):(0,x.jsx)(`div`,{className:`conv-actions`,children:e.map(e=>e.panel?(0,x.jsx)(ge,{action:e,disabled:n,open:i===e.key,onToggle:()=>a(t=>t===e.key?null:e.key),onClose:()=>a(null)},e.key):(0,x.jsx)(`button`,{type:`button`,className:e.danger?`conv-action conv-action-danger`:`conv-action`,onClick:e.onClick,disabled:n,"aria-busy":e.busy,"aria-label":e.label,title:e.title,children:e.icon},e.key))})}var N=`claude-opus-4-8[1m]`,P=`claude-sonnet-5`,F=`claude-haiku-4-5`,I={[N]:`Opus 4.8 (1M context)`,[P]:`Sonnet 5`,[F]:`Haiku 4.5`};function L(e){return I[e]??e}var ve=[N,P,F];function R(e){return e.replace(/\[1m\]$/,``)}function ye(e){return ve.find(t=>R(t)===R(e))??e}var z=[`default`,`acceptEdits`,`plan`,`auto`,`bypassPermissions`],be=[`low`,`medium`,`high`,`xhigh`];[...z],[...be],[...be];var xe=[N,P,F],Se={default:`Ask permissions`,acceptEdits:`Accept edits`,plan:`Plan mode`,auto:`Auto mode`,bypassPermissions:`Bypass permissions`},Ce=z.map(e=>({value:e,label:Se[e]??e})),we={low:`Low`,medium:`Medium`,high:`High`,xhigh:`Highest`},B=be.map(e=>({value:e,label:we[e]??e}));function V(e){if(e){let t=xe.find(t=>R(t)===R(e));if(t)return t}return N}function Te({row:e,adminFetch:t,onError:n}){let[r,i]=(0,b.useState)(()=>V(e.model)),[a,o]=(0,b.useState)(``),[s,c]=(0,b.useState)(``),[l,u]=(0,b.useState)(!1),d=(0,b.useRef)(!1);(0,b.useEffect)(()=>{i(V(e.model))},[e.model]);let f=async()=>{if(!d.current){d.current=!0,u(!0);try{let i=await t(`/api/admin/session-reseat`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({fromSessionId:e.sessionId,model:r,...a?{permissionMode:a}:{},...s?{effort:s}:{}})});if(!i.ok){n(`Could not reset ${e.title} (status ${i.status})`),console.error(`[admin-ui] dashboard-reseat-failed sessionId=${e.sessionId} status=${i.status}`),d.current=!1,u(!1);return}let o=await i.json().catch(()=>({}));o.target?window.location.assign(o.target):(n(`Reset of ${e.title} returned no target`),d.current=!1,u(!1))}catch(t){n(`Could not reset ${e.title} (network error)`),console.error(`[admin-ui] dashboard-reseat-failed sessionId=${e.sessionId} error=${t instanceof Error?t.message:String(t)}`),d.current=!1,u(!1)}}};return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`label`,{className:`reseat-field`,children:[(0,x.jsx)(`span`,{children:`Model`}),(0,x.jsx)(`select`,{"data-kind":`model`,value:r,disabled:l,onChange:e=>i(e.target.value),children:xe.map(e=>(0,x.jsx)(`option`,{value:e,children:L(e)},e))})]}),(0,x.jsxs)(`label`,{className:`reseat-field`,children:[(0,x.jsx)(`span`,{children:`Mode`}),(0,x.jsxs)(`select`,{"data-kind":`mode`,value:a,disabled:l,onChange:e=>o(e.target.value),children:[(0,x.jsx)(`option`,{value:``,children:`Keep current`}),Ce.map(e=>(0,x.jsx)(`option`,{value:e.value,children:e.label},e.value))]})]}),(0,x.jsxs)(`label`,{className:`reseat-field`,children:[(0,x.jsx)(`span`,{children:`Effort`}),(0,x.jsxs)(`select`,{"data-kind":`effort`,value:s,disabled:l,onChange:e=>c(e.target.value),children:[(0,x.jsx)(`option`,{value:``,children:`Keep current`}),B.map(e=>(0,x.jsx)(`option`,{value:e.value,children:e.label},e.value))]})]}),(0,x.jsx)(`button`,{type:`button`,className:`reseat-apply`,disabled:l,"aria-busy":l,onClick:()=>void f(),children:l?(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(ie,{size:12,className:`spin`,"aria-hidden":`true`}),`Resetting…`]}):`Reset`})]})}function Ee(e){if(e<60)return`${e}s`;let t=Math.floor(e/60),n=e%60;return n>0?`${t}m ${n}s`:`${t}m`}function H(e){return e>=1e3?`${(e/1e3).toFixed(1)}k`:String(e)}async function U(e){if(navigator.clipboard)try{return await navigator.clipboard.writeText(e),!0}catch{}try{let t=document.createElement(`textarea`);t.value=e,t.style.position=`fixed`,t.style.opacity=`0`,document.body.appendChild(t),t.select();let n=document.execCommand(`copy`);return document.body.removeChild(t),n}catch{return!1}}function De({target:e,onConfirm:t,onCancel:n}){return e?(0,x.jsx)(`div`,{className:`claude-info-overlay`,onClick:n,children:(0,x.jsxs)(`div`,{className:`claude-info-modal`,onClick:e=>e.stopPropagation(),role:`alertdialog`,"aria-label":`Confirm delete session`,children:[(0,x.jsxs)(`div`,{className:`claude-info-header`,children:[(0,x.jsx)(`span`,{children:`Delete this conversation?`}),(0,x.jsx)(`button`,{className:`claude-info-close`,onClick:n,"aria-label":`Close`,children:`✕`})]}),(0,x.jsxs)(`div`,{className:`claude-info-section`,style:{padding:`12px 14px`,fontSize:`11px`,color:`var(--text-secondary)`},children:[`This permanently removes the transcript. It is not moved to Archive and cannot be recovered.`,e.live&&(0,x.jsx)(`div`,{style:{marginTop:`8px`},children:`This session is running; deleting stops it first.`})]}),(0,x.jsxs)(`div`,{className:`claude-info-section`,style:{display:`flex`,gap:`8px`,padding:`10px 14px`},children:[(0,x.jsx)(w,{variant:`danger`,size:`sm`,style:{flex:1},onClick:t,children:`Delete`}),(0,x.jsx)(w,{variant:`secondary`,size:`sm`,style:{flex:1},onClick:n,children:`Cancel`})]})]})}):null}var Oe=`auth-refresh-failed`;function ke({error:e,onClose:t}){if(!e)return null;let n=e.reason===Oe,r=n?`Claude sign-in expired`:`Session didn’t open`,i=n?`Your claude.ai login has expired. Re-authenticate to resume sessions.`:`The session did not bind within 60 seconds, so there is no live conversation to open.`;return(0,x.jsx)(`div`,{className:`claude-info-overlay`,onClick:t,children:(0,x.jsxs)(`div`,{className:`claude-info-modal`,onClick:e=>e.stopPropagation(),role:`alertdialog`,"aria-label":`Session could not be opened`,children:[(0,x.jsxs)(`div`,{className:`claude-info-header`,children:[(0,x.jsx)(`span`,{children:r}),(0,x.jsx)(`button`,{className:`claude-info-close`,onClick:t,"aria-label":`Close`,children:`✕`})]}),(0,x.jsxs)(`div`,{className:`claude-info-section`,style:{padding:`12px 14px`,fontSize:`11px`,color:`var(--text-secondary)`},children:[i,(0,x.jsxs)(`div`,{className:`claude-info-row`,style:{marginTop:`8px`},children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Reason`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:e.reason})]}),e.sessionId&&(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Session`}),(0,x.jsxs)(`span`,{className:`claude-info-value`,children:[e.sessionId.slice(0,8),`…`]})]})]})]})})}function W(e){let{show:t,onClose:n,claudeInfo:r,messages:i,sessionElapsed:a,sessionId:o,cacheKey:s}=e,[c,l]=(0,b.useState)(null);if(!t)return null;let u=i.flatMap(e=>e.events?.filter(e=>e.type===`usage`)??[]),d=u.at(-1),f=d?.peak_request_pct==null?d?.context_window?Math.round((d.input_tokens+d.cache_creation_tokens+d.cache_read_tokens)/d.context_window*100):0:Math.round(d.peak_request_pct*100),p=u.reduce((e,t)=>e+t.input_tokens+t.cache_creation_tokens+t.cache_read_tokens+t.output_tokens,0),m=i.flatMap(e=>e.events?.filter(e=>e.type===`rate_limit`)??[]).at(-1),h=u.reduce((e,t)=>e+(t.total_cost_usd??0),0),g=r?.account?.subscriptionType,ee=e=>{let t=e*1e3-Date.now();if(t<=0)return`now`;let n=Math.floor(t/36e5),r=Math.floor(t%36e5/6e4);return n>0?`${n}h ${r}m`:`${r}m`};return(0,x.jsx)(`div`,{className:`claude-info-overlay`,onClick:n,children:(0,x.jsxs)(`div`,{className:`claude-info-modal`,onClick:e=>e.stopPropagation(),children:[(0,x.jsxs)(`div`,{className:`claude-info-header`,children:[(0,x.jsx)(`img`,{src:`/brand/claude.png`,alt:`Claude`,className:`claude-info-icon`}),(0,x.jsx)(`span`,{children:`Claude Code`}),(0,x.jsx)(`button`,{className:`claude-info-close`,onClick:n,"aria-label":`Close`,children:`✕`})]}),(0,x.jsxs)(`div`,{className:`claude-info-section`,children:[(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Version`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:r?r.version:`…`})]}),(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Email`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:r?.account?.email??`…`})]})]}),(g||m||h>0)&&(0,x.jsxs)(`div`,{className:`claude-info-section`,children:[g&&(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Plan`}),(0,x.jsx)(`span`,{className:`claude-info-value`,style:{textTransform:`capitalize`},children:g})]}),m&&(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Usage`}),(0,x.jsxs)(`span`,{className:`claude-info-value`,children:[Math.round(m.utilization*100),`%`]})]}),(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Resets in`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:ee(m.resetsAt)})]}),m.isUsingOverage&&(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Overage`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:`Active`})]})]}),h>0&&(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Session cost`}),(0,x.jsxs)(`span`,{className:`claude-info-value`,children:[`$`,h<.01?h.toFixed(4):h.toFixed(2)]})]})]}),(0,x.jsxs)(`div`,{className:`claude-info-section`,children:[(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Model`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:r?.model??`…`})]}),(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Context used`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:f>0?`${f}%`:`—`})]}),(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Tokens`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:p>0?H(p):`—`})]}),(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Session`}),(0,x.jsx)(`span`,{className:`claude-info-value`,children:Ee(a)})]}),(o||s)&&(()=>{let e=o??s??``;return(0,x.jsxs)(`div`,{className:`claude-info-row`,children:[(0,x.jsx)(`span`,{className:`claude-info-label`,children:`Session`}),(0,x.jsx)(`button`,{type:`button`,className:`claude-info-value claude-info-id-copy`,title:`${e} (${o?`flushed`:`pre-flush`}) — click to copy`,onClick:async()=>{l(await U(e)?`copied`:`failed`),setTimeout(()=>l(null),1200)},children:c===`copied`?`copied ✓`:c===`failed`?`copy failed ✕`:`${e.slice(0,8)}…`})]})})()]})]})})}var Ae=`https://claude.ai/code`,je=200,Me=[500,1e3,1500,2e3,2500,3e3,3e3];function Ne(e,t,n){if(e&&n.target)return{kind:`navigate`,url:n.target,sameOrigin:!0};let r=n.slug??n.bridgeSessionId??null;if(e&&r)return{kind:`navigate`,url:`${Ae}/${r}`,sameOrigin:!1};let i=n.reason||n.error||`status ${t}`;return{kind:`error`,sessionId:n.sessionId??null,reason:i}}function Pe(e){if(!e)return``;let t=Date.parse(e);return Number.isFinite(t)?new Date(t).toLocaleString(void 0,{dateStyle:`medium`,timeStyle:`short`}):``}function Fe(e){let{businessName:t,cacheKey:n,role:r,userName:i,userAvatar:a,onSelectProjects:d,onSelectPeople:te,onSelectTasks:ie,onSelectAgents:ae,onCloseMobileDrawer:S,collapsed:C,selectedWhatsappId:w,onSelectWhatsappConversation:A,initialWhatsappSurface:fe=!1,onSelectData:j,onCloseData:pe,initialDataSurface:me=!1}=e,M=g(n),he=(0,b.useCallback)(e=>({key:`reseat`,label:`Reset session ${e.title} (model, mode, effort)`,menuLabel:`Reset`,title:`Reset — move this session onto a chosen model, mode, and effort`,icon:(0,x.jsx)(O,{size:12}),panelClassName:`reseat-panel`,panel:()=>(0,x.jsx)(Te,{row:e,adminFetch:M,onError:e=>V({message:e,failed:!0})})}),[M]),ge=o.productName,N=typeof i==`string`?i:i===null?`name unavailable`:t||ge,P=(N.trim().charAt(0)||`?`).toUpperCase(),[F,I]=(0,b.useState)(me?`data`:fe?`whatsapp`:`sessions`),[L,ve]=(0,b.useState)([]),[R,ye]=(0,b.useState)(!1),[z,be]=(0,b.useState)(null),[xe,Se]=(0,b.useState)(!1),[Ce,we]=(0,b.useState)(`file`),[B,V]=(0,b.useState)(null),[Ee,H]=(0,b.useState)(null),[U,Oe]=(0,b.useState)([]),[W,Ae]=(0,b.useState)(!1),[Fe,Ie]=(0,b.useState)(null),[G,K]=(0,b.useState)(!1),[q,Be]=(0,b.useState)(!1),[J,Ve]=(0,b.useState)(!1),[Y,He]=(0,b.useState)(null),[Ue,We]=(0,b.useState)(new Set),[Ge,Ke]=(0,b.useState)(null),[qe,Je]=(0,b.useState)(new Set),[Ye,Xe]=(0,b.useState)(new Set),[Ze,Qe]=(0,b.useState)(new Set),[X,$e]=(0,b.useState)(null),[et,Z]=(0,b.useState)(``),[tt,nt]=(0,b.useState)(!1),[rt,it]=(0,b.useState)(!1),at=(0,b.useRef)(null),ot=(0,b.useRef)(0),[st,ct]=(0,b.useState)(0);(0,b.useEffect)(()=>{let e=at.current;if(!e||typeof ResizeObserver>`u`)return;let t=new ResizeObserver(e=>{let t=e[0]?.contentRect.width??0;ct(e=>Math.abs(e-t)<1?e:t)});return t.observe(e),()=>t.disconnect()},[F]);let lt=st>0&&st<400,[ut,dt]=(0,b.useState)([]),[ft,pt]=(0,b.useState)(!1),[mt,ht]=(0,b.useState)(null),[gt,_t]=(0,b.useState)(`whatsapp`),vt=(0,b.useCallback)(e=>{if(S(),!n){console.error(`[admin-ui] artefact-download-blocked id=${e.id} reason=no-cache-key`),V({message:`Session not ready — try again`,failed:!0});return}if(!e.downloadPath){console.error(`[admin-ui] artefact-download-blocked id=${e.id} reason=not-downloadable`),V({message:`${e.name} can’t be downloaded`,failed:!0});return}console.info(`[admin-ui] artefact-download id=${e.id} root=${e.downloadRoot??`data`} path=${e.downloadPath}`),ee(n,e.downloadPath,e.downloadRoot??`data`),V({message:`Downloading ${e.name}`,failed:!1})},[n,S]);(0,b.useEffect)(()=>{if(!B)return;let e=setTimeout(()=>V(null),2500);return()=>clearTimeout(e)},[B]);let yt=(0,b.useCallback)(async()=>{if(n){Se(!0),be(null);try{let e=await M(`/api/admin/sidebar-artefacts`);if(!e.ok)throw Error(`status ${e.status}`);ve((await e.json()).artefacts??[]),ye(!0)}catch(e){let t=e instanceof Error?e.message:String(e);be(`Failed to load artefacts: ${t}`),console.error(`[admin-ui] sidebar-artefacts fetch failed: ${t}`)}finally{Se(!1)}}},[n,M]),Q=(0,b.useCallback)(async()=>{if(!n)return null;let e=++ot.current;K(!0),Ie(null);try{let t=await M(`/api/admin/sidebar-sessions`);if(!t.ok)throw Error(`status ${t.status}`);let n=await t.json(),r=n.sessions??[];return e===ot.current?(Oe(r),He(n.accountId??null),Ae(!0),r):(console.info(`[admin-ui] sidebar-sessions-stale-response-dropped token=${e}`),r)}catch(t){let n=t instanceof Error?t.message:String(t);return e===ot.current&&Ie(`Failed to load sessions: ${n}`),console.error(`[admin-ui] sidebar-sessions fetch failed: ${n}`),null}finally{e===ot.current&&K(!1)}},[n,M]),bt=(0,b.useCallback)(async()=>{if(n){ht(null);try{let e=await M(`/api/whatsapp-reader/conversations`);if(!e.ok)throw Error(`status ${e.status}`);dt((await e.json()).conversations??[]),pt(!0)}catch(e){let t=e instanceof Error?e.message:String(e);ht(`Couldn't load conversations.`),console.error(`[admin-ui] channel-convos fetch failed: ${t}`)}}},[n,M]);(0,b.useEffect)(()=>{!n||W||Q()},[n,W,Q]),(0,b.useEffect)(()=>{if(!n)return;let e=null;return bt(),e=setInterval(()=>{bt()},Le),()=>{e!==null&&clearInterval(e)}},[n,bt]),(0,b.useEffect)(()=>{if(!n)return;let e=null;function t(){console.info(`[admin-ui] sidebar-refresh surface=sessions trigger=poll`),Q()}function r(){e===null&&(e=setInterval(t,Re))}function i(){e!==null&&(clearInterval(e),e=null)}function a(){document.hidden?i():(t(),r())}return document.hidden||r(),document.addEventListener(`visibilitychange`,a),()=>{i(),document.removeEventListener(`visibilitychange`,a)}},[n,Q]);let xt=(0,b.useMemo)(()=>{let e=new Map;for(let t of _)e.set(t,[]);for(let t of ut)e.get(t.channel)?.push(t);for(let t of _)console.info(`[admin-ui] sidebar-nav surface=${t} count=${e.get(t).length}`);return e},[ut]),St={whatsapp:`WhatsApp`,telegram:`Telegram`,webchat:`Webchat`},Ct=()=>{A(null),I(`artefacts`),console.info(`[admin-ui] sidebar-nav surface=artefacts count=${R?L.length:0} collapsed=${C}`),yt()},$=1.5,wt=()=>{console.info(`[admin-ui] sidebar-refresh surface=artefacts`),yt()},Tt=()=>{A(null),I(`sessions`),console.info(`[admin-ui] sidebar-nav surface=sessions count=${W?U.length:0} collapsed=${C}`),W||Q()},Et=()=>{console.info(`[admin-ui] sidebar-refresh surface=sessions trigger=manual`),Q()},Dt=()=>{I(`data`),console.info(`[admin-ui] sidebar-nav surface=data collapsed=${C}`),j(),S()},Ot=e=>{pe?.(),I(`whatsapp`),_t(e),console.info(`[admin-ui] sidebar-nav surface=${e} count=${xt.get(e)?.length??0} collapsed=${C}`)},kt=(0,b.useCallback)(async e=>{if(qe.has(e.sessionId))return;Je(t=>{let n=new Set(t);return n.add(e.sessionId),n});let t=window.open(``,`_blank`);try{let n=await M(`/api/admin/session-rc-spawn`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sessionId:e.sessionId,name:e.title||e.sessionId,...e.accountId?{targetAccountId:e.accountId}:{}})}),r=await n.json().catch(()=>({})),i=Ne(n.ok,n.status,{...r,sessionId:r.sessionId??e.sessionId});console.info(`[admin-ui] sidebar-session-resume sessionId=${e.sessionId} status=${n.status} outcome=${i.kind} slug=${r.slug??r.bridgeSessionId??`none`}`),i.kind===`navigate`?(S(),t?t.location.href=i.url:window.open(i.url,`_blank`)):(t?.close(),console.error(`[admin-ui] sidebar-session-resume-failed sessionId=${e.sessionId} status=${n.status} reason=${i.reason}`),H(i))}catch(n){t?.close();let r=n instanceof Error?n.message:String(n);console.error(`[admin-ui] sidebar-session-resume-failed sessionId=${e.sessionId} error=${r}`),H({sessionId:e.sessionId,reason:r})}finally{Je(t=>{let n=new Set(t);return n.delete(e.sessionId),n})}},[M,S,qe]),At=(0,b.useCallback)(async e=>{let t=e.slice(0,8);for(let n=1;n<=Me.length;n++){await new Promise(e=>setTimeout(e,Me[n-1]));let r=await Q();if(r&&r.some(t=>t.sessionId===e)){console.info(`[admin-ui] sidebar-new-session-converged sessionId=${t} via=retry attempts=${n}`);return}}console.error(`[admin-ui] sidebar-new-session-converge-timeout sessionId=${t}`)},[Q]),jt=(0,b.useCallback)(async()=>{if(!rt){it(!0);try{let e=await M(`/api/admin/session-rc-spawn`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({})}),t=await e.json().catch(()=>({})),n=Ne(e.ok,e.status,t);console.info(`[admin-ui] sidebar-new-session-spawned status=${e.status} outcome=${n.kind} slug=${t.slug??t.bridgeSessionId??`none`}`),n.kind===`navigate`?n.sameOrigin?window.location.assign(n.url):(window.open(n.url,`_blank`),t.sessionId?At(t.sessionId):console.error(`[admin-ui] sidebar-new-session-converge-skipped reason=no-session-id`)):(console.error(`[admin-ui] sidebar-new-session-failed status=${e.status} reason=${n.reason}`),H(n))}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`[admin-ui] sidebar-new-session-failed error=${t}`),H({sessionId:null,reason:t})}finally{it(!1)}}},[M,rt,At]),Mt=(0,b.useCallback)((e,t)=>{e.stopPropagation(),Ke(t)},[]),Nt=(0,b.useCallback)(async e=>{if(!Ue.has(e.sessionId)){We(t=>{let n=new Set(t);return n.add(e.sessionId),n});try{let t=await M(`/api/admin/session-delete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sessionId:e.sessionId})});if(!t.ok){let n=await t.json().catch(()=>({}));console.error(`[admin-ui] sidebar-session-delete-failed sessionId=${e.sessionId} status=${t.status} error=${n.error??`unknown`}`),V({message:`Delete failed: ${n.error??`status ${t.status}`}`,failed:!0});return}let n=await t.json();console.info(`[admin-ui] sidebar-session-delete sessionId=${e.sessionId} live=${e.live} confirmed=true pidKilled=${n.pidKilled??`?`} deleted=${n.deleted??`?`}`),Oe(t=>t.filter(t=>t.sessionId!==e.sessionId)),Q()}catch(t){let n=t instanceof Error?t.message:String(t);console.error(`[admin-ui] sidebar-session-delete-failed sessionId=${e.sessionId} error=${n}`),V({message:`Delete failed: ${n}`,failed:!0})}finally{We(t=>{let n=new Set(t);return n.delete(e.sessionId),n})}}},[M,Ue,Q]),Pt=(0,b.useCallback)(async(e,t)=>{if(e.stopPropagation(),!Ye.has(t.sessionId)){Xe(e=>{let n=new Set(e);return n.add(t.sessionId),n});try{let e=await M(`/api/admin/session-stop`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sessionId:t.sessionId})});if(!e.ok){let n=await e.json().catch(()=>({}));console.error(`[admin-ui] sidebar-session-stop-failed sessionId=${t.sessionId} status=${e.status} error=${n.error??`unknown`}`),V({message:`Stop failed: ${n.error??`status ${e.status}`}`,failed:!0});return}console.info(`[admin-ui] sidebar-session-stop sessionId=${t.sessionId}`),Q()}catch(e){let n=e instanceof Error?e.message:String(e);console.error(`[admin-ui] sidebar-session-stop-failed sessionId=${t.sessionId} error=${n}`),V({message:`Stop failed: ${n}`,failed:!0})}finally{Xe(e=>{let n=new Set(e);return n.delete(t.sessionId),n})}}},[M,Ye,Q]),Ft=(0,b.useCallback)(async(e,t,n)=>{if(e.stopPropagation(),!Ze.has(t.sessionId)){Qe(e=>{let n=new Set(e);return n.add(t.sessionId),n});try{let e=await M(`/api/admin/session-archive`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sessionId:t.sessionId,mode:n})});if(!e.ok){let r=await e.json().catch(()=>({})),i=r.detail??r.error??`status ${e.status}`;console.error(`[admin-ui] sidebar-session-archive-failed sessionId=${t.sessionId} mode=${n} status=${e.status} error=${r.error??`unknown`}`),V({message:`${n===`archive`?`Archive`:`Unarchive`} failed: ${i}`,failed:!0});return}console.info(`[admin-ui] sidebar-session-archive sessionId=${t.sessionId} mode=${n}`),Q()}catch(e){let r=e instanceof Error?e.message:String(e);console.error(`[admin-ui] sidebar-session-archive-failed sessionId=${t.sessionId} mode=${n} error=${r}`),V({message:`${n===`archive`?`Archive`:`Unarchive`} failed: ${r}`,failed:!0})}finally{Qe(e=>{let n=new Set(e);return n.delete(t.sessionId),n})}}},[M,Ze,Q]),It=(0,b.useCallback)(async e=>{let t=et.trim();if(!t){V({message:`Title can’t be empty`,failed:!0});return}if(t.length>je){V({message:`Title too long (max ${je})`,failed:!0});return}nt(!0);try{let n=await M(`/api/admin/session-rename`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sessionId:e.sessionId,title:t})});if(!n.ok){let t=await n.json().catch(()=>({})),r=t.reason??t.error??`status ${n.status}`;console.error(`[admin-ui] sidebar-session-rename sessionId=${e.sessionId.slice(0,8)} outcome=rejected reason=${r}`),V({message:`Rename failed: ${r}`,failed:!0});return}console.info(`[admin-ui] sidebar-session-rename sessionId=${e.sessionId.slice(0,8)} outcome=ok`),$e(null),Z(``),Q()}catch(t){let n=t instanceof Error?t.message:String(t);console.error(`[admin-ui] sidebar-session-rename sessionId=${e.sessionId.slice(0,8)} outcome=rejected reason=${n}`),V({message:`Rename failed: ${n}`,failed:!0})}finally{nt(!1)}},[M,et,Q]);return(0,x.jsxs)(`aside`,{className:`side`,children:[(0,x.jsx)(`div`,{className:`side-new-session-row`,children:(0,x.jsxs)(`button`,{type:`button`,className:`side-new-session`,onClick:jt,disabled:rt,"aria-busy":rt,children:[(0,x.jsx)(ue,{size:14}),(0,x.jsx)(`span`,{children:rt?`Spawning…`:`New session`})]})}),(0,x.jsxs)(`nav`,{className:`side-nav`,children:[(0,x.jsxs)(`button`,{type:`button`,className:`nav-row`,onClick:()=>{console.info(`[admin-ui] sidebar-nav surface=people`),te(),S()},children:[(0,x.jsx)(de,{size:20,strokeWidth:$}),(0,x.jsx)(`span`,{className:`label`,children:`People`}),(0,x.jsx)(`span`,{className:`kbd`})]}),(0,x.jsxs)(`button`,{type:`button`,className:`nav-row`,onClick:()=>{console.info(`[admin-ui] sidebar-nav surface=agents`),ae(),S()},children:[(0,x.jsx)(E,{size:20,strokeWidth:$}),(0,x.jsx)(`span`,{className:`label`,children:`Agents`}),(0,x.jsx)(`span`,{className:`kbd`})]}),(0,x.jsxs)(`button`,{type:`button`,className:`nav-row`,onClick:()=>{console.info(`[admin-ui] sidebar-nav surface=projects`),d(),S()},children:[(0,x.jsx)(se,{size:20,strokeWidth:$}),(0,x.jsx)(`span`,{className:`label`,children:p().term}),(0,x.jsx)(`span`,{className:`kbd`})]}),(0,x.jsxs)(`button`,{type:`button`,className:`nav-row`,onClick:()=>{console.info(`[admin-ui] sidebar-nav surface=tasks`),ie(),S()},children:[(0,x.jsx)(D,{size:20,strokeWidth:$}),(0,x.jsx)(`span`,{className:`label`,children:`Tasks`}),(0,x.jsx)(`span`,{className:`kbd`})]}),(0,x.jsxs)(`button`,{type:`button`,className:`nav-row${F===`artefacts`?` active`:``}`,onClick:Ct,children:[(0,x.jsx)(y,{size:20,strokeWidth:$}),(0,x.jsx)(`span`,{className:`label`,children:`Artefacts`}),(0,x.jsx)(`span`,{className:`kbd`})]}),(0,x.jsxs)(`button`,{type:`button`,className:`nav-row${F===`sessions`?` active`:``}`,onClick:Tt,children:[(0,x.jsx)(ce,{size:20,strokeWidth:$}),(0,x.jsx)(`span`,{className:`label`,children:`Sessions`}),(0,x.jsx)(`span`,{className:`kbd`})]}),(0,x.jsxs)(`button`,{type:`button`,className:`nav-row${F===`data`?` active`:``}`,onClick:Dt,children:[(0,x.jsx)(c,{size:20,strokeWidth:$}),(0,x.jsx)(`span`,{className:`label`,children:`Data`}),(0,x.jsx)(`span`,{className:`kbd`})]}),ft&&_.filter(e=>xt.get(e).length>0).map(e=>(0,x.jsxs)(`button`,{type:`button`,className:`nav-row${F===`whatsapp`&>===e?` active`:``}`,onClick:()=>Ot(e),children:[(0,x.jsx)(m,{channel:e,size:16}),(0,x.jsx)(`span`,{className:`label`,children:St[e]}),(0,x.jsx)(`span`,{className:`kbd`})]},e)),mt&&(0,x.jsx)(`div`,{className:`nav-row`,style:{color:`var(--text-tertiary)`,cursor:`default`},"aria-disabled":`true`,children:(0,x.jsx)(`span`,{className:`label`,children:mt})})]}),F===`artefacts`&&(0,x.jsxs)(`div`,{className:`side-list`,children:[(0,x.jsxs)(`div`,{className:`group-head`,children:[(0,x.jsx)(`span`,{children:`Artefacts`}),(0,x.jsxs)(`span`,{className:`group-head-meta`,children:[(0,x.jsx)(`button`,{type:`button`,className:`group-head-refresh`,title:`Refresh artefacts`,"aria-label":`Refresh artefacts`,onClick:wt,disabled:xe,children:(0,x.jsx)(l,{size:12,className:xe?`spinning`:void 0})}),(0,x.jsx)(`span`,{children:xe?`…`:String(L.length)})]})]}),z&&(0,x.jsx)(`div`,{className:`conv`,style:{color:`var(--text-tertiary)`,cursor:`default`},children:z}),R&&!z&&L.length>0&&(()=>{let e=L.filter(e=>e.kind===`agent-template`).length,t=L.length-e;return(0,x.jsx)(`div`,{className:`artefact-filter-chips`,children:[{key:`all`,label:`All`,count:L.length},{key:`agent`,label:`Agents`,count:e},{key:`file`,label:`Files`,count:t}].map(e=>(0,x.jsxs)(`button`,{type:`button`,className:`artefact-filter-chip${Ce===e.key?` active`:``}`,onClick:()=>we(e.key),disabled:e.count===0&&e.key!==`all`,children:[e.label,(0,x.jsx)(`span`,{className:`artefact-filter-chip-count`,children:e.count})]},e.key))})})(),R&&!z&&L.length===0&&(0,x.jsx)(`div`,{className:`conv`,style:{color:`var(--text-tertiary)`,cursor:`default`},children:`No artefacts yet`}),L.filter(e=>Ce===`all`?!0:Ce===`agent`?e.kind===`agent-template`:e.kind!==`agent-template`).map(e=>{let t=e.kind===`agent-template`,n=t?E:y,r=Pe(e.updatedAt),i=e.downloadPath!==null;return(0,x.jsxs)(`button`,{type:`button`,className:`conv`,onClick:()=>vt(e),disabled:!i,style:i?void 0:{cursor:`default`},title:i?`Download ${e.name}`:`${e.name} can’t be downloaded`,children:[(0,x.jsx)(n,{size:14,className:`conv-icon`,"data-kind":t?`agent`:`file`,"aria-label":t?`agent template`:`file`}),(0,x.jsxs)(`span`,{className:`conv-stack`,children:[(0,x.jsx)(`span`,{className:`conv-name-line`,children:(0,x.jsx)(`span`,{className:`conv-name`,children:e.name})}),r&&(0,x.jsx)(`span`,{className:`conv-timestamp`,children:r})]}),i&&(0,x.jsx)(re,{size:12,className:`conv-rc-icon`,"aria-hidden":`true`})]},e.id)})]}),F===`sessions`&&(()=>{let e=U.filter(e=>q?!0:!e.isSubagent).filter(e=>J?!0:!e.archived),t=U.some(e=>e.isSubagent),n=U.some(e=>e.archived);return(0,x.jsxs)(`div`,{className:`side-list`,ref:at,children:[(0,x.jsxs)(`div`,{className:`group-head`,children:[(0,x.jsx)(`span`,{children:`Sessions`}),(0,x.jsxs)(`span`,{className:`group-head-meta`,children:[(0,x.jsx)(`button`,{type:`button`,className:`group-head-refresh`,title:`Refresh sessions`,"aria-label":`Refresh sessions`,onClick:Et,disabled:G,children:(0,x.jsx)(l,{size:12,className:G?`spinning`:void 0})}),(0,x.jsx)(`span`,{children:G?`…`:String(e.length)})]})]}),Y&&(0,x.jsx)(`div`,{className:`side-account-id`,title:`This install's accountId. The first 8 characters match the truncated UUID label on the live Remote Control daemon entry in claude.ai/code.`,children:(0,x.jsx)(`code`,{children:Y})}),(t||n)&&(0,x.jsxs)(`div`,{className:`artefact-filter-chips`,children:[t&&(0,x.jsx)(`button`,{type:`button`,className:`artefact-filter-chip${q?` active`:``}`,"aria-pressed":q,onClick:()=>Be(e=>!e),title:q?`Hide subagent sessions`:`Show subagent sessions`,children:`Subagents`}),n&&(0,x.jsx)(`button`,{type:`button`,className:`artefact-filter-chip${J?` active`:``}`,"aria-pressed":J,onClick:()=>Ve(e=>!e),title:J?`Hide archived sessions`:`Show archived sessions`,children:`Archived`})]}),Fe&&(0,x.jsx)(`div`,{className:`conv`,style:{color:`var(--text-tertiary)`,cursor:`default`},children:Fe}),W&&!Fe&&e.length===0&&(0,x.jsx)(`div`,{className:`conv`,style:{color:`var(--text-tertiary)`,cursor:`default`},children:`No sessions yet`}),e.map(e=>{let t=Pe(e.startedAt),n=qe.has(e.sessionId),r=Ue.has(e.sessionId),i=Ye.has(e.sessionId),a=Ze.has(e.sessionId),o=n||r||i||a,c=()=>{window.location.assign(`/chat?session=${e.sessionId}`)},l=[{key:`open`,label:`Resume session ${e.title} in claude.ai/code`,menuLabel:`Resume in claude.ai/code`,title:`Resume in a fresh local Remote Control PTY`,icon:(0,x.jsx)(s,{size:12}),busy:n,onClick:()=>{kt(e)}},{key:`message`,label:`Open session ${e.title} in admin webchat`,menuLabel:`Open in webchat`,title:`Open in admin webchat (/chat) — sending resumes this session`,icon:(0,x.jsx)(le,{size:12}),onClick:()=>{c()}},...e.live?[{key:`stop`,label:`Stop session ${e.title}`,menuLabel:`Stop`,title:`Stop session process (keeps the conversation, can resume later)`,icon:(0,x.jsx)(k,{size:12}),busy:i,onClick:t=>{Pt(t,e)}}]:[],e.archived?{key:`unarchive`,label:`Unarchive session ${e.title}`,menuLabel:`Unarchive`,title:`Unarchive (move back to the active list)`,icon:(0,x.jsx)(oe,{size:12}),busy:a,onClick:t=>{Ft(t,e,`unarchive`)}}:{key:`archive`,label:`Archive session ${e.title}`,menuLabel:`Archive`,title:`Archive (hide from the list, keeps the conversation resumable)`,icon:(0,x.jsx)(T,{size:12}),busy:a,onClick:t=>{Ft(t,e,`archive`)}},{key:`rename`,label:`Rename session ${e.title}`,menuLabel:`Rename`,title:`Rename this session`,icon:(0,x.jsx)(v,{size:12}),busy:tt&&X===e.sessionId,onClick:t=>{t.stopPropagation(),$e(e.sessionId),Z(e.personName??e.title)}},{key:`delete`,label:`Delete session ${e.title}`,menuLabel:`Delete`,title:`Delete session (stops the process, removes the conversation)`,icon:(0,x.jsx)(f,{size:12}),danger:!0,busy:r,onClick:t=>{Mt(t,e)}},he({sessionId:e.sessionId,model:e.model??null,title:e.personName??e.title})];return(0,x.jsxs)(`div`,{className:`conv conv-with-actions`,children:[(0,x.jsxs)(`div`,{className:`conv-main-static conv-main-clickable`,role:`button`,tabIndex:0,"aria-label":`Open ${e.personName??e.title} in webchat`,onClick:()=>{X!==e.sessionId&&c()},onKeyDown:t=>{X!==e.sessionId&&(t.key===`Enter`||t.key===` `)&&(t.preventDefault(),c())},children:[(0,x.jsx)(`span`,{className:`conv-live-dot`,"data-live":e.live?`1`:`0`,"aria-label":e.live?`live session`:`ended session`}),(0,x.jsxs)(`span`,{className:`conv-stack`,children:[(0,x.jsxs)(`span`,{className:`conv-name-line`,children:[e.channel&&(0,x.jsx)(ne,{channel:e.channel,size:13}),X===e.sessionId?(0,x.jsx)(`input`,{className:`conv-name conv-name-edit`,autoFocus:!0,value:et,disabled:tt,"aria-label":`New title for session ${e.title}`,onChange:e=>Z(e.target.value),onClick:e=>e.stopPropagation(),onKeyDown:t=>{t.key===`Enter`?(t.preventDefault(),It(e)):t.key===`Escape`&&(t.preventDefault(),$e(null),Z(``))},onBlur:()=>{X===e.sessionId&&!tt&&($e(null),Z(``))}}):(0,x.jsx)(`span`,{className:`conv-name`,title:e.personName??e.title,children:e.personName??e.title})]}),(0,x.jsxs)(`span`,{className:`conv-timestamp`,children:[(0,x.jsx)(`code`,{className:`conv-session-id`,title:`First 8 characters of this session's id — distinguishes rows with identical auto-titles. The resume/delete buttons act on the full id.`,children:e.sessionId.slice(0,8)}),t&&(0,x.jsxs)(`span`,{children:[` · `,t]})]})]})]}),(0,x.jsx)(_e,{actions:l,disabled:o,collapsed:lt})]},e.sessionId)})]})})(),F===`whatsapp`&&(0,x.jsxs)(`div`,{className:`side-list`,children:[(0,x.jsxs)(`div`,{className:`group-head`,children:[(0,x.jsx)(`span`,{children:St[gt]}),(0,x.jsxs)(`span`,{className:`group-head-meta`,children:[(0,x.jsx)(`button`,{type:`button`,className:`group-head-refresh`,title:`Refresh conversations`,"aria-label":`Refresh conversations`,onClick:()=>{bt()},children:(0,x.jsx)(l,{size:12})}),(0,x.jsx)(`span`,{children:String(xt.get(gt).length)})]})]}),xt.get(gt).map(e=>{let t=Pe(e.lastMessageAt),n=u(e);return(0,x.jsxs)(`div`,{className:`conv conv-with-actions${e.source===`store`?e.scope===`admin`?` conv-scope-admin`:` conv-scope-public`:``}${w===n?` active`:``}`,children:[(0,x.jsxs)(`button`,{type:`button`,className:`conv-main-static conv-main-btn`,onClick:()=>{A(e),S()},title:e.title,children:[(0,x.jsx)(m,{channel:e.channel,size:14}),(0,x.jsxs)(`span`,{className:`conv-stack`,children:[(0,x.jsx)(`span`,{className:`conv-name-line`,children:(0,x.jsx)(`span`,{className:`conv-name`,children:h(e)})}),t&&(0,x.jsx)(`span`,{className:`conv-timestamp`,children:t})]})]}),e.source===`session`&&(0,x.jsx)(_e,{actions:[he({sessionId:e.sessionId,model:e.model,title:e.title})],disabled:!1,collapsed:lt})]},n)})]}),(0,x.jsx)(ze,{}),(0,x.jsxs)(`div`,{className:`side-foot`,children:[(0,x.jsx)(`div`,{className:`avatar`,children:a?(0,x.jsx)(`img`,{src:a,alt:N}):P}),(0,x.jsxs)(`div`,{className:`who`,children:[(0,x.jsx)(`span`,{className:`name`,children:N}),(0,x.jsx)(`span`,{className:`role`,children:r??`operator`})]})]}),B&&(0,x.jsx)(`div`,{className:`copy-toast${B.failed?` copy-toast-failed`:``}`,role:`status`,children:B.message}),(0,x.jsx)(ke,{error:Ee,onClose:()=>H(null)}),(0,x.jsx)(De,{target:Ge,onCancel:()=>Ke(null),onConfirm:()=>{let e=Ge;Ke(null),e&&Nt(e)}})]})}var Ie=5e3,Le=3e4,Re=3e4;function ze(){let[e,t]=(0,b.useState)(null);if((0,b.useEffect)(()=>{let e=!1,n=null;async function r(){try{let n=await fetch(`/api/admin/system-stats`);if(!n.ok){console.error(`[admin-ui] system-stats-fetch-failed status=${n.status}`);return}let r=await n.json();e||t(r)}catch(e){console.error(`[admin-ui] system-stats-fetch-failed reason=${e instanceof Error?e.message:String(e)}`)}}function i(){n===null&&(r(),n=setInterval(()=>{r()},Ie))}function a(){n!==null&&(clearInterval(n),n=null)}function o(){document.hidden?a():i()}return document.hidden||i(),document.addEventListener(`visibilitychange`,o),()=>{e=!0,a(),document.removeEventListener(`visibilitychange`,o)}},[]),!e||e.platform===`darwin`)return null;let n=e.cpuPct,r=e.memUsedPct,i=n!==null&&n>=.9,a=n!==null&&n>=.98,o=r!==null&&r>=.9,s=r!==null&&r>=.98,c=i||o,l=a||s,u=e=>e===null?`—`:`${Math.round(e*100)}%`,d=e=>{if(e===null)return{width:`0%`,background:`var(--text-tertiary)`};let t=Math.min(1,Math.max(0,e)),n=Math.round(140*(1-t));return{width:`${Math.round(t*100)}%`,background:`hsl(${n}, 65%, 45%)`}},f=[`system-stats`];return c&&f.push(`system-stats--warn`),l&&f.push(`system-stats--crit`),(0,x.jsxs)(`div`,{className:f.join(` `),role:`status`,"aria-label":`host CPU and RAM`,children:[(0,x.jsxs)(`div`,{className:`system-stats__metric`,children:[(0,x.jsxs)(`span`,{className:i?`system-stats__fig system-stats__fig--warn`:`system-stats__fig`,children:[`CPU `,u(n)]}),(0,x.jsx)(`div`,{className:`system-stats__bar`,children:(0,x.jsx)(`div`,{className:`system-stats__bar-fill`,style:d(n)})})]}),(0,x.jsxs)(`div`,{className:`system-stats__metric`,children:[(0,x.jsxs)(`span`,{className:o?`system-stats__fig system-stats__fig--warn`:`system-stats__fig`,children:[`RAM `,u(r)]}),(0,x.jsx)(`div`,{className:`system-stats__bar`,children:(0,x.jsx)(`div`,{className:`system-stats__bar-fill`,style:d(r)})})]})]})}var G=`admin-sidebar-collapsed`,K=`admin-sidebar-drawer-open`;function q(){if(typeof window>`u`)return!1;try{return window.sessionStorage.getItem(G)===`1`}catch{return!1}}function Be(e){if(!(typeof window>`u`))try{e?window.sessionStorage.setItem(G,`1`):window.sessionStorage.removeItem(G)}catch{}}function J(){if(typeof window>`u`)return!1;try{return window.sessionStorage.getItem(K)===`1`}catch{return!1}}function Ve(e){if(!(typeof window>`u`))try{e?window.sessionStorage.setItem(K,`1`):window.sessionStorage.removeItem(K)}catch{}}var Y=720;function He(e,t){return e===`/`?{via:`in-place`}:{via:`navigate`,href:`/?wa=${encodeURIComponent(t.remoteJid??``)}&acct=${encodeURIComponent(t.accountId??``)}`}}function Ue(e){return e===`/`?{via:`in-place`}:{via:`navigate`,href:`/?data=1`}}function We(e,t){if(e!==`/`)return null;let n=new URLSearchParams(t),r=n.get(`wa`),i=n.get(`acct`);return!r||!i?null:{sessionId:``,projectDir:``,title:``,senderId:null,startedAt:``,channel:`whatsapp`,role:`public`,operatorName:null,whatsappName:null,lastMessageAt:null,modelGated:!1,model:null,source:`store`,accountId:i,remoteJid:r}}function Ge(e,t){return e===`/`?new URLSearchParams(t).has(`data`):!1}function Ke(e,t){return t===`operator`&&e===`chat`||e===`dashboard`?`/`:e===`data`?`/data`:e===`graph`?`/graph`:e===`calendar`?`/calendar`:e===`chat`?`/chat`:`/browser`}function qe(e){let{cacheKey:t,businessName:n,variant:r=`admin`,onLogout:i,onDisconnect:a,disconnecting:o,userName:s,userAvatar:c,role:l,onOpenConversations:f,children:m,footer:h}=e,{subAccounts:g,activeAccountId:ee,switching:_,switchAccount:ne}=te(t),[v,re]=(0,b.useState)(()=>q()),[y,ie]=(0,b.useState)(()=>J()),[ae,S]=(0,b.useState)(()=>typeof window<`u`&&window.matchMedia(`(max-width: ${Y}px)`).matches),[C,w]=(0,b.useState)(()=>typeof window>`u`||Ge(window.location.pathname,window.location.search)?null:We(window.location.pathname,window.location.search)),[oe]=(0,b.useState)(()=>C!==null),[T,E]=(0,b.useState)(()=>typeof window>`u`?!1:Ge(window.location.pathname,window.location.search)),[se]=(0,b.useState)(()=>T);(0,b.useEffect)(()=>{if(typeof window>`u`)return;let e=window.matchMedia(`(max-width: ${Y}px)`),t=e=>S(e.matches);return e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]);let ce=(0,b.useCallback)(e=>{Be(e),re(e)},[]),D=(0,b.useCallback)(e=>{Ve(e),ie(e)},[]);(0,b.useEffect)(()=>{if(typeof window>`u`)return;let e=window.location?.pathname??``;console.info(`[admin-ui] shell-mount route=${e} variant=${r} sidebar=${r===`operator`?`none`:`present`} collapsed=${v} drawer=${y}`)},[]),(0,b.useEffect)(()=>{typeof window>`u`||!C||(console.info(`[admin-ui] wa-hydrate route=/ remoteJid=${C.remoteJid??``}`),window.history.replaceState(null,``,`/`))},[]),(0,b.useEffect)(()=>{typeof window>`u`||!T||(console.info(`[admin-ui] data-hydrate route=/`),window.history.replaceState(null,``,`/`))},[]);let le=(0,b.useCallback)(e=>{if(E(!1),e===null){w(null);return}let t=typeof window<`u`?window.location.pathname:`/`,n=He(t,e);console.info(`[admin-ui] wa-open route=${t} via=${n.via} sessionId=${e.sessionId.slice(0,8)}`),n.via===`in-place`?w(e):window.location.href=n.href},[]),ue=(0,b.useCallback)(()=>{let e=typeof window<`u`?window.location.pathname:`/`,t=Ue(e);if(console.info(`[admin-ui] data-open route=${e} via=${t.via}`),t.via===`navigate`){window.location.href=t.href;return}w(null),E(!0)},[]),O=ae?y:!v,k=(0,b.useCallback)(()=>{if(!(typeof window>`u`))if(window.matchMedia(`(max-width: ${Y}px)`).matches){let e=y;console.info(`[admin-ui] header-sidebar-toggle from=${e?`open`:`closed`} mode=drawer`),D(!e)}else{let e=v;console.info(`[admin-ui] header-sidebar-toggle from=${e?`closed`:`open`} mode=collapse`),ce(!e)}},[v,y,ce,D]),de=(0,b.useCallback)(e=>{let t=Ke(e,r);console.info(`[admin-ui] header-menu-nav target=${e} dest=${t}`),window.location.href=t},[r]),A={collapsed:v,mobileDrawerOpen:y,sidebarOpen:O,onToggleSidebar:k,setMobileDrawerOpen:D,selectedWhatsapp:C,onClearWhatsapp:()=>w(null),dataOpen:T};return r===`operator`?(0,x.jsxs)(`div`,{className:`admin-shell admin-page admin-shell-root`,children:[(0,x.jsx)(d,{businessName:n,variant:r,onNavigate:de,onOpenConversations:f,onToggleSidebar:k,sidebarOpen:O,onLogout:i,onDisconnect:a,disconnecting:o,cacheKey:t,subAccounts:g,activeAccountId:ee,switchingAccount:_,onSwitchAccount:ne}),(0,x.jsx)(`div`,{className:`platform platform-operator`,children:typeof m==`function`?m(A):m}),h]}):(0,x.jsxs)(`div`,{className:`admin-shell admin-page admin-shell-root`,children:[(0,x.jsx)(d,{businessName:n,variant:r,onNavigate:de,onOpenConversations:f,onToggleSidebar:k,sidebarOpen:O,onLogout:i,onDisconnect:a,disconnecting:o,cacheKey:t,subAccounts:g,activeAccountId:ee,switchingAccount:_,onSwitchAccount:ne}),(0,x.jsxs)(`div`,{className:`platform${y?` menu-open`:``}${v?` sidebar-collapsed`:``}`,"data-artefact":`closed`,children:[(0,x.jsx)(Fe,{businessName:n,cacheKey:t,role:l??null,userName:s,userAvatar:c??null,onSelectProjects:()=>{window.location.href=`/graph?label=${p().label}`},onSelectPeople:()=>{window.location.href=`/graph?label=Person`},onSelectTasks:()=>{window.location.href=`/graph?label=Task`},onSelectAgents:()=>{window.location.href=`/graph?label=Agent`},onCloseMobileDrawer:()=>D(!1),collapsed:v,mobileDrawerOpen:y,selectedWhatsappId:C?u(C):null,onSelectWhatsappConversation:le,initialWhatsappSurface:oe,onSelectData:ue,onCloseData:()=>E(!1),initialDataSurface:se}),!ae&&(0,x.jsx)(me,{}),typeof m==`function`?m(A):m]}),y&&(0,x.jsx)(`div`,{className:`sidebar-backdrop menu-open`,"aria-hidden":`true`,onClick:()=>D(!1)}),h]})}export{oe as _,F as a,ye as c,k as d,O as f,T as g,E as h,U as i,L as l,D as m,Ne as n,N as o,ue as p,W as r,P as s,qe as t,de as u,w as v,ae as y};
|