@dmsdc-ai/aigentry-telepty 0.4.4 → 0.4.5
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/CHANGELOG.md +56 -0
- package/daemon.js +41 -2
- package/package.json +6 -4
- package/scripts/postinstall.js +94 -0
- package/src/prompt-symbol-registry.js +34 -4
- package/src/submit-gate.js +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,62 @@ All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.4.5] - 2026-05-26
|
|
8
|
+
|
|
9
|
+
### Fixed — Stale-daemon, restart-recovery, force-bypass, codex matcher (tasks #469 #470 #471 #472)
|
|
10
|
+
|
|
11
|
+
- **#469 — npm postinstall hook restarts a stale daemon (`scripts/postinstall.js`).**
|
|
12
|
+
`npm install -g @dmsdc-ai/aigentry-telepty@X` previously overwrote files but
|
|
13
|
+
never signalled the running `telepty-daemon`, so the daemon kept executing
|
|
14
|
+
the previously-loaded code (observed: PID 3222 ran 22 days through 4
|
|
15
|
+
upgrades). The new postinstall script reads `~/.telepty/daemon-state.json`,
|
|
16
|
+
compares the running daemon's reported version to the just-installed
|
|
17
|
+
`package.json` version, and on mismatch invokes the existing
|
|
18
|
+
`cleanupDaemonProcesses()` primitive plus a detached respawn. Skips on
|
|
19
|
+
`TELEPTY_SKIP_POSTINSTALL=1` and on non-global installs
|
|
20
|
+
(`npm_config_global!=='true'`).
|
|
21
|
+
- **#470 — daemon restart re-bootstraps existing sessions
|
|
22
|
+
(`daemon.js` `runStartupBootstrapRestore()`).** After a daemon restart,
|
|
23
|
+
persisted-and-restored sessions remained `ready:false` indefinitely because
|
|
24
|
+
the bootstrap prompt-symbol probe only fired on owner-WebSocket reconnect.
|
|
25
|
+
On startup, each restored gated session whose `ownerPid` is still alive is
|
|
26
|
+
now actively probed (cmux path) or optimistically marked ready (non-cmux
|
|
27
|
+
path), with the chosen reason recorded for log attribution. Sessions whose
|
|
28
|
+
owner process is dead remain unready, matching the prior unready semantics.
|
|
29
|
+
- **#471 — `force: true` bypasses the bootstrap gate (`daemon.js:1969`).**
|
|
30
|
+
The per-request `force` escape hatch (`cli.js --submit-force`,
|
|
31
|
+
`TELEPTY_SUBMIT_FORCE_DEFAULT=1` from 0.4.4) was parsed correctly but the
|
|
32
|
+
bootstrap gate enqueued it and returned 504 long before the force-bypass
|
|
33
|
+
block at L1998 could run. Surgical 1-line condition edit: gate fires only
|
|
34
|
+
when `!force`. The force-bypass code path is now exercisable as documented.
|
|
35
|
+
- **#472 — codex prompt-symbol matcher normalized across environments
|
|
36
|
+
(`src/prompt-symbol-registry.js`).** On real cmux captures the codex `›`
|
|
37
|
+
glyph tail-renders on the same row as the model-status footer and DECRQM /
|
|
38
|
+
cursor-position-query fragments (`>4;0m>7u`, `0 q`) leak into the screen
|
|
39
|
+
buffer, so the prior strict line-leading scan permanently missed and the
|
|
40
|
+
session stuck at `ready:false`. New tolerant detector: (1) modal-UI
|
|
41
|
+
anti-pattern guard for resume picker, first-run directory-trust prompt and
|
|
42
|
+
generic press-enter-to-continue modals (treated as NOT ready); (2)
|
|
43
|
+
multi-signal match on `"OpenAI Codex (v"` plus `/gpt-[0-9.]+\s+\w+\s+fast/`
|
|
44
|
+
anywhere on the screen; (3) legacy strict line-leading scan preserved as a
|
|
45
|
+
back-compat fallback. `awaitPromptSymbol` now emits a single
|
|
46
|
+
`[bootstrap] <cli> ready via: <reason>` log line on stabilize, paired with
|
|
47
|
+
the #470 optimistic-ready logging for unified debuggability.
|
|
48
|
+
|
|
49
|
+
### Notes — 0.4.5
|
|
50
|
+
|
|
51
|
+
- **Tests** — `npm test` passes 416 / 416 (was 411 / 411 in 0.4.4; +5 new
|
|
52
|
+
cases in `test/release-0.4.5-bugfixes.test.js` covering #469/#470/#471/#472
|
|
53
|
+
including env-resistance regression guard on the existing noforce test).
|
|
54
|
+
- **Snyk Code SAST** — all newly authored or modified JS files
|
|
55
|
+
(`scripts/postinstall.js`, `src/prompt-symbol-registry.js`,
|
|
56
|
+
`src/submit-gate.js`, new `daemon.js` line ranges, and
|
|
57
|
+
`test/release-0.4.5-bugfixes.test.js`) report 0 findings. The 55
|
|
58
|
+
pre-existing repo-wide findings in unchanged code are tracked separately as
|
|
59
|
+
task #474 (security cleanup track) and are not part of this release.
|
|
60
|
+
- **Out of scope, tracked separately** — task #473 (session-ID reuse → stale
|
|
61
|
+
command metadata) is queued for a 0.4.6 dispatch and is not addressed here.
|
|
62
|
+
|
|
7
63
|
## [0.4.4] - 2026-05-25
|
|
8
64
|
|
|
9
65
|
### Added — TELEPTY_SUBMIT_FORCE_DEFAULT env var (task #453)
|
package/daemon.js
CHANGED
|
@@ -6,7 +6,7 @@ const crypto = require('crypto');
|
|
|
6
6
|
const { WebSocketServer } = require('ws');
|
|
7
7
|
const { getConfig } = require('./auth');
|
|
8
8
|
const pkg = require('./package.json');
|
|
9
|
-
const { claimDaemonState, clearDaemonState } = require('./daemon-control');
|
|
9
|
+
const { claimDaemonState, clearDaemonState, isProcessRunning } = require('./daemon-control');
|
|
10
10
|
const { checkEntitlement } = require('./entitlement');
|
|
11
11
|
const terminalBackend = require('./terminal-backend');
|
|
12
12
|
const { FileMailbox } = require('./src/mailbox/index');
|
|
@@ -1966,7 +1966,10 @@ app.post('/api/sessions/:id/submit', async (req, res) => {
|
|
|
1966
1966
|
|
|
1967
1967
|
console.log(`[SUBMIT] Session ${id} (${session.command})${retries > 0 ? `, retries: ${retries}, pre_delay: ${preDelayMs}ms` : ''}${gateOff ? ' [gate=off]' : ''}`);
|
|
1968
1968
|
|
|
1969
|
-
|
|
1969
|
+
// #471 (0.4.5): force=true must bypass the bootstrap gate. Without `!force`
|
|
1970
|
+
// here the per-request escape hatch (cli.js --submit-force) is enqueued and
|
|
1971
|
+
// 504s before the force-bypass block below ever runs.
|
|
1972
|
+
if (!force && isBootstrapGatedSession(session) && (!isBootstrapReady(session) || hasBootstrapBacklog(session) || session.bootstrapDraining)) {
|
|
1970
1973
|
const op = enqueueBootstrapOperation(id, session, {
|
|
1971
1974
|
type: 'submit',
|
|
1972
1975
|
body: { ...(req.body || {}) }
|
|
@@ -2882,8 +2885,44 @@ app.patch('/api/threads/:id', (req, res) => {
|
|
|
2882
2885
|
|
|
2883
2886
|
const server = app.listen(PORT, HOST, () => {
|
|
2884
2887
|
console.log(`🚀 aigentry-telepty daemon listening on http://${HOST}:${PORT}`);
|
|
2888
|
+
runStartupBootstrapRestore();
|
|
2885
2889
|
});
|
|
2886
2890
|
|
|
2891
|
+
// #470 (0.4.5): when the daemon restarts under existing telepty allow workers,
|
|
2892
|
+
// persisted sessions are restored at daemon.js:1244 but bootstrapReady stays
|
|
2893
|
+
// false until the owner WS reconnects — leaving every survivor session stuck
|
|
2894
|
+
// at ready:false indefinitely. Re-probe on startup: for cmux sessions whose
|
|
2895
|
+
// owner PID is still alive, run the WS-independent prompt-symbol probe; for
|
|
2896
|
+
// non-cmux survivors, optimistically mark ready (the underlying CLI is alive
|
|
2897
|
+
// and no probe primitive is available).
|
|
2898
|
+
function runStartupBootstrapRestore() {
|
|
2899
|
+
for (const [id, session] of Object.entries(sessions)) {
|
|
2900
|
+
if (!isBootstrapGatedSession(session) || isBootstrapReady(session)) continue;
|
|
2901
|
+
const ownerPid = Number(session.ownerPid);
|
|
2902
|
+
if (!Number.isInteger(ownerPid) || ownerPid <= 0 || !isProcessRunning(ownerPid)) {
|
|
2903
|
+
continue;
|
|
2904
|
+
}
|
|
2905
|
+
if (session.backend === 'cmux' && session.cmuxWorkspaceId) {
|
|
2906
|
+
submitGate.awaitPromptSymbol(session, { timeoutMs: 5000 })
|
|
2907
|
+
.then((result) => {
|
|
2908
|
+
if (result && result.ready) {
|
|
2909
|
+
markBootstrapReady(id, session, 'startup_restore');
|
|
2910
|
+
} else {
|
|
2911
|
+
markBootstrapReady(id, session, 'startup_owner_alive');
|
|
2912
|
+
console.log(`[BOOTSTRAP] Optimistic ready for ${id} (ownerPid=${ownerPid}, probe=${result?.reason || 'timeout'})`);
|
|
2913
|
+
}
|
|
2914
|
+
})
|
|
2915
|
+
.catch(() => {
|
|
2916
|
+
markBootstrapReady(id, session, 'startup_owner_alive');
|
|
2917
|
+
console.log(`[BOOTSTRAP] Optimistic ready for ${id} (ownerPid=${ownerPid}, probe=error)`);
|
|
2918
|
+
});
|
|
2919
|
+
} else {
|
|
2920
|
+
markBootstrapReady(id, session, 'startup_owner_alive');
|
|
2921
|
+
console.log(`[BOOTSTRAP] Optimistic ready for ${id} (ownerPid=${ownerPid}, backend=${session.backend || 'unknown'})`);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2887
2926
|
// --- Mailbox system initialization ---
|
|
2888
2927
|
const mailbox = new FileMailbox();
|
|
2889
2928
|
const mailboxNotifier = new UnixSocketNotifier({ coalesceMs: 25 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aigentry-telepty",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"main": "daemon.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aigentry-telepty": "install.js",
|
|
@@ -28,14 +28,16 @@
|
|
|
28
28
|
"install.sh",
|
|
29
29
|
"install.ps1",
|
|
30
30
|
"mcp-server/",
|
|
31
|
+
"scripts/postinstall.js",
|
|
31
32
|
"src/",
|
|
32
33
|
"skills/",
|
|
33
34
|
"CHANGELOG.md"
|
|
34
35
|
],
|
|
35
36
|
"scripts": {
|
|
36
|
-
"
|
|
37
|
-
"test
|
|
38
|
-
"test:
|
|
37
|
+
"postinstall": "node scripts/postinstall.js",
|
|
38
|
+
"test": "node --test test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
39
|
+
"test:watch": "node --test --watch test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js",
|
|
40
|
+
"test:ci": "node --test --test-reporter=spec test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.test.js test/telepty-kill.test.js test/idle-ttl.test.js test/telepty-clean-older-than.test.js test/lifecycle-transport-agnostic.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js test/session-state.test.js test/mailbox-lock.test.js test/report-enforcement.test.js test/enforce-report.test.js test/submit-gate.test.js test/prompt-symbol-registry.test.js test/inject-submit-flags.test.js test/inject-submit-force-env.test.js test/host-spec.test.js test/cross-host-inject.test.js test/cross-machine-ssh-routing.test.js test/init.test.js test/win-resolve-executable.test.js test/version-handshake.test.js test/win-kill-process.test.js test/daemon-control-port-owner.test.js test/banner-stderr-jq-safety.test.js test/bridge-supervisor-ipc.test.js test/bridge-j3-shim.test.js test/bridge-e2e.test.js test/release-0.4.5-bugfixes.test.js && git diff --exit-code tests/snippet-protocol/v1/",
|
|
39
41
|
"regen-fixtures": "node scripts/regen-snippet-fixtures.js"
|
|
40
42
|
},
|
|
41
43
|
"keywords": [
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// #469 (0.4.5): npm postinstall hook — restart a stale telepty-daemon after
|
|
5
|
+
// `npm install -g`. Without this, the running daemon keeps executing the
|
|
6
|
+
// previously-loaded code (verified: a daemon ran 22 days through 4 npm
|
|
7
|
+
// upgrades), so user-facing upgrades quietly no-op until they manually kill
|
|
8
|
+
// the daemon. Wires the existing daemon-shutdown primitive into npm's
|
|
9
|
+
// lifecycle; does not add new shutdown logic.
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { spawn, execSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const pkg = require('../package.json');
|
|
17
|
+
|
|
18
|
+
function shouldSkip() {
|
|
19
|
+
if (process.env.TELEPTY_SKIP_POSTINSTALL === '1') {
|
|
20
|
+
return 'TELEPTY_SKIP_POSTINSTALL=1';
|
|
21
|
+
}
|
|
22
|
+
// Only act on global installs. Local `npm install` (CI, dev) must not
|
|
23
|
+
// restart a user's daemon.
|
|
24
|
+
if (process.env.npm_config_global !== 'true') {
|
|
25
|
+
return 'non-global install';
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readDaemonState() {
|
|
31
|
+
const statePath = path.join(os.homedir(), '.telepty', 'daemon-state.json');
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveTeleptyBin() {
|
|
40
|
+
try {
|
|
41
|
+
const cmd = os.platform() === 'win32' ? 'where telepty' : 'which telepty';
|
|
42
|
+
return execSync(cmd, { encoding: 'utf8' }).split('\n')[0].trim() || 'telepty';
|
|
43
|
+
} catch {
|
|
44
|
+
return 'telepty';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
(function main() {
|
|
49
|
+
const skip = shouldSkip();
|
|
50
|
+
if (skip) {
|
|
51
|
+
console.log(`[telepty postinstall] Skipped (${skip}).`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const state = readDaemonState();
|
|
56
|
+
if (!state || !Number.isInteger(state.pid) || state.pid <= 0) {
|
|
57
|
+
console.log('[telepty postinstall] No running daemon detected — nothing to restart.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (state.version === pkg.version) {
|
|
62
|
+
console.log(`[telepty postinstall] Running daemon already at ${pkg.version} (pid ${state.pid}). No restart needed.`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`[telepty postinstall] Detected stale daemon ${state.version || 'unknown'} (pid ${state.pid}); upgrading in-place to ${pkg.version}.`);
|
|
67
|
+
|
|
68
|
+
let stopped = 0;
|
|
69
|
+
try {
|
|
70
|
+
// Lazy require so a malformed install of daemon-control.js doesn't abort
|
|
71
|
+
// postinstall before the skip-check runs.
|
|
72
|
+
const { cleanupDaemonProcesses } = require('../daemon-control');
|
|
73
|
+
const result = cleanupDaemonProcesses();
|
|
74
|
+
stopped = result.stopped.length;
|
|
75
|
+
if (result.failed.length > 0) {
|
|
76
|
+
console.warn(`[telepty postinstall] Could not stop ${result.failed.length} daemon process(es).`);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.warn(`[telepty postinstall] cleanupDaemonProcesses failed: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// launchd/systemd KeepAlive will respawn the daemon automatically on
|
|
83
|
+
// macOS/root-Linux. For other platforms (Windows, non-root Linux) or when
|
|
84
|
+
// the user disabled the service, spawn a fresh detached daemon so upgrades
|
|
85
|
+
// never silently leave the user without one.
|
|
86
|
+
try {
|
|
87
|
+
const bin = resolveTeleptyBin();
|
|
88
|
+
const child = spawn(bin, ['daemon'], { detached: true, stdio: 'ignore' });
|
|
89
|
+
child.unref();
|
|
90
|
+
console.log(`[telepty postinstall] Stopped ${stopped} stale daemon(s); spawned fresh ${pkg.version} daemon.`);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.warn(`[telepty postinstall] Daemon respawn failed: ${err.message} (launchd/systemd may restart it automatically).`);
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
@@ -33,19 +33,49 @@ const ENTRIES = {
|
|
|
33
33
|
return { found: false };
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
|
-
// codex
|
|
37
|
-
//
|
|
36
|
+
// #472 (0.4.5): codex previously matched on a strict line-leading "^ › "
|
|
37
|
+
// shape; on real cmux captures the '›' tail-renders on the same row as the
|
|
38
|
+
// model-status footer and DECRQM/cursor-pos fragments leak in, so that
|
|
39
|
+
// strict matcher misses. Multi-signal tolerant matcher: picker anti-pattern
|
|
40
|
+
// first (resume-picker UI must NOT be considered ready), then a tolerant
|
|
41
|
+
// (a + b) signal pair, then the legacy strict scan as a back-compat
|
|
42
|
+
// fallback. Reason field surfaces which signal fired for log-attribution.
|
|
38
43
|
codex: {
|
|
39
44
|
symbol: '›',
|
|
40
45
|
byteSeq: Buffer.from([0xE2, 0x80, 0xBA]),
|
|
41
46
|
detect(screen) {
|
|
42
|
-
const
|
|
47
|
+
const text = String(screen == null ? '' : screen);
|
|
48
|
+
|
|
49
|
+
// Step 1: modal-UI anti-pattern. Resume picker, first-run directory
|
|
50
|
+
// trust prompt, and generic "Press enter to continue" modals are all
|
|
51
|
+
// pre-prompt UIs where Enter would not submit a user message. Treat
|
|
52
|
+
// any of them as NOT ready.
|
|
53
|
+
if (
|
|
54
|
+
/Resume a previous session/.test(text) ||
|
|
55
|
+
/^Filter:/m.test(text) ||
|
|
56
|
+
/Do you trust the contents/i.test(text) ||
|
|
57
|
+
/Press enter to continue/i.test(text)
|
|
58
|
+
) {
|
|
59
|
+
return { found: false, reason: 'codex_modal_ui' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Step 2: multi-signal tolerant. The codex boot box contains
|
|
63
|
+
// "OpenAI Codex (v<version>)" and the status row contains
|
|
64
|
+
// "gpt-<ver> <profile> fast". Both present anywhere on the captured
|
|
65
|
+
// screen → ready, regardless of where the literal '›' rendered.
|
|
66
|
+
if (/OpenAI Codex \(v/.test(text) && /gpt-[0-9.]+\s+\w+\s+fast/.test(text)) {
|
|
67
|
+
return { found: true, reason: 'codex_multi_signal' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Step 3: legacy strict line-leading scan — preserved for back-compat
|
|
71
|
+
// on clean cmux captures where the original matcher already worked.
|
|
72
|
+
const lines = text.split('\n');
|
|
43
73
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
44
74
|
const line = lines[i];
|
|
45
75
|
if (!/^ › /.test(line)) continue;
|
|
46
76
|
const footer = (lines[i + 1] || '') + '\n' + (lines[i + 2] || '');
|
|
47
77
|
if (/gpt-\d/.test(footer)) {
|
|
48
|
-
return { found: true, line_index: i, col: 2 };
|
|
78
|
+
return { found: true, line_index: i, col: 2, reason: 'codex_strict_line' };
|
|
49
79
|
}
|
|
50
80
|
}
|
|
51
81
|
return { found: false };
|
package/src/submit-gate.js
CHANGED
|
@@ -227,7 +227,13 @@ async function awaitPromptSymbol(session, opts = {}) {
|
|
|
227
227
|
if (lastSeenAt === null) {
|
|
228
228
|
lastSeenAt = now();
|
|
229
229
|
} else if (now() - lastSeenAt >= stabilityMs) {
|
|
230
|
-
|
|
230
|
+
// #472 (0.4.5): tag the success reason for debuggability — pairs
|
|
231
|
+
// with daemon.js startup-restore optimistic-ready logging so we
|
|
232
|
+
// can attribute every bootstrap_ready flip to a concrete signal.
|
|
233
|
+
if (match.reason && typeof console !== 'undefined' && console.log) {
|
|
234
|
+
console.log(`[bootstrap] ${session.command} ready via: ${match.reason}`);
|
|
235
|
+
}
|
|
236
|
+
return { ready: true, last_seen_at: lastSeenAt, waited_ms: now() - start, reason: match.reason };
|
|
231
237
|
}
|
|
232
238
|
} else {
|
|
233
239
|
// symbol disappeared — reset the stability streak
|