@yagni-app/code-staging 1.2.6-staging.1940.1 → 1.2.6-staging.1951.1

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.
@@ -72,6 +72,30 @@ export declare const TICKET_IMAGE_RULE: string;
72
72
  * the standalone word "pi", must not open with a `- ` bullet line, no emojis.
73
73
  */
74
74
  export declare const GITHUB_OPERATIONS: string;
75
+ /** Chrome for Testing in the sandbox image (`PI_RENDER_CHROME_DIR` in infra/e2b/pi/template.ts). */
76
+ export declare const SANDBOX_CHROME_PATH = "/opt/chrome/chrome";
77
+ /** Playwright in the sandbox image (`PI_RENDER_DIR` in infra/e2b/pi/template.ts). */
78
+ export declare const SANDBOX_PLAYWRIGHT_DIR = "/opt/yagni/render";
79
+ /**
80
+ * Commands {@link SANDBOX_SECTION} tells the model the sandbox does not have.
81
+ * The pi image smoke fails when the image gains one, as it fails when the
82
+ * image lacks a path the section names.
83
+ */
84
+ export declare const SANDBOX_ABSENT_COMMANDS: readonly string[];
85
+ /**
86
+ * What a session in a YAGNI sandbox (E2B or Machines) really has, in place of
87
+ * {@link TICKET_IMAGE_RULE} and {@link GITHUB_OPERATIONS}, which describe a
88
+ * developer's laptop: there is no `gh` and no GitHub or Linear token here
89
+ * (by design: the sandbox has open egress and the model has read untrusted
90
+ * ticket text), GitHub reads go through `team_read`, the ticket's images
91
+ * arrive as words in its evidence pack, a browser is baked in, and installs
92
+ * are allowed. Every path it names is checked against the image by the pi
93
+ * image smoke (`assertSandboxSection` in scripts/release/smokeChecks.ts).
94
+ *
95
+ * Content constraints (parity with {@link TICKET_IMAGE_RULE}): must not
96
+ * contain the standalone word "pi", no `- ` bullet lines, no emojis.
97
+ */
98
+ export declare const SANDBOX_SECTION: string;
75
99
  /**
76
100
  * File-tool steering: prefer the first-class write/edit tools over bash file
77
101
  * writes, one write call per file instead of a heredoc batch, and never create
@@ -170,6 +194,12 @@ export interface BrandSystemPromptOptions {
170
194
  * all-or-nothing per-surface call, not user policy.
171
195
  */
172
196
  fileToolSteering?: boolean;
197
+ /**
198
+ * The session runs in a YAGNI sandbox (E2B or Machines), not on a laptop:
199
+ * {@link SANDBOX_SECTION} replaces the ticket-image rule and the GitHub
200
+ * operations section. Absent or false, the prompt is exactly the laptop's.
201
+ */
202
+ sandbox?: boolean;
173
203
  }
174
204
  /**
175
205
  * Rebrand pi's assembled system prompt as YAGNI Code's, and optionally inject a
@@ -155,6 +155,58 @@ export const GITHUB_OPERATIONS = "## GitHub Operations\n" +
155
155
  "multi-paragraph.";
156
156
  /** Stable header that starts the GitHub operations section (idempotency anchor). */
157
157
  const GITHUB_OPERATIONS_HEADER = "## GitHub Operations";
