@tyhld/conductor 0.12.46 → 0.12.47

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/cli.js CHANGED
@@ -13,7 +13,7 @@ import { execFileSync, spawnSync } from 'node:child_process';
13
13
  import { existsSync, readFileSync } from 'node:fs';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import os from 'node:os';
16
- import { RELAY_FETCH_TIMEOUT_MS, createRelayState, deliveryReport, parseAgentKind, reconnectDelayMs, relayTick, tmuxHasSession, pendingReportIds, sweepProcessedReports, } from './relay.js';
16
+ import { RELAY_FETCH_TIMEOUT_MS, createRelayState, deliveryReport, parseAgentKind, reconnectDelayMs, relayTick, tmuxHasSession, pendingReportIds, sendPendingReportFiles, sweepProcessedReports, } from './relay.js';
17
17
  import { createRelayRunner } from './relay-runner.js';
18
18
  import { installProcessGuards } from './process-guard.js';
19
19
  import { registerNudgeTarget, unregisterNudgeTarget, watchNudges, } from './nudge.js';
@@ -212,6 +212,12 @@ async function sendReport(cfg) {
212
212
  const msg = err instanceof Error ? err.message : String(err);
213
213
  console.log(`[conductor] ${time} site=${cfg.site} status=${cfg.status} -> 送信失敗: ${msg}`);
214
214
  }
215
+ // ★未処理の報告ファイルの本文を中央へ届ける(便 79840008)。受け取られた物だけ処理済みにする(消さない)。
216
+ // ★配達役がいま追っている便の報告は送らない(完了の道が完了と一緒に渡すため)。
217
+ if (pending.ids.length > 0) {
218
+ const tracking = relayStateForReport?.pending?.id;
219
+ await sendPendingReportFiles(cfg, { exclude: tracking ? [tracking] : [] });
220
+ }
215
221
  }
216
222
  /** `conductor start`: 起動直後に1回 + 以後 intervalSec ごとに報告し続ける。 */
