@exaudeus/workrail 1.4.0 → 1.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.
Files changed (33) hide show
  1. package/dist/manifest.json +94 -30
  2. package/dist/mcp/handlers/v2-resume.d.ts +8 -0
  3. package/dist/mcp/handlers/v2-resume.js +70 -0
  4. package/dist/mcp/output-schemas.d.ts +40 -0
  5. package/dist/mcp/output-schemas.js +17 -1
  6. package/dist/mcp/server.js +12 -0
  7. package/dist/mcp/tool-descriptions.js +22 -0
  8. package/dist/mcp/tools.js +6 -0
  9. package/dist/mcp/types/tool-description-types.d.ts +1 -1
  10. package/dist/mcp/types/tool-description-types.js +1 -0
  11. package/dist/mcp/types/workflow-tool-edition.d.ts +1 -1
  12. package/dist/mcp/types.d.ts +6 -0
  13. package/dist/mcp/v2/tool-registry.js +8 -0
  14. package/dist/mcp/v2/tools.d.ts +15 -0
  15. package/dist/mcp/v2/tools.js +8 -1
  16. package/dist/v2/infra/local/directory-listing/index.d.ts +8 -0
  17. package/dist/v2/infra/local/directory-listing/index.js +18 -0
  18. package/dist/v2/infra/local/fs/index.d.ts +1 -0
  19. package/dist/v2/infra/local/fs/index.js +3 -0
  20. package/dist/v2/infra/local/session-summary-provider/index.d.ts +18 -0
  21. package/dist/v2/infra/local/session-summary-provider/index.js +141 -0
  22. package/dist/v2/ports/directory-listing.port.d.ts +5 -0
  23. package/dist/v2/ports/directory-listing.port.js +2 -0
  24. package/dist/v2/ports/fs.port.d.ts +1 -0
  25. package/dist/v2/ports/session-summary-provider.port.d.ts +9 -0
  26. package/dist/v2/ports/session-summary-provider.port.js +2 -0
  27. package/dist/v2/projections/resume-ranking.d.ts +62 -0
  28. package/dist/v2/projections/resume-ranking.js +105 -0
  29. package/dist/v2/usecases/enumerate-sessions.d.ts +9 -0
  30. package/dist/v2/usecases/enumerate-sessions.js +13 -0
  31. package/dist/v2/usecases/resume-session.d.ts +4 -0
  32. package/dist/v2/usecases/resume-session.js +9 -0
  33. package/package.json +1 -1
@@ -593,6 +593,14 @@
593
593
  "sha256": "561e34c59e1640dd419644b1abc750b2750e8a99bce000abef593e5047badc23",
594
594
  "bytes": 50334
595
595
  },
596
+ "mcp/handlers/v2-resume.d.ts": {
597
+ "sha256": "d88f6c35bcaf946666c837b72fda3702a2ebab5e478eb90f7b4b672a0e5fa24f",
598
+ "bytes": 471
599
+ },
600
+ "mcp/handlers/v2-resume.js": {
601
+ "sha256": "b4ce6f9cc3dffef91ca7da2e428d659c4ff79ceaf984c87d672290215936db97",
602
+ "bytes": 2771
603
+ },
596
604
  "mcp/handlers/v2-state-conversion.d.ts": {
597
605
  "sha256": "e8d70753ed1b83d9396864ef50929ba32cb008bda24bcb2ce624df24adb72570",
598
606
  "bytes": 1988
@@ -634,20 +642,20 @@
634
642
  "bytes": 7535
635
643
  },
636
644
  "mcp/output-schemas.d.ts": {
637
- "sha256": "ae6188036f2e379c9f5498f7256f658e5bdd5301024e7bd863d02ca3dfad9f41",
638
- "bytes": 43607
645
+ "sha256": "f18366a180449212d9a2b280dc9706f4f96a77ccead38bf11035c3859121fd84",
646
+ "bytes": 45137
639
647
  },
640
648
  "mcp/output-schemas.js": {
641
- "sha256": "3ad98beeb67c03622651e100e8a4c02374318e1967fa062335d6dda80664ad8e",
642
- "bytes": 10889
649
+ "sha256": "e7aeb43d06950e4a9ed1fa4ecd56418dc7eb32f54453634701b5dc39456eb3e8",
650
+ "bytes": 11565
643
651
  },
644
652
  "mcp/server.d.ts": {
645
653
  "sha256": "bec49b8d07e189a53db7100d2f0e1e84ffe03150f04e1b06908ee3282982b4a2",
646
654
  "bytes": 168
647
655
  },
648
656
  "mcp/server.js": {
649
- "sha256": "8c7afb047807dd5fcd461b813b7bc714ea375d0999dfe9a7c901bf1462798455",
650
- "bytes": 9940
657
+ "sha256": "37ab570be76b379a4076e78e5bc82a06b7ee1fc73d3d5010ad48b5650e54365a",
658
+ "bytes": 10631
651
659
  },
652
660
  "mcp/tool-description-provider.d.ts": {
653
661
  "sha256": "1d46abc3112e11b68e57197e846f5708293ec9b2281fa71a9124ee2aad71e41b",
@@ -662,8 +670,8 @@
662
670
  "bytes": 132
663
671
  },
664
672
  "mcp/tool-descriptions.js": {
665
- "sha256": "e5f98cd0791507b87b3d0883155e74aaf4a9f03fd17b795195f21835962bd4cf",
666
- "bytes": 15369
673
+ "sha256": "76d25a3a2bba38227ad199c4d1240769b6b14411ebecf965e9e00eaac2f2e01c",
674
+ "bytes": 16389
667
675
  },
668
676
  "mcp/tool-factory.d.ts": {
669
677
  "sha256": "0fe3c6b863b2d7aef0c3d659ff54f3a9ee8a0a3c2005b6565d2f8ad517bc7211",
@@ -678,28 +686,28 @@
678
686
  "bytes": 5976
679
687
  },
680
688
  "mcp/tools.js": {
681
- "sha256": "813dad3f02046e200d99c2228cbf53ad9de58a79e609356730c93b75903d4c01",
682
- "bytes": 8198
689
+ "sha256": "aa364f3e613fc61a39fc7336a723231da06852d92ec7840a95327aa370e42e1c",
690
+ "bytes": 8360
683
691
  },
684
692
  "mcp/types.d.ts": {
685
- "sha256": "9e29d5ae4ec1f80640f073f0d58b603158fe5b22897f44c1c77ed4abaef374f9",
686
- "bytes": 3638
693
+ "sha256": "564136878ad3e71300d5e82913590b1f43d3dfbafab36479c9a590853cd07a0a",
694
+ "bytes": 4050
687
695
  },
688
696
  "mcp/types.js": {
689
697
  "sha256": "0c12576fd0053115ff096fe26b38f77f1e830b7ec4781aaf94564827c4c9e81a",
690
698
  "bytes": 1253
691
699
  },
692
700
  "mcp/types/tool-description-types.d.ts": {
693
- "sha256": "41e31c87b6d5b2e0fb4b66b1e8fac3f74c9769c3ded8a26577804aeccb79ae7b",
694
- "bytes": 805
701
+ "sha256": "80984afa5283a0d651ff22c0c9b2d3ff14c85087aeaf735d306ae291d8cdfe4d",
702
+ "bytes": 823
695
703
  },
696
704
  "mcp/types/tool-description-types.js": {
697
- "sha256": "689e74ea09e25e51de6020e42de42c37965e55980635d8739fc9ad19ba341955",
698
- "bytes": 777
705
+ "sha256": "40c1e5f76edcbfbdf18f203dae2f295cce191b6af670436aeff5bc65d74d1a6b",
706
+ "bytes": 799
699
707
  },
700
708
  "mcp/types/workflow-tool-edition.d.ts": {
701
- "sha256": "e7228be685155777e3035963056edd503e6660985fe7b5f17032402ca6582c8c",
702
- "bytes": 1494
709
+ "sha256": "ebde63f0ea9de501946d75e050c9676c03d08b77b27ad858d88600249e480f6c",
710
+ "bytes": 1513
703
711
  },
