@exaudeus/workrail 1.5.3 → 1.6.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.
@@ -686,16 +686,16 @@
686
686
  "bytes": 2789
687
687
  },
688
688
  "mcp/handlers/v2-execution/start.js": {
689
- "sha256": "6d6763c4cae68d573d228826af6826e830ce70d016772c74d4780207807b1928",
690
- "bytes": 15078
689
+ "sha256": "5eab7a7a5fdf021f7e4bc656de1dbfe791e6610ab9e1bacb15dad4f6423f1d58",
690
+ "bytes": 15248
691
691
  },
692
692
  "mcp/handlers/v2-resume.d.ts": {
693
693
  "sha256": "d88f6c35bcaf946666c837b72fda3702a2ebab5e478eb90f7b4b672a0e5fa24f",
694
694
  "bytes": 471
695
695
  },
696
696
  "mcp/handlers/v2-resume.js": {
697
- "sha256": "b4ce6f9cc3dffef91ca7da2e428d659c4ff79ceaf984c87d672290215936db97",
698
- "bytes": 2771
697
+ "sha256": "a8f51a680eec031e23148bd64f6d647d765c79eec738cbd6182650533248f16f",
698
+ "bytes": 2929
699
699
  },
700
700
  "mcp/handlers/v2-state-conversion.d.ts": {
701
701
  "sha256": "94bd06904ef58dd210ff17ffb75c2492beea8937eb06d99749e5d860c0e0d96b",
@@ -750,8 +750,8 @@
750
750
  "bytes": 168
751
751
  },
752
752
  "mcp/server.js": {
753
- "sha256": "37ab570be76b379a4076e78e5bc82a06b7ee1fc73d3d5010ad48b5650e54365a",
754
- "bytes": 10631
753
+ "sha256": "fd9e264c759db2d5446d88eafce14ec27ce9b73756e62ef10351b2fca52c7c7c",
754
+ "bytes": 11892
755
755
  },
756
756
  "mcp/tool-description-provider.d.ts": {
757
757
  "sha256": "1d46abc3112e11b68e57197e846f5708293ec9b2281fa71a9124ee2aad71e41b",
@@ -786,8 +786,8 @@
786
786
  "bytes": 8360
787
787
  },
788
788
  "mcp/types.d.ts": {
789
- "sha256": "ce700982b88189f08c466384b4357b3345ec0ccb6842ff97a0831c2281185c6c",
790
- "bytes": 4284
789
+ "sha256": "2ef7f9c1bffbe365b06469dfad43b45c4ae3b5292dd269b33423bf1c8c9c7c43",
790
+ "bytes": 4355
791
791
  },
792
792
  "mcp/types.js": {
793
793
  "sha256": "d10c4070e4c3454d80f0fa9cdc0e978c69c53c13fd09baa8710fcd802fed8926",
@@ -905,6 +905,14 @@
905
905
  "sha256": "08870a96c6f32c09e617f43f6eb83593c15007fe39241d498bd9bbbd420101fc",
906
906
  "bytes": 635
907
907
  },
908
+ "mcp/workspace-roots-manager.d.ts": {
909
+ "sha256": "32f342eb4cf2c1a94982f8077dea4185b74f118c1587b40a370200cf2aa06f9c",
910
+ "bytes": 363
911
+ },
912
+ "mcp/workspace-roots-manager.js": {
913
+ "sha256": "1037fa12885161c91af1beed0dc0a0cb05a5636b4a63d1756e1ed3cab76e1d32",
914
+ "bytes": 419
915
+ },
908
916
  "mcp/zod-to-json-schema.d.ts": {
909
917
  "sha256": "c80cfa6980a88c823ac47d04706b2223a351de3f4c76ee778f81ad3dda2c766b",
910
918
  "bytes": 456
@@ -1842,12 +1850,12 @@
1842
1850
  "bytes": 304
1843
1851
  },
1844
1852
  "v2/infra/local/workspace-anchor/index.d.ts": {
1845
- "sha256": "b56193c3da9edb1012d2bbdd8b8dd33e7d62e23a08ecb82c328705d5bca14765",
1846
- "bytes": 431
1853
+ "sha256": "90515ae92ac0230a279a28b82e26bc8be8f62c4cd6f6999630dc07f9a64e3296",
1854
+ "bytes": 705
1847
1855
  },
