Skip to content
Merged
9 changes: 7 additions & 2 deletions packages/contracts/src/app-log-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,18 @@ export type AppLogBackgroundProcess = AsyncDisposable &
export type AppLogProcessCommand =
| Readonly<{
kind: 'host';
request: HostCommandRequest;
/**
* A streamed log tail is stopped by its owner, so a host command here has no deadline to
* honour: the background exec drops `timeoutMs`. It stays out of the type so a producer
* cannot pass a budget that silently never fires.
*/
request: Omit<HostCommandRequest, 'timeoutMs'>;
}>
| Readonly<{
kind: 'android-adb';
serial: string;
args: readonly string[];
options?: Pick<HostCommandRequest, 'allowFailure' | 'cwd' | 'env' | 'timeoutMs'>;
options?: Pick<HostCommandRequest, 'allowFailure' | 'cwd' | 'env'>;
}>;

export type AppLogBackgroundProcessRequest = Readonly<{
Expand Down
28 changes: 28 additions & 0 deletions packages/host-kit/src/internal/exec-boundary-faults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { test } from 'vitest';
import { AppError } from '@agent-device/kernel/errors';
import {
runCmd,
runCmdBackground,
runCmdDetached,
runCmdStreaming,
runCmdSync,
withCommandExecutorOverride,
type CommandExecutorOverride,
} from '@agent-device/host-kit/command';
Expand All @@ -28,3 +32,27 @@ test('fail-Nth executor drives one deterministic command failure without hiding

assert.deepEqual(calls, [['first'], ['second'], ['third']]);
});

test('the override seam covers the foreground commands and no other spawn path', async () => {
const consulted: string[] = [];

await withCommandExecutorOverride(
(command) => {
consulted.push(command);
return undefined;
},
async () => {
runCmdSync(process.execPath, ['-e', 'process.stdout.write("sync")']);
const background = runCmdBackground(process.execPath, [
'-e',
'process.stdout.write("background")',
]);
await background.wait;
runCmdDetached(process.execPath, ['-e', 'process.exit(0)']);
await runCmdStreaming(process.execPath, ['-e', 'process.stdout.write("streaming")']);
await runCmd(process.execPath, ['-e', 'process.stdout.write("foreground")']);
},
);

assert.deepEqual(consulted, [process.execPath, process.execPath]);
});
Loading
Loading