Background only the warm-up, and use the framework's own parameter name
The tool backgrounded EVERY long action and exposed an inverted `foreground` flag. dsh-tool-bash does neither: its parameter is `run_in_background`, it defaults to foreground, and the model opts in. That difference is what made a finished job go uncollected. The evidence, from this session's own log: the only slopo job that ever ran was slopo-1, and the agent KILLED it — `kill()` sets `reported = true`, which suppresses the completion notice by design, because a caller who asked to stop something does not want to be told it stopped. The agent then worked around the tool entirely and re-ran slopo through bash, which is where the six notices in that session came from. Nothing was broken; the tool was asking the caller to depend on a best-effort signal. So the query actions return their report directly. Only `prepare` — index plus embed, the genuinely long and watchable step, and the one the user asked to be able to watch — backgrounds by default. Any action can still be backgrounded with `run_in_background: true`. The render now names the collect call explicitly (`job_output` for that id, with `wait: true` when blocked on it) instead of the vaguer "read the job output when it settles", so a caller that is not woken still has an unambiguous next step. I checked the waiter path while I was in there: a timed-out `wait` decrements `waiters` in a `finally`, so it does not leak and does not suppress the notice. That was my first hypothesis and it is wrong — worth recording, since it was the plausible one.
This commit is contained in:
1 parent
2d7a7d1e73
commit
5ff10eaefd
3 files changed
+71
-30
No files matched your search
@@ -70,7 +70,16 @@ const SKILL_NAMES = ['slopo-review', 'slopo-analyze-ignore', 'slopo-analyze-one'
|
||||
const SKILLS_DIR = fileURLToPath(new URL('./skills/', import.meta.url))
|
||||
|
||||
/** Long-running actions, in the order the tool description advertises them. */
|
||||
const BACKGROUND_ACTIONS = new Set(['prepare', 'review', 'analyze', 'analyze-one'])
|
||||
/**
|
||||
* Actions that default to a background job.
|
||||
*
|
||||
* Only the warm-up. `index` plus `embed` is the long, watchable step — the one
|
||||
* the user asked to be able to watch — while a review finds a warm index and
|
||||
* returns its report directly. Named after dsh-tool-bash's `run_in_background`,
|
||||
* because a model's prior is that backgrounding is something it ASKS for rather
|
||||
* than the default.
|
||||
*/
|
||||
const BACKGROUND_BY_DEFAULT = new Set(['prepare'])
|
||||
|
||||
/** Every configuration field this plugin exposes. */
|
||||
const SETTING_KEYS = [
|
||||
@@ -386,9 +395,9 @@ export function apply(ctx, config) {
|
||||
'dsh-slopo settings; "prepare" runs the local index and then embeds (the slow, possibly costly ' +
|
||||
'step); "review" finds similar code involving Git changes; "analyze" triages the whole codebase; ' +
|
||||
'"analyze-one" reports a single cluster by hash; "update" upgrades the slopo CLI. Slopo is ' +
|
||||
'installed and driven as a separate process. Long actions run as background jobs because ' +
|
||||
'embedding a repository takes minutes and slopo writes its progress to a log file rather than ' +
|
||||
'to stdout.',
|
||||
'installed and driven as a separate process. "prepare" starts a background job by default, because ' +
|
||||
'indexing and embedding a repository takes minutes and slopo writes its progress to a log file rather ' +
|
||||
'than to stdout; everything else returns its report directly.',
|
||||
parameters: {
|
||||
action: {
|
||||
type: 'string',
|
||||
@@ -398,9 +407,11 @@ export function apply(ctx, config) {
|
||||
},
|
||||
base: { type: 'string', description: 'Git ref to compare against for "review". Defaults to HEAD.' },
|
||||
cluster: { type: 'string', description: 'Cluster hash for "analyze-one". Omit for the next cluster.' },
|
||||
foreground: {
|
||||
run_in_background: {
|
||||
type: 'boolean',
|
||||
description: 'Run a long action inline instead of as a background job. Defaults to false.',
|
||||
description:
|
||||
'Return a job id instead of the result, for a run long enough to watch. Defaults to true for ' +
|
||||
'"prepare" (the warm-up) and false for everything else, which is quick once the index is warm.',
|
||||
},
|
||||
config: {
|
||||
type: 'json',
|
||||
@@ -417,7 +428,9 @@ export function apply(ctx, config) {
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Started slopo ${value.action} as background job ${value.jobId}.${log} Read the job output when it settles.`,
|
||||
text:
|
||||
`Started slopo ${value.action} as background job ${value.jobId}.${log} You will be notified when ` +
|
||||
`it settles; collect it with job_output for "${value.jobId}" (add wait: true if you are blocked on it).`,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -740,7 +753,12 @@ export function apply(ctx, config) {
|
||||
details: { counted, excludes, maxIndexUnits: Number(settings.maxIndexUnits) },
|
||||
})
|
||||
|
||||
const wantsBackground = args.foreground !== true && BACKGROUND_ACTIONS.has(args.action)
|
||||
// Background is for the long warm-up, not for every action. A completion
|
||||
// notice is best-effort: a KILLED job deliberately produces none, and an
|
||||
// agent whose turn has ended may have the message injected rather than be
|
||||
// woken for it. A review that returns its report directly is worth more
|
||||
// than a job id the caller has to remember to collect.
|
||||
const wantsBackground = args.run_in_background ?? BACKGROUND_BY_DEFAULT.has(args.action)
|
||||
const jobs = ctx.get('jobs')
|
||||
|
||||
if (wantsBackground && jobs !== undefined) {
|
||||
|
||||
Reference in new issue
Block a user