158
+ /** Chrome for Testing in the sandbox image (`PI_RENDER_CHROME_DIR` in infra/e2b/pi/template.ts). */
159
+ export const SANDBOX_CHROME_PATH = "/opt/chrome/chrome";
160
+ /** Playwright in the sandbox image (`PI_RENDER_DIR` in infra/e2b/pi/template.ts). */
161
+ export const SANDBOX_PLAYWRIGHT_DIR = "/opt/yagni/render";
162
+ /**
163
+ * Commands {@link SANDBOX_SECTION} tells the model the sandbox does not have.
164
+ * The pi image smoke fails when the image gains one, as it fails when the
165
+ * image lacks a path the section names.
166
+ */
167
+ export const SANDBOX_ABSENT_COMMANDS = ["gh"];
168
+ /** Stable header that starts the sandbox section (idempotency anchor). */
169
+ const SANDBOX_SECTION_HEADER = "## Your sandbox";
170
+ /**
171
+ * What a session in a YAGNI sandbox (E2B or Machines) really has, in place of
172
+ * {@link TICKET_IMAGE_RULE} and {@link GITHUB_OPERATIONS}, which describe a
173
+ * developer's laptop: there is no `gh` and no GitHub or Linear token here
174
+ * (by design: the sandbox has open egress and the model has read untrusted
175
+ * ticket text), GitHub reads go through `team_read`, the ticket's images
176
+ * arrive as words in its evidence pack, a browser is baked in, and installs
177
+ * are allowed. Every path it names is checked against the image by the pi
178
+ * image smoke (`assertSandboxSection` in scripts/release/smokeChecks.ts).
179
+ *
180
+ * Content constraints (parity with {@link TICKET_IMAGE_RULE}): must not
181
+ * contain the standalone word "pi", no `- ` bullet lines, no emojis.
182
+ */
183
+ export const SANDBOX_SECTION = `${SANDBOX_SECTION_HEADER}\n` +
184
+ "\n" +
185
+ "This session runs in a YAGNI sandbox, not on a developer's laptop. Four things " +
186
+ "are true here.\n" +
187
+ "\n" +
188
+ "GitHub: this image has no `gh` command and the session has no GitHub or " +
189
+ "Linear token. Read GitHub through team_read with the Team's GitHub tools, " +
190
+ "such as `github_get_pull_request`, `github_list_pull_requests`, " +
191
+ "`github_list_issues` and `github_list_workflow_runs`; `github_get_file` and " +
192
+ "`github_search_code` read the other repositories the ticket touches (this " +
193
+ 'one is your checkout). Call team_read with tool "list" first: it shows which ' +
194
+ "of these this Team can read. YAGNI pushes your branch and posts the pull " +
195
+ "request's comments after you finish; you do neither.\n" +
196
+ "\n" +
197
+ "Ticket images: the ticket's screenshots are not on disk here. Each one is " +
198
+ "described in words in the ticket's evidence pack, the \"Evidence behind the " +
199
+ "ticket\" section of the ticket you were given. Trust that description; a " +
200
+ "screenshot named there as not read could not be described.\n" +
201
+ "\n" +
202
+ `A browser: Chrome is at \`${SANDBOX_CHROME_PATH}\` and Playwright is installed ` +
203
+ `at \`${SANDBOX_PLAYWRIGHT_DIR}\`. Load Playwright from ` +
204
+ `\`${SANDBOX_PLAYWRIGHT_DIR}/node_modules/playwright\` and launch Chrome with ` +
205
+ `\`executablePath: "${SANDBOX_CHROME_PATH}"\` to open a page, click through it, ` +
206
+ "or take a screenshot.\n" +
207
+ "\n" +
208
+ "Installs: the network is open, and you may install what the work needs. The " +
209
+ "mission file says what to install.";
158
210
  /**
159
211
  * File-tool steering: prefer the first-class write/edit tools over bash file
160
212
  * writes, one write call per file instead of a heredoc batch, and never create
@@ -364,14 +416,26 @@ export function brandSystemPrompt(original, opts = {}) {
364
416
  // (so a long, noisy rules block cannot bury it) but BEFORE the closing
365
417
  // reminder (which stays the most-recent instruction). The idempotency guard
366
418
  // keys on the stable section header, mirroring the rules guard above.
367
- if (!s.includes(TICKET_IMAGE_RULE_HEADER)) {
419
+ // A sandbox session gets the "Your sandbox" section in the same place
420
+ // instead: both laptop sections name tools the sandbox does not have.
421
+ if (opts.sandbox) {
422
+ // A prompt an earlier pass branded for a laptop loses both laptop
423
+ // sections, so the two never sit beside the sandbox's.
424
+ s = s
425
+ .replace(`\n\n${TICKET_IMAGE_RULE_HEADER}\n${TICKET_IMAGE_RULE}`, "")
426
+ .replace(`\n\n${GITHUB_OPERATIONS}`, "");
427
+ if (!s.includes(SANDBOX_SECTION_HEADER)) {
428
+ s = `${s}\n\n${SANDBOX_SECTION}`;
429
+ }
430
+ }
431
+ else if (!s.includes(TICKET_IMAGE_RULE_HEADER)) {
368
432
  s = `${s}\n\n${TICKET_IMAGE_RULE_HEADER}\n${TICKET_IMAGE_RULE}`;
369
433
  }
370
434
  // 5b2. GitHub operations — a standing directive added alongside
371
435
  // the ticket-image rule (same placement, same idempotency shape). It is
372
436
  // universal knowledge, not user policy, so it rides the branded prompt
373
- // instead of the repository-rules section.
374
- if (!s.includes(GITHUB_OPERATIONS_HEADER)) {
437
+ // instead of the repository-rules section. Laptop only (see 5b).
438
+ if (!opts.sandbox && !s.includes(GITHUB_OPERATIONS_HEADER)) {
375
439
  s = `${s}\n\n${GITHUB_OPERATIONS}`;
376
440
  }
377
441
  // 5b2b. File-tool steering — same standing-directive placement and
@@ -45,7 +45,7 @@ import { isSandboxDenialOutput, sandboxAutoAllowDecision, sandboxDenialSignature
45
45
  import { sandboxFailureKey } from "./permission/approvedPrefixes.js";
46
46
  import { createEscapeTally } from "./sandbox/escapeTally.js";
47
47
  import { createGrantCensus } from "./permission/grantCensus.js";
48
- import { toolchainSpawnHookFromEnv, toolchainUserBashOperations } from "./toolchain/spawnHook.js";
48
+ import { runsInSandboxHarness, toolchainSpawnHookFromEnv, toolchainUserBashOperations } from "./toolchain/spawnHook.js";
49
49
  import { registerTelemetry as defaultRegisterTelemetry } from "./telemetry/register.js";
50
50
  import { loadHooksConfig, makeHookRunner, registerHooks } from "./hooks.js";
51
51
  import { loadPermissionRules } from "./permissionRules/loadConfig.js";
@@ -1578,6 +1578,12 @@ export async function registerYagni(pi, deps = {}) {
1578
1578
  // Resolved per turn (not once at activation): /ultra can flip the driver
1579
1579
  // between the delegation and diamond identities mid-session.
1580
1580
  const isDriver = isDriverCaller(env);
1581
+ // A sandbox session is told what its sandbox has instead of a laptop's
1582
+ // `gh` recipes. Both signals are the ones the sandbox already rides: the
1583
+ // harness's toolchain file, and eval mode, which is where team_read (the
1584
+ // tool the section sends GitHub reads to) is registered. A local eval
1585
+ // compare sets eval mode without the harness, so it keeps the laptop's.
1586
+ const sandboxSession = evalMode && runsInSandboxHarness(env);
1581
1587
  pi.on("before_agent_start", (event) => noBranding
1582
1588
  ? undefined
1583
1589
  : {
@@ -1601,6 +1607,7 @@ export async function registerYagni(pi, deps = {}) {
1601
1607
  scratchpadSection: scratchpadSectionText,
1602
1608
  attributionSection: attributionPromptSection(attribution()),
1603
1609
  fileToolSteering: condensedToolsRegistered,
1610
+ ...(sandboxSession ? { sandbox: true } : {}),
1604
1611
  }),
1605
1612
  });
1606
1613
  // Turn-lifecycle WAL: a `turn_start` with no matching `turn_end` is the
@@ -48,6 +48,13 @@ export declare function readToolchainFile(path: string, log?: Log): ToolchainFil
48
48
  export declare function makeToolchainSpawnHook(file: ToolchainFile): BashSpawnHook;
49
49
  /** The hooks applied in order (first to last), skipping absent ones; undefined when none is present. */
