@tyhld/conductor 0.12.108 → 0.12.109

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 (2) hide show
  1. package/dist/headless.js +79 -3
  2. package/package.json +1 -1
package/dist/headless.js CHANGED
@@ -566,6 +566,12 @@ export function maskSecrets(text) {
566
566
  *
567
567
  * ★本文を渡せなかったことが先。職人が何も受け取っていないという事実は、
568
568
  * ほかのどの終わり方よりも強い(やり直しても同じ結果になる)。
569
+ *
570
+ * ★並びは変えない(便 a6e01bbc)。本文の未渡し → 時間切れ → 止められた → 認証の問題 の
571
+ * 順番はそのまま。足したのは「止められた」と「認証の問題」の間の1つだけで、
572
+ * これは【完了の知らせの後に残った処理を片づけただけ】の便(=ふつうに終わった便)。
573
+ * 終了コードは片づけの SIGTERM の物なので 0 ではない=ここで拾わないと 'error' になり、
574
+ * 「異常終了 → 自動再開」と同じ見え方になってしまう。
569
575
  */
570
576
  export function jobEndKindOf(j) {
571
577
  if (j.bodyHandoff === 'failed')
@@ -574,10 +580,40 @@ export function jobEndKindOf(j) {
574
580
  return 'timeout';
575
581
  if (j.stopped)
576
582
  return 'stopped';
583
+ if (j.reapedAfterFinish === true)
584
+ return 'completed';
577
585
  if (j.authError)
578
586
  return 'auth_error';
579
587
  return j.exitCode === 0 ? 'completed' : 'error';
580
588
  }
589
+ /**
590
+ * 管制が返した終端の綴りが「職人の完了の知らせで終わった」ものか(★純粋関数・便 a6e01bbc)。
591
+ *
592
+ * ★`done` だけ。取りやめ(cancelled / canceled)・失敗(failed)・放棄(abandoned)は
593
+ * 完了の知らせが無いまま終端になった便なので、今までどおりの終わり方(stopped)にする。
594
+ * ★綴りの判定は小文字・前後の空白を無視する(isTerminalCommandStatus と同じ読み方)。
595
+ */
596
+ export function isArtisanFinishedStatus(status) {
597
+ if (status === null)
598
+ return false;
599
+ return status.trim().toLowerCase() === 'done';
600
+ }
601
+ /**
602
+ * 片づけた便に添える一言(★純粋関数・便 a6e01bbc)。
603
+ *
604
+ * ★事実のまま書く。終了コード・合図は【片づけの結果】だと分かる形でここに添える
605
+ * (終わりの事実の `exitCode` / `exitSignal` には入れない=管制が「ふつうに終わりました
606
+ * (終了コード 143)」と、異常に見える形で並べてしまうため)。
607
+ */
608
+ export function reapedAfterFinishDetail(exitCode, exitSignal) {
609
+ const parts = [];
610
+ if (exitCode !== null)
611
+ parts.push(`終了コード=${exitCode}`);
612
+ if (exitSignal !== null)
613
+ parts.push(`合図=${exitSignal}`);
614
+ const tail = parts.length > 0 ? `(片づけの結果: ${parts.join(', ')})` : '';
615
+ return `完了の知らせの後に、残った処理を片づけました${tail}`;
616
+ }
581
617
  /**
582
618
  * 本文を渡せなかった便として、走っている処理を止めるか(★純粋関数・便 915694ac)。
583
619
  *
@@ -1154,6 +1190,7 @@ resumeNote) {
1154
1190
  exitCode: null,
1155
1191
  exitSignal: null,
1156
1192
  stopped: false,
1193
+ reapedAfterFinish: false,
1157
1194
  timedOut: false,
1158
1195
  eventBuffer: [],
1159
1196
  eventsSent: 0,
@@ -1382,6 +1419,7 @@ async function checkRunningJob(cfg, state) {
1382
1419
  bodyHandoff: job.bodyHandoff,
1383
1420
  timedOut: job.timedOut,
1384
1421
  stopped: job.stopped,
1422
+ reapedAfterFinish: job.reapedAfterFinish,
1385
1423
  authError: isAuthError(job),
1386
1424
  exitCode: job.exitCode,
1387
1425
  });
@@ -1390,13 +1428,18 @@ async function checkRunningJob(cfg, state) {
1390
1428
  // ★本文の言葉は1文字も読まない(2026-09-23 の確定のまま。下書きの印は関門の側だけ)。
1391
1429
  const delivery = reportDelivery(job.commandId);
1392
1430
  const residue = takeFinishGateResidue(job.commandId);
1431
+ // ★完了の知らせの後に残った処理を片づけただけの便(便 a6e01bbc)。
1432
+ // 終了コード・合図は【片づけの SIGTERM の物】なので、便の終わり方を表す欄には入れない
1433
+ // (入れると管制が「ふつうに終わりました(終了コード 143)」と、異常に見える形で出す)。
1434
+ // ★捨てるのではなく、人向けの一言へ「片づけの結果」と分かる形で載せる。
1435
+ const reaped = job.reapedAfterFinish;
1393
1436
  await deliverJobEndFacts(cfg, {
1394
1437
  commandId: job.commandId,
1395
1438
  site: cfg.site,
1396
1439
  finishedAt: new Date(job.finishedAt ?? Date.now()).toISOString(),
1397
1440
  endKind,
1398
- exitCode: job.exitCode,
1399
- exitSignal: job.exitSignal,
1441
+ exitCode: reaped ? null : job.exitCode,
1442
+ exitSignal: reaped ? null : job.exitSignal,
1400
1443
  reportDelivered: delivery.delivered,
1401
1444
  // ★画面なし方式は中継が自分で立ち上げた処理なので、終わりを直接つかんでいる。
1402
1445
  // つかめていないときは false を送る(1-3 の「つかめていない」と同じ形)。
@@ -1407,6 +1450,7 @@ async function checkRunningJob(cfg, state) {
1407
1450
  // そのどちらも無く、報告の形が整っていないときは、その理由を載せる(便 461be339)。
1408
1451
  detail: job.bodyHandoffError
1409
1452
  ?? residue?.text
1453
+ ?? (reaped ? reapedAfterFinishDetail(job.exitCode, job.exitSignal) : null)
1410
1454
  ?? delivery.note
1411
1455
  ?? `attempt ${job.attempt}/${HEADLESS_MAX_ATTEMPTS}`,
1412
1456
  // ★上限に達して「途中で終わった」便だけに付ける(管制が機械で見分けられる形)。
@@ -1445,6 +1489,20 @@ async function checkRunningJob(cfg, state) {
1445
1489
  log(`site=${cfg.site} id=${job.commandId} stop_requested で停止`);
1446
1490
  return;
1447
1491
  }
1492
+ if (job.reapedAfterFinish) {
1493
+ // ★職人が完了の知らせを出し、管制で便が終端(done)になったので、残っていた処理を
1494
+ // 片づけただけ(便 a6e01bbc)。=ふつうに終わった便。
1495
+ // ・「止めました」の合図は送らない(持ち主の画面に「止められて終わりました」と出る元)。
1496
+ // ・管制へ状態は書かない(既に終端。failed を done へ戻さないのと同じ考え)。
1497
+ // ・★ここで必ず畳む=下の「異常終了 → 自動再開」へ入らない
1498
+ // (片づけの終了コードは 0 ではないので、落ちると再開の道へ入ってしまう)。
1499
+ const detail = reapedAfterFinishDetail(job.exitCode, job.exitSignal);
1500
+ noteLastJob('done', detail);
1501
+ state.job = null;
1502
+ rs.lastHeartbeatAt = null;
1503
+ log(`site=${cfg.site} id=${job.commandId} 畳みます(${detail})`);
1504
+ return;
1505
+ }
1448
1506
  if (isAuthError(job)) {
1449
1507
  // 認証エラー → 再試行しない
1450
1508
  const detail = job.authErrorDetected
@@ -1573,12 +1631,30 @@ async function checkRunningJob(cfg, state) {
1573
1631
  if (now - job.lastStopCheckAt >= HEADLESS_STOP_CHECK_MS) {
1574
1632
  job.lastStopCheckAt = now;
1575
1633
  const status = await fetchCommandStatus(cfg, job.commandId);
1576
- if (status === 'stop_requested' || isTerminalCommandStatus(status)) {
1634
+ // ★人・管制が止めた便(便 a6e01bbc で下の終端と分けた)。終わり方は今までどおり stopped。
1635
+ if (status === 'stop_requested') {
1577
1636
  log(`site=${cfg.site} id=${job.commandId} ${status} → SIGTERM`);
1578
1637
  job.stopped = true;
1579
1638
  await stopJob(job);
1580
1639
  return;
1581
1640
  }
1641
+ if (isTerminalCommandStatus(status)) {
1642
+ // ★管制で終端になった便。残っている処理は、どちらにしても片づける(SIGTERM は変えない)。
1643
+ // ただし【なぜ終端になったか】で終わり方を分ける(便 a6e01bbc)。
1644
+ // ・done … 職人が完了の知らせを出した → ふつうに終わった便。片づけただけ。
1645
+ // ・それ以外(取りやめ・失敗・放棄)… 完了の知らせが無い → 今までどおり stopped。
1646
+ if (isArtisanFinishedStatus(status)) {
1647
+ job.reapedAfterFinish = true;
1648
+ log(`site=${cfg.site} id=${job.commandId} ${status}(完了の知らせ済み) → ` +
1649
+ '残った処理を片づけます(SIGTERM・止められたとは扱いません)');
1650
+ }
1651
+ else {
1652
+ job.stopped = true;
1653
+ log(`site=${cfg.site} id=${job.commandId} ${status} → SIGTERM`);
1654
+ }
1655
+ await stopJob(job);
1656
+ return;
1657
+ }
1582
1658
  }
1583
1659
  // タイムアウト — 中央の上限(3時間)で強制停止する。
1584
1660
  // ★RELAY_PENDING_TIMEOUT_MS(15分)は tmux の pending 解除用。画面なし方式では
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyhld/conductor",
3
- "version": "0.12.108",
3
+ "version": "0.12.109",
4
4
  "description": "采配くん管制の見守りアプリ(conductor-agent)。各PCで常駐し、中央(devlog-tracker)へ定期的に生存報告(heartbeat)を送る常駐CLI。",
5
5
  "type": "module",
6
6
  "bin": {