@rubytech/create-maxy-code 0.1.575 → 0.1.577

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 (83) hide show
  1. package/dist/__tests__/preserve-house-cloudflare.test.js +2 -72
  2. package/dist/index.js +8 -59
  3. package/dist/preserve-house-cloudflare.js +2 -49
  4. package/package.json +1 -1
  5. package/payload/platform/lib/active-rules/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/active-rules/dist/index.js +32 -1
  7. package/payload/platform/lib/active-rules/dist/index.js.map +1 -1
  8. package/payload/platform/lib/active-rules/src/index.test.ts +52 -3
  9. package/payload/platform/lib/active-rules/src/index.ts +36 -2
  10. package/payload/platform/lib/dispatch-read/dist/allocate.d.ts +57 -0
  11. package/payload/platform/lib/dispatch-read/dist/allocate.d.ts.map +1 -0
  12. package/payload/platform/lib/dispatch-read/dist/allocate.js +33 -0
  13. package/payload/platform/lib/dispatch-read/dist/allocate.js.map +1 -0
  14. package/payload/platform/lib/dispatch-read/dist/index.d.ts +2 -0
  15. package/payload/platform/lib/dispatch-read/dist/index.d.ts.map +1 -1
  16. package/payload/platform/lib/dispatch-read/dist/index.js +10 -1
  17. package/payload/platform/lib/dispatch-read/dist/index.js.map +1 -1
  18. package/payload/platform/lib/dispatch-read/src/__tests__/allocate.test.ts +41 -0
  19. package/payload/platform/lib/dispatch-read/src/allocate.ts +89 -0
  20. package/payload/platform/lib/dispatch-read/src/index.ts +14 -0
  21. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +7 -2
  22. package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +8 -2
  23. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
  24. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
  25. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-visit-status-write.test.ts +333 -22
  26. package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-visits-scope.test.ts +262 -0
  27. package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +126 -21
  28. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/visit-status.ts +142 -2
  29. package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/visits.ts +118 -6
  30. package/payload/platform/plugins/dispatch/PLUGIN.md +2 -0
  31. package/payload/platform/plugins/dispatch/mcp/dist/lib/allocate.d.ts +19 -50
  32. package/payload/platform/plugins/dispatch/mcp/dist/lib/allocate.d.ts.map +1 -1
  33. package/payload/platform/plugins/dispatch/mcp/dist/lib/allocate.js +1 -28
  34. package/payload/platform/plugins/dispatch/mcp/dist/lib/allocate.js.map +1 -1
  35. package/payload/platform/plugins/dispatch/skills/dispatch-page/SKILL.md +45 -6
  36. package/payload/platform/plugins/dispatch/skills/dispatch-page/template/planner.css +60 -0
  37. package/payload/platform/plugins/dispatch/skills/dispatch-page/template/planner.html +13 -0
  38. package/payload/platform/plugins/dispatch/skills/dispatch-page/template/planner.js +203 -5
  39. package/payload/platform/plugins/docs/references/internals.md +1 -1
  40. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-path.test.d.ts +2 -0
  41. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-path.test.d.ts.map +1 -0
  42. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-path.test.js +19 -0
  43. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-path.test.js.map +1 -0
  44. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-register.test.d.ts +2 -0
  45. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-register.test.d.ts.map +1 -0
  46. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-register.test.js +110 -0
  47. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-register.test.js.map +1 -0
  48. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-verdict.test.d.ts +2 -0
  49. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-verdict.test.d.ts.map +1 -0
  50. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-verdict.test.js +22 -0
  51. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-verdict.test.js.map +1 -0
  52. package/payload/platform/plugins/telegram/mcp/dist/index.js +19 -163
  53. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  54. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +9 -0
  55. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -1
  56. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +11 -0
  57. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -1
  58. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts +30 -3
  59. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts.map +1 -1
  60. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js +29 -4
  61. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js.map +1 -1
  62. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.d.ts +33 -0
  63. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.d.ts.map +1 -0
  64. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.js +145 -0
  65. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.js.map +1 -0
  66. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +4 -2
  67. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +5 -0
  68. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  69. package/payload/platform/services/claude-session-manager/dist/http-server.js +29 -0
  70. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  71. package/payload/platform/services/telegram-channel/dist/notification.d.ts +35 -3
  72. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  73. package/payload/platform/services/telegram-channel/dist/notification.js +38 -5
  74. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  75. package/payload/platform/services/telegram-channel/dist/server.js +1 -1
  76. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  77. package/payload/platform/services/webchat-channel/dist/notification.d.ts +2 -1
  78. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  79. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  80. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +2 -2
  81. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  82. package/payload/server/server.js +670 -155
  83. package/dist/__tests__/converge-client-admins.test.js +0 -50
