pi-harness-runtime 1.1.396 → 1.1.398

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 (236) hide show
  1. package/AGENTS.md +54 -7
  2. package/harness/notification-events.ts +6 -6
  3. package/index.ts +1 -1
  4. package/package.json +1 -1
  5. package/packages/a2a-adapter/package.json +1 -1
  6. package/packages/agent-policy/package.json +1 -1
  7. package/packages/architecture-generator/package.json +1 -1
  8. package/packages/auth/package.json +1 -1
  9. package/packages/autonomous-refactor/package.json +1 -1
  10. package/packages/autonomous-runtime/package.json +1 -1
  11. package/packages/cache-strategy/package.json +1 -1
  12. package/packages/capability-registry/package.json +1 -1
  13. package/packages/checkpoint/package.json +1 -1
  14. package/packages/cli-plugin-sdk/package.json +1 -1
  15. package/packages/clipboard/package.json +1 -1
  16. package/packages/clipboard-plugin/package.json +1 -1
  17. package/packages/code-generation/package.json +1 -1
  18. package/packages/code-review/package.json +1 -1
  19. package/packages/codex-adapter/package.json +1 -1
  20. package/packages/codex-watcher/package.json +1 -1
  21. package/packages/config-capture/package.json +1 -1
  22. package/packages/connection-runtime/package.json +1 -1
  23. package/packages/context-compiler/package.json +1 -1
  24. package/packages/context-discovery/package.json +1 -1
  25. package/packages/context-manager/package.json +1 -1
  26. package/packages/contracts/package.json +1 -1
  27. package/packages/cookie-sanitizer/package.json +1 -1
  28. package/packages/cost-optimizer/package.json +1 -1
  29. package/packages/dependency-analyzer/package.json +1 -1
  30. package/packages/django-plugin/package.json +1 -1
  31. package/packages/doc-generator/package.json +1 -1
  32. package/packages/durable-review/package.json +1 -1
  33. package/packages/e2e-tester/package.json +1 -1
  34. package/packages/evaluation-engine/package.json +1 -1
  35. package/packages/evaluation-runner/package.json +1 -1
  36. package/packages/event-bus/package.json +1 -1
  37. package/packages/event-sink/package.json +1 -1
  38. package/packages/event-store/package.json +1 -1
  39. package/packages/experience-replay/package.json +1 -1
  40. package/packages/feedback-collector/package.json +1 -1
  41. package/packages/file-copy-helper/package.json +1 -1
  42. package/packages/framework-detector/package.json +1 -1
  43. package/packages/framework-plugin-sdk/package.json +1 -1
  44. package/packages/frappe-plugin/package.json +1 -1
  45. package/packages/generic-web-plugin/package.json +1 -1
  46. package/packages/health-monitor/package.json +1 -1
  47. package/packages/intent-analyzer/package.json +1 -1
  48. package/packages/jev-judge/package.json +1 -1
  49. package/packages/knowledge-graph/package.json +1 -1
  50. package/packages/knowledge-retrieval/package.json +1 -1
  51. package/packages/laravel-plugin/package.json +1 -1
  52. package/packages/learning-engine/package.json +1 -1
  53. package/packages/logging/README.md +41 -6
  54. package/packages/logging/dist/classification.d.ts +65 -0
  55. package/packages/logging/dist/classification.d.ts.map +1 -0
  56. package/packages/logging/dist/classification.js +155 -0
  57. package/packages/logging/dist/classification.js.map +1 -0
  58. package/packages/logging/dist/config.d.ts +9 -0
  59. package/packages/logging/dist/config.d.ts.map +1 -1
  60. package/packages/logging/dist/config.js +40 -3
  61. package/packages/logging/dist/config.js.map +1 -1
  62. package/packages/logging/dist/index.d.ts +9 -1
  63. package/packages/logging/dist/index.d.ts.map +1 -1
  64. package/packages/logging/dist/index.js +10 -1
  65. package/packages/logging/dist/index.js.map +1 -1
  66. package/packages/logging/dist/ingest/schema.d.ts +60 -0
  67. package/packages/logging/dist/ingest/schema.d.ts.map +1 -0
  68. package/packages/logging/dist/ingest/schema.js +245 -0
  69. package/packages/logging/dist/ingest/schema.js.map +1 -0
  70. package/packages/logging/dist/installation.d.ts +32 -0
  71. package/packages/logging/dist/installation.d.ts.map +1 -0
  72. package/packages/logging/dist/installation.js +64 -0
  73. package/packages/logging/dist/installation.js.map +1 -0
  74. package/packages/logging/dist/sensitive-keys.d.ts +12 -0
  75. package/packages/logging/dist/sensitive-keys.d.ts.map +1 -0
  76. package/packages/logging/dist/sensitive-keys.js +39 -0
  77. package/packages/logging/dist/sensitive-keys.js.map +1 -0
  78. package/packages/logging/dist/server/credentials.d.ts +85 -0
  79. package/packages/logging/dist/server/credentials.d.ts.map +1 -0
  80. package/packages/logging/dist/server/credentials.js +147 -0
  81. package/packages/logging/dist/server/credentials.js.map +1 -0
  82. package/packages/logging/dist/server/http.d.ts +36 -0
  83. package/packages/logging/dist/server/http.d.ts.map +1 -0
  84. package/packages/logging/dist/server/http.js +139 -0
  85. package/packages/logging/dist/server/http.js.map +1 -0
  86. package/packages/logging/dist/server/index-store.d.ts +95 -0
  87. package/packages/logging/dist/server/index-store.d.ts.map +1 -0
  88. package/packages/logging/dist/server/index-store.js +190 -0
  89. package/packages/logging/dist/server/index-store.js.map +1 -0
  90. package/packages/logging/dist/server/index.d.ts +22 -0
  91. package/packages/logging/dist/server/index.d.ts.map +1 -0
  92. package/packages/logging/dist/server/index.js +15 -0
  93. package/packages/logging/dist/server/index.js.map +1 -0
  94. package/packages/logging/dist/server/keys.d.ts +22 -0
  95. package/packages/logging/dist/server/keys.d.ts.map +1 -0
  96. package/packages/logging/dist/server/keys.js +24 -0
  97. package/packages/logging/dist/server/keys.js.map +1 -0
  98. package/packages/logging/dist/server/outbox-worker.d.ts +55 -0
  99. package/packages/logging/dist/server/outbox-worker.d.ts.map +1 -0
  100. package/packages/logging/dist/server/outbox-worker.js +95 -0
  101. package/packages/logging/dist/server/outbox-worker.js.map +1 -0
  102. package/packages/logging/dist/server/outbox.d.ts +112 -0
  103. package/packages/logging/dist/server/outbox.d.ts.map +1 -0
  104. package/packages/logging/dist/server/outbox.js +247 -0
  105. package/packages/logging/dist/server/outbox.js.map +1 -0
  106. package/packages/logging/dist/server/service.d.ts +83 -0
  107. package/packages/logging/dist/server/service.d.ts.map +1 -0
  108. package/packages/logging/dist/server/service.js +144 -0
  109. package/packages/logging/dist/server/service.js.map +1 -0
  110. package/packages/logging/dist/shipper.d.ts +20 -4
  111. package/packages/logging/dist/shipper.d.ts.map +1 -1
  112. package/packages/logging/dist/shipper.js +31 -5
  113. package/packages/logging/dist/shipper.js.map +1 -1
  114. package/packages/logging/dist/singleton.d.ts.map +1 -1
  115. package/packages/logging/dist/singleton.js +3 -0
  116. package/packages/logging/dist/singleton.js.map +1 -1
  117. package/packages/logging/dist/types.d.ts +28 -1
  118. package/packages/logging/dist/types.d.ts.map +1 -1
  119. package/packages/logging/dist/types.js +8 -0
  120. package/packages/logging/dist/types.js.map +1 -1
  121. package/packages/logging/package.json +1 -1
  122. package/packages/logging/src/classification.ts +175 -0
  123. package/packages/logging/src/config.ts +60 -3
  124. package/packages/logging/src/index.ts +45 -1
  125. package/packages/logging/src/ingest/schema.ts +295 -0
  126. package/packages/logging/src/installation.ts +76 -0
  127. package/packages/logging/src/sensitive-keys.ts +40 -0
  128. package/packages/logging/src/server/credentials.ts +198 -0
  129. package/packages/logging/src/server/http.ts +170 -0
  130. package/packages/logging/src/server/index-store.ts +242 -0
  131. package/packages/logging/src/server/index.ts +56 -0
  132. package/packages/logging/src/server/keys.ts +32 -0
  133. package/packages/logging/src/server/outbox-worker.ts +124 -0
  134. package/packages/logging/src/server/outbox.ts +316 -0
  135. package/packages/logging/src/server/service.ts +196 -0
  136. package/packages/logging/src/shipper.ts +54 -8
  137. package/packages/logging/src/singleton.ts +3 -0
  138. package/packages/logging/src/types.ts +29 -1
  139. package/packages/logging/test/classification.test.ts +137 -0
  140. package/packages/logging/test/identity-transport.test.ts +197 -0
  141. package/packages/logging/test/ingest-schema.test.ts +184 -0
  142. package/packages/logging/test/integration.test.ts +9 -4
  143. package/packages/logging/test/log-http.test.ts +246 -0
  144. package/packages/logging/test/server.test.ts +444 -0
  145. package/packages/logging/test/shipper.test.ts +11 -11
  146. package/packages/logging/test/spool.test.ts +4 -1
  147. package/packages/mcp-adapter/package.json +1 -1
  148. package/packages/memory-engine/package.json +1 -1
  149. package/packages/milestone-manager/package.json +1 -1
  150. package/packages/model-registry/package.json +1 -1
  151. package/packages/nextjs-plugin/package.json +1 -1
  152. package/packages/notification/dist/adapters/detail-format.d.ts +34 -0
  153. package/packages/notification/dist/adapters/detail-format.d.ts.map +1 -0
  154. package/packages/notification/dist/adapters/detail-format.js +55 -0
  155. package/packages/notification/dist/adapters/detail-format.js.map +1 -0
  156. package/packages/notification/dist/adapters/email-adapter.d.ts.map +1 -1
  157. package/packages/notification/dist/adapters/email-adapter.js +3 -2
  158. package/packages/notification/dist/adapters/email-adapter.js.map +1 -1
  159. package/packages/notification/dist/adapters/line-adapter.d.ts.map +1 -1
  160. package/packages/notification/dist/adapters/line-adapter.js +3 -2
  161. package/packages/notification/dist/adapters/line-adapter.js.map +1 -1
  162. package/packages/notification/dist/adapters/ntfy-adapter.d.ts.map +1 -1
  163. package/packages/notification/dist/adapters/ntfy-adapter.js +3 -2
  164. package/packages/notification/dist/adapters/ntfy-adapter.js.map +1 -1
  165. package/packages/notification/dist/adapters/telegram-adapter.d.ts +14 -0
  166. package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
  167. package/packages/notification/dist/adapters/telegram-adapter.js +83 -67
  168. package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
  169. package/packages/notification/dist/notification-center.d.ts.map +1 -1
  170. package/packages/notification/dist/notification-center.js +1 -0
  171. package/packages/notification/dist/notification-center.js.map +1 -1
  172. package/packages/notification/dist/types.d.ts +3 -0
  173. package/packages/notification/dist/types.d.ts.map +1 -1
  174. package/packages/notification/package.json +1 -1
  175. package/packages/notification/src/adapters/detail-format.ts +74 -0
  176. package/packages/notification/src/adapters/email-adapter.ts +3 -2
  177. package/packages/notification/src/adapters/line-adapter.ts +3 -2
  178. package/packages/notification/src/adapters/ntfy-adapter.ts +3 -2
  179. package/packages/notification/src/adapters/telegram-adapter.ts +92 -74
  180. package/packages/notification/src/notification-center.ts +1 -0
  181. package/packages/notification/src/types.ts +3 -0
  182. package/packages/notification/test/detail-format.test.ts +106 -0
  183. package/packages/observability/package.json +1 -1
  184. package/packages/okf-indexer/package.json +1 -1
  185. package/packages/paperclip-integration/package.json +1 -1
  186. package/packages/performance-optimizer/package.json +1 -1
  187. package/packages/permission-mode/package.json +1 -1
  188. package/packages/privilege-broker/package.json +1 -1
  189. package/packages/project-analyzer/package.json +1 -1
  190. package/packages/project-bootstrap/package.json +1 -1
  191. package/packages/projection-engine/package.json +1 -1
  192. package/packages/prompt-compiler/package.json +1 -1
  193. package/packages/prompt-versioning/package.json +1 -1
  194. package/packages/provider-adapter-sdk/package.json +1 -1
  195. package/packages/provider-router/package.json +1 -1
  196. package/packages/provider-selector/package.json +1 -1
  197. package/packages/providers/package.json +1 -1
  198. package/packages/qdrant-skills/package.json +1 -1
  199. package/packages/quota-manager/package.json +1 -1
  200. package/packages/rate-limiter/package.json +1 -1
  201. package/packages/react-vite-plugin/package.json +1 -1
  202. package/packages/relay/package.json +1 -1
  203. package/packages/release-manager/package.json +1 -1
  204. package/packages/requirement-compiler/package.json +1 -1
  205. package/packages/runtime/package.json +1 -1
  206. package/packages/scheduler/package.json +1 -1
  207. package/packages/scheduler-adapter/package.json +1 -1
  208. package/packages/session/package.json +1 -1
  209. package/packages/session-api/package.json +1 -1
  210. package/packages/session-export/package.json +1 -1
  211. package/packages/shared-context/package.json +1 -1
  212. package/packages/skill-mcp-client/package.json +1 -1
  213. package/packages/skill-registry/package.json +1 -1
  214. package/packages/skills/package.json +1 -1
  215. package/packages/sprint-planner/package.json +1 -1
  216. package/packages/subscription-engine/package.json +1 -1
  217. package/packages/task-compiler/package.json +1 -1
  218. package/packages/telegram/package.json +1 -1
  219. package/packages/tencentdb-memory/package.json +1 -1
  220. package/packages/tencentdb-sync/package.json +1 -1
  221. package/packages/test-data-generator/package.json +1 -1
  222. package/packages/test-generator/package.json +1 -1
  223. package/packages/todo-bd-sync/package.json +1 -1
  224. package/packages/token-estimation/package.json +1 -1
  225. package/packages/token-optimizer/package.json +1 -1
  226. package/packages/trajectory/package.json +1 -1
  227. package/packages/tui/package.json +1 -1
  228. package/packages/types/package.json +1 -1
  229. package/packages/voice-api/package.json +1 -1
  230. package/packages/webmcp-tester/package.json +1 -1
  231. package/packages/wiki/package.json +1 -1
  232. package/packages/workflow-events/package.json +1 -1
  233. package/packages/workspace-scanner/package.json +1 -1
  234. package/packages/worktree/package.json +1 -1
  235. package/packages/write-review/package.json +1 -1
  236. package/scripts/unified-webhook-server.ts +86 -81
