@cocorograph/hub-agent 0.8.31 → 0.8.32

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocorograph/hub-agent",
3
- "version": "0.8.31",
3
+ "version": "0.8.32",
4
4
  "description": "Hub Hosted Cockpit のローカル常駐 agent。Hub と outbound WSS で接続し、ローカルの Claude / Codex チャットと tmux セッションを中継する。",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -85,8 +85,12 @@ export function normalizeProcessStartedAt(raw) {
85
85
  return raw.trim()
86
86
  }
87
87
 
88
- async function defaultPidStartedAt(pid) {
89
- const { stdout } = await execFile("ps", ["-o", "lstart=", "-p", String(pid)])
88
+ export async function defaultPidStartedAt(pid, exec = execFile) {
89
+ // 送信側と同じ TZ/locale で比較可能な文字列を作る。契約の正本は Hub の codex_agent.py。
90
+ // launchd / WSL の環境は利用者のシェルと一致しないため、常駐サービスの環境は継承しない。
91
+ const { stdout } = await exec("ps", ["-o", "lstart=", "-p", String(pid)], {
92
+ env: { TZ: "UTC", LC_ALL: "C", LANG: "C" },
93
+ })
90
94
  return normalizeProcessStartedAt(stdout)
91
95
  }
92
96
 
@@ -352,6 +356,13 @@ export class SiblingInjectBridge {
352
356
  }
353
357
  }
354
358
 
359
+ _acceptsDelegateEvent(cwd, sessionId, event) {
360
+ this._pruneDelegations()
361
+ if (event.phase !== "started") return true
362
+ const sessionKey = this._delegationSessionKey(cwd, sessionId)
363
+ return !this._terminalDelegations.get(sessionKey)?.has(event.delegation_id)
364
+ }
365
+
355
366
  async _delegateProcessAlive(event) {
356
367
  if (!Number.isInteger(event.pid) || event.pid <= 0 || !event.pid_started_at) return true
357
368
  try {
@@ -610,17 +621,23 @@ export class SiblingInjectBridge {
610
621
  ...(pid_started_at !== undefined ? { pid_started_at } : {}),
611
622
  timestamp: timestamp || new Date().toISOString(),
612
623
  }
613
- const r = this.claudeBridge?.emitToStream
614
- ? this.claudeBridge.emitToStream(cwd, target_session_id, event)
615
- : { status: "not_found" }
616
- if (r.status === "delivered") {
617
- this._recordDelegateEvent(cwd, target_session_id, event)
618
- result = { ok: true, delivered_to: r.session }
624
+ // 墓標確認から配送成功時の台帳反映まで await を挟まない同期区間に閉じる。
625
+ // 配送失敗した started は台帳へ残さず、終端後に遅着した started は配送前に拒否する。
626
+ if (!this._acceptsDelegateEvent(cwd, target_session_id, event)) {
627
+ result = { ok: false, error: "stale_delegate_event" }
619
628
  } else {
620
- result = {
621
- ok: false,
622
- error: AMBIGUOUS_TARGET_ERROR,
623
- ...(r.candidates ? { candidates: r.candidates } : {}),
629
+ const r = this.claudeBridge?.emitToStream
630
+ ? this.claudeBridge.emitToStream(cwd, target_session_id, event)
631
+ : { status: "not_found" }
632
+ if (r.status === "delivered") {
633
+ this._recordDelegateEvent(cwd, target_session_id, event)
634
+ result = { ok: true, delivered_to: r.session }
635
+ } else {
636
+ result = {
637
+ ok: false,
638
+ error: AMBIGUOUS_TARGET_ERROR,
639
+ ...(r.candidates ? { candidates: r.candidates } : {}),
640
+ }
624
641
  }
625
642
  }
626
643
  }