@@ -0,0 +1,145 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { admitsWebhookPath, botIdFromToken, persistTelegramBot, CANONICAL_TELEGRAM_PREFIX, } from "../lib/account-write.js";
4
+ import { setWebhook, getWebhookInfo, webhookVerdict } from "../lib/telegram.js";
5
+ import { configDirName, resolveSecretFilePath } from "../lib/secret-path.js";
6
+ import { accountDirFor, isActiveAgentSlug } from "../lib/agent-active.js";
7
+ import { buildWebhookUrl } from "../lib/webhook-url.js";
8
+ const TAG = "[telegram-setup]";
9
+ const fail = (text) => ({ text, isError: true });
10
+ /** Register or re-register one bot's webhook.
11
+ *
12
+ * Task 2560 — the order is load-bearing. The path guard runs before Telegram is
13
+ * touched, so a refused path leaves no registration behind. The freshness stamp
14
+ * is taken immediately before setWebhook so the probe can tell an error
15
+ * Telegram saw since THIS registration from one it kept from the previous URL.
16
+ * The persist runs LAST: a registration that cannot be proved reachable leaves
17
+ * nothing on disk. */
18
+ export async function webhookRegister(params, deps) {
19
+ const { botToken, webhookUrl, role, agent, adminUsers, dmPolicy, allowFrom } = params;
20
+ const { platformRoot, accountId, log } = deps;
21
+ const fetchImpl = deps.fetchImpl ?? fetch;
22
+ const nowSec = deps.nowSec ?? (() => Math.floor(Date.now() / 1000));
23
+ const secretPathFor = deps.secretPathFor ?? resolveSecretFilePath;
24
+ // Task 2365 — the account comes first, because the secret path and the
25
+ // registered URL are both per-account now and neither can be built without
26
+ // it. This guard used to sit after setWebhook, where a missing ACCOUNT_ID
27
+ // still left a registered webhook behind.
28
+ if (!platformRoot || !accountId) {
29
+ log(`${TAG} op=token-write-rejected reason=no-account-env`);
30
+ return fail(`PLATFORM_ROOT/ACCOUNT_ID not in MCP env — cannot persist the bot token to account.json`);
31
+ }
32
+ // Task 2368 — the bot id is the token's colon-prefix. It keys the secret
33
+ // file, the registered URL and the config entry, so a malformed token has
34
+ // nowhere to land and is refused before anything is written or registered.
35
+ const botId = botIdFromToken(botToken);
36
+ if (!botId) {
37
+ log(`${TAG} op=token-write-rejected reason=malformed-token`);
38
+ return fail(`Bot token is malformed — it must look like 123456:ABC-DEF…`);
39
+ }
40
+ // A public bot must name the agent it answers as. An admin bot names none:
41
+ // it reads agents/admin by construction, so an `agent` field there would be
42
+ // stored and never consumed.
43
+ if (role === "public") {
44
+ if (!agent || agent.length === 0) {
45
+ log(`${TAG} op=token-write-rejected reason=no-agent botId=${botId}`);
46
+ return fail(`A public bot must name the agent it answers as. Pass \`agent\` with one of this account's active agent slugs.`);
47
+ }
48
+ if (!isActiveAgentSlug(accountDirFor(platformRoot, accountId), agent)) {
49
+ log(`${TAG} op=token-write-rejected reason=agent-inactive botId=${botId} agent=${agent}`);
50
+ return fail(`Agent "${agent}" is not an active agent on this account, so a bot cannot answer as it.`);
51
+ }
52
+ }
53
+ // Task 2560 — exactly one path reaches the handler. Asserted before Telegram
54
+ // is touched, so a refused path leaves no registration behind.
55
+ const webhookPath = (() => {
56
+ try {
57
+ return new URL(webhookUrl).pathname;
58
+ }
59
+ catch {
60
+ return webhookUrl;
61
+ }
62
+ })();
63
+ const admitted = admitsWebhookPath(webhookPath);
64
+ log(`${TAG} op=webhook-register path=${webhookPath} botId=${botId} account=${accountId} admitted=${admitted}`);
65
+ if (!admitted) {
66
+ return fail(`Webhook path "${webhookPath}" is not ${CANONICAL_TELEGRAM_PREFIX} — that is the only path this ` +
67
+ `install serves, so anything else 404s on every inbound. Use a URL whose path is exactly ${CANONICAL_TELEGRAM_PREFIX}`);
68
+ }
69
+ const secretFilePath = secretPathFor(botId);
70
+ mkdirSync(dirname(secretFilePath), { recursive: true, mode: 0o700 });
71
+ // Task 2368 — the URL Telegram is actually given. buildWebhookUrl only
72
+ // touches the query, so the admitted check above still describes the path
73
+ // that gets registered.
74
+ const registeredUrl = buildWebhookUrl(webhookUrl, botId);
75
+ try {
76
+ // Verify the token is valid by calling getMe
77
+ const meRes = await fetchImpl(`https://api.telegram.org/bot${botToken}/getMe`);
78
+ const meData = (await meRes.json());
79
+ if (!meData.ok) {
80
+ return fail(`Invalid bot token: ${meData.description ?? "getMe failed"}`);
81
+ }
82
+ const botUsername = meData.result?.username ?? "unknown";
83
+ // Stamped immediately before registering: the probe below only treats an
84
+ // error Telegram saw AT OR AFTER this moment as this registration's.
85
+ const registeredAtSec = nowSec();
86
+ const result = await setWebhook(botToken, registeredUrl, secretFilePath, fetchImpl);
87
+ if (!result.ok) {
88
+ return fail(`Webhook registration failed: ${result.error}`);
89
+ }
90
+ // Task 2560 — prove the URL reaches the handler before anything lands on
91
+ // disk. A probe that THROWS does not fail the registration: a transient
92
+ // network error is not evidence of unreachability. That leaves one blind
93
+ // spot, and probe=failed is its only marker.
94
+ let verifyInfo = "";
95
+ try {
96
+ const info = await getWebhookInfo(botToken, fetchImpl);
97
+ const verdict = webhookVerdict(info, registeredAtSec);
98
+ log(`${TAG} op=webhook-verify path=${webhookPath} botId=${botId} reachable=${verdict.reachable} ` +
99
+ `lastError=${info.lastErrorMessage ?? "none"} lastErrorDate=${info.lastErrorDate} ` +
100
+ `registeredAt=${registeredAtSec} pending=${info.pendingUpdateCount}`);
101
+ if (!verdict.reachable) {
102
+ return fail(`Webhook registered at ${registeredUrl} but Telegram cannot reach it: ${verdict.reason}. ` +
103
+ `Nothing was written to account.json. Fix the tunnel or the app and register again.`);
104
+ }
105
+ verifyInfo = `\nURL: ${info.url}\nPending updates: ${info.pendingUpdateCount}`;
106
+ }
107
+ catch (e) {
108
+ log(`${TAG} op=webhook-verify path=${webhookPath} botId=${botId} probe=failed ` +
109
+ `reason=${e instanceof Error ? e.message : String(e)}`);
110
+ verifyInfo = "\n(Could not verify — getWebhookInfo failed, but registration succeeded)";
111
+ }
112
+ // Persist this bot's entry into account.json's telegram.bots array, under
113
+ // THIS spawn's account. Task 2368 — the inbound receiver resolves the same
114
+ // entry by the bot id on the registered URL, and the write replaces only
115
+ // the entry with that id, so registering a second bot cannot disturb the
116
+ // first. persistTelegramBot asserts the resolved dir is under the brand
117
+ // install BEFORE writing (non-brand-path is rejected, not written), so a
118
+ // failure here means nothing was persisted.
119
+ const entry = role === "admin"
120
+ ? { id: botId, token: botToken, role: "admin", ...(adminUsers ? { adminUsers } : {}) }
121
+ : { id: botId, token: botToken, role: "public", agent: agent, ...(dmPolicy ? { dmPolicy } : {}), ...(allowFrom ? { allowFrom } : {}) };
122
+ const persisted = persistTelegramBot(platformRoot, accountId, entry);
123
+ if (!persisted.ok) {
124
+ log(`${TAG} op=token-write-rejected reason=${persisted.error}${persisted.dir ? ` dir=${persisted.dir}` : ""}`);
125
+ return fail(persisted.error === "non-brand-path"
126
+ ? `Refusing to write token outside the brand install: ${persisted.dir}`
127
+ : `Token persist failed: ${persisted.error}`);
128
+ }
129
+ log(`${TAG} op=token-write dir=${persisted.dir} brand=${configDirName()}`);
130
+ // Task 2368 — the pre-registry secret files are removed by the boot
131
+ // converter (server/telegram-migrate.ts), which owns the whole two-slot
132
+ // shape. This tool no longer deletes anything of its own.
133
+ return {
134
+ isError: false,
135
+ text: `Webhook registered for @${botUsername} (bot ${botId}, role ${role}${role === "public" ? `, answering as ${agent}` : ""})` +
136
+ `\nURL: ${registeredUrl}` +
137
+ `\nSecret: generated and saved to ${secretFilePath}` +
138
+ verifyInfo,
139
+ };
140
+ }
141
+ catch (err) {
142
+ return fail(`Webhook registration error: ${err instanceof Error ? err.message : String(err)}`);
143
+ }
144
+ }
145
+ //# sourceMappingURL=webhook-register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-register.js","sourceRoot":"","sources":["../../src/tools/webhook-register.ts"],"names":[],"mappings":"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"}
@@ -58,7 +58,7 @@ Call `telegram-webhook-register` with:
58
58
  - admin bot: `adminUsers` — the numeric IDs from Step 2
