@dmsdc-ai/aigentry-telepty 0.5.0 → 0.5.2
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 +33 -0
- package/cli.js +27 -122
- package/daemon.js +326 -432
- package/package.json +9 -4
- package/src/cli/session-view.js +100 -0
- package/src/lifecycle.js +114 -1
- package/src/protocol/http-auth.js +71 -0
- package/src/session-store/persistence.js +88 -0
- package/src/submit-gate.js +157 -0
- package/src/transport/peer-relay.js +51 -0
- package/src/transport/websocket.js +287 -0
- package/terminal-backend.js +339 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@ All notable changes to `@dmsdc-ai/aigentry-telepty` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.2] - 2026-06-06
|
|
8
|
+
|
|
9
|
+
### Fixed — submit handshake confirmation (#507-B / #508)
|
|
10
|
+
|
|
11
|
+
- **`--submit` Enter sometimes did not register in a CLI's TUI** (the recurring
|
|
12
|
+
"Enter 안눌림" bug). `inject --submit` wrote the carriage return but did not
|
|
13
|
+
confirm the target actually consumed it, so under timing pressure the submit
|
|
14
|
+
could be dropped and the injected prompt left sitting unsubmitted. **Fix:** a
|
|
15
|
+
submit-gate handshake in `src/submit-gate.js` confirms the submit landed, with
|
|
16
|
+
`cli.js` / `daemon.js` wiring the gate into the inject path. Landed on `main`
|
|
17
|
+
at commit `2a21265`. This release ships that already-tested fix (npm 0.5.1
|
|
18
|
+
still served the pre-fix code; the running daemon must be restarted separately
|
|
19
|
+
to pick it up). (telepty#512)
|
|
20
|
+
|
|
21
|
+
## [0.5.1] - 2026-05-30
|
|
22
|
+
|
|
23
|
+
### Fixed — daemon never started (CRITICAL, regresses 0.5.0)
|
|
24
|
+
|
|
25
|
+
- **The daemon failed to start for all users on 0.5.0.** `daemon.js` guarded
|
|
26
|
+
`app.listen()` behind `require.main === module` (added in 0.5.0 for test
|
|
27
|
+
isolation so `require('./daemon.js')` is side-effect-free). But the production
|
|
28
|
+
CLI launches the daemon via `require('./daemon.js')` (`telepty daemon`, and the
|
|
29
|
+
auto-start spawns `node cli.js daemon`), so `require.main` is always `cli.js` —
|
|
30
|
+
`app.listen` never ran, the process exited 0 right after `[PERSIST] Restored
|
|
31
|
+
session … (awaiting reconnect)`, and every CLI reported `Daemon restart failed
|
|
32
|
+
after 3 attempts` / `fetch failed`. **Fix:** `cli.js` sets
|
|
33
|
+
`AIGENTRY_TELEPTY_DAEMON_MAIN=1` before requiring `daemon.js`; the guard is now
|
|
34
|
+
`require.main === module || process.env.AIGENTRY_TELEPTY_DAEMON_MAIN === '1'`.
|
|
35
|
+
Tests that `require()` daemon.js without the env stay side-effect-free. (telepty#15)
|
|
36
|
+
- Follow-up (tracked): a daemon-launch integration smoke test — assert the HTTP
|
|
37
|
+
endpoint responds when the daemon is launched via the real CLI path. The unit-test
|
|
38
|
+
guard masked this regression; an integration test would have caught it.
|
|
39
|
+
|
|
7
40
|
## [0.5.0] - 2026-05-30
|
|
8
41
|
|
|
9
42
|
### Changed — Surface-ownership boundary (ADR 2026-05-30)
|
package/cli.js
CHANGED
|
@@ -17,6 +17,13 @@ const { getRuntimeInfo } = require('./runtime-info');
|
|
|
17
17
|
const { formatHostLabel, groupSessionsByHost, pickSessionTarget } = require('./session-routing');
|
|
18
18
|
const { buildSharedContextPrompt, createSharedContextDescriptor, ensureSharedContextFile } = require('./shared-context');
|
|
19
19
|
const { runInteractiveSkillInstaller } = require('./skill-installer');
|
|
20
|
+
const {
|
|
21
|
+
detectTerminalProgram,
|
|
22
|
+
formatSessionTerminal,
|
|
23
|
+
enrichSessionIdle,
|
|
24
|
+
formatSessionStatusWithIdle,
|
|
25
|
+
printSessionInfo
|
|
26
|
+
} = require('./src/cli/session-view');
|
|
20
27
|
const { resolveWindowsExecutable } = require('./src/win-resolve-executable');
|
|
21
28
|
const { decideVersionAction } = require('./src/version-handshake');
|
|
22
29
|
const crossMachine = require('./cross-machine');
|
|
@@ -176,62 +183,6 @@ async function getDaemonMeta(host = REMOTE_HOST) {
|
|
|
176
183
|
}
|
|
177
184
|
}
|
|
178
185
|
|
|
179
|
-
function detectTerminalProgram(env = process.env) {
|
|
180
|
-
const rawTermProgram = typeof env.TERM_PROGRAM === 'string' ? env.TERM_PROGRAM.trim() : '';
|
|
181
|
-
if (rawTermProgram) {
|
|
182
|
-
return rawTermProgram;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (env.TMUX) {
|
|
186
|
-
return 'tmux';
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const term = typeof env.TERM === 'string' ? env.TERM.toLowerCase() : '';
|
|
190
|
-
if (term.includes('kitty')) return 'kitty';
|
|
191
|
-
if (term.includes('ghostty')) return 'ghostty';
|
|
192
|
-
if (term.includes('tmux')) return 'tmux';
|
|
193
|
-
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function formatSessionTerminal(session) {
|
|
198
|
-
const terminal = session.terminal || session.termProgram || null;
|
|
199
|
-
const term = session.term || null;
|
|
200
|
-
if (terminal && term) {
|
|
201
|
-
return `${terminal} (${term})`;
|
|
202
|
-
}
|
|
203
|
-
return terminal || term || 'unknown';
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function formatSessionHealth(session) {
|
|
207
|
-
const status = session.healthStatus || 'UNKNOWN';
|
|
208
|
-
const reason = session.healthReason || null;
|
|
209
|
-
if (reason && reason !== status) {
|
|
210
|
-
return `${status} (${reason})`;
|
|
211
|
-
}
|
|
212
|
-
return status;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function enrichSessionIdle(session, nowMs = Date.now()) {
|
|
216
|
-
const idleSeconds = typeof session.idleSeconds === 'number'
|
|
217
|
-
? session.idleSeconds
|
|
218
|
-
: lifecycle.computeIdleSeconds(session.lastActivityAt, nowMs);
|
|
219
|
-
return {
|
|
220
|
-
...session,
|
|
221
|
-
idleSeconds,
|
|
222
|
-
idle_seconds: idleSeconds
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function formatSessionStatusWithIdle(session) {
|
|
227
|
-
const base = formatSessionHealth(session);
|
|
228
|
-
const idleSeconds = typeof session.idleSeconds === 'number' ? session.idleSeconds : null;
|
|
229
|
-
if (idleSeconds !== null && idleSeconds > 60) {
|
|
230
|
-
return `${base} 💤 idle (${lifecycle.formatIdleDuration(idleSeconds)})`;
|
|
231
|
-
}
|
|
232
|
-
return base;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
186
|
function formatApiError(data, fallback = 'Request failed.') {
|
|
236
187
|
if (!data) {
|
|
237
188
|
return fallback;
|
|
@@ -352,39 +303,6 @@ function ensureRemoteSharedReference(peerName, descriptor, message = '') {
|
|
|
352
303
|
};
|
|
353
304
|
}
|
|
354
305
|
|
|
355
|
-
function printSessionInfo(session, options = {}) {
|
|
356
|
-
const host = options.host || session.host || '127.0.0.1';
|
|
357
|
-
console.log('\x1b[1mSession Info:\x1b[0m');
|
|
358
|
-
console.log(` - ID: \x1b[36m${session.id}\x1b[0m`);
|
|
359
|
-
console.log(` Host: ${formatHostLabel(host)}`);
|
|
360
|
-
console.log(` Command: ${session.command}`);
|
|
361
|
-
console.log(` Type: ${session.type || 'unknown'}`);
|
|
362
|
-
console.log(` Status: ${formatSessionHealth(session)}`);
|
|
363
|
-
console.log(` Terminal: ${session.terminal || session.termProgram || 'unknown'}`);
|
|
364
|
-
console.log(` TERM: ${session.term || 'n/a'}`);
|
|
365
|
-
console.log(` CWD: ${session.cwd}`);
|
|
366
|
-
console.log(` Clients: ${session.active_clients ?? 0}`);
|
|
367
|
-
if (session.createdAt) {
|
|
368
|
-
console.log(` Started: ${new Date(session.createdAt).toLocaleString()}`);
|
|
369
|
-
}
|
|
370
|
-
if (session.lastActivityAt) {
|
|
371
|
-
console.log(` Last Activity: ${new Date(session.lastActivityAt).toLocaleString()}`);
|
|
372
|
-
}
|
|
373
|
-
if (typeof session.idleSeconds === 'number') {
|
|
374
|
-
console.log(` Idle: ${session.idleSeconds}s`);
|
|
375
|
-
}
|
|
376
|
-
if (session.semantic && session.semantic.phase) {
|
|
377
|
-
console.log(` Phase: ${session.semantic.phase}`);
|
|
378
|
-
}
|
|
379
|
-
if (session.semantic && session.semantic.current_task) {
|
|
380
|
-
console.log(` Current Task: ${session.semantic.current_task}`);
|
|
381
|
-
}
|
|
382
|
-
if (session.semantic && session.semantic.blocker) {
|
|
383
|
-
console.log(` Blocker: ${session.semantic.blocker}`);
|
|
384
|
-
}
|
|
385
|
-
console.log('');
|
|
386
|
-
}
|
|
387
|
-
|
|
388
306
|
function resolveTeleptyEntryPoint() {
|
|
389
307
|
// After npm upgrade, process.argv[1] still points to the OLD version's cli.js.
|
|
390
308
|
// Resolve the current telepty binary from PATH, which npm updates on install.
|
|
@@ -966,6 +884,10 @@ async function main() {
|
|
|
966
884
|
|
|
967
885
|
if (cmd === 'daemon') {
|
|
968
886
|
console.log('Starting telepty daemon...');
|
|
887
|
+
// daemon.js binds the port only when launched as the daemon. The CLI reaches
|
|
888
|
+
// it via require() (not as require.main), so signal intent explicitly — tests
|
|
889
|
+
// that `require('./daemon.js')` without this env stay side-effect-free. (#15 / 0.5.0 daemon-never-listened regression)
|
|
890
|
+
process.env.AIGENTRY_TELEPTY_DAEMON_MAIN = '1';
|
|
969
891
|
require('./daemon.js');
|
|
970
892
|
return;
|
|
971
893
|
}
|
|
@@ -1950,43 +1872,26 @@ async function main() {
|
|
|
1950
1872
|
}
|
|
1951
1873
|
const submitBody = {
|
|
1952
1874
|
injected_body: injectPrompt || '',
|
|
1953
|
-
retries:
|
|
1875
|
+
retries: submitRetries,
|
|
1954
1876
|
retry_delay_ms: 500,
|
|
1955
1877
|
...(submitForce ? { force: true } : {}),
|
|
1956
1878
|
};
|
|
1957
|
-
const RETRY_DELAY_MS = 300;
|
|
1958
|
-
const RETRY_SAFE_REASONS = new Set([
|
|
1959
|
-
'gated_dispatch_unconsumed',
|
|
1960
|
-
'gate_timeout',
|
|
1961
|
-
'no_prompt_symbol_seen',
|
|
1962
|
-
]);
|
|
1963
|
-
const maxAttempts = 1 + submitRetries;
|
|
1964
1879
|
let submitRes = null;
|
|
1965
1880
|
let submitData = null;
|
|
1966
1881
|
let attemptsMade = 0;
|
|
1967
1882
|
let lastError = null;
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
} catch (submitErr) {
|
|
1981
|
-
lastError = submitErr;
|
|
1982
|
-
submitRes = null;
|
|
1983
|
-
submitData = null;
|
|
1984
|
-
break;
|
|
1985
|
-
}
|
|
1986
|
-
if (submitRes.ok) break;
|
|
1987
|
-
if (submitRes.status !== 504) break;
|
|
1988
|
-
const retryReason = submitData && typeof submitData.reason === 'string' ? submitData.reason : null;
|
|
1989
|
-
if (!RETRY_SAFE_REASONS.has(retryReason)) break;
|
|
1883
|
+
try {
|
|
1884
|
+
attemptsMade = 1;
|
|
1885
|
+
submitRes = await fetchWithAuth(`${daemonUrl(target.host)}/api/sessions/${encodeURIComponent(target.id)}/submit`, {
|
|
1886
|
+
method: 'POST',
|
|
1887
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1888
|
+
body: JSON.stringify(submitBody),
|
|
1889
|
+
});
|
|
1890
|
+
submitData = await submitRes.json();
|
|
1891
|
+
} catch (submitErr) {
|
|
1892
|
+
lastError = submitErr;
|
|
1893
|
+
submitRes = null;
|
|
1894
|
+
submitData = null;
|
|
1990
1895
|
}
|
|
1991
1896
|
if (lastError) {
|
|
1992
1897
|
console.error(`⚠️ Submit failed: ${lastError.message}`);
|
|
@@ -1998,16 +1903,16 @@ async function main() {
|
|
|
1998
1903
|
? ' (dispatched-after-gate-timeout)'
|
|
1999
1904
|
: '';
|
|
2000
1905
|
const attemptsNote = submitData.attempts > 1 ? ` (${submitData.attempts} attempts)` : '';
|
|
2001
|
-
const retryNote = attemptsMade > 1 ? ` [retry ${attemptsMade - 1}/${submitRetries}]` : '';
|
|
2002
1906
|
const forcedNote = submitData.forced ? ' [forced]' : '';
|
|
2003
|
-
console.log(`✅ Submitted via ${submitData.strategy}${attemptsNote}${gateNote}${lateNote}${
|
|
1907
|
+
console.log(`✅ Submitted via ${submitData.strategy}${attemptsNote}${gateNote}${lateNote}${forcedNote}.`);
|
|
2004
1908
|
} else if (submitRes && submitRes.status === 504) {
|
|
2005
1909
|
// Soft failure: REPL never readied. Orchestrator scripts depend on
|
|
2006
1910
|
// exit 0 here — surface a clear remediation hint but do not exit
|
|
2007
1911
|
// non-zero.
|
|
2008
1912
|
const reason = (submitData && submitData.reason) || 'gate_timeout';
|
|
2009
1913
|
const lastState = (submitData && submitData.last_state) || 'unknown';
|
|
2010
|
-
const
|
|
1914
|
+
const daemonAttempts = submitData && Number.isFinite(Number(submitData.attempts)) ? Number(submitData.attempts) : attemptsMade;
|
|
1915
|
+
const retriesNote = daemonAttempts > 1 ? ` after ${daemonAttempts} attempts` : '';
|
|
2011
1916
|
const hint = submitForce
|
|
2012
1917
|
? ''
|
|
2013
1918
|
: ` Try \`telepty inject --submit --submit-force ${target.id} ...\` or manual \`telepty send-key ${target.id} enter\`.`;
|