aibroker 0.54.0 → 0.55.1
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.md +3 -0
- package/dist/a2a/agentish-extension.d.ts +111 -0
- package/dist/a2a/agentish-extension.d.ts.map +1 -0
- package/dist/a2a/agentish-extension.js +124 -0
- package/dist/a2a/agentish-extension.js.map +1 -0
- package/dist/a2a/client.d.ts +45 -0
- package/dist/a2a/client.d.ts.map +1 -0
- package/dist/a2a/client.js +112 -0
- package/dist/a2a/client.js.map +1 -0
- package/dist/a2a/exposure.d.ts +24 -0
- package/dist/a2a/exposure.d.ts.map +1 -0
- package/dist/a2a/exposure.js +70 -0
- package/dist/a2a/exposure.js.map +1 -0
- package/dist/a2a/schema/types.d.ts +153 -0
- package/dist/a2a/schema/types.d.ts.map +1 -0
- package/dist/a2a/schema/types.js +28 -0
- package/dist/a2a/schema/types.js.map +1 -0
- package/dist/a2a/schema/validate.d.ts +27 -0
- package/dist/a2a/schema/validate.d.ts.map +1 -0
- package/dist/a2a/schema/validate.js +190 -0
- package/dist/a2a/schema/validate.js.map +1 -0
- package/dist/a2a/server.d.ts +73 -0
- package/dist/a2a/server.d.ts.map +1 -0
- package/dist/a2a/server.js +325 -0
- package/dist/a2a/server.js.map +1 -0
- package/dist/a2a/tasks.d.ts +81 -0
- package/dist/a2a/tasks.d.ts.map +1 -0
- package/dist/a2a/tasks.js +148 -0
- package/dist/a2a/tasks.js.map +1 -0
- package/dist/agentish/index.d.ts +80 -0
- package/dist/agentish/index.d.ts.map +1 -0
- package/dist/agentish/index.js +263 -0
- package/dist/agentish/index.js.map +1 -0
- package/dist/daemon/a2a-cli.d.ts +46 -0
- package/dist/daemon/a2a-cli.d.ts.map +1 -0
- package/dist/daemon/a2a-cli.js +385 -0
- package/dist/daemon/a2a-cli.js.map +1 -0
- package/dist/daemon/agentish-cli.d.ts +17 -0
- package/dist/daemon/agentish-cli.d.ts.map +1 -0
- package/dist/daemon/agentish-cli.js +137 -0
- package/dist/daemon/agentish-cli.js.map +1 -0
- package/dist/daemon/agentish-stats.d.ts +65 -0
- package/dist/daemon/agentish-stats.d.ts.map +1 -0
- package/dist/daemon/agentish-stats.js +125 -0
- package/dist/daemon/agentish-stats.js.map +1 -0
- package/dist/daemon/agentish.d.ts +13 -0
- package/dist/daemon/agentish.d.ts.map +1 -0
- package/dist/daemon/agentish.js +13 -0
- package/dist/daemon/agentish.js.map +1 -0
- package/dist/daemon/cli.js +12 -1
- package/dist/daemon/cli.js.map +1 -1
- package/dist/daemon/core-handlers.d.ts.map +1 -1
- package/dist/daemon/core-handlers.js +38 -0
- package/dist/daemon/core-handlers.js.map +1 -1
- package/dist/daemon/manage.d.ts +62 -0
- package/dist/daemon/manage.d.ts.map +1 -1
- package/dist/daemon/manage.js +198 -4
- package/dist/daemon/manage.js.map +1 -1
- package/dist/daemon/screenshot.d.ts +13 -0
- package/dist/daemon/screenshot.d.ts.map +1 -1
- package/dist/daemon/screenshot.js +60 -29
- package/dist/daemon/screenshot.js.map +1 -1
- package/dist/daemon/todoist-webhook.d.ts +25 -2
- package/dist/daemon/todoist-webhook.d.ts.map +1 -1
- package/dist/daemon/todoist-webhook.js +136 -17
- package/dist/daemon/todoist-webhook.js.map +1 -1
- package/dist/mcp/index.js +41 -1
- package/dist/mcp/index.js.map +1 -1
- package/package.json +1 -1
package/dist/daemon/manage.d.ts
CHANGED
|
@@ -19,6 +19,18 @@
|
|
|
19
19
|
* carries one-shot instructions from the operator into the next arming, and it
|
|
20
20
|
* says what it did. Everything requiring judgement stays with the person.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Whether the pane is showing a Claude Code permission/approval prompt.
|
|
24
|
+
*
|
|
25
|
+
* Kept deliberately narrow. A busy pane and a stuck one both look the same
|
|
26
|
+
* to a change-hash — nothing moves — so telling them apart has to come from
|
|
27
|
+
* the actual words on screen, and the words checked here are ones a genuine
|
|
28
|
+
* permission dialog produces and very little else does. A background-agent
|
|
29
|
+
* status line ("Waiting for 1 background agent to finish") or a bare shell
|
|
30
|
+
* prompt must both read as false here, or every long tool call starts
|
|
31
|
+
* looking like a stuck session and the manager stops arming real work.
|
|
32
|
+
*/
|
|
33
|
+
export declare function pendingPrompt(content: string): boolean;
|
|
22
34
|
export interface ManagedSession {
|
|
23
35
|
sessionId: string;
|
|
24
36
|
/** Human name at the time of starting, for logs only — sessions get renamed. */
|
|
@@ -56,6 +68,35 @@ export interface ManagedSession {
|
|
|
56
68
|
clearAfterHandover?: boolean;
|
|
57
69
|
/** Consecutive armings typed but never seen to land. Bounded — see ARM_ATTEMPTS. */
|
|
58
70
|
armFails?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Consecutive arm-failure CAPS crossed, never reset by hitting the cap.
|
|
73
|
+
*
|
|
74
|
+
* `armFails` resets to 0 the instant it reaches ARM_ATTEMPTS, which is right
|
|
75
|
+
* for the backoff mechanics but wrong for detecting a genuinely stuck
|
|
76
|
+
* session: a content-agnostic "are we stuck" check keyed off `armFails`
|
|
77
|
+
* never sees anything but 0..ARM_ATTEMPTS-1. This survives that reset, so
|
|
78
|
+
* `blockedReason` has something durable to look at.
|
|
79
|
+
*/
|
|
80
|
+
armFailStreak?: number;
|
|
81
|
+
/**
|
|
82
|
+
* When arming first crossed ARM_ATTEMPTS and the manager gave up typing at
|
|
83
|
+
* this session. Distinct from `blockedSince` (when the operator was told):
|
|
84
|
+
* this is set the moment the streak crosses the cap, `blockedSince` only
|
|
85
|
+
* once the pane has also gone static long enough to confirm it.
|
|
86
|
+
*/
|
|
87
|
+
stuckSince?: number;
|
|
88
|
+
/**
|
|
89
|
+
* When this session was first seen BLOCKED — waiting on a permission
|
|
90
|
+
* prompt or similar, not merely idle. Kept across ticks so the operator
|
|
91
|
+
* can be told how long it has actually been stuck, not just that it is.
|
|
92
|
+
* Cleared the moment it stops looking blocked, so a later block re-alerts
|
|
93
|
+
* fresh rather than inheriting a stale duration.
|
|
94
|
+
*/
|
|
95
|
+
blockedSince?: number;
|
|
96
|
+
/** When the operator was last alerted about this BLOCKED session — the
|
|
97
|
+
* dedup clock. See BLOCKED_REALERT_MS: without this, every 20-second tick
|
|
98
|
+
* the pane still shows the prompt would be its own alert. */
|
|
99
|
+
blockedAlertedAt?: number;
|
|
59
100
|
/** When a handover was last obtained, so it is not demanded every tick. */
|
|
60
101
|
handoverDoneAt?: number;
|
|
61
102
|
/** The context reading when it was obtained, so "how much work since" is
|
|
@@ -303,6 +344,27 @@ export declare function screenLease(m: ManagedSession, now: number): {
|
|
|
303
344
|
until: number;
|
|
304
345
|
since: number;
|
|
305
346
|
} | null;
|
|
347
|
+
/**
|
|
348
|
+
* Why a managed session should be treated as BLOCKED — waiting on a human
|
|
349
|
+
* decision arming cannot supply — rather than merely idle or slow.
|
|
350
|
+
*
|
|
351
|
+
* Two paths, checked in this order because they answer different questions.
|
|
352
|
+
* The fast path reads the words on screen and needs nothing else: a
|
|
353
|
+
* permission prompt IS a permission prompt the instant it appears, however
|
|
354
|
+
* recently the pane last changed. The slow path exists for what the fast one
|
|
355
|
+
* cannot see — a native macOS dialog steals keyboard focus and leaves no
|
|
356
|
+
* trace in the terminal's own buffer — so it infers the same state from
|
|
357
|
+
* arming's own repeated failure instead: ARM_ATTEMPTS have already come back
|
|
358
|
+
* unconfirmed AND the screen has still not moved since. Neither signal alone
|
|
359
|
+
* would be trustworthy (a slow turn fails to arm too; a quiet pane is often
|
|
360
|
+
* just a quiet pane) but together they are the two ways this actually
|
|
361
|
+
* happens.
|
|
362
|
+
*
|
|
363
|
+
* Returns the reason string rather than a boolean because the caller puts it
|
|
364
|
+
* straight into the operator alert — computing it a second time to describe
|
|
365
|
+
* what the first computation found is how the two descriptions drift apart.
|
|
366
|
+
*/
|
|
367
|
+
export declare function blockedReason(m: Pick<ManagedSession, "armFailStreak" | "stuckSince" | "lastChangeAt">, content: string, now: number): string | null;
|
|
306
368
|
export declare function startManagerLoop(): void;
|
|
307
369
|
export interface ManageResult {
|
|
308
370
|
ok: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manage.d.ts","sourceRoot":"","sources":["../../src/daemon/manage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"manage.d.ts","sourceRoot":"","sources":["../../src/daemon/manage.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon/manage.js
CHANGED
|
@@ -29,8 +29,10 @@ import { typeIntoSession } from "../transport/sync-facade.js";
|
|
|
29
29
|
import { discoverLiveSessions } from "../core/session-discovery.js";
|
|
30
30
|
import { hasPailotClients } from "../adapters/pailot/gateway.js";
|
|
31
31
|
import { getAibpBridge } from "../core/state.js";
|
|
32
|
+
import { captureSessionPng } from "./screenshot.js";
|
|
32
33
|
import { listDialogs, answerDialog } from "./dialogs.js";
|
|
33
34
|
import { readControls, grantUntil, returnToOperator, verdict as screenVerdict, describeControls, screenGrantedClause, } from "./pointer-controls.js";
|
|
35
|
+
import { AG2_SPEC } from "./agentish.js";
|
|
34
36
|
const STATE_FILE = join(homedir(), ".aibroker", "managers.json");
|
|
35
37
|
/** How often every manager is looked at. Cheap: one content read per managed session. */
|
|
36
38
|
const TICK_MS = 20_000;
|
|
@@ -108,6 +110,32 @@ const HANDOVER_GRACE_MS = 6 * 60_000;
|
|
|
108
110
|
* leans towards firing.
|
|
109
111
|
*/
|
|
110
112
|
const STUCK_AFTER_MS = 12 * 60_000;
|
|
113
|
+
/**
|
|
114
|
+
* How long a session may sit with arming failing before it is treated as
|
|
115
|
+
* BLOCKED rather than merely slow to confirm.
|
|
116
|
+
*
|
|
117
|
+
* This is the content-agnostic path, for the case the fast prompt-text match
|
|
118
|
+
* cannot see: a native macOS dialog (a permission or security escalation
|
|
119
|
+
* that even bypass mode raises) steals keyboard focus without leaving any
|
|
120
|
+
* trace in the pane it sits in front of. Nothing distinguishes that from an
|
|
121
|
+
* ordinary slow turn except that arming keeps failing — so this fires only
|
|
122
|
+
* once BOTH are true: ARM_ATTEMPTS have already failed, and the screen still
|
|
123
|
+
* has not moved since. Three minutes, not twelve, because a session already
|
|
124
|
+
* failing to arm is already a worse sign than a merely quiet one.
|
|
125
|
+
*/
|
|
126
|
+
const BLOCKED_STATIC_MS = 3 * 60_000;
|
|
127
|
+
/**
|
|
128
|
+
* How long to wait before re-alerting the operator about a BLOCKED session.
|
|
129
|
+
*
|
|
130
|
+
* The overnight failure this exists for produced the same log line every
|
|
131
|
+
* tick for four hours with nobody told. The opposite fault — a buzz every
|
|
132
|
+
* 20 seconds for as long as the dialog sits there — is nearly as bad, since
|
|
133
|
+
* an alert that repeats every tick teaches its reader to swipe it away
|
|
134
|
+
* unread. Fifteen minutes says it once, then again if it is still true a
|
|
135
|
+
* while later, which is the shape a "you need to look at this" message
|
|
136
|
+
* should have.
|
|
137
|
+
*/
|
|
138
|
+
const BLOCKED_REALERT_MS = 15 * 60_000;
|
|
111
139
|
/**
|
|
112
140
|
* The startup banner, which is the pane's only POSITIVE evidence of a clear.
|
|
113
141
|
*
|
|
@@ -190,6 +218,23 @@ const OUT_OF_GOAL = [
|
|
|
190
218
|
/goal not achieved/i,
|
|
191
219
|
/could not achieve the goal/i,
|
|
192
220
|
];
|
|
221
|
+
/**
|
|
222
|
+
* Whether the pane is showing a Claude Code permission/approval prompt.
|
|
223
|
+
*
|
|
224
|
+
* Kept deliberately narrow. A busy pane and a stuck one both look the same
|
|
225
|
+
* to a change-hash — nothing moves — so telling them apart has to come from
|
|
226
|
+
* the actual words on screen, and the words checked here are ones a genuine
|
|
227
|
+
* permission dialog produces and very little else does. A background-agent
|
|
228
|
+
* status line ("Waiting for 1 background agent to finish") or a bare shell
|
|
229
|
+
* prompt must both read as false here, or every long tool call starts
|
|
230
|
+
* looking like a stuck session and the manager stops arming real work.
|
|
231
|
+
*/
|
|
232
|
+
export function pendingPrompt(content) {
|
|
233
|
+
return (/Do you want to proceed\?/i.test(content) ||
|
|
234
|
+
/tell Claude what to do differently/i.test(content) ||
|
|
235
|
+
/❯\s*\d+\.\s*(Yes|No)\b/.test(content) ||
|
|
236
|
+
/Yes,\s*and don'?t ask again/i.test(content));
|
|
237
|
+
}
|
|
193
238
|
/**
|
|
194
239
|
* A duration nobody can print nonsense from.
|
|
195
240
|
*
|
|
@@ -552,6 +597,36 @@ function reportToOperator(text) {
|
|
|
552
597
|
return;
|
|
553
598
|
alertOperator(text);
|
|
554
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* Alert the operator about a BLOCKED session, with a screenshot if one can
|
|
602
|
+
* be had.
|
|
603
|
+
*
|
|
604
|
+
* A picture of the actual prompt is worth having — "blocked" alone does not
|
|
605
|
+
* say whether it is a two-line yes/no or a wall of text — but capturing one
|
|
606
|
+
* means stealing the operator's window focus for a moment (see
|
|
607
|
+
* captureSessionPng), so it is only attempted at all when a phone is
|
|
608
|
+
* actually there to receive it. Any failure along the way — no client
|
|
609
|
+
* connected, the window could not be found, the capture itself errored —
|
|
610
|
+
* falls back to the plain text alert rather than saying nothing.
|
|
611
|
+
*/
|
|
612
|
+
async function alertBlocked(m, caption) {
|
|
613
|
+
if (hasPailotClients()) {
|
|
614
|
+
try {
|
|
615
|
+
const shot = await captureSessionPng(m.sessionId);
|
|
616
|
+
if (shot) {
|
|
617
|
+
getAibpBridge()?.routeToMobile(m.sessionId, caption, "IMAGE", {
|
|
618
|
+
imageBase64: shot.buffer.toString("base64"),
|
|
619
|
+
mimeType: shot.mime,
|
|
620
|
+
});
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
catch (e) {
|
|
625
|
+
log(`[manage:${m.name}] blocked-alert screenshot failed — ${e.message}`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
alertOperator(caption);
|
|
629
|
+
}
|
|
555
630
|
/**
|
|
556
631
|
* A note that is ALSO worth a buzz on the phone.
|
|
557
632
|
*
|
|
@@ -1232,7 +1307,15 @@ function goalText(m) {
|
|
|
1232
1307
|
const lease = screenLease(m, Date.now());
|
|
1233
1308
|
return lease ? screenGrantedClause(lease.until, !rules) : "";
|
|
1234
1309
|
})();
|
|
1235
|
-
|
|
1310
|
+
// AG2 goes first, ahead of the objective and the standing rules, on every
|
|
1311
|
+
// arming — not once at setup. A managed session works unattended for hours
|
|
1312
|
+
// and a reminder given only at the start does not survive a compaction or a
|
|
1313
|
+
// `/clear`. This is folded into composeGoal's own objective argument, not
|
|
1314
|
+
// spliced in afterwards, so the ordering it produces (task, then rules,
|
|
1315
|
+
// then right-now context) still holds with AG2 simply riding in front of
|
|
1316
|
+
// the task. What a managed session sends the operator, and what it commits
|
|
1317
|
+
// to git, stays prose either way — AG2 is only how agents talk to agents.
|
|
1318
|
+
return composeGoal(`${AG2_SPEC} ${m.objective}`, rules, hands, extra, standingRulesSource());
|
|
1236
1319
|
}
|
|
1237
1320
|
/**
|
|
1238
1321
|
* Did it land? Look for the goal's own words in the transcript.
|
|
@@ -1329,6 +1412,38 @@ async function arm(m, reason) {
|
|
|
1329
1412
|
notify(m, `typed but the objective's own words never appeared — treating as NOT armed (${reason})`);
|
|
1330
1413
|
return false;
|
|
1331
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Why a managed session should be treated as BLOCKED — waiting on a human
|
|
1417
|
+
* decision arming cannot supply — rather than merely idle or slow.
|
|
1418
|
+
*
|
|
1419
|
+
* Two paths, checked in this order because they answer different questions.
|
|
1420
|
+
* The fast path reads the words on screen and needs nothing else: a
|
|
1421
|
+
* permission prompt IS a permission prompt the instant it appears, however
|
|
1422
|
+
* recently the pane last changed. The slow path exists for what the fast one
|
|
1423
|
+
* cannot see — a native macOS dialog steals keyboard focus and leaves no
|
|
1424
|
+
* trace in the terminal's own buffer — so it infers the same state from
|
|
1425
|
+
* arming's own repeated failure instead: ARM_ATTEMPTS have already come back
|
|
1426
|
+
* unconfirmed AND the screen has still not moved since. Neither signal alone
|
|
1427
|
+
* would be trustworthy (a slow turn fails to arm too; a quiet pane is often
|
|
1428
|
+
* just a quiet pane) but together they are the two ways this actually
|
|
1429
|
+
* happens.
|
|
1430
|
+
*
|
|
1431
|
+
* Returns the reason string rather than a boolean because the caller puts it
|
|
1432
|
+
* straight into the operator alert — computing it a second time to describe
|
|
1433
|
+
* what the first computation found is how the two descriptions drift apart.
|
|
1434
|
+
*/
|
|
1435
|
+
export function blockedReason(m, content, now) {
|
|
1436
|
+
if (pendingPrompt(content))
|
|
1437
|
+
return "permission prompt";
|
|
1438
|
+
const quietFor = now - (m.lastChangeAt ?? now);
|
|
1439
|
+
// Stuck = we tried to arm repeatedly (streak survived the cap reset) AND,
|
|
1440
|
+
// now that we have stopped typing, the pane has gone static. The static
|
|
1441
|
+
// check matters because our own arming used to keep the pane changing.
|
|
1442
|
+
if (m.stuckSince !== undefined && quietFor >= BLOCKED_STATIC_MS) {
|
|
1443
|
+
return `stuck ${Math.round((now - m.stuckSince) / 60_000)}m, input not accepted`;
|
|
1444
|
+
}
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1332
1447
|
function reasonToArm(m, content, now) {
|
|
1333
1448
|
if (m.paused)
|
|
1334
1449
|
return null;
|
|
@@ -1459,8 +1574,16 @@ async function tick() {
|
|
|
1459
1574
|
* turn, costing nothing; against a dead one it is the whole recovery. The
|
|
1460
1575
|
* asymmetry is the argument — and it is why this fires on a signal as crude
|
|
1461
1576
|
* as "nothing changed", which no more precise test would improve on.
|
|
1577
|
+
*
|
|
1578
|
+
* Excluded once `stuckSince` is set: that means arming has already been
|
|
1579
|
+
* tried and failed enough to be classed as stuck, and this backstop's
|
|
1580
|
+
* response — type into it, and count the typing itself as a change — is
|
|
1581
|
+
* exactly the loop the blocked-alert exists to stop. Firing here would
|
|
1582
|
+
* reset `lastChangeAt` right when the blocked check needs it to keep
|
|
1583
|
+
* climbing, silently clearing the confirmed-blocked state without the
|
|
1584
|
+
* session having moved at all.
|
|
1462
1585
|
*/
|
|
1463
|
-
if (!m.paused && !m.handoverAskedAt && now - m.lastChangeAt > STUCK_AFTER_MS) {
|
|
1586
|
+
if (!m.paused && !m.handoverAskedAt && !m.stuckSince && now - m.lastChangeAt > STUCK_AFTER_MS) {
|
|
1464
1587
|
notify(m, `nothing has moved on that screen for ${minutesSince(m.lastChangeAt, now)} — arming, because a managed session is never meant to be still this long`);
|
|
1465
1588
|
// Counted as a change so a session that stays stuck is not re-armed every
|
|
1466
1589
|
// tick: this is a recovery, and a recovery that repeats is a loop.
|
|
@@ -1836,6 +1959,65 @@ async function tick() {
|
|
|
1836
1959
|
continue;
|
|
1837
1960
|
}
|
|
1838
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* BLOCKED: waiting on a human decision that arming cannot supply.
|
|
1964
|
+
*
|
|
1965
|
+
* This is the fix for a session that hit a permission or security
|
|
1966
|
+
* prompt overnight — even bypass mode escalates some of these — and sat
|
|
1967
|
+
* answered by nobody for four hours while the manager kept typing the
|
|
1968
|
+
* goal at it and logging "will retry". Typing MORE text at a modal does
|
|
1969
|
+
* not help; at best it queues, at worst it lands somewhere the operator
|
|
1970
|
+
* never sees. So this is checked BEFORE any arm attempt below, and it
|
|
1971
|
+
* never answers the prompt itself — only says, loudly, that it exists.
|
|
1972
|
+
*
|
|
1973
|
+
* Gated on holding a screen grant (`lease`, computed above) because
|
|
1974
|
+
* confirming a screenshot requires bringing that iTerm2 window to the
|
|
1975
|
+
* front via AppleScript, which steals whatever window currently has the
|
|
1976
|
+
* operator's focus. Without a grant that is not this manager's window to
|
|
1977
|
+
* take, so a session in that state gets no special handling here and
|
|
1978
|
+
* simply keeps going through the ordinary arm path below — worse than
|
|
1979
|
+
* ideal, but never worse than before this existed.
|
|
1980
|
+
*/
|
|
1981
|
+
if (!m.paused && lease) {
|
|
1982
|
+
const blocked = blockedReason(m, content, now);
|
|
1983
|
+
if (blocked) {
|
|
1984
|
+
if (!m.blockedAlertedAt || now - m.blockedAlertedAt >= BLOCKED_REALERT_MS) {
|
|
1985
|
+
const quietMin = Math.round(Math.max(0, now - m.lastChangeAt) / 60_000);
|
|
1986
|
+
m.blockedSince ??= now;
|
|
1987
|
+
m.blockedAlertedAt = now;
|
|
1988
|
+
const caption = `⚠️ ${m.name} looks blocked (${blocked}) — no change for ${quietMin}m. ` +
|
|
1989
|
+
`Needs your approve/decline; I will not answer it.`;
|
|
1990
|
+
void alertBlocked(m, caption);
|
|
1991
|
+
note(m, `blocked (${blocked}) — alerted the operator, not arming`);
|
|
1992
|
+
dirty = true;
|
|
1993
|
+
}
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
// Recovery. Gated on having ALREADY confirmed-and-alerted a block —
|
|
1997
|
+
// not merely on armFailStreak/stuckSince being set — because quietFor
|
|
1998
|
+
// is, by construction, still under BLOCKED_STATIC_MS for the entire
|
|
1999
|
+
// ramp from "stuck" to "confirmed blocked": clearing on that alone
|
|
2000
|
+
// would wipe stuckSince on the very next tick, every time, and the
|
|
2001
|
+
// alert below would never get a chance to fire. Once blockedSince
|
|
2002
|
+
// exists, though, BLOCKED_STATIC_MS of true silence already elapsed,
|
|
2003
|
+
// so a later drop in quietFor is unambiguously the session moving on
|
|
2004
|
+
// its own, not an artifact of this manager's own last typed attempt.
|
|
2005
|
+
if (m.blockedSince !== undefined || m.blockedAlertedAt !== undefined) {
|
|
2006
|
+
m.armFailStreak = 0;
|
|
2007
|
+
delete m.stuckSince;
|
|
2008
|
+
delete m.blockedSince;
|
|
2009
|
+
delete m.blockedAlertedAt;
|
|
2010
|
+
dirty = true;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
// Once arming has failed enough to be considered stuck, stop typing into
|
|
2014
|
+
// the session — the only way BLOCKED_STATIC_MS of true quiet is ever
|
|
2015
|
+
// reached is if this manager is not the one refreshing the pane every
|
|
2016
|
+
// REARM_COOLDOWN_MS. This is what actually fixes the overnight bug: the
|
|
2017
|
+
// old code kept re-arming (and re-spamming) forever because each retype
|
|
2018
|
+
// reset lastChangeAt and the freeze detector never got a quiet window.
|
|
2019
|
+
if (m.stuckSince !== undefined)
|
|
2020
|
+
continue;
|
|
1839
2021
|
if (now - m.lastRearmAt < REARM_COOLDOWN_MS)
|
|
1840
2022
|
continue;
|
|
1841
2023
|
const reason = reasonToArm(m, content, now);
|
|
@@ -1862,14 +2044,26 @@ async function tick() {
|
|
|
1862
2044
|
dirty = true;
|
|
1863
2045
|
if (armed) {
|
|
1864
2046
|
m.armFails = 0;
|
|
2047
|
+
m.armFailStreak = 0;
|
|
2048
|
+
delete m.stuckSince;
|
|
2049
|
+
delete m.blockedSince;
|
|
2050
|
+
delete m.blockedAlertedAt;
|
|
1865
2051
|
continue;
|
|
1866
2052
|
}
|
|
1867
2053
|
m.armFails = (m.armFails ?? 0) + 1;
|
|
2054
|
+
m.armFailStreak = (m.armFailStreak ?? 0) + 1;
|
|
1868
2055
|
if (m.armFails >= ARM_ATTEMPTS) {
|
|
1869
2056
|
m.armFails = 0;
|
|
1870
2057
|
m.lastRearmAt = now;
|
|
1871
|
-
|
|
1872
|
-
|
|
2058
|
+
m.stuckSince ??= now;
|
|
2059
|
+
// No operator push here. Pushing on every cap is the 144-alerts-overnight
|
|
2060
|
+
// bug: this fires again every ~2-4 minutes for as long as arming keeps
|
|
2061
|
+
// failing, and it is content-agnostic — it cannot tell "mid-turn, will
|
|
2062
|
+
// clear itself" from "stuck on a modal for hours". The blocked check
|
|
2063
|
+
// above owns the operator alert now: it has `armFailStreak`/`stuckSince`
|
|
2064
|
+
// (which survive this reset) plus a static-pane confirmation, and its
|
|
2065
|
+
// own BLOCKED_REALERT_MS dedup. This stays log-only.
|
|
2066
|
+
note(m, `arming keeps failing (streak ${m.armFailStreak}) — holding off; the blocked check owns the operator alert now`);
|
|
1873
2067
|
}
|
|
1874
2068
|
}
|
|
1875
2069
|
if (dirty)
|