@dawn-ai/core 0.5.0 → 0.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/capabilities/built-in/workspace.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,gBAAgB,EAAsB,MAAM,aAAa,CAAA;AAuNvE,wBAAgB,qBAAqB,IAAI,gBAAgB,CAoBxD"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/capabilities/built-in/workspace.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAsB,MAAM,aAAa,CAAA;AA6GvE,wBAAgB,qBAAqB,IAAI,gBAAgB,CAoBxD"}
@@ -1,9 +1,9 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join, relative, resolve, sep } from "node:path";
3
- import { suggestedCommandPattern, suggestedPathPattern } from "@dawn-ai/permissions";
4
3
  import { localExec, localFilesystem } from "@dawn-ai/workspace";
5
- import { interrupt } from "@langchain/langgraph";
6
4
  import { z } from "zod";
5
+ import { gateBashOp } from "../permission-gate.js";
6
+ import { createWorkspaceFs } from "../workspace-fs.js";
7
7
  const WORKSPACE_DIRNAME = "workspace";
8
8
  /**
9
9
  * Resolve the workspace root relative to the given app root. In production
@@ -21,90 +21,18 @@ const RUN_BASH_INPUT = z.object({ command: z.string().min(1) });
21
21
  function backendContext(workspaceRoot, signal) {
22
22
  return { signal, workspaceRoot };
23
23
  }
24
- async function gatePathOp(permissions, operation, absPath, workspaceRoot) {
25
- // If permissions store is absent, allow (legacy behavior — capability used without permissions context).
26
- if (!permissions)
27
- return { allowed: true };
28
- if (permissions.mode === "bypass")
29
- return { allowed: true };
30
- const insideWorkspace = absPath === workspaceRoot || absPath.startsWith(workspaceRoot + sep);
31
- // Inside workspace: always allow silently.
32
- if (insideWorkspace)
33
- return { allowed: true };
34
- // Outside workspace: consult the store.
35
- const decision = permissions.match(operation, absPath);
36
- if (decision === "allow")
37
- return { allowed: true };
38
- if (decision === "deny") {
39
- return { allowed: false, reason: `Permission denied by user: ${absPath}` };
40
- }
41
- // decision === "unknown"
42
- if (permissions.mode === "non-interactive") {
43
- return { allowed: false, reason: `Permission denied (fail-closed): ${absPath}` };
44
- }
45
- // Interactive: emit LangGraph interrupt and await user decision.
46
- const result = await emitPermissionInterrupt({
47
- kind: "path",
48
- operation,
49
- path: absPath,
50
- permissions,
51
- });
52
- if (result === "deny") {
53
- return { allowed: false, reason: `Permission denied by user: ${absPath}` };
54
- }
55
- return { allowed: true };
56
- }
57
- async function gateBashOp(permissions, command) {
58
- if (!permissions)
59
- return { allowed: true };
60
- if (permissions.mode === "bypass")
61
- return { allowed: true };
62
- const decision = permissions.match("bash", command);
63
- if (decision === "allow")
64
- return { allowed: true };
65
- if (decision === "deny") {
66
- return { allowed: false, reason: `Permission denied by user: ${command}` };
67
- }
68
- if (permissions.mode === "non-interactive") {
69
- return { allowed: false, reason: `Permission denied (fail-closed): ${command}` };
70
- }
71
- const result = await emitPermissionInterrupt({
72
- kind: "command",
73
- command,
74
- permissions,
75
- });
76
- if (result === "deny") {
77
- return { allowed: false, reason: `Permission denied by user: ${command}` };
78
- }
79
- return { allowed: true };
80
- }
81
- async function emitPermissionInterrupt(args) {
82
- const interruptId = `perm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
83
- const suggestedPattern = args.kind === "command"
84
- ? suggestedCommandPattern(args.command ?? "")
85
- : suggestedPathPattern(args.path ?? "");
86
- const payload = {
87
- interruptId,
88
- type: "permission-request",
89
- kind: args.kind,
90
- detail: args.kind === "command"
91
- ? { command: args.command ?? "", suggestedPattern }
92
- : {
93
- operation: args.operation ?? "readFile",
94
- path: args.path ?? "",
95
- suggestedPattern,
96
- },
97
- };
98
- const decision = interrupt(payload);
99
- if (decision === "deny")
100
- return "deny";
101
- if (decision === "always") {
102
- const tool = args.kind === "command" ? "bash" : (args.operation ?? "readFile");
103
- await args.permissions.addAllow(tool, suggestedPattern);
104
- }
105
- return "allow";
106
- }
107
24
  function buildWorkspaceTools(workspaceRoot, fs, exec, permissions) {
25
+ // Agent tools run inside the graph, so the handle may surface the
26
+ // interactive LangGraph permission interrupt.
27
+ function handleFor(signal) {
28
+ return createWorkspaceFs({
29
+ workspaceRoot,
30
+ backend: fs,
31
+ permissions,
32
+ signal,
33
+ interruptCapable: true,
34
+ });
35
+ }
108
36
  const readFile = {
109
37
  name: "readFile",
110
38
  description: "Read a UTF-8 file from the workspace.",
@@ -112,19 +40,15 @@ function buildWorkspaceTools(workspaceRoot, fs, exec, permissions) {
112
40
  overridable: true,
113
41
  run: async (input, ctx) => {
114
42
  const { path } = READ_FILE_INPUT.parse(input);
43
+ const handle = handleFor(ctx.signal);
115
44
  const absPath = resolve(workspaceRoot, path);
116
- const gate = await gatePathOp(permissions, "readFile", absPath, workspaceRoot);
117
- if (!gate.allowed) {
118
- throw new Error(gate.reason);
119
- }
120
- const bctx = backendContext(workspaceRoot, ctx.signal);
121
45
  const rel = relative(workspaceRoot, absPath);
122
46
  // NOTE: must match SUBDIR ("tool-outputs") in @dawn-ai/langchain offload-store.ts
123
47
  const isToolOutput = rel === "tool-outputs" || rel.startsWith(`tool-outputs${sep}`);
124
- const data = await fs.readFile(absPath, bctx, isToolOutput ? { maxBytes: Number.POSITIVE_INFINITY } : undefined);
48
+ const data = await handle.readFile(path, isToolOutput ? { maxBytes: Number.POSITIVE_INFINITY } : undefined);
125
49
  if (isToolOutput && fs.touchFile) {
126
50
  try {
127
- await fs.touchFile(absPath, bctx);
51
+ await fs.touchFile(absPath, backendContext(workspaceRoot, ctx.signal));
128
52
  }
129
53
  catch {
130
54
  /* touch is best-effort; never fail a read because of it */
