pi-crew 0.9.62 → 0.9.64

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.
@@ -60,6 +60,10 @@ export interface AgentConfig {
60
60
  fallbackModels?: string[];
61
61
  thinking?: string;
62
62
  tools?: string[];
63
+ /** Phase 1 scratchpad opt-in (execute tool). `false` wins over a
64
+ * role default (F6 kill-switch); read-only roles are gated out regardless
65
+ * (S-6) by `isScratchpadEnabledForRole`. Parsed from frontmatter `scratchpad:`. */
66
+ scratchpad?: boolean;
63
67
  extensions?: string[];
64
68
  /**
65
69
  * F1 (v0.7.9): extension denylist (case-insensitive plain names). Applied
@@ -15,6 +15,7 @@ export function serializeAgent(agent: AgentConfig): string {
15
15
  line("fallbackModels", agent.fallbackModels),
16
16
  line("thinking", agent.thinking),
17
17
  line("tools", agent.tools),
18
+ line("scratchpad", agent.scratchpad),
18
19
  agent.extensions !== undefined ? (line("extensions", agent.extensions) ?? "extensions:") : undefined,
19
20
  line("skills", agent.skills),
20
21
  line("systemPromptMode", agent.systemPromptMode),
@@ -427,6 +427,14 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
427
427
  fallbackModels: parseCsv(frontmatter.fallbackModels),
428
428
  thinking: frontmatter.thinking === "false" ? undefined : frontmatter.thinking || undefined,
429
429
  tools: parseToolsField(frontmatter.tools),
430
+ // Phase 1 scratchpad opt-in (Q3: pi ignores unknown frontmatter keys; pi-crew
431
+ // is the sole consumer in the worker path — task arrives via -p, agent file
432
+ // is not re-read by pi). 3-STATE parse (NOT `=== "true"` like
433
+ // inheritProjectContext): omitted/malformed → undefined so the F6 kill-switch
434
+ // (`agent.scratchpad === false`) only fires on an EXPLICIT `scratchpad: false`,
435
+ // not on every agent without the key (which would wrongly kill role
436
+ // default-on). Only the literal "true"/"false" are honored.
437
+ scratchpad: frontmatter.scratchpad === "true" ? true : frontmatter.scratchpad === "false" ? false : undefined,
430
438
  // SEC-1: Strip extensions/excludeExtensions for untrusted project-sourced
431
439
  // agents (RCE prevention). Both `project` (.crew/agents/) and
432
440
  // `project-pi` (.pi/agents/) are repo-adjacent / untrusted sources —
@@ -3,11 +3,17 @@
3
3
  * Uses the excludeTools option from pi v0.77.0.
4
4
  */
5
5
 
6
+ import { permissionForRole } from "../runtime/role-permission.ts";
7
+
6
8
  export interface RoleToolConfig {
7
9
  /** Explicit list of tools to use (if undefined, use all default tools) */
8
10
  tools?: string[];
9
11
  /** Tools to exclude from the default set */
10
12
  excludeTools?: string[];
13
+ /** Phase 1 scratchpad: opt-in the persistent Bun-free JS evaluator (execute tool)
14
+ * for this role. Only effective on WRITE roles — read-only roles are gated
15
+ * out by `isScratchpadEnabledForRole` (S-6, privilege-elevation guard). */
16
+ scratchpad?: boolean;
11
17
  }
12
18
 
13
19
  export const ROLE_TOOL_CONFIGS: Record<string, RoleToolConfig> = {
@@ -44,9 +50,11 @@ export const ROLE_TOOL_CONFIGS: Record<string, RoleToolConfig> = {
44
50
  excludeTools: ["edit", "write", "bash", "web"],
45
51
  },
46
52
 
47
- // Executor - Full access (default)
53
+ // Executor - Full access (default). Phase 1 scratchpad-enabled (stateful
54
+ // evaluator compounds intermediate results across execute calls).
48
55
  executor: {
49
56
  // No restrictions - full tool access
57
+ scratchpad: true,
50
58
  },
51
59
 
52
60
  // Reviewer - Read and review, no write
@@ -78,12 +86,16 @@ export const ROLE_TOOL_CONFIGS: Record<string, RoleToolConfig> = {
78
86
  verifier: {
79
87
  tools: ["read", "grep", "find", "ls", "bash"],
80
88
  excludeTools: ["edit", "write", "web"],
89
+ // Phase 1 scratchpad: multi-cell test/verify flows reuse parsed state.
90
+ scratchpad: true,
81
91
  },
82
92
 
83
93
  // Test Engineer - Can write tests (F1: hyphenated key)
84
94
  "test-engineer": {
85
95
  tools: ["read", "edit", "write", "bash", "ls"],
86
96
  excludeTools: ["web"],
97
+ // Phase 1 scratchpad: build/run test suites with state across cells.
98
+ scratchpad: true,
87
99
  },
88
100
  };
89
101
 
@@ -116,3 +128,39 @@ export function getRestrictedRoles(): string[] {
116
128
  .filter(([, config]) => config.tools !== undefined || config.excludeTools !== undefined)
117
129
  .map(([role]) => role);
118
130
  }
131
+
132
+ /** Agent frontmatter shape consumed by the scratchpad opt-in check. */
133
+ export interface ScratchpadAgentOption {
134
+ scratchpad?: boolean;
135
+ }
136
+
137
+ /**
138
+ * Phase 1: is the scratchpad (execute tool) enabled for this role/agent?
139
+ *
140
+ * Decision order (load-bearing — do not reorder):
141
+ * 1. S-6 (SECURITY, privilege-elevation guard): read-only roles NEVER enable,
142
+ * regardless of any agent frontmatter opt-in. A `scratchpad: true` on a
143
+ * read-only role (e.g. security-reviewer, whose tool-set is read/grep/find
144
+ * with NO bash) would grant full-trust JS execution (fs/network/env/
145
+ * child_process) inside a role designed to have no execution — a real
146
+ * elevation. `permissionForRole` is default-deny (unknown → read_only), so
147
+ * typos/underscore/case-drift cannot bypass this.
148
+ * 2. F6 (kill-switch): `agent.scratchpad === false` WINS over the role default,
149
+ * giving an emergency off-switch for an experimental full-trust feature.
150
+ * 3. agent explicit opt-in (`scratchpad: true`) OR role default (`scratchpad:
151
+ * true` in ROLE_TOOL_CONFIGS).
152
+ *
153
+ * F10: `permissionForRole` and `getToolConfig` both expect HYPHENATED role
154
+ * strings (runtime convention); a raw underscore role would default-deny
155
+ * wrongly, so normalize FIRST.
156
+ */
157
+ export function isScratchpadEnabledForRole(role: string, agent?: ScratchpadAgentOption): boolean {
158
+ // F10: normalize underscore→hyphen before every downstream lookup.
159
+ const normalized = role.includes("_") ? role.replaceAll("_", "-") : role;
160
+ // S-6 (điều kiện tiên quyết): read-only gate trước mọi agent flag.
161
+ if (permissionForRole(normalized) === "read_only") return false;
162
+ // F6: explicit-false agent override (kill-switch) wins over role default.
163
+ if (agent?.scratchpad === false) return false;
164
+ // agent explicit opt-in OR role default.
165
+ return agent?.scratchpad === true || getToolConfig(normalized).scratchpad === true;
166
+ }
@@ -4,6 +4,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import { startChildBrokerClient } from "../runtime/broker/crew-broker-child.ts";
5
5
  import { logInternalError } from "../utils/internal-error.ts";
6
6
  import { resolveRealContainedPath } from "../utils/safe-paths.ts";
7
+ import { registerScratchpadLifecycle } from "./scratchpad-lifecycle.ts";
7
8
 
8
9
  export const PI_TEAMS_INHERIT_PROJECT_CONTEXT_ENV = "PI_TEAMS_INHERIT_PROJECT_CONTEXT";
9
10
  export const PI_TEAMS_INHERIT_SKILLS_ENV = "PI_TEAMS_INHERIT_SKILLS";
@@ -377,4 +378,9 @@ export default function registerPiTeamsPromptRuntime(pi: ExtensionAPI): void {
377
378
  if (rewritten === event.systemPrompt) return;
378
379
  return { systemPrompt: rewritten };
379
380
  });
381
+
382
+ // ── Phase 1 scratchpad (T6): conditional `execute` tool + EngineManager
383
+ // lifecycle. Registers nothing when PI_CREW_SCRATCHPAD !== "1" (D3) and
384
+ // only flushes+kills the engine on session_shutdown reason "quit" (F3).
385
+ registerScratchpadLifecycle(pi);
380
386
  }