peertable 0.8.55 → 0.8.56
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/README.ja.md +32 -33
- package/README.md +36 -37
- package/package.json +3 -1
- package/room/client.mjs +24 -33
- package/room/server.mjs +1 -1
- package/skill/SKILL.md +69 -94
- package/skill/scripts/aiterm-client.mjs +48 -0
- package/skill/scripts/aiterm-deliver.mjs +3 -1
- package/skill/scripts/change-seat.mjs +86 -0
- package/skill/scripts/change-seat.sh +3 -276
- package/skill/scripts/change-seat.test.mjs +129 -0
- package/skill/scripts/cli.mjs +77 -0
- package/skill/scripts/doctor.mjs +53 -0
- package/skill/scripts/doctor.sh +2 -213
- package/skill/scripts/ensure-all-bridges.mjs +42 -0
- package/skill/scripts/ensure-all-bridges.sh +3 -80
- package/skill/scripts/ensure-bridge.mjs +11 -0
- package/skill/scripts/ensure-bridge.sh +2 -95
- package/skill/scripts/ensure-codex-room-mcp.mjs +1 -1
- package/skill/scripts/ensure-project-runtime.mjs +85 -0
- package/skill/scripts/ensure-project-runtime.sh +2 -11
- package/skill/scripts/ensure-project-runtime.test.mjs +40 -0
- package/skill/scripts/install-skill.mjs +89 -0
- package/skill/scripts/install-skill.test.mjs +84 -0
- package/skill/scripts/launch-seat.mjs +156 -0
- package/skill/scripts/launch-seat.sh +3 -797
- package/skill/scripts/launch-seat.test.mjs +159 -0
- package/skill/scripts/leave-seat.mjs +42 -0
- package/skill/scripts/leave-seat.sh +3 -92
- package/skill/scripts/leave-seat.test.mjs +53 -0
- package/skill/scripts/legacy-entry.mjs +33 -0
- package/skill/scripts/platform/windows/resolve-lattice-command.mjs +12 -1
- package/skill/scripts/project-runtime.mjs +95 -0
- package/skill/scripts/project-runtime.test.mjs +29 -0
- package/skill/scripts/project-scaffold.mjs +179 -0
- package/skill/scripts/project-scaffold.test.mjs +129 -0
- package/skill/scripts/refresh-seat-identity.mjs +11 -8
- package/skill/scripts/remove-managed-room-mcp.mjs +9 -4
- package/skill/scripts/resume.sh +2 -225
- package/skill/scripts/room-api.mjs +28 -0
- package/skill/scripts/room-public-session.test.mjs +73 -0
- package/skill/scripts/runtime-contract.test.mjs +13 -146
- package/skill/scripts/runtime-launch-command.mjs +19 -0
- package/skill/scripts/runtime-launch-command.test.mjs +32 -0
- package/skill/scripts/seat-approval.mjs +24 -0
- package/skill/scripts/seat-approval.test.mjs +43 -0
- package/skill/scripts/seat-identity.mjs +2 -71
- package/skill/scripts/seat-observer.mjs +54 -0
- package/skill/scripts/seat-observer.test.mjs +58 -0
- package/skill/scripts/seat-session.mjs +17 -0
- package/skill/scripts/seat-status-bridge.mjs +33 -206
- package/skill/scripts/seat-usage.mjs +3 -334
- package/skill/scripts/setup.sh +3 -215
- package/skill/scripts/teardown.mjs +113 -0
- package/skill/scripts/teardown.sh +3 -410
- package/skill/scripts/teardown.test.mjs +62 -0
- package/skill/scripts/upgrade-team-assets.mjs +268 -0
- package/skill/scripts/upgrade-team-assets.sh +2 -282
- package/skill/scripts/wakeup-bridge.mjs +24 -32
- package/skill/scripts/wakeup-delivery.mjs +4 -14
- package/skill/templates/charter.md +1 -1
- package/skill/templates/member-standalone.md +1 -1
- package/skill/templates/member.md +3 -3
- package/skill/scripts/agent-pane-status.mjs +0 -6
- package/skill/scripts/aiterm-configure.mjs +0 -31
- package/skill/scripts/aiterm-launch.mjs +0 -64
- package/skill/scripts/aiterm-send.mjs +0 -22
- package/skill/scripts/claude-dialog.mjs +0 -26
- package/skill/scripts/codex-dialog.mjs +0 -67
- package/skill/scripts/platform/windows/build-bridge-command.mjs +0 -52
- package/skill/scripts/seat-input.mjs +0 -36
- package/skill/scripts/tmux-at.bash +0 -22
- package/skill/scripts/tmux-socket.mjs +0 -19
- package/skill/scripts/vendors/grok/pane-status.mjs +0 -30
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 既存卓のPeertable所有assetを現行packageへ同期する。
|
|
3
|
+
import { chmodSync, lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { createHash, randomBytes } from 'node:crypto'
|
|
5
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
6
|
+
import { pathToFileURL, fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const projectArg = process.argv[2]
|
|
9
|
+
const repoDir = fileURLToPath(new URL('../../', import.meta.url))
|
|
10
|
+
const { expectedRoomMcp, isExpectedRoomMcp } = await import(pathToFileURL(
|
|
11
|
+
join(repoDir, 'skill', 'scripts', 'room-mcp-config.mjs')))
|
|
12
|
+
|
|
13
|
+
function fail(code, detail) {
|
|
14
|
+
console.error(JSON.stringify({
|
|
15
|
+
schema: 'peertable.generated_assets_upgrade_result.v2',
|
|
16
|
+
result: 'rejected',
|
|
17
|
+
code,
|
|
18
|
+
detail,
|
|
19
|
+
}))
|
|
20
|
+
process.exit(1)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function lstatOrNull(path) {
|
|
24
|
+
try {
|
|
25
|
+
return lstatSync(path)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error?.code === 'ENOENT') return null
|
|
28
|
+
fail('PEERTABLE_UPGRADE_PATH_UNREADABLE', `${path}: ${error.message}`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function requireRegularFile(path, code, label) {
|
|
33
|
+
const stat = lstatOrNull(path)
|
|
34
|
+
if (!stat || stat.isSymbolicLink() || !stat.isFile()) {
|
|
35
|
+
fail(code, `${label}: ${path}`)
|
|
36
|
+
}
|
|
37
|
+
return stat
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validateDirectory(path, required, label) {
|
|
41
|
+
const stat = lstatOrNull(path)
|
|
42
|
+
if (!stat) {
|
|
43
|
+
if (required) fail('PEERTABLE_GENERATED_ASSET_UNSAFE_PATH', `${label} が無い: ${path}`)
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
47
|
+
fail('PEERTABLE_GENERATED_ASSET_UNSAFE_PATH', `${label} がsymlinkまたはdirectoryでない: ${path}`)
|
|
48
|
+
}
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function validateTargetPath(project, relativePath) {
|
|
53
|
+
const parts = relativePath.split('/')
|
|
54
|
+
let current = project
|
|
55
|
+
for (const part of parts.slice(0, -1)) {
|
|
56
|
+
current = join(current, part)
|
|
57
|
+
validateDirectory(current, part === '.team', `管理asset親 ${part}`)
|
|
58
|
+
}
|
|
59
|
+
const target = join(project, relativePath)
|
|
60
|
+
const stat = lstatOrNull(target)
|
|
61
|
+
if (stat && (stat.isSymbolicLink() || !stat.isFile())) {
|
|
62
|
+
fail('PEERTABLE_GENERATED_ASSET_UNSAFE_PATH', `管理assetがsymlinkまたはregular fileでない: ${target}`)
|
|
63
|
+
}
|
|
64
|
+
return { target, stat }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function templateText(repo, relativePath) {
|
|
68
|
+
const path = join(repo, relativePath)
|
|
69
|
+
requireRegularFile(path, 'PEERTABLE_GENERATED_ASSET_TEMPLATE_INVALID', 'template')
|
|
70
|
+
try {
|
|
71
|
+
return readFileSync(path)
|
|
72
|
+
} catch (error) {
|
|
73
|
+
fail('PEERTABLE_GENERATED_ASSET_TEMPLATE_INVALID', `${path}: ${error.message}`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sha256(content) {
|
|
78
|
+
return createHash('sha256').update(content).digest('hex')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderMember(template, state) {
|
|
82
|
+
if (state.mode === 'standalone') return template
|
|
83
|
+
const phases = state.phases
|
|
84
|
+
const scope = phases.length === 0
|
|
85
|
+
? 'この卓の claim 範囲は plan 全体(phase 指定なしで立っている)。'
|
|
86
|
+
: `**この卓の claim 範囲は phase ${phases.join(' ')} の task だけ**。範囲外の phase の task は、ready に見えていても取らない——同じ plan へ別 campaign が相乗りしている時、範囲外を取ると他卓の工程を横取りする(越境が2回実測されたことへの対処)。範囲外に手を入れる必要が出たら room へ出して裁定を仰ぐ。`
|
|
87
|
+
return template
|
|
88
|
+
.toString('utf8')
|
|
89
|
+
.replaceAll('{{PLAN_KEY}}', state.plan_key)
|
|
90
|
+
.replaceAll('{{CLAIM_SCOPE}}', scope)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function loadState(project) {
|
|
94
|
+
const path = join(project, '.team', 'setup-state.json')
|
|
95
|
+
requireRegularFile(path, 'PEERTABLE_SETUP_STATE_INVALID', 'setup-state.json')
|
|
96
|
+
let state
|
|
97
|
+
try {
|
|
98
|
+
state = JSON.parse(readFileSync(path, 'utf8'))
|
|
99
|
+
} catch (error) {
|
|
100
|
+
fail('PEERTABLE_SETUP_STATE_INVALID', `${path}: JSONを読めない: ${error.message}`)
|
|
101
|
+
}
|
|
102
|
+
if (!state || typeof state !== 'object' || Array.isArray(state)) {
|
|
103
|
+
fail('PEERTABLE_SETUP_STATE_INVALID', 'setup-state.json はobjectでなければならない')
|
|
104
|
+
}
|
|
105
|
+
if (!['lattice', 'standalone'].includes(state.mode)) {
|
|
106
|
+
fail('PEERTABLE_SETUP_STATE_INVALID', `modeが不正: ${String(state.mode)}`)
|
|
107
|
+
}
|
|
108
|
+
if (typeof state.room !== 'string' || !state.room || typeof state.server_url !== 'string' || !state.server_url) {
|
|
109
|
+
fail('PEERTABLE_SETUP_STATE_INVALID', 'room/server_url が不正')
|
|
110
|
+
}
|
|
111
|
+
if (typeof state.plan_key !== 'string') {
|
|
112
|
+
fail('PEERTABLE_SETUP_STATE_INVALID', 'plan_key がstringでない')
|
|
113
|
+
}
|
|
114
|
+
if (state.mode === 'lattice' && !state.plan_key) {
|
|
115
|
+
fail('PEERTABLE_SETUP_STATE_INVALID', 'lattice mode の plan_key が空')
|
|
116
|
+
}
|
|
117
|
+
if (state.phases !== undefined && (!Array.isArray(state.phases) || state.phases.some(phase => typeof phase !== 'string' || !phase))) {
|
|
118
|
+
fail('PEERTABLE_SETUP_STATE_INVALID', 'phases がstring配列でない')
|
|
119
|
+
}
|
|
120
|
+
for (const key of ['added_root_mcp', 'root_mcp_json_fallback']) {
|
|
121
|
+
if (state[key] !== undefined && typeof state[key] !== 'boolean') {
|
|
122
|
+
fail('PEERTABLE_SETUP_STATE_INVALID', `${key} がbooleanでない`)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { ...state, phases: state.phases ?? [],
|
|
126
|
+
root_mcp_managed: state.room_mcp_managed ?? state.added_root_mcp ?? state.root_mcp_json_fallback ?? false }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function prepareRoomMcp(project, repo, state) {
|
|
130
|
+
const relativePath = '.mcp.json'
|
|
131
|
+
const { target, stat } = validateTargetPath(project, relativePath)
|
|
132
|
+
if (!state.root_mcp_managed && !stat) {
|
|
133
|
+
fail('PEERTABLE_PREEXISTING_MCP_STALE', `${target}: 既存room MCPが無い。手動mergeが必要`)
|
|
134
|
+
}
|
|
135
|
+
let config = { mcpServers: {} }
|
|
136
|
+
if (stat) {
|
|
137
|
+
try {
|
|
138
|
+
config = JSON.parse(readFileSync(target, 'utf8'))
|
|
139
|
+
} catch (error) {
|
|
140
|
+
fail(state.root_mcp_managed ? 'PEERTABLE_MANAGED_MCP_INVALID' : 'PEERTABLE_PREEXISTING_MCP_STALE',
|
|
141
|
+
`${target}: JSONを読めない: ${error.message}`)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)
|
|
145
|
+
|| (config.mcpServers !== undefined
|
|
146
|
+
&& (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)))) {
|
|
147
|
+
fail(state.root_mcp_managed ? 'PEERTABLE_MANAGED_MCP_INVALID' : 'PEERTABLE_PREEXISTING_MCP_STALE',
|
|
148
|
+
`${target}: root/mcpServersがobjectでない`)
|
|
149
|
+
}
|
|
150
|
+
const expected = expectedRoomMcp(repo)
|
|
151
|
+
if (!state.root_mcp_managed) {
|
|
152
|
+
if (!isExpectedRoomMcp(config.mcpServers?.room, expected)) {
|
|
153
|
+
fail('PEERTABLE_PREEXISTING_MCP_STALE', `${target}: room blockを現行treeへ手動mergeする必要がある`)
|
|
154
|
+
}
|
|
155
|
+
return { item: null,
|
|
156
|
+
report: { path: relativePath, ownership: 'preexisting', action: 'unchanged-preexisting' } }
|
|
157
|
+
}
|
|
158
|
+
config.mcpServers ??= {}
|
|
159
|
+
config.mcpServers.room = expected
|
|
160
|
+
const item = { relativePath, sourcePath: 'room/client.mjs', target, stat,
|
|
161
|
+
content: Buffer.from(`${JSON.stringify(config, null, 2)}\n`, 'utf8'), mode: stat ? stat.mode & 0o777 : 0o644 }
|
|
162
|
+
return { item, report: { path: relativePath, ownership: 'managed', action: null } }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function writeAtomically(target, content, mode) {
|
|
166
|
+
const temporary = join(dirname(target), `.${basename(target)}.upgrade-${process.pid}-${randomBytes(6).toString('hex')}.tmp`)
|
|
167
|
+
try {
|
|
168
|
+
writeFileSync(temporary, content, { mode })
|
|
169
|
+
chmodSync(temporary, mode)
|
|
170
|
+
renameSync(temporary, target)
|
|
171
|
+
} catch (error) {
|
|
172
|
+
try { lstatSync(temporary); unlinkSync(temporary) } catch {}
|
|
173
|
+
fail('PEERTABLE_GENERATED_ASSET_WRITE_FAILED', `${target}: ${error.message}`)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function main() {
|
|
178
|
+
if (!projectArg || !repoDir) fail('PEERTABLE_UPGRADE_USAGE', 'project_dir が無い')
|
|
179
|
+
const projectStat = lstatOrNull(projectArg)
|
|
180
|
+
if (!projectStat || projectStat.isSymbolicLink() || !projectStat.isDirectory()) {
|
|
181
|
+
fail('PEERTABLE_UPGRADE_PROJECT_INVALID', `project directoryが不正: ${projectArg}`)
|
|
182
|
+
}
|
|
183
|
+
const project = resolve(projectArg)
|
|
184
|
+
const repo = resolve(repoDir)
|
|
185
|
+
validateDirectory(join(project, '.team'), true, '.team')
|
|
186
|
+
validateDirectory(join(repo, 'skill'), true, 'Peertable repo')
|
|
187
|
+
const state = loadState(project)
|
|
188
|
+
|
|
189
|
+
const common = [
|
|
190
|
+
['.team/CLAUDE.md', 'skill/templates/charter.md', 0o644],
|
|
191
|
+
['.team/roles/parent.md', 'skill/templates/parent.md', 0o644],
|
|
192
|
+
['.team/scripts/alarm-set.sh', 'skill/scripts/alarm-set.sh', 0o755],
|
|
193
|
+
['.team/scripts/alarm-write.mjs', 'skill/scripts/alarm-write.mjs', 0o755],
|
|
194
|
+
['.team/scripts/alarm-condition.mjs', 'skill/scripts/alarm-condition.mjs', 0o755],
|
|
195
|
+
['.team/scripts/pull-attach-input.mjs', 'skill/scripts/pull-attach-input.mjs', 0o755],
|
|
196
|
+
]
|
|
197
|
+
const modeSpecific = state.mode === 'lattice'
|
|
198
|
+
? [
|
|
199
|
+
['.team/roles/member.md', 'skill/templates/member.md', 0o644],
|
|
200
|
+
['.team/scripts/done.sh', 'skill/templates/done.sh', 0o755],
|
|
201
|
+
['.team/scripts/independence-refresh.sh', 'skill/templates/independence-refresh.sh', 0o755],
|
|
202
|
+
]
|
|
203
|
+
: [['.team/roles/member.md', 'skill/templates/member-standalone.md', 0o644]]
|
|
204
|
+
const definitions = [...common, ...modeSpecific]
|
|
205
|
+
|
|
206
|
+
const prepared = definitions.map(([relativePath, sourcePath, mode]) => {
|
|
207
|
+
const { target, stat } = validateTargetPath(project, relativePath)
|
|
208
|
+
const source = templateText(repo, sourcePath)
|
|
209
|
+
const content = relativePath === '.team/roles/member.md' && state.mode === 'lattice'
|
|
210
|
+
? Buffer.from(renderMember(source, state), 'utf8')
|
|
211
|
+
: source
|
|
212
|
+
return { relativePath, sourcePath, target, stat, content, mode }
|
|
213
|
+
})
|
|
214
|
+
const roomMcp = prepareRoomMcp(project, repo, state)
|
|
215
|
+
if (roomMcp.item) prepared.push(roomMcp.item)
|
|
216
|
+
|
|
217
|
+
const changes = prepared.map(item => {
|
|
218
|
+
if (!item.stat) return { path: item.relativePath, action: 'created', sha256: sha256(item.content) }
|
|
219
|
+
const current = readFileSync(item.target)
|
|
220
|
+
const mode = item.stat.mode & 0o777
|
|
221
|
+
const modeMatches = process.platform === 'win32' || mode === item.mode
|
|
222
|
+
if (current.equals(item.content) && modeMatches) {
|
|
223
|
+
return { path: item.relativePath, action: 'unchanged', sha256: sha256(item.content) }
|
|
224
|
+
}
|
|
225
|
+
return { path: item.relativePath, action: current.equals(item.content) ? 'mode-updated' : 'updated', sha256: sha256(item.content) }
|
|
226
|
+
})
|
|
227
|
+
const obsolete = ['.team/scripts/start.sh', '.team/scripts/start-event.mjs']
|
|
228
|
+
.map(relativePath => ({ relativePath, ...validateTargetPath(project, relativePath) }))
|
|
229
|
+
|
|
230
|
+
// 全対象の安全性・template・差分を先に確定してから、管理allowlistだけへ書く。
|
|
231
|
+
for (const [index, item] of prepared.entries()) {
|
|
232
|
+
const change = changes[index]
|
|
233
|
+
if (change.action === 'unchanged') continue
|
|
234
|
+
const parent = dirname(item.target)
|
|
235
|
+
mkdirSync(parent, { recursive: true, mode: 0o755 })
|
|
236
|
+
if (change.action === 'mode-updated') chmodSync(item.target, item.mode)
|
|
237
|
+
else writeAtomically(item.target, item.content, item.mode)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const removed = []
|
|
241
|
+
for (const { relativePath, target, stat } of obsolete) {
|
|
242
|
+
if (!stat) continue
|
|
243
|
+
unlinkSync(target)
|
|
244
|
+
removed.push(relativePath)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log(JSON.stringify({
|
|
248
|
+
schema: 'peertable.generated_assets_upgrade_result.v2',
|
|
249
|
+
result: 'ok',
|
|
250
|
+
project,
|
|
251
|
+
mode: state.mode,
|
|
252
|
+
managed: prepared.map(item => ({ path: item.relativePath, source: item.sourcePath, sha256: sha256(item.content) })),
|
|
253
|
+
changes,
|
|
254
|
+
room_mcp: roomMcp.item
|
|
255
|
+
? { ...roomMcp.report,
|
|
256
|
+
action: changes.find(change => change.path === roomMcp.item.relativePath).action }
|
|
257
|
+
: roomMcp.report,
|
|
258
|
+
removed,
|
|
259
|
+
changed_count: changes.filter(change => change.action !== 'unchanged').length + removed.length,
|
|
260
|
+
}))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
main()
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error?.code && typeof error.message === 'string') fail('PEERTABLE_GENERATED_ASSET_UPGRADE_FAILED', error.message)
|
|
267
|
+
fail('PEERTABLE_GENERATED_ASSET_UPGRADE_FAILED', String(error))
|
|
268
|
+
}
|
|
@@ -1,283 +1,3 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
if [ "$#" -ne 1 ]; then
|
|
7
|
-
echo 'PEERTABLE_UPGRADE_USAGE: upgrade-team-assets.sh <project_dir>' >&2
|
|
8
|
-
exit 2
|
|
9
|
-
fi
|
|
10
|
-
|
|
11
|
-
# skill dir は ~/.claude/skills/peertable のような symlink 経由で呼ばれるので、$0 を実体へ解決してから repo を求める
|
|
12
|
-
# (解決しないと ../.. が ~/.claude/skills になり room-mcp-config.mjs を見失う。2026-09-04 実測)
|
|
13
|
-
script_dir=$(CDPATH= cd -- "$(dirname -- "$(readlink -f -- "$0")")" && pwd)
|
|
14
|
-
repo_dir=$(CDPATH= cd -- "$script_dir/../.." && pwd)
|
|
15
|
-
|
|
16
|
-
node --input-type=module - "$1" "$repo_dir" <<'NODE'
|
|
17
|
-
import { chmodSync, lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
18
|
-
import { createHash, randomBytes } from 'node:crypto'
|
|
19
|
-
import { basename, dirname, join, resolve } from 'node:path'
|
|
20
|
-
import { pathToFileURL } from 'node:url'
|
|
21
|
-
|
|
22
|
-
const projectArg = process.argv[2]
|
|
23
|
-
const repoDir = process.argv[3]
|
|
24
|
-
const { expectedRoomMcp, isExpectedRoomMcp } = await import(pathToFileURL(
|
|
25
|
-
join(repoDir, 'skill', 'scripts', 'room-mcp-config.mjs')))
|
|
26
|
-
|
|
27
|
-
function fail(code, detail) {
|
|
28
|
-
console.error(JSON.stringify({
|
|
29
|
-
schema: 'peertable.generated_assets_upgrade_result.v2',
|
|
30
|
-
result: 'rejected',
|
|
31
|
-
code,
|
|
32
|
-
detail,
|
|
33
|
-
}))
|
|
34
|
-
process.exit(1)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function lstatOrNull(path) {
|
|
38
|
-
try {
|
|
39
|
-
return lstatSync(path)
|
|
40
|
-
} catch (error) {
|
|
41
|
-
if (error?.code === 'ENOENT') return null
|
|
42
|
-
fail('PEERTABLE_UPGRADE_PATH_UNREADABLE', `${path}: ${error.message}`)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function requireRegularFile(path, code, label) {
|
|
47
|
-
const stat = lstatOrNull(path)
|
|
48
|
-
if (!stat || stat.isSymbolicLink() || !stat.isFile()) {
|
|
49
|
-
fail(code, `${label}: ${path}`)
|
|
50
|
-
}
|
|
51
|
-
return stat
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function validateDirectory(path, required, label) {
|
|
55
|
-
const stat = lstatOrNull(path)
|
|
56
|
-
if (!stat) {
|
|
57
|
-
if (required) fail('PEERTABLE_GENERATED_ASSET_UNSAFE_PATH', `${label} が無い: ${path}`)
|
|
58
|
-
return false
|
|
59
|
-
}
|
|
60
|
-
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
61
|
-
fail('PEERTABLE_GENERATED_ASSET_UNSAFE_PATH', `${label} がsymlinkまたはdirectoryでない: ${path}`)
|
|
62
|
-
}
|
|
63
|
-
return true
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function validateTargetPath(project, relativePath) {
|
|
67
|
-
const parts = relativePath.split('/')
|
|
68
|
-
let current = project
|
|
69
|
-
for (const part of parts.slice(0, -1)) {
|
|
70
|
-
current = join(current, part)
|
|
71
|
-
validateDirectory(current, part === '.team', `管理asset親 ${part}`)
|
|
72
|
-
}
|
|
73
|
-
const target = join(project, relativePath)
|
|
74
|
-
const stat = lstatOrNull(target)
|
|
75
|
-
if (stat && (stat.isSymbolicLink() || !stat.isFile())) {
|
|
76
|
-
fail('PEERTABLE_GENERATED_ASSET_UNSAFE_PATH', `管理assetがsymlinkまたはregular fileでない: ${target}`)
|
|
77
|
-
}
|
|
78
|
-
return { target, stat }
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function templateText(repo, relativePath) {
|
|
82
|
-
const path = join(repo, relativePath)
|
|
83
|
-
requireRegularFile(path, 'PEERTABLE_GENERATED_ASSET_TEMPLATE_INVALID', 'template')
|
|
84
|
-
try {
|
|
85
|
-
return readFileSync(path)
|
|
86
|
-
} catch (error) {
|
|
87
|
-
fail('PEERTABLE_GENERATED_ASSET_TEMPLATE_INVALID', `${path}: ${error.message}`)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function sha256(content) {
|
|
92
|
-
return createHash('sha256').update(content).digest('hex')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function renderMember(template, state) {
|
|
96
|
-
if (state.mode === 'standalone') return template
|
|
97
|
-
const phases = state.phases
|
|
98
|
-
const scope = phases.length === 0
|
|
99
|
-
? 'この卓の claim 範囲は plan 全体(phase 指定なしで立っている)。'
|
|
100
|
-
: `**この卓の claim 範囲は phase ${phases.join(' ')} の task だけ**。範囲外の phase の task は、ready に見えていても取らない——同じ plan へ別 campaign が相乗りしている時、範囲外を取ると他卓の工程を横取りする(越境が2回実測されたことへの対処)。範囲外に手を入れる必要が出たら room へ出して裁定を仰ぐ。`
|
|
101
|
-
return template
|
|
102
|
-
.toString('utf8')
|
|
103
|
-
.replaceAll('{{PLAN_KEY}}', state.plan_key)
|
|
104
|
-
.replaceAll('{{CLAIM_SCOPE}}', scope)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function loadState(project) {
|
|
108
|
-
const path = join(project, '.team', 'setup-state.json')
|
|
109
|
-
requireRegularFile(path, 'PEERTABLE_SETUP_STATE_INVALID', 'setup-state.json')
|
|
110
|
-
let state
|
|
111
|
-
try {
|
|
112
|
-
state = JSON.parse(readFileSync(path, 'utf8'))
|
|
113
|
-
} catch (error) {
|
|
114
|
-
fail('PEERTABLE_SETUP_STATE_INVALID', `${path}: JSONを読めない: ${error.message}`)
|
|
115
|
-
}
|
|
116
|
-
if (!state || typeof state !== 'object' || Array.isArray(state)) {
|
|
117
|
-
fail('PEERTABLE_SETUP_STATE_INVALID', 'setup-state.json はobjectでなければならない')
|
|
118
|
-
}
|
|
119
|
-
if (!['lattice', 'standalone'].includes(state.mode)) {
|
|
120
|
-
fail('PEERTABLE_SETUP_STATE_INVALID', `modeが不正: ${String(state.mode)}`)
|
|
121
|
-
}
|
|
122
|
-
if (typeof state.room !== 'string' || !state.room || typeof state.server_url !== 'string' || !state.server_url) {
|
|
123
|
-
fail('PEERTABLE_SETUP_STATE_INVALID', 'room/server_url が不正')
|
|
124
|
-
}
|
|
125
|
-
if (typeof state.plan_key !== 'string') {
|
|
126
|
-
fail('PEERTABLE_SETUP_STATE_INVALID', 'plan_key がstringでない')
|
|
127
|
-
}
|
|
128
|
-
if (state.mode === 'lattice' && !state.plan_key) {
|
|
129
|
-
fail('PEERTABLE_SETUP_STATE_INVALID', 'lattice mode の plan_key が空')
|
|
130
|
-
}
|
|
131
|
-
if (state.phases !== undefined && (!Array.isArray(state.phases) || state.phases.some(phase => typeof phase !== 'string' || !phase))) {
|
|
132
|
-
fail('PEERTABLE_SETUP_STATE_INVALID', 'phases がstring配列でない')
|
|
133
|
-
}
|
|
134
|
-
for (const key of ['added_root_mcp', 'root_mcp_json_fallback']) {
|
|
135
|
-
if (state[key] !== undefined && typeof state[key] !== 'boolean') {
|
|
136
|
-
fail('PEERTABLE_SETUP_STATE_INVALID', `${key} がbooleanでない`)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return { ...state, phases: state.phases ?? [],
|
|
140
|
-
root_mcp_managed: state.added_root_mcp ?? state.root_mcp_json_fallback ?? false }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function prepareRoomMcp(project, repo, state) {
|
|
144
|
-
const relativePath = '.mcp.json'
|
|
145
|
-
const { target, stat } = validateTargetPath(project, relativePath)
|
|
146
|
-
if (!state.root_mcp_managed && !stat) {
|
|
147
|
-
fail('PEERTABLE_PREEXISTING_MCP_STALE', `${target}: 既存room MCPが無い。手動mergeが必要`)
|
|
148
|
-
}
|
|
149
|
-
let config = { mcpServers: {} }
|
|
150
|
-
if (stat) {
|
|
151
|
-
try {
|
|
152
|
-
config = JSON.parse(readFileSync(target, 'utf8'))
|
|
153
|
-
} catch (error) {
|
|
154
|
-
fail(state.root_mcp_managed ? 'PEERTABLE_MANAGED_MCP_INVALID' : 'PEERTABLE_PREEXISTING_MCP_STALE',
|
|
155
|
-
`${target}: JSONを読めない: ${error.message}`)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)
|
|
159
|
-
|| (config.mcpServers !== undefined
|
|
160
|
-
&& (!config.mcpServers || typeof config.mcpServers !== 'object' || Array.isArray(config.mcpServers)))) {
|
|
161
|
-
fail(state.root_mcp_managed ? 'PEERTABLE_MANAGED_MCP_INVALID' : 'PEERTABLE_PREEXISTING_MCP_STALE',
|
|
162
|
-
`${target}: root/mcpServersがobjectでない`)
|
|
163
|
-
}
|
|
164
|
-
const expected = expectedRoomMcp(repo)
|
|
165
|
-
if (!state.root_mcp_managed) {
|
|
166
|
-
if (!isExpectedRoomMcp(config.mcpServers?.room, expected)) {
|
|
167
|
-
fail('PEERTABLE_PREEXISTING_MCP_STALE', `${target}: room blockを現行treeへ手動mergeする必要がある`)
|
|
168
|
-
}
|
|
169
|
-
return { item: null,
|
|
170
|
-
report: { path: relativePath, ownership: 'preexisting', action: 'unchanged-preexisting' } }
|
|
171
|
-
}
|
|
172
|
-
config.mcpServers ??= {}
|
|
173
|
-
config.mcpServers.room = expected
|
|
174
|
-
const item = { relativePath, sourcePath: 'room/client.mjs', target, stat,
|
|
175
|
-
content: Buffer.from(`${JSON.stringify(config, null, 2)}\n`, 'utf8'), mode: stat ? stat.mode & 0o777 : 0o644 }
|
|
176
|
-
return { item, report: { path: relativePath, ownership: 'managed', action: null } }
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function writeAtomically(target, content, mode) {
|
|
180
|
-
const temporary = join(dirname(target), `.${basename(target)}.upgrade-${process.pid}-${randomBytes(6).toString('hex')}.tmp`)
|
|
181
|
-
try {
|
|
182
|
-
writeFileSync(temporary, content, { mode })
|
|
183
|
-
chmodSync(temporary, mode)
|
|
184
|
-
renameSync(temporary, target)
|
|
185
|
-
} catch (error) {
|
|
186
|
-
try { lstatSync(temporary); unlinkSync(temporary) } catch {}
|
|
187
|
-
fail('PEERTABLE_GENERATED_ASSET_WRITE_FAILED', `${target}: ${error.message}`)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function main() {
|
|
192
|
-
if (!projectArg || !repoDir) fail('PEERTABLE_UPGRADE_USAGE', 'project_dir が無い')
|
|
193
|
-
const projectStat = lstatOrNull(projectArg)
|
|
194
|
-
if (!projectStat || projectStat.isSymbolicLink() || !projectStat.isDirectory()) {
|
|
195
|
-
fail('PEERTABLE_UPGRADE_PROJECT_INVALID', `project directoryが不正: ${projectArg}`)
|
|
196
|
-
}
|
|
197
|
-
const project = resolve(projectArg)
|
|
198
|
-
const repo = resolve(repoDir)
|
|
199
|
-
validateDirectory(join(project, '.team'), true, '.team')
|
|
200
|
-
validateDirectory(join(repo, 'skill'), true, 'Peertable repo')
|
|
201
|
-
const state = loadState(project)
|
|
202
|
-
|
|
203
|
-
const common = [
|
|
204
|
-
['.team/CLAUDE.md', 'skill/templates/charter.md', 0o644],
|
|
205
|
-
['.team/roles/parent.md', 'skill/templates/parent.md', 0o644],
|
|
206
|
-
['.team/scripts/alarm-set.sh', 'skill/scripts/alarm-set.sh', 0o755],
|
|
207
|
-
['.team/scripts/alarm-write.mjs', 'skill/scripts/alarm-write.mjs', 0o755],
|
|
208
|
-
['.team/scripts/alarm-condition.mjs', 'skill/scripts/alarm-condition.mjs', 0o755],
|
|
209
|
-
['.team/scripts/pull-attach-input.mjs', 'skill/scripts/pull-attach-input.mjs', 0o755],
|
|
210
|
-
]
|
|
211
|
-
const modeSpecific = state.mode === 'lattice'
|
|
212
|
-
? [
|
|
213
|
-
['.team/roles/member.md', 'skill/templates/member.md', 0o644],
|
|
214
|
-
['.team/scripts/done.sh', 'skill/templates/done.sh', 0o755],
|
|
215
|
-
['.team/scripts/independence-refresh.sh', 'skill/templates/independence-refresh.sh', 0o755],
|
|
216
|
-
]
|
|
217
|
-
: [['.team/roles/member.md', 'skill/templates/member-standalone.md', 0o644]]
|
|
218
|
-
const definitions = [...common, ...modeSpecific]
|
|
219
|
-
|
|
220
|
-
const prepared = definitions.map(([relativePath, sourcePath, mode]) => {
|
|
221
|
-
const { target, stat } = validateTargetPath(project, relativePath)
|
|
222
|
-
const source = templateText(repo, sourcePath)
|
|
223
|
-
const content = relativePath === '.team/roles/member.md' && state.mode === 'lattice'
|
|
224
|
-
? Buffer.from(renderMember(source, state), 'utf8')
|
|
225
|
-
: source
|
|
226
|
-
return { relativePath, sourcePath, target, stat, content, mode }
|
|
227
|
-
})
|
|
228
|
-
const roomMcp = prepareRoomMcp(project, repo, state)
|
|
229
|
-
if (roomMcp.item) prepared.push(roomMcp.item)
|
|
230
|
-
|
|
231
|
-
const changes = prepared.map(item => {
|
|
232
|
-
if (!item.stat) return { path: item.relativePath, action: 'created', sha256: sha256(item.content) }
|
|
233
|
-
const current = readFileSync(item.target)
|
|
234
|
-
const mode = item.stat.mode & 0o777
|
|
235
|
-
const modeMatches = process.platform === 'win32' || mode === item.mode
|
|
236
|
-
if (current.equals(item.content) && modeMatches) {
|
|
237
|
-
return { path: item.relativePath, action: 'unchanged', sha256: sha256(item.content) }
|
|
238
|
-
}
|
|
239
|
-
return { path: item.relativePath, action: current.equals(item.content) ? 'mode-updated' : 'updated', sha256: sha256(item.content) }
|
|
240
|
-
})
|
|
241
|
-
const obsolete = ['.team/scripts/start.sh', '.team/scripts/start-event.mjs']
|
|
242
|
-
.map(relativePath => ({ relativePath, ...validateTargetPath(project, relativePath) }))
|
|
243
|
-
|
|
244
|
-
// 全対象の安全性・template・差分を先に確定してから、管理allowlistだけへ書く。
|
|
245
|
-
for (const [index, item] of prepared.entries()) {
|
|
246
|
-
const change = changes[index]
|
|
247
|
-
if (change.action === 'unchanged') continue
|
|
248
|
-
const parent = dirname(item.target)
|
|
249
|
-
mkdirSync(parent, { recursive: true, mode: 0o755 })
|
|
250
|
-
if (change.action === 'mode-updated') chmodSync(item.target, item.mode)
|
|
251
|
-
else writeAtomically(item.target, item.content, item.mode)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const removed = []
|
|
255
|
-
for (const { relativePath, target, stat } of obsolete) {
|
|
256
|
-
if (!stat) continue
|
|
257
|
-
unlinkSync(target)
|
|
258
|
-
removed.push(relativePath)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
console.log(JSON.stringify({
|
|
262
|
-
schema: 'peertable.generated_assets_upgrade_result.v2',
|
|
263
|
-
result: 'ok',
|
|
264
|
-
project,
|
|
265
|
-
mode: state.mode,
|
|
266
|
-
managed: prepared.map(item => ({ path: item.relativePath, source: item.sourcePath, sha256: sha256(item.content) })),
|
|
267
|
-
changes,
|
|
268
|
-
room_mcp: roomMcp.item
|
|
269
|
-
? { ...roomMcp.report,
|
|
270
|
-
action: changes.find(change => change.path === roomMcp.item.relativePath).action }
|
|
271
|
-
: roomMcp.report,
|
|
272
|
-
removed,
|
|
273
|
-
changed_count: changes.filter(change => change.action !== 'unchanged').length + removed.length,
|
|
274
|
-
}))
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
main()
|
|
279
|
-
} catch (error) {
|
|
280
|
-
if (error?.code && typeof error.message === 'string') fail('PEERTABLE_GENERATED_ASSET_UPGRADE_FAILED', error.message)
|
|
281
|
-
fail('PEERTABLE_GENERATED_ASSET_UPGRADE_FAILED', String(error))
|
|
282
|
-
}
|
|
283
|
-
NODE
|
|
2
|
+
# 旧入口互換。処理の正本はOS共通のNode実装。
|
|
3
|
+
exec node "$(dirname "$0")/upgrade-team-assets.mjs" "$@"
|
|
@@ -20,8 +20,10 @@ import {
|
|
|
20
20
|
import { join } from 'node:path'
|
|
21
21
|
import { promisify } from 'node:util'
|
|
22
22
|
import { fileURLToPath } from 'node:url'
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { resolvePostToken } from './seat-usage.mjs'
|
|
24
|
+
import { AitermClient } from './aiterm-client.mjs'
|
|
25
|
+
import { seatSessionId } from './seat-session.mjs'
|
|
26
|
+
import { passSeatApproval } from './seat-approval.mjs'
|
|
25
27
|
import { BROADCAST_RECIPIENT, formatWakeNotice, isIdleSelfWake, isWakeupBridgeTarget, memberHarness, shouldDeferGrokWake } from './wakeup-delivery.mjs'
|
|
26
28
|
import { bridgeRecordLive } from './bridge-record-live.mjs'
|
|
27
29
|
|
|
@@ -366,20 +368,11 @@ function advanceLastSeq() {
|
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
const deferredBusy = new Set()
|
|
369
|
-
|
|
370
|
-
|
|
371
|
+
const aiterm = new AitermClient()
|
|
372
|
+
async function passKnownSeatApproval(member, observation) {
|
|
371
373
|
if (!isWakeupBridgeTarget(member, targetOpts)) return false
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
const pane = await run('tmux', tmuxArgv(['capture-pane', '-t', observation.target, '-p'], { socket: observation.socket }))
|
|
375
|
-
const dialog = keysForCodexPane(String(pane.stdout ?? ''))
|
|
376
|
-
if (!dialog) return false
|
|
377
|
-
for (const key of dialog.keys) {
|
|
378
|
-
await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, key], { socket: observation.socket }))
|
|
379
|
-
await sleep(150)
|
|
380
|
-
}
|
|
381
|
-
log(`Codex ${dialog.kind} を通した: ${member.name}`)
|
|
382
|
-
return true
|
|
374
|
+
const session = seatSessionId(member)
|
|
375
|
+
return session ? passSeatApproval(aiterm, session, memberHarness(member), observation) : false
|
|
383
376
|
}
|
|
384
377
|
|
|
385
378
|
async function wake(seat, msgs) {
|
|
@@ -389,8 +382,8 @@ async function wake(seat, msgs) {
|
|
|
389
382
|
await refreshMembers()
|
|
390
383
|
const member = members.get(seat)
|
|
391
384
|
if (!isWakeupBridgeTarget(member, targetOpts)) return 'skipped'
|
|
392
|
-
const
|
|
393
|
-
if (
|
|
385
|
+
const sessionId = seatSessionId(member)
|
|
386
|
+
if (!sessionId) {
|
|
394
387
|
const code = members.has(seat) ? 'DESCRIPTOR_MISSING' : 'MEMBER_MISSING'
|
|
395
388
|
const error = new Error(`${code}: ${seat}`)
|
|
396
389
|
error.code = code
|
|
@@ -401,12 +394,16 @@ async function wake(seat, msgs) {
|
|
|
401
394
|
// 所有し、bridge は「誰に・いつ・何を届けるか」と receipt だけを持つ(責務境界。2026-09-04
|
|
402
395
|
// オーナー裁定: 0.8.51/0.8.52 で bridge に足した fg/stty は aiterm へ移し、ここからは撤去)。
|
|
403
396
|
// 死んだ席(agent 不在の素の shell)へは aiterm の ready gate が入力受付不能として送らない。
|
|
404
|
-
const
|
|
405
|
-
|
|
397
|
+
const observed = await aiterm.observe(sessionId)
|
|
398
|
+
if (!observed.exists || ['dead', 'missing'].includes(observed.state) || observed.harness_alive === false) {
|
|
399
|
+
throw Object.assign(new Error(`SEAT_TUI_GONE: ${seat}`), { code: 'SEAT_TUI_GONE' })
|
|
400
|
+
}
|
|
401
|
+
if (observed.harness_alive === null || observed.state === 'unknown') {
|
|
402
|
+
throw Object.assign(new Error(`SEAT_OBSERVATION_UNKNOWN: ${seat}: ${observed.reason}`), { code: 'SEAT_OBSERVATION_UNKNOWN' })
|
|
403
|
+
}
|
|
406
404
|
const harness = memberHarness(member)
|
|
407
|
-
const tail = screen.split('\n').slice(-14).join('\n')
|
|
408
405
|
if (harness === 'grok') {
|
|
409
|
-
if (shouldDeferGrokWake(harness,
|
|
406
|
+
if (shouldDeferGrokWake(harness, observed.state)) {
|
|
410
407
|
if (!deferredBusy.has(seat)) {
|
|
411
408
|
log(`Grok席が実行中なのでidleまで待つ: ${seat} ← ${msgs.length} 件`)
|
|
412
409
|
deferredBusy.add(seat)
|
|
@@ -415,13 +412,8 @@ async function wake(seat, msgs) {
|
|
|
415
412
|
}
|
|
416
413
|
deferredBusy.delete(seat)
|
|
417
414
|
}
|
|
418
|
-
if (await
|
|
419
|
-
const
|
|
420
|
-
? member.aiterm_session_id
|
|
421
|
-
: observation.target
|
|
422
|
-
// Codex は実行中ターンへ steer(0.153 は「次の tool call 後に送る」キューへ積む)、idle なら dispatch。
|
|
423
|
-
// 実行中の判定は画面の実行中マーカーだけ(読むだけで、状態を変えない)。
|
|
424
|
-
const busy = classifyPaneTail(tail) === 'busy' || tail.includes('esc to interrupt')
|
|
415
|
+
if (await passKnownSeatApproval(member, observed)) return 'deferred'
|
|
416
|
+
const busy = observed.state === 'busy'
|
|
425
417
|
const deliverScript = fileURLToPath(new URL('./aiterm-deliver.mjs', import.meta.url))
|
|
426
418
|
const deliverFile = join(proj, '.team', `wakeup-deliver-${process.pid}.txt`)
|
|
427
419
|
writeFileSync(deliverFile, text)
|
|
@@ -616,9 +608,9 @@ setInterval(() => {
|
|
|
616
608
|
}
|
|
617
609
|
}, 2000)
|
|
618
610
|
|
|
619
|
-
//
|
|
611
|
+
// 席のtool approvalはroom発言の配達時だけでなく、席自身の最初のtool実行でも
|
|
620
612
|
// 遅れて現れる。pending DMが無い時も既知dialogだけを処理し続け、ランプがblockedのまま
|
|
621
|
-
// 放置される状態を作らない。未知dialogは
|
|
613
|
+
// 放置される状態を作らない。未知dialogはAitermが明示的に拒否する。
|
|
622
614
|
let dialogSweepRunning = false
|
|
623
615
|
setInterval(async () => {
|
|
624
616
|
if (dialogSweepRunning) return
|
|
@@ -626,8 +618,8 @@ setInterval(async () => {
|
|
|
626
618
|
try {
|
|
627
619
|
for (const member of members.values()) {
|
|
628
620
|
if (member.status_effective === 'dead') continue
|
|
629
|
-
try { await
|
|
630
|
-
log(`
|
|
621
|
+
try { await passKnownSeatApproval(member) } catch (error) {
|
|
622
|
+
log(`SEAT_APPROVAL_SWEEP_FAILED: ${member.name} ${error.message}`)
|
|
631
623
|
}
|
|
632
624
|
}
|
|
633
625
|
} finally {
|