704
712
  "mcp/types/workflow-tool-edition.js": {
705
713
  "sha256": "2dfd26acd3da2c249b728e7fc48c7eab3dc675f786e7689dbdeec53c25589369",
@@ -718,16 +726,16 @@
718
726
  "bytes": 408
719
727
  },
720
728
  "mcp/v2/tool-registry.js": {
721
- "sha256": "719429102bc33c9e5bcd7cf38b0242ecc038484de4159811fbe9b0efa2ebc460",
722
- "bytes": 2623
729
+ "sha256": "4b192604ddafd986416865041db7e395d205da6fa1d398bec07646900f6ce28c",
730
+ "bytes": 3078
723
731
  },
724
732
  "mcp/v2/tools.d.ts": {
725
- "sha256": "8fba15da4a60a46ed803046f9b61ea0b8947967880563562473d8a87de68d27c",
726
- "bytes": 3600
733
+ "sha256": "5da50237fcbce6240737441d134c8070256a23649b387d5c3438b41b2705905e",
734
+ "bytes": 4156
727
735
  },
728
736
  "mcp/v2/tools.js": {
729
- "sha256": "93f33152aebe0cbeb92daa727a3dc43ccb3f4cc1001e7564fdd7b31ee78b2475",
730
- "bytes": 5743
737
+ "sha256": "5c9b958f1a9eb609557ed5867fc9ea131a2a0a4aaf52ff05a38246dffe2b359d",
738
+ "bytes": 6435
731
739
  },
732
740
  "mcp/validation/bounded-json.d.ts": {
733
741
  "sha256": "82203ac6123d5c6989606c3b5405aaea99ab829c8958835f9ae3ba45b8bc8fd5",
@@ -1529,13 +1537,21 @@
1529
1537
  "sha256": "02d0ad7e3b8c615c6fa3fead6698664d282b2375ea3fc8bd563c12cc7853691c",
1530
1538
  "bytes": 3012
1531
1539
  },
1540
+ "v2/infra/local/directory-listing/index.d.ts": {
1541
+ "sha256": "18fd3d9d74c5541fe95f01be6dd276c6cf6ecf1426fcd0ad1e695d756d9ef93e",
1542
+ "bytes": 429
1543
+ },
1544
+ "v2/infra/local/directory-listing/index.js": {
1545
+ "sha256": "1930cb982b9034f46751a000011ea069b6e03becd898973afad4b74e6ebc1759",
1546
+ "bytes": 566
1547
+ },
1532
1548
  "v2/infra/local/fs/index.d.ts": {
1533
- "sha256": "d830aed74ebb3372ea5efa21ec96680cd188fc76dea6078286f5788b3736d2bf",
1534
- "bytes": 1252
1549
+ "sha256": "fe3f06badea2d62e9ade3954f716d0b7aa4778c4d49e5fb9f6f58db6d2e9429a",
1550
+ "bytes": 1323
1535
1551
  },
1536
1552
  "v2/infra/local/fs/index.js": {
1537
- "sha256": "97ce973a4bd48aa43e890fb18dc7b8c8b3f8451400539bf15b4c79f3680e2ba3",
1538
- "bytes": 7045
1553
+ "sha256": "05fcd1345b3f003c19a13ee8db4ef657f23815c253e4fa36fa4bd7092f0a0b68",
1554
+ "bytes": 7179
1539
1555
  },
1540
1556
  "v2/infra/local/hmac-sha256/index.d.ts": {
1541
1557
  "sha256": "dda3865510dfaf2f13947410d998da6ffecc9a2e728b3574f81e69d5db859815",
@@ -1593,6 +1609,14 @@
1593
1609
  "sha256": "53b9ee8545d99e23cfeecb12b39d202fc552c3e89de9fe71b5d1aaaf9afa6d95",
1594
1610
  "bytes": 29559
1595
1611
  },
1612
+ "v2/infra/local/session-summary-provider/index.d.ts": {
1613
+ "sha256": "8843c26694d12147c322a4ff1f220cef53774c1ea7087241cb6badd12a360fda",
1614
+ "bytes": 1069
1615
+ },
1616
+ "v2/infra/local/session-summary-provider/index.js": {
1617
+ "sha256": "d75b34ff09b4e5b0e4cd1b75b67c4efe643db0231afc919da54c29034c1c5c1d",
1618
+ "bytes": 5566
1619
+ },
1596
1620
  "v2/infra/local/sha256/index.d.ts": {
1597
1621
  "sha256": "8a727b7e54a38275ca6f9f1b8730f97cfc0a212df035df1bdc58e716e6824230",
1598
1622
  "bytes": 246
@@ -1665,9 +1689,17 @@
1665
1689
  "sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
1666
1690
  "bytes": 77
1667
1691
  },
1692
+ "v2/ports/directory-listing.port.d.ts": {
1693
+ "sha256": "3184229b7793bbd3100611c6308d33a96145c4dc63ba451d8c29ff507084e7b4",
1694
+ "bytes": 207
1695
+ },
1696
+ "v2/ports/directory-listing.port.js": {
1697
+ "sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
1698
+ "bytes": 77
1699
+ },
1668
1700
  "v2/ports/fs.port.d.ts": {
1669
- "sha256": "e752765aa1154de19c720590410a370c3ae485d73363dd66cf9cc17fcf75c103",
1670
- "bytes": 1534
1701
+ "sha256": "e4d80716cbfe86352f9017480af31f21e25dfea97c7449b384fe64237bba961d",
1702
+ "bytes": 1605
1671
1703
  },
1672
1704
  "v2/ports/fs.port.js": {
1673
1705
  "sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
@@ -1721,6 +1753,14 @@
1721
1753
  "sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
1722
1754
  "bytes": 77
1723
1755
  },
1756
+ "v2/ports/session-summary-provider.port.d.ts": {
1757
+ "sha256": "41861cb0e70ca81e0d931d9f504226c9c7361f59db21a8beab95c2b8ed13cb57",
1758
+ "bytes": 400
1759
+ },
1760
+ "v2/ports/session-summary-provider.port.js": {
1761
+ "sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
1762
+ "bytes": 77
1763
+ },
1724
1764
  "v2/ports/sha256.port.d.ts": {
1725
1765
  "sha256": "2ca3e0558992a96614d005582e1f38435de4335df82f8231f69eabf191272022",
1726
1766
  "bytes": 145
@@ -1817,6 +1857,14 @@
1817
1857
  "sha256": "c8190e4899ba9695c7c7a9e58e872d4a56c90dcb4dfde659d6c2801a8cb895a7",
1818
1858
  "bytes": 732
1819
1859
  },
1860
+ "v2/projections/resume-ranking.d.ts": {
1861
+ "sha256": "50ef48d0b195f89f01c0051be453666e6a680e356ca9c328a754aced90ee8d7a",
1862
+ "bytes": 2337
1863
+ },
1864
+ "v2/projections/resume-ranking.js": {
1865
+ "sha256": "4253bd05c81e4b5a95b3472cca9ea3c2d7e9e28fdaabe95c984a1165ecc3a69c",
1866
+ "bytes": 4232
1867
+ },
1820
1868
  "v2/projections/run-context.d.ts": {
1821
1869
  "sha256": "7bdc4b2bacfb4b42005b7a83d7ddaf8a8d414bed90b948c02dbeeacd741b31cc",
1822
1870
  "bytes": 909
@@ -1857,6 +1905,14 @@
1857
1905
  "sha256": "6ea788b80605db6d2057b3d5e520be48ace8afe6d8f37bf55cef00ba1471a6b7",
1858
1906
  "bytes": 1935
1859
1907
  },