59
59
  - public bot: `agent` — the slug from Step 2, plus `dmPolicy` and, for `allowlist`, `allowFrom`
60
60
 
61
- On success the tool has: verified the token, derived the bot id, set the webhook with a generated secret (written to `~/.<brand>/telegram-secrets/<botId>`), persisted the entry into `account.json`'s `telegram.bots` array, and logged `op=token-write` + `op=webhook-register admitted=true`. It replaces only the entry with this bot's id, so the account's other bots are untouched. If it returns an error, read it back to the operator verbatim — the common ones are a wrong path (`admitted=false`), a public bot with no or an inactive `agent` (`op=token-write-rejected reason=no-agent|agent-inactive`), or a token write blocked outside the brand install.
61
+ On success the tool has, in this order: verified the token, derived the bot id, set the webhook with a generated secret (written to `~/.<brand>/telegram-secrets/<botId>`), probed Telegram and confirmed no delivery error since the registration, and only then persisted the entry into `account.json`'s `telegram.bots` array. It logs `op=webhook-register admitted=true` + `op=webhook-verify reachable=true` + `op=token-write`. It replaces only the entry with this bot's id, so the account's other bots are untouched. If it returns an error, read it back to the operator verbatim — the common ones are a wrong path (`admitted=false`), a webhook Telegram cannot reach (`op=webhook-verify reachable=false`), a public bot with no or an inactive `agent` (`op=token-write-rejected reason=no-agent|agent-inactive`), or a token write blocked outside the brand install.
62
62
 