1848
1856
  "v2/infra/local/workspace-anchor/index.js": {
1849
- "sha256": "1d49c3a0115603a7ee995a47f3b80bbd0fc80ca285ce9768e00036dc700b05a9",
1850
- "bytes": 1477
1857
+ "sha256": "69541a7f2a6837ff0790c99740000a71521a60671b4fd7c4a2245a48e56bb9b7",
1858
+ "bytes": 2102
1851
1859
  },
1852
1860
  "v2/ports/base32.port.d.ts": {
1853
1861
  "sha256": "64aa2f2003a552917cbf71474472fc5e4afffaa29577204bbcbe5ffa989ceb82",
@@ -1986,8 +1994,8 @@
1986
1994
  "bytes": 77
1987
1995
  },
1988
1996
  "v2/ports/workspace-anchor.port.d.ts": {
1989
- "sha256": "3c93de8b4e971c70c16adc0de85c8e849bc57c48a18525111648da45d044384c",
1990
- "bytes": 522
1997
+ "sha256": "228cce9bff3cbaa430bf38be3a1bc326885ec07c7952af2824735bc7adbf9af3",
1998
+ "bytes": 759
1991
1999
  },
1992
2000
  "v2/ports/workspace-anchor.port.js": {
1993
2001
  "sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
@@ -229,9 +229,12 @@ function executeStartWorkflow(input, ctx) {
229
229
  pinnedStore,
230
230
  })
