Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"acpx": "dist/cli.js"
},
"scripts": {
"build": "tsup src/cli.ts src/queue-owner-main.ts --format esm --dts --clean",
"build": "tsup src/cli.ts --format esm --dts --clean",
"build:test": "node -e \"require('node:fs').rmSync('dist-test',{recursive:true,force:true})\" && tsc -p tsconfig.test.json",
"test": "npm run build:test && node --test dist-test/test/*.test.js",
"prepare": "husky",
Expand Down
34 changes: 12 additions & 22 deletions src/cli-core.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#!/usr/bin/env node

import { Command, CommanderError, InvalidArgumentError } from "commander";
import { realpathSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { findSkillsRoot, maybeHandleSkillflag } from "skillflag";
import { listBuiltInAgents } from "./agent-registry.js";
import {
Expand Down Expand Up @@ -76,6 +74,7 @@ import {
type SessionAgentContent,
type SessionUserContent,
} from "./types.js";
import { runQueueOwnerFromEnv } from "./queue-owner-env.js";

class NoSessionError extends Error {
constructor(message: string) {
Expand Down Expand Up @@ -1402,6 +1401,17 @@ async function runWithOutputPolicy<T>(
}

export async function main(argv: string[] = process.argv): Promise<void> {
if (argv[2] === "__queue-owner") {
try {
await runQueueOwnerFromEnv(process.env);
return;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`[acpx] queue owner failed: ${message}\n`);
process.exit(EXIT_CODES.ERROR);
}
}

await maybeHandleSkillflag(argv, {
skillsRoot: findSkillsRoot(import.meta.url),
includeBundledSkill: false,
Expand Down Expand Up @@ -1535,23 +1545,3 @@ Examples:
}
});
}

function isCliEntrypoint(argv: string[]): boolean {
const entry = argv[1];
if (!entry) {
return false;
}

try {
// Resolve symlinks so global npm installs match (argv[1] is the
// symlink in node_modules/.bin, import.meta.url is the real path).
const resolved = pathToFileURL(realpathSync(entry)).href;
return import.meta.url === resolved;
} catch {
return false;
}
}

if (isCliEntrypoint(process.argv)) {
void main(process.argv);
}
182 changes: 0 additions & 182 deletions src/cli-internal-owner.ts

This file was deleted.

15 changes: 3 additions & 12 deletions src/queue-owner-main.ts → src/queue-owner-env.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/usr/bin/env node

import {
runSessionQueueOwner,
type QueueOwnerRuntimeOptions,
Expand All @@ -14,7 +12,7 @@ function asRecord(value: unknown): UnknownRecord | undefined {
return value as UnknownRecord;
}

function parseQueueOwnerPayload(raw: string): QueueOwnerRuntimeOptions {
export function parseQueueOwnerPayload(raw: string): QueueOwnerRuntimeOptions {
const parsed = JSON.parse(raw) as unknown;
const record = asRecord(parsed);
if (!record) {
Expand Down Expand Up @@ -71,18 +69,11 @@ function parseQueueOwnerPayload(raw: string): QueueOwnerRuntimeOptions {
return options;
}

async function main(): Promise<void> {
const payload = process.env.ACPX_QUEUE_OWNER_PAYLOAD;
export async function runQueueOwnerFromEnv(env: NodeJS.ProcessEnv): Promise<void> {
const payload = env.ACPX_QUEUE_OWNER_PAYLOAD;
if (!payload) {
throw new Error("missing ACPX_QUEUE_OWNER_PAYLOAD");
}

const options = parseQueueOwnerPayload(payload);
await runSessionQueueOwner(options);
}

void main().catch((error) => {
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`[acpx] queue owner failed: ${message}\n`);
process.exit(1);
});
17 changes: 12 additions & 5 deletions src/session-runtime/queue-owner-process.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { realpathSync } from "node:fs";
import type {
AuthPolicy,
NonInteractivePermissionPolicy,
Expand Down Expand Up @@ -28,9 +28,16 @@ type SessionSendLike = {
ttlMs?: number;
};

const QUEUE_OWNER_MAIN_PATH = fileURLToPath(
new URL("../queue-owner-main.js", import.meta.url),
);
export function resolveQueueOwnerSpawnArgs(
argv: readonly string[] = process.argv,
): string[] {
const entry = argv[1];
if (!entry || entry.trim().length === 0) {
throw new Error("acpx self-spawn failed: missing CLI entry path");
}
const resolvedEntry = realpathSync(entry);
return [resolvedEntry, "__queue-owner"];
}

export function queueOwnerRuntimeOptionsFromSend(
options: SessionSendLike,
Expand All @@ -49,7 +56,7 @@ export function queueOwnerRuntimeOptionsFromSend(

export function spawnQueueOwnerProcess(options: QueueOwnerRuntimeOptions): void {
const payload = JSON.stringify(options);
const child = spawn(process.execPath, [QUEUE_OWNER_MAIN_PATH], {
const child = spawn(process.execPath, resolveQueueOwnerSpawnArgs(), {
detached: true,
stdio: "ignore",
env: {
Expand Down
47 changes: 47 additions & 0 deletions test/queue-owner-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
parseQueueOwnerPayload,
runQueueOwnerFromEnv,
} from "../src/queue-owner-env.js";

describe("parseQueueOwnerPayload", () => {
it("parses valid payload", () => {
const parsed = parseQueueOwnerPayload(
JSON.stringify({
sessionId: "session-1",
permissionMode: "approve-reads",
ttlMs: 1234,
}),
);
assert.equal(parsed.sessionId, "session-1");
assert.equal(parsed.permissionMode, "approve-reads");
assert.equal(parsed.ttlMs, 1234);
});

it("rejects invalid payloads", () => {
assert.throws(() => parseQueueOwnerPayload("{}"), {
message: "queue owner payload missing sessionId",
});
assert.throws(
() =>
parseQueueOwnerPayload(
JSON.stringify({
sessionId: "session-1",
permissionMode: "invalid",
}),
),
{
message: "queue owner payload has invalid permissionMode",
},
);
});
});

describe("runQueueOwnerFromEnv", () => {
it("fails when payload env is missing", async () => {
await assert.rejects(async () => await runQueueOwnerFromEnv({}), {
message: "missing ACPX_QUEUE_OWNER_PAYLOAD",
});
});
});
Loading