import {
extractProjectDirectory,
getProjectCronJobsOverview,
} from './projects.js';
import {
getProjectDiscoveryPlansOverview,
rerunDiscoveryPlan,
} from './discovery-plans.js';
import { getPilotDeckGateway } from './pilotdeck-bridge.js';
const TARGET_ALIASES = new Map([
['cron', 'cron'],
['crons', 'cron'],
['job', 'cron'],
['jobs', 'cron'],
['cron-job', 'cron'],
['cron-jobs', 'cron'],
['plan', 'plan'],
['plans', 'plan'],
]);
function normalizeTargetType(value) {
if (typeof value !== 'string') {
return null;
}
return TARGET_ALIASES.get(value.trim().toLowerCase()) || null;
}
function formatDateTime(value, fallback = '-') {
if (value === undefined || value === null || value === '') {
return fallback;
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return fallback;
}
return date.toLocaleString();
}
function formatText(value, fallback = '-') {
if (typeof value !== 'string') {
return fallback;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : fallback;
}
function summarizeText(value, maxLength = 140, fallback = '-') {
const text = formatText(value, '');
if (!text) {
return fallback;
}
const collapsed = text.replace(/\s+/g, ' ');
return collapsed.length > maxLength
? `${collapsed.slice(0, maxLength - 3)}...`
: collapsed;
}
function buildUsageMarkdown() {
return [
'# Always-On Slash',
'',
'Usage:',
'- `/ao list [cron|plan]`',
'- `/ao status <cron|plan> <id>`',
'- `/ao run <cron|plan> <id>`',
'- `/ao help`',
].join('\n');
}
function buildResponse(content, extraData = {}) {
return {
type: 'builtin',
action: 'ao',
data: {
mode: 'message',
content,
...extraData,
},
};
}
export function parseAlwaysOnSlashArgs(args = []) {
const [actionRaw = 'help', targetRaw, idRaw, ...extra] = Array.isArray(args) ? args : [];
const action = String(actionRaw).trim().toLowerCase();
if (!action || action === 'help') {
return { action: 'help' };
}
if (action === 'list') {
if (!targetRaw) {
return { action: 'list', target: 'all' };
}
const target = normalizeTargetType(targetRaw);
if (!target || extra.length > 0 || idRaw) {
return {
action: 'help',
error: 'Usage: `/ao list [cron|plan]`',
};
}
return { action: 'list', target };
}
if (action === 'status' || action === 'run') {
const target = normalizeTargetType(targetRaw);
const id = typeof idRaw === 'string' ? idRaw.trim() : '';
if (!target || !id || extra.length > 0) {
return {
action: 'help',
error: `Usage: \`/ao ${action} <cron|plan> <id>\``,
};
}
return { action, target, id };
}
return {
action: 'help',
error: `Unknown /ao action: \`${action}\``,
};
}
function getProjectContext(context) {
const projectName =
typeof context?.projectName === 'string' ? context.projectName.trim() : '';
const projectPath =
typeof context?.projectPath === 'string' ? context.projectPath.trim() : '';
if (!projectName) {
return null;
}
return {
projectName,
projectPath,
};
}
function buildCronListMarkdown(projectPath, jobs) {
const lines = [
`## Cron jobs (${jobs.length})`,
'',
];
if (projectPath) {
lines.push(`Workspace: \`${projectPath}\``);
lines.push('');
}
if (jobs.length === 0) {
lines.push('No cron jobs found.');
return lines.join('\n');
}
for (const job of jobs) {
const kind = [
job.durable === false ? 'session' : 'durable',
job.recurring ? 'recurring' : 'one-shot',
job.manualOnly ? 'manual-only' : null,
].filter(Boolean).join(', ');
lines.push(`- \`${job.id}\` - ${summarizeText(job.prompt)}`);
lines.push(` - Status: \`${job.status}\``);
lines.push(` - Kind: ${kind}`);
lines.push(` - Schedule: \`${job.cron}\``);
lines.push(` - Last fired: ${formatDateTime(job.lastFiredAt)}`);
if (job.latestRun?.summary) {
lines.push(` - Latest summary: ${summarizeText(job.latestRun.summary, 180)}`);
}
}
return lines.join('\n');
}
function buildPlanListMarkdown(projectPath, plans) {
const lines = [
`## Discovery plans (${plans.length})`,
'',
];
if (projectPath) {
lines.push(`Workspace: \`${projectPath}\``);
lines.push('');
}
if (plans.length === 0) {
lines.push('No discovery plans found.');
return lines.join('\n');
}
for (const plan of plans) {
lines.push(`- \`${plan.id}\` - ${formatText(plan.title)}`);
lines.push(` - Status: \`${plan.status}\``);
lines.push(` - Updated: ${formatDateTime(plan.updatedAt)}`);
lines.push(` - Summary: ${summarizeText(plan.summary, 180)}`);
}
return lines.join('\n');
}
function buildCombinedListMarkdown(projectPath, { jobs, plans }) {
return [
'# Always-On',
'',
buildPlanListMarkdown(projectPath, plans),
'',
buildCronListMarkdown(projectPath, jobs),
].join('\n');
}
function buildCronStatusMarkdown(projectPath, job) {
const latestRun = job.latestRun || null;
return [
`# Cron job \`${job.id}\``,
'',
projectPath ? `Workspace: \`${projectPath}\`` : '',
projectPath ? '' : '',
`- Status: \`${job.status}\``,
`- Schedule: \`${job.cron}\``,
`- Scope: \`${job.durable === false ? 'session' : 'durable'}\``,
`- Type: \`${job.recurring ? 'recurring' : 'one-shot'}\``,
`- Manual only: \`${job.manualOnly ? 'yes' : 'no'}\``,
`- Created: ${formatDateTime(job.createdAt)}`,
`- Last fired: ${formatDateTime(job.lastFiredAt)}`,
`- Origin session: ${formatText(job.originSessionId)}`,
`- Transcript key: ${formatText(job.transcriptKey)}`,
'',
'## Prompt',
'',
formatText(job.prompt),
'',
'## Latest run',
'',
`- Last activity: ${formatDateTime(latestRun?.lastActivity)}`,
`- Summary: ${formatText(latestRun?.summary)}`,
`- Task ID: ${formatText(latestRun?.taskId)}`,
`- Transcript: ${formatText(latestRun?.relativeTranscriptPath)}`,
`- Output file: ${formatText(latestRun?.outputFile)}`,
].filter(Boolean).join('\n');
}
function buildPlanStatusMarkdown(projectPath, plan) {
return [
`# Discovery plan \`${plan.id}\``,
'',
projectPath ? `Workspace: \`${projectPath}\`` : '',
projectPath ? '' : '',
`- Title: ${formatText(plan.title)}`,
`- Status: \`${plan.status}\``,
`- Updated: ${formatDateTime(plan.updatedAt)}`,
`- Execution session: ${formatText(plan.executionSessionId)}`,
`- Execution started: ${formatDateTime(plan.executionStartedAt)}`,
`- Last activity: ${formatDateTime(plan.executionLastActivityAt)}`,
`- Plan file: ${formatText(plan.planFilePath)}`,
'',
'## Summary',
'',
formatText(plan.summary),
'',
'## Rationale',
'',
formatText(plan.rationale),
'',
'## Latest summary',
'',
formatText(plan.latestSummary),
].filter(Boolean).join('\n');
}
function buildCronRunMarkdown(jobId, result) {
if (result?.reason === 'already_running' || result?.started === false) {
return [
'# Always-On',
'',
`Cron job \`${jobId}\` is already running.`,
'',
`Use \`/ao status cron ${jobId}\` to inspect the current state.`,
].join('\n');
}
return [
'# Always-On',
'',
`Started cron job \`${jobId}\` immediately.`,
'',
`Use \`/ao status cron ${jobId}\` to inspect the latest state.`,
].join('\n');
}
function buildNotFoundMarkdown(target, id) {
return [
'# Always-On',
'',
`No ${target} found with id \`${id}\`.`,
].join('\n');
}
function buildErrorMarkdown(message) {
return [
'# Always-On',
'',
message,
].join('\n');
}
function sortCronJobs(jobs) {
return [...jobs].sort((left, right) => right.createdAt - left.createdAt);
}
export async function executeAlwaysOnSlashCommand(args = [], context = {}) {
const project = getProjectContext(context);
if (!project) {
return buildResponse(
'Please select a project before using `/ao`.',
);
}
const parsed = parseAlwaysOnSlashArgs(args);
if (parsed.action === 'help') {
const content = parsed.error
? `${buildErrorMarkdown(parsed.error)}\n\n${buildUsageMarkdown()}`
: buildUsageMarkdown();
return buildResponse(content);
}
try {
if (parsed.action === 'list') {
if (parsed.target === 'cron') {
const overview = await getProjectCronJobsOverview(project.projectName);
return buildResponse(
buildCronListMarkdown(project.projectPath, sortCronJobs(overview.jobs || [])),
);
}
if (parsed.target === 'plan') {
const overview = await getProjectDiscoveryPlansOverview(project.projectName);
return buildResponse(
buildPlanListMarkdown(project.projectPath, overview.plans || []),
);
}
const [cronOverview, planOverview] = await Promise.all([
getProjectCronJobsOverview(project.projectName),
getProjectDiscoveryPlansOverview(project.projectName),
]);
return buildResponse(
buildCombinedListMarkdown(project.projectPath, {
jobs: sortCronJobs(cronOverview.jobs || []),
plans: planOverview.plans || [],
}),
);
}
if (parsed.action === 'status' && parsed.target === 'cron') {
const overview = await getProjectCronJobsOverview(project.projectName);
const job = (overview.jobs || []).find((candidate) => candidate.id === parsed.id);
if (!job) {
return buildResponse(buildNotFoundMarkdown('cron job', parsed.id));
}
return buildResponse(buildCronStatusMarkdown(project.projectPath, job));
}
if (parsed.action === 'status' && parsed.target === 'plan') {
const overview = await getProjectDiscoveryPlansOverview(project.projectName);
const plan = (overview.plans || []).find((candidate) => candidate.id === parsed.id);
if (!plan) {
return buildResponse(buildNotFoundMarkdown('discovery plan', parsed.id));
}
return buildResponse(buildPlanStatusMarkdown(project.projectPath, plan));
}
if (parsed.action === 'run' && parsed.target === 'cron') {
const gateway = await getPilotDeckGateway();
const result = await gateway.cronRunNow({ taskId: parsed.id });
if (result.reason === 'not_found') {
return buildResponse(buildNotFoundMarkdown('cron job', parsed.id));
}
return buildResponse(buildCronRunMarkdown(parsed.id, result));
}
if (parsed.action === 'run' && parsed.target === 'plan') {
const result = await rerunDiscoveryPlan(project.projectName, parsed.id);
return buildResponse(
`Plan \`${parsed.id}\` has been queued for re-execution (runId: \`${result.runId}\`).`,
);
}
return buildResponse(buildUsageMarkdown());
} catch (error) {
const message =
error instanceof Error && error.message.trim().length > 0
? error.message
: 'Always-On slash command failed.';
if (parsed.action === 'run' && parsed.target === 'plan' && /not found/i.test(message)) {
return buildResponse(buildNotFoundMarkdown('discovery plan', parsed.id));
}
return buildResponse(buildErrorMarkdown(message));
}
}
export {
buildUsageMarkdown as getAlwaysOnSlashUsage,
};