@adhdev/daemon-core 0.9.12 → 0.9.14

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "0.9.12",
3
+ "version": "0.9.14",
4
4
  "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.12",
3
+ "version": "0.9.14",
4
4
  "description": "ADHDev daemon core \u2014 CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -156,6 +156,8 @@ export declare class ProviderCliAdapter implements CliAdapter {
156
156
  * Uses resolveAction script if available, otherwise falls back to standard text.
157
157
  */
158
158
  resolveAction(data: any): Promise<void>;
159
+ private writeToPty;
160
+ private resetPendingSendState;
159
161
  sendMessage(text: string): Promise<void>;
160
162
  getPartialResponse(): string;
161
163
  getRuntimeMetadata(): PtyRuntimeMetadata | null;
@@ -168,7 +170,7 @@ export declare class ProviderCliAdapter implements CliAdapter {
168
170
  clearHistory(): void;
169
171
  isProcessing(): boolean;
170
172
  isReady(): boolean;
171
- writeRaw(data: string): void;
173
+ writeRaw(data: string): Promise<void>;
172
174
  resolveModal(buttonIndex: number): void;
173
175
  resize(cols: number, rows: number): void;
174
176
  getDebugState(): Record<string, any>;
@@ -1779,6 +1779,22 @@ export class ProviderCliAdapter implements CliAdapter {
1779
1779
  await this.sendMessage(promptText);
1780
1780
  }
1781
1781
 
1782
+ private async writeToPty(data: string): Promise<void> {
1783
+ if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
1784
+ await this.ptyProcess.write(data);
1785
+ }
1786
+
1787
+ private resetPendingSendState(reason: string): void {
1788
+ this.isWaitingForResponse = false;
1789
+ this.responseBuffer = '';
1790
+ this.currentTurnScope = null;
1791
+ this.submitPendingUntil = 0;
1792
+ this.clearIdleFinishCandidate(reason);
1793
+ if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
1794
+ if (this.submitRetryTimer) { clearTimeout(this.submitRetryTimer); this.submitRetryTimer = null; }
1795
+ if (this.finishRetryTimer) { clearTimeout(this.finishRetryTimer); this.finishRetryTimer = null; }
1796
+ }
1797
+
1782
1798
  async sendMessage(text: string): Promise<void> {
1783
1799
  if (!this.ptyProcess) throw new Error(`${this.cliName} is not running`);
1784
1800
  const allowInputDuringGeneration = this.provider.allowInputDuringGeneration === true;
@@ -1881,20 +1897,30 @@ export class ProviderCliAdapter implements CliAdapter {
1881
1897
  if (this.isWaitingForResponse) this.finishResponse();
1882
1898
  }, this.timeouts.maxResponse);
1883
1899
  };
