@rubytech/create-sitedesk-code 0.1.438 → 0.1.440

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.
Files changed (81) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -4
  3. package/payload/platform/plugins/docs/references/admin-identity-gate.md +32 -17
  4. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  5. package/payload/platform/plugins/docs/references/internals.md +4 -0
  6. package/payload/platform/plugins/docs/references/outlook-guide.md +2 -2
  7. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
  8. package/payload/platform/plugins/docs/references/troubleshooting.md +8 -0
  9. package/payload/platform/plugins/graph/.claude-plugin/plugin.json +19 -0
  10. package/payload/platform/plugins/graph/PLUGIN.md +10 -4
  11. package/payload/platform/plugins/graph-viewer/.claude-plugin/plugin.json +13 -0
  12. package/payload/platform/plugins/graph-viewer/PLUGIN.md +1 -1
  13. package/payload/platform/plugins/graph-viewer/lib/mcp-spawn-tee/index.js +193 -0
  14. package/payload/platform/plugins/graph-viewer/lib/mcp-spawn-tee/package.json +3 -0
  15. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.d.ts +2 -0
  16. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.js +55 -0
  18. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-list.test.js.map +1 -0
  19. package/payload/platform/plugins/outlook/mcp/dist/index.js +1 -1
  20. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  21. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +17 -0
  22. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
  23. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +84 -21
  24. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
  25. package/payload/platform/plugins/outlook/references/graph-surfaces.md +15 -1
  26. package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +100 -0
  27. package/payload/platform/scripts/lib/account-schema-owned-dirs.py +207 -0
  28. package/payload/platform/scripts/lib/account-schema-owned-dirs.sh +33 -0
  29. package/payload/platform/scripts/lib/provision-account-dir.sh +7 -0
  30. package/payload/platform/scripts/setup-account.sh +10 -0
  31. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  32. package/payload/platform/services/claude-session-manager/dist/http-server.js +6 -4
  33. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  34. package/payload/platform/services/claude-session-manager/dist/index.js +36 -0
  35. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  36. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.d.ts +58 -0
  37. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.d.ts.map +1 -0
  38. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.js +196 -0
  39. package/payload/platform/services/claude-session-manager/dist/tool-registration-drift.js.map +1 -0
  40. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/.claude-plugin/plugin.json +13 -0
  41. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/PLUGIN.md +2 -1
  42. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/lib/mcp-spawn-tee/index.js +193 -0
  43. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/lib/mcp-spawn-tee/package.json +3 -0
  44. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/lib/gates.d.ts +7 -1
  45. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/lib/gates.d.ts.map +1 -1
  46. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/lib/gates.js +8 -2
  47. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/lib/gates.js.map +1 -1
  48. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/src/lib/__tests__/gates.test.ts +13 -0
  49. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/src/lib/__tests__/render-run.test.ts +11 -0
  50. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/src/lib/gates.ts +8 -2
  51. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/skills/quotation/SKILL.md +4 -2
  52. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/skills/quotation/references/quote-engine.md +8 -0
  53. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/skills/quotation/references/verification.md +12 -0
  54. package/payload/server/{chunk-UGPQCVIW.js → chunk-WC4WJR6X.js} +0 -6
  55. package/payload/server/maxy-edge.js +1 -1
  56. package/payload/server/public/assets/{AdminLoginScreens-DeR71H29.js → AdminLoginScreens-CukwQufW.js} +1 -1
  57. package/payload/server/public/assets/AdminShell-BwEBH6tT.js +2 -0
  58. package/payload/server/public/assets/{admin-C15h-NL0.js → admin-CdFRCUUZ.js} +1 -1
  59. package/payload/server/public/assets/{browser-B7xQLIRK.js → browser-BWR59IkB.js} +1 -1
  60. package/payload/server/public/assets/{calendar-A-vGm6pL.js → calendar-kuTcxX1t.js} +1 -1
  61. package/payload/server/public/assets/chat-ejnq2rck.js +1 -0
  62. package/payload/server/public/assets/data-dZbOyLdT.js +1 -0
  63. package/payload/server/public/assets/{graph-D0orIRAh.js → graph-NgQDY0Pb.js} +1 -1
  64. package/payload/server/public/assets/{operator-DZzuotDB.js → operator-oSaB1mya.js} +1 -1
  65. package/payload/server/public/assets/{page-Ck_pLhAk.js → page-DEkI-Gl1.js} +1 -1
  66. package/payload/server/public/assets/{page-3g0A19Rv.js → page-DMeJHVfA.js} +5 -5
  67. package/payload/server/public/assets/{public-WFUQw9AD.js → public-C54tl6V4.js} +1 -1
  68. package/payload/server/public/assets/{useCopyFeedback-CSk9KjhY.js → useCopyFeedback-DHoYHiK9.js} +1 -1
  69. package/payload/server/public/assets/{useVoiceRecorder-DCyJQkFK.js → useVoiceRecorder-b7HzTFs0.js} +1 -1
  70. package/payload/server/public/browser.html +2 -2
  71. package/payload/server/public/calendar.html +2 -2
  72. package/payload/server/public/chat.html +5 -5
  73. package/payload/server/public/data.html +3 -3
  74. package/payload/server/public/graph.html +3 -3
  75. package/payload/server/public/index.html +5 -5
  76. package/payload/server/public/operator.html +6 -6
  77. package/payload/server/public/public.html +5 -5
  78. package/payload/server/server.js +186 -112
  79. package/payload/server/public/assets/AdminShell-DlAVQUda.js +0 -2
  80. package/payload/server/public/assets/chat-DAae6Z51.js +0 -1
  81. package/payload/server/public/assets/data-BNWpKx1d.js +0 -1
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * MCP spawn-tee — in-process stderr capture + lifecycle observability.
5
+ *
6
+ * Claude Code spawns each MCP server itself; the platform never holds a
7
+ * ChildProcess handle. This shim sits between Claude Code and the real MCP
8
+ * server: Claude Code runs `node <this> <real-entry>`, and the shim runs the
9
+ * real entry IN ITS OWN PROCESS via dynamic import() — one node runtime, not
10
+ * two. Before importing, it replaces process.stderr.write with a tee so every
11
+ * stderr byte the server writes is mirrored to the log sinks; stdin and stdout
12
+ * (the JSON-RPC channel) are never touched.
13
+ *
14
+ * Claude Code CLI → shim (this file) = node running <real-entry> in-process
15
+ * stdin/stdout : untouched (JSON-RPC channel)
16
+ * stderr : process.stderr.write teed → per-date + per-session + passthrough
17
+ *
18
+ * Destinations (Task 706) — unchanged:
19
+ * - `${LOG_DIR}/mcp-<name>-stderr-<date>.log` — per-date raw (back-compat;
20
+ * the in-process mcp-stderr-tee and the [mcp-init-error] probe read it).
21
+ * - `${LOG_DIR}/mcp-<name>-<SESSION_ID>.log` — per-session raw + lifecycle
22
+ * lines. Authoritative sink. Absent when SESSION_ID is unset (enumeration
23
+ * spawn) — then the per-date file is the fallback.
24
+ * - `server.log` via the loopback log-ingest route — best-effort mirror of
25
+ * the [mcp-helper] lifecycle lines.
26
+ *
27
+ * Lifecycle lines, correlation key `session=<id8> server=<name>` — unchanged:
28
+ * [mcp-helper] op=spawn ... pid= entry=
29
+ * [mcp-helper] op=boot ... head=<first stderr bytes>
30
+ * [mcp-helper] op=exit ... code= signal= lifetimeMs= stderr-tail=
31
+ *
32
+ * Process model (Task 989): the shim IS the server's process. op=exit fires
33
+ * from process.on("exit"), so it covers a normal exit, a non-zero exit, an
34
+ * uncaught throw (code 1), and a catchable-signal exit (SIGTERM/SIGINT/SIGHUP,
35
+ * whose handlers record the signal name). An external SIGKILL is uncatchable
36
+ * and leaves no op=exit line — the per-session stderr tail already on disk and
37
+ * Claude Code's own transport-drop are the evidence for that death mode.
38
+ *
39
+ * The shim never writes to fd 1 (stdout) — that is the JSON-RPC channel.
40
+ */
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ const node_fs_1 = require("node:fs");
43
+ const node_path_1 = require("node:path");
44
+ const node_url_1 = require("node:url");
45
+ const SERVER_NAME = process.env.MCP_SPAWN_TEE_NAME ?? "unknown";
46
+ const LOG_DIR = process.env.LOG_DIR;
47
+ const SESSION_ID = process.env.SESSION_ID;
48
+ const PLATFORM_PORT = process.env.PLATFORM_PORT;
49
+ const ENTRY = process.argv[2];
50
+ const SESSION_ID8 = SESSION_ID ? SESSION_ID.slice(0, 8) : "—";
51
+ const spawnStamp = Date.now();
52
+ // Per-session raw + lifecycle log (Task 706). Empty SESSION_ID (enumeration
53
+ // spawn) → undefined, and the per-date file is the fallback.
54
+ const perSessionPath = LOG_DIR && SESSION_ID
55
+ ? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-${SESSION_ID}.log`)
56
+ : undefined;
57
+ const perDatePath = LOG_DIR
58
+ ? (0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-stderr-${new Date(spawnStamp).toISOString().slice(0, 10)}.log`)
59
+ : undefined;
60
+ // Rolling tail of entry stderr for op=exit. Capped so a chatty server cannot
61
+ // grow the buffer without bound.
62
+ const TAIL_CAP = 2048;
63
+ let stderrTail = "";
64
+ let bootEmitted = false;
65
+ let exitEmitted = false;
66
+ let exitSignal;
67
+ // The real stderr writer, captured before the tee replaces it. Lifecycle lines
68
+ // and stderr passthrough both go through this so they are never re-teed into
69
+ // the per-date sink nor recursed back into the patched writer.
70
+ const rawStderrWrite = process.stderr.write.bind(process.stderr);
71
+ // LOG_DIR is created once, lazily, on the first successful append — not per
72
+ // chunk. teeWrite runs on the server's own stderr hot path now, so a per-call
73
+ // mkdirSync would be two syscalls per log line for nothing.
74
+ let logDirReady = false;
75
+ function appendSafe(path, data) {
76
+ if (!path || !LOG_DIR)
77
+ return;
78
+ try {
79
+ if (!logDirReady) {
80
+ (0, node_fs_1.mkdirSync)(LOG_DIR, { recursive: true });
81
+ logDirReady = true;
82
+ }
83
+ (0, node_fs_1.appendFileSync)(path, data);
84
+ }
85
+ catch {
86
+ /* unwritable destination — never mask primary output */
87
+ }
88
+ }
89
+ // Best-effort mirror of a lifecycle line to server.log via the loopback
90
+ // log-ingest route. Fire-and-forget: the per-session file is authoritative, so
91
+ // a dropped POST loses nothing. On the "exit" event the loop is stopping, so
92
+ // the op=exit mirror may not flush — the per-session line, written sync, holds.
93
+ function postToServerLog(suffix, level) {
94
+ if (!PLATFORM_PORT)
95
+ return;
96
+ try {
97
+ const ctrl = new AbortController();
98
+ const t = setTimeout(() => ctrl.abort(), 500);
99
+ t.unref?.(); // never let the abort timer keep the shim's event loop alive
100
+ void fetch(`http://127.0.0.1:${PLATFORM_PORT}/api/admin/log-ingest`, {
101
+ method: "POST",
102
+ headers: { "content-type": "application/json" },
103
+ body: JSON.stringify({ tag: "mcp-helper", level, line: suffix }),
104
+ signal: ctrl.signal,
105
+ }).then(() => clearTimeout(t)).catch(() => clearTimeout(t));
106
+ }
107
+ catch {
108
+ /* fetch threw synchronously — best-effort only */
109
+ }
110
+ }
111
+ // Emit one [mcp-helper] lifecycle line: authoritative per-session file (sync,
112
+ // survives process.exit), the shim's own stderr via the RAW writer (journald /
113
+ // Claude Code visibility, never re-teed), and a best-effort server.log mirror.
114
+ // `suffix` is the line body after the tag and carries no newline (the
115
+ // log-ingest route rejects newlines).
116
+ function emitLifecycle(suffix, level) {
117
+ const line = `[mcp-helper] ${suffix}\n`;
118
+ appendSafe(perSessionPath, line);
119
+ try {
120
+ rawStderrWrite(line);
121
+ }
122
+ catch { /* stderr closed */ }
123
+ postToServerLog(suffix, level);
124
+ }
125
+ if (!ENTRY) {
126
+ emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason="no entry given (argv[2] missing)"`, "error");
127
+ process.exit(2);
128
+ }
129
+ // Replace process.stderr.write with a tee: mirror every server stderr byte to
130
+ // the per-date and per-session sinks, keep a rolling tail for op=exit, emit
131
+ // op=boot on the first bytes, then pass the write through to the real stderr.
132
+ const teeWrite = ((...args) => {
133
+ const chunk = args[0];
134
+ appendSafe(perDatePath, chunk); // per-date raw (back-compat)
135
+ appendSafe(perSessionPath, chunk); // per-session raw (Task 706)
136
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
137
+ stderrTail = (stderrTail + text).slice(-TAIL_CAP);
138
+ if (!bootEmitted) {
139
+ bootEmitted = true;
140
+ const head = text.split("\n")[0].slice(0, 200);
141
+ emitLifecycle(`op=boot session=${SESSION_ID8} server=${SERVER_NAME} head=${JSON.stringify(head)}`, "info");
142
+ }
143
+ return rawStderrWrite(...args); // passthrough
144
+ });
145
+ process.stderr.write = teeWrite;
146
+ // Catchable-signal handling. The shim drives the exit ONLY when it is the sole
147
+ // listener for the signal — i.e. the imported entry installed no handler of its
148
+ // own. In that case it records the signal (so op=exit carries signal=<sig>) and
149
+ // exits with 128+signum, mirroring the prior wrapper's exit-status convention.
150
+ //
151
+ // When the entry DID install a handler, the shim defers entirely and records
152
+ // nothing: the entry's handler decides the exit code, and op=exit reflects that
153
+ // exit verbatim. This matches the prior two-process model, where a child that
154
+ // caught the signal and exited cleanly was observed as code=0 signal=— (a clean
155
+ // self-exit), not as a signal death. Setting exitSignal here unconditionally
156
+ // would mislabel every graceful signal-driven shutdown as a signal kill.
157
+ function onSignal(sig, code) {
158
+ return () => {
159
+ if (process.listenerCount(sig) === 1) {
160
+ exitSignal = sig;
161
+ process.exit(code);
162
+ }
163
+ };
164
+ }
165
+ process.on("SIGTERM", onSignal("SIGTERM", 143));
166
+ process.on("SIGINT", onSignal("SIGINT", 130));
167
+ process.on("SIGHUP", onSignal("SIGHUP", 129));
168
+ // op=exit, emitted once from the process "exit" event. Sync-only — the loop is
169
+ // stopping. When a catchable signal caused the exit, report code=— signal=<sig>
170
+ // to match the prior wrapper's format; otherwise code=<exit code> signal=—.
171
+ process.on("exit", (code) => {
172
+ if (exitEmitted)
173
+ return;
174
+ exitEmitted = true;
175
+ const lifetimeMs = Date.now() - spawnStamp;
176
+ const tail = stderrTail.slice(-200);
177
+ const codeField = exitSignal ? "—" : String(code);
178
+ const level = exitSignal || code !== 0 ? "error" : "info";
179
+ emitLifecycle(`op=exit session=${SESSION_ID8} server=${SERVER_NAME} pid=${process.pid} ` +
180
+ `code=${codeField} signal=${exitSignal ?? "—"} lifetimeMs=${lifetimeMs} stderr-tail=${JSON.stringify(tail)}`, level);
181
+ });
182
+ emitLifecycle(`op=spawn session=${SESSION_ID8} server=${SERVER_NAME} pid=${process.pid} entry=${ENTRY}`, "info");
183
+ // Run the real MCP server in THIS process. Dynamic import() (not top-level
184
+ // await — this file compiles to CommonJS) loads the ESM entry; a load-time
185
+ // failure mirrors the old child spawn-error path (op=error, exit 127). The
186
+ // op=exit handler is suppressed on this path so op=error stays the sole record.
187
+ import((0, node_url_1.pathToFileURL)(ENTRY).href).catch((err) => {
188
+ const msg = err instanceof Error ? err.message : String(err);
189
+ exitEmitted = true;
190
+ emitLifecycle(`op=error session=${SESSION_ID8} server=${SERVER_NAME} reason=${JSON.stringify(`import error: ${msg}`)}`, "error");
191
+ process.exit(127);
192
+ });
193
+ //# sourceMappingURL=index.js.map
@@ -11,7 +11,13 @@ export interface SignLinkVerdict {
11
11
  reason?: "no-anchor" | "empty" | "fragment" | "non-https" | "relative";
12
12
  }
13
13
  /** The document's displayed grand total (data-quote-total) must equal the
14
- * engine total. No marker fail closed (an unverifiable total is not shipped). */
14
+ * engine total at penny precision money is penny-precise, so a sub-penny
15
+ * engine total (e.g. 3395.235) reconciles against the penny total the client
16
+ * document shows (3395.24). A genuine >=1p discrepancy still fails. Both sides
17
+ * are rounded to pennies with toFixed(2) — the same rounding an operator's
18
+ * render performs — so an engine total sitting exactly on a half-penny (2.045)
19
+ * reconciles rather than diverging, which a *100 multiply-then-round would.
20
+ * No marker → fail closed (an unverifiable total is not shipped). */
15
21
  export declare function gateReconcile(html: string, engineTotal: number): ReconcileVerdict;
16
22
  /** A client document carrying a cost/margin marker (data-internal-only) is a
17
23
  * leak — the boundary is structural, not a matter of remembering to omit. */
@@ -1 +1 @@
1
- {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/lib/gates.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AACnE,MAAM,WAAW,WAAW;IAAG,IAAI,EAAE,OAAO,CAAA;CAAE;AAC9C,MAAM,WAAW,eAAe;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,CAAA;CAAE;AAEzI;oFACoF;AACpF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAMjF;AAED;8EAC8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAExD;AAED;+EAC+E;AAC/E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAS1D"}
1
+ {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/lib/gates.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AACnE,MAAM,WAAW,WAAW;IAAG,IAAI,EAAE,OAAO,CAAA;CAAE;AAC9C,MAAM,WAAW,eAAe;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,CAAA;CAAE;AAEzI;;;;;;;sEAOsE;AACtE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAMjF;AAED;8EAC8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAExD;AAED;+EAC+E;AAC/E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAS1D"}
@@ -5,7 +5,13 @@
5
5
  // data-internal-only cost/margin-bearing figures (must be absent from client docs)
6
6
  // data-esign-accept the client quote's Sign-Online anchor (absolute https only)
7
7
  /** The document's displayed grand total (data-quote-total) must equal the
8
- * engine total. No marker fail closed (an unverifiable total is not shipped). */
8
+ * engine total at penny precision money is penny-precise, so a sub-penny
9
+ * engine total (e.g. 3395.235) reconciles against the penny total the client
10
+ * document shows (3395.24). A genuine >=1p discrepancy still fails. Both sides
11
+ * are rounded to pennies with toFixed(2) — the same rounding an operator's
12
+ * render performs — so an engine total sitting exactly on a half-penny (2.045)
13
+ * reconciles rather than diverging, which a *100 multiply-then-round would.
14
+ * No marker → fail closed (an unverifiable total is not shipped). */
9
15
  export function gateReconcile(html, engineTotal) {
10
16
  const m = html.match(/data-quote-total="([^"]*)"/i);
11
17
  if (!m || m[1].trim() === "")
@@ -13,7 +19,7 @@ export function gateReconcile(html, engineTotal) {
13
19
  const shown = Number(m[1]);
14
20
  if (!Number.isFinite(shown))
15
21
  return { pass: false };
16
- return { pass: shown === engineTotal, shown };
22
+ return { pass: Number(shown.toFixed(2)) === Number(engineTotal.toFixed(2)), shown };
17
23
  }
18
24
  /** A client document carrying a cost/margin marker (data-internal-only) is a
19
25
  * leak — the boundary is structural, not a matter of remembering to omit. */
@@ -1 +1 @@
1
- {"version":3,"file":"gates.js","sourceRoot":"","sources":["../../src/lib/gates.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,6DAA6D;AAC7D,gFAAgF;AAChF,qFAAqF;AACrF,mFAAmF;AAMnF;oFACoF;AACpF,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,WAAmB;IAC7D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACpD,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACrD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACpD,OAAO,EAAE,IAAI,EAAE,KAAK,KAAK,WAAW,EAAE,KAAK,EAAE,CAAC;AAChD,CAAC;AAED;8EAC8E;AAC9E,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,EAAE,IAAI,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACzD,CAAC;AAED;+EAC+E;AAC/E,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,qCAAqC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACtD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACrD,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3D,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC/D,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC3E,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACzF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACnD,CAAC"}
1
+ {"version":3,"file":"gates.js","sourceRoot":"","sources":["../../src/lib/gates.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,6DAA6D;AAC7D,gFAAgF;AAChF,qFAAqF;AACrF,mFAAmF;AAMnF;;;;;;;sEAOsE;AACtE,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,WAAmB;IAC7D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACpD,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACrD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACpD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;AACtF,CAAC;AAED;8EAC8E;AAC9E,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,EAAE,IAAI,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACzD,CAAC;AAED;+EAC+E;AAC/E,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,qCAAqC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACtD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACrD,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3D,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC/D,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC3E,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACzF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACnD,CAAC"}
@@ -14,6 +14,19 @@ describe("gateReconcile — displayed total must equal the engine total", () =>
14
14
  it("fails closed on an empty data-quote-total marker, even when the engine total is zero", () => {
15
15
  expect(gateReconcile('<p data-quote-total="">x</p>', 0)).toEqual({ pass: false });
16
16
  });
17
+ it("passes when the displayed penny total equals a sub-penny engine total rounded to pennies", () => {
18
+ expect(gateReconcile('<p data-quote-total="3395.24">x</p>', 3395.235)).toEqual({ pass: true, shown: 3395.24 });
19
+ });
20
+ it("passes at an exact half-penny engine total (2.045 renders 2.04, both round the same way)", () => {
21
+ // guards the *100-multiply divergence: Math.round(2.045*100)=205 but toFixed(2)=2.04
22
+ expect(gateReconcile('<p data-quote-total="2.04">x</p>', 2.045)).toEqual({ pass: true, shown: 2.04 });
23
+ });
24
+ it("fails a genuine 1-penny discrepancy (tightest boundary)", () => {
25
+ expect(gateReconcile('<p data-quote-total="3395.24">x</p>', 3395.25)).toEqual({ pass: false, shown: 3395.24 });
26
+ });
27
+ it("fails a genuine multi-pound discrepancy", () => {
28
+ expect(gateReconcile('<p data-quote-total="3395.24">x</p>', 3396.0)).toEqual({ pass: false, shown: 3395.24 });
29
+ });
17
30
  });
18
31
 
19
32
  describe("gateMarginLeak — client docs must not carry cost/margin markers", () => {
@@ -37,6 +37,9 @@ process.stdin.on("end", () => {
37
37
  }));
38
38
  });
39
39
  `;
40
+ // operator's render penny-rounds the display (toFixed(2)) while the engine total
41
+ // is sub-penny → the doc shows the penny total, the engine total is sub-penny
42
+ const SUBPENNY = GOOD.replace("const t = figures.total;", "const t = figures.total.toFixed(2);");
40
43
  const THROW = `process.stdin.resume(); process.exit(1);`;
41
44
 
42
45
  let dir: string;
@@ -51,6 +54,7 @@ beforeAll(() => {
51
54
  writeFileSync(join(dir, "quoting", "render-leak.mjs"), LEAK);
52
55
  writeFileSync(join(dir, "quoting", "render-deadlink.mjs"), DEADLINK);
53
56
  writeFileSync(join(dir, "quoting", "render-wrongtotal.mjs"), WRONGTOTAL);
57
+ writeFileSync(join(dir, "quoting", "render-subpenny.mjs"), SUBPENNY);
54
58
  writeFileSync(join(dir, "quoting", "render-throw.mjs"), THROW);
55
59
  });
56
60
  afterAll(() => rmSync(dir, { recursive: true, force: true }));
@@ -85,6 +89,13 @@ describe("runRender — a failing gate is fail-closed and atomic (no doc filed)"
85
89
  const r = await runRender({ accountDir: dir, renderScriptPath: rs("render-wrongtotal.mjs"), figures, signUrl, jobId: "job-wt" });
86
90
  expect(r).toMatchObject({ ok: false, reason: "reconcile-fail" });
87
91
  });
