thinkpool-pair 0.7.102 → 0.7.104
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/bridge.mjs +25 -1
- package/package.json +1 -1
- package/service.mjs +18 -5
package/bridge.mjs
CHANGED
|
@@ -1433,6 +1433,26 @@ function drainPending(s) {
|
|
|
1433
1433
|
s.pending.clear()
|
|
1434
1434
|
}
|
|
1435
1435
|
|
|
1436
|
+
// Switching a terminal to bypassPermissions must RETROACTIVELY clear the cards the
|
|
1437
|
+
// current turn already raised — setPermissionMode is a streaming control request that
|
|
1438
|
+
// only applies going forward, so without this a user who flips to bypass mid-turn keeps
|
|
1439
|
+
// staring at "the agent wants to run X" cards on a terminal that's supposedly hands-off
|
|
1440
|
+
// (the 2026-06-30 "AGENT STILL ASKING PERMISSIONS" report — incl. subagent/Explore tool
|
|
1441
|
+
// calls whose requestPermission was invoked before the toggle). bypass = allow-all, so
|
|
1442
|
+
// resolving every pending card as 'allow' is exactly what the new mode means.
|
|
1443
|
+
function allowPending(s) {
|
|
1444
|
+
if (!s?.pending) return
|
|
1445
|
+
for (const [key, p] of s.pending) {
|
|
1446
|
+
// Only auto-clear ordinary tool cards. A pending PLAN approval (risk:'plan') or an
|
|
1447
|
+
// AskUserQuestion (risk:'ask') is a deliberate human gate — flipping to bypass means
|
|
1448
|
+
// "run tools without asking", not "auto-approve my plan / answer my question" — leave
|
|
1449
|
+
// those for the human.
|
|
1450
|
+
if (p?.payload?.risk === 'plan' || p?.payload?.risk === 'ask') continue
|
|
1451
|
+
try { p.resolve('allow') } catch { /* noop */ }
|
|
1452
|
+
s.pending.delete(key)
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1436
1456
|
function endStructured(id) {
|
|
1437
1457
|
if (!id) return
|
|
1438
1458
|
const s = sessions.get(id)
|
|
@@ -1712,7 +1732,11 @@ channel
|
|
|
1712
1732
|
})
|
|
1713
1733
|
.on('broadcast', { event: 'code-mode' }, ({ payload }) => {
|
|
1714
1734
|
const s = payload?.term && sessions.get(payload.term)
|
|
1715
|
-
if (s) {
|
|
1735
|
+
if (s) {
|
|
1736
|
+
s.mode = payload.mode; s.session.setMode(payload.mode) // track for persist/restore
|
|
1737
|
+
// Flipping to bypass = "stop asking, including the cards this turn already raised."
|
|
1738
|
+
if (payload.mode === 'bypassPermissions') allowPending(s)
|
|
1739
|
+
}
|
|
1716
1740
|
})
|
|
1717
1741
|
.on('broadcast', { event: 'code-close' }, ({ payload }) => {
|
|
1718
1742
|
endStructured(payload?.id)
|
package/package.json
CHANGED
package/service.mjs
CHANGED
|
@@ -115,12 +115,25 @@ export function buildArtifact(platform, { room = null, cmdArgs = [], autoUpdate
|
|
|
115
115
|
// retried on the EIO race → a final `print` so the command's exit code reports the
|
|
116
116
|
// real loaded state (installService warns if it ends not-loaded).
|
|
117
117
|
const lbl = label(room), dom = 'gui/$(id -u)'
|
|
118
|
+
const pr = `launchctl print ${dom}/${lbl} >/dev/null 2>&1`
|
|
118
119
|
const post = [
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
// Evict any existing instance so the REWRITTEN plist (new pinned version) is what
|
|
121
|
+
// loads. bootout is async + occasionally sticky: if the old supervised process
|
|
122
|
+
// won't leave the domain, the follow-up bootstrap throws "Bootstrap failed: 5:
|
|
123
|
+
// Input/output error" and we'd SILENTLY keep running the OLD pinned version while
|
|
124
|
+
// reporting success — the 2026-06-30 "Restart & update" stuck-on-0.7.96 bug. So:
|
|
125
|
+
// bootout, wait for it to actually leave, and if it WON'T, SIGKILL the wedged pid
|
|
126
|
+
// then bootout again (KeepAlive can't relaunch a job that's booting out).
|
|
127
|
+
`launchctl bootout ${dom}/${lbl} 2>/dev/null || true`,
|
|
128
|
+
`for i in $(seq 1 25); do ${pr} || break; sleep 0.2; done`,
|
|
129
|
+
`if ${pr}; then p=$(launchctl print ${dom}/${lbl} 2>/dev/null | sed -n 's/.*[^a-z]pid = \\([0-9][0-9]*\\).*/\\1/p' | head -1); [ -n "$p" ] && kill -9 "$p" 2>/dev/null || true; launchctl bootout ${dom}/${lbl} 2>/dev/null || true; for i in $(seq 1 25); do ${pr} || break; sleep 0.2; done; fi`,
|
|
130
|
+
// Clear a stale `disabled` override (from a prior bootout / account-storm cleanup),
|
|
131
|
+
// then load the rewritten plist from disk, retried on the EIO teardown race.
|
|
132
|
+
`launchctl enable ${dom}/${lbl} 2>/dev/null || true`,
|
|
133
|
+
`ok=0; for i in $(seq 1 25); do launchctl bootstrap ${dom} "${file}" 2>/dev/null && { ok=1; break; }; sleep 0.3; done`,
|
|
134
|
+
// Be HONEST about the outcome: if it never loaded, say so + give the exact manual
|
|
135
|
+
// recovery — never let a failed reload masquerade as a successful update.
|
|
136
|
+
`if [ "$ok" != 1 ] && ! ${pr}; then echo " ⚠ thinkpool-pair: launchd reload FAILED — run by hand: launchctl bootout ${dom}/${lbl} ; launchctl bootstrap ${dom} \\"${file}\\"" >&2; fi`,
|
|
124
137
|
].join('; ')
|
|
125
138
|
return { file, content, logDir, post: [post], note: 'Loaded as a LaunchAgent — starts at login, restarts on crash.' }
|
|
126
139
|
}
|