@exaudeus/workrail 1.7.3 → 1.7.5

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.
@@ -562,12 +562,12 @@
562
562
  "bytes": 5774
563
563
  },
564
564
  "mcp/handlers/v2-advance-core/index.d.ts": {
565
- "sha256": "a927c7e13912cfcad6340922a24f5b9331d513ce25191049aabdce96a7a86a68",
566
- "bytes": 3500
565
+ "sha256": "7a641c5419b4f273feff454d465bb5c5f3b2c349cc5e0032ab41ab3c3ebd033f",
566
+ "bytes": 3448
567
567
  },
568
568
  "mcp/handlers/v2-advance-core/index.js": {
569
- "sha256": "f144bbf46678ff03dcdf3e76d6fe0375942469a9e45d40de748c80d4c49bb2c6",
570
- "bytes": 7134
569
+ "sha256": "bcdaa5e283da9def40695b6c4b529b7dcc0861d748133377bb9ed62bdd28a2d2",
570
+ "bytes": 7107
571
571
  },
572
572
  "mcp/handlers/v2-advance-core/input-validation.d.ts": {
573
573
  "sha256": "d736f163f6673b30f653c63336a287cdfe99c9f58b601c7220ce441590f802ba",
@@ -582,16 +582,16 @@
582
582
  "bytes": 1014
583
583
  },
584
584
  "mcp/handlers/v2-advance-core/outcome-blocked.js": {
585
- "sha256": "12ac27a78a1d42ea145b93f76baab595a6b22e491b29021af1ac0b408f39ee8e",
586
- "bytes": 3399
585
+ "sha256": "78e3c2c6dd5352dfb90ec9a89aeab1692cdb3aea02f89522f34f748aa68a3487",
586
+ "bytes": 3400
587
587
  },
588
588
  "mcp/handlers/v2-advance-core/outcome-success.d.ts": {
589
589
  "sha256": "da1bcf2d275ba9f1c0f073446f3c87b67394161b0b69c669f70c758bc597c8be",
590
590
  "bytes": 936
591
591
  },
592
592
  "mcp/handlers/v2-advance-core/outcome-success.js": {
593
- "sha256": "fad3e883e4688f344374bab6f58a753f8cce8fa3f1e931daf081db18155d04dd",
594
- "bytes": 5988
593
+ "sha256": "e08268f3b8fd11213e67052721e231a6e055daa1dd4f729784f6ed3b20ddddb4",
594
+ "bytes": 5961
595
595
  },
596
596
  "mcp/handlers/v2-advance-events.d.ts": {
597
597
  "sha256": "02cdb52a2c16dd619645b5496caf0880e57937bf21ea9efe44e6cd195cd43b94",
@@ -694,8 +694,8 @@
694
694
  "bytes": 471
695
695
  },
696
696
  "mcp/handlers/v2-resume.js": {
697
- "sha256": "74d66519764615780c556828c23ada5fa7df887c8263793de4323322aef14408",
698
- "bytes": 2826
697
+ "sha256": "851690a586328cb61a1aa0f9c2b8cac87b1a158430bab6e9601f63244a4dd69d",
698
+ "bytes": 3385
699
699
  },
700
700
  "mcp/handlers/v2-state-conversion.d.ts": {
701
701
  "sha256": "94bd06904ef58dd210ff17ffb75c2492beea8937eb06d99749e5d860c0e0d96b",
@@ -1822,8 +1822,8 @@
1822
1822
  "bytes": 1004
1823
1823
  },
1824
1824
  "v2/infra/local/session-summary-provider/index.js": {
1825
- "sha256": "f6c0df4a3355241ee3eb83c0013f9a252479f27f4c9172e0607464339fba6920",
1826
- "bytes": 5655
1825
+ "sha256": "a38c877faddb8e14bf1c30e173e4299af5edb4a1f8fb4b036732c6ba6f11ca6f",
1826
+ "bytes": 5933
1827
1827
  },
1828
1828
  "v2/infra/local/sha256/index.d.ts": {
1829
1829
  "sha256": "8a727b7e54a38275ca6f9f1b8730f97cfc0a212df035df1bdc58e716e6824230",
@@ -2074,12 +2074,12 @@
2074
2074
  "bytes": 732
2075
2075
  },