92
+ it("reconciles when the engine total is sub-penny and the docs show the penny-rounded total", async () => {
93
+ const subFigures = { total: 3395.235, header: { phone: "44700900000" }, lines: [] };
94
+ const r = await runRender({ accountDir: dir, renderScriptPath: rs("render-subpenny.mjs"), figures: subFigures, signUrl, jobId: "job-sub" });
95
+ expect(r.ok).toBe(true);
96
+ if (!r.ok) return;
97
+ expect(r.reconcile).toEqual({ internal: true, breakdown: true, quote: true });
98
+ });
88
99
  it("render-error when the figures header phone contains a path traversal", async () => {
89
100
  const evil = { total: 1250, header: { phone: "../../etc" }, lines: [] };
90
101
  const r = await runRender({ accountDir: dir, renderScriptPath: rs("render.mjs"), figures: evil, signUrl, jobId: "job-evil" });
@@ -10,13 +10,19 @@ export interface LeakVerdict { pass: boolean }
10
10
  export interface SignLinkVerdict { pass: boolean; href?: string; reason?: "no-anchor" | "empty" | "fragment" | "non-https" | "relative" }
11
11
 
12
12
  /** The document's displayed grand total (data-quote-total) must equal the
13
- * engine total. No marker fail closed (an unverifiable total is not shipped). */
13
+ * engine total at penny precision money is penny-precise, so a sub-penny
14
+ * engine total (e.g. 3395.235) reconciles against the penny total the client
15
+ * document shows (3395.24). A genuine >=1p discrepancy still fails. Both sides
16
+ * are rounded to pennies with toFixed(2) — the same rounding an operator's
17
+ * render performs — so an engine total sitting exactly on a half-penny (2.045)
18
+ * reconciles rather than diverging, which a *100 multiply-then-round would.
19
+ * No marker → fail closed (an unverifiable total is not shipped). */
14
20
  export function gateReconcile(html: string, engineTotal: number): ReconcileVerdict {
15
21
  const m = html.match(/data-quote-total="([^"]*)"/i);
16
22
  if (!m || m[1].trim() === "") return { pass: false };
17
23
  const shown = Number(m[1]);
18
24
  if (!Number.isFinite(shown)) return { pass: false };
19
- return { pass: shown === engineTotal, shown };
25
+ return { pass: Number(shown.toFixed(2)) === Number(engineTotal.toFixed(2)), shown };
20
26
  }
21
27
 
22
28
  /** A client document carrying a cost/margin marker (data-internal-only) is a
@@ -44,7 +44,7 @@ Everything this skill does lives under `references/`. Reach for one by what you
44
44
  - `references/manual-definition.md` — build or edit the method by hand for an operator with no usable history, or to make an adjustment.
45
45
 
46
46
  **Keep it correct**
47
- - `references/verification.md` — accept the method only when it reproduces the operator's own past quotes to their own precision; the report is a per-level delta, never a boolean.
47
+ - `references/verification.md` — accept the method only when it reproduces the operator's own past quotes to their own precision; the report is a per-level delta, never a boolean, and the reproduction that accepts the method runs through the `quote-engine-run` tool in `mode: "verify"`, never a hand-run.
48
48
  - `references/maintenance.md` — re-learn or edit while preserving prior manual adjustments, then re-verify green.
49
49
 
50
50
  **Apply it to produce a quote**
@@ -69,7 +69,9 @@ Two things are always visible:
69
69
  ambiguous, or guessed — is logged as such, so gaps are seen at onboarding, not later in a wrong quote.
70
70
  - **A reconciliation report.** The standing check is reproduction against the operator's own history:
71
71
  per past job, the **delta** between the reproduced figures and the operator's own, at line, group,
72
- and final level — a delta, never a boolean. `references/verification.md` owns why and how.
72
+ and final level — a delta, never a boolean. That reproduction runs through `quote-engine-run` in
73
+ `mode: "verify"`, so an accepted method leaves an observable `[quote-engine] op=verify` line rather
74
+ than passing silently through a hand-run. `references/verification.md` owns why and how.
73
75
 
74
76
  ## Scope
75
77
 
@@ -135,6 +135,14 @@ history, supplied with their method; there are no built-in figures to test again
135
135
  history, not parsing cleanly, is the signal that the engine prices the way they do. Verify on every
136
136
  generate and every method change; a reproduction failure blocks issuing a computed quote.
137
137
 
138
+ The reproduction that **signs off** a method is produced by the `sitedesk-job:quote-engine-run` tool
139
+ in `mode: "verify"` — the same deterministic apply-path tool that runs the persisted engine — never a
140
+ hand-run of the engine or a side script. A reproduction produced any other way is not acceptance. This
141
+ closes the gap where an engine the tool cannot run (a non-node or non-conformant script), or one the
142
+ owner's live path bypasses, is declared verified through a parallel run the shipped tools never touch.
143
+ Every tool verify leaves a `[quote-engine] op=verify jobId=… worst=…` line; that line is the evidence
144
+ the sign-off actually ran, and its absence for a method claimed verified is the bypass to catch.
145
+
138
146
  ## Boundaries
139
147
 
140
148
  - **Capturing and maintaining** the content of the method (items, rules, dials, roll-up, past-job
@@ -37,3 +37,15 @@ The compute that turns the captured method plus a job into reproduced figures
37
37
  runs that compute against the operator's past jobs. This reference owns the acceptance discipline:
38
38
  which jobs to reproduce, that the comparison is a per-level delta, and that reproduction to the
39
39
  operator's precision is the bar.
40
+
41
+ ## The reproduction runs through the tool
42
+
43
+ The delta report that accepts a method is the output of `quote-engine-run` in `mode: "verify"` against
44
+ the operator's past jobs — not a hand-run of the engine and not a side script. A method declared
45
+ verified with no corresponding `mode: "verify"` tool run is **not** accepted, however exactly a
46
+ hand-run reproduces the figures: the sign-off is the tool run, because that is the path a live quote
47
+ takes. Verifying by any parallel route — a script in another language, an engine the tool cannot
48
+ execute, a direct invocation that bypasses the persisted `quoting/engine.mjs` — proves nothing about
49
+ the quote the operator will actually issue. The tool records each verify as `[quote-engine] op=verify
50
+ jobId=… worst=…`; absence of that line for a method claimed verified is the bypass this discipline
51
+ exists to catch.
@@ -4031,11 +4031,6 @@ function setWantsPriorConversation(cacheKey2) {
4031
4031
  function unregisterSession(cacheKey2) {
4032
4032
  return sessionStore.delete(cacheKey2);
4033
4033
  }
4034
- function retagAccountForUser(userId, accountId) {
4035
- for (const session of sessionStore.values()) {
4036
- if (session.userId === userId) session.accountId = accountId;
4037
- }
4038
- }
4039
4034
  function getSessionKeyBySessionId(_sessionId) {
4040
4035
  return void 0;
4041
4036
  }
@@ -5961,7 +5956,6 @@ export {
5961
5956
  setSessionIdForSession,
5962
5957
  setWantsPriorConversation,
5963
5958
  unregisterSession,
5964
- retagAccountForUser,
5965
5959
  getSessionKeyBySessionId,
5966
5960
  findMissingPlugins,
5967
5961
  walkPremiumBundles,
@@ -16,7 +16,7 @@ import {
16
16
  sanitizeClientCorrId,
17
17
  vncLog,
18
18
  websockifyLog
19
- } from "./chunk-UGPQCVIW.js";
19
+ } from "./chunk-WC4WJR6X.js";
20
20
  import "./chunk-PFF6I7KP.js";
21
21
 
22
22
  // server/edge.ts
@@ -1 +1 @@
1
- import{o as e}from"./chunk-Ck5AOu5v.js";import{E as t,S as n,_ as r,x as i,y as a}from"./useSubAccountSwitcher-DLJFISqf.js";import{E as o}from"./useVoiceRecorder-DCyJQkFK.js";import{b as s,y as c}from"./AdminShell-DlAVQUda.js";import{t as l}from"./Checkbox-B629mOPp.js";var u=`admin-landing-redirected`,d=`/graph`;function f(e){return e.variant===`operator`?!1:e.appState===`chat`&&!e.alreadyRedirected}var p=e(t(),1);function m(e=`admin`){let[t,r]=(0,p.useState)(`loading`),[i,a]=(0,p.useState)(``),[c,l]=(0,p.useState)(``),[m,h]=(0,p.useState)(``),[g,_]=(0,p.useState)(!1),[v,y]=(0,p.useState)(!1),[b,x]=(0,p.useState)(!1),[S,C]=(0,p.useState)(!1),[w,T]=(0,p.useState)(!1),[E,D]=(0,p.useState)(null),[O,k]=(0,p.useState)(null),[A,j]=(0,p.useState)(void 0),[M,N]=(0,p.useState)(null),[P,F]=(0,p.useState)(void 0),[I,L]=(0,p.useState)(null),[R,z]=(0,p.useState)(null),[B,V]=(0,p.useState)(void 0),H=(0,p.useRef)(void 0),[U,W]=(0,p.useState)(!1),[G,K]=(0,p.useState)(!0);(0,p.useEffect)(()=>{typeof window>`u`||fetch(`/api/remote-auth/status`).then(e=>e.ok?e.json():null).then(e=>{e?.configured&&W(!0)}).catch(()=>{})},[]);let q=(0,p.useRef)(null),J=(0,p.useRef)(null);(0,p.useEffect)(()=>{async function e(){let e=null;try{e=sessionStorage.getItem(`maxy-admin-session-key`)}catch{}if(!e)return!1;try{let t=await fetch(`/api/admin/session?session_key=${encodeURIComponent(e)}`);if(t.status===401){try{sessionStorage.removeItem(`maxy-admin-session-key`)}catch{}return!1}if(!t.ok)return!1;let n=await t.json();D(n.session_key),z(n.sessionId??null),j(n.businessName),N(n.role??null),F(n.userName===void 0?null:n.userName),L(n.avatar??null);let i=o(n.thinkingView);return H.current=i,V(i),r(`chat`),!0}catch(e){return console.error(`[admin] session restore failed:`,e),!1}}async function t(n=2){try{let i=await fetch(`/api/health`);if(!i.ok){if(n>0)return await new Promise(e=>setTimeout(e,1500)),t(n-1);console.error(`[admin] health check returned ${i.status} after retries`),r(`set-pin`);return}let a=await i.json();if(!a.pin_configured){r(`set-pin`);return}K(!!a.claude_authenticated);let o=!1;try{o=new URLSearchParams(window.location.search).get(`connect`)===`claude`}catch{}if(o){try{let e=new URL(window.location.href);e.searchParams.delete(`connect`),window.history.replaceState(null,``,e.pathname+e.search+e.hash)}catch{}if(!a.claude_authenticated){r(`connect-claude`);return}}if(await e())return;r(`enter-pin`)}catch(e){if(n>0)return await new Promise(e=>setTimeout(e,1500)),t(n-1);console.error(`[admin] health check failed:`,e),r(`set-pin`)}}t()},[]),(0,p.useEffect)(()=>{t===`chat`&&fetch(`/api/admin/claude-info`).then(e=>{if(e.ok)return e.json()}).then(e=>{e&&k(e)}).catch(()=>{})},[t]),(0,p.useEffect)(()=>{if(typeof window>`u`)return;let n=!1;try{n=sessionStorage.getItem(u)===`1`}catch{}if(f({appState:t,alreadyRedirected:n,variant:e})){try{sessionStorage.setItem(u,`1`)}catch{}console.info(`[admin-ui] landing-redirect target=${d}`),window.location.replace(d)}},[t,e]);let Y=(0,p.useRef)(null);(0,p.useEffect)(()=>{if(t!==`chat`)return;let e=setInterval(async()=>{try{let e=await fetch(`/api/health`);if(e.ok){let t=await e.json();K(t.auth_status!==`dead`&&t.auth_status!==`missing`)}}catch{}if(E)try{let e=await fetch(`/api/admin/session?session_key=${encodeURIComponent(E)}`);if(e.status!==401)return;let t=(await e.clone().json().catch(()=>null))?.code??`unknown-401`;if(t===`remote-auth-required`){n(`heartbeat`,`/api/admin/session`);return}console.warn(`[admin-auth] outcome=heartbeat-detected-expiry code=${t}`),Y.current?.()}catch{}},300*1e3);return()=>clearInterval(e)},[t,E]),(0,p.useEffect)(()=>{t===`connect-claude`&&fetch(`/api/health`).then(e=>e.ok?e.json():null).then(e=>{e?.claude_authenticated&&(K(!0),r(`enter-pin`))}).catch(()=>{})},[t]);async function X(e,t){y(!0);try{let i=await fetch(`/api/admin/session`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({pin:e,...t?{accountId:t}:{}})});if(!i.ok){let e=await i.json().catch(()=>({}));if(i.status===401&&e?.code===`remote-auth-required`){n(`enter-pin`,`/api/admin/session`);return}h(e.error||`Invalid PIN`);return}let s=await i.json();D(s.session_key),z(s.sessionId??null),j(s.businessName),N(s.role??null),F(s.userName===void 0?null:s.userName),L(s.avatar??null);let c=o(s.thinkingView);H.current=c,V(c);try{sessionStorage.setItem(`maxy-admin-session-key`,s.session_key)}catch{}a(``),r(`chat`)}catch(e){console.error(`[admin] connection error:`,e),h(`Could not connect.`)}finally{y(!1)}}let Z=(0,p.useCallback)(async e=>{if(e.preventDefault(),v)return;h(``);let t=c.trim();if(!t){h(`Please enter your name.`);return}if(i.length<4){h(`PIN must be at least 4 characters.`);return}let o=i;y(!0);try{let e=await fetch(`/api/onboarding/set-pin`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({pin:o,name:t})});if(!e.ok){let t=await e.json().catch(()=>({}));if(e.status===401&&t?.code===`remote-auth-required`){n(`set-pin`,`/api/onboarding/set-pin`);return}if(e.status===409){console.log(`[admin] PIN already configured — re-checking health`);try{let e=await fetch(`/api/health`);if(e.ok){let n=await e.json();n.pin_configured&&n.claude_authenticated?r(`enter-pin`):n.pin_configured?r(`connect-claude`):h(t.error||`Failed to set PIN.`)}else r(`enter-pin`)}catch{r(`enter-pin`)}return}h(t.error||`Failed to set PIN.`);return}let i=await fetch(`/api/health`);if((i.ok?await i.json():null)?.claude_authenticated){await X(o);return}a(``),r(`connect-claude`)}catch(e){console.error(`[admin] connection error:`,e),h(`Could not connect.`)}finally{y(!1)}},[i,v,c]),Q=(0,p.useCallback)(async e=>{e.preventDefault(),h(``),await X(i)},[i]),ee=(0,p.useCallback)(async()=>{T(!0);try{if(!await s())return console.warn(`[admin-ui] claude-disconnect not verified — credentials may persist; staying put`),!1;K(!1),D(null),N(null),F(void 0),L(null);try{sessionStorage.removeItem(`maxy-admin-session-key`),sessionStorage.removeItem(u)}catch{}return r(`connect-claude`),!0}finally{T(!1)}},[]),$=(0,p.useCallback)(()=>{D(null),N(null),F(void 0),L(null);try{sessionStorage.removeItem(`maxy-admin-session-key`),sessionStorage.removeItem(u)}catch{}a(``),h(``),r(`enter-pin`)},[]);return(0,p.useEffect)(()=>{Y.current=$},[$]),{appState:t,setAppState:r,pin:i,setPin:a,operatorName:c,setOperatorName:l,pinError:m,setPinError:h,showPin:g,setShowPin:_,pinLoading:v,authPolling:b,setAuthPolling:x,authLoading:S,setAuthLoading:C,disconnecting:w,cacheKey:E,setCacheKey:D,claudeInfo:O,setClaudeInfo:k,businessName:A,role:M,userName:P,userAvatar:I,sessionId:R,setSessionId:z,expandAll:B,setExpandAll:V,expandAllDefaultRef:H,remoteAuthEnabled:U,claudeConnected:G,pinInputRef:q,setPinFormRef:J,handleSetPin:Z,handleLogin:Q,handleDisconnect:ee,handleLogout:$}}var h=i();function g({inputRef:e,value:t,onChange:n,onComplete:r,showPin:i,autoFocus:a}){let o=(0,p.useRef)([]);function s(e,r){r.key===`Backspace`?(r.preventDefault(),t[e]?n(t.slice(0,e)+t.slice(e+1)):e>0&&(n(t.slice(0,e-1)+t.slice(e)),o.current[e-1]?.focus())):r.key===`ArrowLeft`&&e>0?o.current[e-1]?.focus():r.key===`ArrowRight`&&e<5?o.current[e+1]?.focus():r.key===`Enter`&&(r.preventDefault(),r.currentTarget.form?.requestSubmit())}function c(e,i){let a=i.nativeEvent.data;if(!a||!/^\d$/.test(a))return;let s=t.split(``);for(s[e]=a;s.length<e;)s.push(``);let c=s.join(``).replace(/\D/g,``).slice(0,6);n(c),c.length===6?r?.(c):e<5&&o.current[e+1]?.focus()}function l(e){e.preventDefault();let t=e.clipboardData.getData(`text`).replace(/\D/g,``).slice(0,6);t&&(n(t),t.length===6?r?.(t):o.current[t.length]?.focus())}return(0,h.jsx)(`div`,{className:`pin-field`,children:Array.from({length:6}).map((n,r)=>(0,h.jsx)(`input`,{ref:t=>{o.current[r]=t,r===0&&e&&(e.current=t)},type:`text`,inputMode:`numeric`,className:`pin-box${t[r]?` pin-box-filled`:``}`,value:t[r]?i?t[r]:`•`:``,onKeyDown:e=>s(r,e),onInput:e=>c(r,e),onPaste:l,onFocus:e=>e.target.select(),autoFocus:a&&r===0,autoComplete:`off`,maxLength:1,"aria-label":`PIN digit ${r+1}`},r))})}function _(e){let{pin:t,setPin:n,showPin:i,setShowPin:o,pinLoading:s,pinError:u,pinInputRef:d,setPinFormRef:f,onSubmit:p,operatorName:m,setOperatorName:_}=e;return(0,h.jsx)(`div`,{className:`connect-page`,children:(0,h.jsxs)(`div`,{className:`connect-content`,children:[(0,h.jsx)(`img`,{src:a,alt:r.productName,className:`connect-logo connect-logo--maxy`}),!r.logoContainsName&&(0,h.jsxs)(`h1`,{className:`connect-title`,children:[`Welcome to `,r.productName]}),(0,h.jsxs)(`p`,{className:`connect-subtitle`,children:[`Tell `,r.productName,` who you are, then choose a PIN.`]}),(0,h.jsxs)(`form`,{ref:f,onSubmit:p,className:`connect-pin-form`,children:[(0,h.jsxs)(`div`,{className:`pin-input-row`,children:[(0,h.jsx)(`input`,{type:`text`,className:`connect-name-input`,placeholder:`Your full name`,value:m,onChange:e=>_(e.target.value),autoComplete:`name`,autoFocus:!0,required:!0,"aria-label":`Your full name`}),(0,h.jsx)(`div`,{style:{width:38,flexShrink:0},"aria-hidden":`true`})]}),(0,h.jsxs)(`div`,{className:`pin-input-row`,children:[(0,h.jsx)(g,{inputRef:d,value:t,onChange:n,onComplete:()=>{},showPin:i}),(0,h.jsx)(c,{variant:`send`,type:`submit`,disabled:!t||!m.trim(),loading:s,"aria-label":`Set PIN`,children:(0,h.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,h.jsx)(`line`,{x1:`5`,y1:`12`,x2:`19`,y2:`12`}),(0,h.jsx)(`polyline`,{points:`12 5 19 12 12 19`})]})})]}),(0,h.jsx)(l,{checked:i,onChange:()=>o(e=>!e),label:`Show PIN`})]}),u&&(0,h.jsx)(`p`,{className:`admin-pin-error`,children:u})]})})}function v(e){let{pin:t,setPin:n,showPin:i,setShowPin:o,pinLoading:s,pinError:u,pinInputRef:d,onSubmit:f,remoteAuthEnabled:p,onSignOutRemote:m}=e;return(0,h.jsxs)(`div`,{className:`connect-page`,children:[p&&m&&(0,h.jsx)(`button`,{type:`button`,className:`connect-signout`,onClick:m,children:`Sign out`}),(0,h.jsxs)(`div`,{className:`connect-content`,children:[(0,h.jsx)(`img`,{src:a,alt:r.productName,className:`connect-logo connect-logo--maxy`}),!r.logoContainsName&&(0,h.jsx)(`h1`,{className:`connect-title`,children:r.productName}),(0,h.jsxs)(`form`,{onSubmit:f,className:`connect-pin-form`,children:[(0,h.jsxs)(`div`,{className:`pin-input-row`,children:[(0,h.jsx)(g,{inputRef:d,value:t,onChange:n,onComplete:()=>{},showPin:i,autoFocus:!0}),(0,h.jsx)(c,{variant:`send`,type:`submit`,disabled:!t,loading:s,children:(0,h.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,h.jsx)(`line`,{x1:`5`,y1:`12`,x2:`19`,y2:`12`}),(0,h.jsx)(`polyline`,{points:`12 5 19 12 12 19`})]})})]}),(0,h.jsx)(`div`,{className:`pin-options`,children:(0,h.jsx)(l,{checked:i,onChange:()=>o(e=>!e),label:`Show PIN`})})]}),u&&(0,h.jsx)(`p`,{className:`admin-pin-error`,children:u})]})]})}function y(e){let{authPolling:t,setAuthPolling:n,authLoading:i,setAuthLoading:o,pinError:s,setPinError:l,setAppState:u}=e,[d,f]=(0,p.useState)(!1),[m,g]=(0,p.useState)(!1),[_,v]=(0,p.useState)(``),[y,b]=(0,p.useState)(``),[x,S]=(0,p.useState)(``);async function C(){S(``);try{let e=await(await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`paste-code`,code:y})})).json();e.accepted?(S(`Code submitted. Finishing sign-in…`),b(``)):S(e.error||`Could not submit code.`)}catch(e){console.error(`[admin] paste-code error:`,e),S(`Could not submit code.`)}}async function w(){g(!0),l(``);try{let e=await(await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`launch-browser`})})).json();e.launched?f(!0):e.error&&l(e.error)}catch(e){console.error(`[admin] browser launch error:`,e),l(`Could not launch browser.`)}g(!1)}async function T(){o(!0),l(``);try{let e=await(await fetch(`/api/onboarding/claude-auth`,{method:`POST`})).json();if(e.started){n(!0),f(!0),o(!1),v(e.transport||``);for(let e=0;e<120;e++)if(await new Promise(e=>setTimeout(e,2e3)),(await(await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`wait`})})).json()).authenticated){await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`stop`})}),u(`enter-pin`);return}l(`Timed out waiting for sign-in. Try again.`),n(!1)}else e.error&&l(e.error)}catch(e){console.error(`[admin] auth flow error:`,e),l(`Could not start auth flow.`)}o(!1)}async function E(){await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`stop`})}),n(!1),l(``)}return t||d?(0,h.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100dvh`,overflow:`auto`},children:[(0,h.jsxs)(`header`,{className:`chat-header`,style:{paddingBottom:`12px`,flexShrink:0,position:`relative`,maxWidth:`680px`,width:`100%`,margin:`0 auto`,padding:`24px 20px 12px`},children:[t?(0,h.jsx)(`button`,{onClick:E,style:{position:`absolute`,top:`12px`,right:`12px`,background:`none`,border:`none`,color:`#999`,fontSize:`13px`,cursor:`pointer`,padding:`4px 8px`},"aria-label":`Cancel`,children:`✕`}):(0,h.jsx)(`button`,{onClick:()=>f(!1),style:{position:`absolute`,top:`12px`,right:`12px`,background:`none`,border:`none`,color:`#999`,fontSize:`13px`,cursor:`pointer`,padding:`4px 8px`},"aria-label":`Close browser`,children:`✕`}),(0,h.jsx)(`img`,{src:`/brand/claude.png`,alt:`Claude`,className:`chat-logo`}),(0,h.jsx)(`h1`,{className:`chat-tagline`,children:`Connect Claude`}),(0,h.jsx)(`p`,{className:`chat-intro`,children:t?`Sign in and authorize in the browser below.`:`Open your email or prepare your accounts, then sign in.`}),!t&&(0,h.jsx)(`div`,{style:{marginTop:`12px`},children:(0,h.jsx)(c,{variant:`primary`,onClick:T,disabled:i,children:i?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`span`,{className:`spin`,style:{display:`inline-block`},children:`✱`}),` Connecting…`]}):`Sign in to Claude`})}),t&&_===`native`&&(0,h.jsxs)(`div`,{style:{marginTop:`14px`,maxWidth:`420px`,margin:`14px auto 0`,display:`flex`,flexDirection:`column`,gap:`8px`},children:[(0,h.jsx)(`label`,{style:{fontSize:`12px`,color:`#999`,lineHeight:1.4},children:`If the browser didn't finish sign-in, paste the code or full redirect URL from Claude's page:`}),(0,h.jsxs)(`div`,{style:{display:`flex`,gap:`8px`},children:[(0,h.jsx)(`input`,{type:`text`,value:y,onChange:e=>b(e.target.value),placeholder:`Paste code or redirect URL`,style:{flex:1,padding:`8px 10px`,fontSize:`13px`,border:`1px solid #ccc`,borderRadius:`6px`}}),(0,h.jsx)(c,{variant:`primary`,onClick:C,disabled:!y.trim(),children:`Submit`})]}),x&&(0,h.jsx)(`p`,{style:{fontSize:`12px`,color:`#999`,margin:0},children:x})]})]}),(0,h.jsx)(`div`,{style:{flex:1,display:`flex`,flexDirection:`column`,minHeight:0,gap:`10px`,padding:`0 0 16px`},children:(0,h.jsx)(`iframe`,{src:`/vnc-viewer.html`,style:{flex:1,width:`100%`,minHeight:0,border:`none`,background:`#111`,display:`block`},title:`Claude Sign-in`})}),s&&(0,h.jsx)(`p`,{className:`admin-pin-error`,style:{textAlign:`center`,padding:`0 20px 16px`},children:s})]}):(0,h.jsx)(`div`,{className:`connect-page`,children:(0,h.jsxs)(`div`,{className:`connect-content`,children:[(0,h.jsxs)(`div`,{className:`connect-logos`,children:[(0,h.jsx)(`div`,{className:`connect-logo-wrap`,children:(0,h.jsx)(`img`,{src:`/brand/claude.png`,alt:`Claude`,className:`connect-logo`})}),(0,h.jsx)(`svg`,{className:`connect-arrow`,viewBox:`0 0 48 24`,fill:`none`,xmlns:`http://www.w3.org/2000/svg`,children:(0,h.jsx)(`path`,{d:`M0 12h44m0 0l-8-8m8 8l-8 8`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`})}),(0,h.jsxs)(`div`,{className:`connect-logo-wrap`,children:[(0,h.jsx)(`img`,{src:a,alt:r.productName,className:`connect-logo connect-logo--maxy`}),!r.logoContainsName&&(0,h.jsx)(`span`,{className:`connect-logo-label`,children:r.productName})]})]}),(0,h.jsxs)(`h1`,{className:`connect-title`,children:[`Connect Claude to power `,r.productName]}),(0,h.jsx)(`p`,{className:`connect-subtitle`,children:`Sign in with your Anthropic account to get started.`}),(0,h.jsx)(c,{variant:`primary`,onClick:T,disabled:i,children:i?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`span`,{className:`spin`,style:{display:`inline-block`},children:`✱`}),` Connecting…`]}):`Sign in to Claude`}),(0,h.jsx)(`p`,{style:{marginTop:`6px`,fontSize:`11px`,color:`#999`,maxWidth:`300px`,textAlign:`center`,lineHeight:`1.4`},children:`First time? You may need to sign into your email and Anthropic account in the browser before connecting.`}),(0,h.jsx)(`button`,{onClick:w,disabled:m,style:{marginTop:`12px`,background:`none`,border:`none`,color:`var(--color-primary, #666)`,fontSize:`13px`,cursor:`pointer`,textDecoration:`underline`,textUnderlineOffset:`3px`},children:m?`Launching…`:`Open browser first`}),s&&(0,h.jsx)(`p`,{className:`admin-pin-error`,children:s})]})})}function b({auth:e}){return e.appState===`loading`?(0,h.jsx)(`div`,{className:`connect-page`}):e.appState===`set-pin`?(0,h.jsx)(_,{pin:e.pin,setPin:e.setPin,showPin:e.showPin,setShowPin:e.setShowPin,pinLoading:e.pinLoading,pinError:e.pinError,pinInputRef:e.pinInputRef,setPinFormRef:e.setPinFormRef,onSubmit:e.handleSetPin,operatorName:e.operatorName,setOperatorName:e.setOperatorName}):e.appState===`connect-claude`?(0,h.jsx)(y,{authPolling:e.authPolling,setAuthPolling:e.setAuthPolling,authLoading:e.authLoading,setAuthLoading:e.setAuthLoading,pinError:e.pinError,setPinError:e.setPinError,setAppState:e.setAppState}):e.appState===`enter-pin`?(0,h.jsx)(v,{pin:e.pin,setPin:e.setPin,showPin:e.showPin,setShowPin:e.setShowPin,pinLoading:e.pinLoading,pinError:e.pinError,pinInputRef:e.pinInputRef,onSubmit:e.handleLogin,remoteAuthEnabled:e.remoteAuthEnabled,onSignOutRemote:()=>{console.info(`[admin-ui] remote-auth sign-out → /__remote-auth/logout`),window.location.href=`/__remote-auth/logout`}}):null}export{m as n,b as t};
1
+ import{o as e}from"./chunk-Ck5AOu5v.js";import{E as t,S as n,_ as r,x as i,y as a}from"./useSubAccountSwitcher-DLJFISqf.js";import{E as o}from"./useVoiceRecorder-b7HzTFs0.js";import{b as s,y as c}from"./AdminShell-BwEBH6tT.js";import{t as l}from"./Checkbox-B629mOPp.js";var u=`admin-landing-redirected`,d=`/graph`;function f(e){return e.variant===`operator`?!1:e.appState===`chat`&&!e.alreadyRedirected}var p=e(t(),1);function m(e=`admin`){let[t,r]=(0,p.useState)(`loading`),[i,a]=(0,p.useState)(``),[c,l]=(0,p.useState)(``),[m,h]=(0,p.useState)(``),[g,_]=(0,p.useState)(!1),[v,y]=(0,p.useState)(!1),[b,x]=(0,p.useState)(!1),[S,C]=(0,p.useState)(!1),[w,T]=(0,p.useState)(!1),[E,D]=(0,p.useState)(null),[O,k]=(0,p.useState)(null),[A,j]=(0,p.useState)(void 0),[M,N]=(0,p.useState)(null),[P,F]=(0,p.useState)(void 0),[I,L]=(0,p.useState)(null),[R,z]=(0,p.useState)(null),[B,V]=(0,p.useState)(void 0),H=(0,p.useRef)(void 0),[U,W]=(0,p.useState)(!1),[G,K]=(0,p.useState)(!0);(0,p.useEffect)(()=>{typeof window>`u`||fetch(`/api/remote-auth/status`).then(e=>e.ok?e.json():null).then(e=>{e?.configured&&W(!0)}).catch(()=>{})},[]);let q=(0,p.useRef)(null),J=(0,p.useRef)(null);(0,p.useEffect)(()=>{async function e(){let e=null;try{e=sessionStorage.getItem(`maxy-admin-session-key`)}catch{}if(!e)return!1;try{let t=await fetch(`/api/admin/session?session_key=${encodeURIComponent(e)}`);if(t.status===401){try{sessionStorage.removeItem(`maxy-admin-session-key`)}catch{}return!1}if(!t.ok)return!1;let n=await t.json();D(n.session_key),z(n.sessionId??null),j(n.businessName),N(n.role??null),F(n.userName===void 0?null:n.userName),L(n.avatar??null);let i=o(n.thinkingView);return H.current=i,V(i),r(`chat`),!0}catch(e){return console.error(`[admin] session restore failed:`,e),!1}}async function t(n=2){try{let i=await fetch(`/api/health`);if(!i.ok){if(n>0)return await new Promise(e=>setTimeout(e,1500)),t(n-1);console.error(`[admin] health check returned ${i.status} after retries`),r(`set-pin`);return}let a=await i.json();if(!a.pin_configured){r(`set-pin`);return}K(!!a.claude_authenticated);let o=!1;try{o=new URLSearchParams(window.location.search).get(`connect`)===`claude`}catch{}if(o){try{let e=new URL(window.location.href);e.searchParams.delete(`connect`),window.history.replaceState(null,``,e.pathname+e.search+e.hash)}catch{}if(!a.claude_authenticated){r(`connect-claude`);return}}if(await e())return;r(`enter-pin`)}catch(e){if(n>0)return await new Promise(e=>setTimeout(e,1500)),t(n-1);console.error(`[admin] health check failed:`,e),r(`set-pin`)}}t()},[]),(0,p.useEffect)(()=>{t===`chat`&&fetch(`/api/admin/claude-info`).then(e=>{if(e.ok)return e.json()}).then(e=>{e&&k(e)}).catch(()=>{})},[t]),(0,p.useEffect)(()=>{if(typeof window>`u`)return;let n=!1;try{n=sessionStorage.getItem(u)===`1`}catch{}if(f({appState:t,alreadyRedirected:n,variant:e})){try{sessionStorage.setItem(u,`1`)}catch{}console.info(`[admin-ui] landing-redirect target=${d}`),window.location.replace(d)}},[t,e]);let Y=(0,p.useRef)(null);(0,p.useEffect)(()=>{if(t!==`chat`)return;let e=setInterval(async()=>{try{let e=await fetch(`/api/health`);if(e.ok){let t=await e.json();K(t.auth_status!==`dead`&&t.auth_status!==`missing`)}}catch{}if(E)try{let e=await fetch(`/api/admin/session?session_key=${encodeURIComponent(E)}`);if(e.status!==401)return;let t=(await e.clone().json().catch(()=>null))?.code??`unknown-401`;if(t===`remote-auth-required`){n(`heartbeat`,`/api/admin/session`);return}console.warn(`[admin-auth] outcome=heartbeat-detected-expiry code=${t}`),Y.current?.()}catch{}},300*1e3);return()=>clearInterval(e)},[t,E]),(0,p.useEffect)(()=>{t===`connect-claude`&&fetch(`/api/health`).then(e=>e.ok?e.json():null).then(e=>{e?.claude_authenticated&&(K(!0),r(`enter-pin`))}).catch(()=>{})},[t]);async function X(e,t){y(!0);try{let i=await fetch(`/api/admin/session`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({pin:e,...t?{accountId:t}:{}})});if(!i.ok){let e=await i.json().catch(()=>({}));if(i.status===401&&e?.code===`remote-auth-required`){n(`enter-pin`,`/api/admin/session`);return}h(e.error||`Invalid PIN`);return}let s=await i.json();D(s.session_key),z(s.sessionId??null),j(s.businessName),N(s.role??null),F(s.userName===void 0?null:s.userName),L(s.avatar??null);let c=o(s.thinkingView);H.current=c,V(c);try{sessionStorage.setItem(`maxy-admin-session-key`,s.session_key)}catch{}a(``),r(`chat`)}catch(e){console.error(`[admin] connection error:`,e),h(`Could not connect.`)}finally{y(!1)}}let Z=(0,p.useCallback)(async e=>{if(e.preventDefault(),v)return;h(``);let t=c.trim();if(!t){h(`Please enter your name.`);return}if(i.length<4){h(`PIN must be at least 4 characters.`);return}let o=i;y(!0);try{let e=await fetch(`/api/onboarding/set-pin`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({pin:o,name:t})});if(!e.ok){let t=await e.json().catch(()=>({}));if(e.status===401&&t?.code===`remote-auth-required`){n(`set-pin`,`/api/onboarding/set-pin`);return}if(e.status===409){console.log(`[admin] PIN already configured — re-checking health`);try{let e=await fetch(`/api/health`);if(e.ok){let n=await e.json();n.pin_configured&&n.claude_authenticated?r(`enter-pin`):n.pin_configured?r(`connect-claude`):h(t.error||`Failed to set PIN.`)}else r(`enter-pin`)}catch{r(`enter-pin`)}return}h(t.error||`Failed to set PIN.`);return}let i=await fetch(`/api/health`);if((i.ok?await i.json():null)?.claude_authenticated){await X(o);return}a(``),r(`connect-claude`)}catch(e){console.error(`[admin] connection error:`,e),h(`Could not connect.`)}finally{y(!1)}},[i,v,c]),Q=(0,p.useCallback)(async e=>{e.preventDefault(),h(``),await X(i)},[i]),ee=(0,p.useCallback)(async()=>{T(!0);try{if(!await s())return console.warn(`[admin-ui] claude-disconnect not verified — credentials may persist; staying put`),!1;K(!1),D(null),N(null),F(void 0),L(null);try{sessionStorage.removeItem(`maxy-admin-session-key`),sessionStorage.removeItem(u)}catch{}return r(`connect-claude`),!0}finally{T(!1)}},[]),$=(0,p.useCallback)(()=>{D(null),N(null),F(void 0),L(null);try{sessionStorage.removeItem(`maxy-admin-session-key`),sessionStorage.removeItem(u)}catch{}a(``),h(``),r(`enter-pin`)},[]);return(0,p.useEffect)(()=>{Y.current=$},[$]),{appState:t,setAppState:r,pin:i,setPin:a,operatorName:c,setOperatorName:l,pinError:m,setPinError:h,showPin:g,setShowPin:_,pinLoading:v,authPolling:b,setAuthPolling:x,authLoading:S,setAuthLoading:C,disconnecting:w,cacheKey:E,setCacheKey:D,claudeInfo:O,setClaudeInfo:k,businessName:A,role:M,userName:P,userAvatar:I,sessionId:R,setSessionId:z,expandAll:B,setExpandAll:V,expandAllDefaultRef:H,remoteAuthEnabled:U,claudeConnected:G,pinInputRef:q,setPinFormRef:J,handleSetPin:Z,handleLogin:Q,handleDisconnect:ee,handleLogout:$}}var h=i();function g({inputRef:e,value:t,onChange:n,onComplete:r,showPin:i,autoFocus:a}){let o=(0,p.useRef)([]);function s(e,r){r.key===`Backspace`?(r.preventDefault(),t[e]?n(t.slice(0,e)+t.slice(e+1)):e>0&&(n(t.slice(0,e-1)+t.slice(e)),o.current[e-1]?.focus())):r.key===`ArrowLeft`&&e>0?o.current[e-1]?.focus():r.key===`ArrowRight`&&e<5?o.current[e+1]?.focus():r.key===`Enter`&&(r.preventDefault(),r.currentTarget.form?.requestSubmit())}function c(e,i){let a=i.nativeEvent.data;if(!a||!/^\d$/.test(a))return;let s=t.split(``);for(s[e]=a;s.length<e;)s.push(``);let c=s.join(``).replace(/\D/g,``).slice(0,6);n(c),c.length===6?r?.(c):e<5&&o.current[e+1]?.focus()}function l(e){e.preventDefault();let t=e.clipboardData.getData(`text`).replace(/\D/g,``).slice(0,6);t&&(n(t),t.length===6?r?.(t):o.current[t.length]?.focus())}return(0,h.jsx)(`div`,{className:`pin-field`,children:Array.from({length:6}).map((n,r)=>(0,h.jsx)(`input`,{ref:t=>{o.current[r]=t,r===0&&e&&(e.current=t)},type:`text`,inputMode:`numeric`,className:`pin-box${t[r]?` pin-box-filled`:``}`,value:t[r]?i?t[r]:`•`:``,onKeyDown:e=>s(r,e),onInput:e=>c(r,e),onPaste:l,onFocus:e=>e.target.select(),autoFocus:a&&r===0,autoComplete:`off`,maxLength:1,"aria-label":`PIN digit ${r+1}`},r))})}function _(e){let{pin:t,setPin:n,showPin:i,setShowPin:o,pinLoading:s,pinError:u,pinInputRef:d,setPinFormRef:f,onSubmit:p,operatorName:m,setOperatorName:_}=e;return(0,h.jsx)(`div`,{className:`connect-page`,children:(0,h.jsxs)(`div`,{className:`connect-content`,children:[(0,h.jsx)(`img`,{src:a,alt:r.productName,className:`connect-logo connect-logo--maxy`}),!r.logoContainsName&&(0,h.jsxs)(`h1`,{className:`connect-title`,children:[`Welcome to `,r.productName]}),(0,h.jsxs)(`p`,{className:`connect-subtitle`,children:[`Tell `,r.productName,` who you are, then choose a PIN.`]}),(0,h.jsxs)(`form`,{ref:f,onSubmit:p,className:`connect-pin-form`,children:[(0,h.jsxs)(`div`,{className:`pin-input-row`,children:[(0,h.jsx)(`input`,{type:`text`,className:`connect-name-input`,placeholder:`Your full name`,value:m,onChange:e=>_(e.target.value),autoComplete:`name`,autoFocus:!0,required:!0,"aria-label":`Your full name`}),(0,h.jsx)(`div`,{style:{width:38,flexShrink:0},"aria-hidden":`true`})]}),(0,h.jsxs)(`div`,{className:`pin-input-row`,children:[(0,h.jsx)(g,{inputRef:d,value:t,onChange:n,onComplete:()=>{},showPin:i}),(0,h.jsx)(c,{variant:`send`,type:`submit`,disabled:!t||!m.trim(),loading:s,"aria-label":`Set PIN`,children:(0,h.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,h.jsx)(`line`,{x1:`5`,y1:`12`,x2:`19`,y2:`12`}),(0,h.jsx)(`polyline`,{points:`12 5 19 12 12 19`})]})})]}),(0,h.jsx)(l,{checked:i,onChange:()=>o(e=>!e),label:`Show PIN`})]}),u&&(0,h.jsx)(`p`,{className:`admin-pin-error`,children:u})]})})}function v(e){let{pin:t,setPin:n,showPin:i,setShowPin:o,pinLoading:s,pinError:u,pinInputRef:d,onSubmit:f,remoteAuthEnabled:p,onSignOutRemote:m}=e;return(0,h.jsxs)(`div`,{className:`connect-page`,children:[p&&m&&(0,h.jsx)(`button`,{type:`button`,className:`connect-signout`,onClick:m,children:`Sign out`}),(0,h.jsxs)(`div`,{className:`connect-content`,children:[(0,h.jsx)(`img`,{src:a,alt:r.productName,className:`connect-logo connect-logo--maxy`}),!r.logoContainsName&&(0,h.jsx)(`h1`,{className:`connect-title`,children:r.productName}),(0,h.jsxs)(`form`,{onSubmit:f,className:`connect-pin-form`,children:[(0,h.jsxs)(`div`,{className:`pin-input-row`,children:[(0,h.jsx)(g,{inputRef:d,value:t,onChange:n,onComplete:()=>{},showPin:i,autoFocus:!0}),(0,h.jsx)(c,{variant:`send`,type:`submit`,disabled:!t,loading:s,children:(0,h.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,h.jsx)(`line`,{x1:`5`,y1:`12`,x2:`19`,y2:`12`}),(0,h.jsx)(`polyline`,{points:`12 5 19 12 12 19`})]})})]}),(0,h.jsx)(`div`,{className:`pin-options`,children:(0,h.jsx)(l,{checked:i,onChange:()=>o(e=>!e),label:`Show PIN`})})]}),u&&(0,h.jsx)(`p`,{className:`admin-pin-error`,children:u})]})]})}function y(e){let{authPolling:t,setAuthPolling:n,authLoading:i,setAuthLoading:o,pinError:s,setPinError:l,setAppState:u}=e,[d,f]=(0,p.useState)(!1),[m,g]=(0,p.useState)(!1),[_,v]=(0,p.useState)(``),[y,b]=(0,p.useState)(``),[x,S]=(0,p.useState)(``);async function C(){S(``);try{let e=await(await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`paste-code`,code:y})})).json();e.accepted?(S(`Code submitted. Finishing sign-in…`),b(``)):S(e.error||`Could not submit code.`)}catch(e){console.error(`[admin] paste-code error:`,e),S(`Could not submit code.`)}}async function w(){g(!0),l(``);try{let e=await(await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`launch-browser`})})).json();e.launched?f(!0):e.error&&l(e.error)}catch(e){console.error(`[admin] browser launch error:`,e),l(`Could not launch browser.`)}g(!1)}async function T(){o(!0),l(``);try{let e=await(await fetch(`/api/onboarding/claude-auth`,{method:`POST`})).json();if(e.started){n(!0),f(!0),o(!1),v(e.transport||``);for(let e=0;e<120;e++)if(await new Promise(e=>setTimeout(e,2e3)),(await(await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`wait`})})).json()).authenticated){await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`stop`})}),u(`enter-pin`);return}l(`Timed out waiting for sign-in. Try again.`),n(!1)}else e.error&&l(e.error)}catch(e){console.error(`[admin] auth flow error:`,e),l(`Could not start auth flow.`)}o(!1)}async function E(){await fetch(`/api/onboarding/claude-auth`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({action:`stop`})}),n(!1),l(``)}return t||d?(0,h.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,height:`100dvh`,overflow:`auto`},children:[(0,h.jsxs)(`header`,{className:`chat-header`,style:{paddingBottom:`12px`,flexShrink:0,position:`relative`,maxWidth:`680px`,width:`100%`,margin:`0 auto`,padding:`24px 20px 12px`},children:[t?(0,h.jsx)(`button`,{onClick:E,style:{position:`absolute`,top:`12px`,right:`12px`,background:`none`,border:`none`,color:`#999`,fontSize:`13px`,cursor:`pointer`,padding:`4px 8px`},"aria-label":`Cancel`,children:`✕`}):(0,h.jsx)(`button`,{onClick:()=>f(!1),style:{position:`absolute`,top:`12px`,right:`12px`,background:`none`,border:`none`,color:`#999`,fontSize:`13px`,cursor:`pointer`,padding:`4px 8px`},"aria-label":`Close browser`,children:`✕`}),(0,h.jsx)(`img`,{src:`/brand/claude.png`,alt:`Claude`,className:`chat-logo`}),(0,h.jsx)(`h1`,{className:`chat-tagline`,children:`Connect Claude`}),(0,h.jsx)(`p`,{className:`chat-intro`,children:t?`Sign in and authorize in the browser below.`:`Open your email or prepare your accounts, then sign in.`}),!t&&(0,h.jsx)(`div`,{style:{marginTop:`12px`},children:(0,h.jsx)(c,{variant:`primary`,onClick:T,disabled:i,children:i?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`span`,{className:`spin`,style:{display:`inline-block`},children:`✱`}),` Connecting…`]}):`Sign in to Claude`})}),t&&_===`native`&&(0,h.jsxs)(`div`,{style:{marginTop:`14px`,maxWidth:`420px`,margin:`14px auto 0`,display:`flex`,flexDirection:`column`,gap:`8px`},children:[(0,h.jsx)(`label`,{style:{fontSize:`12px`,color:`#999`,lineHeight:1.4},children:`If the browser didn't finish sign-in, paste the code or full redirect URL from Claude's page:`}),(0,h.jsxs)(`div`,{style:{display:`flex`,gap:`8px`},children:[(0,h.jsx)(`input`,{type:`text`,value:y,onChange:e=>b(e.target.value),placeholder:`Paste code or redirect URL`,style:{flex:1,padding:`8px 10px`,fontSize:`13px`,border:`1px solid #ccc`,borderRadius:`6px`}}),(0,h.jsx)(c,{variant:`primary`,onClick:C,disabled:!y.trim(),children:`Submit`})]}),x&&(0,h.jsx)(`p`,{style:{fontSize:`12px`,color:`#999`,margin:0},children:x})]})]}),(0,h.jsx)(`div`,{style:{flex:1,display:`flex`,flexDirection:`column`,minHeight:0,gap:`10px`,padding:`0 0 16px`},children:(0,h.jsx)(`iframe`,{src:`/vnc-viewer.html`,style:{flex:1,width:`100%`,minHeight:0,border:`none`,background:`#111`,display:`block`},title:`Claude Sign-in`})}),s&&(0,h.jsx)(`p`,{className:`admin-pin-error`,style:{textAlign:`center`,padding:`0 20px 16px`},children:s})]}):(0,h.jsx)(`div`,{className:`connect-page`,children:(0,h.jsxs)(`div`,{className:`connect-content`,children:[(0,h.jsxs)(`div`,{className:`connect-logos`,children:[(0,h.jsx)(`div`,{className:`connect-logo-wrap`,children:(0,h.jsx)(`img`,{src:`/brand/claude.png`,alt:`Claude`,className:`connect-logo`})}),(0,h.jsx)(`svg`,{className:`connect-arrow`,viewBox:`0 0 48 24`,fill:`none`,xmlns:`http://www.w3.org/2000/svg`,children:(0,h.jsx)(`path`,{d:`M0 12h44m0 0l-8-8m8 8l-8 8`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`})}),(0,h.jsxs)(`div`,{className:`connect-logo-wrap`,children:[(0,h.jsx)(`img`,{src:a,alt:r.productName,className:`connect-logo connect-logo--maxy`}),!r.logoContainsName&&(0,h.jsx)(`span`,{className:`connect-logo-label`,children:r.productName})]})]}),(0,h.jsxs)(`h1`,{className:`connect-title`,children:[`Connect Claude to power `,r.productName]}),(0,h.jsx)(`p`,{className:`connect-subtitle`,children:`Sign in with your Anthropic account to get started.`}),(0,h.jsx)(c,{variant:`primary`,onClick:T,disabled:i,children:i?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`span`,{className:`spin`,style:{display:`inline-block`},children:`✱`}),` Connecting…`]}):`Sign in to Claude`}),(0,h.jsx)(`p`,{style:{marginTop:`6px`,fontSize:`11px`,color:`#999`,maxWidth:`300px`,textAlign:`center`,lineHeight:`1.4`},children:`First time? You may need to sign into your email and Anthropic account in the browser before connecting.`}),(0,h.jsx)(`button`,{onClick:w,disabled:m,style:{marginTop:`12px`,background:`none`,border:`none`,color:`var(--color-primary, #666)`,fontSize:`13px`,cursor:`pointer`,textDecoration:`underline`,textUnderlineOffset:`3px`},children:m?`Launching…`:`Open browser first`}),s&&(0,h.jsx)(`p`,{className:`admin-pin-error`,children:s})]})})}function b({auth:e}){return e.appState===`loading`?(0,h.jsx)(`div`,{className:`connect-page`}):e.appState===`set-pin`?(0,h.jsx)(_,{pin:e.pin,setPin:e.setPin,showPin:e.showPin,setShowPin:e.setShowPin,pinLoading:e.pinLoading,pinError:e.pinError,pinInputRef:e.pinInputRef,setPinFormRef:e.setPinFormRef,onSubmit:e.handleSetPin,operatorName:e.operatorName,setOperatorName:e.setOperatorName}):e.appState===`connect-claude`?(0,h.jsx)(y,{authPolling:e.authPolling,setAuthPolling:e.setAuthPolling,authLoading:e.authLoading,setAuthLoading:e.setAuthLoading,pinError:e.pinError,setPinError:e.setPinError,setAppState:e.setAppState}):e.appState===`enter-pin`?(0,h.jsx)(v,{pin:e.pin,setPin:e.setPin,showPin:e.showPin,setShowPin:e.setShowPin,pinLoading:e.pinLoading,pinError:e.pinError,pinInputRef:e.pinInputRef,onSubmit:e.handleLogin,remoteAuthEnabled:e.remoteAuthEnabled,onSignOutRemote:()=>{console.info(`[admin-ui] remote-auth sign-out → /__remote-auth/logout`),window.location.href=`/__remote-auth/logout`}}):null}export{m as n,b as t};