231
231
  .andThen(({ workflow, firstStep, workflowHash, pinnedWorkflow }) => {
232
- const workspaceAnchor = ctx.v2?.workspaceAnchor;
233
- const anchorsRA = workspaceAnchor
234
- ? workspaceAnchor.resolveAnchors()
232
+ const workspaceResolver = ctx.v2.workspaceResolver;
233
+ const primaryRootUri = ctx.v2.resolvedRootUris?.[0];
234
+ const anchorsRA = workspaceResolver
235
+ ? (primaryRootUri
236
+ ? workspaceResolver.resolveFromUri(primaryRootUri)
237
+ : workspaceResolver.resolveFromCwd())
235
238
  .map((anchors) => (0, observation_builder_js_1.anchorsToObservations)(anchors))
236
239
  .orElse(() => (0, neverthrow_1.okAsync)([]))
237
240
  : (0, neverthrow_1.okAsync)([]);
@@ -19,8 +19,11 @@ async function handleV2ResumeSession(input, ctx) {
19
19
  return (0, types_js_1.errNotRetryable)('INTERNAL_ERROR', 'resume_session requires sessionSummaryProvider port');
20
20
  }
21
21
  let anchors = [];
22
- if (v2.workspaceAnchor) {
23
- const anchorRes = await v2.workspaceAnchor.resolveAnchors();
22
+ if (v2.workspaceResolver) {
23
+ const primaryRootUri = v2.resolvedRootUris?.[0];
24
+ const anchorRes = await (primaryRootUri
25
+ ? v2.workspaceResolver.resolveFromUri(primaryRootUri)
26
+ : v2.workspaceResolver.resolveFromCwd());
24
27
  if (anchorRes.isOk()) {
25
28
  anchors = anchorRes.value;
26
29
  }
@@ -41,6 +41,7 @@ const tokens_js_1 = require("../di/tokens.js");
41
41
  const assert_never_js_1 = require("../runtime/assert-never.js");
42
42
  const token_codec_ports_js_1 = require("../v2/durable-core/tokens/token-codec-ports.js");
43
43
  const index_js_1 = require("../v2/infra/local/workspace-anchor/index.js");
44
+ const workspace_roots_manager_js_1 = require("./workspace-roots-manager.js");
44
45
  const index_js_2 = require("../v2/infra/local/directory-listing/index.js");
45
46
  const index_js_3 = require("../v2/infra/local/session-summary-provider/index.js");
46
47
  const tool_factory_js_1 = require("./tool-factory.js");
@@ -101,7 +102,8 @@ async function createToolContext() {
101
102
  crypto,
102
103
  idFactory,
103
104
  tokenCodecPorts,
104
- workspaceAnchor: new index_js_1.LocalWorkspaceAnchorV2(process.cwd()),
105
+ resolvedRootUris: [],
106
+ workspaceResolver: new index_js_1.LocalWorkspaceAnchorV2(process.cwd()),
105
107
  dataDir,
106
108
  directoryListing,
107
109
  sessionSummaryProvider: new index_js_3.LocalSessionSummaryProviderV2({
@@ -148,9 +150,10 @@ async function startServer() {
148
150
  default:
149
151
  (0, assert_never_js_1.assertNever)(workflowEdition);
150
152
  }
153
+ const rootsManager = new workspace_roots_manager_js_1.WorkspaceRootsManager();
151
154
  const { Server } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/index.js')));
152
155
  const { StdioServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/stdio.js')));
153
- const { CallToolRequestSchema, ListToolsRequestSchema, } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/types.js')));
156
+ const { CallToolRequestSchema, ListToolsRequestSchema, RootsListChangedNotificationSchema, } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/types.js')));
154
157
  const server = new Server({
155
158
  name: 'workrail-server',
156
159
  version: '0.1.0',
@@ -182,10 +185,31 @@ async function startServer() {
182
185
  isError: true,
183
186
  };
184
187
  }
185
- return handler(args ?? {}, ctx);
188
+ const requestCtx = ctx.v2
189
+ ? { ...ctx, v2: { ...ctx.v2, resolvedRootUris: rootsManager.getCurrentRootUris() } }
190
+ : ctx;
191
+ return handler(args ?? {}, requestCtx);
192
+ });
193
+ server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
194
+ try {
195
+ const result = await server.listRoots();
196
+ rootsManager.updateRootUris(result.roots.map((r) => r.uri));
197
+ console.error(`[Roots] Updated workspace roots: ${result.roots.map((r) => r.uri).join(', ') || '(none)'}`);
198
+ }
199
+ catch {
200
+ console.error('[Roots] Failed to fetch updated roots after change notification');
201
+ }
186
202
  });
187
203
  const transport = new StdioServerTransport();
188
204
  await server.connect(transport);
205
+ try {
206
+ const result = await server.listRoots();
207
+ rootsManager.updateRootUris(result.roots.map((r) => r.uri));
208
+ console.error(`[Roots] Initial workspace roots: ${result.roots.map((r) => r.uri).join(', ') || '(none)'}`);
209
+ }
210
+ catch {
211
+ console.error('[Roots] Client does not support roots/list; workspace context will use server CWD fallback');
212
+ }
189
213
  console.error('WorkRail MCP Server running on stdio');
190
214
  const shutdownEvents = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ShutdownEvents);
191
215
  const processSignals = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ProcessSignals);
@@ -12,7 +12,7 @@ import type { CryptoPortV2 } from '../v2/durable-core/canonical/hashing.js';
12
12
  import type { IdFactoryV2 } from '../v2/infra/local/id-factory/index.js';
13
13
  import type { JsonValue } from './output-schemas.js';
14
14
  import type { TokenCodecPorts } from '../v2/durable-core/tokens/token-codec-ports.js';
15
- import type { WorkspaceAnchorPortV2 } from '../v2/ports/workspace-anchor.port.js';
15
+ import type { WorkspaceContextResolverPortV2 } from '../v2/ports/workspace-anchor.port.js';
16
16
  import type { DataDirPortV2 } from '../v2/ports/data-dir.port.js';
17
17
  import type { DirectoryListingPortV2 } from '../v2/ports/directory-listing.port.js';
18
18
  import type { SessionSummaryProviderPortV2 } from '../v2/ports/session-summary-provider.port.js';
@@ -55,7 +55,8 @@ export interface V2Dependencies {
55
55
  readonly crypto: CryptoPortV2;
56
56
  readonly idFactory: IdFactoryV2;
57
57
  readonly tokenCodecPorts: TokenCodecPorts;
58
- readonly workspaceAnchor?: WorkspaceAnchorPortV2;
58
+ readonly resolvedRootUris?: readonly string[];
59
+ readonly workspaceResolver?: WorkspaceContextResolverPortV2;
59
60
  readonly dataDir?: DataDirPortV2;
60
61
  readonly directoryListing?: DirectoryListingPortV2;
61
62
  readonly sessionSummaryProvider?: SessionSummaryProviderPortV2;
@@ -0,0 +1,11 @@
1
+ export interface RootsReader {
2
+ getCurrentRootUris(): readonly string[];
3
+ }
4
+ export interface RootsWriter {
5
+ updateRootUris(uris: readonly string[]): void;
6
+ }
7
+ export declare class WorkspaceRootsManager implements RootsReader, RootsWriter {
8
+ private rootUris;
9
+ updateRootUris(uris: readonly string[]): void;
10
+ getCurrentRootUris(): readonly string[];
11
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkspaceRootsManager = void 0;
4
+ class WorkspaceRootsManager {
5
+ constructor() {
6
+ this.rootUris = Object.freeze([]);
7
+ }
8
+ updateRootUris(uris) {
9
+ this.rootUris = Object.freeze([...uris]);
10
+ }
11
+ getCurrentRootUris() {
12
+ return this.rootUris;
13
+ }
14
+ }
15
+ exports.WorkspaceRootsManager = WorkspaceRootsManager;
@@ -1,9 +1,12 @@
1
1
  import { ResultAsync as RA } from 'neverthrow';
2
- import type { WorkspaceAnchorPortV2, WorkspaceAnchor, WorkspaceAnchorError } from '../../../ports/workspace-anchor.port.js';
3
- export declare class LocalWorkspaceAnchorV2 implements WorkspaceAnchorPortV2 {
4
- private readonly cwd;
5
- constructor(cwd: string);
2
+ import type { WorkspaceAnchorPortV2, WorkspaceContextResolverPortV2, WorkspaceAnchor, WorkspaceAnchorError } from '../../../ports/workspace-anchor.port.js';
3
+ export declare class LocalWorkspaceAnchorV2 implements WorkspaceAnchorPortV2, WorkspaceContextResolverPortV2 {
4
+ private readonly defaultCwd;
5
+ constructor(defaultCwd: string);
6
6
  resolveAnchors(): RA<readonly WorkspaceAnchor[], WorkspaceAnchorError>;
7
+ resolveFromUri(rootUri: string): RA<readonly WorkspaceAnchor[], WorkspaceAnchorError>;
8
+ resolveFromCwd(): RA<readonly WorkspaceAnchor[], WorkspaceAnchorError>;
9
+ private resolveFromPath;
7
10
  private resolve;
8
11
  private gitCommand;
9
12
  }
@@ -4,33 +4,46 @@ exports.LocalWorkspaceAnchorV2 = void 0;
4
4
  const neverthrow_1 = require("neverthrow");
5
5
  const child_process_1 = require("child_process");
6
6
  const util_1 = require("util");
7
+ const url_1 = require("url");
7
8
  const execAsync = (0, util_1.promisify)(child_process_1.exec);
8
9
  class LocalWorkspaceAnchorV2 {
9
- constructor(cwd) {
10
- this.cwd = cwd;
10
+ constructor(defaultCwd) {
11
+ this.defaultCwd = defaultCwd;
11
12
  }
12
13
  resolveAnchors() {
13
- return neverthrow_1.ResultAsync.fromPromise(this.resolve(), (cause) => ({
14
+ return this.resolveFromPath(this.defaultCwd);
15
+ }
16
+ resolveFromUri(rootUri) {
17
+ const fsPath = uriToFsPath(rootUri);
18
+ if (fsPath === null)
19
+ return (0, neverthrow_1.okAsync)([]);
20
+ return this.resolveFromPath(fsPath);
21
+ }
22
+ resolveFromCwd() {
23
+ return this.resolveFromPath(this.defaultCwd);
24
+ }
25
+ resolveFromPath(cwd) {
26
+ return neverthrow_1.ResultAsync.fromPromise(this.resolve(cwd), (cause) => ({
14
27
  code: 'ANCHOR_RESOLVE_FAILED',
15
28
  message: `Failed to resolve workspace anchors: ${String(cause)}`,
16
29
  }));
17
30
  }
18
- async resolve() {
31
+ async resolve(cwd) {
19
32
  const anchors = [];
20
- const branch = await this.gitCommand('git rev-parse --abbrev-ref HEAD');
33
+ const branch = await this.gitCommand('git rev-parse --abbrev-ref HEAD', cwd);
21
34
  if (branch && branch !== 'HEAD') {
22
35
  anchors.push({ key: 'git_branch', value: branch });
23
36
  }
24
- const sha = await this.gitCommand('git rev-parse HEAD');
37
+ const sha = await this.gitCommand('git rev-parse HEAD', cwd);
25
38
  if (sha && /^[0-9a-f]{40}$/.test(sha)) {
26
39
  anchors.push({ key: 'git_head_sha', value: sha });
27
40
  }
28
41
  return anchors;
29
42
  }
30
- async gitCommand(cmd) {
43
+ async gitCommand(cmd, cwd) {
31
44
  try {
32
45
  const { stdout } = await execAsync(cmd, {
33
- cwd: this.cwd,
46
+ cwd,
34
47
  timeout: 5000,
35
48
  encoding: 'utf8',
36
49
  });
@@ -42,3 +55,13 @@ class LocalWorkspaceAnchorV2 {
42
55
  }
43
56
  }
44
57
  exports.LocalWorkspaceAnchorV2 = LocalWorkspaceAnchorV2;
58
+ function uriToFsPath(uri) {
59
+ if (!uri.startsWith('file://'))
60
+ return null;
61
+ try {
62
+ return (0, url_1.fileURLToPath)(uri);
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
@@ -16,3 +16,7 @@ export type WorkspaceAnchorError = {
16
16
  export interface WorkspaceAnchorPortV2 {
17
17
  resolveAnchors(): ResultAsync<readonly WorkspaceAnchor[], WorkspaceAnchorError>;
18
18
  }
19
+ export interface WorkspaceContextResolverPortV2 {
20
+ resolveFromUri(rootUri: string): ResultAsync<readonly WorkspaceAnchor[], WorkspaceAnchorError>;
21
+ resolveFromCwd(): ResultAsync<readonly WorkspaceAnchor[], WorkspaceAnchorError>;
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "test-session-persistence",
3
+ "name": "Session Persistence Test",
4
+ "description": "Purpose-built workflow for testing v2 session persistence, cross-chat resume, and checkpoint mechanics. Has 5 simple steps that agents can complete quickly with unique marker outputs.",
5
+ "version": "1.0.0",
6
+ "steps": [
7
+ {
8
+ "id": "step-1-alpha",
9
+ "title": "Step 1: Alpha Marker",
10
+ "prompt": "You are testing WorkRail v2 session persistence.\n\nYour task is simple: provide output containing the unique marker **ALPHA**.\n\nInclude in your output:\n- The marker string exactly as shown above\n- A brief note that you completed this step\n\nThis marker will be used in a future test to verify cross-chat session resumption works correctly."
11
+ },
12
+ {
13
+ "id": "step-2-beta",
14
+ "title": "Step 2: Beta Marker",
15
+ "prompt": "You are testing WorkRail v2 session persistence.\n\nYour task is simple: provide output containing the unique marker **BETA**.\n\nInclude in your output:\n- The marker string exactly as shown above\n- A brief note that you completed this step\n\nThis marker will be used in a future test to verify cross-chat session resumption works correctly."
16
+ },
17
+ {
18
+ "id": "step-3-gamma",
19
+ "title": "Step 3: Gamma Marker",
20
+ "prompt": "You are testing WorkRail v2 session persistence.\n\nYour task is simple: provide output containing the unique marker **GAMMA**.\n\nInclude in your output:\n- The marker string exactly as shown above\n- A brief note that you completed this step\n\nThis marker will be used in a future test to verify cross-chat session resumption works correctly."
21
+ },
22
+ {
23
+ "id": "step-4-delta",
24
+ "title": "Step 4: Delta Marker",
25
+ "prompt": "You are testing WorkRail v2 session persistence.\n\nYour task is simple: provide output containing the unique marker **DELTA**.\n\nInclude in your output:\n- The marker string exactly as shown above\n- A brief note that you completed this step\n\nThis is the penultimate step."
26
+ },
27
+ {
28
+ "id": "step-5-final",
29
+ "title": "Step 5: Final Step",
30
+ "prompt": "You are testing WorkRail v2 session persistence.\n\nThis is the **final step**. Provide a summary:\n- How many unique markers did you write? (Should be 4: ALPHA, BETA, GAMMA, DELTA)\n- Did the workflow progress smoothly through all steps?\n- What CHAT_ID were you using?\n\nAfter you provide this output, the workflow will complete."
31
+ }
32
+ ]
33
+ }