@cocorograph/hub-agent 0.8.42 → 0.8.43
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 +1 -1
- package/src/main.mjs +2 -2
- package/src/sibling-inject.mjs +107 -16
package/package.json
CHANGED
package/src/main.mjs
CHANGED
|
@@ -3791,11 +3791,11 @@ async function dispatch(msg, ctx) {
|
|
|
3791
3791
|
})
|
|
3792
3792
|
if (resumeSessionId) {
|
|
3793
3793
|
void ctx.siblingInjectBridge
|
|
3794
|
-
?.
|
|
3794
|
+
?.replayDelegations(cwdArg || process.env.HOME || process.cwd(), resumeSessionId)
|
|
3795
3795
|
.catch((err) => {
|
|
3796
3796
|
ctx.logger?.warn?.(
|
|
3797
3797
|
{ err: err?.message || String(err), stream_id, session_id: resumeSessionId },
|
|
3798
|
-
"failed to replay
|
|
3798
|
+
"failed to replay delegations after Claude attach",
|
|
3799
3799
|
)
|
|
3800
3800
|
})
|
|
3801
3801
|
}
|
package/src/sibling-inject.mjs
CHANGED
|
@@ -75,20 +75,68 @@ export const ACTIVE_DELEGATIONS_PER_SESSION_LIMIT = 100
|
|
|
75
75
|
export const DELEGATION_LEDGER_RETENTION_MS = 24 * 60 * 60 * 1000
|
|
76
76
|
const SIBLING_REQUEST_MAX_BYTES = 1024 * 1024
|
|
77
77
|
const DELEGATE_PROMPT_MAX_LENGTH = 64_000
|
|
78
|
+
const TERMINAL_DELEGATE_PROMPT_MAX_LENGTH = 2_000
|
|
79
|
+
const TERMINAL_DELEGATE_AGENT_MAX_LENGTH = 200
|
|
80
|
+
const TERMINAL_DELEGATE_DESCRIPTION_MAX_LENGTH = 2_000
|
|
81
|
+
const DELEGATION_ID_MAX_LENGTH = 200
|
|
78
82
|
|
|
79
83
|
const execFile = promisify(execFileCallback)
|
|
80
84
|
|
|
81
|
-
function
|
|
82
|
-
if (
|
|
83
|
-
let end =
|
|
84
|
-
const lastCodeUnit =
|
|
85
|
+
function truncateString(value, maxLength, truncationNote) {
|
|
86
|
+
if (value.length <= maxLength) return value
|
|
87
|
+
let end = maxLength
|
|
88
|
+
const lastCodeUnit = value.charCodeAt(end - 1)
|
|
85
89
|
if (lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff) end -= 1
|
|
86
|
-
return (
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
return value.slice(0, end) + truncationNote(value.length)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function truncateDelegatePrompt(prompt) {
|
|
94
|
+
return truncateString(
|
|
95
|
+
prompt,
|
|
96
|
+
DELEGATE_PROMPT_MAX_LENGTH,
|
|
97
|
+
(length) => `\n\n[依頼本文はここで打ち切られています(全 ${length} 文字)]`,
|
|
89
98
|
)
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
function compactTerminalEvent(event, includePrompt = false) {
|
|
102
|
+
const storedEvent = {}
|
|
103
|
+
for (const field of [
|
|
104
|
+
"type",
|
|
105
|
+
"subtype",
|
|
106
|
+
"delegation_id",
|
|
107
|
+
"phase",
|
|
108
|
+
"background",
|
|
109
|
+
"timestamp",
|
|
110
|
+
]) {
|
|
111
|
+
if (event[field] !== undefined) storedEvent[field] = event[field]
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof event.agent === "string") {
|
|
115
|
+
storedEvent.agent = truncateString(
|
|
116
|
+
event.agent,
|
|
117
|
+
TERMINAL_DELEGATE_AGENT_MAX_LENGTH,
|
|
118
|
+
(length) => `\n\n[エージェント名はここで打ち切られています(全 ${length} 文字)]`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
if (typeof event.description === "string") {
|
|
122
|
+
storedEvent.description = truncateString(
|
|
123
|
+
event.description,
|
|
124
|
+
TERMINAL_DELEGATE_DESCRIPTION_MAX_LENGTH,
|
|
125
|
+
(length) => `\n\n[説明はここで打ち切られています(全 ${length} 文字)]`,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
if (includePrompt && typeof event.prompt === "string") {
|
|
129
|
+
storedEvent.prompt = truncateString(
|
|
130
|
+
event.prompt,
|
|
131
|
+
TERMINAL_DELEGATE_PROMPT_MAX_LENGTH,
|
|
132
|
+
(length) =>
|
|
133
|
+
`\n\n[終了済み委譲の依頼本文はここで打ち切られています(全 ${length} 文字)]`,
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return storedEvent
|
|
138
|
+
}
|
|
139
|
+
|
|
92
140
|
/**
|
|
93
141
|
* `ps` の起動時刻を送受信双方で比較する契約形へ正規化する。
|
|
94
142
|
* 先頭・末尾の Unicode 空白だけを除去し、日時本体の内部空白は変更しない。
|
|
@@ -312,7 +360,7 @@ export class SiblingInjectBridge {
|
|
|
312
360
|
this._pidStartedAt = pidStartedAt
|
|
313
361
|
/** @type {Map<string, Map<string, {event: object, recordedAt: number}>>} cwd+session ごとの進行中委譲。 */
|
|
314
362
|
this._activeDelegations = new Map()
|
|
315
|
-
/** @type {Map<string, Map<string, number>>}
|
|
363
|
+
/** @type {Map<string, Map<string, {startedEvent?: object, terminalEvent: object, startedAt?: number, recordedAt: number}>>} 終了済み委譲の再送情報を兼ねた墓標。 */
|
|
316
364
|
this._terminalDelegations = new Map()
|
|
317
365
|
}
|
|
318
366
|
|
|
@@ -329,8 +377,8 @@ export class SiblingInjectBridge {
|
|
|
329
377
|
if (entries.size === 0) this._activeDelegations.delete(sessionKey)
|
|
330
378
|
}
|
|
331
379
|
for (const [sessionKey, entries] of this._terminalDelegations) {
|
|
332
|
-
for (const [delegationId,
|
|
333
|
-
if (recordedAt < cutoff) entries.delete(delegationId)
|
|
380
|
+
for (const [delegationId, entry] of entries) {
|
|
381
|
+
if (entry.recordedAt < cutoff) entries.delete(delegationId)
|
|
334
382
|
}
|
|
335
383
|
if (entries.size === 0) this._terminalDelegations.delete(sessionKey)
|
|
336
384
|
}
|
|
@@ -340,6 +388,10 @@ export class SiblingInjectBridge {
|
|
|
340
388
|
this._pruneDelegations()
|
|
341
389
|
const sessionKey = this._delegationSessionKey(cwd, sessionId)
|
|
342
390
|
if (event.phase !== "started") {
|
|
391
|
+
const activeEntry = this._activeDelegations.get(sessionKey)?.get(event.delegation_id)
|
|
392
|
+
const existingTerminalEntry = this._terminalDelegations
|
|
393
|
+
.get(sessionKey)
|
|
394
|
+
?.get(event.delegation_id)
|
|
343
395
|
this._activeDelegations.get(sessionKey)?.delete(event.delegation_id)
|
|
344
396
|
if (this._activeDelegations.get(sessionKey)?.size === 0) {
|
|
345
397
|
this._activeDelegations.delete(sessionKey)
|
|
@@ -350,7 +402,21 @@ export class SiblingInjectBridge {
|
|
|
350
402
|
this._terminalDelegations.set(sessionKey, terminalEntries)
|
|
351
403
|
}
|
|
352
404
|
terminalEntries.delete(event.delegation_id)
|
|
353
|
-
terminalEntries.set(event.delegation_id,
|
|
405
|
+
terminalEntries.set(event.delegation_id, {
|
|
406
|
+
...(activeEntry
|
|
407
|
+
? {
|
|
408
|
+
startedEvent: compactTerminalEvent(activeEntry.event, true),
|
|
409
|
+
startedAt: activeEntry.recordedAt,
|
|
410
|
+
}
|
|
411
|
+
: existingTerminalEntry?.startedEvent && existingTerminalEntry.startedAt !== undefined
|
|
412
|
+
? {
|
|
413
|
+
startedEvent: existingTerminalEntry.startedEvent,
|
|
414
|
+
startedAt: existingTerminalEntry.startedAt,
|
|
415
|
+
}
|
|
416
|
+
: {}),
|
|
417
|
+
terminalEvent: compactTerminalEvent(event),
|
|
418
|
+
recordedAt: this._now(),
|
|
419
|
+
})
|
|
354
420
|
while (terminalEntries.size > ACTIVE_DELEGATIONS_PER_SESSION_LIMIT) {
|
|
355
421
|
terminalEntries.delete(terminalEntries.keys().next().value)
|
|
356
422
|
}
|
|
@@ -394,14 +460,35 @@ export class SiblingInjectBridge {
|
|
|
394
460
|
}
|
|
395
461
|
}
|
|
396
462
|
|
|
397
|
-
/** attach/再接続時に同じ cwd+session
|
|
398
|
-
async
|
|
463
|
+
/** attach/再接続時に同じ cwd+session の進行中・終了済み委譲を開始順で再送する。 */
|
|
464
|
+
async replayDelegations(cwd, sessionId) {
|
|
399
465
|
if (!cwd || !sessionId) return
|
|
400
466
|
this._pruneDelegations()
|
|
401
467
|
const sessionKey = this._delegationSessionKey(cwd, sessionId)
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
468
|
+
const replayEntries = [
|
|
469
|
+
...[...(this._activeDelegations.get(sessionKey) || [])].map(([delegationId, entry]) => ({
|
|
470
|
+
delegationId,
|
|
471
|
+
entry,
|
|
472
|
+
kind: "active",
|
|
473
|
+
startedAt: entry.recordedAt,
|
|
474
|
+
})),
|
|
475
|
+
...[...(this._terminalDelegations.get(sessionKey) || [])]
|
|
476
|
+
.filter(([, entry]) => entry.startedEvent && entry.startedAt !== undefined)
|
|
477
|
+
.map(([delegationId, entry]) => ({
|
|
478
|
+
delegationId,
|
|
479
|
+
entry,
|
|
480
|
+
kind: "terminal",
|
|
481
|
+
startedAt: entry.startedAt,
|
|
482
|
+
})),
|
|
483
|
+
].sort((a, b) => a.startedAt - b.startedAt)
|
|
484
|
+
|
|
485
|
+
for (const { delegationId, entry, kind } of replayEntries) {
|
|
486
|
+
if (kind === "terminal") {
|
|
487
|
+
if (this._terminalDelegations.get(sessionKey)?.get(delegationId) !== entry) continue
|
|
488
|
+
this.claudeBridge?.emitToStream?.(cwd, sessionId, entry.startedEvent)
|
|
489
|
+
this.claudeBridge?.emitToStream?.(cwd, sessionId, entry.terminalEvent)
|
|
490
|
+
continue
|
|
491
|
+
}
|
|
405
492
|
const alive = await this._delegateProcessAlive(entry.event)
|
|
406
493
|
// 生存確認中に終端が届いた場合、古い snapshot の started/stopped を後から流さない。
|
|
407
494
|
if (this._activeDelegations.get(sessionKey)?.get(delegationId) !== entry) continue
|
|
@@ -571,6 +658,7 @@ export class SiblingInjectBridge {
|
|
|
571
658
|
phase,
|
|
572
659
|
agent,
|
|
573
660
|
description,
|
|
661
|
+
background,
|
|
574
662
|
prompt,
|
|
575
663
|
timestamp,
|
|
576
664
|
pid,
|
|
@@ -590,10 +678,12 @@ export class SiblingInjectBridge {
|
|
|
590
678
|
const badDelegateBody =
|
|
591
679
|
typeof delegation_id !== "string" ||
|
|
592
680
|
!delegation_id ||
|
|
681
|
+
delegation_id.length > DELEGATION_ID_MAX_LENGTH ||
|
|
593
682
|
!delegatePhases.has(phase) ||
|
|
594
683
|
(phase === "started" && (typeof agent !== "string" || !agent)) ||
|
|
595
684
|
(agent !== undefined && typeof agent !== "string") ||
|
|
596
685
|
(description !== undefined && typeof description !== "string") ||
|
|
686
|
+
(background !== undefined && typeof background !== "boolean") ||
|
|
597
687
|
(prompt !== undefined && typeof prompt !== "string") ||
|
|
598
688
|
(pid !== undefined && (!Number.isInteger(pid) || pid <= 0)) ||
|
|
599
689
|
(pid_started_at !== undefined && typeof pid_started_at !== "string") ||
|
|
@@ -656,6 +746,7 @@ export class SiblingInjectBridge {
|
|
|
656
746
|
phase,
|
|
657
747
|
...(agent !== undefined ? { agent } : {}),
|
|
658
748
|
...(description !== undefined ? { description } : {}),
|
|
749
|
+
...(background !== undefined ? { background } : {}),
|
|
659
750
|
...(phase === "started" && prompt ? { prompt: truncateDelegatePrompt(prompt) } : {}),
|
|
660
751
|
...(pid !== undefined ? { pid } : {}),
|
|
661
752
|
...(pid_started_at !== undefined ? { pid_started_at } : {}),
|