1908
+ "v2/usecases/enumerate-sessions.d.ts": {
1909
+ "sha256": "6043d64b45360dfc6a15b822d9f013a0832127596706bf81569dfce256236d2d",
1910
+ "bytes": 499
1911
+ },
1912
+ "v2/usecases/enumerate-sessions.js": {
1913
+ "sha256": "99e135a7c7411ce850c9dd89963ef6a05d3488078d6c6b2c540cb2d2043a2e89",
1914
+ "bytes": 526
1915
+ },
1860
1916
  "v2/usecases/execution-session-gate.d.ts": {
1861
1917
  "sha256": "339c4a8e02a77416e725e063a57d39a20788244498ae2c7a31dc48d111af6280",
1862
1918
  "bytes": 2084
@@ -1880,6 +1936,14 @@
1880
1936
  "v2/usecases/import-session.js": {
1881
1937
  "sha256": "9b9c8922cc2e6d6904f564c35ff0e402a2e755187f36ee7e54e76826f51bed5c",
1882
1938
  "bytes": 2788
1939
+ },
1940
+ "v2/usecases/resume-session.d.ts": {
1941
+ "sha256": "02eec4908e02dc591bff06b0d38d686aad64719694fd353670b2b1d17d8f9543",
1942
+ "bytes": 434
1943
+ },
1944
+ "v2/usecases/resume-session.js": {
1945
+ "sha256": "db50776bbbd18f0a07c0c1b0d94689f78596d980824f4fe73f5cbe47a62d0d60",
1946
+ "bytes": 393
1883
1947
  }
1884
1948
  }
1885
1949
  }
@@ -0,0 +1,8 @@
1
+ import type { z } from 'zod';
2
+ import type { V2ResumeSessionInput } from '../v2/tools.js';
3
+ import type { ToolContext, ToolResult } from '../types.js';
4
+ import { V2ResumeSessionOutputSchema } from '../output-schemas.js';
5
+ type ResumeInput = z.infer<typeof V2ResumeSessionInput>;
6
+ type ResumeOutput = z.infer<typeof V2ResumeSessionOutputSchema>;
7
+ export declare function handleV2ResumeSession(input: ResumeInput, ctx: ToolContext): Promise<ToolResult<ResumeOutput>>;
8
+ export {};
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleV2ResumeSession = handleV2ResumeSession;
4
+ const types_js_1 = require("../types.js");
5
+ const output_schemas_js_1 = require("../output-schemas.js");
6
+ const v2_token_ops_js_1 = require("./v2-token-ops.js");
7
+ const index_js_1 = require("../../v2/durable-core/ids/index.js");
8
+ const resume_session_js_1 = require("../../v2/usecases/resume-session.js");
9
+ function anchorValue(anchors, key) {
10
+ const anchor = anchors.find((a) => a.key === key);
11
+ return anchor?.value;
12
+ }
13
+ async function handleV2ResumeSession(input, ctx) {
14
+ const v2 = ctx.v2;
15
+ if (!v2) {
16
+ return (0, types_js_1.errNotRetryable)('INTERNAL_ERROR', 'v2 dependencies not available');
17
+ }
18
+ if (!v2.sessionSummaryProvider) {
19
+ return (0, types_js_1.errNotRetryable)('INTERNAL_ERROR', 'resume_session requires sessionSummaryProvider port');
20
+ }
21
+ let anchors = [];
22
+ if (v2.workspaceAnchor) {
23
+ const anchorRes = await v2.workspaceAnchor.resolveAnchors();
24
+ if (anchorRes.isOk()) {
25
+ anchors = anchorRes.value;
26
+ }
27
+ }
28
+ const query = {
29
+ gitHeadSha: input.gitHeadSha ?? anchorValue(anchors, 'git_head_sha'),
30
+ gitBranch: input.gitBranch ?? anchorValue(anchors, 'git_branch'),
31
+ freeTextQuery: input.query,
32
+ };
33
+ const resumeResult = await (0, resume_session_js_1.resumeSession)(query, v2.sessionSummaryProvider);
34
+ if (resumeResult.isErr()) {
35
+ return (0, types_js_1.errNotRetryable)('INTERNAL_ERROR', `Resume failed: ${resumeResult.error.message}`);
36
+ }
37
+ const candidates = resumeResult.value;
38
+ const outputCandidates = [];
39
+ for (const candidate of candidates) {
40
+ const stateTokenRes = (0, v2_token_ops_js_1.signTokenOrErr)({
41
+ payload: {
42
+ tokenVersion: 1,
43
+ tokenKind: 'state',
44
+ sessionId: candidate.sessionId,
45
+ runId: (0, index_js_1.asRunId)(candidate.runId),
46
+ nodeId: (0, index_js_1.asNodeId)(candidate.preferredTipNodeId),
47
+ workflowHashRef: (0, index_js_1.asWorkflowHashRef)(''),
48
+ },
49
+ ports: v2.tokenCodecPorts,
50
+ });
51
+ if (stateTokenRes.isErr()) {
52
+ continue;
53
+ }
54
+ outputCandidates.push({
55
+ sessionId: candidate.sessionId,
56
+ runId: candidate.runId,
57
+ stateToken: stateTokenRes.value,
58
+ snippet: candidate.snippet,
59
+ whyMatched: [...candidate.whyMatched],
60
+ });
61
+ }
62
+ const output = output_schemas_js_1.V2ResumeSessionOutputSchema.parse({
63
+ candidates: outputCandidates,
64
+ totalEligible: candidates.length,
65
+ });
66
+ return {
67
+ type: 'success',
68
+ data: output,
69
+ };
70
+ }
@@ -1067,6 +1067,46 @@ export declare const V2ContinueWorkflowOutputSchema: z.ZodEffects<z.ZodDiscrimin
1067
1067
  retryable?: boolean | undefined;
1068
1068
  retryAckToken?: string | undefined;
1069
1069
  }>;