2076
2076
  "v2/projections/resume-ranking.d.ts": {
2077
- "sha256": "50ef48d0b195f89f01c0051be453666e6a680e356ca9c328a754aced90ee8d7a",
2078
- "bytes": 2337
2077
+ "sha256": "ca5cb0701edded54f08395deebfe50161528d36edb74294c26481501391e64bc",
2078
+ "bytes": 2558
2079
2079
  },
2080
2080
  "v2/projections/resume-ranking.js": {
2081
- "sha256": "2cc54d484865507e65cc6202dfb7c42df4535e4cfd6faca6b0d9ce059852a8bc",
2082
- "bytes": 4281
2081
+ "sha256": "aa6ba4b5480caebba3107f86058b9a834cbd89962d0340c89342417f77956e47",
2082
+ "bytes": 4251
2083
2083
  },
2084
2084
  "v2/projections/run-context.d.ts": {
2085
2085
  "sha256": "a4d57470a435ac9860f60b3244d1b828853995027cd510d8da42762d21b2a687",
@@ -47,7 +47,6 @@ export interface AdvanceContext {
47
47
  }
48
48
  export interface ComputedAdvanceResults {
49
49
  readonly reasons: readonly ReasonV1[];
50
- readonly effectiveReasons: readonly ReasonV1[];
51
50
  readonly outputRequirement: ReturnType<typeof getOutputRequirementStatusWithArtifactsV1>;
52
51
  readonly validation: ValidationResult | undefined;
53
52
  }
@@ -69,7 +69,7 @@ function executeAdvanceCore(args) {
69
69
  suggestions: [(0, v2_execution_helpers_js_1.internalSuggestion)('Retry your submission with the same output.', 'The validation criteria for this step may be misconfigured.')],
70
70
  };
71
71
  const ctx = { truth, sessionId, runId, currentNodeId, attemptId, workflowHash, inputOutput, pinnedWorkflow, engineState, pendingStep };
72
- const computed = { reasons, effectiveReasons, outputRequirement, validation: evalValidation };
72
+ const computed = { reasons: effectiveReasons, outputRequirement, validation: evalValidation };
73
73
  const portsLocal = { snapshotStore, sessionStore, sha256, idFactory };
74
74
  return (0, outcome_blocked_js_1.buildBlockedOutcome)({ mode, snap, ctx, computed, lock, ports: portsLocal });
75
75
  }
