@rubytech/create-maxy-code 0.1.321 → 0.1.322
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/index.js +82 -1
- package/package.json +1 -1
- package/payload/platform/lib/embed-client/dist/index.d.ts +2 -0
- package/payload/platform/lib/embed-client/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/embed-client/dist/index.js +50 -10
- package/payload/platform/lib/embed-client/dist/index.js.map +1 -1
- package/payload/platform/lib/embed-client/src/index.ts +57 -9
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +4 -1
- package/payload/platform/plugins/docs/references/deployment.md +1 -0
- package/payload/platform/plugins/docs/references/internals.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-body-server-sliced.test.js +41 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-body-server-sliced.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +34 -4
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- 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 +75 -2
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +13 -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 +16 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/server/public/assets/{AdminLoginScreens-DKwWudsP.js → AdminLoginScreens-D1X4M9wX.js} +1 -1
- package/payload/server/public/assets/{AdminShell-BsPsXvJv.js → AdminShell-BGZ3d3gY.js} +1 -1
- package/payload/server/public/assets/{Checkbox-V5ubVZG1.js → Checkbox-DjANdKoK.js} +1 -1
- package/payload/server/public/assets/{OperatorConversations-DOGCHZhv.css → OperatorConversations-C-eFXT7w.css} +1 -1
- package/payload/server/public/assets/{admin-Cfqj5N4P.js → admin--FlkQOMu.js} +1 -1
- package/payload/server/public/assets/{browser-CtMwlpyW.js → browser-vowQc0TJ.js} +1 -1
- package/payload/server/public/assets/chat-Cw6rUWiC.js +1 -0
- package/payload/server/public/assets/{data-7_dN5vN5.js → data-9ArhwX1q.js} +1 -1
- package/payload/server/public/assets/{graph-Bez5QF37.js → graph-B-64Cq-y.js} +1 -1
- package/payload/server/public/assets/{graph-labels-BkbaBPIP.js → graph-labels-Dtd2ckXH.js} +1 -1
- package/payload/server/public/assets/{operator-F5KHg5WG.js → operator-Cqn1Ne60.js} +1 -1
- package/payload/server/public/assets/page-DQ5D6Dg9.js +30 -0
- package/payload/server/public/assets/{public-wy93nfmL.js → public-CrFepd06.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/chat.html +5 -5
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/operator.html +7 -7
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +185 -12
- package/payload/server/public/assets/chat-Bm11OGsU.js +0 -1
- package/payload/server/public/assets/page-CExyEcvh.js +0 -30
- /package/payload/server/public/assets/{OperatorConversations-DPRRlS_z.js → OperatorConversations-BkitSIiT.js} +0 -0
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Graph — Maxy</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/graph-
|
|
8
|
+
<script type="module" crossorigin src="/assets/graph-B-64Cq-y.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-Pqm5yXtL.js">
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/OperatorConversations-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/graph-labels-
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/AdminShell-
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/Checkbox-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/OperatorConversations-
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/OperatorConversations-BkitSIiT.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/graph-labels-Dtd2ckXH.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/AdminShell-BGZ3d3gY.js">
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/Checkbox-DjANdKoK.js">
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/OperatorConversations-C-eFXT7w.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Maxy</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/admin
|
|
8
|
+
<script type="module" crossorigin src="/assets/admin--FlkQOMu.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-Pqm5yXtL.js">
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/OperatorConversations-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/AdminShell-
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/OperatorConversations-BkitSIiT.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/AdminShell-BGZ3d3gY.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/admin-types-CJrGd46U.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/Checkbox-
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/AdminLoginScreens-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/OperatorConversations-
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/Checkbox-DjANdKoK.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/AdminLoginScreens-D1X4M9wX.js">
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/OperatorConversations-C-eFXT7w.css">
|
|
16
16
|
<link rel="stylesheet" crossorigin href="/assets/AdminLoginScreens-CWMpccrR.css">
|
|
17
17
|
<link rel="stylesheet" href="/brand-defaults.css">
|
|
18
18
|
</head>
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Operator — Maxy</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/operator-
|
|
8
|
+
<script type="module" crossorigin src="/assets/operator-Cqn1Ne60.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-Pqm5yXtL.js">
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/OperatorConversations-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/AdminShell-
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/OperatorConversations-BkitSIiT.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/AdminShell-BGZ3d3gY.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/admin-types-CJrGd46U.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/Checkbox-
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/AdminLoginScreens-
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/Checkbox-DjANdKoK.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/AdminLoginScreens-D1X4M9wX.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-Bf_JiD2A.js">
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/page-
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/assets/OperatorConversations-
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/page-DQ5D6Dg9.js">
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/OperatorConversations-C-eFXT7w.css">
|
|
18
18
|
<link rel="stylesheet" crossorigin href="/assets/AdminLoginScreens-CWMpccrR.css">
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Maxy</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/public-
|
|
8
|
+
<script type="module" crossorigin src="/assets/public-CrFepd06.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-Pqm5yXtL.js">
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/OperatorConversations-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/AdminShell-
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/OperatorConversations-BkitSIiT.js">
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/AdminShell-BGZ3d3gY.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/admin-types-CJrGd46U.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-Bf_JiD2A.js">
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/page-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/OperatorConversations-
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/page-DQ5D6Dg9.js">
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/OperatorConversations-C-eFXT7w.css">
|
|
16
16
|
<link rel="stylesheet" href="/brand-defaults.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body class="public-surface">
|
package/payload/server/server.js
CHANGED
|
@@ -8637,7 +8637,7 @@ ${note}` : note;
|
|
|
8637
8637
|
}
|
|
8638
8638
|
const known = resolvedProjectDir !== null || sessionSidecarExists(target);
|
|
8639
8639
|
const indicators2 = await fetchComposerIndicators(target);
|
|
8640
|
-
return c.json({ sessionId: target, projectDir: resolvedProjectDir, transcriptSessionId, channelBinding: conflictingBinding(channel), known, sizeBytes: jsonlSizeBytes(projectDir2, target), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${target}`) ?? null, ...indicators2, ...readLevers() });
|
|
8640
|
+
return c.json({ sessionId: target, projectDir: resolvedProjectDir, transcriptSessionId, channelBinding: conflictingBinding(channel), known, sizeBytes: jsonlSizeBytes(projectDir2, target), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${target}`) ?? null, deliveryFailure: deps.deliveryFailureFor?.(`session:${target}`) ?? null, ...indicators2, ...readLevers() });
|
|
8641
8641
|
}
|
|
8642
8642
|
let account;
|
|
8643
8643
|
try {
|
|
@@ -8670,7 +8670,7 @@ ${note}` : note;
|
|
|
8670
8670
|
}
|
|
8671
8671
|
}
|
|
8672
8672
|
const indicators = await fetchComposerIndicators(sessionId);
|
|
8673
|
-
return c.json({ sessionId, projectDir, sizeBytes: jsonlSizeBytes(projectDir, sessionId), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${sessionId}`) ?? null, ...indicators, ...readLevers(account) });
|
|
8673
|
+
return c.json({ sessionId, projectDir, sizeBytes: jsonlSizeBytes(projectDir, sessionId), pendingPermissionPrompt: deps.pendingPromptFor?.(`session:${sessionId}`) ?? null, deliveryFailure: deps.deliveryFailureFor?.(`session:${sessionId}`) ?? null, ...indicators, ...readLevers(account) });
|
|
8674
8674
|
});
|
|
8675
8675
|
app53.post("/permission-verdict", requireAdminSession, async (c) => {
|
|
8676
8676
|
const body = await c.req.json().catch(() => null);
|
|
@@ -8914,6 +8914,33 @@ async function managerRcSpawn(opts) {
|
|
|
8914
8914
|
const out = await r.json();
|
|
8915
8915
|
return { sessionId: out.sessionId ?? opts.sessionId, pid: out.spawnedPid ?? null };
|
|
8916
8916
|
}
|
|
8917
|
+
async function managerRcRecover(opts) {
|
|
8918
|
+
const auth = await ensureAuth();
|
|
8919
|
+
if (auth.status === "dead" || auth.status === "missing") {
|
|
8920
|
+
console.log(`[channel-pty-bridge] rc-recover-refused reason=auth-${auth.status} ${opts.logContext}`);
|
|
8921
|
+
return { error: "claude-auth-dead", status: 503 };
|
|
8922
|
+
}
|
|
8923
|
+
const res = await fetch(`${managerBase2()}/rc-spawn`, {
|
|
8924
|
+
method: "POST",
|
|
8925
|
+
headers: { "content-type": "application/json" },
|
|
8926
|
+
body: JSON.stringify({
|
|
8927
|
+
sessionId: opts.sessionId,
|
|
8928
|
+
recover: true,
|
|
8929
|
+
role: "admin",
|
|
8930
|
+
channel: "webchat",
|
|
8931
|
+
senderId: opts.webchatChannel.key,
|
|
8932
|
+
adminUserId: opts.adminUserId,
|
|
8933
|
+
webchatChannel: opts.webchatChannel
|
|
8934
|
+
})
|
|
8935
|
+
}).catch((err) => ({ __throw: err instanceof Error ? err.message : String(err) }));
|
|
8936
|
+
if ("__throw" in res) {
|
|
8937
|
+
return { error: res.__throw, status: 0 };
|
|
8938
|
+
}
|
|
8939
|
+
const r = res;
|
|
8940
|
+
if (!r.ok) return { error: `manager-status-${r.status}`, status: r.status };
|
|
8941
|
+
const out = await r.json();
|
|
8942
|
+
return { live: out.live === true, classification: out.classification, recovered: out.recovered === true };
|
|
8943
|
+
}
|
|
8917
8944
|
async function managerWriteInput(sessionId, text) {
|
|
8918
8945
|
const res = await fetch(`${managerBase2()}/${sessionId}/input`, {
|
|
8919
8946
|
method: "POST",
|
|
@@ -19439,12 +19466,12 @@ var InboundHub2 = class {
|
|
|
19439
19466
|
* that never reached the agent:
|
|
19440
19467
|
* - a still-queued message older than queuedThresholdMs WHILE a subscriber
|
|
19441
19468
|
* is attached -> inbound-undelivered (the readiness handoff broke);
|
|
19442
|
-
* - a still-queued message older than queuedThresholdMs with NO subscriber
|
|
19443
|
-
* attached -> spawn-undelivered (the channel server never attached: spawn
|
|
19444
|
-
* failed, or the session ended and never resumed);
|
|
19445
19469
|
* - a drained message unacked past ackThresholdMs -> drain-undelivered (the
|
|
19446
19470
|
* channel server never confirmed receipt — the silent cold-start drop).
|
|
19447
|
-
*
|
|
19471
|
+
* A still-queued message with NO subscriber is NOT handled here: the gateway
|
|
19472
|
+
* owns that policy via staleQueued/dropQueued (Task-962) — a live session is
|
|
19473
|
+
* given a recovery window + respawn before any drop; a not-live one keeps the
|
|
19474
|
+
* cold-start drop. Each message is emitted at most once (removed when emitted). */
|
|
19448
19475
|
sweep(now, t) {
|
|
19449
19476
|
const events = [];
|
|
19450
19477
|
for (const [key, s] of this.keys) {
|
|
@@ -19453,10 +19480,6 @@ var InboundHub2 = class {
|
|
|
19453
19480
|
events.push({ kind: "inbound-undelivered", key, messageId: e.payload.messageId, ageMs: now - e.enqueuedAt });
|
|
19454
19481
|
return false;
|
|
19455
19482
|
}
|
|
19456
|
-
if (e.state === "queued" && !s.subscriber && now - e.enqueuedAt > t.queuedThresholdMs) {
|
|
19457
|
-
events.push({ kind: "spawn-undelivered", key, messageId: e.payload.messageId, ageMs: now - e.enqueuedAt });
|
|
19458
|
-
return false;
|
|
19459
|
-
}
|
|
19460
19483
|
if (e.state === "drained" && e.drainedAt != null && now - e.drainedAt > t.ackThresholdMs) {
|
|
19461
19484
|
events.push({ kind: "drain-undelivered", key, messageId: e.payload.messageId, ageMs: now - e.drainedAt });
|
|
19462
19485
|
return false;
|
|
@@ -19466,6 +19489,32 @@ var InboundHub2 = class {
|
|
|
19466
19489
|
}
|
|
19467
19490
|
return events;
|
|
19468
19491
|
}
|
|
19492
|
+
/** Task-962 — queued messages with NO subscriber attached, older than
|
|
19493
|
+
* thresholdMs. Read-only: it does NOT remove anything. The gateway drives
|
|
19494
|
+
* recovery/drop for these (a live session gets a redelivery window + a wedged
|
|
19495
|
+
* respawn; a not-live one keeps the immediate cold-start drop). */
|
|
19496
|
+
staleQueued(now, thresholdMs) {
|
|
19497
|
+
const out = [];
|
|
19498
|
+
for (const [key, s] of this.keys) {
|
|
19499
|
+
if (s.subscriber) continue;
|
|
19500
|
+
for (const e of s.inFlight) {
|
|
19501
|
+
if (e.state === "queued" && now - e.enqueuedAt > thresholdMs) {
|
|
19502
|
+
out.push({ key, messageId: e.payload.messageId, ageMs: now - e.enqueuedAt });
|
|
19503
|
+
}
|
|
19504
|
+
}
|
|
19505
|
+
}
|
|
19506
|
+
return out;
|
|
19507
|
+
}
|
|
19508
|
+
/** Task-962 — terminal-drop one still-queued in-flight message by id. Returns
|
|
19509
|
+
* whether a matching queued entry was found and removed. */
|
|
19510
|
+
dropQueued(key, messageId) {
|
|
19511
|
+
const s = this.keys.get(key);
|
|
19512
|
+
if (!s) return false;
|
|
19513
|
+
const i = s.inFlight.findIndex((e) => e.payload.messageId === messageId && e.state === "queued");
|
|
19514
|
+
if (i < 0) return false;
|
|
19515
|
+
s.inFlight.splice(i, 1);
|
|
19516
|
+
return true;
|
|
19517
|
+
}
|
|
19469
19518
|
/** Whether a key currently has a live channel server attached. The gateway
|
|
19470
19519
|
* uses this to decide whether an inbound needs a cold-start spawn/resume. */
|
|
19471
19520
|
hasSubscriber(key) {
|
|
@@ -19550,6 +19599,8 @@ function createWebchatChannelRoutes(deps) {
|
|
|
19550
19599
|
var QUEUED_THRESHOLD_MS2 = 3e4;
|
|
19551
19600
|
var ACK_THRESHOLD_MS2 = 3e4;
|
|
19552
19601
|
var SWEEP_INTERVAL_MS2 = 15e3;
|
|
19602
|
+
var RECOVER_TRIGGER_MS = Number(process.env.WEBCHAT_RECOVER_TRIGGER_MS ?? String(3e4));
|
|
19603
|
+
var DELIVER_FINAL_MS = Number(process.env.WEBCHAT_DELIVER_FINAL_MS ?? String(9e4));
|
|
19553
19604
|
function channelIdleMs() {
|
|
19554
19605
|
return Number(process.env.CHANNEL_PTY_IDLE_MS ?? String(5 * 6e4));
|
|
19555
19606
|
}
|
|
@@ -19581,6 +19632,18 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
19581
19632
|
/** Task 756 — live public sessions, for the idle reaper + session-end review.
|
|
19582
19633
|
* Admin sessions are never recorded here (they never reap). */
|
|
19583
19634
|
publicSessions = /* @__PURE__ */ new Map();
|
|
19635
|
+
/** Task-962 — per-key recover context, set on each admin inbound so the
|
|
19636
|
+
* recovery pass can ask the manager to respawn a wedged live child. */
|
|
19637
|
+
recoverable = /* @__PURE__ */ new Map();
|
|
19638
|
+
/** Task-962 — per-session delivery-failure markers (set when redelivery is
|
|
19639
|
+
* exhausted; cleared on the next successful drain/ack). Surfaced on the
|
|
19640
|
+
* /session pointer the operator /chat polls. */
|
|
19641
|
+
deliveryFailures = /* @__PURE__ */ new Map();
|
|
19642
|
+
/** Task-962 — `${key}::${messageId}` for messages a recovery call has already
|
|
19643
|
+
* been issued for, so recovery is attempted at most once per message. */
|
|
19644
|
+
recoveryAttempted = /* @__PURE__ */ new Set();
|
|
19645
|
+
/** Task-962 — guards against overlapping async recovery passes. */
|
|
19646
|
+
recovering = false;
|
|
19584
19647
|
seq = 0;
|
|
19585
19648
|
/** Register a one-shot awaiter for the next reply on `key`. Resolves with the
|
|
19586
19649
|
* reply text, or `{ timeout: true }` if no reply arrives within `timeoutMs`.
|
|
@@ -19702,6 +19765,7 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
19702
19765
|
if (count > 0) {
|
|
19703
19766
|
console.error(`[webchat:inbound] op=drained key=${key} count=${count}`);
|
|
19704
19767
|
this.lastDeliveredAt.set(key, now);
|
|
19768
|
+
this.deliveryFailures.delete(key);
|
|
19705
19769
|
}
|
|
19706
19770
|
},
|
|
19707
19771
|
onReceived: (key, messageId) => {
|
|
@@ -19709,6 +19773,10 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
19709
19773
|
console.error(
|
|
19710
19774
|
`[webchat:inbound] op=channel-received key=${key} messageId=${messageId}${found ? "" : " note=unknown"}`
|
|
19711
19775
|
);
|
|
19776
|
+
if (found) {
|
|
19777
|
+
this.recoveryAttempted.delete(`${key}::${messageId}`);
|
|
19778
|
+
this.deliveryFailures.delete(key);
|
|
19779
|
+
}
|
|
19712
19780
|
},
|
|
19713
19781
|
onReply: (key, text) => this.handleReply(key, text),
|
|
19714
19782
|
awaitPermissionVerdict: (p) => this.awaitPermissionVerdict(p, permissionVerdictTtlMs())
|
|
@@ -19727,9 +19795,85 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
19727
19795
|
}
|
|
19728
19796
|
return events;
|
|
19729
19797
|
}
|
|
19730
|
-
/** Start the periodic reconciliation sweep in the long-lived UI process.
|
|
19798
|
+
/** Start the periodic reconciliation sweep in the long-lived UI process. Each
|
|
19799
|
+
* tick runs the sync sweep (drain-/inbound-undelivered) AND the async
|
|
19800
|
+
* recovery pass for queued-no-subscriber messages (Task-962). */
|
|
19731
19801
|
startSweeper(intervalMs = SWEEP_INTERVAL_MS2) {
|
|
19732
|
-
return setInterval(() =>
|
|
19802
|
+
return setInterval(() => {
|
|
19803
|
+
const now = Date.now();
|
|
19804
|
+
this.reconcile(now);
|
|
19805
|
+
void this.recoverStalled(now);
|
|
19806
|
+
}, intervalMs);
|
|
19807
|
+
}
|
|
19808
|
+
/** Task-962 — the per-session delivery-failure marker for the /session pointer.
|
|
19809
|
+
* Non-null only when redelivery was exhausted for `key`; cleared on the next
|
|
19810
|
+
* successful drain/ack. */
|
|
19811
|
+
deliveryFailureFor(key) {
|
|
19812
|
+
return this.deliveryFailures.get(key) ?? null;
|
|
19813
|
+
}
|
|
19814
|
+
/** Task-962 — clear a key's delivery-failure marker (a later message drained,
|
|
19815
|
+
* or the operator dismissed/retried). */
|
|
19816
|
+
clearDeliveryFailure(key) {
|
|
19817
|
+
this.deliveryFailures.delete(key);
|
|
19818
|
+
}
|
|
19819
|
+
/** Task-962 — recovery-aware pass over queued-no-subscriber messages.
|
|
19820
|
+
* - age in [RECOVER_TRIGGER_MS, DELIVER_FINAL_MS): ask the manager to recover
|
|
19821
|
+
* the live session ONCE. busy → leave queued for the channel server's
|
|
19822
|
+
* auto-reconnect to drain; wedged → the manager respawns and the resumed
|
|
19823
|
+
* child re-binds the channel + drains. A not-live target (or no recover
|
|
19824
|
+
* dep/descriptor) is dropped now — the cold-start class, out of scope.
|
|
19825
|
+
* - age ≥ DELIVER_FINAL_MS: terminal-drop and record a delivery-failure
|
|
19826
|
+
* marker (redelivery exhausted).
|
|
19827
|
+
* Single-flight; safe to call every sweep tick. */
|
|
19828
|
+
async recoverStalled(now) {
|
|
19829
|
+
if (this.recovering) return;
|
|
19830
|
+
this.recovering = true;
|
|
19831
|
+
try {
|
|
19832
|
+
for (const c of this.hub.staleQueued(now, RECOVER_TRIGGER_MS)) {
|
|
19833
|
+
const attemptKey = `${c.key}::${c.messageId}`;
|
|
19834
|
+
if (c.ageMs >= DELIVER_FINAL_MS) {
|
|
19835
|
+
this.hub.dropQueued(c.key, c.messageId);
|
|
19836
|
+
this.recoveryAttempted.delete(attemptKey);
|
|
19837
|
+
this.deliveryFailures.set(c.key, { messageId: c.messageId, at: now });
|
|
19838
|
+
console.error(
|
|
19839
|
+
`[webchat:inbound] op=delivery-failed key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs}`
|
|
19840
|
+
);
|
|
19841
|
+
continue;
|
|
19842
|
+
}
|
|
19843
|
+
if (this.recoveryAttempted.has(attemptKey)) continue;
|
|
19844
|
+
const d = this.recoverable.get(c.key);
|
|
19845
|
+
if (!d || !this.deps.recoverLiveSession) {
|
|
19846
|
+
this.hub.dropQueued(c.key, c.messageId);
|
|
19847
|
+
console.error(
|
|
19848
|
+
`[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=no-descriptor`
|
|
19849
|
+
);
|
|
19850
|
+
continue;
|
|
19851
|
+
}
|
|
19852
|
+
this.recoveryAttempted.add(attemptKey);
|
|
19853
|
+
let r;
|
|
19854
|
+
try {
|
|
19855
|
+
r = await this.deps.recoverLiveSession(d);
|
|
19856
|
+
} catch (err) {
|
|
19857
|
+
console.error(
|
|
19858
|
+
`[webchat:inbound] op=recover-error key=${keyDisplay(c.key)} messageId=${c.messageId} err=${err instanceof Error ? err.message : String(err)}`
|
|
19859
|
+
);
|
|
19860
|
+
continue;
|
|
19861
|
+
}
|
|
19862
|
+
if (!r.live) {
|
|
19863
|
+
this.hub.dropQueued(c.key, c.messageId);
|
|
19864
|
+
this.recoveryAttempted.delete(attemptKey);
|
|
19865
|
+
console.error(
|
|
19866
|
+
`[webchat:inbound] op=spawn-undelivered key=${keyDisplay(c.key)} messageId=${c.messageId} ageMs=${c.ageMs} reason=not-live`
|
|
19867
|
+
);
|
|
19868
|
+
} else {
|
|
19869
|
+
console.error(
|
|
19870
|
+
`[webchat:inbound] op=recover key=${keyDisplay(c.key)} messageId=${c.messageId} classification=${r.classification ?? "unknown"} respawned=${r.recovered === true}`
|
|
19871
|
+
);
|
|
19872
|
+
}
|
|
19873
|
+
}
|
|
19874
|
+
} finally {
|
|
19875
|
+
this.recovering = false;
|
|
19876
|
+
}
|
|
19733
19877
|
}
|
|
19734
19878
|
/** Handle one inbound webchat message (browser POST → here). */
|
|
19735
19879
|
async handleInbound(input) {
|
|
@@ -19744,6 +19888,15 @@ var WebchatGateway = class _WebchatGateway {
|
|
|
19744
19888
|
console.error(
|
|
19745
19889
|
`[webchat:inbound] op=received key=${keyDisplay(input.key)} role=${input.role} bytes=${bytes} delivery=${willDeliverNow ? "immediate" : "queued"}`
|
|
19746
19890
|
);
|
|
19891
|
+
if (input.role === "admin") {
|
|
19892
|
+
this.recoverable.set(input.key, {
|
|
19893
|
+
accountId: input.accountId,
|
|
19894
|
+
key: input.key,
|
|
19895
|
+
role: input.role,
|
|
19896
|
+
...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
|
|
19897
|
+
...input.adminUserId !== void 0 ? { adminUserId: input.adminUserId } : {}
|
|
19898
|
+
});
|
|
19899
|
+
}
|
|
19747
19900
|
const existing = this.publicSessions.get(input.key);
|
|
19748
19901
|
if (existing) existing.lastActivityAt = Date.now();
|
|
19749
19902
|
if (!hadSubscriber && !this.spawning.has(input.key)) {
|
|
@@ -20313,6 +20466,25 @@ var webchatGateway = new WebchatGateway({
|
|
|
20313
20466
|
}
|
|
20314
20467
|
return { sessionId: result.sessionId };
|
|
20315
20468
|
},
|
|
20469
|
+
// Task-962 — the recovery pass asks the manager to respawn a wedged already-
|
|
20470
|
+
// live admin session (rc-spawn recover:true). Resolve the session id the same
|
|
20471
|
+
// way ensureChannelSession does. Public sessions recover via the idle reaper,
|
|
20472
|
+
// so a non-admin descriptor is reported not-live (no respawn).
|
|
20473
|
+
recoverLiveSession: async ({ accountId, key, role, sessionId, adminUserId }) => {
|
|
20474
|
+
if (role !== "admin") return { live: false };
|
|
20475
|
+
const id = sessionId ?? adminSessionIdFor(accountId, key, adminUserId);
|
|
20476
|
+
const r = await managerRcRecover({
|
|
20477
|
+
sessionId: id,
|
|
20478
|
+
webchatChannel: { key, gatewayUrl: webchatGatewayUrl(), serverPath: webchatServerPath() },
|
|
20479
|
+
...adminUserId !== void 0 ? { adminUserId } : {},
|
|
20480
|
+
logContext: `channel=webchat key=${key} recover`
|
|
20481
|
+
});
|
|
20482
|
+
if ("error" in r) {
|
|
20483
|
+
console.error(`[webchat:inbound] op=recover-failed key=${key} error=${r.error} status=${r.status}`);
|
|
20484
|
+
return { live: false };
|
|
20485
|
+
}
|
|
20486
|
+
return r;
|
|
20487
|
+
},
|
|
20316
20488
|
// Task 756 — the public idle reaper deletes the manager PTY and dispatches the
|
|
20317
20489
|
// session-end review (gated visitors), since the native path has no bridge
|
|
20318
20490
|
// entry to drive them. Admin sessions are never recorded, so never reaped.
|
|
@@ -20641,6 +20813,7 @@ app52.route("/api/webchat", createWebchatRoutes({
|
|
|
20641
20813
|
handleInbound: (input) => webchatGateway.handleInbound(input),
|
|
20642
20814
|
// Task 940 — the permission relay's pointer read + verdict write.
|
|
20643
20815
|
pendingPromptFor: (key) => webchatGateway.pendingPromptFor(key),
|
|
20816
|
+
deliveryFailureFor: (key) => webchatGateway.deliveryFailureFor(key),
|
|
20644
20817
|
resolvePermissionVerdict: (key, requestId, behavior) => webchatGateway.resolvePermissionVerdict(key, requestId, behavior)
|
|
20645
20818
|
}));
|
|
20646
20819
|
app52.route("/api/webchat/greeting", webchat_greeting_default);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{L as e,x as t}from"./OperatorConversations-DPRRlS_z.js";import"./admin-types-CJrGd46U.js";import"./AdminShell-BsPsXvJv.js";import{n}from"./page-CExyEcvh.js";var r=e(),i=t();(0,r.createRoot)(document.getElementById(`root`)).render((0,i.jsx)(n,{}));
|