63
63
  ### Step 5 — Confirm
64
64
 
@@ -66,7 +66,9 @@ Tell the operator the bot is connected, which role it has, and that messaging th
66
66
 
67
67
  ## Failure modes (state these loudly, do not paper over)
68
68
 
69
- - **Wrong webhook path** — register returns an error and logs `op=webhook-register admitted=false`. Fix the URL to use `/api/telegram/`.
69
+ - **Wrong webhook path** — register returns an error and logs `op=webhook-register admitted=false`. The path must be **exactly** `/api/telegram/`; a subpath such as `/api/telegram/webhook` is refused because the router leaves every subpath on 404, and `/api/telegram` is refused because the auth gate 401s that shape. Fix the URL and register again.
70
+ - **Registered but unreachable** — the tool returns an error quoting Telegram's own message (commonly `Wrong response from the webhook: 404 Not Found`) and logs `op=webhook-verify reachable=false`. Nothing was written to `account.json`, and the webhook is still registered at Telegram. Fix the tunnel or the app, then register again. Do not retry blindly.
71
+ - **Probe could not run** — `op=webhook-verify probe=failed reason=…` means `getWebhookInfo` itself threw. The registration was kept and the entry persisted, but reachability was never proved. Send the bot one message and confirm `[telegram-inbound] op=received` before telling the operator it is connected.
70
72
  - **Token write outside the brand** — `op=token-write-rejected reason=non-brand-path`. The MCP env is misconfigured; do not retry blindly — report it.