1070
+ export declare const V2ResumeSessionOutputSchema: z.ZodObject<{
1071
+ candidates: z.ZodArray<z.ZodObject<{
1072
+ sessionId: z.ZodString;
1073
+ runId: z.ZodString;
1074
+ stateToken: z.ZodString;
1075
+ snippet: z.ZodString;
1076
+ whyMatched: z.ZodArray<z.ZodEnum<["matched_head_sha", "matched_branch", "matched_notes", "matched_workflow_id", "recency_fallback"]>, "many">;
1077
+ }, "strip", z.ZodTypeAny, {
1078
+ sessionId: string;
1079
+ runId: string;
1080
+ stateToken: string;
1081
+ snippet: string;
1082
+ whyMatched: ("matched_head_sha" | "matched_branch" | "matched_notes" | "matched_workflow_id" | "recency_fallback")[];
1083
+ }, {
1084
+ sessionId: string;
1085
+ runId: string;
1086
+ stateToken: string;
1087
+ snippet: string;
1088
+ whyMatched: ("matched_head_sha" | "matched_branch" | "matched_notes" | "matched_workflow_id" | "recency_fallback")[];
1089
+ }>, "many">;
1090
+ totalEligible: z.ZodNumber;
1091
+ }, "strip", z.ZodTypeAny, {
1092
+ candidates: {
1093
+ sessionId: string;
1094
+ runId: string;
1095
+ stateToken: string;
1096
+ snippet: string;
1097
+ whyMatched: ("matched_head_sha" | "matched_branch" | "matched_notes" | "matched_workflow_id" | "recency_fallback")[];
1098
+ }[];
1099
+ totalEligible: number;
1100
+ }, {
1101
+ candidates: {
1102
+ sessionId: string;
1103
+ runId: string;
1104
+ stateToken: string;
1105
+ snippet: string;
1106
+ whyMatched: ("matched_head_sha" | "matched_branch" | "matched_notes" | "matched_workflow_id" | "recency_fallback")[];
1107
+ }[];
1108
+ totalEligible: number;
1109
+ }>;
1070
1110
  export declare const V2CheckpointWorkflowOutputSchema: z.ZodObject<{
1071
1111
  checkpointNodeId: z.ZodString;
1072
1112
  stateToken: z.ZodString;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OpenDashboardOutputSchema = exports.ReadSessionSchemaOutputSchema = exports.ReadSessionOutputSchema = exports.UpdateSessionOutputSchema = exports.CreateSessionOutputSchema = exports.V2StartWorkflowOutputSchema = exports.V2CheckpointWorkflowOutputSchema = exports.V2ContinueWorkflowOutputSchema = exports.V2BlockerReportSchema = exports.V2NextCallSchema = exports.V2NextIntentSchema = exports.V2PreferencesSchema = exports.V2PendingStepSchema = exports.V2WorkflowInspectOutputSchema = exports.V2WorkflowListOutputSchema = exports.V2WorkflowListItemSchema = exports.WorkflowGetSchemaOutputSchema = exports.WorkflowValidateJsonOutputSchema = exports.WorkflowNextOutputSchema = exports.WorkflowGetOutputSchema = exports.WorkflowListOutputSchema = exports.WorkflowSummarySchema = exports.JsonValueSchema = void 0;
3
+ exports.OpenDashboardOutputSchema = exports.ReadSessionSchemaOutputSchema = exports.ReadSessionOutputSchema = exports.UpdateSessionOutputSchema = exports.CreateSessionOutputSchema = exports.V2StartWorkflowOutputSchema = exports.V2CheckpointWorkflowOutputSchema = exports.V2ResumeSessionOutputSchema = exports.V2ContinueWorkflowOutputSchema = exports.V2BlockerReportSchema = exports.V2NextCallSchema = exports.V2NextIntentSchema = exports.V2PreferencesSchema = exports.V2PendingStepSchema = exports.V2WorkflowInspectOutputSchema = exports.V2WorkflowListOutputSchema = exports.V2WorkflowListItemSchema = exports.WorkflowGetSchemaOutputSchema = exports.WorkflowValidateJsonOutputSchema = exports.WorkflowNextOutputSchema = exports.WorkflowGetOutputSchema = exports.WorkflowListOutputSchema = exports.WorkflowSummarySchema = exports.JsonValueSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const state_js_1 = require("../domain/execution/state.js");
6
6
  const JsonPrimitiveSchema = zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean(), zod_1.z.null()]);
@@ -204,6 +204,22 @@ exports.V2ContinueWorkflowOutputSchema = zod_1.z.discriminatedUnion('kind', [
204
204
  V2ContinueWorkflowOkSchema,
205
205
  V2ContinueWorkflowBlockedSchema,
206
206
  ]).refine((data) => (data.pending ? data.ackToken != null : true), { message: 'ackToken is required when a pending step exists' });
207
+ exports.V2ResumeSessionOutputSchema = zod_1.z.object({
208
+ candidates: zod_1.z.array(zod_1.z.object({
209
+ sessionId: zod_1.z.string().min(1),
210
+ runId: zod_1.z.string().min(1),
211
+ stateToken: zod_1.z.string().regex(/^st1[023456789acdefghjklmnpqrstuvwxyz]+$/, 'Invalid stateToken format'),
212
+ snippet: zod_1.z.string().max(1024),
213
+ whyMatched: zod_1.z.array(zod_1.z.enum([
214
+ 'matched_head_sha',
215
+ 'matched_branch',
216
+ 'matched_notes',
217
+ 'matched_workflow_id',
218
+ 'recency_fallback',
219
+ ])),
220
+ })).max(5),
221
+ totalEligible: zod_1.z.number().int().min(0),
222
+ });
207
223
  exports.V2CheckpointWorkflowOutputSchema = zod_1.z.object({
208
224
  checkpointNodeId: zod_1.z.string().min(1),
209
225
  stateToken: zod_1.z.string().regex(/^st1[023456789acdefghjklmnpqrstuvwxyz]+$/, 'Invalid stateToken format'),
@@ -41,6 +41,8 @@ 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 index_js_2 = require("../v2/infra/local/directory-listing/index.js");
45
+ const index_js_3 = require("../v2/infra/local/session-summary-provider/index.js");
44
46
  const tool_factory_js_1 = require("./tool-factory.js");
45
47
  const handler_factory_js_1 = require("./handler-factory.js");
46
48
  const workflow_tool_edition_selector_js_1 = require("./workflow-tool-edition-selector.js");
@@ -87,6 +89,9 @@ async function createToolContext() {
87
89
  base32,
88
90
  bech32m,
89
91
  });
92
+ const dataDir = container_js_1.container.resolve(tokens_js_1.DI.V2.DataDir);
93
+ const fsPort = container_js_1.container.resolve(tokens_js_1.DI.V2.FileSystem);
94
+ const directoryListing = new index_js_2.LocalDirectoryListingV2(fsPort);
90
95
  v2 = {
91
96
  gate,
92
97
  sessionStore,
@@ -97,6 +102,13 @@ async function createToolContext() {
97
102
  idFactory,
98
103
  tokenCodecPorts,
99
104
  workspaceAnchor: new index_js_1.LocalWorkspaceAnchorV2(process.cwd()),
105
+ dataDir,
106
+ directoryListing,
107
+ sessionSummaryProvider: new index_js_3.LocalSessionSummaryProviderV2({
108
+ directoryListing,
109
+ dataDir,
110
+ sessionStore,
111
+ }),
100
112
  };
101
113
  console.error('[FeatureFlags] v2 tools enabled');
102
114
  }
@@ -123,6 +123,18 @@ Requires: checkpointToken (from the most recent start_workflow or continue_workf
123
123
  Idempotent: calling with the same checkpointToken multiple times is safe and returns the same result.
124
124
 
125
125
  Returns: checkpointNodeId + a fresh stateToken.`,
126
+ resume_session: `Find and reconnect to an existing workflow session (WorkRail v2, feature-flagged).
127
+
128
+ Use this when you need to resume a previously started workflow but don't have the stateToken (e.g., new chat, lost context).
129
+
130
+ WorkRail ranks sessions using a 5-tier matching algorithm:
131
+ 1. Exact git HEAD SHA match
132
+ 2. Git branch match (exact or prefix)
133
+ 3. Free text match against session notes
134
+ 4. Free text match against workflow ID
135
+ 5. Recency fallback
136
+
137
+ Returns: Up to 5 ranked candidates, each with a stateToken you can use with continue_workflow.`,
126
138
  },
127
139
  authoritative: {
128
140
  discover_workflows: `Check for workflows that apply to the user's request. Workflows are the user's pre-defined instructions that you MUST follow when they exist.
@@ -268,5 +280,15 @@ Creates a durable checkpoint without advancing. Use for long-running steps to sa
268
280
  Requires: checkpointToken from the most recent response. Idempotent.
269
281
 
270
282
  Returns: checkpointNodeId + fresh stateToken.`,
283
+ resume_session: `Find and reconnect to an existing workflow session (WorkRail v2, feature-flagged).
284
+
285
+ Call this when resuming a workflow without a stateToken. WorkRail ranks sessions deterministically:
286
+ 1. Exact git HEAD SHA match (tier 1)
287
+ 2. Git branch match (tier 2)
288
+ 3. Notes content match (tier 3)
289
+ 4. Workflow ID match (tier 4)
290
+ 5. Recency (tier 5)
291
+
292
+ Returns: Up to 5 candidates with stateTokens. Use the best match's stateToken with continue_workflow.`,
271
293
  },
272
294
  };
package/dist/mcp/tools.js CHANGED
@@ -87,6 +87,11 @@ exports.WORKFLOW_TOOL_ANNOTATIONS = {
87
87
  destructiveHint: false,
88
88
  idempotentHint: true,
89
89
  },
90
+ resume_session: {
91
+ readOnlyHint: true,
92
+ destructiveHint: false,
93
+ idempotentHint: true,
94
+ },
90
95
  };