1884
- await new Promise<void>((resolve) => {
1900
+ await new Promise<void>((resolve, reject) => {
1885
1901
  let resolved = false;
1886
1902
  const resolveOnce = () => {
1887
1903
  if (resolved) return;
1888
1904
  resolved = true;
1889
1905
  resolve();
1890
1906
  };
1907
+ const rejectOnce = (error: unknown) => {
1908
+ if (resolved) return;
1909
+ this.resetPendingSendState('send_write_failed');
1910
+ resolved = true;
1911
+ reject(error);
1912
+ };
1913
+ const writeRetryKey = (mode: string) => {
1914
+ void this.writeToPty(this.sendKey).catch((error) => {
1915
+ LOG.warn('CLI', `[${this.cliType}] ${mode} write failed: ${error?.message || error}`);
1916
+ });
1917
+ };
1891
1918
 
1892
1919
  const submit = () => {
1893
1920
  if (!this.ptyProcess) {
1894
1921
  resolveOnce();
1895
1922
  return;
1896
1923
  }
1897
- commitUserTurn();
1898
1924
  this.submitPendingUntil = 0;
1899
1925
  const screenText = this.terminalScreen.getText();
1900
1926
  this.recordTrace('submit_write', {
@@ -1902,7 +1928,6 @@ export class ProviderCliAdapter implements CliAdapter {
1902
1928
  sendKey: this.sendKey,
1903
1929
  screenText: summarizeCliTraceText(screenText, 500),
1904
1930
  });
1905
- this.ptyProcess!.write(this.sendKey);
1906
1931
  const retrySubmitIfStuck = (attempt: number) => {
1907
1932
  this.submitRetryTimer = null;
1908
1933
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
@@ -1922,20 +1947,22 @@ export class ProviderCliAdapter implements CliAdapter {
1922
1947
  sendKey: this.sendKey,
1923
1948
  screenText: summarizeCliTraceText(screenText, 500),
1924
1949
  });
1925
- this.ptyProcess.write(this.sendKey);
1950
+ writeRetryKey('submit_retry');
1926
1951
  if (attempt >= 3) {
1927
1952
  this.submitRetryUsed = true;
1928
1953
  return;
1929
1954
  }
1930
1955
  this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
1931
1956
  };
1932
- this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
1933
- startResponseTimeout();
1934
- resolveOnce();
1957
+ void this.writeToPty(this.sendKey).then(() => {
1958
+ commitUserTurn();
1959
+ this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
1960
+ startResponseTimeout();
1961
+ resolveOnce();
1962
+ }, rejectOnce);
1935
1963
  };
1936
1964
 
1937
1965
  if (this.submitStrategy === 'immediate') {
1938
- commitUserTurn();
1939
1966
  this.submitPendingUntil = 0;
1940
1967
  this.recordTrace('submit_write', {
1941
1968
  mode: 'immediate',
@@ -1943,38 +1970,39 @@ export class ProviderCliAdapter implements CliAdapter {
1943
1970
  sendKey: this.sendKey,
1944
1971
  screenText: summarizeCliTraceText(this.terminalScreen.getText(), 500),
1945
1972
  });
1946
- this.ptyProcess!.write(text + this.sendKey);
1947
- this.submitRetryTimer = setTimeout(() => {
1948
- this.submitRetryTimer = null;
1949
- if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
1950
- if (this.currentStatus === 'waiting_approval') return;
1951
- if (this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) return;
1952
- const screenText = this.terminalScreen.getText();
1953
- if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
1954
- const liveApproval = this.runParseApproval(screenText) || this.runParseApproval(this.recentOutputBuffer);
1955
- if (liveApproval) return;
1956
- const liveStatus = this.runDetectStatus(screenText) || this.runDetectStatus(this.recentOutputBuffer);
1957
- if (liveStatus === 'generating' || liveStatus === 'waiting_approval') return;
1958
- LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
1959
- this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1960
- this.recordTrace('submit_write', {
1961
- mode: 'immediate_retry',
1962
- attempt: 1,
1963
- sendKey: this.sendKey,
1964
- screenText: summarizeCliTraceText(screenText, 500),
1965
- });
1966
- this.ptyProcess.write(this.sendKey);
1967
- this.submitRetryUsed = true;
1968
- }, retryDelayMs);
1969
- startResponseTimeout();
1970
- resolveOnce();
1973
+ void this.writeToPty(text + this.sendKey).then(() => {
1974
+ commitUserTurn();
1975
+ this.submitRetryTimer = setTimeout(() => {
1976
+ this.submitRetryTimer = null;
1977
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
1978
+ if (this.currentStatus === 'waiting_approval') return;
1979
+ if (this.hasMeaningfulResponseBuffer(normalizedPromptSnippet)) return;
1980
+ const screenText = this.terminalScreen.getText();
1981
+ if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
1982
+ const liveApproval = this.runParseApproval(screenText) || this.runParseApproval(this.recentOutputBuffer);
1983
+ if (liveApproval) return;
1984
+ const liveStatus = this.runDetectStatus(screenText) || this.runDetectStatus(this.recentOutputBuffer);
1985
+ if (liveStatus === 'generating' || liveStatus === 'waiting_approval') return;
1986
+ LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
1987
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1988
+ this.recordTrace('submit_write', {
1989
+ mode: 'immediate_retry',
1990
+ attempt: 1,
1991
+ sendKey: this.sendKey,
1992
+ screenText: summarizeCliTraceText(screenText, 500),
1993
+ });
1994
+ writeRetryKey('immediate_retry');
1995
+ this.submitRetryUsed = true;
1996
+ }, retryDelayMs);
1997
+ startResponseTimeout();
1998
+ resolveOnce();
1999
+ }, rejectOnce);
1971
2000
  return;
1972
2001
  }
1973
2002
 
1974
2003
  if (submitDelayMs > 0) {
1975
2004
  this.submitPendingUntil = Date.now() + submitDelayMs;
1976
2005
  }
1977
- this.ptyProcess!.write(text);
1978
2006
  this.recordTrace('submit_write', {
1979
2007
  mode: 'type_then_submit',
1980
2008
  text: summarizeCliTraceText(text, 500),
@@ -2014,7 +2042,7 @@ export class ProviderCliAdapter implements CliAdapter {
2014
2042
 
2015
2043
  setTimeout(waitForEchoAndSubmit, 50);
2016
2044
  };
2017
- waitForEchoAndSubmit();
2045
+ void this.writeToPty(text).then(() => waitForEchoAndSubmit(), rejectOnce);
2018
2046
  });
2019
2047
  }
2020
2048
 
@@ -2168,12 +2196,12 @@ export class ProviderCliAdapter implements CliAdapter {
2168
2196
  isProcessing(): boolean { return this.isWaitingForResponse; }
2169
2197
  isReady(): boolean { return this.ready; }
2170
2198
 
2171
- writeRaw(data: string): void {
2199
+ async writeRaw(data: string): Promise<void> {
2172
2200
  this.recordTrace('write_raw', {
2173
2201
  keys: JSON.stringify(data),
2174
2202
  length: data.length,
2175
2203
  });
2176
- this.ptyProcess?.write(data);
2204
+ await this.writeToPty(data);
2177
2205
  }
2178
2206
 
2179
2207
  resolveModal(buttonIndex: number): void {
@@ -28,7 +28,7 @@ export interface PtyRuntimeTransport {
28
28
  readonly pid: number;
29
29
  readonly ready: Promise<void>;
30
30
  readonly terminalQueriesHandled?: boolean;
31
- write(data: string): void;
31
+ write(data: string): void | Promise<void>;
32
32
  resize(cols: number, rows: number): void;
33
33
  kill(): void;
34
34
  clearBuffer?(): void;
@@ -53,7 +53,7 @@ export interface PtyRuntimeTransport {
53
53
  readonly pid: number;
54
54
  readonly ready: Promise<void>;
55
55
  readonly terminalQueriesHandled?: boolean;
56
- write(data: string): void;
56
+ write(data: string): void | Promise<void>;
57
57
  resize(cols: number, rows: number): void;
58
58
  kill(): void;
59
59
  clearBuffer?(): void;
@@ -82,8 +82,8 @@ class SessionHostRuntimeTransport implements PtyRuntimeTransport {
82
82
  this.exitCallbacks.add(callback);
83
83
  }
84
84
 
85
- write(data: string): void {
86
- this.enqueue(async () => {
85
+ write(data: string): Promise<void> {
86
+ return this.enqueue(async () => {
87
87
  let response = await this.client.request({
88
88
  type: 'send_input',
89
89
  payload: {
@@ -404,13 +404,14 @@ class SessionHostRuntimeTransport implements PtyRuntimeTransport {
404
404
  };
405
405
  }
406
406
 
407
- private enqueue(action: () => Promise<void>): void {
408
- this.operationChain = this.operationChain
407
+ private enqueue(action: () => Promise<void>): Promise<void> {
408
+ const operation = this.operationChain
409
409
  .then(() => this.ready)
410
410
  .then(action)
411
- .catch((error) => {
412
- LOG.warn('CLI', `[session-host:${this.options.runtimeId}] ${error?.message || error}`);
413
- });
411
+ this.operationChain = operation.catch((error) => {
412
+ LOG.warn('CLI', `[session-host:${this.options.runtimeId}] ${error?.message || error}`);
413
+ });
414
+ return operation;
414
415
  }
415
416
 
416
417
  private async closeClient(destroy = false): Promise<void> {
@@ -818,10 +818,16 @@ export class DaemonCommandRouter {
818
818
  // ignore ls failures; upgrade can still proceed
819
819
  }
820
820
 
821
- if (currentInstalled === latest) {
821
+ const runningVersion = typeof this.deps.statusVersion === 'string'
822
+ ? this.deps.statusVersion.trim().replace(/^v/, '')
823
+ : null;
824
+ if (currentInstalled === latest && runningVersion === latest) {
822
825
  LOG.info('Upgrade', `Already on latest version v${latest}; skipping install`);
823
826
  return { success: true, upgraded: false, alreadyLatest: true, version: latest };
824
827
  }
828
+ if (currentInstalled === latest && runningVersion && runningVersion !== latest) {
829
+ LOG.info('Upgrade', `Installed package is v${latest}, but running daemon is v${runningVersion}; scheduling restart`);
830
+ }
825
831
 
826
832
  spawnDetachedDaemonUpgradeHelper({
827
833
  packageName: pkgName,
@@ -5,7 +5,7 @@
5
5
  import type { CommandResult, CommandHelpers } from './handler.js';
6
6
  export declare function handleSelectSession(h: CommandHelpers, args: any): Promise<CommandResult>;
7
7
  export declare function handleOpenPanel(h: CommandHelpers, args: any): Promise<CommandResult>;
8
- export declare function handlePtyInput(h: CommandHelpers, args: any): CommandResult;
8
+ export declare function handlePtyInput(h: CommandHelpers, args: any): Promise<CommandResult>;
9
9
  export declare function handlePtyResize(h: CommandHelpers, args: any): CommandResult;
10
10
  export declare function handleGetProviderSettings(h: CommandHelpers, args: any): CommandResult;
11
11
  export declare function handleSetProviderSetting(h: CommandHelpers, args: any): Promise<CommandResult>;
@@ -110,14 +110,14 @@ export async function handleOpenPanel(h: CommandHelpers, args: any): Promise<Com
110
110
 
111
111
  // ─── PTY Raw I/O ──────────────────────────────────
112
112
 
113
- export function handlePtyInput(h: CommandHelpers, args: any): CommandResult {
113
+ export async function handlePtyInput(h: CommandHelpers, args: any): Promise<CommandResult> {
114
114
  const { cliType, data, targetSessionId } = args || {};
115
115
  if (!data) return { success: false, error: 'data required' };
116
116
  const adapter = h.getCliAdapter(targetSessionId || cliType);
117
117
  if (!adapter || typeof adapter.writeRaw !== 'function') {
118
118
  return { success: false, error: `CLI adapter not found: ${targetSessionId || cliType || 'unknown'}` };
119
119
  }
120
- adapter.writeRaw(data);
120
+ await adapter.writeRaw(data);
121
121
  return { success: true };
122
122
  }
123
123
 
@@ -343,7 +343,7 @@ async function executeProviderScript(h: CommandHelpers, args: any, scriptName: s
343
343
  if (cliCommand?.type === 'send_message' && cliCommand.text) {
344
344
  await adapter.sendMessage(cliCommand.text);
345
345
  } else if (cliCommand?.type === 'pty_write' && cliCommand.text && adapter.writeRaw) {
346
- adapter.writeRaw(cliCommand.text + '\r');
346
+ await adapter.writeRaw(cliCommand.text + '\r');
347
347
  }
348
348
  applyProviderPatch(h, args, parsed.payload);
349
349
  return {
@@ -1195,7 +1195,7 @@ export async function handleCliRaw(ctx: DevServerContext, req: http.IncomingMess
1195
1195
 
1196
1196
  try {
1197
1197
  if (typeof adapter.writeRaw === 'function') {
1198
- adapter.writeRaw(keys);
1198
+ await adapter.writeRaw(keys);
1199
1199
  ctx.json(res, 200, { sent: true, type: target.type, instanceId: target.instanceId, keysLength: keys.length });
1200
1200
  } else {
1201
1201
  ctx.json(res, 400, { error: 'writeRaw not available on this adapter' });
@@ -526,7 +526,7 @@ export class CliProviderInstance implements ProviderInstance {
526
526
  if (cliCommand?.type === 'send_message' && cliCommand.text) {
527
527
  await this.adapter.sendMessage(cliCommand.text);
528
528
  } else if (cliCommand?.type === 'pty_write' && cliCommand.text) {
529
- this.adapter.writeRaw(cliCommand.text + '\r');
529
+ await this.adapter.writeRaw(cliCommand.text + '\r');
530
530
  }
531
531
 
532
532
  this.applyProviderResponse(parsed.payload, { phase: 'immediate' });