50
50
  export declare function composeSpawnHooks(...hooks: Array<BashSpawnHook | undefined>): BashSpawnHook | undefined;
51
+ /**
52
+ * This process runs under the sandbox harness (`infra/e2b/pi/pi-harness.sh`
53
+ * exports `YAGNI_TOOLCHAIN_FILE` on every E2B and Machines session, Reeve's
54
+ * and Wright's alike, and `/go` children inherit it). A developer's own
55
+ * terminal never sets it.
56
+ */
57
+ export declare function runsInSandboxHarness(env?: NodeJS.ProcessEnv): boolean;
51
58
  /**
52
59
  * The hook for this process: present only when `YAGNI_TOOLCHAIN_FILE` names a
53
60
  * readable, well-formed file. Unset is silent (not a sandbox); set but
@@ -97,6 +97,15 @@ export function composeSpawnHooks(...hooks) {
97
97
  return present[0];
98
98
  return (ctx) => present.reduce((acc, hook) => hook(acc), ctx);
99
99
  }
100
+ /**
101
+ * This process runs under the sandbox harness (`infra/e2b/pi/pi-harness.sh`
102
+ * exports `YAGNI_TOOLCHAIN_FILE` on every E2B and Machines session, Reeve's
103
+ * and Wright's alike, and `/go` children inherit it). A developer's own
104
+ * terminal never sets it.
105
+ */
106
+ export function runsInSandboxHarness(env = process.env) {
107
+ return Boolean(env.YAGNI_TOOLCHAIN_FILE?.trim());
108
+ }
100
109
  /**
101
110
  * The hook for this process: present only when `YAGNI_TOOLCHAIN_FILE` names a
102
111
  * readable, well-formed file. Unset is silent (not a sandbox); set but
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "1.2.6-staging.1940.1",
3
+ "version": "1.2.6-staging.1951.1",
4
4
  "description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
@@ -58,5 +58,5 @@
58
58
  "turndown": "^7.2.4",
59
59
  "typebox": "^1.3.15"
60
60
  },
61
- "yagniSourceSha": "3f6e449b2bf568cf6c1b6001dbfb8894f26b6cb7"
61
+ "yagniSourceSha": "0985f34f33f3c59e0fb7ed50af520abbcbb2466c"
62
62
  }