@@ -30,8 +30,18 @@ import type { RelayServerOptions } from "../packages/relay/src/server";
30
30
  // chat id) - the harness and this server must agree on who may write in.
31
31
  import { getAllowedUserIds, isAuthorizedTelegramSender } from "../harness/telegram-auth";
32
32
  // Centralized application logs: the harness ships redacted batches to
33
- // /api/logs, the server persists them and relays them to Cloudflare R2.
34
- import { redactValue } from "../packages/logging/src/redact";
33
+ // /api/logs, the server queues them durably and a worker uploads them to
34
+ // Cloudflare R2.
35
+ import {
36
+ LogCredentialStore,
37
+ LogIndexStore,
38
+ LogIngestService,
39
+ LogOutbox,
40
+ LogOutboxWorker,
41
+ createLogHttpHandler,
42
+ serverObjectKey,
43
+ } from "../packages/logging/src/server/index";
44
+ import type { OutboxEntry } from "../packages/logging/src/server/outbox";
35
45
  import { loadR2Config, R2Client } from "./r2-storage";
36
46
 
37
47
  // --- Configuration ---
@@ -78,8 +88,37 @@ const CALLBACK_QUEUE_FILE = join(process.env.HOME || homedir() || "/tmp", ".pi-h
78
88
 
79
89
  // --- Log ingest (centralized application logs) ---
80
90
  const LOG_INGEST_DIR = join(process.env.HOME || homedir() || "/tmp", ".pi-harness-runtime", "logs", "inbound");
91
+ /** Durable queue of accepted batches (server side outbox). */
92
+ const LOG_OUTBOX_DIR = join(process.env.HOME || homedir() || "/tmp", ".pi-harness-runtime", "logs", "outbox");
93
+ /** Metadata index used by the query endpoint. */
94
+ const LOG_INDEX_DIR = join(process.env.HOME || homedir() || "/tmp", ".pi-harness-runtime", "logs", "index");
81
95
  /** Largest accepted ingest body (2 MB). A batch is NDJSON, so this is generous. */
82
96
  const LOG_INGEST_MAX_BYTES = 2 * 1024 * 1024;
97
+ /** Largest accepted query body (64 KB is plenty for a filter object). */
98
+ const LOG_QUERY_MAX_BYTES = 64 * 1024;
99
+
100
+ /**
101
+ * The ingest pipeline.
102
+ *
103
+ * The credential store decides whether the endpoint exists at all: without a
104
+ * credential the endpoint refuses every write (fail closed) instead of
105
+ * accepting anonymous batches.
106
+ */
107
+ const logCredentials = LogCredentialStore.load();
108
+ const logOutbox = new LogOutbox({ dir: LOG_OUTBOX_DIR });
109
+ const logIndex = new LogIndexStore({ dir: LOG_INDEX_DIR });
110
+ const logService = new LogIngestService({
111
+ credentials: logCredentials,
112
+ outbox: logOutbox,
113
+ index: logIndex,
114
+ });
115
+ /** The HTTP surface of the log service (ingest, query, diagnostics). */
116
+ const logHttpHandler = createLogHttpHandler({
117
+ service: logService,
118
+ outbox: logOutbox,
119
+ index: logIndex,
120
+ localCopyDir: LOG_INGEST_DIR,
121
+ });
83
122
 
84
123
  /**
85
124
  * Persist a callback to disk when no WebSocket client is connected.
@@ -96,93 +135,40 @@ function queueCallback(payload: CallbackPayload): void {
96
135
  }
97
136
 
98
137
  /**
99
- * Persist an inbound log batch and relay it to Cloudflare R2.
138
+ * Read a request body with a hard size limit.
100
139
  *
101
- * The harness already redacts what it sends; the server re-redacts it
102
- * (defence in depth) and writes NDJSON next to the other harness logs. The
103
- * R2 relay is best effort: a failed upload never fails the request, because
104
- * the batch is already on disk.
140
+ * Returns null when the body was too large (the response is already sent).
105
141
  */
106
- async function handleLogIngest(req: IncomingMessage, res: ServerResponse): Promise<void> {
107
- const ingestToken = process.env.LOG_INGEST_TOKEN;
108
- if (ingestToken) {
109
- const auth = req.headers.authorization;
110
- if (auth !== `Bearer ${ingestToken}`) {
111
- res.writeHead(403, { "Content-Type": "application/json" });
112
- res.end(JSON.stringify({ error: "forbidden" }));
113
- return;
114
- }
115
- }
116
-
142
+ async function readBody(req: IncomingMessage, maxBytes: number): Promise<Buffer | null> {
117
143
  const chunks: Buffer[] = [];
118
144
  let size = 0;
119
145
  for await (const chunk of req) {
120
146
  size += (chunk as Buffer).length;
121
- if (size > LOG_INGEST_MAX_BYTES) {
122
- res.writeHead(413, { "Content-Type": "application/json" });
123
- res.end(JSON.stringify({ error: "payload too large" }));
124
- return;
147
+ if (size > maxBytes) {
148
+ return null;
125
149
  }
126
150
  chunks.push(chunk as Buffer);
127
151
  }
128
-
129
- let payload: { batchId?: string; records?: unknown[] };
130
- try {
131
- payload = JSON.parse(Buffer.concat(chunks).toString());
132
- } catch {
133
- res.writeHead(400, { "Content-Type": "application/json" });
134
- res.end(JSON.stringify({ error: "invalid json" }));
135
- return;
136
- }
137
-
138
- const incoming = Array.isArray(payload.records) ? payload.records : [];
139
- const records = incoming.map((record) => sanitizeLogRecord(record));
140
-
141
- // Persist locally first - the R2 relay is best effort.
142
- try {
143
- if (!existsSync(LOG_INGEST_DIR)) mkdirSync(LOG_INGEST_DIR, { recursive: true });
144
- const day = new Date().toISOString().slice(0, 10);
145
- const file = join(LOG_INGEST_DIR, `logs-${day}.ndjson`);
146
- appendFileSync(file, records.map((record) => JSON.stringify(record)).join("\n") + "\n");
147
- } catch (err) {
148
- console.error("[LogIngest] Failed to persist batch:", err);
149
- }
150
-
151
- let relayed = false;
152
- try {
153
- const r2Config = loadR2Config();
154
- if (r2Config) {
155
- const client = new R2Client(r2Config);
156
- const now = new Date();
157
- const stamp = `${now.getUTCFullYear()}/${String(now.getUTCMonth() + 1).padStart(2, "0")}/${String(now.getUTCDate()).padStart(2, "0")}`;
158
- await client.upload({
159
- key: `logs/inbound/${stamp}/${payload.batchId ?? Date.now()}.ndjson`,
160
- body: records.map((record) => JSON.stringify(record)).join("\n") + "\n",
161
- contentType: "application/x-ndjson",
162
- });
163
- relayed = true;
164
- }
165
- } catch (err) {
166
- console.error("[LogIngest] Failed to relay batch to R2:", err);
167
- }
168
-
169
- res.writeHead(202, { "Content-Type": "application/json" });
170
- res.end(JSON.stringify({ accepted: records.length, relayed }));
152
+ return Buffer.concat(chunks);
171
153
  }
172
154
 
173
- /** Re-redact one inbound record. Unknown shapes are kept as a redacted value. */
174
- function sanitizeLogRecord(record: unknown): Record<string, unknown> {
175
- if (!record || typeof record !== "object") {
176
- return { value: redactValue(record) };
177
- }
178
- const safe: Record<string, unknown> = { ...(record as Record<string, unknown>) };
179
- if (typeof safe.message === "string") {
180
- safe.message = redactValue(safe.message);
181
- }
182
- if (safe.fields && typeof safe.fields === "object") {
183
- safe.fields = redactValue(safe.fields);
184
- }
185
- return safe;
155
+ /**
156
+ * Upload one queued batch to R2.
157
+ *
158
+ * The worker calls this in the background: an R2 outage grows the queue
159
+ * instead of blocking the ingest endpoint.
160
+ */
161
+ async function uploadQueuedBatch(entry: OutboxEntry): Promise<void> {
162
+ const r2Config = loadR2Config();
163
+ if (!r2Config) throw new Error("R2 is not configured");
164
+ const client = new R2Client(r2Config);
165
+ const key = serverObjectKey({
166
+ tenantId: entry.batch.tenantId,
167
+ batchId: entry.batch.batchId,
168
+ sentAt: entry.batch.sentAt,
169
+ });
170
+ const body = entry.batch.records.map((record) => JSON.stringify(record)).join("\n") + "\n";
171
+ await client.upload({ key, body, contentType: "application/x-ndjson" });
186
172
  }
187
173
 
188
174
  interface JobCommand {
@@ -649,9 +635,13 @@ async function main() {
649
635
  acknowledgeCommands();
650
636
  res.writeHead(200, { "Content-Type": "application/json" });
651
637
  res.end(JSON.stringify({ success: true }));
652
- } else if (url.pathname === "/api/logs" && req.method === "POST") {
653
- // Centralized application logs from the harness
654
- await handleLogIngest(req, res);
638
+ } else if (
639
+ url.pathname === "/api/logs" ||
640
+ url.pathname === "/api/logs/query" ||
641
+ url.pathname === "/api/logs/health"
642
+ ) {
643
+ // Centralized application logs: ingest, query and diagnostics.
644
+ await logHttpHandler(req, res);
655
645
  } else if (url.pathname === "/health") {
656
646
  // Keep the plain-text contract that external monitors check for "OK".
657
647
  res.writeHead(200, { "Content-Type": "text/plain" });
@@ -684,9 +674,24 @@ async function main() {
684
674
  if (config.line) console.log(` LINE: https://webhook.bunchee.online/line`);
685
675
  });
686
676
 
677
+ // The outbox worker uploads the accepted batches to R2 in the background:
678
+ // an R2 outage grows the queue instead of blocking the ingest endpoint.
679
+ const outboxWorker = new LogOutboxWorker({ outbox: logOutbox, upload: uploadQueuedBatch });
680
+ outboxWorker.start();
681
+ if (!logService.configured) {
682
+ console.error(
683
+ "[LogIngest] No ingest credential configured (LOG_INGEST_TOKEN, keys/log-ingest-token.txt or keys/log-ingest-credentials.json) - /api/logs will refuse every write.",
684
+ );
685
+ } else {
686
+ console.log(
687
+ `[LogIngest] Ingest enabled - ${logOutbox.stats().pending} batch(es) queued, outbox at ${LOG_OUTBOX_DIR}`,
688
+ );
689
+ }
690
+
687
691
  // Graceful shutdown
688
692
  const shutdown = async () => {
689
693
  console.log("\nShutting down...");
694
+ await outboxWorker.stop();
690
695
  await relayServer.stop();
691
696
  server.close();
692
697
  process.exit(0);