@dmsdc-ai/aigentry-telepty 0.5.1 → 0.5.3
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 +14 -0
- package/cli.js +37 -123
- package/daemon.js +332 -428
- 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 +295 -0
- package/terminal-backend.js +339 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ 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
|
+
|
|
7
21
|
## [0.5.1] - 2026-05-30
|
|
8
22
|
|
|
9
23
|
### Fixed — daemon never started (CRITICAL, regresses 0.5.0)
|
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.
|
|
@@ -1355,6 +1273,10 @@ async function main() {
|
|
|
1355
1273
|
let wsReady = false;
|
|
1356
1274
|
let reconnectAttempts = 0;
|
|
1357
1275
|
let reconnectTimer = null;
|
|
1276
|
+
// BUG-C: the daemon mints a per-owner token on each owner claim/reclaim and pushes it here.
|
|
1277
|
+
// We echo it on the teardown DELETE so the daemon can tell our (current-owner) exit apart
|
|
1278
|
+
// from a stale/displaced owner's exit and avoid the shared-fate teardown.
|
|
1279
|
+
let currentOwnerToken = null;
|
|
1358
1280
|
let lastInjectTextTime = 0;
|
|
1359
1281
|
const MAX_RECONNECT_DELAY = 30000;
|
|
1360
1282
|
|
|
@@ -1400,6 +1322,10 @@ async function main() {
|
|
|
1400
1322
|
daemonWs.on('message', (message) => {
|
|
1401
1323
|
try {
|
|
1402
1324
|
const msg = JSON.parse(message);
|
|
1325
|
+
if (msg.type === 'owner_token') {
|
|
1326
|
+
currentOwnerToken = msg.token || null;
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1403
1329
|
if (msg.type === 'inject') {
|
|
1404
1330
|
const chunks = [];
|
|
1405
1331
|
const rawData = typeof msg.data === 'string' ? msg.data : String(msg.data ?? '');
|
|
@@ -1512,7 +1438,12 @@ async function main() {
|
|
|
1512
1438
|
// Purge bridge mailbox on clean exit (undelivered messages are stale)
|
|
1513
1439
|
try { bridgeMailbox.purge(bridgeTarget); } catch {}
|
|
1514
1440
|
process.stdout.write(`\x1b]0;\x07`);
|
|
1515
|
-
|
|
1441
|
+
// BUG-C: carry our owner token so the daemon destroys only on the CURRENT owner's exit;
|
|
1442
|
+
// a stale/displaced owner's DELETE (mismatched token) must not tear down the live owner.
|
|
1443
|
+
const deleteUrl = currentOwnerToken
|
|
1444
|
+
? `${DAEMON_URL}/api/sessions/${encodeURIComponent(sessionId)}?owner_token=${encodeURIComponent(currentOwnerToken)}`
|
|
1445
|
+
: `${DAEMON_URL}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
1446
|
+
fetchWithAuth(deleteUrl, { method: 'DELETE' }).catch(() => {});
|
|
1516
1447
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
1517
1448
|
try {
|
|
1518
1449
|
daemonWs.close();
|
|
@@ -1954,43 +1885,26 @@ async function main() {
|
|
|
1954
1885
|
}
|
|
1955
1886
|
const submitBody = {
|
|
1956
1887
|
injected_body: injectPrompt || '',
|
|
1957
|
-
retries:
|
|
1888
|
+
retries: submitRetries,
|
|
1958
1889
|
retry_delay_ms: 500,
|
|
1959
1890
|
...(submitForce ? { force: true } : {}),
|
|
1960
1891
|
};
|
|
1961
|
-
const RETRY_DELAY_MS = 300;
|
|
1962
|
-
const RETRY_SAFE_REASONS = new Set([
|
|
1963
|
-
'gated_dispatch_unconsumed',
|
|
1964
|
-
'gate_timeout',
|
|
1965
|
-
'no_prompt_symbol_seen',
|
|
1966
|
-
]);
|
|
1967
|
-
const maxAttempts = 1 + submitRetries;
|
|
1968
1892
|
let submitRes = null;
|
|
1969
1893
|
let submitData = null;
|
|
1970
1894
|
let attemptsMade = 0;
|
|
1971
1895
|
let lastError = null;
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
} catch (submitErr) {
|
|
1985
|
-
lastError = submitErr;
|
|
1986
|
-
submitRes = null;
|
|
1987
|
-
submitData = null;
|
|
1988
|
-
break;
|
|
1989
|
-
}
|
|
1990
|
-
if (submitRes.ok) break;
|
|
1991
|
-
if (submitRes.status !== 504) break;
|
|
1992
|
-
const retryReason = submitData && typeof submitData.reason === 'string' ? submitData.reason : null;
|
|
1993
|
-
if (!RETRY_SAFE_REASONS.has(retryReason)) break;
|
|
1896
|
+
try {
|
|
1897
|
+
attemptsMade = 1;
|
|
1898
|
+
submitRes = await fetchWithAuth(`${daemonUrl(target.host)}/api/sessions/${encodeURIComponent(target.id)}/submit`, {
|
|
1899
|
+
method: 'POST',
|
|
1900
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1901
|
+
body: JSON.stringify(submitBody),
|
|
1902
|
+
});
|
|
1903
|
+
submitData = await submitRes.json();
|
|
1904
|
+
} catch (submitErr) {
|
|
1905
|
+
lastError = submitErr;
|
|
1906
|
+
submitRes = null;
|
|
1907
|
+
submitData = null;
|
|
1994
1908
|
}
|
|
1995
1909
|
if (lastError) {
|
|
1996
1910
|
console.error(`⚠️ Submit failed: ${lastError.message}`);
|
|
@@ -2002,16 +1916,16 @@ async function main() {
|
|
|
2002
1916
|
? ' (dispatched-after-gate-timeout)'
|
|
2003
1917
|
: '';
|
|
2004
1918
|
const attemptsNote = submitData.attempts > 1 ? ` (${submitData.attempts} attempts)` : '';
|
|
2005
|
-
const retryNote = attemptsMade > 1 ? ` [retry ${attemptsMade - 1}/${submitRetries}]` : '';
|
|
2006
1919
|
const forcedNote = submitData.forced ? ' [forced]' : '';
|
|
2007
|
-
console.log(`✅ Submitted via ${submitData.strategy}${attemptsNote}${gateNote}${lateNote}${
|
|
1920
|
+
console.log(`✅ Submitted via ${submitData.strategy}${attemptsNote}${gateNote}${lateNote}${forcedNote}.`);
|
|
2008
1921
|
} else if (submitRes && submitRes.status === 504) {
|
|
2009
1922
|
// Soft failure: REPL never readied. Orchestrator scripts depend on
|
|
2010
1923
|
// exit 0 here — surface a clear remediation hint but do not exit
|
|
2011
1924
|
// non-zero.
|
|
2012
1925
|
const reason = (submitData && submitData.reason) || 'gate_timeout';
|
|
2013
1926
|
const lastState = (submitData && submitData.last_state) || 'unknown';
|
|
2014
|
-
const
|
|
1927
|
+
const daemonAttempts = submitData && Number.isFinite(Number(submitData.attempts)) ? Number(submitData.attempts) : attemptsMade;
|
|
1928
|
+
const retriesNote = daemonAttempts > 1 ? ` after ${daemonAttempts} attempts` : '';
|
|
2015
1929
|
const hint = submitForce
|
|
2016
1930
|
? ''
|
|
2017
1931
|
: ` Try \`telepty inject --submit --submit-force ${target.id} ...\` or manual \`telepty send-key ${target.id} enter\`.`;
|