@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 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: 1,
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
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1969
- if (attempt > 0) {
1970
- await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
1971
- }
1972
- attemptsMade = attempt + 1;
1973
- try {
1974
- submitRes = await fetchWithAuth(`${daemonUrl(target.host)}/api/sessions/${encodeURIComponent(target.id)}/submit`, {
1975
- method: 'POST',
1976
- headers: { 'Content-Type': 'application/json' },
1977
- body: JSON.stringify(submitBody),
1978
- });
1979
- submitData = await submitRes.json();
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}${retryNote}${forcedNote}.`);
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 retriesNote = attemptsMade > 1 ? ` after ${attemptsMade} attempts` : '';
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\`.`;