pi-quiet 0.1.2 → 0.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/README.md +5 -8
- package/package.json +8 -4
- package/src/bash-options.ts +39 -0
- package/src/index.ts +75 -47
package/README.md
CHANGED
|
@@ -19,16 +19,13 @@ rendering returns in full — complete commands, full output, truncation
|
|
|
19
19
|
warnings. Press it again to re-collapse. It's a live toggle over the whole
|
|
20
20
|
transcript: work collapsed, expand when something looks off.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## What it owns, by construction
|
|
23
23
|
|
|
24
|
-
The extension re-registers the built-in `bash` tool via pi's own
|
|
25
|
-
`createBashToolDefinition` and overrides only the two render slots. Execution
|
|
26
|
-
*is* the built-in tool:
|
|
24
|
+
The extension re-registers the built-in `bash` tool via pi's own `createBashToolDefinition`. It is the one package on a rig that registers `bash`, and it owns three things about it:
|
|
27
25
|
|
|
28
|
-
- the model receives the full,
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
- session logs store the full result.
|
|
26
|
+
- **Rendering.** The two render slots above. Execution *is* the built-in tool: the model receives the full output, permission systems, reviewers, and sandboxes that hook execution are unaffected, and session logs store the full result.
|
|
27
|
+
- **pi's shell settings.** The built-in is constructed with `shellCommandPrefix` and `shellPath` — read with the same settings pi itself would use, including pi's own project-trust decision for the current directory (`session_start` is where that decision becomes available, so that is where the tool is registered). A trusted project's `.pi/settings.json` contributes its `shellCommandPrefix` and `shellPath`; an untrusted project's are ignored, exactly as pi ignores them. Re-registration on each `session_start` means a `/reload` after editing settings picks up the new prefix.
|
|
28
|
+
- **Session identity.** Every command's environment carries `AGENT_SESSION_ID`, set from pi's per-command `PI_SESSION_ID`. Tools that scope themselves to the session that ran them (muster, galley, tackle) read that neutral name. It is set per command from pi's live session, never from the extension's process environment, so an in-process subagent can never leave the parent's commands carrying a child's id.
|
|
32
29
|
|
|
33
30
|
## Install
|
|
34
31
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-quiet",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "This rig's bash tool for the pi coding agent: calm one-line rendering (ctrl+o expands), pi's shell settings honoured, AGENT_SESSION_ID stamped per command from PI_SESSION_ID. Execution is still pi's built-in.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
"pi-coding-agent",
|
|
16
16
|
"tui",
|
|
17
17
|
"tool-output",
|
|
18
|
-
"quiet"
|
|
18
|
+
"quiet",
|
|
19
|
+
"session-identity"
|
|
19
20
|
],
|
|
20
21
|
"files": [
|
|
21
22
|
"src",
|
|
23
|
+
"!src/*.test.ts",
|
|
22
24
|
"README.md"
|
|
23
25
|
],
|
|
24
26
|
"pi": {
|
|
@@ -27,11 +29,13 @@
|
|
|
27
29
|
]
|
|
28
30
|
},
|
|
29
31
|
"scripts": {
|
|
30
|
-
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
32
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
33
|
+
"test": "node --experimental-strip-types --test src/*.test.ts"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
33
36
|
"@earendil-works/pi-coding-agent": "^0.84.3",
|
|
34
37
|
"@earendil-works/pi-tui": "^0.84.3",
|
|
38
|
+
"@types/node": "^22.20.1",
|
|
35
39
|
"typescript": "^5.6.0"
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { BashSpawnContext, BashToolOptions } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export const SESSION_ID_VAR = "AGENT_SESSION_ID";
|
|
4
|
+
|
|
5
|
+
// The two pi settings the built-in bash tool is constructed with. pi's own
|
|
6
|
+
// SettingsManager satisfies this; tests pass a literal.
|
|
7
|
+
export type ShellSettings = {
|
|
8
|
+
getShellCommandPrefix(): string | undefined;
|
|
9
|
+
getShellPath(): string | undefined;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Stamps the harness-neutral session id on every command from pi's own
|
|
13
|
+
// per-command PI_SESSION_ID, which pi injects into `context.env` before the
|
|
14
|
+
// hook runs. Per command, from pi's live session manager, never from
|
|
15
|
+
// process.env: an in-process child session (pi-subagents) can write
|
|
16
|
+
// process.env, but it cannot change what pi puts here for THIS command.
|
|
17
|
+
// When pi did not expose a session (exposeSessionEnvironment: false), an
|
|
18
|
+
// inherited value is removed rather than passed through — a command never
|
|
19
|
+
// carries an identity that is not its session's.
|
|
20
|
+
export function identitySpawnHook(context: BashSpawnContext): BashSpawnContext {
|
|
21
|
+
const env: NodeJS.ProcessEnv = { ...context.env };
|
|
22
|
+
const id = env.PI_SESSION_ID;
|
|
23
|
+
if (id === undefined || id === "") delete env[SESSION_ID_VAR];
|
|
24
|
+
else env[SESSION_ID_VAR] = id;
|
|
25
|
+
return { ...context, env };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Options for createBashToolDefinition that match what pi itself passes to
|
|
29
|
+
// the built-in (settings.shellCommandPrefix and settings.shellPath) plus the
|
|
30
|
+
// identity hook. Unset settings are omitted so pi's defaults apply.
|
|
31
|
+
export function buildBashOptions(settings: ShellSettings): BashToolOptions {
|
|
32
|
+
const commandPrefix = settings.getShellCommandPrefix();
|
|
33
|
+
const shellPath = settings.getShellPath();
|
|
34
|
+
return {
|
|
35
|
+
...(commandPrefix !== undefined ? { commandPrefix } : {}),
|
|
36
|
+
...(shellPath !== undefined ? { shellPath } : {}),
|
|
37
|
+
spawnHook: identitySpawnHook,
|
|
38
|
+
};
|
|
39
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* quiet
|
|
2
|
+
* pi-quiet: this rig's bash tool registration.
|
|
3
3
|
*
|
|
4
|
-
* Re-registers the built-in bash tool
|
|
5
|
-
*
|
|
6
|
-
* slots. Collapsed (the default): the call renders as a single truncated
|
|
7
|
-
* `$ command` line and the result as a single muted summary line. Expanded
|
|
8
|
-
* (ctrl+o / app.tools.expand): both slots defer to the built-in renderer,
|
|
9
|
-
* so the full command, streamed output, truncation warnings, and duration
|
|
10
|
-
* come back exactly as stock pi shows them.
|
|
4
|
+
* Re-registers the built-in bash tool via pi's own createBashToolDefinition
|
|
5
|
+
* and owns three things about it:
|
|
11
6
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* 1. Rendering. Collapsed (the default): the call renders as a single
|
|
8
|
+
* truncated `$ command` line and the result as a single muted summary
|
|
9
|
+
* line. Expanded (ctrl+o / app.tools.expand): both slots defer to the
|
|
10
|
+
* built-in renderer, so the full command, streamed output, truncation
|
|
11
|
+
* warnings, and duration come back exactly as stock pi shows them.
|
|
12
|
+
* 2. pi's shell settings. The built-in is constructed with
|
|
13
|
+
* settings.shellCommandPrefix and settings.shellPath, as pi itself does;
|
|
14
|
+
* a re-registration that passed nothing silently dropped both. The
|
|
15
|
+
* settings are read with pi's own project-trust decision, so a trusted
|
|
16
|
+
* project's .pi/settings.json contributes its shellCommandPrefix and
|
|
17
|
+
* shellPath and an untrusted one's are ignored — exactly as pi ignores
|
|
18
|
+
* them.
|
|
19
|
+
* 3. Session identity. A spawn hook copies pi's per-command PI_SESSION_ID
|
|
20
|
+
* into AGENT_SESSION_ID, the harness-neutral name tools read — see
|
|
21
|
+
* bash-options.ts for why that must be per command and never process.env.
|
|
22
|
+
*
|
|
23
|
+
* Execution is still the built-in: permission gating, auto-review, and the
|
|
24
|
+
* sandbox all hook execution, which this file never replaces.
|
|
14
25
|
*/
|
|
15
26
|
|
|
16
27
|
import {
|
|
17
28
|
createBashToolDefinition,
|
|
29
|
+
SettingsManager,
|
|
18
30
|
type ExtensionAPI,
|
|
19
31
|
} from "@earendil-works/pi-coding-agent";
|
|
20
32
|
import { Text } from "@earendil-works/pi-tui";
|
|
33
|
+
import { buildBashOptions } from "./bash-options.ts";
|
|
21
34
|
|
|
22
35
|
function firstLine(command: string): { line: string; more: number } {
|
|
23
36
|
const lines = command.split("\n");
|
|
@@ -32,45 +45,60 @@ function resultText(result: { content?: { type: string; text?: string }[] }): st
|
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
export default function quietTools(pi: ExtensionAPI) {
|
|
35
|
-
|
|
48
|
+
// Registration happens at session_start, not at load: that is the first
|
|
49
|
+
// point where pi's own project-trust decision for this cwd is available on
|
|
50
|
+
// the context. Reading settings with that flag makes the tool identical to
|
|
51
|
+
// the one pi would have built — a trusted project's shellCommandPrefix and
|
|
52
|
+
// shellPath apply, an untrusted project's are ignored. session_start also
|
|
53
|
+
// fires for reload/new/resume/fork, and re-registering under the same name
|
|
54
|
+
// each time is the point: a `/reload` after editing settings picks up the
|
|
55
|
+
// new prefix.
|
|
56
|
+
pi.on("session_start", (_event, ctx) => {
|
|
57
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
58
|
+
// Fail closed on an older pi that does not expose the decision.
|
|
59
|
+
const projectTrusted =
|
|
60
|
+
typeof ctx.isProjectTrusted === "function" ? ctx.isProjectTrusted() : false;
|
|
61
|
+
const settings = SettingsManager.create(cwd, undefined, { projectTrusted });
|
|
62
|
+
const builtin = createBashToolDefinition(cwd, buildBashOptions(settings));
|
|
36
63
|
|
|
37
|
-
|
|
38
|
-
|
|
64
|
+
pi.registerTool({
|
|
65
|
+
...builtin,
|
|
39
66
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
67
|
+
renderCall(args: any, theme: any, context: any) {
|
|
68
|
+
if (context.expanded && builtin.renderCall) {
|
|
69
|
+
return builtin.renderCall(args, theme, context);
|
|
70
|
+
}
|
|
71
|
+
const command = typeof args?.command === "string" ? args.command : "...";
|
|
72
|
+
const { line, more } = firstLine(command.trim());
|
|
73
|
+
const suffix = more > 0 ? theme.fg("muted", ` (+${more} lines)`) : "";
|
|
74
|
+
// Text handles width truncation; keep the line itself short anyway so
|
|
75
|
+
// narrow panes stay one visual row.
|
|
76
|
+
const shown = line.length > 200 ? `${line.slice(0, 200)}…` : line;
|
|
77
|
+
return new Text(
|
|
78
|
+
theme.fg("toolTitle", theme.bold(`$ ${shown}`)) + suffix,
|
|
79
|
+
0,
|
|
80
|
+
0,
|
|
81
|
+
);
|
|
82
|
+
},
|
|
56
83
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
renderResult(result: any, options: any, theme: any, context: any) {
|
|
85
|
+
if (options.expanded && builtin.renderResult) {
|
|
86
|
+
return builtin.renderResult(result, options, theme, context);
|
|
87
|
+
}
|
|
88
|
+
if (options.isPartial) {
|
|
89
|
+
return new Text(theme.fg("muted", " … running"), 0, 0);
|
|
90
|
+
}
|
|
91
|
+
const text = resultText(result).trim();
|
|
92
|
+
const lineCount = text ? text.split("\n").length : 0;
|
|
93
|
+
const status = result.isError ? theme.fg("error", "error") : "ok";
|
|
94
|
+
const tail = text ? text.split("\n").at(-1) ?? "" : "";
|
|
95
|
+
const tailShown = tail.length > 120 ? `${tail.slice(0, 120)}…` : tail;
|
|
96
|
+
const summary =
|
|
97
|
+
lineCount <= 1
|
|
98
|
+
? ` → ${status}${tailShown ? theme.fg("toolOutput", ` · ${tailShown}`) : ""}`
|
|
99
|
+
: ` → ${status} · ${lineCount} lines${tailShown ? theme.fg("toolOutput", ` · … ${tailShown}`) : ""}`;
|
|
100
|
+
return new Text(theme.fg("muted", summary), 0, 0);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
75
103
|
});
|
|
76
104
|
}
|