@@ -84,15 +84,15 @@ function executeAdvanceCore(args) {
84
84
  const missingNotes = !v.notesOptional && !v.notesMarkdown?.trim()
85
85
  ? { stepId: v.pendingStep.stepId }
86
86
  : undefined;
87
- const reasonsRes = (0, blocking_decision_js_1.detectBlockingReasonsV1)({ outputRequirement, missingNotes });
88
- if (reasonsRes.isErr()) {
89
- return errAsync({ kind: 'invariant_violation', message: reasonsRes.error.message });
87
+ const rawReasonsRes = (0, blocking_decision_js_1.detectBlockingReasonsV1)({ outputRequirement, missingNotes });
88
+ if (rawReasonsRes.isErr()) {
89
+ return errAsync({ kind: 'invariant_violation', message: rawReasonsRes.error.message });
90
90
  }
91
- const reasons = reasonsRes.value;
92
- const { blocking: effectiveReasons } = (0, risk_policy_guardrails_js_1.applyGuardrails)(v.riskPolicy, reasons);
93
- const shouldBlockNow = effectiveReasons.length > 0 && (0, reason_model_js_1.shouldBlock)(v.autonomy, effectiveReasons);
91
+ const rawReasons = rawReasonsRes.value;
92
+ const { blocking: reasons } = (0, risk_policy_guardrails_js_1.applyGuardrails)(v.riskPolicy, rawReasons);
93
+ const shouldBlockNow = reasons.length > 0 && (0, reason_model_js_1.shouldBlock)(v.autonomy, reasons);
94
94
  const ctx = { truth, sessionId, runId, currentNodeId, attemptId, workflowHash, inputOutput, pinnedWorkflow, engineState, pendingStep };
95
- const computed = { reasons, effectiveReasons, outputRequirement, validation };
95
+ const computed = { reasons, outputRequirement, validation };
96
96
  const ports = { snapshotStore, sessionStore, sha256, idFactory };
97
97
  if (shouldBlockNow) {
98
98
  return (0, outcome_blocked_js_1.buildBlockedOutcome)({ mode, snap, ctx, computed, lock, ports });
@@ -9,9 +9,9 @@ const event_builders_js_1 = require("./event-builders.js");
9
9
  function buildBlockedOutcome(args) {
10
10
  const { mode, snap, lock, ports } = args;
11
11
  const { truth, sessionId, runId, currentNodeId, attemptId, workflowHash } = args.ctx;
12
- const { reasons, effectiveReasons, outputRequirement, validation } = args.computed;
12
+ const { reasons, outputRequirement, validation } = args.computed;
13
13
  const { snapshotStore, sessionStore, sha256, idFactory } = ports;
14
- const blockersRes = (0, reason_model_js_1.buildBlockerReport)(effectiveReasons);
14
+ const blockersRes = (0, reason_model_js_1.buildBlockerReport)(reasons);
15
15
  if (blockersRes.isErr()) {
16
16
  return errAsync({ kind: 'invariant_violation', message: blockersRes.error.message });
17
17
  }
@@ -37,7 +37,7 @@ function buildBlockedOutcome(args) {
37
37
  const extraEventsToAppend = [validationEventRes.value];
38
38
  const primaryReason = reasons[0];
39
39
  if (!primaryReason) {
40
- return errAsync({ kind: 'invariant_violation', message: 'shouldBlockNow=true requires at least one reason' });
40
+ return errAsync({ kind: 'invariant_violation', message: 'shouldBlockNow=true requires at least one effective reason (post-guardrails)' });
41
41
  }
42
42
  const blockedSnapshotRes = (0, blocked_node_builder_js_1.buildBlockedNodeSnapshot)({
43
43
  priorSnapshot: snap,
@@ -18,7 +18,7 @@ function successNodeKind(mode) {
18
18
  function buildSuccessOutcome(args) {
19
19
  const { mode, v, lock, ports } = args;
20
20
  const { truth, sessionId, runId, currentNodeId, attemptId, workflowHash, inputOutput, pinnedWorkflow, engineState, pendingStep } = args.ctx;
21
- const { effectiveReasons, outputRequirement, validation } = args.computed;
21
+ const { reasons, outputRequirement, validation } = args.computed;
22
22
  const { snapshotStore, sessionStore, sha256, idFactory } = ports;
23
23
  const compiler = new workflow_compiler_js_1.WorkflowCompiler();
24
24
  const interpreter = new workflow_interpreter_js_1.WorkflowInterpreter();
@@ -48,9 +48,9 @@ function buildSuccessOutcome(args) {
48
48
  }
49
49
  const out = nextRes.value;
50
50
  const extraEventsToAppend = [];
51
- if (v.autonomy === 'full_auto_never_stop' && effectiveReasons.length > 0) {
51
+ if (v.autonomy === 'full_auto_never_stop' && reasons.length > 0) {
52
52
  extraEventsToAppend.push(...(0, v2_advance_events_js_1.buildGapEvents)({
53
- gaps: effectiveReasons,
53
+ gaps: reasons,
54
54
  sessionId: String(sessionId),
55
55
  runId,
56
56
  nodeId: currentNodeId,
@@ -31,8 +31,28 @@ async function handleV2ResumeSession(input, ctx) {
31
31
  return (0, types_js_1.errNotRetryable)('INTERNAL_ERROR', `Resume failed: ${resumeResult.error.message}`);
32
32
  }
33
33
  const candidates = resumeResult.value;
34
+ const { outputCandidates, skipped } = mintCandidateTokens(candidates, v2.tokenCodecPorts);
35
+ if (skipped > 0) {
36
+ console.error(`[workrail:resume] ${skipped}/${candidates.length} candidate(s) skipped: token minting failed (workflowHashRef derivation or signing error)`);
37
+ }
38
+ const output = output_schemas_js_1.V2ResumeSessionOutputSchema.parse({
39
+ candidates: outputCandidates,
40
+ totalEligible: candidates.length,
41
+ });
42
+ return {
43
+ type: 'success',
44
+ data: output,
45
+ };
46
+ }
47
+ function mintCandidateTokens(candidates, ports) {
34
48
  const outputCandidates = [];
49
+ let skipped = 0;
35
50
  for (const candidate of candidates) {
51
+ const wfRefRes = (0, index_js_1.deriveWorkflowHashRef)(candidate.workflowHash);
52
+ if (wfRefRes.isErr()) {
53
+ skipped++;
54
+ continue;
55
+ }
36
56
  const stateTokenRes = (0, v2_token_ops_js_1.signTokenOrErr)({
37
57
  payload: {
38
58
  tokenVersion: 1,
@@ -40,11 +60,12 @@ async function handleV2ResumeSession(input, ctx) {
40
60
  sessionId: candidate.sessionId,
41
61
  runId: (0, index_js_1.asRunId)(candidate.runId),
42
62
  nodeId: (0, index_js_1.asNodeId)(candidate.preferredTipNodeId),
43
- workflowHashRef: (0, index_js_1.asWorkflowHashRef)(''),
63
+ workflowHashRef: wfRefRes.value,
44
64
  },
45
- ports: v2.tokenCodecPorts,
65
+ ports,
46
66
  });
47
67
  if (stateTokenRes.isErr()) {
68
+ skipped++;
48
69
  continue;
49
70
  }
50
71
  outputCandidates.push({
@@ -55,12 +76,5 @@ async function handleV2ResumeSession(input, ctx) {
55
76
  whyMatched: [...candidate.whyMatched],
56
77
  });
57
78
  }
58
- const output = output_schemas_js_1.V2ResumeSessionOutputSchema.parse({
59
- candidates: outputCandidates,
60
- totalEligible: candidates.length,
61
- });
62
- return {
63
- type: 'success',
64
- data: output,
65
- };
79
+ return { outputCandidates, skipped };
66
80
  }
@@ -7,6 +7,7 @@ const enumerate_sessions_js_1 = require("../../../usecases/enumerate-sessions.js
7
7
  const session_health_js_1 = require("../../../projections/session-health.js");
8
8
  const run_dag_js_1 = require("../../../projections/run-dag.js");
9
9
  const node_outputs_js_1 = require("../../../projections/node-outputs.js");
10
+ const index_js_1 = require("../../../durable-core/ids/index.js");
10
11
  const constants_js_1 = require("../../../durable-core/constants.js");
11
12
  const MAX_SESSIONS_TO_SCAN = 50;
12
13
  const MAX_RECAP_ANCESTOR_DEPTH = 100;
@@ -15,10 +16,6 @@ const EMPTY_OBSERVATIONS = {
15
16
  gitBranch: null,
16
17
  repoRootHash: null,
17
18
  };
18
- const UNKNOWN_WORKFLOW = {
19
- workflowId: null,
20
- workflowName: null,
21
- };
22
19
  class LocalSessionSummaryProviderV2 {
23
20
  constructor(ports) {
24
21
  this.ports = ports;
@@ -55,6 +52,9 @@ function projectSessionSummary(sessionId, truth) {
55
52
  const bestRun = selectBestRun(Object.values(dag.value.runsById));
56
53
  if (!bestRun)
57
54
  return null;
55
+ const workflow = extractWorkflowIdentity(truth.events, bestRun.run.runId);
56
+ if (!workflow)
57
+ return null;
58
58
  const outputsRes = (0, node_outputs_js_1.projectNodeOutputsV2)(truth.events);
59
59
  const recapSnippet = outputsRes.isOk()
60
60
  ? extractAggregateRecap(outputsRes.value, bestRun.run, bestRun.tipNodeId)
@@ -68,7 +68,7 @@ function projectSessionSummary(sessionId, truth) {
68
68
  },
69
69
  recapSnippet,
70
70
  observations: extractObservations(truth.events),
71
- workflow: extractWorkflowIdentity(truth.events, bestRun.run.runId),
71
+ workflow,
72
72
  };
73
73
  }
74
74
  function selectBestRun(runs) {
@@ -99,10 +99,13 @@ function extractWorkflowIdentity(events, runId) {
99
99
  .filter((e) => e.kind === constants_js_1.EVENT_KIND.RUN_STARTED)
100
100
  .find((e) => e.scope.runId === runId);
101
101
  if (!event)
102
- return UNKNOWN_WORKFLOW;
102
+ return null;
103
+ if (!constants_js_1.SHA256_DIGEST_PATTERN.test(event.data.workflowHash))
104
+ return null;
103
105
  return {
104
- workflowId: event.data.workflowId,
105
- workflowName: null,
106
+ kind: 'identified',
107
+ workflowId: (0, index_js_1.asWorkflowId)(event.data.workflowId),
108
+ workflowHash: (0, index_js_1.asWorkflowHash)((0, index_js_1.asSha256Digest)(event.data.workflowHash)),
106
109
  };
107
110
  }
108
111
  function collectAncestorNodeIds(nodesById, nodeId, remainingDepth) {
@@ -1,4 +1,4 @@
1
- import type { SessionId } from '../durable-core/ids/index.js';
1
+ import type { SessionId, WorkflowHash, WorkflowId } from '../durable-core/ids/index.js';
2
2
  export type RecapSnippet = string & {
3
3
  readonly __brand: 'RecapSnippet';
4
4
  };
@@ -8,10 +8,16 @@ export interface SessionObservations {
8
8
  readonly gitBranch: string | null;
9
9
  readonly repoRootHash: string | null;
10
10
  }
11
- export interface WorkflowIdentity {
12
- readonly workflowId: string | null;
13
- readonly workflowName: string | null;
14
- }
11
+ export type WorkflowIdentity = {
12
+ readonly kind: 'unknown';
13
+ } | {
14
+ readonly kind: 'identified';
15
+ readonly workflowId: WorkflowId;
16
+ readonly workflowHash: WorkflowHash;
17
+ };
18
+ export type IdentifiedWorkflow = Extract<WorkflowIdentity, {
19
+ kind: 'identified';
20
+ }>;
15
21
  export interface HealthySessionSummary {
16
22
  readonly sessionId: SessionId;
17
23
  readonly runId: string;
@@ -21,7 +27,7 @@ export interface HealthySessionSummary {
21
27
  };
22
28
  readonly recapSnippet: RecapSnippet | null;
23
29
  readonly observations: SessionObservations;
24
- readonly workflow: WorkflowIdentity;
30
+ readonly workflow: IdentifiedWorkflow;
25
31
  }
26
32
  export type WhyMatched = 'matched_head_sha' | 'matched_branch' | 'matched_notes' | 'matched_workflow_id' | 'recency_fallback';
27
33
  export type TierAssignment = {
@@ -57,6 +63,7 @@ export interface RankedResumeCandidate {
57
63
  readonly whyMatched: readonly WhyMatched[];
58
64
  readonly tierAssignment: TierAssignment;
59
65
  readonly lastActivityEventIndex: number;
66
+ readonly workflowHash: WorkflowHash;
60
67
  }
61
68
  export declare const MAX_RESUME_CANDIDATES = 5;
62
69
  export declare function rankResumeCandidates(summaries: readonly HealthySessionSummary[], query: ResumeQuery): readonly RankedResumeCandidate[];
@@ -56,13 +56,9 @@ function assignTier(summary, query) {
56
56
  return { tier: 3, kind: 'matched_notes' };
57
57
  }
58
58
  }
59
- if (query.freeTextQuery) {
59
+ if (query.freeTextQuery && summary.workflow.kind === 'identified') {
60
60
  const queryTokens = normalizeToTokens(query.freeTextQuery);
61
- const workflowText = [
62
- summary.workflow.workflowId ?? '',
63
- summary.workflow.workflowName ?? '',
64
- ].join(' ');
65
- const workflowTokens = normalizeToTokens(workflowText);
61
+ const workflowTokens = normalizeToTokens(String(summary.workflow.workflowId));
66
62
  if (allQueryTokensMatch(queryTokens, workflowTokens)) {
67
63
  return { tier: 4, kind: 'matched_workflow_id' };
68
64
  }
@@ -101,5 +97,6 @@ function rankResumeCandidates(summaries, query) {
101
97
  whyMatched: [tierToWhyMatched(tier)],
102
98
  tierAssignment: tier,
103
99
  lastActivityEventIndex: summary.preferredTip.lastActivityEventIndex,
100
+ workflowHash: summary.workflow.workflowHash,
104
101
  }));
105
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/workrail",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
4
4
  "description": "Step-by-step workflow enforcement for AI agents via MCP",
5
5
  "license": "MIT",
6
6
  "repository": {