217
223
  async function start(args) {
package/dist/relay.js CHANGED
@@ -1357,6 +1357,95 @@ export function pendingReportIds(env, limit = PENDING_REPORTS_MAX) {
1357
1357
  const max = Math.max(0, Math.floor(limit));
1358
1358
  return { ids: sorted.slice(0, max), total: sorted.length };
1359
1359
  }
1360
+ // ───────────────────────────────────────────────────────────────────────────
1361
+ // 未処理の報告ファイルの本文を中央へ届ける(便 79840008・回収の残り②・中央は ai-kana PR #351)
1362
+ //
1363
+ // 【なぜ要るか】追跡が切れたあと(配達役の再起動・時間切れ・職人が自分で終えた等)に書かれた報告は、
1364
+ // 手元に残るだけで本文が中央へ届かなかった(生存報告には指示IDだけを載せていた=pendingReports)。
1365
+ // 中央に受け口 POST /api/conductor/report/files ができたので、ファイルの全文をそのまま送る。
1366
+ // 【決まり】
1367
+ // ・1回 REPORT_FILES_BATCH(20)件まで。多ければ分けて送る。
1368
+ // ・返りの results[i].ok === true の物だけ processed/ へ移す(★消さない)。ok:false と、送れなかった物は
1369
+ // 未処理のまま残す(次の周でやり直す)。理由はログに出す。
1370
+ // ・★配達役は完了にしない・便の状態の値を書かない(中央も完了にしない=止まった便を「回収必要」まで進めるだけ)。
1371
+ // ・★配達役がいま追っている便の報告は送らない。それは完了の道(completeWithReport)が読んで完了と一緒に
1372
+ // 渡す。先に processed/ へ移すと、completeWithReport が「渡し済み」とみなして完了を書かなくなる。
1373
+ // ───────────────────────────────────────────────────────────────────────────
1374
+ /** 1回に送る報告ファイルの件数の上限(★中央の FILE_INTAKE_LIMIT と同じ値にすること)。 */
1375
+ export const REPORT_FILES_BATCH = 20;
1376
+ /** 1周で送る件数の上限(多すぎる分は次の周に回す=落とさない)。 */
1377
+ export const REPORT_FILES_MAX_PER_ROUND = 100;
1378
+ /**
1379
+ * 未処理の報告ファイルの全文を中央へ送り、受け取られた物だけ処理済みにする。
1380
+ * 返り: 送った件数・処理済みにした件数・未処理のまま残した件数。
1381
+ */
1382
+ export async function sendPendingReportFiles(cfg, opts = {}) {
1383
+ const env = opts.env;
1384
+ const doFetch = opts.fetchImpl ?? fetch;
1385
+ const logLine = opts.logLine ?? log;
1386
+ const exclude = new Set(opts.exclude ?? []);
1387
+ const items = [];
1388
+ for (const id of pendingReportIds(env, REPORT_FILES_MAX_PER_ROUND).ids) {
1389
+ if (exclude.has(id))
1390
+ continue;
1391
+ for (const file of pendingCandidates(id, env)) {
1392
+ const raw = readIfFilled(file);
1393
+ if (raw !== null) {
1394
+ items.push({ id, raw });
1395
+ break;
1396
+ }
1397
+ }
1398
+ }
1399
+ let sent = 0;
1400
+ let processed = 0;
1401
+ let kept = 0;
1402
+ for (let i = 0; i < items.length; i += REPORT_FILES_BATCH) {
1403
+ const chunk = items.slice(i, i + REPORT_FILES_BATCH);
1404
+ let results = null;
1405
+ try {
1406
+ const res = await doFetch(`${cfg.url}/api/conductor/report/files`, {
1407
+ method: 'POST',
1408
+ headers: { ...authHeaders(cfg), 'Content-Type': 'application/json' },
1409
+ body: JSON.stringify({ files: chunk.map((c) => c.raw) }),
1410
+ signal: AbortSignal.timeout(RELAY_FETCH_TIMEOUT_MS),
1411
+ });
1412
+ sent += chunk.length;
1413
+ if (!res.ok) {
1414
+ logLine(`site=${cfg.site} 報告ファイルの送信 -> HTTP ${res.status}(${chunk.length}件は未処理のまま残す)`);
1415
+ }
1416
+ else {
1417
+ const data = (await res.json().catch(() => null));
1418
+ if (Array.isArray(data?.results) && data.results.length === chunk.length) {
1419
+ results = data.results;
1420
+ }
1421
+ else {
1422
+ logLine(`site=${cfg.site} 報告ファイルの送信 -> 返りの形が違う(${chunk.length}件は未処理のまま残す)`);
1423
+ }
1424
+ }
1425
+ }
1426
+ catch (err) {
1427
+ logLine(`site=${cfg.site} 報告ファイルの送信 -> 失敗: ${err instanceof Error ? err.message : String(err)}` +
1428
+ `(${chunk.length}件は未処理のまま残す)`);
1429
+ }
1430
+ for (let k = 0; k < chunk.length; k++) {
1431
+ const r = results?.[k];
1432
+ if (r && r.ok === true && markReportProcessed(chunk[k].id, env)) {
1433
+ processed++;
1434
+ }
1435
+ else {
1436
+ kept++;
1437
+ if (r) {
1438
+ const why = String(r.reason ?? r.error ?? '理由なし').slice(0, 200);
1439
+ logLine(`site=${cfg.site} id=${chunk[k].id} の報告ファイルは受け取られませんでした(未処理のまま残す): ${why}`);
1440
+ }
1441
+ }
1442
+ }
1443
+ }
1444
+ if (items.length > 0) {
1445
+ logLine(`site=${cfg.site} 報告ファイル: 送った ${sent} 件・処理済み ${processed} 件・未処理のまま ${kept} 件`);
1446
+ }
1447
+ return { sent, processed, kept };
1448
+ }
1360
1449
  /**
1361
1450
  * 古い控えを片付ける。★消すのは `processed/` の中だけ(未処理は何日たっても消さない)。
1362
1451
  * 返り: 消した件数。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyhld/conductor",
3
- "version": "0.12.46",
3
+ "version": "0.12.47",
4
4
  "description": "采配くん管制の見守りアプリ(conductor-agent)。各PCで常駐し、中央(devlog-tracker)へ定期的に生存報告(heartbeat)を送る常駐CLI。",
5
5
  "type": "module",
6
6
  "bin": {