pi-herdr-agents 3.0.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +19 -1
- package/CONTEXT.md +33 -5
- package/README.md +166 -14
- package/RELEASING.md +4 -2
- package/docs/README.md +9 -0
- package/docs/worktree-subagents.md +1 -0
- package/maestro/adapters/pi/launch.ts +26 -4
- package/maestro/adapters/pi/pi-harness-adapter.ts +4 -0
- package/maestro/core/config/model-config.ts +83 -10
- package/maestro/core/config/sidebar-config.ts +63 -0
- package/maestro/core/config/task-model-init.ts +208 -13
- package/maestro/core/config/task-model-types.ts +19 -1
- package/maestro/core/config/type-guards.ts +5 -0
- package/maestro/runtime/delegated-marker.ts +206 -0
- package/maestro/runtime/index.ts +2 -1
- package/maestro/runtime/pi-run-session.ts +58 -7
- package/maestro/runtime/task-model-init.ts +106 -9
- package/maestro/surfaces/herdr/herdr-socket.ts +165 -0
- package/package.json +6 -6
- package/pi-extension/subagents/index.ts +159 -13
- package/plugins/sidebar/README.md +242 -0
- package/plugins/sidebar/herdr-plugin.toml +21 -0
- package/plugins/sidebar/sidebar.mjs +238 -0
- package/skills/pi-herdr-agents/SKILL.md +10 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AgentToolResult,
|
|
3
3
|
ExtensionAPI,
|
|
4
|
+
ExtensionCommandContext,
|
|
4
5
|
ExtensionContext,
|
|
5
6
|
SessionShutdownEvent,
|
|
6
7
|
} from "@earendil-works/pi-coding-agent";
|
|
@@ -19,8 +20,9 @@ import { randomUUID } from "node:crypto";
|
|
|
19
20
|
import type { RunCancellation, Task } from "../../maestro/core/types.ts";
|
|
20
21
|
import {
|
|
21
22
|
createDefaultRunSession,
|
|
22
|
-
|
|
23
|
+
startTaskModelInit,
|
|
23
24
|
observePiActivity,
|
|
25
|
+
type TaskModelInitOutcome,
|
|
24
26
|
type PiRunSession,
|
|
25
27
|
type PiRunSessionInfrastructure,
|
|
26
28
|
type PiRunRecord,
|
|
@@ -36,6 +38,7 @@ import {
|
|
|
36
38
|
type CancelReport,
|
|
37
39
|
} from "../../maestro/runtime/index.ts";
|
|
38
40
|
import { loadSupervisionConfig } from "../../maestro/core/config/supervision-config.ts";
|
|
41
|
+
import { loadSidebarConfig } from "../../maestro/core/config/sidebar-config.ts";
|
|
39
42
|
import {
|
|
40
43
|
discoverAgentCatalog as discoverCoreAgentCatalog,
|
|
41
44
|
ROLE_PACK_DISCOVERY_EVENT,
|
|
@@ -60,9 +63,17 @@ import { wrapPiModelRegistry } from "./model-registry.ts";
|
|
|
60
63
|
import {
|
|
61
64
|
loadModelConfig,
|
|
62
65
|
MISSING_CONFIG_REVISION,
|
|
66
|
+
parseRankingBasis,
|
|
67
|
+
readModelConfigSnapshot,
|
|
63
68
|
resolveModelDefault,
|
|
64
69
|
writeTaskModelConfig,
|
|
65
70
|
} from "../../maestro/core/config/model-config.ts";
|
|
71
|
+
import {
|
|
72
|
+
type OfferCallback,
|
|
73
|
+
type TaskModelBrief,
|
|
74
|
+
TASK_MODEL_INIT_APPROVAL_EVENT,
|
|
75
|
+
TASK_MODEL_INIT_START_EVENT,
|
|
76
|
+
} from "../../maestro/core/config/task-model-init.ts";
|
|
66
77
|
import {
|
|
67
78
|
TASK_CATEGORIES,
|
|
68
79
|
TASK_CATEGORY_DESCRIPTIONS,
|
|
@@ -98,6 +109,7 @@ import { isSubagentActivityScope } from "../../maestro/core/activity.ts";
|
|
|
98
109
|
import type { SubagentActivityState } from "../../maestro/core/types.ts";
|
|
99
110
|
import {
|
|
100
111
|
isFiniteNumber,
|
|
112
|
+
isFunction,
|
|
101
113
|
isPlainObject,
|
|
102
114
|
isString,
|
|
103
115
|
} from "../../maestro/core/config/type-guards.ts";
|
|
@@ -344,6 +356,47 @@ function discoverAgentDefinitions(
|
|
|
344
356
|
return discoverAgentCatalog(pi).agents;
|
|
345
357
|
}
|
|
346
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Payload of TASK_MODEL_INIT_APPROVAL_EVENT. `context` is the live context of
|
|
361
|
+
* the command that started init. It passes between trusted in-process
|
|
362
|
+
* extensions and is current only while the request is emitted.
|
|
363
|
+
*/
|
|
364
|
+
interface TaskModelApprovalRequest {
|
|
365
|
+
apiVersion: 1;
|
|
366
|
+
brief: TaskModelBrief;
|
|
367
|
+
context: ExtensionCommandContext;
|
|
368
|
+
offer: OfferCallback;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function hasMethods(value: any, names: readonly string[]): boolean {
|
|
372
|
+
return value != null && names.every((name) => isFunction(value[name]));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Read a TASK_MODEL_INIT_START_EVENT v1 payload. A v1 request whose context or
|
|
377
|
+
* preferences cannot run init still gets an offer, so its emitter reports why
|
|
378
|
+
* instead of concluding that no host is loaded.
|
|
379
|
+
*/
|
|
380
|
+
function parseInitStartRequest(raw: any):
|
|
381
|
+
| {
|
|
382
|
+
offer: OfferCallback;
|
|
383
|
+
input?: { context: ExtensionCommandContext; preferences: string };
|
|
384
|
+
}
|
|
385
|
+
| undefined {
|
|
386
|
+
if (raw?.apiVersion !== 1 || !isFunction(raw.offer)) return undefined;
|
|
387
|
+
const { offer, context, preferences } = raw;
|
|
388
|
+
const usable =
|
|
389
|
+
isString(preferences) &&
|
|
390
|
+
hasMethods(context, ["isIdle", "hasPendingMessages"]) &&
|
|
391
|
+
hasMethods(context.modelRegistry, ["getAvailable"]);
|
|
392
|
+
return {
|
|
393
|
+
offer: (value) => offer.call(raw, value),
|
|
394
|
+
// The members init reads were checked above; the rest is the emitting
|
|
395
|
+
// command's own live context.
|
|
396
|
+
input: usable ? { context, preferences } : undefined,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
347
400
|
function missingRoleMessage(agentName: string): string {
|
|
348
401
|
return `Agent "${agentName}" was not found. pi-herdr-agents ships no roles: define it in .pi/agents or the global agents directory, install a role pack that provides it, or omit agent for a bare launch.`;
|
|
349
402
|
}
|
|
@@ -521,6 +574,10 @@ const supervisionConfig = loadSupervisionConfig(
|
|
|
521
574
|
getSubagentsConfigDir(),
|
|
522
575
|
getSubagentsConfigExamplePath(),
|
|
523
576
|
);
|
|
577
|
+
const sidebarConfig = loadSidebarConfig(
|
|
578
|
+
getSubagentsConfigDir(),
|
|
579
|
+
getSubagentsConfigExamplePath(),
|
|
580
|
+
);
|
|
524
581
|
|
|
525
582
|
const MAX_RESULT_PRESENTATION_CHARS = 16_000;
|
|
526
583
|
const MAX_SESSION_REFERENCE_CHARS = 10_000;
|
|
@@ -2395,6 +2452,7 @@ export default function subagentsExtension(
|
|
|
2395
2452
|
configExamplePath: getSubagentsConfigExamplePath(),
|
|
2396
2453
|
roles: [],
|
|
2397
2454
|
forcePolling: supervisionConfig.forcePolling,
|
|
2455
|
+
sidebarMarkers: sidebarConfig.enabled,
|
|
2398
2456
|
infrastructure: options.infrastructure,
|
|
2399
2457
|
getLaunchSnapshot() {
|
|
2400
2458
|
const ctx = runtime.latestCtx;
|
|
@@ -2616,7 +2674,7 @@ export default function subagentsExtension(
|
|
|
2616
2674
|
pi.registerTool({
|
|
2617
2675
|
name: "subagents_write_task_models",
|
|
2618
2676
|
label: "Write task model preferences",
|
|
2619
|
-
description: `Validate and atomically replace models.tasks and models.tasksMeta in the durable Pi agent config, preserving unrelated settings. Supported categories: ${TASK_CATEGORIES.join(", ")}. Partial nonempty categories are accepted; omitted categories are removed. Rejects duplicate exact refs within a category. Review the active authenticated registry and existing preferences first. Optional expectedConfigRevision makes the write conditional: it fails without replacing configuration when the config file no longer matches the revision the proposal was read from. Cooperating writes are serialized and fail on contention rather than waiting. Returns normalized saved preferences, missing categories,
|
|
2677
|
+
description: `Validate and atomically replace models.tasks and models.tasksMeta in the durable Pi agent config, preserving unrelated settings. Supported categories: ${TASK_CATEGORIES.join(", ")}. Partial nonempty categories are accepted; omitted categories are removed. Rejects duplicate exact refs within a category. Review the active authenticated registry and existing preferences first. Optional expectedConfigRevision makes the write conditional: it fails without replacing configuration when the config file no longer matches the revision the proposal was read from. Optional basis records what the ranking rests on; its kind must equal tasksMeta.method, and it is returned but not saved. Cooperating writes are serialized and fail on contention rather than waiting. Returns normalized saved preferences, missing categories, the configRevision of the written file, and any basis; reload required.`,
|
|
2620
2678
|
parameters: Type.Object({
|
|
2621
2679
|
tasks: Type.Object(
|
|
2622
2680
|
Object.fromEntries(
|
|
@@ -2650,6 +2708,37 @@ export default function subagentsExtension(
|
|
|
2650
2708
|
},
|
|
2651
2709
|
),
|
|
2652
2710
|
),
|
|
2711
|
+
basis: Type.Optional(
|
|
2712
|
+
Type.Union(
|
|
2713
|
+
[
|
|
2714
|
+
Type.Object(
|
|
2715
|
+
{ kind: Type.Literal("registry-only") },
|
|
2716
|
+
{ additionalProperties: false },
|
|
2717
|
+
),
|
|
2718
|
+
Type.Object(
|
|
2719
|
+
{
|
|
2720
|
+
kind: Type.Literal("research"),
|
|
2721
|
+
sources: Type.Array(
|
|
2722
|
+
Type.Object(
|
|
2723
|
+
{
|
|
2724
|
+
url: Type.String({ minLength: 1 }),
|
|
2725
|
+
influence: Type.String({ minLength: 1 }),
|
|
2726
|
+
},
|
|
2727
|
+
{ additionalProperties: false },
|
|
2728
|
+
),
|
|
2729
|
+
{ minItems: 1 },
|
|
2730
|
+
),
|
|
2731
|
+
uncertainty: Type.String({ minLength: 1 }),
|
|
2732
|
+
},
|
|
2733
|
+
{ additionalProperties: false },
|
|
2734
|
+
),
|
|
2735
|
+
],
|
|
2736
|
+
{
|
|
2737
|
+
description:
|
|
2738
|
+
'What the ranking rests on, as submitted by the proposer and not independently verified: {"kind":"registry-only"}, or {"kind":"research"} with the http(s) sources consulted in this run, how each informed the ranking, and the remaining uncertainty. kind must equal tasksMeta.method. Not saved. Omit for a legacy write.',
|
|
2739
|
+
},
|
|
2740
|
+
),
|
|
2741
|
+
),
|
|
2653
2742
|
}),
|
|
2654
2743
|
execute: async (_id, params, _signal, _update, ctx) => {
|
|
2655
2744
|
const registry = wrapPiModelRegistry(ctx.modelRegistry);
|
|
@@ -2657,6 +2746,10 @@ export default function subagentsExtension(
|
|
|
2657
2746
|
const tasks = params.tasks as TaskPreferences;
|
|
2658
2747
|
// SAFETY: TypeBox validates the tool payload; the write seam performs stricter schema validation.
|
|
2659
2748
|
const tasksMeta = params.tasksMeta as TaskPreferencesMeta;
|
|
2749
|
+
// Present-but-invalid values (including null) fail before anything is written.
|
|
2750
|
+
const basis = Object.hasOwn(params, "basis")
|
|
2751
|
+
? parseRankingBasis(params.basis, tasksMeta.method)
|
|
2752
|
+
: undefined;
|
|
2660
2753
|
const saved = writeTaskModelConfig(
|
|
2661
2754
|
getSubagentsConfigPath(),
|
|
2662
2755
|
getSubagentsConfigExamplePath(),
|
|
@@ -2673,14 +2766,15 @@ export default function subagentsExtension(
|
|
|
2673
2766
|
? { expectedConfigRevision: params.expectedConfigRevision }
|
|
2674
2767
|
: {},
|
|
2675
2768
|
);
|
|
2769
|
+
const details = basis ? { ...saved, basis } : saved;
|
|
2676
2770
|
return {
|
|
2677
2771
|
content: [
|
|
2678
2772
|
{
|
|
2679
2773
|
type: "text",
|
|
2680
|
-
text: `Wrote task model preferences. Reload required.\n${JSON.stringify(
|
|
2774
|
+
text: `Wrote task model preferences. Reload required.\n${JSON.stringify(details, null, 2)}`,
|
|
2681
2775
|
},
|
|
2682
2776
|
],
|
|
2683
|
-
details
|
|
2777
|
+
details,
|
|
2684
2778
|
};
|
|
2685
2779
|
},
|
|
2686
2780
|
});
|
|
@@ -3327,21 +3421,73 @@ export default function subagentsExtension(
|
|
|
3327
3421
|
},
|
|
3328
3422
|
});
|
|
3329
3423
|
|
|
3330
|
-
if (!process.env.PI_SUBAGENT_ID)
|
|
3424
|
+
if (!process.env.PI_SUBAGENT_ID) {
|
|
3425
|
+
const runTaskModelInit = (
|
|
3426
|
+
ctx: ExtensionCommandContext,
|
|
3427
|
+
preferences: string,
|
|
3428
|
+
): TaskModelInitOutcome => {
|
|
3429
|
+
if (!ctx.isIdle() || ctx.hasPendingMessages())
|
|
3430
|
+
return {
|
|
3431
|
+
kind: "not-started",
|
|
3432
|
+
reason:
|
|
3433
|
+
"a turn is in progress or messages are queued. Nothing was queued; retry when idle",
|
|
3434
|
+
};
|
|
3435
|
+
// Pi refuses a prompt without a model only after submission; refuse first.
|
|
3436
|
+
if (!ctx.model)
|
|
3437
|
+
return {
|
|
3438
|
+
kind: "not-started",
|
|
3439
|
+
reason:
|
|
3440
|
+
"no model is selected, so Pi cannot run the init prompt. Select a model, then retry",
|
|
3441
|
+
};
|
|
3442
|
+
return startTaskModelInit({
|
|
3443
|
+
preferences,
|
|
3444
|
+
readConfig: () => readModelConfigSnapshot(getSubagentsConfigDir()),
|
|
3445
|
+
projectActiveRegistry: (project) => project(ctx.modelRegistry),
|
|
3446
|
+
emitApprovalRequest: (brief, offer) =>
|
|
3447
|
+
pi.events.emit(
|
|
3448
|
+
TASK_MODEL_INIT_APPROVAL_EVENT,
|
|
3449
|
+
Object.freeze({
|
|
3450
|
+
apiVersion: 1,
|
|
3451
|
+
brief,
|
|
3452
|
+
context: ctx,
|
|
3453
|
+
offer,
|
|
3454
|
+
} satisfies TaskModelApprovalRequest),
|
|
3455
|
+
),
|
|
3456
|
+
isToolActive: (name) => pi.getActiveTools().includes(name),
|
|
3457
|
+
submit: (prompt) => pi.sendUserMessage(prompt),
|
|
3458
|
+
});
|
|
3459
|
+
};
|
|
3331
3460
|
pi.registerCommand("subagents-init", {
|
|
3332
3461
|
description:
|
|
3333
3462
|
"Draft task-category model preferences from the live registry; optional arguments set ranking preferences",
|
|
3334
3463
|
handler: async (args, ctx) => {
|
|
3335
|
-
const
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
});
|
|
3342
|
-
pi.sendUserMessage(prompt);
|
|
3464
|
+
const outcome = runTaskModelInit(ctx, args);
|
|
3465
|
+
if (outcome.kind === "not-started")
|
|
3466
|
+
ctx.ui.notify(
|
|
3467
|
+
`Task-model init not started: ${outcome.reason}. Nothing was written.`,
|
|
3468
|
+
"warning",
|
|
3469
|
+
);
|
|
3343
3470
|
},
|
|
3344
3471
|
});
|
|
3472
|
+
// Another extension's command may start the same init; it renders the outcome.
|
|
3473
|
+
const stopInitStart = pi.events.on(TASK_MODEL_INIT_START_EVENT, (raw) => {
|
|
3474
|
+
const request = parseInitStartRequest(raw);
|
|
3475
|
+
if (!request) return;
|
|
3476
|
+
const { input } = request;
|
|
3477
|
+
request.offer({
|
|
3478
|
+
owner: "pi-herdr-agents",
|
|
3479
|
+
start: (): TaskModelInitOutcome =>
|
|
3480
|
+
input
|
|
3481
|
+
? runTaskModelInit(input.context, input.preferences)
|
|
3482
|
+
: {
|
|
3483
|
+
kind: "not-started",
|
|
3484
|
+
reason:
|
|
3485
|
+
"the start request lacks a usable command context or preferences text",
|
|
3486
|
+
},
|
|
3487
|
+
});
|
|
3488
|
+
});
|
|
3489
|
+
pi.on("session_shutdown", () => stopInitStart());
|
|
3490
|
+
}
|
|
3345
3491
|
|
|
3346
3492
|
pi.registerCommand("worktree", {
|
|
3347
3493
|
description: parentSession
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Pi Herdr Agents Sidebar
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
> **Compatibility.** The npm release `pi-herdr-agents@3.1.0` predates sidebar support. Use a local checkout that contains this plugin and the `sidebar.enabled` setting, or a later release that includes both.
|
|
6
|
+
|
|
7
|
+
An optional [Herdr](https://herdr.dev) plugin for [Pi Herdr Agents](../../README.md). It keeps your main agents easy to find in Herdr's Agents view while [Pi](https://github.com/earendil-works/pi) runs delegated children.
|
|
8
|
+
|
|
9
|
+
The plugin adds two actions. The extension in the main package marks each delegated child's pane, and Focus hides the marked rows.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Focus.** Hides delegated Pi children from the Agents view, except blocked children and children whose status is unknown.
|
|
14
|
+
- **All.** Removes Focus if this plugin set it, then shows every agent again. If another tool replaced the view, All leaves that view in place and reports which tool owns it.
|
|
15
|
+
- **Display only.** Neither action closes, moves, or signals anything. Hidden children keep running, keep their panes, and still raise notifications. `herdr agent list` still lists them.
|
|
16
|
+
- **No automatic Focus.** Installing, linking, or enabling the plugin never turns Focus on. You invoke it.
|
|
17
|
+
- **Opt-in markers.** The extension writes pane markers only when you set `sidebar.enabled` to `true`.
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Herdr `0.9.2-preview.2026-09-29-8e78f929d8f0`, the only build the plugin was tested on. Herdr accepts only a plain version in `min_herdr_version` and reports this preview as `0.9.2`, so the manifest declares `0.9.2`. Herdr then also accepts other 0.9.2 builds and newer ones, all untested. A build without the agent view API makes Focus and All fail with Herdr's error.
|
|
22
|
+
- Linux. The extension identifies child processes through `/proc`, so it marks nothing on other systems, and the manifest declares only `linux`.
|
|
23
|
+
- `node` on the `PATH` of the Herdr server. Herdr runs the actions as `node sidebar.mjs focus` and `node sidebar.mjs all`.
|
|
24
|
+
- The `pi-herdr-agents` extension, installed in Pi from a revision that supports `sidebar.enabled`, with that setting turned on. Without markers, Focus hides nothing.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
Setup has two parts. Install the Herdr plugin, then enable markers in a compatible Pi extension. The plugin alone hides nothing, and the setting alone adds no actions.
|
|
29
|
+
|
|
30
|
+
### 1. Install the Herdr plugin
|
|
31
|
+
|
|
32
|
+
Review the plugin source, then link its directory from your local checkout:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
herdr plugin link /path/to/pi-herdr-agents/plugins/sidebar
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Once the GitHub default branch contains `plugins/sidebar`, you can install the subdirectory instead. Herdr shows the manifest and commands for review:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
herdr plugin install giuseppecrj/pi-herdr-agents/plugins/sidebar
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For another published branch, tag, or commit, add `--ref` followed by that revision. The revision must contain the plugin directory.
|
|
45
|
+
|
|
46
|
+
### 2. Turn on markers in the Pi extension
|
|
47
|
+
|
|
48
|
+
The Pi extension must also contain sidebar support. If you do not already load a compatible copy, install the same local checkout in Pi:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pi install /path/to/pi-herdr-agents
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use `pi list` to check your existing package sources. If another copy of Pi Herdr Agents is enabled, disable that copy with `pi config` before loading the checkout. Do not load both copies. Start Pi inside Herdr.
|
|
55
|
+
|
|
56
|
+
1. Open `$PI_CODING_AGENT_DIR/herdr-agents/config.json`. The default is `~/.pi/agent/herdr-agents/config.json`.
|
|
57
|
+
2. If the file does not exist, copy `config.json.example` from the extension there first. The extension rejects a `config.json` without its `status` section.
|
|
58
|
+
3. Merge the `sidebar` section into the file. Keep the existing `status`, `models`, and other sections. Do not replace the file.
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"sidebar": {
|
|
63
|
+
"enabled": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
4. Run `/reload` in each parent Pi session, or start a new one.
|
|
69
|
+
5. Launch new children. Children that were already running stay unmarked.
|
|
70
|
+
6. Invoke Focus yourself. See [Use Focus and All](#use-focus-and-all).
|
|
71
|
+
|
|
72
|
+
### Check the result
|
|
73
|
+
|
|
74
|
+
After you launch a new child and run Focus, a marked child with status `idle`, `working`, or `done` disappears from the Agents view while your main session stays. Blocked children remain visible. Run All to show the hidden row again. If nothing changes, read the action log:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
herdr plugin log list --plugin pi-herdr-agents.sidebar
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Contents
|
|
81
|
+
|
|
82
|
+
- [Requirements](#requirements), [Install](#install), [Use Focus and All](#use-focus-and-all)
|
|
83
|
+
- [Troubleshooting](#troubleshooting), [Safety](#safety), [Uninstall](#uninstall)
|
|
84
|
+
- [Which rows Focus hides](#which-rows-focus-hides), [What a marker proves](#what-a-marker-proves), [Reusing a delegated pane](#reusing-a-delegated-pane), [Limits](#limits)
|
|
85
|
+
- Main package: [Pi Herdr Agents](../../README.md) and its [sidebar markers](../../README.md#sidebar-markers) setting
|
|
86
|
+
|
|
87
|
+
## Use Focus and All
|
|
88
|
+
|
|
89
|
+
Run an action from the command line:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
herdr plugin action invoke pi-herdr-agents.sidebar.focus
|
|
93
|
+
herdr plugin action invoke pi-herdr-agents.sidebar.all
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
To bind a key, add an entry like this to your Herdr config and pick a key you do not already use:
|
|
97
|
+
|
|
98
|
+
```toml
|
|
99
|
+
[[keys.command]]
|
|
100
|
+
key = "prefix+g"
|
|
101
|
+
type = "plugin_action"
|
|
102
|
+
command = "pi-herdr-agents.sidebar.focus"
|
|
103
|
+
description = "hide delegated Pi children"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Herdr keeps one agent view for the whole server. Focus replaces the view that was active, including a view that another tool set. All sends its clear with this plugin as the source, so Herdr clears the view only if this plugin owns it. If another tool replaced Focus, All leaves that view in place and reports which tool owns it.
|
|
107
|
+
|
|
108
|
+
Each action writes one message to its log. Read it with `herdr plugin log list --plugin pi-herdr-agents.sidebar`. The exit status tells you what happened:
|
|
109
|
+
|
|
110
|
+
| Exit status | Meaning |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| `0` | Focus is on, or All found no view left. |
|
|
113
|
+
| `1` | The action failed. Herdr refused the request, sent no usable reply within 3 seconds, or still reports this plugin's view after All. The action also fails when it runs outside Herdr or as another plugin. The message names the cause. |
|
|
114
|
+
| `2` | The action name was not `focus` or `all`. |
|
|
115
|
+
| `3` | All left another tool's view in place. |
|
|
116
|
+
|
|
117
|
+
A Herdr server restart drops the view and every marker. The plugin has no startup hook, so run Focus again after a restart if you want it.
|
|
118
|
+
|
|
119
|
+
## Troubleshooting
|
|
120
|
+
|
|
121
|
+
- **A child you expect to be hidden is still visible.** Check that `sidebar.enabled` is `true`, that you ran `/reload` in the parent, and that the child was launched after the reload. Then run Focus again. A blocked child or a child with unknown status is always shown.
|
|
122
|
+
- **A row you expect is missing.** Run All. Markers change only what the Agents view shows.
|
|
123
|
+
- **Focus or All exits with status `1`.** Read the log with the command above. The message names the cause, such as a Herdr build without the agent view API.
|
|
124
|
+
- **All exits with status `3`.** Another tool owns the view. All leaves it in place.
|
|
125
|
+
- **A new agent in a delegated pane is hidden.** See [Reusing a delegated pane](#reusing-a-delegated-pane).
|
|
126
|
+
|
|
127
|
+
## Safety
|
|
128
|
+
|
|
129
|
+
- Markers are display only. They are not a security boundary.
|
|
130
|
+
- The plugin sends requests to your local Herdr server. It does not close, move, or signal panes.
|
|
131
|
+
- With `sidebar.enabled` on, the parent records each child's process identity and reports one pane token, `piha_delegated_v1`, through the Herdr socket. With it off, the extension writes no pane metadata. See [Safety and uninstall](../../README.md#safety-and-uninstall) in the main README.
|
|
132
|
+
|
|
133
|
+
## Uninstall
|
|
134
|
+
|
|
135
|
+
1. Run All, or skip this step. Herdr also drops this plugin's view when you disable, unlink, or uninstall the plugin.
|
|
136
|
+
2. Remove the plugin. Use `uninstall` for a GitHub install and `unlink` for a linked directory:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
herdr plugin uninstall pi-herdr-agents.sidebar
|
|
140
|
+
herdr plugin unlink pi-herdr-agents.sidebar
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
3. Set `sidebar.enabled` to `false`, or delete the `sidebar` key, and run `/reload` in each parent session. The reload clears the markers of children that are still running and stops renewing them. A marker whose clear does not reach Herdr expires within 15 seconds.
|
|
144
|
+
|
|
145
|
+
## Which rows Focus hides
|
|
146
|
+
|
|
147
|
+
The extension writes the pane token `piha_delegated_v1` with the value `live`
|
|
148
|
+
on a delegated child's pane. Focus hides a row only when that token is `live`
|
|
149
|
+
and the agent status is `idle`, `working`, or `done`. Focus sends this
|
|
150
|
+
filter, with no sort:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"op": "not",
|
|
155
|
+
"filter": {
|
|
156
|
+
"op": "all",
|
|
157
|
+
"filters": [
|
|
158
|
+
{ "op": "eq", "field": { "token": "piha_delegated_v1" }, "value": "live" },
|
|
159
|
+
{ "op": "in", "field": "status", "values": ["idle", "working", "done"] }
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
| Agent | Marker | Focus |
|
|
166
|
+
| --- | --- | --- |
|
|
167
|
+
| Main session or manually started agent | none | shown |
|
|
168
|
+
| `/worktree` handoff session | none | shown |
|
|
169
|
+
| Delegated child: fresh, resumed, persistent, or in a managed worktree | `live` | hidden |
|
|
170
|
+
| Delegated child that Herdr reports as blocked | `live` | shown |
|
|
171
|
+
| Agent whose status is unknown | any | shown |
|
|
172
|
+
| Pane with a missing marker or any other value | missing or other | shown |
|
|
173
|
+
| Retained worktree shell after its child exits | cleared | shown |
|
|
174
|
+
|
|
175
|
+
The `done` status means idle and not yet seen. It does not mean that anyone
|
|
176
|
+
accepted the child's work, so Focus hides it like any idle child.
|
|
177
|
+
|
|
178
|
+
## What a marker proves
|
|
179
|
+
|
|
180
|
+
A `live` marker means the extension classified the pane's process as a
|
|
181
|
+
delegated child within the last 15 seconds. It does not prove that the same
|
|
182
|
+
process occupies the pane when Herdr draws the row.
|
|
183
|
+
|
|
184
|
+
The parent Pi session writes the marker after its child records its own
|
|
185
|
+
process identity and the parent verifies it. The parent renews the marker every
|
|
186
|
+
4 to 5 seconds while that exact process (PID and start time) is alive. Renewal
|
|
187
|
+
runs on the parent's existing supervision checks, so if those checks stall,
|
|
188
|
+
the marker expires and the child reappears. The parent clears the marker when
|
|
189
|
+
the child finishes, when it stops supervising the child, or when it finds that
|
|
190
|
+
process gone or unverifiable. The marker belongs to the pane, not to the process.
|
|
191
|
+
Renewal checks only the original process, so a different process in the same
|
|
192
|
+
pane can be hidden for as long as the original process stays alive and
|
|
193
|
+
supervised. See [Reusing a delegated pane](#reusing-a-delegated-pane).
|
|
194
|
+
|
|
195
|
+
Each write carries a 15-second time-to-live and a sequence number that
|
|
196
|
+
increases across all of a parent's writes. The 15-second guarantee counts from
|
|
197
|
+
the last write Herdr accepted. It does not restart when the pane's occupant
|
|
198
|
+
changes or the original process is suspended. Herdr ignores a write whose
|
|
199
|
+
sequence number is not higher than the last one it applied, so a delayed
|
|
200
|
+
renewal cannot undo a clear. The extension does not retry a failed write and
|
|
201
|
+
stops writing after three failures in a row.
|
|
202
|
+
|
|
203
|
+
Some leftover markers still occur:
|
|
204
|
+
|
|
205
|
+
- If the parent process exits or crashes, its last marker stays until its
|
|
206
|
+
time-to-live ends, up to 15 seconds after the last write Herdr applied.
|
|
207
|
+
- A renewal that Herdr processes late starts a new 15-second period, unless a
|
|
208
|
+
clear with a higher sequence number has already arrived.
|
|
209
|
+
- A new agent started in the same pane during that window stays hidden by
|
|
210
|
+
Focus until the marker expires.
|
|
211
|
+
- If the parent restarts while the same Herdr server keeps running, and the
|
|
212
|
+
system clock has moved backwards, the new parent's sequence numbers can fall
|
|
213
|
+
behind the last ones Herdr applied. Herdr then ignores its writes until the
|
|
214
|
+
clock catches up. A Herdr server restart resets the sequence state and drops
|
|
215
|
+
every marker.
|
|
216
|
+
|
|
217
|
+
Run All whenever a row you expect is missing. Markers change only what the
|
|
218
|
+
Agents view shows. They are not a security boundary.
|
|
219
|
+
|
|
220
|
+
### Reusing a delegated pane
|
|
221
|
+
|
|
222
|
+
The marker follows the pane, and the parent keeps renewing it while the
|
|
223
|
+
original child process is alive and supervised. A child suspended with Ctrl-Z
|
|
224
|
+
is still alive, so if you start another agent in that pane by hand, Focus can
|
|
225
|
+
hide the new agent until the original child is stopped or no longer
|
|
226
|
+
supervised. Run All before you reuse a delegated pane, or start the new agent
|
|
227
|
+
in a fresh pane. To end renewal, stop the child with the host's normal
|
|
228
|
+
controls. The last marker then lasts at most 15 seconds after the last write
|
|
229
|
+
Herdr accepted. The first version of the plugin has this limitation, and no
|
|
230
|
+
fix for pane reuse is planned for it.
|
|
231
|
+
|
|
232
|
+
## Limits
|
|
233
|
+
|
|
234
|
+
- Tested only on Linux with the Herdr build named above, one local server, and
|
|
235
|
+
one attached client. Remote machines and several attached clients are
|
|
236
|
+
untested.
|
|
237
|
+
- The marker follows the pane, not the process. See
|
|
238
|
+
[Reusing a delegated pane](#reusing-a-delegated-pane).
|
|
239
|
+
- Herdr allows at most 32 sequenced metadata sources per pane. The extension
|
|
240
|
+
uses the single source `pi-herdr-agents` for every marker.
|
|
241
|
+
- The plugin has no popup, settings, or startup hook, and it does not hide
|
|
242
|
+
workspaces or notifications.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
id = "pi-herdr-agents.sidebar"
|
|
2
|
+
name = "Pi Herdr Agents Sidebar"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
# Tested only on 0.9.2-preview.2026-09-29-8e78f929d8f0. Herdr accepts only a
|
|
5
|
+
# plain version here and reports that preview as 0.9.2, so any 0.9.2 build,
|
|
6
|
+
# including a stable one, passes this check untested. See README.md.
|
|
7
|
+
min_herdr_version = "0.9.2"
|
|
8
|
+
description = "Hide delegated Pi children from the Agents view (Focus) or show every agent (All)"
|
|
9
|
+
platforms = ["linux"]
|
|
10
|
+
|
|
11
|
+
[[actions]]
|
|
12
|
+
id = "focus"
|
|
13
|
+
title = "Pi agents: Focus (hide delegated children)"
|
|
14
|
+
contexts = ["global"]
|
|
15
|
+
command = ["node", "sidebar.mjs", "focus"]
|
|
16
|
+
|
|
17
|
+
[[actions]]
|
|
18
|
+
id = "all"
|
|
19
|
+
title = "Pi agents: All (show every agent)"
|
|
20
|
+
contexts = ["global"]
|
|
21
|
+
command = ["node", "sidebar.mjs", "all"]
|