@@ -140,12 +64,7 @@ function buildWorkspaceTools(workspaceRoot, fs, exec, permissions) {
140
64
  overridable: true,
141
65
  run: async (input, ctx) => {
142
66
  const { path, content } = WRITE_FILE_INPUT.parse(input);
143
- const absPath = resolve(workspaceRoot, path);
144
- const gate = await gatePathOp(permissions, "writeFile", absPath, workspaceRoot);
145
- if (!gate.allowed) {
146
- throw new Error(gate.reason);
147
- }
148
- const result = await fs.writeFile(absPath, content, backendContext(workspaceRoot, ctx.signal));
67
+ const result = await handleFor(ctx.signal).writeFile(path, content);
149
68
  return `wrote ${result.bytesWritten} bytes to ${path}`;
150
69
  },
151
70
  };
@@ -156,13 +75,7 @@ function buildWorkspaceTools(workspaceRoot, fs, exec, permissions) {
156
75
  overridable: true,
157
76
  run: async (input, ctx) => {
158
77
  const { path } = LIST_DIR_INPUT.parse(input);
159
- const absPath = resolve(workspaceRoot, path);
160
- const gate = await gatePathOp(permissions, "listDir", absPath, workspaceRoot);
161
- if (!gate.allowed) {
162
- throw new Error(gate.reason);
163
- }
164
- const entries = await fs.listDir(absPath, backendContext(workspaceRoot, ctx.signal));
165
- return [...entries];
78
+ return [...(await handleFor(ctx.signal).listDir(path))];
166
79
  },
167
80
  };