91
96
  exports.WORKFLOW_TOOL_TITLES = {
92
97
  discover_workflows: 'Discover Available Workflows',
@@ -99,6 +104,7 @@ exports.WORKFLOW_TOOL_TITLES = {
99
104
  start_workflow: 'Start Workflow (v2)',
100
105
  continue_workflow: 'Continue Workflow (v2)',
101
106
  checkpoint_workflow: 'Checkpoint Workflow (v2)',
107
+ resume_session: 'Resume Session (v2)',
102
108
  };
103
109
  exports.CreateSessionInput = zod_1.z.object({
104
110
  workflowId: zod_1.z
@@ -1,6 +1,6 @@
1
1
  export declare const DESCRIPTION_MODES: readonly ["standard", "authoritative"];
2
2
  export type DescriptionMode = typeof DESCRIPTION_MODES[number];
3
- export declare const WORKFLOW_TOOL_NAMES: readonly ["discover_workflows", "preview_workflow", "advance_workflow", "validate_workflow", "get_workflow_schema", "list_workflows", "inspect_workflow", "start_workflow", "continue_workflow", "checkpoint_workflow"];
3
+ export declare const WORKFLOW_TOOL_NAMES: readonly ["discover_workflows", "preview_workflow", "advance_workflow", "validate_workflow", "get_workflow_schema", "list_workflows", "inspect_workflow", "start_workflow", "continue_workflow", "checkpoint_workflow", "resume_session"];
4
4
  export type WorkflowToolName = typeof WORKFLOW_TOOL_NAMES[number];
5
5
  export type ToolDescriptionMap = Readonly<Record<WorkflowToolName, string>>;
6
6
  export type DescriptionsByMode = Readonly<Record<DescriptionMode, ToolDescriptionMap>>;
@@ -18,6 +18,7 @@ exports.WORKFLOW_TOOL_NAMES = [
18
18
  'start_workflow',
19
19
  'continue_workflow',
20
20
  'checkpoint_workflow',
21
+ 'resume_session',
21
22
  ];
22
23
  function isDescriptionMode(value) {
23
24
  return exports.DESCRIPTION_MODES.includes(value);
@@ -2,7 +2,7 @@ import type { z } from 'zod';
2
2
  import type { ToolDefinition } from '../tool-factory.js';
3
3
  import type { ToolContext } from '../types.js';
4
4
  export type V1WorkflowToolName = 'discover_workflows' | 'preview_workflow' | 'advance_workflow' | 'validate_workflow' | 'get_workflow_schema';
5
- export type V2WorkflowToolName = 'list_workflows' | 'inspect_workflow' | 'start_workflow' | 'continue_workflow' | 'checkpoint_workflow';
5
+ export type V2WorkflowToolName = 'list_workflows' | 'inspect_workflow' | 'start_workflow' | 'continue_workflow' | 'checkpoint_workflow' | 'resume_session';
6
6
  export type McpCallToolResult = {
7
7
  readonly content: ReadonlyArray<{
8
8
  readonly type: 'text';
@@ -13,6 +13,9 @@ 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
15
  import type { WorkspaceAnchorPortV2 } from '../v2/ports/workspace-anchor.port.js';
16
+ import type { DataDirPortV2 } from '../v2/ports/data-dir.port.js';
17
+ import type { DirectoryListingPortV2 } from '../v2/ports/directory-listing.port.js';
18
+ import type { SessionSummaryProviderPortV2 } from '../v2/ports/session-summary-provider.port.js';
16
19
  export interface SessionHealthDetails {
17
20
  readonly health: SessionHealthV2;
18
21
  }
@@ -53,6 +56,9 @@ export interface V2Dependencies {
53
56
  readonly idFactory: IdFactoryV2;
54
57
  readonly tokenCodecPorts: TokenCodecPorts;
55
58
  readonly workspaceAnchor?: WorkspaceAnchorPortV2;
59
+ readonly dataDir?: DataDirPortV2;
60
+ readonly directoryListing?: DirectoryListingPortV2;
61
+ readonly sessionSummaryProvider?: SessionSummaryProviderPortV2;
56
62
  }
57
63
  export interface ToolContext {
58
64
  readonly workflowService: WorkflowService;
@@ -6,6 +6,7 @@ const tools_js_1 = require("./tools.js");
6
6
  const v2_execution_js_1 = require("../handlers/v2-execution.js");
7
7
  const v2_workflow_js_1 = require("../handlers/v2-workflow.js");
8
8
  const v2_checkpoint_js_1 = require("../handlers/v2-checkpoint.js");
9
+ const v2_resume_js_1 = require("../handlers/v2-resume.js");
9
10
  function buildV2ToolRegistry(buildTool) {
10
11
  const tools = [
11
12
  buildTool({
@@ -38,6 +39,12 @@ function buildV2ToolRegistry(buildTool) {
38
39
  inputSchema: tools_js_1.V2CheckpointWorkflowInput,
39
40
  annotations: tools_js_1.V2_TOOL_ANNOTATIONS.checkpoint_workflow,
40
41
  }),
42
+ buildTool({
43
+ name: 'resume_session',
44
+ title: tools_js_1.V2_TOOL_TITLES.resume_session,
45
+ inputSchema: tools_js_1.V2ResumeSessionInput,
46
+ annotations: tools_js_1.V2_TOOL_ANNOTATIONS.resume_session,
47
+ }),
41
48
  ];
42
49
  const handlers = {
43
50
  list_workflows: (0, handler_factory_js_1.createHandler)(tools_js_1.V2ListWorkflowsInput, v2_workflow_js_1.handleV2ListWorkflows),
@@ -45,6 +52,7 @@ function buildV2ToolRegistry(buildTool) {
45
52
  start_workflow: (0, handler_factory_js_1.createHandler)(tools_js_1.V2StartWorkflowInput, v2_execution_js_1.handleV2StartWorkflow),
46
53
  continue_workflow: (0, handler_factory_js_1.createHandler)(tools_js_1.V2ContinueWorkflowInput, v2_execution_js_1.handleV2ContinueWorkflow),
47
54
  checkpoint_workflow: (0, handler_factory_js_1.createHandler)(tools_js_1.V2CheckpointWorkflowInput, v2_checkpoint_js_1.handleV2CheckpointWorkflow),
55
+ resume_session: (0, handler_factory_js_1.createHandler)(tools_js_1.V2ResumeSessionInput, v2_resume_js_1.handleV2ResumeSession),
48
56
  };
49
57
  return { tools, handlers };
50
58
  }
@@ -77,6 +77,20 @@ export declare const V2ContinueWorkflowInput: z.ZodEffects<z.ZodObject<{
77
77
  ackToken?: string | undefined;
78
78
  }>;
79
79
  export type V2ContinueWorkflowInput = z.infer<typeof V2ContinueWorkflowInput>;
80
+ export declare const V2ResumeSessionInput: z.ZodObject<{
81
+ query: z.ZodOptional<z.ZodString>;
82
+ gitBranch: z.ZodOptional<z.ZodString>;
83
+ gitHeadSha: z.ZodOptional<z.ZodString>;
84
+ }, "strict", z.ZodTypeAny, {
85
+ query?: string | undefined;
86
+ gitBranch?: string | undefined;
87
+ gitHeadSha?: string | undefined;
88
+ }, {
89
+ query?: string | undefined;
90
+ gitBranch?: string | undefined;
91
+ gitHeadSha?: string | undefined;
92
+ }>;
93
+ export type V2ResumeSessionInput = z.infer<typeof V2ResumeSessionInput>;
80
94
  export declare const V2CheckpointWorkflowInput: z.ZodObject<{
81
95
  checkpointToken: z.ZodString;
82
96
  }, "strict", z.ZodTypeAny, {
@@ -91,5 +105,6 @@ export declare const V2_TOOL_TITLES: {
91
105
  readonly start_workflow: "Start Workflow (v2)";
92
106
  readonly continue_workflow: "Continue Workflow (v2)";
93
107
  readonly checkpoint_workflow: "Checkpoint Workflow (v2)";
108
+ readonly resume_session: "Resume Session (v2)";
94
109
  };
95
110
  export declare const V2_TOOL_ANNOTATIONS: Readonly<Record<keyof typeof V2_TOOL_TITLES, ToolAnnotations>>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.V2_TOOL_ANNOTATIONS = exports.V2_TOOL_TITLES = exports.V2CheckpointWorkflowInput = exports.V2ContinueWorkflowInput = exports.V2StartWorkflowInput = exports.V2InspectWorkflowInput = exports.V2ListWorkflowsInput = void 0;
3
+ exports.V2_TOOL_ANNOTATIONS = exports.V2_TOOL_TITLES = exports.V2CheckpointWorkflowInput = exports.V2ResumeSessionInput = exports.V2ContinueWorkflowInput = exports.V2StartWorkflowInput = exports.V2InspectWorkflowInput = exports.V2ListWorkflowsInput = void 0;
4
4
  const zod_1 = require("zod");
5
5
  exports.V2ListWorkflowsInput = zod_1.z.object({});
6
6
  exports.V2InspectWorkflowInput = zod_1.z.object({
@@ -54,6 +54,11 @@ exports.V2ContinueWorkflowInput = zod_1.z.object({
54
54
  });
55
55
  }
56
56
  });
57
+ exports.V2ResumeSessionInput = zod_1.z.object({
58
+ query: zod_1.z.string().max(256).optional().describe('Free text search to find a relevant session. Matches against recap notes and workflow IDs.'),
59
+ gitBranch: zod_1.z.string().max(256).optional().describe('Git branch name to match against session observations. Overrides auto-detected branch.'),
60
+ gitHeadSha: zod_1.z.string().regex(/^[0-9a-f]{40}$/).optional().describe('Git HEAD SHA to match against session observations. Overrides auto-detected HEAD.'),
61
+ }).strict();
57
62
  exports.V2CheckpointWorkflowInput = zod_1.z.object({
58
63
  checkpointToken: zod_1.z.string().min(1).describe('The checkpoint token from the most recent start_workflow or continue_workflow response. ' +
59
64
  'Creates a checkpoint on the current step without advancing. Idempotent — calling with the same token is safe.'),
@@ -64,6 +69,7 @@ exports.V2_TOOL_TITLES = {
64
69
  start_workflow: 'Start Workflow (v2)',
65
70
  continue_workflow: 'Continue Workflow (v2)',
66
71
  checkpoint_workflow: 'Checkpoint Workflow (v2)',
72
+ resume_session: 'Resume Session (v2)',
67
73
  };
68
74
  exports.V2_TOOL_ANNOTATIONS = {
69
75
  list_workflows: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
@@ -71,4 +77,5 @@ exports.V2_TOOL_ANNOTATIONS = {
71
77
  start_workflow: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
72
78
  continue_workflow: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
73
79
  checkpoint_workflow: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
80
+ resume_session: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
74
81
  };
@@ -0,0 +1,8 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { FsError, FileSystemPortV2 } from '../../../ports/fs.port.js';
3
+ import type { DirectoryListingPortV2 } from '../../../ports/directory-listing.port.js';
4
+ export declare class LocalDirectoryListingV2 implements DirectoryListingPortV2 {
5
+ private readonly fs;
6
+ constructor(fs: FileSystemPortV2);
7
+ readdir(dirPath: string): ResultAsync<readonly string[], FsError>;
8
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalDirectoryListingV2 = void 0;
4
+ const neverthrow_1 = require("neverthrow");
5
+ class LocalDirectoryListingV2 {
6
+ constructor(fs) {
7
+ this.fs = fs;
8
+ }
9
+ readdir(dirPath) {
10
+ return this.fs.readdir(dirPath).orElse((e) => {
11
+ if (e.code === 'FS_NOT_FOUND') {
12
+ return (0, neverthrow_1.okAsync)([]);
13
+ }
14
+ return (0, neverthrow_1.errAsync)(e);
15
+ });
16
+ }
17
+ }
18
+ exports.LocalDirectoryListingV2 = LocalDirectoryListingV2;
@@ -23,4 +23,5 @@ export declare class NodeFileSystemV2 implements FileSystemPortV2 {
23
23
  stat(filePath: string): ResultAsync<{
24
24
  readonly sizeBytes: number;
25
25
  }, FsError>;
26
+ readdir(dirPath: string): ResultAsync<readonly string[], FsError>;
26
27
  }
@@ -159,5 +159,8 @@ class NodeFileSystemV2 {
159
159
  stat(filePath) {
160
160
  return neverthrow_1.ResultAsync.fromPromise(fs.stat(filePath), (e) => mapFsError(e, filePath)).map((s) => ({ sizeBytes: s.size }));
161
161
  }
162
+ readdir(dirPath) {
163
+ return neverthrow_1.ResultAsync.fromPromise(fs.readdir(dirPath), (e) => mapFsError(e, dirPath));
164
+ }
162
165
  }
163
166
  exports.NodeFileSystemV2 = NodeFileSystemV2;
@@ -0,0 +1,18 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { DirectoryListingPortV2 } from '../../../ports/directory-listing.port.js';
3
+ import type { DataDirPortV2 } from '../../../ports/data-dir.port.js';
4
+ import type { SessionEventLogReadonlyStorePortV2 } from '../../../ports/session-event-log-store.port.js';
5
+ import type { SessionSummaryProviderPortV2, SessionSummaryError } from '../../../ports/session-summary-provider.port.js';
6
+ import type { HealthySessionSummary } from '../../../projections/resume-ranking.js';
7
+ export interface LocalSessionSummaryProviderPorts {
8
+ readonly directoryListing: DirectoryListingPortV2;
9
+ readonly dataDir: DataDirPortV2;
10
+ readonly sessionStore: SessionEventLogReadonlyStorePortV2;
11
+ }
12
+ export declare class LocalSessionSummaryProviderV2 implements SessionSummaryProviderPortV2 {
13
+ private readonly ports;
14
+ constructor(ports: LocalSessionSummaryProviderPorts);
15
+ loadHealthySummaries(): ResultAsync<readonly HealthySessionSummary[], SessionSummaryError>;
16
+ private collectHealthySummaries;
17
+ private tryLoadSummary;
18
+ }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalSessionSummaryProviderV2 = void 0;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const resume_ranking_js_1 = require("../../../projections/resume-ranking.js");
6
+ const enumerate_sessions_js_1 = require("../../../usecases/enumerate-sessions.js");
7
+ const session_health_js_1 = require("../../../projections/session-health.js");
8
+ const run_dag_js_1 = require("../../../projections/run-dag.js");
9
+ const node_outputs_js_1 = require("../../../projections/node-outputs.js");
10
+ const MAX_SESSIONS_TO_SCAN = 50;
11
+ class LocalSessionSummaryProviderV2 {
12
+ constructor(ports) {
13
+ this.ports = ports;
14
+ }
15
+ loadHealthySummaries() {
16
+ return (0, enumerate_sessions_js_1.enumerateSessions)({
17
+ directoryListing: this.ports.directoryListing,
18
+ dataDir: this.ports.dataDir,
19
+ })
20
+ .mapErr((fsErr) => ({
21
+ code: 'SESSION_SUMMARY_ENUMERATION_FAILED',
22
+ message: `Failed to enumerate sessions: ${fsErr.message}`,
23
+ }))
24
+ .andThen((sessionIds) => {
25
+ const capped = sessionIds.slice(0, MAX_SESSIONS_TO_SCAN);
26
+ return this.collectHealthySummaries(capped);
27
+ });
28
+ }
29
+ collectHealthySummaries(sessionIds) {
30
+ const summaries = [];
31
+ let chain = (0, neverthrow_1.okAsync)(undefined);
32
+ for (const sessionId of sessionIds) {
33
+ chain = chain.andThen(() => this.tryLoadSummary(sessionId).map((summary) => {
34
+ if (summary !== null) {
35
+ summaries.push(summary);
36
+ }
37
+ }));
38
+ }
39
+ return chain.map(() => summaries);
40
+ }
41
+ tryLoadSummary(sessionId) {
42
+ return this.ports.sessionStore
43
+ .load(sessionId)
44
+ .map((truth) => {
45
+ const healthRes = (0, session_health_js_1.projectSessionHealthV2)(truth);
46
+ if (healthRes.isErr())
47
+ return null;
48
+ if (healthRes.value.kind !== 'healthy')
49
+ return null;
50
+ const dagRes = (0, run_dag_js_1.projectRunDagV2)(truth.events);
51
+ if (dagRes.isErr())
52
+ return null;
53
+ const runs = Object.values(dagRes.value.runsById);
54
+ if (runs.length === 0)
55
+ return null;
56
+ const runsWithActivity = runs
57
+ .filter((r) => r.preferredTipNodeId !== null)
58
+ .map((r) => {
59
+ const tipNode = r.nodesById[r.preferredTipNodeId];
60
+ const maxEventIndex = tipNode ? tipNode.createdAtEventIndex : 0;
61
+ return { run: r, lastActivityEventIndex: maxEventIndex };
62
+ })
63
+ .sort((a, b) => b.lastActivityEventIndex - a.lastActivityEventIndex);
64
+ if (runsWithActivity.length === 0)
65
+ return null;
66
+ const bestRun = runsWithActivity[0];
67
+ const run = bestRun.run;
68
+ const outputsRes = (0, node_outputs_js_1.projectNodeOutputsV2)(truth.events);
69
+ const recapSnippet = outputsRes.isOk() && run.preferredTipNodeId
70
+ ? extractRecapFromOutputs(outputsRes.value, run.preferredTipNodeId)
71
+ : null;
72
+ const observations = extractObservations(truth.events);
73
+ const workflow = extractWorkflowIdentity(truth.events, run.runId);
74
+ return {
75
+ sessionId,
76
+ runId: run.runId,
77
+ preferredTip: {
78
+ nodeId: run.preferredTipNodeId,
79
+ lastActivityEventIndex: bestRun.lastActivityEventIndex,
80
+ },
81
+ recapSnippet,
82
+ observations,
83
+ workflow,
84
+ };
85
+ })
86
+ .orElse(() => (0, neverthrow_1.okAsync)(null));
87
+ }
88
+ }
89
+ exports.LocalSessionSummaryProviderV2 = LocalSessionSummaryProviderV2;
90
+ function extractRecapFromOutputs(outputs, nodeId) {
91
+ const nodeView = outputs.nodesById[nodeId];
92
+ if (!nodeView)
93
+ return null;
94
+ const recapOutputs = nodeView.currentByChannel['recap'];
95
+ if (!recapOutputs || recapOutputs.length === 0)
96
+ return null;
97
+ const latest = recapOutputs[recapOutputs.length - 1];
98
+ if (latest.payload.payloadKind !== 'notes')
99
+ return null;
100
+ const markdown = latest.payload.notesMarkdown;
101
+ if (!markdown || typeof markdown !== 'string')
102
+ return null;
103
+ return (0, resume_ranking_js_1.asRecapSnippet)(markdown);
104
+ }
105
+ function extractObservations(events) {
106
+ let gitHeadSha = null;
107
+ let gitBranch = null;
108
+ let repoRootHash = null;
109
+ for (const event of events) {
110
+ if (event.kind !== 'observation_recorded')
111
+ continue;
112
+ const data = event.data;
113
+ switch (data.key) {
114
+ case 'git_head_sha':
115
+ gitHeadSha = data.value.value;
116
+ break;
117
+ case 'git_branch':
118
+ gitBranch = data.value.value;
119
+ break;
120
+ case 'repo_root_hash':
121
+ repoRootHash = data.value.value;
122
+ break;
123
+ }
124
+ }
125
+ return { gitHeadSha, gitBranch, repoRootHash };
126
+ }
127
+ function extractWorkflowIdentity(events, runId) {
128
+ for (const event of events) {
129
+ if (event.kind !== 'run_started')
130
+ continue;
131
+ const scope = event.scope;
132
+ if (scope?.runId !== runId)
133
+ continue;
134
+ const data = event.data;
135
+ return {
136
+ workflowId: data.workflowId ?? null,
137
+ workflowName: null,
138
+ };
139
+ }
140
+ return { workflowId: null, workflowName: null };
141
+ }
@@ -0,0 +1,5 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { FsError } from './fs.port.js';
3
+ export interface DirectoryListingPortV2 {
4
+ readdir(dirPath: string): ResultAsync<readonly string[], FsError>;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -38,4 +38,5 @@ export interface FileSystemPortV2 {
38
38
  stat(filePath: string): ResultAsync<{
39
39
  readonly sizeBytes: number;
40
40
  }, FsError>;
41
+ readdir(dirPath: string): ResultAsync<readonly string[], FsError>;
41
42
  }
@@ -0,0 +1,9 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { HealthySessionSummary } from '../projections/resume-ranking.js';
3
+ export interface SessionSummaryError {
4
+ readonly code: 'SESSION_SUMMARY_ENUMERATION_FAILED';
5
+ readonly message: string;
6
+ }
7
+ export interface SessionSummaryProviderPortV2 {
8
+ loadHealthySummaries(): ResultAsync<readonly HealthySessionSummary[], SessionSummaryError>;
9
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,62 @@
1
+ import type { SessionId } from '../durable-core/ids/index.js';
2
+ export type RecapSnippet = string & {
3
+ readonly __brand: 'RecapSnippet';
4
+ };
5
+ export declare function asRecapSnippet(raw: string): RecapSnippet;
6
+ export interface SessionObservations {
7
+ readonly gitHeadSha: string | null;
8
+ readonly gitBranch: string | null;
9
+ readonly repoRootHash: string | null;
10
+ }
11
+ export interface WorkflowIdentity {
12
+ readonly workflowId: string | null;
13
+ readonly workflowName: string | null;
14
+ }
15
+ export interface HealthySessionSummary {
16
+ readonly sessionId: SessionId;
17
+ readonly runId: string;
18
+ readonly preferredTip: {
19
+ readonly nodeId: string;
20
+ readonly lastActivityEventIndex: number;
21
+ };
22
+ readonly recapSnippet: RecapSnippet | null;
23
+ readonly observations: SessionObservations;
24
+ readonly workflow: WorkflowIdentity;
25
+ }
26
+ export type WhyMatched = 'matched_head_sha' | 'matched_branch' | 'matched_notes' | 'matched_workflow_id' | 'recency_fallback';
27
+ export type TierAssignment = {
28
+ readonly tier: 1;
29
+ readonly kind: 'matched_head_sha';
30
+ } | {
31
+ readonly tier: 2;
32
+ readonly kind: 'matched_branch';
33
+ readonly matchType: 'exact' | 'prefix';
34
+ } | {
35
+ readonly tier: 3;
36
+ readonly kind: 'matched_notes';
37
+ } | {
38
+ readonly tier: 4;
39
+ readonly kind: 'matched_workflow_id';
40
+ } | {
41
+ readonly tier: 5;
42
+ readonly kind: 'recency_fallback';
43
+ };
44
+ export declare function normalizeToTokens(text: string): ReadonlySet<string>;
45
+ export declare function allQueryTokensMatch(queryTokens: ReadonlySet<string>, candidateTokens: ReadonlySet<string>): boolean;
46
+ export interface ResumeQuery {
47
+ readonly gitHeadSha?: string;
48
+ readonly gitBranch?: string;
49
+ readonly freeTextQuery?: string;
50
+ }
51
+ export declare function assignTier(summary: HealthySessionSummary, query: ResumeQuery): TierAssignment;
52
+ export interface RankedResumeCandidate {
53
+ readonly sessionId: SessionId;
54
+ readonly runId: string;
55
+ readonly preferredTipNodeId: string;
56
+ readonly snippet: string;
57
+ readonly whyMatched: readonly WhyMatched[];
58
+ readonly tierAssignment: TierAssignment;
59
+ readonly lastActivityEventIndex: number;
60
+ }
61
+ export declare const MAX_RESUME_CANDIDATES = 5;
62
+ export declare function rankResumeCandidates(summaries: readonly HealthySessionSummary[], query: ResumeQuery): readonly RankedResumeCandidate[];
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_RESUME_CANDIDATES = void 0;
4
+ exports.asRecapSnippet = asRecapSnippet;
5
+ exports.normalizeToTokens = normalizeToTokens;
6
+ exports.allQueryTokensMatch = allQueryTokensMatch;
7
+ exports.assignTier = assignTier;
8
+ exports.rankResumeCandidates = rankResumeCandidates;
9
+ const MAX_SNIPPET_BYTES = 1024;
10
+ const TRUNCATION_MARKER = '\n\n[TRUNCATED]';
11
+ function asRecapSnippet(raw) {
12
+ const stripped = raw.endsWith(TRUNCATION_MARKER)
13
+ ? raw.slice(0, -TRUNCATION_MARKER.length)
14
+ : raw;
15
+ const encoder = new TextEncoder();
16
+ const bytes = encoder.encode(stripped);
17
+ if (bytes.length <= MAX_SNIPPET_BYTES) {
18
+ return stripped;
19
+ }
20
+ const decoder = new TextDecoder('utf-8', { fatal: false });
21
+ const truncated = decoder.decode(bytes.slice(0, MAX_SNIPPET_BYTES));
22
+ return truncated;
23
+ }
24
+ const TOKEN_REGEX = /[a-z0-9_-]+/g;
25
+ function normalizeToTokens(text) {
26
+ const normalized = text.normalize('NFKC').toLowerCase();
27
+ const matches = normalized.match(TOKEN_REGEX);
28
+ return new Set(matches ?? []);
29
+ }
30
+ function allQueryTokensMatch(queryTokens, candidateTokens) {
31
+ if (queryTokens.size === 0)
32
+ return false;
33
+ for (const token of queryTokens) {
34
+ if (!candidateTokens.has(token))
35
+ return false;
36
+ }
37
+ return true;
38
+ }
39
+ function assignTier(summary, query) {
40
+ if (query.gitHeadSha && summary.observations.gitHeadSha === query.gitHeadSha) {
41
+ return { tier: 1, kind: 'matched_head_sha' };
42
+ }
43
+ if (query.gitBranch && summary.observations.gitBranch) {
44
+ if (summary.observations.gitBranch === query.gitBranch) {
45
+ return { tier: 2, kind: 'matched_branch', matchType: 'exact' };
46
+ }
47
+ if (summary.observations.gitBranch.startsWith(query.gitBranch) ||
48
+ query.gitBranch.startsWith(summary.observations.gitBranch)) {
49
+ return { tier: 2, kind: 'matched_branch', matchType: 'prefix' };
50
+ }
51
+ }
52
+ if (query.freeTextQuery && summary.recapSnippet) {
53
+ const queryTokens = normalizeToTokens(query.freeTextQuery);
54
+ const noteTokens = normalizeToTokens(summary.recapSnippet);
55
+ if (allQueryTokensMatch(queryTokens, noteTokens)) {
56
+ return { tier: 3, kind: 'matched_notes' };
57
+ }
58
+ }
59
+ if (query.freeTextQuery) {
60
+ const queryTokens = normalizeToTokens(query.freeTextQuery);
61
+ const workflowText = [
62
+ summary.workflow.workflowId ?? '',
63
+ summary.workflow.workflowName ?? '',
64
+ ].join(' ');
65
+ const workflowTokens = normalizeToTokens(workflowText);
66
+ if (allQueryTokensMatch(queryTokens, workflowTokens)) {
67
+ return { tier: 4, kind: 'matched_workflow_id' };
68
+ }
69
+ }
70
+ return { tier: 5, kind: 'recency_fallback' };
71
+ }
72
+ function tierToWhyMatched(tier) {
73
+ switch (tier.kind) {
74
+ case 'matched_head_sha': return 'matched_head_sha';
75
+ case 'matched_branch': return 'matched_branch';
76
+ case 'matched_notes': return 'matched_notes';
77
+ case 'matched_workflow_id': return 'matched_workflow_id';
78
+ case 'recency_fallback': return 'recency_fallback';
79
+ }
80
+ }
81
+ exports.MAX_RESUME_CANDIDATES = 5;
82
+ function rankResumeCandidates(summaries, query) {
83
+ const withTier = summaries.map((summary) => ({
84
+ summary,
85
+ tier: assignTier(summary, query),
86
+ }));
87
+ const sorted = [...withTier].sort((a, b) => {
88
+ if (a.tier.tier !== b.tier.tier)
89
+ return a.tier.tier - b.tier.tier;
90
+ const actA = a.summary.preferredTip.lastActivityEventIndex;
91
+ const actB = b.summary.preferredTip.lastActivityEventIndex;
92
+ if (actA !== actB)
93
+ return actB - actA;
94
+ return String(a.summary.sessionId).localeCompare(String(b.summary.sessionId));
95
+ });
96
+ return sorted.slice(0, exports.MAX_RESUME_CANDIDATES).map(({ summary, tier }) => ({
97
+ sessionId: summary.sessionId,
98
+ runId: summary.runId,
99
+ preferredTipNodeId: summary.preferredTip.nodeId,
100
+ snippet: summary.recapSnippet ?? '',
101
+ whyMatched: [tierToWhyMatched(tier)],
102
+ tierAssignment: tier,
103
+ lastActivityEventIndex: summary.preferredTip.lastActivityEventIndex,
104
+ }));
105
+ }
@@ -0,0 +1,9 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { DirectoryListingPortV2 } from '../ports/directory-listing.port.js';
3
+ import type { DataDirPortV2 } from '../ports/data-dir.port.js';
4
+ import type { FsError } from '../ports/fs.port.js';
5
+ import { type SessionId } from '../durable-core/ids/index.js';
6
+ export declare function enumerateSessions(ports: {
7
+ readonly directoryListing: DirectoryListingPortV2;
8
+ readonly dataDir: DataDirPortV2;
9
+ }): ResultAsync<readonly SessionId[], FsError>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.enumerateSessions = enumerateSessions;
4
+ const index_js_1 = require("../durable-core/ids/index.js");
5
+ const SESSION_DIR_PATTERN = /^sess_[a-zA-Z0-9_]+$/;
6
+ function enumerateSessions(ports) {
7
+ return ports.directoryListing
8
+ .readdir(ports.dataDir.sessionsDir())
9
+ .map((entries) => entries
10
+ .filter((entry) => SESSION_DIR_PATTERN.test(entry))
11
+ .sort()
12
+ .map((entry) => (0, index_js_1.asSessionId)(entry)));
13
+ }
@@ -0,0 +1,4 @@
1
+ import type { ResultAsync } from 'neverthrow';
2
+ import type { SessionSummaryProviderPortV2, SessionSummaryError } from '../ports/session-summary-provider.port.js';
3
+ import { type ResumeQuery, type RankedResumeCandidate } from '../projections/resume-ranking.js';
4
+ export declare function resumeSession(query: ResumeQuery, summaryProvider: SessionSummaryProviderPortV2): ResultAsync<readonly RankedResumeCandidate[], SessionSummaryError>;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resumeSession = resumeSession;
4
+ const resume_ranking_js_1 = require("../projections/resume-ranking.js");
5
+ function resumeSession(query, summaryProvider) {
6
+ return summaryProvider
7
+ .loadHealthySummaries()
8
+ .map((summaries) => (0, resume_ranking_js_1.rankResumeCandidates)(summaries, query));
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {