@agent-native/core 0.157.28 → 0.158.2

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.
@@ -647,6 +647,7 @@ function buildClaudeCliPrompt(run, prompt) {
647
647
  const additionalSkillsRoot = process.env.AGENT_NATIVE_CODE_AGENT_SKILLS_ROOT?.trim();
648
648
  return [
649
649
  `You are running from Agent-Native Code in ${run.cwd || process.cwd()}.`,
650
+ `Treat ${run.cwd || process.cwd()} as the only project checkout for this run. Keep shell commands, file operations, git pushes, and pull requests rooted there; do not use absolute paths or .. to reach another checkout.`,
650
651
  "Follow the repository AGENTS.md and any relevant skill instructions.",
651
652
  ...(additionalSkillsRoot
652
653
  ? [
@@ -1465,6 +1466,7 @@ function buildCodexCliPrompt(run, prompt) {
1465
1466
  const additionalSkillsRoot = process.env.AGENT_NATIVE_CODE_AGENT_SKILLS_ROOT?.trim();
1466
1467
  return [
1467
1468
  `You are running from Agent-Native Code in ${run.cwd || process.cwd()}.`,
1469
+ `Treat ${run.cwd || process.cwd()} as the only project checkout for this run. Keep shell commands, file operations, git pushes, and pull requests rooted there; do not use absolute paths or .. to reach another checkout.`,
1468
1470
  "Follow the repository AGENTS.md and any relevant skill instructions.",
1469
1471
  ...(additionalSkillsRoot
1470
1472
  ? [
@@ -2164,6 +2166,7 @@ You bring a senior engineer's judgment to the work, but you let it arrive throug
2164
2166
  - When you search for text or files, reach first for \`rg\` or \`rg --files\`; they are much faster than \`grep\` or \`find\`. If \`rg\` is unavailable, use the next best tool without fuss.
2165
2167
  - Parallelize independent read-only work (file reads, searches) so you gather context quickly. Keep mutating steps ordered.
2166
2168
  - Read relevant files before editing them. Do not edit a file you have not actually read.
2169
+ - Treat ${cwd} as the only project checkout for this run. Keep shell commands, file operations, git pushes, and pull requests rooted there; do not use absolute paths or .. to reach another checkout.
2167
2170
 
2168
2171
  # Engineering judgment
2169
2172
 
@@ -1,6 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import fs from "node:fs";
3
- import os from "node:os";
4
3
  import path from "node:path";
5
4
  const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
6
5
  const DEFAULT_MAX_OUTPUT_CHARS = 50_000;
@@ -63,6 +62,9 @@ export function createCodingToolRegistry(options = {}) {
63
62
  if (!commandCwd) {
64
63
  return "Error: cwd must stay inside the workspace.";
65
64
  }
65
+ if (restrictToCwd && commandReferencesOutsideWorkspace(command, cwd)) {
66
+ return "Error: command paths must stay inside the workspace.";
67
+ }
66
68
  const requestedTimeoutMs = Number(args.timeoutMs);
67
69
  const timeoutMs = Number.isFinite(requestedTimeoutMs) && requestedTimeoutMs > 0
68
70
  ? Math.min(requestedTimeoutMs, 10 * 60_000)
@@ -367,7 +369,12 @@ export async function runCodingCommand(command, cwd, timeoutMs, options = {}) {
367
369
  * calling registry).
368
370
  */
369
371
  export function spawnBackgroundCommand(command, cwd) {
370
- const logFile = path.join(os.tmpdir(), `an-bg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.log`);
372
+ const logDirectory = resolveCodingPath(cwd, path.join(".agent-native", "background-logs"), { restrictToCwd: true });
373
+ if (!logDirectory) {
374
+ throw new Error("Background log path must stay inside the workspace.");
375
+ }
376
+ fs.mkdirSync(logDirectory, { recursive: true });
377
+ const logFile = path.join(logDirectory, `an-bg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.log`);
371
378
  const logFd = fs.openSync(logFile, "a");
372
379
  const child = spawn(command, {
373
380
  cwd,
@@ -464,11 +471,65 @@ function resolveCodingPath(cwd, value, options) {
464
471
  : path.resolve(cwd, target);
465
472
  if (!options.restrictToCwd)
466
473
  return resolved;
467
- const relative = path.relative(cwd, resolved);
474
+ const workspaceRoot = resolveRealPath(cwd);
475
+ const targetPath = resolveRealPathWithMissingTail(resolved);
476
+ if (!workspaceRoot || !targetPath)
477
+ return null;
478
+ const relative = path.relative(workspaceRoot, targetPath);
468
479
  if (relative.startsWith("..") || path.isAbsolute(relative))
469
480
  return null;
470
481
  return resolved;
471
482
  }
483
+ function resolveRealPath(value) {
484
+ try {
485
+ return fs.realpathSync(value);
486
+ }
487
+ catch {
488
+ // coercion-ok: an unreadable workspace cannot safely authorize a path.
489
+ return null;
490
+ }
491
+ }
492
+ function resolveRealPathWithMissingTail(value) {
493
+ let candidate = value;
494
+ const missingTail = [];
495
+ while (true) {
496
+ try {
497
+ fs.lstatSync(candidate);
498
+ const existingPath = resolveRealPath(candidate);
499
+ return existingPath ? path.resolve(existingPath, ...missingTail) : null;
500
+ }
501
+ catch (error) {
502
+ if (error.code !== "ENOENT") {
503
+ // coercion-ok: an unreadable path cannot safely authorize a workspace path.
504
+ return null;
505
+ }
506
+ const parent = path.dirname(candidate);
507
+ if (parent === candidate)
508
+ return null;
509
+ missingTail.unshift(path.basename(candidate));
510
+ candidate = parent;
511
+ }
512
+ }
513
+ }
514
+ function commandReferencesOutsideWorkspace(command, cwd) {
515
+ if (/(^|[\s"'=:(])\.\.(?:[/\s"';&|<>)]|$)/.test(command)) {
516
+ return true;
517
+ }
518
+ const workspaceRoot = resolveRealPath(cwd) ?? path.resolve(cwd);
519
+ const absolutePathPattern = /(?:^|[\s"'=:(])((?:\/(?!\/)|~(?:\/|$))[^\s"';&|<>)]*)/g;
520
+ for (const match of command.matchAll(absolutePathPattern)) {
521
+ const value = match[1];
522
+ if (!value || value.startsWith("~"))
523
+ return true;
524
+ const targetPath = resolveRealPathWithMissingTail(path.resolve(value));
525
+ if (!targetPath)
526
+ return true;
527
+ const relative = path.relative(workspaceRoot, targetPath);
528
+ if (relative.startsWith("..") || path.isAbsolute(relative))
529
+ return true;
530
+ }
531
+ return false;
532
+ }
472
533
  function formatFileReadOutput(cwd, filePath, content, args) {
473
534
  const lines = content.split("\n");
474
535
  const offset = positiveInteger(args.offset, 1);
@@ -11,6 +11,7 @@ const CONTROL_ACTIONS = new Set([
11
11
  "type",
12
12
  "key",
13
13
  "navigate",
14
+ "open-tab",
14
15
  "scroll",
15
16
  ]);
16
17
  const BROWSER_KEYS = new Set([
@@ -142,7 +143,7 @@ function buildReadAction(args) {
142
143
  function buildControlAction(args) {
143
144
  const action = readString(args, "action");
144
145
  if (!action || (!CONTROL_ACTIONS.has(action) && action !== "stop")) {
145
- throw new Error("Control action must be attach, click, type, key, navigate, scroll, or stop");
146
+ throw new Error("Control action must be attach, click, type, key, navigate, open-tab, scroll, or stop");
146
147
  }
147
148
  if (action === "attach") {
148
149
  return {
@@ -201,6 +202,16 @@ function buildControlAction(args) {
201
202
  input: { url },
202
203
  };
203
204
  }
205
+ if (action === "open-tab") {
206
+ const url = readString(args, "url");
207
+ if (!url)
208
+ throw new Error("url is required");
209
+ return {
210
+ type: "browser.open-tab",
211
+ target: null,
212
+ input: { url },
213
+ };
214
+ }
204
215
  if (action === "scroll") {
205
216
  return {
206
217
  type: "browser.scroll",
@@ -388,7 +399,7 @@ export function createRemoteBrowserActionEntries(options) {
388
399
  timeoutMs: 30_000,
389
400
  needsApproval: (args) => args.action !== "stop",
390
401
  tool: {
391
- description: "Attach to and control a user-granted Chrome page. Attach once per conversation before observing or acting. Targets must come from the latest observation. Control actions require inline user approval.",
402
+ description: "Attach to and control a user-granted Chrome page. Attach once per conversation before observing or acting. Targets must come from the latest observation. The approved open-tab action creates an inactive same-origin tab and moves the lease without focusing Chrome. Control actions require inline user approval.",
392
403
  parameters: {
393
404
  type: "object",
394
405
  properties: {
@@ -401,6 +412,7 @@ export function createRemoteBrowserActionEntries(options) {
401
412
  "type",
402
413
  "key",
403
414
  "navigate",
415
+ "open-tab",
404
416
  "scroll",
405
417
  "stop",
406
418
  ],
@@ -42,22 +42,22 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
42
42
  avgEvalScore: number;
43
43
  } | {
44
44
  error?: undefined;
45
+ ok?: undefined;
45
46
  summary: import("./types.js").TraceSummary;
46
47
  spans: import("./types.js").TraceSpan[];
47
48
  id?: undefined;
48
- ok?: undefined;
49
49
  } | {
50
50
  error?: undefined;
51
+ ok?: undefined;
51
52
  summary?: undefined;
52
53
  spans?: undefined;
53
54
  id: string;
54
- ok?: undefined;
55
55
  } | {
56
+ ok?: undefined;
56
57
  summary?: undefined;
57
58
  spans?: undefined;
58
59
  id?: undefined;
59
60
  error: any;
60
- ok?: undefined;
61
61
  } | {
62
62
  error?: undefined;
63
63
  summary?: undefined;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- ok: boolean;
19
18
  error?: undefined;
19
+ ok: boolean;
20
20
  }>>;
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- error: string;
52
51
  ok?: undefined;
52
+ error: string;
53
53
  } | {
54
54
  error?: undefined;
55
55
  ok: boolean;
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
34
34
  /** POST /_agent-native/secrets/:key — write a secret. */
35
35
  export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
36
36
  error: string;
37
- status?: undefined;
38
37
  ok?: undefined;
38
+ status?: undefined;
39
39
  } | {
40
+ error?: undefined;
40
41
  ok: boolean;
41
42
  status: string;
42
- error?: undefined;
43
43
  } | {
44
+ ok?: undefined;
44
45
  error: string;
45
46
  removed?: undefined;
46
- ok?: undefined;
47
47
  } | {
48
+ error?: undefined;
48
49
  ok: boolean;
49
50
  removed: boolean;
50
- error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
54
54
  * or the current stored value without changing anything.
55
55
  */
56
56
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
+ ok?: undefined;
57
58
  error: string;
58
59
  note?: undefined;
59
- ok?: undefined;
60
60
  } | {
61
+ error?: undefined;
61
62
  ok: boolean;
62
63
  note?: undefined;
63
- error?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  ok: boolean;
66
67
  note: string;
67
- error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
+ error?: undefined;
98
99
  ok: boolean;
99
100
  key: string;
100
- error?: undefined;
101
101
  } | {
102
+ error?: undefined;
102
103
  ok: boolean;
103
104
  removed: boolean;
104
- error?: undefined;
105
105
  }>>;
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  token: string;
30
31
  expiresAt: string;
31
32
  ttlSeconds: number;
32
- error?: undefined;
33
33
  }>>;
@@ -20,6 +20,6 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- text: string;
24
23
  error?: undefined;
24
+ text: string;
25
25
  }>>;
@@ -179,14 +179,17 @@ flag state. Custom and third-party apps are excluded.
179
179
  Canonical hosted apps recognize packaged Desktop requests without per-app
180
180
  identity-hub environment configuration. Ordinary browsers and self-hosted apps
181
181
  still require `AGENT_NATIVE_IDENTITY_HUB_URL` to opt in.
182
- Stable Desktop builds do not initialize the broker until a later release is
183
- separately approved.
182
+ Every Desktop build may initialize the broker when the per-device
183
+ `desktopSsoEnabled` preference is true (the default); an explicit persisted
184
+ `false` remains an opt-out. The `desktop.workspace-sso` Dispatch flag must also
185
+ be enabled. The Canary user-agent marker no longer gates broker initialization;
186
+ it only identifies the update channel.
184
187
  The Canary user-agent marker is an availability hint rather than remote
185
188
  attestation. Authentication and authorization never trust it; supervised
186
189
  acceptance binds the client through exact signed-artifact provenance.
187
- The Canary does not intercept anonymous app sign-in navigation. The user must
188
- explicitly choose workspace sign-in in Desktop Settings for the active
189
- registered app; ordinary app sign-in remains the default path.
190
+ For an eligible registered app, Desktop presents the workspace sign-in surface
191
+ over the app's sign-in flow. If the rollout is unavailable or the app is not
192
+ eligible, ordinary per-app sign-in remains available.
190
193
 
191
194
  ## QA Accounts {#qa-accounts}
192
195
 
@@ -37,6 +37,22 @@ cookie**. Each separately hosted app still creates and owns its own local
37
37
  session. Dispatch proves the user's identity at the app's front door, and the
38
38
  app then continues with its normal local auth and access checks.
39
39
 
40
+ ## Mobile and app-scoped embed sessions {#mobile-embed-sessions}
41
+
42
+ Mobile chat and Dispatch panes use a different contract from MCP-hosted iframe
43
+ embeds. The mobile client first authenticates the parent session, then requests
44
+ an app-scoped, one-time embed ticket for the target workspace app. The ticket
45
+ is exchanged at the target app's embed-start endpoint for a short-lived local
46
+ embed session and is consumed once. It is not a reusable bearer token, is not
47
+ placed in a long-lived URL, and does not grant access to another app.
48
+
49
+ This flow lets a mobile WebView or Dispatch pane open a target app while the
50
+ target app still owns its session and authorization checks. A missing, expired,
51
+ replayed, or cross-app ticket must fail closed; it must not be treated as an
52
+ anonymous session or silently fall back to the parent bearer. The `embed
53
+ session` described in [MCP Apps](/docs/mcp-apps) is a separate MCP-host iframe
54
+ feature and is not this mobile handoff contract.
55
+
40
56
  The packaged Desktop Canary is deliberately more explicit: it never intercepts
41
57
  anonymous app sign-in navigation. The user chooses workspace sign-in once in
42
58
  Desktop Settings; Desktop then provisions every currently eligible registered
@@ -352,14 +368,21 @@ Desktop leaves ordinary per-app sign-in, sign-out, and Settings unchanged.
352
368
  Canonical hosted apps recognize the packaged Desktop request and ordinary web
353
369
  browsers without requiring `AGENT_NATIVE_IDENTITY_HUB_URL` on every deployment;
354
370
  self-hosted apps still require that explicit configuration.
355
- Stable Desktop builds do not initialize the broker until a later release is
356
- separately approved.
371
+ Every Desktop build may initialize the broker when the per-device
372
+ `desktopSsoEnabled` preference is true (the default); an explicit persisted
373
+ `false` remains an opt-out. The `desktop.workspace-sso` Dispatch flag must also
374
+ be enabled. The Canary user-agent marker no longer gates broker initialization;
375
+ it only identifies the update channel.
357
376
  The Canary user-agent marker is an availability hint, not remote attestation;
358
377
  the server cannot infer code signature from HTTP. No authentication or
359
378
  authorization decision trusts it. Supervised acceptance therefore binds the
360
379
  tested client through the signed artifact's exact commit, manifest, hashes, and
361
380
  notarization evidence.
362
381
 
382
+ For an eligible registered app, Desktop presents the workspace sign-in surface
383
+ over the app's sign-in flow. If the rollout is unavailable or the app is not
384
+ eligible, ordinary per-app sign-in remains available.
385
+
363
386
  ## Self-hosting {#self-hosting}
364
387
 
365
388
  Any Dispatch deployment can serve as the identity hub — you are not limited to `dispatch.agent-native.com`. Set `AGENT_NATIVE_IDENTITY_HUB_URL` on each client app to point at your Dispatch instance:
@@ -0,0 +1,61 @@
1
+ ---
2
+ title: "Portal"
3
+ description: "Pair a computer and continue Agent-Native work across devices with Portal."
4
+ ---
5
+
6
+ # Portal
7
+
8
+ Portal connects a phone or browser to a registered computer so an Agent-Native
9
+ workspace can continue work on the machine that owns the local tools. It is a
10
+ device handoff feature, not a second chat or a cloud runner.
11
+
12
+ ## Pair a computer
13
+
14
+ In the app, open **Settings → Workspace** and choose **Pair or repair**. The
15
+ pairing flow shows a relay URL and workspace path. Open the URL on the computer
16
+ you want to pair and confirm the workspace path there. The computer registers
17
+ itself with the workspace through:
18
+
19
+ ```text
20
+ POST /_agent-native/integrations/remote/register
21
+ ```
22
+
23
+ The registration returns a device token. Store that token in the computer's
24
+ private `~/.agent-native/remote-device.json` file. Do not put it in source
25
+ control, a URL, or a client-visible application setting. If pairing is lost,
26
+ use **Pair or repair** again to replace the device registration.
27
+
28
+ ## Execution modes
29
+
30
+ The chat composer can target the execution environment that fits the task:
31
+
32
+ - **Local** runs on the current app/runtime.
33
+ - **Computer** runs on a paired computer through Portal, so local files and
34
+ installed coding tools are available there.
35
+ - **Cloud** is a waitlisted execution mode. Until access is enabled, choosing
36
+ Cloud does not start a run; it only shows the waitlist state.
37
+
38
+ The selected mode is part of the run request. Pairing a computer does not copy
39
+ credentials or grant it access to unrelated workspaces; the device and
40
+ workspace registration remain scoped to the authenticated account.
41
+
42
+ ## Troubleshooting
43
+
44
+ If Computer is unavailable, confirm the paired computer is online, the relay
45
+ URL is reachable, and the workspace path still exists. Repair the pairing when
46
+ the device token was rotated or the workspace moved. If Cloud is unavailable,
47
+ the run has not started - use Local or Computer instead of retrying a queued
48
+ cloud request.
49
+
50
+ ## Environment variables
51
+
52
+ Self-hosted deployments can customize the Portal connection with:
53
+
54
+ | Variable | Purpose |
55
+ | ------------------------------------ | -------------------------------------------------------------------- |
56
+ | `WORKSPACE_GATEWAY_URL` | Public workspace gateway used for device handoff. |
57
+ | `AGENT_NATIVE_REMOTE_DEVICE_PATH` | Override the local device registration file path. |
58
+ | `AGENT_NATIVE_COMPUTER_BRIDGE_URL` | Computer bridge URL used by remote execution. |
59
+ | `AGENT_NATIVE_COMPUTER_BRIDGE_TOKEN` | Token used to authenticate with that bridge. |
60
+ | `AGENT_NATIVE_REMOTE_ENGINES` | Comma-separated remote execution engines available to the workspace. |
61
+ | `AGENT_NATIVE_IDENTITY_HUB_URL` | Optional Dispatch identity hub for self-hosted cross-app sign-in. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.157.28",
3
+ "version": "0.158.2",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {