feat: add thinking option support and autonomous run handling

This commit is contained in:
Mohamed Boudra
2026-02-21 22:09:48 +07:00
parent c3c8103563
commit d7e2ab4437
35 changed files with 4701 additions and 593 deletions
+2
View File
@@ -27,6 +27,7 @@ export function createAgentCommand(): Command {
.option('-a, --all', 'Include archived agents')
.option('-g, --global', 'Legacy no-op (kept for compatibility)')
.option('--label <key=value>', 'Filter by label (can be used multiple times)', collectMultiple, [])
.option('--thinking <id>', 'Filter by thinking option ID')
.option('--json', 'Output in JSON format')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action(withOutput(runLsCommand))
@@ -39,6 +40,7 @@ export function createAgentCommand(): Command {
.option('--name <name>', 'Assign a name/title to the agent')
.option('--provider <provider>', 'Agent provider: claude | codex | opencode', 'claude')
.option('--model <model>', 'Model to use (e.g., claude-sonnet-4-20250514, claude-3-5-haiku-20241022)')
.option('--thinking <id>', 'Thinking option ID to use for this run')
.option('--mode <mode>', 'Provider-specific mode (e.g., plan, default, bypass)')
.option('--cwd <path>', 'Working directory (default: current)')
.option('--label <key=value>', 'Add label(s) to the agent (can be used multiple times)', collectMultiple, [])
+15 -1
View File
@@ -9,6 +9,7 @@ interface AgentInspect {
Name: string
Provider: string
Model: string
Thinking: string
Status: string
Mode: string
Cwd: string
@@ -84,6 +85,17 @@ function formatCost(costUsd: number): string {
return `$${costUsd.toFixed(2)}`
}
function normalizeModelId(value: string | null | undefined): string | null {
if (typeof value !== 'string') return null
const normalized = value.trim()
if (!normalized || normalized.toLowerCase() === 'default') return null
return normalized
}
function resolveModel(snapshot: AgentSnapshotPayload): string | null {
return normalizeModelId(snapshot.runtimeInfo?.model) ?? normalizeModelId(snapshot.model)
}
/** Convert agent snapshot to inspection data */
function toInspectData(snapshot: AgentSnapshotPayload): AgentInspect {
const lastUsage = snapshot.lastUsage
@@ -112,7 +124,8 @@ function toInspectData(snapshot: AgentSnapshotPayload): AgentInspect {
Id: snapshot.id,
Name: snapshot.title ?? '-',
Provider: snapshot.provider,
Model: snapshot.model ?? '-',
Model: resolveModel(snapshot) ?? '-',
Thinking: snapshot.effectiveThinkingOptionId ?? 'auto',
Status: snapshot.status,
Mode: snapshot.currentModeId ?? 'default',
Cwd: snapshot.cwd,
@@ -139,6 +152,7 @@ function toInspectRows(agent: AgentInspect): InspectRow[] {
{ key: 'Name', value: agent.Name },
{ key: 'Provider', value: agent.Provider },
{ key: 'Model', value: agent.Model },
{ key: 'Thinking', value: agent.Thinking },
{ key: 'Status', value: agent.Status },
{ key: 'Mode', value: agent.Mode },
{ key: 'Cwd', value: shortenPath(agent.Cwd) },
+52 -15
View File
@@ -9,6 +9,7 @@ export interface AgentListItem {
shortId: string
name: string
provider: string
thinking: string
status: string
cwd: string
created: string
@@ -35,6 +36,13 @@ function shortenPath(path: string): string {
return path
}
function normalizeModelId(modelId: string | null | undefined): string | null {
if (typeof modelId !== 'string') return null
const normalized = modelId.trim()
if (!normalized || normalized.toLowerCase() === 'default') return null
return normalized
}
/** Schema for agent ls output */
export const agentLsSchema: OutputSchema<AgentListItem> = {
idField: 'shortId',
@@ -42,6 +50,7 @@ export const agentLsSchema: OutputSchema<AgentListItem> = {
{ header: 'AGENT ID', field: 'shortId', width: 12 },
{ header: 'NAME', field: 'name', width: 20 },
{ header: 'PROVIDER', field: 'provider', width: 15 },
{ header: 'THINKING', field: 'thinking', width: 12 },
{
header: 'STATUS',
field: 'status',
@@ -60,11 +69,13 @@ export const agentLsSchema: OutputSchema<AgentListItem> = {
/** Transform agent snapshot to AgentListItem */
function toListItem(agent: AgentSnapshotPayload): AgentListItem {
const model = normalizeModelId(agent.runtimeInfo?.model) ?? normalizeModelId(agent.model)
return {
id: agent.id,
shortId: agent.id.slice(0, 7),
name: agent.title ?? '-',
provider: agent.model ? `${agent.provider}/${agent.model}` : agent.provider,
provider: model ? `${agent.provider}/${model}` : agent.provider,
thinking: agent.effectiveThinkingOptionId ?? 'auto',
status: agent.status,
cwd: shortenPath(agent.cwd),
created: relativeTime(agent.createdAt),
@@ -84,6 +95,8 @@ export interface AgentLsOptions extends CommandOptions {
cwd?: string
/** Filter by labels (key=value format) */
label?: string[]
/** Filter by thinking option ID */
thinking?: string
}
/**
@@ -111,8 +124,45 @@ export async function runLsCommand(
}
try {
const normalizedThinkingOptionId = options.thinking?.trim()
if (options.thinking !== undefined && !normalizedThinkingOptionId) {
const error: CommandError = {
code: 'INVALID_THINKING_OPTION',
message: '--thinking cannot be empty',
}
throw error
}
// Parse --label filters (key=value).
const labelFilters: Record<string, string> = {}
if (options.label) {
for (const labelStr of options.label) {
const eqIndex = labelStr.indexOf('=')
if (eqIndex !== -1) {
const key = labelStr.slice(0, eqIndex)
const value = labelStr.slice(eqIndex + 1)
labelFilters[key] = value
}
}
}
const daemonFilter: {
includeArchived?: boolean
labels?: Record<string, string>
thinkingOptionId?: string
} = {}
if (options.all) {
daemonFilter.includeArchived = true
}
if (Object.keys(labelFilters).length > 0) {
daemonFilter.labels = labelFilters
}
if (normalizedThinkingOptionId) {
daemonFilter.thinkingOptionId = normalizedThinkingOptionId
}
const fetchPayload = await client.fetchAgents({
filter: options.all ? { includeArchived: true } : undefined,
filter: Object.keys(daemonFilter).length > 0 ? daemonFilter : undefined,
})
let agents = fetchPayload.entries.map((entry) => entry.agent)
@@ -135,19 +185,6 @@ export async function runLsCommand(
})
}
// Parse --label filters (key=value).
const labelFilters: Record<string, string> = {}
if (options.label) {
for (const labelStr of options.label) {
const eqIndex = labelStr.indexOf('=')
if (eqIndex !== -1) {
const key = labelStr.slice(0, eqIndex)
const value = labelStr.slice(eqIndex + 1)
labelFilters[key] = value
}
}
}
// Apply label filtering only when explicitly requested.
if (Object.keys(labelFilters).length > 0) {
agents = agents.filter((a) => {
+13
View File
@@ -36,6 +36,7 @@ export interface AgentRunOptions extends CommandOptions {
name?: string
provider?: string
model?: string
thinking?: string
mode?: string
worktree?: string
base?: string
@@ -219,6 +220,16 @@ export async function runRunCommand(
try {
// Resolve working directory
const cwd = options.cwd ?? process.cwd()
const thinkingOptionId = options.thinking?.trim()
if (options.thinking !== undefined && !thinkingOptionId) {
const error: CommandError = {
code: 'INVALID_THINKING_OPTION',
message: '--thinking cannot be empty',
details:
'Provide a thinking option ID. Use "paseo provider models <provider> --thinking" to list valid IDs.',
}
throw error
}
// Process images if provided
let images: Array<{ data: string; mimeType: string }> | undefined
@@ -290,6 +301,7 @@ export async function runRunCommand(
title: options.name,
modeId: options.mode,
model: options.model,
thinkingOptionId,
initialPrompt: structuredPrompt,
images,
git,
@@ -387,6 +399,7 @@ export async function runRunCommand(
title: options.name,
modeId: options.mode,
model: options.model,
thinkingOptionId,
initialPrompt: prompt,
images,
git,