feat(cli): add agent update command for name and labels
This commit is contained in:
@@ -16,6 +16,7 @@ import { runSendCommand } from './commands/agent/send.js'
|
|||||||
import { runInspectCommand } from './commands/agent/inspect.js'
|
import { runInspectCommand } from './commands/agent/inspect.js'
|
||||||
import { runWaitCommand } from './commands/agent/wait.js'
|
import { runWaitCommand } from './commands/agent/wait.js'
|
||||||
import { runAttachCommand } from './commands/agent/attach.js'
|
import { runAttachCommand } from './commands/agent/attach.js'
|
||||||
|
import { runUpdateCommand } from './commands/agent/update.js'
|
||||||
import { withOutput } from './output/index.js'
|
import { withOutput } from './output/index.js'
|
||||||
|
|
||||||
const VERSION = '0.1.0'
|
const VERSION = '0.1.0'
|
||||||
@@ -125,6 +126,21 @@ export function createCli(): Command {
|
|||||||
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
||||||
.action(withOutput(runWaitCommand))
|
.action(withOutput(runWaitCommand))
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('update')
|
||||||
|
.description('Update an agent (alias for "paseo agent update")')
|
||||||
|
.argument('<id>', 'Agent ID (or prefix)')
|
||||||
|
.option('--name <name>', "Update the agent's display name")
|
||||||
|
.option(
|
||||||
|
'--label <label>',
|
||||||
|
'Add/set label(s) on the agent (can be used multiple times or comma-separated)',
|
||||||
|
collectMultiple,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
.option('--json', 'Output in JSON format')
|
||||||
|
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
||||||
|
.action(withOutput(runUpdateCommand))
|
||||||
|
|
||||||
// Top-level local daemon shortcuts
|
// Top-level local daemon shortcuts
|
||||||
program.addCommand(daemonStartCommand())
|
program.addCommand(daemonStartCommand())
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { runSendCommand } from './send.js'
|
|||||||
import { runInspectCommand } from './inspect.js'
|
import { runInspectCommand } from './inspect.js'
|
||||||
import { runWaitCommand } from './wait.js'
|
import { runWaitCommand } from './wait.js'
|
||||||
import { runAttachCommand } from './attach.js'
|
import { runAttachCommand } from './attach.js'
|
||||||
|
import { runUpdateCommand } from './update.js'
|
||||||
import { withOutput } from '../../output/index.js'
|
import { withOutput } from '../../output/index.js'
|
||||||
|
|
||||||
export function createAgentCommand(): Command {
|
export function createAgentCommand(): Command {
|
||||||
@@ -120,5 +121,20 @@ export function createAgentCommand(): Command {
|
|||||||
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
||||||
.action(withOutput(runArchiveCommand))
|
.action(withOutput(runArchiveCommand))
|
||||||
|
|
||||||
|
agent
|
||||||
|
.command('update')
|
||||||
|
.description("Update an agent's metadata")
|
||||||
|
.argument('<id>', 'Agent ID (or prefix)')
|
||||||
|
.option('--name <name>', "Update the agent's display name")
|
||||||
|
.option(
|
||||||
|
'--label <label>',
|
||||||
|
'Add/set label(s) on the agent (can be used multiple times or comma-separated)',
|
||||||
|
collectMultiple,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
.option('--json', 'Output in JSON format')
|
||||||
|
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
|
||||||
|
.action(withOutput(runUpdateCommand))
|
||||||
|
|
||||||
return agent
|
return agent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import type { Command } from 'commander'
|
||||||
|
import { connectToDaemon, getDaemonHost } from '../../utils/client.js'
|
||||||
|
import type { CommandOptions, SingleResult, OutputSchema, CommandError } from '../../output/index.js'
|
||||||
|
|
||||||
|
/** Result type for agent update command */
|
||||||
|
export interface AgentUpdateResult {
|
||||||
|
agentId: string
|
||||||
|
name: string | null
|
||||||
|
labels: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Schema for update command output */
|
||||||
|
export const updateSchema: OutputSchema<AgentUpdateResult> = {
|
||||||
|
idField: 'agentId',
|
||||||
|
columns: [
|
||||||
|
{ header: 'AGENT ID', field: 'agentId' },
|
||||||
|
{ header: 'NAME', field: 'name' },
|
||||||
|
{ header: 'LABELS', field: 'labels' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentUpdateOptions extends CommandOptions {
|
||||||
|
name?: string
|
||||||
|
label?: string[]
|
||||||
|
host?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentUpdateCommandResult = SingleResult<AgentUpdateResult>
|
||||||
|
|
||||||
|
function parseLabelOptions(labels: string[] | undefined): Record<string, string> {
|
||||||
|
const parsed: Record<string, string> = {}
|
||||||
|
if (!labels) {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rawLabel of labels) {
|
||||||
|
for (const segment of rawLabel.split(',')) {
|
||||||
|
const label = segment.trim()
|
||||||
|
if (!label) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const eqIndex = label.indexOf('=')
|
||||||
|
if (eqIndex === -1) {
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'INVALID_LABEL',
|
||||||
|
message: `Invalid label format: ${label}`,
|
||||||
|
details: 'Labels must be in key=value format',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = label.slice(0, eqIndex).trim()
|
||||||
|
const value = label.slice(eqIndex + 1)
|
||||||
|
if (!key) {
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'INVALID_LABEL',
|
||||||
|
message: `Invalid label format: ${label}`,
|
||||||
|
details: 'Labels must include a non-empty key in key=value format',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLabels(labels: Record<string, string>): string {
|
||||||
|
const entries = Object.entries(labels)
|
||||||
|
if (entries.length === 0) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
return entries.map(([key, value]) => `${key}=${value}`).join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runUpdateCommand(
|
||||||
|
agentIdArg: string,
|
||||||
|
options: AgentUpdateOptions,
|
||||||
|
_command: Command
|
||||||
|
): Promise<AgentUpdateCommandResult> {
|
||||||
|
const host = getDaemonHost({ host: options.host as string | undefined })
|
||||||
|
|
||||||
|
// Validate arguments
|
||||||
|
if (!agentIdArg || agentIdArg.trim().length === 0) {
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'MISSING_AGENT_ID',
|
||||||
|
message: 'Agent ID is required',
|
||||||
|
details: 'Usage: paseo agent update <id> [--name <name>] [--label <key=value>]',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = options.name?.trim()
|
||||||
|
if (options.name !== undefined && !name) {
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'INVALID_NAME',
|
||||||
|
message: 'Name cannot be empty',
|
||||||
|
details: 'Use --name <name> with a non-empty value',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = parseLabelOptions(options.label)
|
||||||
|
if (!name && Object.keys(labels).length === 0) {
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'NO_CHANGES_PROVIDED',
|
||||||
|
message: 'Nothing to update',
|
||||||
|
details: 'Provide at least one of: --name <name>, --label <key=value>',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
let client
|
||||||
|
try {
|
||||||
|
client = await connectToDaemon({ host: options.host as string | undefined })
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'DAEMON_NOT_RUNNING',
|
||||||
|
message: `Cannot connect to daemon at ${host}: ${message}`,
|
||||||
|
details: 'Start the daemon with: paseo daemon start',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const agent = await client.fetchAgent(agentIdArg)
|
||||||
|
if (!agent) {
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'AGENT_NOT_FOUND',
|
||||||
|
message: `Agent not found: ${agentIdArg}`,
|
||||||
|
details: 'Use "paseo ls" to list available agents',
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
const agentId = agent.id
|
||||||
|
|
||||||
|
await client.updateAgent(agentId, {
|
||||||
|
...(name ? { name } : {}),
|
||||||
|
...(Object.keys(labels).length > 0 ? { labels } : {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const updated = await client.fetchAgent(agentId)
|
||||||
|
if (!updated) {
|
||||||
|
throw new Error(`Agent not found after update: ${agentId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'single',
|
||||||
|
data: {
|
||||||
|
agentId,
|
||||||
|
name: updated.title,
|
||||||
|
labels: formatLabels(updated.labels),
|
||||||
|
},
|
||||||
|
schema: updateSchema,
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
await client.close().catch(() => {})
|
||||||
|
|
||||||
|
// Re-throw CommandError as-is
|
||||||
|
if (err && typeof err === 'object' && 'code' in err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
const error: CommandError = {
|
||||||
|
code: 'UPDATE_FAILED',
|
||||||
|
message: `Failed to update agent: ${message}`,
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env npx tsx
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase 16: Agent Update Command Tests
|
||||||
|
*
|
||||||
|
* Tests the agent update command for metadata updates.
|
||||||
|
* Since daemon may not be running, we test:
|
||||||
|
* - Help and argument parsing
|
||||||
|
* - Validation for required update fields
|
||||||
|
* - Graceful daemon connection errors
|
||||||
|
* - Top-level alias support (`paseo update`)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import assert from 'node:assert'
|
||||||
|
import { $ } from 'zx'
|
||||||
|
import { mkdtemp, rm } from 'fs/promises'
|
||||||
|
import { tmpdir } from 'os'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
|
$.verbose = false
|
||||||
|
|
||||||
|
console.log('=== Agent Update Command Tests ===\n')
|
||||||
|
|
||||||
|
// Get random port that's definitely not in use (never 6767)
|
||||||
|
const port = 10000 + Math.floor(Math.random() * 50000)
|
||||||
|
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test 1: agent update --help shows options
|
||||||
|
{
|
||||||
|
console.log('Test 1: agent update --help shows options')
|
||||||
|
const result = await $`npx paseo agent update --help`.nothrow()
|
||||||
|
assert.strictEqual(result.exitCode, 0, 'agent update --help should exit 0')
|
||||||
|
assert(result.stdout.includes('--name'), 'help should mention --name flag')
|
||||||
|
assert(result.stdout.includes('--label'), 'help should mention --label flag')
|
||||||
|
assert(result.stdout.includes('--host'), 'help should mention --host option')
|
||||||
|
assert(result.stdout.includes('<id>'), 'help should mention required id argument')
|
||||||
|
console.log('✓ agent update --help shows options\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: agent update requires ID argument
|
||||||
|
{
|
||||||
|
console.log('Test 2: agent update requires ID argument')
|
||||||
|
const result =
|
||||||
|
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent update --name "New Name"`.nothrow()
|
||||||
|
assert.notStrictEqual(result.exitCode, 0, 'should fail without id')
|
||||||
|
const output = result.stdout + result.stderr
|
||||||
|
const hasError =
|
||||||
|
output.toLowerCase().includes('missing') ||
|
||||||
|
output.toLowerCase().includes('required') ||
|
||||||
|
output.toLowerCase().includes('argument') ||
|
||||||
|
output.toLowerCase().includes('id')
|
||||||
|
assert(hasError, 'error should mention missing argument')
|
||||||
|
console.log('✓ agent update requires ID argument\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: agent update requires at least one update field
|
||||||
|
{
|
||||||
|
console.log('Test 3: agent update requires update field')
|
||||||
|
const result =
|
||||||
|
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent update abc123`.nothrow()
|
||||||
|
assert.notStrictEqual(result.exitCode, 0, 'should fail without --name/--label')
|
||||||
|
const output = result.stdout + result.stderr
|
||||||
|
const hasError =
|
||||||
|
output.toLowerCase().includes('nothing to update') ||
|
||||||
|
output.toLowerCase().includes('name') ||
|
||||||
|
output.toLowerCase().includes('label')
|
||||||
|
assert(hasError, 'error should mention missing update fields')
|
||||||
|
console.log('✓ agent update requires update field\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: agent update handles daemon not running
|
||||||
|
{
|
||||||
|
console.log('Test 4: agent update handles daemon not running')
|
||||||
|
const result =
|
||||||
|
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent update abc123 --name "Renamed Agent"`.nothrow()
|
||||||
|
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
|
||||||
|
const output = result.stdout + result.stderr
|
||||||
|
const hasError =
|
||||||
|
output.toLowerCase().includes('daemon') ||
|
||||||
|
output.toLowerCase().includes('connect') ||
|
||||||
|
output.toLowerCase().includes('cannot')
|
||||||
|
assert(hasError, 'error should mention connection issue')
|
||||||
|
console.log('✓ agent update handles daemon not running\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: agent update accepts multiple/comma-separated labels
|
||||||
|
{
|
||||||
|
console.log('Test 5: agent update accepts multi-label syntax')
|
||||||
|
const result =
|
||||||
|
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent update abc123 --label ui=true,area=frontend --label priority=high`.nothrow()
|
||||||
|
const output = result.stdout + result.stderr
|
||||||
|
assert(!output.includes('unknown option'), 'should accept --label flag')
|
||||||
|
assert(!output.includes('error: option'), 'should not have option parsing error')
|
||||||
|
console.log('✓ agent update accepts multi-label syntax\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: agent --help shows update subcommand
|
||||||
|
{
|
||||||
|
console.log('Test 6: agent --help shows update subcommand')
|
||||||
|
const result = await $`npx paseo agent --help`.nothrow()
|
||||||
|
assert.strictEqual(result.exitCode, 0, 'agent --help should exit 0')
|
||||||
|
assert(result.stdout.includes('update'), 'help should mention update subcommand')
|
||||||
|
console.log('✓ agent --help shows update subcommand\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: top-level update alias --help works
|
||||||
|
{
|
||||||
|
console.log('Test 7: top-level update --help works')
|
||||||
|
const result = await $`npx paseo update --help`.nothrow()
|
||||||
|
assert.strictEqual(result.exitCode, 0, 'update --help should exit 0')
|
||||||
|
assert(result.stdout.includes('--name'), 'help should mention --name flag')
|
||||||
|
assert(result.stdout.includes('--label'), 'help should mention --label flag')
|
||||||
|
console.log('✓ top-level update --help works\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: top-level update alias accepts flags
|
||||||
|
{
|
||||||
|
console.log('Test 8: top-level update alias accepts flags')
|
||||||
|
const result =
|
||||||
|
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo update abc123 --name "Alias Name" --host localhost:${port}`.nothrow()
|
||||||
|
const output = result.stdout + result.stderr
|
||||||
|
assert(!output.includes('unknown option'), 'should accept top-level update flags')
|
||||||
|
assert(!output.includes('error: option'), 'should not have option parsing error')
|
||||||
|
console.log('✓ top-level update alias accepts flags\n')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Clean up temp directory
|
||||||
|
await rm(paseoHome, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('=== All agent update tests passed ===')
|
||||||
@@ -1074,6 +1074,40 @@ export class DaemonClient {
|
|||||||
return { archivedAt: result.archivedAt };
|
return { archivedAt: result.archivedAt };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateAgent(
|
||||||
|
agentId: string,
|
||||||
|
updates: { name?: string; labels?: Record<string, string> }
|
||||||
|
): Promise<void> {
|
||||||
|
const requestId = this.createRequestId();
|
||||||
|
const message = SessionInboundMessageSchema.parse({
|
||||||
|
type: "update_agent_request",
|
||||||
|
agentId,
|
||||||
|
...(updates.name !== undefined ? { name: updates.name } : {}),
|
||||||
|
...(updates.labels && Object.keys(updates.labels).length > 0
|
||||||
|
? { labels: updates.labels }
|
||||||
|
: {}),
|
||||||
|
requestId,
|
||||||
|
});
|
||||||
|
const payload = await this.sendRequest({
|
||||||
|
requestId,
|
||||||
|
message,
|
||||||
|
timeout: 10000,
|
||||||
|
options: { skipQueue: true },
|
||||||
|
select: (msg) => {
|
||||||
|
if (msg.type !== "update_agent_response") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (msg.payload.requestId !== requestId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return msg.payload;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!payload.accepted) {
|
||||||
|
throw new Error(payload.error ?? "updateAgent rejected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async resumeAgent(
|
async resumeAgent(
|
||||||
handle: AgentPersistenceHandle,
|
handle: AgentPersistenceHandle,
|
||||||
overrides?: Partial<AgentSessionConfig>
|
overrides?: Partial<AgentSessionConfig>
|
||||||
|
|||||||
@@ -527,6 +527,16 @@ export class AgentManager {
|
|||||||
this.emitState(agent);
|
this.emitState(agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setLabels(
|
||||||
|
agentId: string,
|
||||||
|
labels: Record<string, string>
|
||||||
|
): Promise<void> {
|
||||||
|
const agent = this.requireAgent(agentId);
|
||||||
|
agent.labels = { ...agent.labels, ...labels };
|
||||||
|
await this.persistSnapshot(agent);
|
||||||
|
this.emitState(agent);
|
||||||
|
}
|
||||||
|
|
||||||
notifyAgentState(agentId: string): void {
|
notifyAgentState(agentId: string): void {
|
||||||
const agent = this.agents.get(agentId);
|
const agent = this.agents.get(agentId);
|
||||||
if (!agent || agent.internal) {
|
if (!agent || agent.internal) {
|
||||||
|
|||||||
@@ -1125,6 +1125,15 @@ export class Session {
|
|||||||
await this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
|
await this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "update_agent_request":
|
||||||
|
await this.handleUpdateAgentRequest(
|
||||||
|
msg.agentId,
|
||||||
|
msg.name,
|
||||||
|
msg.labels,
|
||||||
|
msg.requestId
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
case "set_voice_mode":
|
case "set_voice_mode":
|
||||||
await this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
|
await this.handleSetVoiceMode(msg.enabled, msg.agentId, msg.requestId);
|
||||||
break;
|
break;
|
||||||
@@ -1506,6 +1515,88 @@ export class Session {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleUpdateAgentRequest(
|
||||||
|
agentId: string,
|
||||||
|
name: string | undefined,
|
||||||
|
labels: Record<string, string> | undefined,
|
||||||
|
requestId: string
|
||||||
|
): Promise<void> {
|
||||||
|
this.sessionLogger.info(
|
||||||
|
{ agentId, requestId, hasName: typeof name === "string", labelCount: labels ? Object.keys(labels).length : 0 },
|
||||||
|
"session: update_agent_request"
|
||||||
|
);
|
||||||
|
|
||||||
|
const normalizedName = name?.trim();
|
||||||
|
const normalizedLabels =
|
||||||
|
labels && Object.keys(labels).length > 0 ? labels : undefined;
|
||||||
|
|
||||||
|
if (!normalizedName && !normalizedLabels) {
|
||||||
|
this.emit({
|
||||||
|
type: "update_agent_response",
|
||||||
|
payload: {
|
||||||
|
requestId,
|
||||||
|
agentId,
|
||||||
|
accepted: false,
|
||||||
|
error: "Nothing to update (provide name and/or labels)",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const liveAgent = this.agentManager.getAgent(agentId);
|
||||||
|
if (liveAgent) {
|
||||||
|
if (normalizedName) {
|
||||||
|
await this.agentManager.setTitle(agentId, normalizedName);
|
||||||
|
}
|
||||||
|
if (normalizedLabels) {
|
||||||
|
await this.agentManager.setLabels(agentId, normalizedLabels);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const existing = await this.agentStorage.get(agentId);
|
||||||
|
if (!existing) {
|
||||||
|
throw new Error(`Agent not found: ${agentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.agentStorage.upsert({
|
||||||
|
...existing,
|
||||||
|
...(normalizedName ? { title: normalizedName } : {}),
|
||||||
|
...(normalizedLabels
|
||||||
|
? { labels: { ...existing.labels, ...normalizedLabels } }
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit({
|
||||||
|
type: "update_agent_response",
|
||||||
|
payload: { requestId, agentId, accepted: true, error: null },
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
this.sessionLogger.error(
|
||||||
|
{ err: error, agentId, requestId },
|
||||||
|
"session: update_agent_request error"
|
||||||
|
);
|
||||||
|
this.emit({
|
||||||
|
type: "activity_log",
|
||||||
|
payload: {
|
||||||
|
id: uuidv4(),
|
||||||
|
timestamp: new Date(),
|
||||||
|
type: "error",
|
||||||
|
content: `Failed to update agent: ${error.message}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.emit({
|
||||||
|
type: "update_agent_response",
|
||||||
|
payload: {
|
||||||
|
requestId,
|
||||||
|
agentId,
|
||||||
|
accepted: false,
|
||||||
|
error: error?.message ? String(error.message) : "Failed to update agent",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle voice mode toggle
|
* Handle voice mode toggle
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -422,6 +422,14 @@ export const ArchiveAgentRequestMessageSchema = z.object({
|
|||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const UpdateAgentRequestMessageSchema = z.object({
|
||||||
|
type: z.literal("update_agent_request"),
|
||||||
|
agentId: z.string(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
labels: z.record(z.string()).optional(),
|
||||||
|
requestId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
export const SetVoiceModeMessageSchema = z.object({
|
export const SetVoiceModeMessageSchema = z.object({
|
||||||
type: z.literal("set_voice_mode"),
|
type: z.literal("set_voice_mode"),
|
||||||
enabled: z.boolean(),
|
enabled: z.boolean(),
|
||||||
@@ -660,6 +668,16 @@ export const SetAgentThinkingResponseMessageSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const UpdateAgentResponseMessageSchema = z.object({
|
||||||
|
type: z.literal("update_agent_response"),
|
||||||
|
payload: z.object({
|
||||||
|
requestId: z.string(),
|
||||||
|
agentId: z.string(),
|
||||||
|
accepted: z.boolean(),
|
||||||
|
error: z.string().nullable(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const SetVoiceModeResponseMessageSchema = z.object({
|
export const SetVoiceModeResponseMessageSchema = z.object({
|
||||||
type: z.literal("set_voice_mode_response"),
|
type: z.literal("set_voice_mode_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
@@ -971,6 +989,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
|||||||
UnsubscribeAgentUpdatesMessageSchema,
|
UnsubscribeAgentUpdatesMessageSchema,
|
||||||
DeleteAgentRequestMessageSchema,
|
DeleteAgentRequestMessageSchema,
|
||||||
ArchiveAgentRequestMessageSchema,
|
ArchiveAgentRequestMessageSchema,
|
||||||
|
UpdateAgentRequestMessageSchema,
|
||||||
SetVoiceModeMessageSchema,
|
SetVoiceModeMessageSchema,
|
||||||
SendAgentMessageRequestSchema,
|
SendAgentMessageRequestSchema,
|
||||||
WaitForFinishRequestSchema,
|
WaitForFinishRequestSchema,
|
||||||
@@ -1809,6 +1828,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
|||||||
SetAgentModeResponseMessageSchema,
|
SetAgentModeResponseMessageSchema,
|
||||||
SetAgentModelResponseMessageSchema,
|
SetAgentModelResponseMessageSchema,
|
||||||
SetAgentThinkingResponseMessageSchema,
|
SetAgentThinkingResponseMessageSchema,
|
||||||
|
UpdateAgentResponseMessageSchema,
|
||||||
WaitForFinishResponseMessageSchema,
|
WaitForFinishResponseMessageSchema,
|
||||||
AgentPermissionRequestMessageSchema,
|
AgentPermissionRequestMessageSchema,
|
||||||
AgentPermissionResolvedMessageSchema,
|
AgentPermissionResolvedMessageSchema,
|
||||||
@@ -1878,6 +1898,7 @@ export type SendAgentMessageResponseMessage = z.infer<
|
|||||||
export type SetVoiceModeResponseMessage = z.infer<
|
export type SetVoiceModeResponseMessage = z.infer<
|
||||||
typeof SetVoiceModeResponseMessageSchema
|
typeof SetVoiceModeResponseMessageSchema
|
||||||
>;
|
>;
|
||||||
|
export type UpdateAgentResponseMessage = z.infer<typeof UpdateAgentResponseMessageSchema>;
|
||||||
export type WaitForFinishResponseMessage = z.infer<
|
export type WaitForFinishResponseMessage = z.infer<
|
||||||
typeof WaitForFinishResponseMessageSchema
|
typeof WaitForFinishResponseMessageSchema
|
||||||
>;
|
>;
|
||||||
@@ -1917,6 +1938,7 @@ export type SpeechModelsDownloadRequestMessage = z.infer<
|
|||||||
>;
|
>;
|
||||||
export type ResumeAgentRequestMessage = z.infer<typeof ResumeAgentRequestMessageSchema>;
|
export type ResumeAgentRequestMessage = z.infer<typeof ResumeAgentRequestMessageSchema>;
|
||||||
export type DeleteAgentRequestMessage = z.infer<typeof DeleteAgentRequestMessageSchema>;
|
export type DeleteAgentRequestMessage = z.infer<typeof DeleteAgentRequestMessageSchema>;
|
||||||
|
export type UpdateAgentRequestMessage = z.infer<typeof UpdateAgentRequestMessageSchema>;
|
||||||
export type InitializeAgentRequestMessage = z.infer<typeof InitializeAgentRequestMessageSchema>;
|
export type InitializeAgentRequestMessage = z.infer<typeof InitializeAgentRequestMessageSchema>;
|
||||||
export type SetAgentModeRequestMessage = z.infer<typeof SetAgentModeRequestMessageSchema>;
|
export type SetAgentModeRequestMessage = z.infer<typeof SetAgentModeRequestMessageSchema>;
|
||||||
export type SetAgentModelRequestMessage = z.infer<typeof SetAgentModelRequestMessageSchema>;
|
export type SetAgentModelRequestMessage = z.infer<typeof SetAgentModelRequestMessageSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user