Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/cli-smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,14 @@ jobs:
cache-dependency-path: yarn.lock

- name: Install dependencies
env:
SKIP_HAPPY_WIRE_BUILD: "1"
run: yarn install --immutable

- name: Build happy-wire
shell: bash
run: cd packages/happy-wire && yarn build

- name: Build package
run: yarn workspace happy-coder build

Expand Down
8 changes: 6 additions & 2 deletions packages/happy-app/sources/app/(app)/new/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1061,17 +1061,21 @@ function NewSessionWizard() {
return 'session'
},
});
} else if (result.type === 'error') {
throw new Error(result.errorMessage);
} else {
throw new Error('Session spawning failed - no session ID returned.');
throw new Error('Session spawning failed - unexpected response.');
}
} catch (error) {
console.error('Failed to start session', error);
let errorMessage = 'Failed to start session. Make sure the daemon is running on the target machine.';
if (error instanceof Error) {
if (error instanceof Error && error.message) {
if (error.message.includes('timeout')) {
errorMessage = 'Session startup timed out. The machine may be slow or the daemon may not be responding.';
} else if (error.message.includes('Socket not connected')) {
errorMessage = 'Not connected to server. Check your internet connection.';
} else {
errorMessage = error.message;
}
}
Modal.alert(t('common.error'), errorMessage);
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"scripts": {
"why do we need to build before running tests / dev?": "We need the binary to be built so we run daemon commands which directly run the binary - we don't want them to go out of sync or have custom spawn logic depending how we started happy",
"typecheck": "tsc --noEmit",
"build": "shx rm -rf dist && npx tsc --noEmit && pkgroll",
"build": "shx rm -rf dist && tsc --noEmit && pkgroll",
"test": "$npm_execpath run build && vitest run",
"start": "$npm_execpath run build && node ./bin/happy.mjs",
"cli": "tsx src/index.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-cli/src/agent/acp/runAcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ export async function runAcp(opts: {
if (verbose) {
logAcp('muted', `Outgoing modes from ${opts.agentName} (${modes.availableModes.length}), current=${modes.currentModeId}:`);
for (const mode of modes.availableModes) {
logAcp('muted', ` mode=${mode.id} name=${mode.name}${formatOptionalDetail(mode.description, 160)}`);
logAcp('muted', ` mode=${mode.id} name=${mode.name}${formatOptionalDetail(mode.description ?? undefined, 160)}`);
}
}
session.updateMetadata((currentMetadata) =>
Expand Down
1 change: 1 addition & 0 deletions packages/happy-cli/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export type Metadata = {
happyToolsDir: string,
startedFromDaemon?: boolean,
hostPid?: number,
spawnToken?: string,
startedBy?: 'daemon' | 'terminal',
// Lifecycle state management
lifecycleState?: 'running' | 'archiveRequested' | 'archived' | string,
Expand Down
1 change: 1 addition & 0 deletions packages/happy-cli/src/claude/runClaude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export async function runClaude(credentials: Credentials, options: StartOptions
happyToolsDir: resolve(projectPath(), 'tools', 'unpacked'),
startedFromDaemon: options.startedBy === 'daemon',
hostPid: process.pid,
spawnToken: process.env.HAPPY_SPAWN_TOKEN || undefined,
startedBy: options.startedBy || 'terminal',
// Initialize lifecycle state
lifecycleState: 'running',
Expand Down
264 changes: 214 additions & 50 deletions packages/happy-cli/src/daemon/run.ts

Large diffs are not rendered by default.

45 changes: 44 additions & 1 deletion packages/happy-cli/src/utils/createSessionMetadata.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest';
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
import type { SandboxConfig } from '@/persistence';
import { createSessionMetadata } from './createSessionMetadata';

Expand Down Expand Up @@ -71,4 +71,47 @@ describe('createSessionMetadata', () => {

expect(metadata.dangerouslySkipPermissions).toBe(true);
});

describe('spawnToken', () => {
const originalSpawnToken = process.env.HAPPY_SPAWN_TOKEN;

afterEach(() => {
if (originalSpawnToken !== undefined) {
process.env.HAPPY_SPAWN_TOKEN = originalSpawnToken;
} else {
delete process.env.HAPPY_SPAWN_TOKEN;
}
});

it('sets metadata.spawnToken from HAPPY_SPAWN_TOKEN env var', () => {
process.env.HAPPY_SPAWN_TOKEN = 'abc123';
const { metadata } = createSessionMetadata({
flavor: 'claude',
machineId: 'machine-6',
});

expect(metadata.spawnToken).toBe('abc123');
});

it('sets metadata.spawnToken to undefined when env var not set', () => {
delete process.env.HAPPY_SPAWN_TOKEN;
const { metadata } = createSessionMetadata({
flavor: 'claude',
machineId: 'machine-7',
});

expect(metadata.spawnToken).toBeUndefined();
});

it('sets metadata.spawnToken to undefined when env var is empty string', () => {
process.env.HAPPY_SPAWN_TOKEN = '';
const { metadata } = createSessionMetadata({
flavor: 'claude',
machineId: 'machine-8',
});

// '' || undefined → undefined
expect(metadata.spawnToken).toBeUndefined();
});
});
});
1 change: 1 addition & 0 deletions packages/happy-cli/src/utils/createSessionMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export function createSessionMetadata(opts: CreateSessionMetadataOptions): Sessi
happyToolsDir: resolve(projectPath(), 'tools', 'unpacked'),
startedFromDaemon: opts.startedBy === 'daemon',
hostPid: process.pid,
spawnToken: process.env.HAPPY_SPAWN_TOKEN || undefined,
startedBy: opts.startedBy || 'terminal',
lifecycleState: 'running',
lifecycleStateSince: Date.now(),
Expand Down
Loading