Skip to content

Commit 33ab20f

Browse files
authored
Merge pull request #991 from taras/agent/workspace-mounts-20261025
2 parents 7e2d824 + e8fd4f0 commit 33ab20f

51 files changed

Lines changed: 4011 additions & 426 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎architecture.md‎

Lines changed: 56 additions & 3 deletions
Large diffs are not rendered by default.

‎packages/cli/src/cli.ts‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { resolve } from "node:path";
2+
import { parseWorkspaceMounts } from "./workspace-mounts.ts";
13
/**
24
* CLI — run an executable markdown document.
35
*
@@ -320,6 +322,10 @@ const REPL_DESCRIPTION = "Run and reconstruct one XMD entry in an interactive te
320322
* their entries is not the same choice as who their entries talk to.
321323
*/
322324
const replConfig = object({
325+
mount: {
326+
description: "external SOURCE:TARGET directory; repeat for each explicit TARGET",
327+
...field(z.array(z.string()), field.default([]), field.array()),
328+
},
323329
location: {
324330
description:
325331
"a location this REPL printed earlier — `xmd://repl/<execution>/<surface>...` — to reopen " +
@@ -598,6 +604,8 @@ const REPL_HELP = [
598604
" Each one starts from the bindings the entries before it published, so a",
599605
" later entry can read and revise what an earlier one left behind.",
600606
"",
607+
" --mount SOURCE:TARGET adds an external directory at a required document path.",
608+
" Repeat it: --mount /work/manuals:/reference --mount /work/fixtures:/fixtures.",
601609
" --include <directory> and --include=<directory> select component directories.",
602610
" Repeat them in search order; explicit values replace the default list [components, .].",
603611
" Relative directories resolve from the invocation working directory.",
@@ -657,14 +665,15 @@ export interface ReplHostAssembly {
657665
* silence would let them believe they got it.
658666
*/
659667
/**
660-
* The four REPL options that carry a value, by the spelling a caller writes.
668+
* The five REPL options that carry a value, by the spelling a caller writes.
661669
*
662670
* Exported because the documentation check reads this command's own examples,
663671
* and a token after one of these is that option's value rather than a location.
664672
* A second roster beside this one would be a reader that disagreed with the
665673
* command about what it had been handed.
666674
*/
667675
export const REPL_VALUES: readonly string[] = [
676+
"--mount",
668677
"--include",
669678
"--agent-provider",
670679
"--default-agent",
@@ -722,7 +731,7 @@ export function replGrammarError(
722731
return (
723732
`unrecognized option for xmd repl: ${name} — xmd repl takes one optional location, ` +
724733
"the agent options --agent-provider, --default-agent, --approve-all, --approve-reads " +
725-
"and --deny-all, --include for component directories, and --sidekick-agent for the Sidekick's own conversations"
734+
"and --deny-all, --include for component directories, --mount SOURCE:TARGET for workspace directories, and --sidekick-agent for the Sidekick's own conversations"
726735
);
727736
}
728737
if (positional.length > 1) {
@@ -2943,6 +2952,13 @@ function* dispatch(
29432952
yield* exit(1);
29442953
break;
29452954
}
2955+
const mountCwd = yield* cwd();
2956+
const mounts = parseWorkspaceMounts(command.config.mount, mountCwd, { resolve });
2957+
if (!mounts.ok) {
2958+
console.error(mounts.error.message);
2959+
yield* exit(1);
2960+
break;
2961+
}
29462962
// The Agent configuration, settled before a terminal exists: mutual
29472963
// exclusion, an unknown provider and `DEFAULT_AGENT_NAME` precedence are
29482964
// all decided here, so an invocation nobody could run refuses without
@@ -3015,6 +3031,7 @@ function* dispatch(
30153031
[testAgentChildDeclaration()],
30163032
);
30173033
},
3034+
mounts: mounts.value,
30183035
includes: command.config.include,
30193036
...(command.config.sidekickAgent === undefined
30203037
? {}

‎packages/cli/src/repl-entry-installation.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { WorkspaceMountTable } from "./workspace-mounts.ts";
12
/**
23
* The entry capability this host carries, as inert code.
34
*
@@ -45,7 +46,11 @@ import type { ExecutionInstallation } from "@executablemd/core/host";
4546
*/
4647
export type ReplEntryInstallation = (
4748
owner: string,
48-
placement?: { readonly projectCwd?: string; readonly workspace?: string },
49+
placement?: {
50+
readonly projectCwd?: string;
51+
readonly workspace?: string;
52+
readonly mounts?: WorkspaceMountTable;
53+
},
4954
) => Operation<ExecutionInstallation>;
5055

5156
/** The factory this host runs REPL entries with, or absence. */

‎packages/cli/src/repl-profile.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { WorkspaceMountTable } from "./workspace-mounts.ts";
12
/**
23
* Capture command selections once, then finalize one execution's component paths.
34
*
@@ -55,6 +56,7 @@ export interface ReplProfileSeams {
5556
* parameter: a caller states what it knows and leaves out the rest.
5657
*/
5758
export interface ReplProfileFacts extends ReplProfileSeams {
59+
readonly mounts?: WorkspaceMountTable;
5860
readonly testingHarness?: (includes: readonly string[]) => Operation<ExecutionInstallation>;
5961
readonly includes?: readonly string[];
6062
/**
@@ -97,6 +99,7 @@ export interface ReplProfileAssembly {
9799

98100
/** One finalized execution profile, shared by validation, descriptions and entries. */
99101
export interface ReplExecutionProfile {
102+
readonly mounts?: WorkspaceMountTable;
100103
readonly testing?: ReplTesting;
101104
readonly projectCwd?: string;
102105
/** Where a declared name is looked for, outermost first. */
@@ -202,6 +205,7 @@ export function* assembleReplProfile(
202205
facts: ReplProfileFacts = {},
203206
): Operation<ReplProfileAssembly> {
204207
const projectCwd = yield* cwd();
208+
const mounts = facts.mounts;
205209
const configuredIncludes = Object.freeze(
206210
(facts.includes ?? REPL_INCLUDES).map((path) => resolve(projectCwd, path)),
207211
);
@@ -258,6 +262,7 @@ export function* assembleReplProfile(
258262
});
259263

260264
const profile = Object.freeze({
265+
mounts,
261266
includes,
262267
projectCwd,
263268
vocabulary: entryVocabulary(includes, plugins),

0 commit comments

Comments
 (0)