71
73
  - **Paired agent switched off** — `[telegram-inbound] op=agent … active=false` then `op=reject reason=agent-inactive`, and the standing `[telegram-audit] op=entry-agent-inactive` names the bot. Re-activate that agent or re-register the bot against another. There is no fall back to a different agent: answering as an agent the operator switched off is exactly what this refusal prevents.
72
74
  - **Bad secret on inbound** — `[telegram-inbound] op=secret … result=mismatch` means the registered secret and the stored secret diverged; re-run register.
@@ -46,6 +46,7 @@ export declare function parseSpawnRole(body: {
46
46
  } | null;
47
47
  export declare const SESSION_ID_PATTERN: RegExp;
48
48
  export declare const RC_SPAWN_URL_WAIT_DEFAULT_MS = 60000;
49
+ export declare const LOG_FOLLOW_KEEPALIVE_DEFAULT_MS = 30000;
49
50
  export interface HttpDeps extends Omit<SpawnDeps, 'onSessionReady' | 'watcher'> {
50
51
  /** Admin operator's userId, sourced from users.json at boot (same as
51
52
  * RcDaemonDeps.userId). Passed to buildRcChildEnv so the scheduling MCP
@@ -85,6 +86,10 @@ export interface HttpDeps extends Omit<SpawnDeps, 'onSessionReady' | 'watcher'>
85
86
  * omits this and gets RC_SPAWN_URL_WAIT_DEFAULT_MS (60s); tests inject a
86
87
  * short value to exercise the bind-timeout response without a 60s wait. */
87
88
  rcSpawnUrlWaitMs?: number;
89
+ /** Task 2557 — idle gap after which `/log?follow=1` writes a bare newline.
90
+ * Production omits this and gets LOG_FOLLOW_KEEPALIVE_DEFAULT_MS (30s);
91
+ * tests inject a short value to exercise the keepalive without a 30s wait. */
92
+ logFollowKeepaliveMs?: number;
88
93
  /** Task 1749 — accessor for the standing PTY census's most recent snapshot,
89
94
  * wired from `createPtyCensus(...).latest`. Optional so the pre-1749 test
90
95
  * deps factories keep working; production index.ts always injects it.
@@ -1 +1 @@
1
- {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAqB3B,OAAO,KAAK,EAAU,SAAS,EAAW,MAAM,YAAY,CAAA;AAM5D,OAAO,EAkBL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAqBzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAoBlD,OAAO,KAAK,EAAE,SAAS,EAAyB,MAAM,iBAAiB,CAAA;AACvE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAEzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAE3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAQlE;;;;;;;;;;;mFAWmF;AACnF,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GACvB,OAAO,CAAC,SAAS,EAAE,OAAO,GAAG,QAAQ,CAAC,GAAG,IAAI,CAE/C;AAED;;;;;;;;;;2EAU2E;AAC3E,wBAAgB,cAAc,CAC5B,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,EAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,GAAG,IAAI,CAOlD;AAsFD,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAS1D,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAIlD,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E;;qEAEiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,SAAS,CAAA;IAClB;;oEAEgE;IAChE,qBAAqB,CAAC,EAAE,qBAAqB,CAAA;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,WAAW,CAAA;IAC/B,eAAe,EAAE,aAAa,CAAA;IAC9B;kFAC8E;IAC9E,WAAW,EAAE,kBAAkB,CAAA;IAC/B;;gFAE4E;IAC5E,cAAc,EAAE,cAAc,CAAA;IAC9B;;;6BAGyB;IACzB,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;;sBAEkB;IAClB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB;;;sCAGkC;IAClC,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;IAC3C;;gFAE4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;4EAGwE;IACxE,YAAY,CAAC,EAAE,MAAM,iBAAiB,GAAG,IAAI,CAAA;CAC9C;AAsFD;;;;;;mEAMmE;AACnE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAiC9D;AAMD;;;;;;iBAMiB;AACjB,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM;AACjB;;;;;gFAKgF;AAChF,WAAW,CAAC,EAAE;IAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CA8CT;AAID;;;;;;yBAMyB;AACzB,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAiCzD;AAwCD,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEpG;AAgBD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAUD,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAMvE;AAMD,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,CAER;AAkCD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAe5F;AAkGD,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CASvG;AAUD,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,EAChB,gBAAgB,EAAE,OAAO,GACxB,IAAI,CA4BN;AAkED;;;kEAGkE;AAClE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpD;;;+CAG2C;IAC3C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD,CAAA;AA8CD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAyxHpD"}
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
@@ -207,6 +207,17 @@ export const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
207
207
  // Injectable via `HttpDeps.rcSpawnUrlWaitMs` so the timeout path is
208
208
  // unit-testable without a real 60s wait.
209
209
  export const RC_SPAWN_URL_WAIT_DEFAULT_MS = 60_000;
210
+ // Task 2557 — how long a follow stream may go without writing a byte before it
211
+ // emits a bare newline. The stream writes only when the JSONL grows, and Node's
212
+ // global fetch (undici) kills a response body after 300s with no bytes, so a
213
+ // conversation between messages killed its own reader: 129 of 129
214
+ // log-follow-close lines on laptop 192.168.88.10 read reason=aborted, shortest
215
+ // lifetime 313440ms, and no follower in the whole log history outlived a quiet
216
+ // patch. 30s costs one byte and leaves a tenfold margin against a slow tick.
217
+ // A genuinely stalled manager still trips undici's bound, because a stalled
218
+ // manager writes no keepalive either. Injectable via
219
+ // `HttpDeps.logFollowKeepaliveMs` so the path is testable without a 30s wait.
220
+ export const LOG_FOLLOW_KEEPALIVE_DEFAULT_MS = 30_000;
210
221
  const LIST_TAIL_BYTES = 64 * 1024;
211
222
  function timed(logger, method, path, status, ms) {
212
223
  logger(`api method=${method} path=${path} outcome=${status} ms=${ms}`);
@@ -2534,6 +2545,10 @@ export function buildHttpApp(deps) {
2534
2545
  let position = 0;
2535
2546
  let linesStreamed = 0;
2536
2547
  let closeReason = 'end';
2548
+ // Task 2557 — when this stream last put a byte on the wire. Every real
2549
+ // write stamps it; the poll loop below sends a keepalive once the gap
2550
+ // reaches the threshold, so an idle session never looks dead to undici.
2551
+ let lastWriteAt = Date.now();
2537
2552
  const countNewlines = (buf) => {
2538
2553
  let n = 0;
2539
2554
  for (let i = 0; i < buf.length; i++)
@@ -2548,6 +2563,7 @@ export function buildHttpApp(deps) {
2548
2563
  const buf = chunk;
2549
2564
  linesStreamed += countNewlines(buf);
2550
2565
  await out.write(buf);
2566
+ lastWriteAt = Date.now();
2551
2567
  }
2552
2568
  position = initialSize;
2553
2569
  }
@@ -2587,12 +2603,25 @@ export function buildHttpApp(deps) {
2587
2603
  const buf = chunk;
2588
2604
  linesStreamed += countNewlines(buf);
2589
2605
  void out.write(buf);
2606
+ lastWriteAt = Date.now();
2590
2607
  }
2591
2608
  });
2592
2609
  };
2593
2610
  watchFile(jsonlPath, { interval: 250 }, listener);
2611
+ const keepaliveMs = deps.logFollowKeepaliveMs ?? LOG_FOLLOW_KEEPALIVE_DEFAULT_MS;
2594
2612
  while (active) {
2595
2613
  await new Promise((resolve) => setTimeout(resolve, 1_000));
2614
+ if (!active)
2615
+ break;
2616
+ if (Date.now() - lastWriteAt < keepaliveMs)
2617
+ continue;
2618
+ // Task 2557 — a bare newline, not a file line: it is excluded from
2619
+ // `linesStreamed` because that counter is what this stream carried from
2620
+ // the JSONL, and the emitter drops it at its own `if (!line) continue`.
2621
+ // Its only job is to be a byte, so undici's bodyTimeout never elapses
2622
+ // on a conversation that is merely quiet.
2623
+ await out.write(Buffer.from('\n'));
2624
+ lastWriteAt = Date.now();
2596
2625
  }
2597
2626
  deps.logger(`log-follow-close sessionId=${sessionId} reason=${closeReason} linesStreamed=${linesStreamed} ms=${Date.now() - followStreamStart}`);
2598
2627
  });