168
81
  const runBash = {
@@ -0,0 +1,13 @@
1
+ import type { PermissionsStore } from "@dawn-ai/permissions";
2
+ export type PathOperation = "readFile" | "writeFile" | "listDir";
3
+ export type GateResult = {
4
+ allowed: true;
5
+ } | {
6
+ allowed: false;
7
+ reason: string;
8
+ };
9
+ export declare function gatePathOp(permissions: PermissionsStore | undefined, operation: PathOperation, absPath: string, workspaceRoot: string, opts?: {
10
+ readonly interruptCapable?: boolean;
11
+ }): Promise<GateResult>;
12
+ export declare function gateBashOp(permissions: PermissionsStore | undefined, command: string): Promise<GateResult>;
13
+ //# sourceMappingURL=permission-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-gate.d.ts","sourceRoot":"","sources":["../../src/capabilities/permission-gate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAI5D,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAA;AAEhE,MAAM,MAAM,UAAU,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/E,wBAAsB,UAAU,CAC9B,WAAW,EAAE,gBAAgB,GAAG,SAAS,EACzC,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7C,OAAO,CAAC,UAAU,CAAC,CAwCrB;AAED,wBAAsB,UAAU,CAC9B,WAAW,EAAE,gBAAgB,GAAG,SAAS,EACzC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,CAAC,CAqBrB"}
@@ -0,0 +1,94 @@
1
+ import { sep } from "node:path";
2
+ import { suggestedCommandPattern, suggestedPathPattern } from "@dawn-ai/permissions";
3
+ import { interrupt } from "@langchain/langgraph";
4
+ export async function gatePathOp(permissions, operation, absPath, workspaceRoot, opts) {
5
+ // If permissions store is absent, allow (legacy behavior — capability used without permissions context).
6
+ if (!permissions)
7
+ return { allowed: true };
8
+ if (permissions.mode === "bypass")
9
+ return { allowed: true };
10
+ const insideWorkspace = absPath === workspaceRoot || absPath.startsWith(workspaceRoot + sep);
11
+ // Inside workspace: always allow silently.
12
+ if (insideWorkspace)
13
+ return { allowed: true };
14
+ // Outside workspace: consult the store.
15
+ const decision = permissions.match(operation, absPath);
16
+ if (decision === "allow")
17
+ return { allowed: true };
18
+ if (decision === "deny") {
19
+ return { allowed: false, reason: `Permission denied by user: ${absPath}` };
20
+ }
21
+ // decision === "unknown"
22
+ if (permissions.mode === "non-interactive") {
23
+ return { allowed: false, reason: `Permission denied (fail-closed): ${absPath}` };
24
+ }
25
+ if (opts?.interruptCapable === false) {
26
+ return {
27
+ allowed: false,
28
+ reason: `Permission denied: ${absPath} is outside the workspace and interactive ` +
29
+ `permission prompts are not available in this execution context. ` +
30
+ `Add an allow rule for "${operation}" to the permissions config in dawn.config.ts.`,
31
+ };
32
+ }
33
+ // Interactive: emit LangGraph interrupt and await user decision.
34
+ const result = await emitPermissionInterrupt({
35
+ kind: "path",
36
+ operation,
37
+ path: absPath,
38
+ permissions,
39
+ });
40
+ if (result === "deny") {
41
+ return { allowed: false, reason: `Permission denied by user: ${absPath}` };
42
+ }
43
+ return { allowed: true };
44
+ }
45
+ export async function gateBashOp(permissions, command) {
46
+ if (!permissions)
47
+ return { allowed: true };
48
+ if (permissions.mode === "bypass")
49
+ return { allowed: true };
50
+ const decision = permissions.match("bash", command);
51
+ if (decision === "allow")
52
+ return { allowed: true };
53
+ if (decision === "deny") {
54
+ return { allowed: false, reason: `Permission denied by user: ${command}` };
55
+ }
56
+ if (permissions.mode === "non-interactive") {
57
+ return { allowed: false, reason: `Permission denied (fail-closed): ${command}` };
58
+ }
59
+ const result = await emitPermissionInterrupt({
60
+ kind: "command",
61
+ command,
62
+ permissions,
63
+ });
64
+ if (result === "deny") {
65
+ return { allowed: false, reason: `Permission denied by user: ${command}` };
66
+ }
67
+ return { allowed: true };
68
+ }
69
+ async function emitPermissionInterrupt(args) {
70
+ const interruptId = `perm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
71
+ const suggestedPattern = args.kind === "command"
72
+ ? suggestedCommandPattern(args.command ?? "")
73
+ : suggestedPathPattern(args.path ?? "");
74
+ const payload = {
75
+ interruptId,
76
+ type: "permission-request",
77
+ kind: args.kind,
78
+ detail: args.kind === "command"
79
+ ? { command: args.command ?? "", suggestedPattern }
80
+ : {
81
+ operation: args.operation ?? "readFile",
82
+ path: args.path ?? "",
83
+ suggestedPattern,
84
+ },
85
+ };
86
+ const decision = interrupt(payload);
87
+ if (decision === "deny")
88
+ return "deny";
89
+ if (decision === "always") {
90
+ const tool = args.kind === "command" ? "bash" : (args.operation ?? "readFile");
91
+ await args.permissions.addAllow(tool, suggestedPattern);
92
+ }
93
+ return "allow";
94
+ }
@@ -1,5 +1,5 @@
1
1
  import type { PermissionsStore } from "@dawn-ai/permissions";
2
- import type { DawnAgent } from "@dawn-ai/sdk";
2
+ import type { DawnAgent, WorkspaceFs } from "@dawn-ai/sdk";
3
3
  import type { ExecBackend, FilesystemBackend } from "@dawn-ai/workspace";
4
4
  import type { ResolvedStateField, RouteManifest } from "../types.js";
5
5
  export interface CapabilityMarkerContext {
@@ -20,6 +20,7 @@ export interface DawnToolDefinition {
20
20
  readonly run: (input: unknown, context: {
21
21
  readonly middleware?: Readonly<Record<string, unknown>>;
22
22
  readonly signal: AbortSignal;
23
+ readonly fs?: WorkspaceFs;
23
24
  }) => Promise<unknown> | unknown;
24
25
  readonly schema?: unknown;
25
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/capabilities/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEpE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAA;IACrC,QAAQ,CAAC,UAAU,EAAE,SAAS,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAA;QACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;KAC5B,CAAA;IACD,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAA;IACvC,4IAA4I;IAC5I,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,CACZ,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;QACP,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;QACvD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;KAC7B,KACE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAA;IACvC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,MAAM,CAAA;CACtE;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,QAAQ,CAAC,SAAS,EAAE,CAClB,KAAK,EAAE,sBAAsB,KAC1B,QAAQ,CAAC,uBAAuB,CAAC,GAAG,aAAa,CAAC,uBAAuB,CAAC,CAAA;CAChF;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAA;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAA;IACxD,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;IACxC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAA;CAC/D;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACzF,QAAQ,CAAC,IAAI,EAAE,CACb,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,uBAAuB,KAC7B,OAAO,CAAC,sBAAsB,CAAC,CAAA;CACrC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/capabilities/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { PermissionsStore } from "@dawn-ai/permissions";
2
+ import type { WorkspaceFs } from "@dawn-ai/sdk";
3
+ import type { FilesystemBackend } from "@dawn-ai/workspace";
4
+ export interface CreateWorkspaceFsOptions {
5
+ readonly workspaceRoot: string;
6
+ readonly backend: FilesystemBackend;
7
+ readonly permissions: PermissionsStore | undefined;
8
+ readonly signal: AbortSignal;
9
+ /**
10
+ * Whether this execution context can surface the interactive LangGraph
11
+ * permission interrupt (true inside agent-route tool execution; false for
12
+ * workflow/graph entries, which run outside the graph).
13
+ */
14
+ readonly interruptCapable: boolean;
15
+ }
16
+ /**
17
+ * Build the author-facing sandboxed filesystem handle (`ctx.fs`). Paths are
18
+ * workspace-relative; every call runs the same permission gate as the
19
+ * agent-facing workspace tools.
20
+ */
21
+ export declare function createWorkspaceFs(opts: CreateWorkspaceFsOptions): WorkspaceFs;
22
+ //# sourceMappingURL=workspace-fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-fs.d.ts","sourceRoot":"","sources":["../../src/capabilities/workspace-fs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAG3D,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAA;IAClD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAA;CACnC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,WAAW,CAoC7E"}
@@ -0,0 +1,41 @@
1
+ import { resolve } from "node:path";
2
+ import { gatePathOp } from "./permission-gate.js";
3
+ /**
4
+ * Build the author-facing sandboxed filesystem handle (`ctx.fs`). Paths are
5
+ * workspace-relative; every call runs the same permission gate as the
6
+ * agent-facing workspace tools.
7
+ */
8
+ export function createWorkspaceFs(opts) {
9
+ const bctx = { signal: opts.signal, workspaceRoot: opts.workspaceRoot };
10
+ async function gate(operation, path) {
11
+ const absPath = resolve(opts.workspaceRoot, path);
12
+ const result = await gatePathOp(opts.permissions, operation, absPath, opts.workspaceRoot, {
13
+ interruptCapable: opts.interruptCapable,
14
+ });
15
+ if (!result.allowed)
16
+ throw new Error(result.reason);
17
+ return absPath;
18
+ }
19
+ return {
20
+ async readFile(path, readOpts) {
21
+ return opts.backend.readFile(await gate("readFile", path), bctx, readOpts);
22
+ },
23
+ async readBinaryFile(path, readOpts) {
24
+ // Check backend capability before gating so users are never prompted to
25
+ // approve a read that will immediately fail.
26
+ const { readBinaryFile } = opts.backend;
27
+ if (!readBinaryFile) {
28
+ throw new Error("The configured filesystem backend does not support binary reads (readBinaryFile). " +
29
+ "localFilesystem supports it; custom backends must implement it.");
30
+ }
31
+ const absPath = await gate("readFile", path);
32
+ return readBinaryFile.call(opts.backend, absPath, bctx, readOpts);
33
+ },
34
+ async writeFile(path, content) {
35
+ return opts.backend.writeFile(await gate("writeFile", path), content, bctx);
36
+ },
37
+ async listDir(path = ".") {
38
+ return [...(await opts.backend.listDir(await gate("listDir", path), bctx))];
39
+ },
40
+ };
41
+ }
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ export { createWorkspaceMarker } from "./capabilities/built-in/workspace.js";
8
8
  export type { AppliedContribution, ApplyResult, CapabilityError, CapabilityRegistry, } from "./capabilities/registry.js";
9
9
  export { applyCapabilities, createCapabilityRegistry } from "./capabilities/registry.js";
10
10
  export type { CapabilityContribution, CapabilityMarker, CapabilityMarkerContext, DawnToolDefinition, PromptFragment, StreamTransformer, StreamTransformerInput, StreamTransformerOutput, } from "./capabilities/types.js";
11
+ export type { CreateWorkspaceFsOptions } from "./capabilities/workspace-fs.js";
12
+ export { createWorkspaceFs } from "./capabilities/workspace-fs.js";
11
13
  export { loadDawnConfig } from "./config.js";
12
14
  export { discoverRoutes } from "./discovery/discover-routes.js";
13
15
  export { assertDawnRoutesDir, findDawnApp } from "./discovery/find-dawn-app.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAA;AAC3E,YAAY,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAA;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AAC5E,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,kBAAkB,GACnB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACxF,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/E,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,GAChB,MAAM,+BAA+B,CAAA;AACtC,YAAY,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AACpE,YAAY,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAA;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAA;AAC7E,YAAY,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAA;AAC1E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACnF,YAAY,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAA;AAC3E,YAAY,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAA;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AAC5E,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,kBAAkB,GACnB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACxF,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAChC,YAAY,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAA;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/E,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,GAChB,MAAM,+BAA+B,CAAA;AACtC,YAAY,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAA;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AACpE,YAAY,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAA;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAA;AAC7E,YAAY,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAA;AAC1E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACnF,YAAY,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AAChE,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { createSkillsMarker } from "./capabilities/built-in/skills.js";
4
4
  export { createSubagentsMarker } from "./capabilities/built-in/subagents.js";
5
5
  export { createWorkspaceMarker } from "./capabilities/built-in/workspace.js";
6
6
  export { applyCapabilities, createCapabilityRegistry } from "./capabilities/registry.js";
7
+ export { createWorkspaceFs } from "./capabilities/workspace-fs.js";
7
8
  export { loadDawnConfig } from "./config.js";
8
9
  export { discoverRoutes } from "./discovery/discover-routes.js";
9
10
  export { assertDawnRoutesDir, findDawnApp } from "./discovery/find-dawn-app.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dawn-ai/core",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,9 +35,9 @@
35
35
  "typescript": "5.8.3",
36
36
  "zod": "^4.4.3",
37
37
  "@dawn-ai/permissions": "0.1.8",
38
+ "@dawn-ai/sdk": "0.7.0",
38
39
  "@dawn-ai/sqlite-storage": "0.2.0",
39
- "@dawn-ai/workspace": "0.2.0",
40
- "@dawn-ai/sdk": "0.5.0"
40
+ "@dawn-ai/workspace": "0.3.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@langchain/langgraph-checkpoint": "^1.0.2"
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@langchain/langgraph-checkpoint": "^1.0.2",
47
47
  "@types/node": "25.6.0",
48
- "@dawn-ai/config-typescript": "0.5.0",
48
+ "@dawn-ai/config-typescript": "0.7.0",
49
49
  "@dawn-ai/sqlite-storage": "0.2.0"
50
50
  },
51
51
  "scripts": {