Agent Surface Changes
This commit is contained in:
@@ -218,6 +218,31 @@ function mockSlash() {
|
||||
this.json.options.push({ type: 5, name: o.name })
|
||||
return this
|
||||
}
|
||||
s.addIntegerOption = function (ofn) {
|
||||
const o = {
|
||||
name: '',
|
||||
setName(n) {
|
||||
this.name = n
|
||||
return this
|
||||
},
|
||||
setDescription() {
|
||||
return this
|
||||
},
|
||||
setRequired() {
|
||||
return this
|
||||
},
|
||||
setMinValue() {
|
||||
return this
|
||||
},
|
||||
setMaxValue() {
|
||||
return this
|
||||
}
|
||||
}
|
||||
ofn(o)
|
||||
this.json.options = this.json.options || []
|
||||
this.json.options.push({ type: 4, name: o.name })
|
||||
return this
|
||||
}
|
||||
fn(s)
|
||||
this.json.options.push({
|
||||
type: 1,
|
||||
@@ -1692,6 +1717,24 @@ test('Discord /agent is gated on config and runs the guest agent', async (t) =>
|
||||
t.ok(agentCmd, 'slash /agent exists')
|
||||
const subs = (agentCmd.options || []).filter((o) => o.type === 1).map((o) => o.name)
|
||||
t.ok(subs.indexOf('ask') >= 0 && subs.indexOf('reset') >= 0 && subs.indexOf('status') >= 0)
|
||||
for (const name of [
|
||||
'skills',
|
||||
'todos',
|
||||
'plan',
|
||||
'hooks',
|
||||
'history',
|
||||
'recap',
|
||||
'undo',
|
||||
'rewind',
|
||||
'compact',
|
||||
'export',
|
||||
'remember',
|
||||
'models',
|
||||
'config',
|
||||
'stop'
|
||||
]) {
|
||||
t.ok(subs.indexOf(name) >= 0, '/agent has ' + name)
|
||||
}
|
||||
const askSub = (agentCmd.options || []).find((o) => o.type === 1 && o.name === 'ask')
|
||||
const askPrompt = ((askSub && askSub.options) || []).find((o) => o.name === 'prompt')
|
||||
t.ok(askPrompt && askPrompt.required, '/agent ask prompt is required')
|
||||
@@ -1700,6 +1743,31 @@ test('Discord /agent is gated on config and runs the guest agent', async (t) =>
|
||||
((askSub && askSub.options) || []).some((o) => o.name === 'new' && o.type === 5),
|
||||
'/agent ask has optional new flag'
|
||||
)
|
||||
t.ok(
|
||||
((askSub && askSub.options) || []).some((o) => o.name === 'auto' && o.type === 5),
|
||||
'/agent ask has optional auto flag'
|
||||
)
|
||||
t.ok(
|
||||
((askSub && askSub.options) || []).some((o) => o.name === 'compact' && o.type === 5),
|
||||
'/agent ask has optional compact flag'
|
||||
)
|
||||
t.ok(
|
||||
((askSub && askSub.options) || []).some((o) => o.name === 'max_turns' && o.type === 4),
|
||||
'/agent ask has max_turns'
|
||||
)
|
||||
const rememberSub = (agentCmd.options || []).find((o) => o.type === 1 && o.name === 'remember')
|
||||
t.ok(
|
||||
((rememberSub && rememberSub.options) || []).some((o) => o.name === 'text' && o.required),
|
||||
'/agent remember text is required'
|
||||
)
|
||||
t.ok(
|
||||
cmds.settingsSpecs.some((s) => s.jsonKey === 'emergency_stop_mutations'),
|
||||
'settings expose emergency_stop_mutations'
|
||||
)
|
||||
t.ok(
|
||||
cmds.settingsSpecs.some((s) => s.jsonKey === 'todo_nudge_enabled'),
|
||||
'settings expose todo_nudge_enabled'
|
||||
)
|
||||
|
||||
const views = []
|
||||
const tree = {}
|
||||
@@ -1793,6 +1861,18 @@ test('Discord /agent is gated on config and runs the guest agent', async (t) =>
|
||||
),
|
||||
'Models button on HUD'
|
||||
)
|
||||
t.ok(
|
||||
(ready.components || []).some((row) =>
|
||||
(row.components || []).some((c) => c.custom_id === 'agent:inspect')
|
||||
),
|
||||
'Inspect menu on HUD'
|
||||
)
|
||||
t.ok(
|
||||
(ready.components || []).some((row) =>
|
||||
(row.components || []).some((c) => c.custom_id === 'agent:recap')
|
||||
),
|
||||
'Recap button on HUD'
|
||||
)
|
||||
t.ok(cmds.qvacChatModels.some((m) => m.id === 'QWEN3_8B_INST_Q4_K_M'))
|
||||
const parsed = cmds.parseOpenAiModels({
|
||||
data: [{ id: 'grok-4' }, { id: 'text-embedding-3-small' }]
|
||||
@@ -1892,8 +1972,10 @@ test('Discord /agent is gated on config and runs the guest agent', async (t) =>
|
||||
commandName: 'agent',
|
||||
options: {
|
||||
getSubcommand: () => 'ask',
|
||||
getString: (k) => (k === 'prompt' ? 'list /bin' : ''),
|
||||
getBoolean: (k) => (k === 'new' ? true : k === 'plan' ? true : null)
|
||||
getString: (k) => (k === 'prompt' ? 'list /bin' : k === 'model' ? 'QWEN3_4B_INST_Q4_K_M' : ''),
|
||||
getBoolean: (k) =>
|
||||
k === 'new' || k === 'plan' || k === 'auto' || k === 'compact' ? true : null,
|
||||
getInteger: (k) => (k === 'max_turns' ? 12 : null)
|
||||
},
|
||||
user: { id: 'ag1' },
|
||||
deferReply: async () => {
|
||||
@@ -1908,8 +1990,17 @@ test('Discord /agent is gated on config and runs the guest agent', async (t) =>
|
||||
})
|
||||
t.ok(lines.some((l) => /agent /.test(l.line) && /list \/bin/.test(l.line)))
|
||||
t.ok(
|
||||
lines.some((l) => /--new/.test(l.line) && /--plan/.test(l.line) && /list \/bin/.test(l.line)),
|
||||
'new + plan flags are passed through to /bin/agent'
|
||||
lines.some(
|
||||
(l) =>
|
||||
/--new/.test(l.line) &&
|
||||
/--plan/.test(l.line) &&
|
||||
/--auto/.test(l.line) &&
|
||||
/--compact/.test(l.line) &&
|
||||
/--max-turns 12/.test(l.line) &&
|
||||
/--model/.test(l.line) &&
|
||||
/list \/bin/.test(l.line)
|
||||
),
|
||||
'new + plan + auto + compact + max-turns + model are passed through to /bin/agent'
|
||||
)
|
||||
t.ok(lines.some((l) => l.timeoutMs > 60000), 'agent timeout longer than /r')
|
||||
t.ok(
|
||||
@@ -1957,6 +2048,68 @@ test('Discord /agent is gated on config and runs the guest agent', async (t) =>
|
||||
}
|
||||
)
|
||||
t.ok(denied[0] && /Access denied/.test(denied[0].content), 'whitelist still gates /agent')
|
||||
|
||||
const inspectLines = []
|
||||
ctx.execLine = async (line) => {
|
||||
inspectLines.push(String(line))
|
||||
ctx.console.log('## skills\n| id | name |\n| --- | --- |\n| demo | Demo |')
|
||||
ctx.exitCode = 0
|
||||
}
|
||||
const inspectViews = []
|
||||
await cmds.dispatchInteraction(ctx, {
|
||||
isChatInputCommand: () => true,
|
||||
commandName: 'agent',
|
||||
options: { getSubcommand: () => 'skills', getString: () => '' },
|
||||
user: { id: 'ag1' },
|
||||
reply: async (p) => {
|
||||
inspectViews.push(p)
|
||||
}
|
||||
})
|
||||
t.ok(
|
||||
inspectLines.some((l) => /^agent skills$/.test(l)),
|
||||
'skills inspect calls guest /bin/agent'
|
||||
)
|
||||
t.ok(/demo/.test(JSON.stringify(inspectViews[0] || {})), 'skills inspect shows output')
|
||||
|
||||
await cmds.dispatchInteraction(ctx, {
|
||||
isChatInputCommand: () => true,
|
||||
commandName: 'agent',
|
||||
options: {
|
||||
getSubcommand: () => 'remember',
|
||||
getString: (k) => (k === 'text' ? 'holesail keys live in ~/.holesail' : '')
|
||||
},
|
||||
user: { id: 'ag1' },
|
||||
reply: async (p) => {
|
||||
inspectViews.push(p)
|
||||
}
|
||||
})
|
||||
t.ok(
|
||||
inspectLines.some((l) => /agent remember/.test(l) && /holesail keys/.test(l)),
|
||||
'remember inspect quotes the fact'
|
||||
)
|
||||
|
||||
await cmds.dispatchInteraction(ctx, {
|
||||
isChatInputCommand: () => true,
|
||||
commandName: 'agent',
|
||||
options: {
|
||||
getSubcommand: () => 'rewind',
|
||||
getString: () => '',
|
||||
getInteger: (k) => (k === 'steps' ? 2 : null)
|
||||
},
|
||||
user: { id: 'ag1' },
|
||||
reply: async (p) => {
|
||||
inspectViews.push(p)
|
||||
}
|
||||
})
|
||||
t.ok(
|
||||
inspectLines.some((l) => /agent rewind/.test(l) && /['"]?2['"]?/.test(l)),
|
||||
'rewind passes steps'
|
||||
)
|
||||
|
||||
tree['~/.agent/config.json'] =
|
||||
'{"backend":"rest","provider":"groq","rest_api_key":"gsk","model":"x"}\n'
|
||||
const restNoUrl = cmds.agentReady(ctx, JSON.parse(tree['~/.agent/config.json']))
|
||||
t.absent(restNoUrl.ok, 'REST without base URL is not ready')
|
||||
})
|
||||
|
||||
test('Discord /run captures stdout, raw writes, and writeScreen (no TTY leak)', async (t) => {
|
||||
|
||||
Reference in New Issue
Block a user