@dsh-cc/session-cwd 0.5.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.
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @dsh-cc/session-cwd
2
+
3
+ Session-scoped working directory for DeepSeek Harness CC — the WS1 + WS2
4
+ foundation of the [worktree-session-isolation design](../../../docs/plans/worktree-session-isolation.md).
5
+
6
+ ## What it owns
7
+
8
+ - **`worktree/entered` session event** (WS1): registered into the persistence
9
+ layer's `KNOWN_SESSION_EVENT_TYPES` at module load, so logs containing it
10
+ resume cleanly. Payload: `{ path }` — the new absolute session cwd.
11
+ `ExitWorktree` restores the previous directory through the same event
12
+ (last-wins fold).
13
+ - **Foldable state** (`src/state.ts`): `foldSessionCwd` folds the current cwd
14
+ out of the event log; a process-local `SessionCwdStore` overlays the live
15
+ value so reads are immediate and sessions stay independent in one process.
16
+ - **APIs** (`src/api.ts`): `getSessionCwd(agent)` (live overlay → durable fold
17
+ → session header → fallback) and `setSessionCwd(agent, path)` (durable
18
+ event + overlay; absolute paths only).
19
+ - **Workspace boundary guard** (WS2, `src/listener.ts`): a
20
+ `tools/pre-execute` listener registered with `{ prepend: true }` — ahead of
21
+ the permission-rules waterfall — that checks every fs-family call's target
22
+ path against the session cwd. Out-of-workspace targets return
23
+ `{ kind: 'ask' }` ("Operation targets path outside session workspace") in
24
+ every mode except `bypassPermissions`, which allows (the existing audit
25
+ trail covers it). Non-fs tools, pathless calls, and agents without a
26
+ resolvable cwd pass through untouched; the guard is a pre-execute
27
+ convenience, not a hard security boundary.
28
+
29
+ ## Consumers
30
+
31
+ - `tool-git-worktree`: `EnterWorktree` calls `setSessionCwd(worktreePath)`;
32
+ `ExitWorktree` restores `session.originalCwd` the same way.
33
+ - TUI driver: project/history resolution prefers the durable fold over the
34
+ boot-time header cwd.
35
+ - CC preset (`packages/preset/cc/agent.cordis.yml`) composes the plugin, which
36
+ installs the boundary guard.
37
+
38
+ ## Tests
39
+
40
+ ```bash
41
+ cd packages/workspace/session-cwd && npx vitest run tests/
42
+ ```
43
+
44
+ (In a worktree whose root `node_modules` is a symlink the sandbox cannot write
45
+ into, add `--configLoader runner` to skip the `.vite-temp` bundle step.)
package/lib/api.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Session cwd APIs. `getSessionCwd` resolves the authoritative session
3
+ * working directory (live overlay → durable `worktree/entered` fold →
4
+ * session header → fallback); `setSessionCwd` records the change durably by
5
+ * appending a `worktree/entered` event and updating the live overlay.
6
+ *
7
+ * @module @dsh-cc/session-cwd/api
8
+ */
9
+ import type { Agent } from '@deepseek-ai/dsh-agent';
10
+ import { type SessionCwdStore } from './state.ts';
11
+ /** Options accepted by the cwd APIs; default to the shared process store. */
12
+ export interface SessionCwdOptions {
13
+ /** The store to read/write; defaults to the process-wide singleton. */
14
+ store?: SessionCwdStore;
15
+ /** Final fallback when neither the log nor the header records a cwd. */
16
+ fallback?: string;
17
+ }
18
+ /**
19
+ * Read the authoritative session working directory. Resolution order: the
20
+ * live store overlay, the durable `worktree/entered` fold, the session
21
+ * header cwd, then the caller's fallback (defaulting to the process cwd).
22
+ * @param agent - the live agent whose session cwd is being read.
23
+ * @param options - store and fallback overrides.
24
+ * @returns the absolute session cwd.
25
+ */
26
+ export declare function getSessionCwd(agent: Agent, options?: SessionCwdOptions): string;
27
+ /**
28
+ * Change the session's working directory: append a durable `worktree/entered`
29
+ * event (last-wins fold) and update the live overlay. The path must be
30
+ * absolute and is normalized before writing.
31
+ * @param agent - the live agent whose session cwd is changing.
32
+ * @param path - the new absolute working directory.
33
+ * @param options - store override.
34
+ */
35
+ export declare function setSessionCwd(agent: Agent, path: string, options?: SessionCwdOptions): void;
36
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAEnD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAElE,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAOnF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,IAAI,CAQ/F"}
package/lib/api.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Session cwd APIs. `getSessionCwd` resolves the authoritative session
3
+ * working directory (live overlay → durable `worktree/entered` fold →
4
+ * session header → fallback); `setSessionCwd` records the change durably by
5
+ * appending a `worktree/entered` event and updating the live overlay.
6
+ *
7
+ * @module @dsh-cc/session-cwd/api
8
+ */
9
+ import { isAbsolute, resolve } from 'node:path';
10
+ import { appendWorktreeEntered } from "./events.js";
11
+ import { sessionCwdStore } from "./state.js";
12
+ /**
13
+ * Read the authoritative session working directory. Resolution order: the
14
+ * live store overlay, the durable `worktree/entered` fold, the session
15
+ * header cwd, then the caller's fallback (defaulting to the process cwd).
16
+ * @param agent - the live agent whose session cwd is being read.
17
+ * @param options - store and fallback overrides.
18
+ * @returns the absolute session cwd.
19
+ */
20
+ export function getSessionCwd(agent, options = {}) {
21
+ const { store = sessionCwdStore, fallback } = options;
22
+ const sessionId = String(agent.session.id);
23
+ return store.resolve(sessionId, agent.session.events)
24
+ ?? agent.session.header.cwd
25
+ ?? fallback
26
+ ?? process.cwd();
27
+ }
28
+ /**
29
+ * Change the session's working directory: append a durable `worktree/entered`
30
+ * event (last-wins fold) and update the live overlay. The path must be
31
+ * absolute and is normalized before writing.
32
+ * @param agent - the live agent whose session cwd is changing.
33
+ * @param path - the new absolute working directory.
34
+ * @param options - store override.
35
+ */
36
+ export function setSessionCwd(agent, path, options = {}) {
37
+ if (!isAbsolute(path)) {
38
+ throw new TypeError(`session cwd must be an absolute path: "${path}"`);
39
+ }
40
+ const { store = sessionCwdStore } = options;
41
+ const normalized = resolve(path);
42
+ appendWorktreeEntered(agent.session, normalized);
43
+ store.set(String(agent.session.id), normalized);
44
+ }
45
+ //# sourceMappingURL=api.js.map
package/lib/api.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAE/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAAE,eAAe,EAAwB,MAAM,YAAY,CAAA;AAUlE;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,UAA6B,EAAE;IACzE,MAAM,EAAE,KAAK,GAAG,eAAe,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IACrD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;WAChD,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG;WACxB,QAAQ;WACR,OAAO,CAAC,GAAG,EAAE,CAAA;AACpB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,IAAY,EAAE,UAA6B,EAAE;IACvF,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,SAAS,CAAC,0CAA0C,IAAI,GAAG,CAAC,CAAA;IACxE,CAAC;IACD,MAAM,EAAE,KAAK,GAAG,eAAe,EAAE,GAAG,OAAO,CAAA;IAC3C,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC,qBAAqB,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;IAChD,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,CAAA;AACjD,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Durable session cwd events. This module registers the `worktree/entered`
3
+ * session event type into the upstream `KNOWN_SESSION_EVENT_TYPES` set at
4
+ * load (required for persistence-layer compatibility: persistence refuses
5
+ * unknown event types unless the type is registered there). The set is typed
6
+ * `ReadonlySet` but is a live `Set` — same cross-repo registration pattern as
7
+ * `permission/mode` in `@dsh-cc/permission-rules`.
8
+ *
9
+ * The event payload is a local wire face (`WorktreeEnteredWire`) rather than
10
+ * `SessionEventMap['worktree/entered']` so both the CI pin (type absent from
11
+ * the typed map) and a newer local harness typecheck: `Session.append` is
12
+ * compiler-validated against the upstream event map, so appends go through a
13
+ * widened function face.
14
+ *
15
+ * @module @dsh-cc/session-cwd/events
16
+ */
17
+ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
18
+ /** The session event type carrying a session cwd change. */
19
+ export declare const WORKTREE_ENTERED_EVENT = "worktree/entered";
20
+ /**
21
+ * The `worktree/entered` payload as written by this plugin: the new absolute
22
+ * session working directory. `ExitWorktree` restores the previous directory
23
+ * through the same event (last-wins fold), so one type covers both moves.
24
+ */
25
+ export interface WorktreeEnteredEventData {
26
+ path: string;
27
+ }
28
+ /**
29
+ * Fold the session's current working directory from its event log: the last
30
+ * `worktree/entered` path, or `undefined` when the session never recorded one
31
+ * (callers apply the process/session default).
32
+ * @param events - session events in log order (other event types are skipped).
33
+ * @returns the last recorded cwd, or `undefined` without one.
34
+ */
35
+ export declare function foldSessionCwd(events: readonly SessionEvent[]): string | undefined;
36
+ /**
37
+ * Append one durable `worktree/entered` event. `path` must be absolute; it is
38
+ * written verbatim (callers normalize before calling).
39
+ * @param session - the session the cwd change belongs to.
40
+ * @param path - the new absolute session working directory.
41
+ */
42
+ export declare function appendWorktreeEntered(session: Session, path: string): void;
43
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAErE,4DAA4D;AAC5D,eAAO,MAAM,sBAAsB,qBAAqB,CAEvD;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAA;CACb;AAaD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,GAAG,SAAS,CAQlF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAG1E"}
package/lib/events.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Durable session cwd events. This module registers the `worktree/entered`
3
+ * session event type into the upstream `KNOWN_SESSION_EVENT_TYPES` set at
4
+ * load (required for persistence-layer compatibility: persistence refuses
5
+ * unknown event types unless the type is registered there). The set is typed
6
+ * `ReadonlySet` but is a live `Set` — same cross-repo registration pattern as
7
+ * `permission/mode` in `@dsh-cc/permission-rules`.
8
+ *
9
+ * The event payload is a local wire face (`WorktreeEnteredWire`) rather than
10
+ * `SessionEventMap['worktree/entered']` so both the CI pin (type absent from
11
+ * the typed map) and a newer local harness typecheck: `Session.append` is
12
+ * compiler-validated against the upstream event map, so appends go through a
13
+ * widened function face.
14
+ *
15
+ * @module @dsh-cc/session-cwd/events
16
+ */
17
+ import { KNOWN_SESSION_EVENT_TYPES } from '@deepseek-ai/dsh-session';
18
+ /** The session event type carrying a session cwd change. */
19
+ export const WORKTREE_ENTERED_EVENT = 'worktree/entered';
20
+ KNOWN_SESSION_EVENT_TYPES.add(WORKTREE_ENTERED_EVENT);
21
+ /** Read a log event through the extended `worktree/entered` face. */
22
+ function asEnteredEvent(event) {
23
+ return event;
24
+ }
25
+ /**
26
+ * Fold the session's current working directory from its event log: the last
27
+ * `worktree/entered` path, or `undefined` when the session never recorded one
28
+ * (callers apply the process/session default).
29
+ * @param events - session events in log order (other event types are skipped).
30
+ * @returns the last recorded cwd, or `undefined` without one.
31
+ */
32
+ export function foldSessionCwd(events) {
33
+ for (let i = events.length - 1; i >= 0; i--) {
34
+ const event = asEnteredEvent(events[i]);
35
+ if (event.type === WORKTREE_ENTERED_EVENT && typeof event.data?.path === 'string') {
36
+ return event.data.path;
37
+ }
38
+ }
39
+ return undefined;
40
+ }
41
+ /**
42
+ * Append one durable `worktree/entered` event. `path` must be absolute; it is
43
+ * written verbatim (callers normalize before calling).
44
+ * @param session - the session the cwd change belongs to.
45
+ * @param path - the new absolute session working directory.
46
+ */
47
+ export function appendWorktreeEntered(session, path) {
48
+ const data = { path };
49
+ session.append(WORKTREE_ENTERED_EVENT, data);
50
+ }
51
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAA;AAGpE,4DAA4D;AAC5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,kBAAkB,CAEvD;AAAC,yBAAyC,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAA;AAiBvE,qEAAqE;AACrE,SAAS,cAAc,CAAC,KAAmB;IACzC,OAAO,KAAuC,CAAA;AAChD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,MAA+B;IAC5D,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAA;QACxC,IAAI,KAAK,CAAC,IAAI,KAAK,sBAAsB,IAAI,OAAO,KAAK,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAA;QACxB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB,EAAE,IAAY;IAClE,MAAM,IAAI,GAA6B,EAAE,IAAI,EAAE,CAC9C;IAAC,OAAO,CAAC,MAAkF,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAA;AAC5H,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Session-scoped working directory for DeepSeek Harness CC (WS1 + WS2).
3
+ * Registers the `worktree/entered` session event type at load (required for
4
+ * persistence compatibility), maintains foldable session cwd state, exposes
5
+ * `getSessionCwd` / `setSessionCwd`, and installs the `tools/pre-execute`
6
+ * workspace boundary guard (prepend, ahead of permission-rules) that routes
7
+ * out-of-workspace fs targets to an approval ask.
8
+ *
9
+ * @module @dsh-cc/session-cwd
10
+ */
11
+ import z from '@deepseek-ai/schemastery';
12
+ import './events.ts';
13
+ import type { Context } from '@deepseek-ai/cordis';
14
+ import type { Agent } from '@deepseek-ai/dsh-agent';
15
+ import { type SessionCwdStore } from './state.ts';
16
+ import { type SessionCwdOptions } from './api.ts';
17
+ export { WORKTREE_ENTERED_EVENT, foldSessionCwd, appendWorktreeEntered, type WorktreeEnteredEventData } from './events.ts';
18
+ export { SessionCwdStore, sessionCwdStore, foldSessionCwdState, reduceSessionCwdState, EMPTY_SESSION_CWD_STATE, type SessionCwdState } from './state.ts';
19
+ export { getSessionCwd, setSessionCwd, type SessionCwdOptions } from './api.ts';
20
+ export { registerSessionCwdBoundary, boundaryDecision, isFsTool, targetPathOf, isInsideWorkspace, readPermissionMode, DEFAULT_FS_TOOLS, type BoundaryListenerConfig, } from './listener.ts';
21
+ /** Plugin runtime configuration. All optional; the schema applies the defaults shown. */
22
+ export interface Config {
23
+ /** Whether the pre-execute workspace boundary guard is installed (default true). */
24
+ boundaryEnabled?: boolean;
25
+ /** Tool names treated as filesystem operations; defaults to the standard fs set. */
26
+ fsTools?: string[];
27
+ }
28
+ /** Runtime configuration schema. */
29
+ export declare const Config: z<Config>;
30
+ /** The plugin module face consumed by the Cordis loader. */
31
+ export declare const inject: readonly string[];
32
+ /**
33
+ * Plugin entry point: install the workspace boundary guard. The event-type
34
+ * registration happens at module load (the `./events.ts` side-effect import
35
+ * above), so even API-only consumers register the type.
36
+ * @param ctx - the Cordis context.
37
+ * @param config - runtime configuration.
38
+ */
39
+ export declare function apply(ctx: Context, config?: Config): void;
40
+ /** Convenience face for host code that does not import the pure APIs directly. */
41
+ export declare const sessionCwd: {
42
+ /** Read the authoritative session working directory. */
43
+ get(agent: Agent, options?: SessionCwdOptions): string;
44
+ /** Change the session working directory (durable `worktree/entered` event). */
45
+ set(agent: Agent, path: string, options?: SessionCwdOptions): void;
46
+ /** The process-wide cwd overlay. */
47
+ store: SessionCwdStore;
48
+ };
49
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAIxC,OAAO,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAKnD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAgC,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAE/E,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAC1H,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AACxJ,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC/E,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAA;AAEtB,yFAAyF;AACzF,MAAM,WAAW,MAAM;IACrB,oFAAoF;IACpF,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,oCAAoC;AACpC,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAG3B,CAAA;AAEF,4DAA4D;AAC5D,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EAAO,CAAA;AAE3C;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI,CAO7D;AAED,kFAAkF;AAClF,eAAO,MAAM,UAAU;IACrB,wDAAwD;eAC7C,KAAK,YAAY,iBAAiB,GAAG,MAAM;IAGtD,+EAA+E;eACpE,KAAK,QAAQ,MAAM,YAAY,iBAAiB,GAAG,IAAI;IAGlE,oCAAoC;WACV,eAAe;CAC1C,CAAA"}
package/lib/index.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Session-scoped working directory for DeepSeek Harness CC (WS1 + WS2).
3
+ * Registers the `worktree/entered` session event type at load (required for
4
+ * persistence compatibility), maintains foldable session cwd state, exposes
5
+ * `getSessionCwd` / `setSessionCwd`, and installs the `tools/pre-execute`
6
+ * workspace boundary guard (prepend, ahead of permission-rules) that routes
7
+ * out-of-workspace fs targets to an approval ask.
8
+ *
9
+ * @module @dsh-cc/session-cwd
10
+ */
11
+ import z from '@deepseek-ai/schemastery';
12
+ // Side-effect module import: adds `worktree/entered` to the persistence
13
+ // layer's known-event-type set at plugin load — required even when only the
14
+ // pure APIs are consumed.
15
+ import "./events.js";
16
+ import { registerSessionCwdBoundary, } from "./listener.js";
17
+ import { sessionCwdStore } from "./state.js";
18
+ import { getSessionCwd, setSessionCwd } from "./api.js";
19
+ export { WORKTREE_ENTERED_EVENT, foldSessionCwd, appendWorktreeEntered } from "./events.js";
20
+ export { SessionCwdStore, sessionCwdStore, foldSessionCwdState, reduceSessionCwdState, EMPTY_SESSION_CWD_STATE } from "./state.js";
21
+ export { getSessionCwd, setSessionCwd } from "./api.js";
22
+ export { registerSessionCwdBoundary, boundaryDecision, isFsTool, targetPathOf, isInsideWorkspace, readPermissionMode, DEFAULT_FS_TOOLS, } from "./listener.js";
23
+ /** Runtime configuration schema. */
24
+ export const Config = z.object({
25
+ boundaryEnabled: z.boolean().default(true),
26
+ fsTools: z.array(z.string()),
27
+ });
28
+ /** The plugin module face consumed by the Cordis loader. */
29
+ export const inject = [];
30
+ /**
31
+ * Plugin entry point: install the workspace boundary guard. The event-type
32
+ * registration happens at module load (the `./events.ts` side-effect import
33
+ * above), so even API-only consumers register the type.
34
+ * @param ctx - the Cordis context.
35
+ * @param config - runtime configuration.
36
+ */
37
+ export function apply(ctx, config = {}) {
38
+ if (config.boundaryEnabled ?? true) {
39
+ const listenerConfig = {
40
+ ...config.fsTools !== undefined ? { fsTools: config.fsTools } : {},
41
+ };
42
+ registerSessionCwdBoundary(ctx, listenerConfig);
43
+ }
44
+ }
45
+ /** Convenience face for host code that does not import the pure APIs directly. */
46
+ export const sessionCwd = {
47
+ /** Read the authoritative session working directory. */
48
+ get(agent, options) {
49
+ return getSessionCwd(agent, options);
50
+ },
51
+ /** Change the session working directory (durable `worktree/entered` event). */
52
+ set(agent, path, options) {
53
+ setSessionCwd(agent, path, options);
54
+ },
55
+ /** The process-wide cwd overlay. */
56
+ store: sessionCwdStore,
57
+ };
58
+ // No default export: cordis-plugin-loader unwrapExports prefers `.default`, so
59
+ // a convenience object without `apply` fails preset mount as "invalid plugin".
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,wEAAwE;AACxE,4EAA4E;AAC5E,0BAA0B;AAC1B,OAAO,aAAa,CAAA;AAGpB,OAAO,EACL,0BAA0B,GAE3B,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,eAAe,EAAwB,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,aAAa,EAAE,aAAa,EAA0B,MAAM,UAAU,CAAA;AAE/E,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAiC,MAAM,aAAa,CAAA;AAC1H,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,uBAAuB,EAAwB,MAAM,YAAY,CAAA;AACxJ,OAAO,EAAE,aAAa,EAAE,aAAa,EAA0B,MAAM,UAAU,CAAA;AAC/E,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,GAEjB,MAAM,eAAe,CAAA;AAUtB,oCAAoC;AACpC,MAAM,CAAC,MAAM,MAAM,GAAc,CAAC,CAAC,MAAM,CAAC;IACxC,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC1C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC7B,CAAC,CAAA;AAEF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,MAAM,GAAsB,EAAE,CAAA;AAE3C;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,SAAiB,EAAE;IACrD,IAAI,MAAM,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC;QACnC,MAAM,cAAc,GAA2B;YAC7C,GAAG,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE;SACnE,CAAA;QACD,0BAA0B,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;IACjD,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,wDAAwD;IACxD,GAAG,CAAC,KAAY,EAAE,OAA2B;QAC3C,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACtC,CAAC;IACD,+EAA+E;IAC/E,GAAG,CAAC,KAAY,EAAE,IAAY,EAAE,OAA2B;QACzD,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IACrC,CAAC;IACD,oCAAoC;IACpC,KAAK,EAAE,eAAkC;CAC1C,CAAA;AAED,+EAA+E;AAC/E,+EAA+E"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Filesystem boundary enforcement (WS2). A `tools/pre-execute` listener
3
+ * registered with `{ prepend: true }` — ahead of the permission-rules
4
+ * waterfall — that inspects every `fs`-family call, resolves its target path
5
+ * against the session cwd (WS1), and routes out-of-workspace targets to an
6
+ * approval ask unless the session is in `bypassPermissions` (allowed with the
7
+ * existing audit trail). The guard is a pre-execute convenience, not a hard
8
+ * security boundary: it does not intercept system calls.
9
+ *
10
+ * @module @dsh-cc/session-cwd/listener
11
+ */
12
+ import type { Context } from '@deepseek-ai/cordis';
13
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
14
+ import type { PreToolDecision, ToolExecution } from '@dsh-cc/tools';
15
+ import { type SessionCwdStore } from './state.ts';
16
+ /** Listener configuration. */
17
+ export interface BoundaryListenerConfig {
18
+ /** Tool names treated as filesystem operations; defaults to {@link DEFAULT_FS_TOOLS}. */
19
+ fsTools?: readonly string[];
20
+ /** The store the listener resolves the session cwd from; defaults to the shared one. */
21
+ store?: SessionCwdStore;
22
+ }
23
+ /** The standard fs/edit tool set, matched case-insensitively. */
24
+ export declare const DEFAULT_FS_TOOLS: readonly string[];
25
+ /** Whether one tool name is an fs operation (case-insensitive match). */
26
+ export declare function isFsTool(name: string, fsTools?: readonly string[]): boolean;
27
+ /**
28
+ * Extract the target path of one call from its arguments. The first present
29
+ * string-valued target key wins; tools without a path argument yield
30
+ * `undefined` (the listener then passes them through untouched).
31
+ * @param exec - the pending tool execution.
32
+ * @returns the raw target path, or `undefined` when the call has none.
33
+ */
34
+ export declare function targetPathOf(exec: ToolExecution): string | undefined;
35
+ /**
36
+ * Whether a target path lies inside the workspace root (boundary is
37
+ * workspace-scoped, not path-scoped). Both sides are resolved, so `..`
38
+ * segments and relative targets cannot slip past.
39
+ * @param target - the raw target path.
40
+ * @param root - the workspace root (the session cwd).
41
+ * @returns true when the target is the root or inside it.
42
+ */
43
+ export declare function isInsideWorkspace(target: string, root: string): boolean;
44
+ /**
45
+ * Read the session's current permission mode from its event log: the last
46
+ * `permission/mode` value, or `undefined` without one. Local wire-face fold —
47
+ * the mode is an upstream-extensible session event, not owned here.
48
+ * @param events - session events in log order.
49
+ * @returns the recorded mode, or `undefined`.
50
+ */
51
+ export declare function readPermissionMode(events: readonly SessionEvent[]): string | undefined;
52
+ /**
53
+ * The boundary decision for one call, factored out of the listener for direct
54
+ * testing. `undefined` means "not my decision" (the caller must call `next`).
55
+ * @param exec - the pending tool execution.
56
+ * @param config - listener configuration.
57
+ * @returns an ask decision for out-of-workspace targets, `allow` for
58
+ * bypassPermissions, or `undefined` when the guard has no opinion.
59
+ */
60
+ export declare function boundaryDecision(exec: ToolExecution, config?: BoundaryListenerConfig): PreToolDecision | undefined;
61
+ /**
62
+ * Register the boundary guard on `tools/pre-execute` with `{ prepend: true }`
63
+ * so it runs before the permission-rules waterfall and every later listener.
64
+ * Non-fs tools, pathless calls, agents without a resolvable cwd, and
65
+ * in-workspace targets pass through to `next` untouched.
66
+ * @param ctx - the Cordis context.
67
+ * @param config - listener configuration.
68
+ * @returns a disposer that unregisters the listener.
69
+ */
70
+ export declare function registerSessionCwdBoundary(ctx: Context, config?: BoundaryListenerConfig): () => void;
71
+ //# sourceMappingURL=listener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AACnE,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAElE,8BAA8B;AAC9B,MAAM,WAAW,sBAAsB;IACrC,yFAAyF;IACzF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,wFAAwF;IACxF,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB;AAED,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAS7C,CAAA;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,SAAS,MAAM,EAAqB,GAAG,OAAO,CAG7F;AAKD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAQpE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOvE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,GAAG,SAAS,CAQtF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,aAAa,EACnB,MAAM,GAAE,sBAA2B,GAClC,eAAe,GAAG,SAAS,CAe7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,sBAA2B,GAAG,MAAM,IAAI,CAMxG"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Filesystem boundary enforcement (WS2). A `tools/pre-execute` listener
3
+ * registered with `{ prepend: true }` — ahead of the permission-rules
4
+ * waterfall — that inspects every `fs`-family call, resolves its target path
5
+ * against the session cwd (WS1), and routes out-of-workspace targets to an
6
+ * approval ask unless the session is in `bypassPermissions` (allowed with the
7
+ * existing audit trail). The guard is a pre-execute convenience, not a hard
8
+ * security boundary: it does not intercept system calls.
9
+ *
10
+ * @module @dsh-cc/session-cwd/listener
11
+ */
12
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
13
+ import { sessionCwdStore } from "./state.js";
14
+ /** The standard fs/edit tool set, matched case-insensitively. */
15
+ export const DEFAULT_FS_TOOLS = [
16
+ 'edit',
17
+ 'write',
18
+ 'read',
19
+ 'multi_edit',
20
+ 'notebook_edit',
21
+ 'str_replace_editor',
22
+ 'glob',
23
+ 'grep',
24
+ ];
25
+ /** Whether one tool name is an fs operation (case-insensitive match). */
26
+ export function isFsTool(name, fsTools = DEFAULT_FS_TOOLS) {
27
+ const normalized = name.toLowerCase();
28
+ return fsTools.some(tool => tool.toLowerCase() === normalized);
29
+ }
30
+ /** Argument keys inspected, in order, for a call's target path. */
31
+ const TARGET_KEYS = ['file_path', 'notebook_path', 'path', 'workdir', 'cwd'];
32
+ /**
33
+ * Extract the target path of one call from its arguments. The first present
34
+ * string-valued target key wins; tools without a path argument yield
35
+ * `undefined` (the listener then passes them through untouched).
36
+ * @param exec - the pending tool execution.
37
+ * @returns the raw target path, or `undefined` when the call has none.
38
+ */
39
+ export function targetPathOf(exec) {
40
+ const args = exec.arguments;
41
+ if (args === undefined || typeof args !== 'object')
42
+ return undefined;
43
+ for (const key of TARGET_KEYS) {
44
+ const value = args[key];
45
+ if (typeof value === 'string' && value.length > 0)
46
+ return value;
47
+ }
48
+ return undefined;
49
+ }
50
+ /**
51
+ * Whether a target path lies inside the workspace root (boundary is
52
+ * workspace-scoped, not path-scoped). Both sides are resolved, so `..`
53
+ * segments and relative targets cannot slip past.
54
+ * @param target - the raw target path.
55
+ * @param root - the workspace root (the session cwd).
56
+ * @returns true when the target is the root or inside it.
57
+ */
58
+ export function isInsideWorkspace(target, root) {
59
+ // Relative targets resolve against the workspace root, not the process cwd.
60
+ const targetPath = isAbsolute(target) ? resolve(target) : resolve(root, target);
61
+ const rootPath = resolve(root);
62
+ if (targetPath === rootPath)
63
+ return true;
64
+ const rel = relative(rootPath, targetPath);
65
+ return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
66
+ }
67
+ /**
68
+ * Read the session's current permission mode from its event log: the last
69
+ * `permission/mode` value, or `undefined` without one. Local wire-face fold —
70
+ * the mode is an upstream-extensible session event, not owned here.
71
+ * @param events - session events in log order.
72
+ * @returns the recorded mode, or `undefined`.
73
+ */
74
+ export function readPermissionMode(events) {
75
+ for (let i = events.length - 1; i >= 0; i--) {
76
+ const event = events[i];
77
+ if (event.type === 'permission/mode' && typeof event.data?.mode === 'string') {
78
+ return event.data.mode;
79
+ }
80
+ }
81
+ return undefined;
82
+ }
83
+ /**
84
+ * The boundary decision for one call, factored out of the listener for direct
85
+ * testing. `undefined` means "not my decision" (the caller must call `next`).
86
+ * @param exec - the pending tool execution.
87
+ * @param config - listener configuration.
88
+ * @returns an ask decision for out-of-workspace targets, `allow` for
89
+ * bypassPermissions, or `undefined` when the guard has no opinion.
90
+ */
91
+ export function boundaryDecision(exec, config = {}) {
92
+ if (!isFsTool(exec.name, config.fsTools ?? DEFAULT_FS_TOOLS))
93
+ return undefined;
94
+ const target = targetPathOf(exec);
95
+ if (target === undefined)
96
+ return undefined;
97
+ const agent = exec.agent;
98
+ if (agent === undefined)
99
+ return undefined;
100
+ const store = config.store ?? sessionCwdStore;
101
+ const root = store.resolve(String(agent.session.id), agent.session.events) ?? agent.session.header.cwd;
102
+ if (root === undefined)
103
+ return undefined;
104
+ if (isInsideWorkspace(target, root))
105
+ return undefined;
106
+ if (readPermissionMode(agent.session.events) === 'bypassPermissions')
107
+ return { kind: 'allow' };
108
+ return {
109
+ kind: 'ask',
110
+ reason: `Operation targets path outside session workspace: ${resolve(target)} (workspace: ${resolve(root)})`,
111
+ };
112
+ }
113
+ /**
114
+ * Register the boundary guard on `tools/pre-execute` with `{ prepend: true }`
115
+ * so it runs before the permission-rules waterfall and every later listener.
116
+ * Non-fs tools, pathless calls, agents without a resolvable cwd, and
117
+ * in-workspace targets pass through to `next` untouched.
118
+ * @param ctx - the Cordis context.
119
+ * @param config - listener configuration.
120
+ * @returns a disposer that unregisters the listener.
121
+ */
122
+ export function registerSessionCwdBoundary(ctx, config = {}) {
123
+ const dispose = ctx.on('tools/pre-execute', async (exec, next) => {
124
+ const decision = boundaryDecision(exec, config);
125
+ return decision ?? next();
126
+ }, { prepend: true });
127
+ return dispose;
128
+ }
129
+ //# sourceMappingURL=listener.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.js","sourceRoot":"","sources":["../src/listener.ts"],"names":[],"mappings":"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"}
package/lib/state.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Foldable session cwd state. The durable state is the session event log
3
+ * itself (`worktree/entered`, last-wins); this module folds it into a typed
4
+ * state value and adds a process-local live overlay for the current session
5
+ * (the same pattern as tool-git-worktree's active worktree session), which
6
+ * lets `getSessionCwd` read back before the event round-trips and keeps
7
+ * sessions independent inside one process.
8
+ *
9
+ * @module @dsh-cc/session-cwd/state
10
+ */
11
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
12
+ /** The folded cwd state of one session: the current cwd, when known. */
13
+ export interface SessionCwdState {
14
+ readonly cwd?: string;
15
+ }
16
+ /** The empty state: no cwd recorded in this fold. */
17
+ export declare const EMPTY_SESSION_CWD_STATE: SessionCwdState;
18
+ /**
19
+ * Reduce one event into the state. Only `worktree/entered` events change the
20
+ * state; everything else passes through unchanged (a fold, not a filter).
21
+ * @param state - the state so far.
22
+ * @param event - the next session event in log order.
23
+ * @returns the next state.
24
+ */
25
+ export declare function reduceSessionCwdState(state: SessionCwdState, event: SessionEvent): SessionCwdState;
26
+ /**
27
+ * Fold the cwd state over a session event log, in log order.
28
+ * @param events - session events in log order.
29
+ * @returns the folded state (empty when no event applied).
30
+ */
31
+ export declare function foldSessionCwdState(events: readonly SessionEvent[]): SessionCwdState;
32
+ /**
33
+ * Process-local cwd overlay keyed by session id. Writes are applied on
34
+ * `setSessionCwd`; reads fall through to the durable event-log fold so a
35
+ * restarted process still resolves the cwd from the persisted session.
36
+ */
37
+ export declare class SessionCwdStore {
38
+ private readonly live;
39
+ /**
40
+ * Record the live cwd for one session.
41
+ * @param sessionId - the session key.
42
+ * @param cwd - the new absolute cwd.
43
+ */
44
+ set(sessionId: string, cwd: string): void;
45
+ /** The live cwd for one session, or `undefined` without a local write. */
46
+ get(sessionId: string): string | undefined;
47
+ /**
48
+ * Resolve the authoritative cwd for one session: the live overlay first,
49
+ * then the durable event-log fold.
50
+ * @param sessionId - the session key.
51
+ * @param events - the session's event log, in log order.
52
+ * @returns the resolved cwd, or `undefined` when nothing is recorded.
53
+ */
54
+ resolve(sessionId: string, events: readonly SessionEvent[]): string | undefined;
55
+ /** Drop the live overlay for one session (used on session dispose). */
56
+ clear(sessionId: string): void;
57
+ }
58
+ /** The process-wide cwd overlay shared by the plugin, APIs, and listener. */
59
+ export declare const sessionCwdStore: SessionCwdStore;
60
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAG5D,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,qDAAqD;AACrD,eAAO,MAAM,uBAAuB,EAAE,eAAoB,CAAA;AAE1D;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,GAAG,eAAe,CAIlG;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,eAAe,CAIpF;AAED;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IAEjD;;;;OAIG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzC,0EAA0E;IAC1E,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI1C;;;;;;OAMG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,GAAG,SAAS;IAI/E,uEAAuE;IACvE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAG/B;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,iBAAwB,CAAA"}
package/lib/state.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Foldable session cwd state. The durable state is the session event log
3
+ * itself (`worktree/entered`, last-wins); this module folds it into a typed
4
+ * state value and adds a process-local live overlay for the current session
5
+ * (the same pattern as tool-git-worktree's active worktree session), which
6
+ * lets `getSessionCwd` read back before the event round-trips and keeps
7
+ * sessions independent inside one process.
8
+ *
9
+ * @module @dsh-cc/session-cwd/state
10
+ */
11
+ import { foldSessionCwd } from "./events.js";
12
+ /** The empty state: no cwd recorded in this fold. */
13
+ export const EMPTY_SESSION_CWD_STATE = {};
14
+ /**
15
+ * Reduce one event into the state. Only `worktree/entered` events change the
16
+ * state; everything else passes through unchanged (a fold, not a filter).
17
+ * @param state - the state so far.
18
+ * @param event - the next session event in log order.
19
+ * @returns the next state.
20
+ */
21
+ export function reduceSessionCwdState(state, event) {
22
+ const wire = event;
23
+ if (wire.type !== 'worktree/entered' || typeof wire.data?.path !== 'string')
24
+ return state;
25
+ return { cwd: wire.data.path };
26
+ }
27
+ /**
28
+ * Fold the cwd state over a session event log, in log order.
29
+ * @param events - session events in log order.
30
+ * @returns the folded state (empty when no event applied).
31
+ */
32
+ export function foldSessionCwdState(events) {
33
+ let state = EMPTY_SESSION_CWD_STATE;
34
+ for (const event of events)
35
+ state = reduceSessionCwdState(state, event);
36
+ return state;
37
+ }
38
+ /**
39
+ * Process-local cwd overlay keyed by session id. Writes are applied on
40
+ * `setSessionCwd`; reads fall through to the durable event-log fold so a
41
+ * restarted process still resolves the cwd from the persisted session.
42
+ */
43
+ export class SessionCwdStore {
44
+ live = new Map();
45
+ /**
46
+ * Record the live cwd for one session.
47
+ * @param sessionId - the session key.
48
+ * @param cwd - the new absolute cwd.
49
+ */
50
+ set(sessionId, cwd) {
51
+ this.live.set(sessionId, cwd);
52
+ }
53
+ /** The live cwd for one session, or `undefined` without a local write. */
54
+ get(sessionId) {
55
+ return this.live.get(sessionId);
56
+ }
57
+ /**
58
+ * Resolve the authoritative cwd for one session: the live overlay first,
59
+ * then the durable event-log fold.
60
+ * @param sessionId - the session key.
61
+ * @param events - the session's event log, in log order.
62
+ * @returns the resolved cwd, or `undefined` when nothing is recorded.
63
+ */
64
+ resolve(sessionId, events) {
65
+ return this.live.get(sessionId) ?? foldSessionCwd(events);
66
+ }
67
+ /** Drop the live overlay for one session (used on session dispose). */
68
+ clear(sessionId) {
69
+ this.live.delete(sessionId);
70
+ }
71
+ }
72
+ /** The process-wide cwd overlay shared by the plugin, APIs, and listener. */
73
+ export const sessionCwdStore = new SessionCwdStore();
74
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAO5C,qDAAqD;AACrD,MAAM,CAAC,MAAM,uBAAuB,GAAoB,EAAE,CAAA;AAE1D;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAsB,EAAE,KAAmB;IAC/E,MAAM,IAAI,GAAG,KAA+D,CAAA;IAC5E,IAAI,IAAI,CAAC,IAAI,KAAK,kBAAkB,IAAI,OAAO,IAAI,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACzF,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA+B;IACjE,IAAI,KAAK,GAAG,uBAAuB,CAAA;IACnC,KAAK,MAAM,KAAK,IAAI,MAAM;QAAE,KAAK,GAAG,qBAAqB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IACvE,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,eAAe;IACT,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAA;IAEjD;;;;OAIG;IACH,GAAG,CAAC,SAAiB,EAAE,GAAW;QAChC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IAC/B,CAAC;IAED,0EAA0E;IAC1E,GAAG,CAAC,SAAiB;QACnB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACjC,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,SAAiB,EAAE,MAA+B;QACxD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,CAAA;IAC3D,CAAC;IAED,uEAAuE;IACvE,KAAK,CAAC,SAAiB;QACrB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC7B,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAA"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@dsh-cc/session-cwd",
3
+ "description": "Session-scoped working directory state for DeepSeek Harness CC: durable worktree/entered session events, foldable cwd state, getSessionCwd/setSessionCwd APIs, and a pre-execute workspace boundary guard",
4
+ "version": "0.5.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/dsh-cc/dsh-cc.git",
8
+ "directory": "packages/workspace/session-cwd"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./src/*": "./src/*",
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "lib"
21
+ ],
22
+ "license": "Apache-2.0",
23
+ "peerDependencies": {
24
+ "@deepseek-ai/cordis": ">=0.1.1-rc.2",
25
+ "@deepseek-ai/dsh-agent": ">=0.1.1-rc.2",
26
+ "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2",
27
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.2",
28
+ "@deepseek-ai/schemastery": "^3.18.1",
29
+ "@dsh-cc/tools": "^0.5.0"
30
+ },
31
+ "devDependencies": {
32
+ "@deepseek-ai/cordis": "link:../../../../deepseek-harness/vendor/cordis",
33
+ "@deepseek-ai/dsh-agent": "link:../../../../deepseek-harness/packages/core/agent",
34
+ "@deepseek-ai/dsh-llm": "link:../../../../deepseek-harness/packages/llm/llm",
35
+ "@deepseek-ai/dsh-session": "link:../../../../deepseek-harness/packages/core/session",
36
+ "@deepseek-ai/schemastery": "link:../../../../deepseek-harness/vendor/schemastery",
37
+ "@dsh-cc/tools": "link:../../core/tools"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }